| // Copyright 2014 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package blueprint |
| |
| import ( |
| "fmt" |
| "io" |
| "strings" |
| "unicode" |
| ) |
| |
| const ( |
| indentWidth = 4 |
| maxIndentDepth = 2 |
| lineWidth = 80 |
| ) |
| |
| var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth) |
| |
| type ninjaWriter struct { |
| writer io.Writer |
| |
| justDidBlankLine bool // true if the last operation was a BlankLine |
| } |
| |
| func newNinjaWriter(writer io.Writer) *ninjaWriter { |
| return &ninjaWriter{ |
| writer: writer, |
| } |
| } |
| |
| func (n *ninjaWriter) Comment(comment string) error { |
| n.justDidBlankLine = false |
| |
| const lineHeaderLen = len("# ") |
| const maxLineLen = lineWidth - lineHeaderLen |
| |
| var lineStart, lastSplitPoint int |
| for i, r := range comment { |
| if unicode.IsSpace(r) { |
| // We know we can safely split the line here. |
| lastSplitPoint = i + 1 |
| } |
| |
| var line string |
| var writeLine bool |
| switch { |
| case r == '\n': |
| // Output the line without trimming the left so as to allow comments |
| // to contain their own indentation. |
| line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace) |
| writeLine = true |
| |
| case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart): |
| // The line has grown too long and is splittable. Split it at the |
| // last split point. |
| line = strings.TrimSpace(comment[lineStart:lastSplitPoint]) |
| writeLine = true |
| } |
| |
| if writeLine { |
| line = strings.TrimSpace("# "+line) + "\n" |
| _, err := io.WriteString(n.writer, line) |
| if err != nil { |
| return err |
| } |
| lineStart = lastSplitPoint |
| } |
| } |
| |
| if lineStart != len(comment) { |
| line := strings.TrimSpace(comment[lineStart:]) |
| _, err := fmt.Fprintf(n.writer, "# %s\n", line) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (n *ninjaWriter) Pool(name string) error { |
| n.justDidBlankLine = false |
| _, err := fmt.Fprintf(n.writer, "pool %s\n", name) |
| return err |
| } |
| |
| func (n *ninjaWriter) Rule(name string) error { |
| n.justDidBlankLine = false |
| _, err := fmt.Fprintf(n.writer, "rule %s\n", name) |
| return err |
| } |
| |
| func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts, |
| explicitDeps, implicitDeps, orderOnlyDeps []string) error { |
| |
| n.justDidBlankLine = false |
| |
| const lineWrapLen = len(" $") |
| const maxLineLen = lineWidth - lineWrapLen |
| |
| wrapper := ninjaWriterWithWrap{ |
| ninjaWriter: n, |
| maxLineLen: maxLineLen, |
| } |
| |
| if comment != "" { |
| wrapper.Comment(comment) |
| } |
| |
| wrapper.WriteString("build") |
| |
| for _, output := range outputs { |
| wrapper.WriteStringWithSpace(output) |
| } |
| |
| if len(implicitOuts) > 0 { |
| wrapper.WriteStringWithSpace("|") |
| |
| for _, out := range implicitOuts { |
| wrapper.WriteStringWithSpace(out) |
| } |
| } |
| |
| wrapper.WriteString(":") |
| |
| wrapper.WriteStringWithSpace(rule) |
| |
| for _, dep := range explicitDeps { |
| wrapper.WriteStringWithSpace(dep) |
| } |
| |
| if len(implicitDeps) > 0 { |
| wrapper.WriteStringWithSpace("|") |
| |
| for _, dep := range implicitDeps { |
| wrapper.WriteStringWithSpace(dep) |
| } |
| } |
| |
| if len(orderOnlyDeps) > 0 { |
| wrapper.WriteStringWithSpace("||") |
| |
| for _, dep := range orderOnlyDeps { |
| wrapper.WriteStringWithSpace(dep) |
| } |
| } |
| |
| return wrapper.Flush() |
| } |
| |
| func (n *ninjaWriter) Assign(name, value string) error { |
| n.justDidBlankLine = false |
| _, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value) |
| return err |
| } |
| |
| func (n *ninjaWriter) ScopedAssign(name, value string) error { |
| n.justDidBlankLine = false |
| _, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indentString[:indentWidth], name, value) |
| return err |
| } |
| |
| func (n *ninjaWriter) Default(targets ...string) error { |
| n.justDidBlankLine = false |
| |
| const lineWrapLen = len(" $") |
| const maxLineLen = lineWidth - lineWrapLen |
| |
| wrapper := ninjaWriterWithWrap{ |
| ninjaWriter: n, |
| maxLineLen: maxLineLen, |
| } |
| |
| wrapper.WriteString("default") |
| |
| for _, target := range targets { |
| wrapper.WriteString(" " + target) |
| } |
| |
| return wrapper.Flush() |
| } |
| |
| func (n *ninjaWriter) Subninja(file string) error { |
| n.justDidBlankLine = false |
| _, err := fmt.Fprintf(n.writer, "subninja %s\n", file) |
| return err |
| } |
| |
| func (n *ninjaWriter) BlankLine() (err error) { |
| // We don't output multiple blank lines in a row. |
| if !n.justDidBlankLine { |
| n.justDidBlankLine = true |
| _, err = io.WriteString(n.writer, "\n") |
| } |
| return err |
| } |
| |
| type ninjaWriterWithWrap struct { |
| *ninjaWriter |
| maxLineLen int |
| writtenLen int |
| err error |
| } |
| |
| func (n *ninjaWriterWithWrap) writeString(s string, space bool) { |
| if n.err != nil { |
| return |
| } |
| |
| spaceLen := 0 |
| if space { |
| spaceLen = 1 |
| } |
| |
| if n.writtenLen+len(s)+spaceLen > n.maxLineLen { |
| _, n.err = io.WriteString(n.writer, " $\n") |
| if n.err != nil { |
| return |
| } |
| _, n.err = io.WriteString(n.writer, indentString[:indentWidth*2]) |
| if n.err != nil { |
| return |
| } |
| n.writtenLen = indentWidth * 2 |
| s = strings.TrimLeftFunc(s, unicode.IsSpace) |
| } else if space { |
| io.WriteString(n.writer, " ") |
| n.writtenLen++ |
| } |
| |
| _, n.err = io.WriteString(n.writer, s) |
| n.writtenLen += len(s) |
| } |
| |
| func (n *ninjaWriterWithWrap) WriteString(s string) { |
| n.writeString(s, false) |
| } |
| |
| func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) { |
| n.writeString(s, true) |
| } |
| |
| func (n *ninjaWriterWithWrap) Flush() error { |
| if n.err != nil { |
| return n.err |
| } |
| _, err := io.WriteString(n.writer, "\n") |
| return err |
| } |