| // 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 |
| lineWidth = 80 |
| ) |
| |
| 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(rule string, outputs, explicitDeps, implicitDeps, |
| orderOnlyDeps []string) error { |
| |
| n.justDidBlankLine = false |
| |
| const lineWrapLen = len(" $") |
| const maxLineLen = lineWidth - lineWrapLen |
| |
| line := "build" |
| |
| appendWithWrap := func(s string) (err error) { |
| if len(line)+len(s) > maxLineLen { |
| _, err = fmt.Fprintf(n.writer, "%s $\n", line) |
| line = strings.Repeat(" ", indentWidth*2) |
| s = strings.TrimLeftFunc(s, unicode.IsSpace) |
| } |
| line += s |
| return |
| } |
| |
| for _, output := range outputs { |
| err := appendWithWrap(" " + output) |
| if err != nil { |
| return err |
| } |
| } |
| |
| err := appendWithWrap(":") |
| if err != nil { |
| return err |
| } |
| |
| err = appendWithWrap(" " + rule) |
| if err != nil { |
| return err |
| } |
| |
| for _, dep := range explicitDeps { |
| err := appendWithWrap(" " + dep) |
| if err != nil { |
| return err |
| } |
| } |
| |
| if len(implicitDeps) > 0 { |
| err := appendWithWrap(" |") |
| if err != nil { |
| return err |
| } |
| |
| for _, dep := range implicitDeps { |
| err := appendWithWrap(" " + dep) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| |
| if len(orderOnlyDeps) > 0 { |
| err := appendWithWrap(" ||") |
| if err != nil { |
| return err |
| } |
| |
| for _, dep := range orderOnlyDeps { |
| err := appendWithWrap(" " + dep) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| |
| _, err = fmt.Fprintln(n.writer, line) |
| return err |
| } |
| |
| 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 |
| indent := strings.Repeat(" ", indentWidth) |
| _, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indent, name, value) |
| return err |
| } |
| |
| func (n *ninjaWriter) Default(targets ...string) error { |
| n.justDidBlankLine = false |
| |
| const lineWrapLen = len(" $") |
| const maxLineLen = lineWidth - lineWrapLen |
| |
| line := "default" |
| |
| appendWithWrap := func(s string) (err error) { |
| if len(line)+len(s) > maxLineLen { |
| _, err = fmt.Fprintf(n.writer, "%s $\n", line) |
| line = strings.Repeat(" ", indentWidth*2) |
| s = strings.TrimLeftFunc(s, unicode.IsSpace) |
| } |
| line += s |
| return |
| } |
| |
| for _, target := range targets { |
| err := appendWithWrap(" " + target) |
| if err != nil { |
| return err |
| } |
| } |
| |
| _, err := fmt.Fprintln(n.writer, line) |
| 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 |
| } |
| |
| func writeAssignments(w io.Writer, indent int, assignments ...string) error { |
| var maxNameLen int |
| for i := 0; i < len(assignments); i += 2 { |
| name := assignments[i] |
| err := validateNinjaName(name) |
| if err != nil { |
| return err |
| } |
| if maxNameLen < len(name) { |
| maxNameLen = len(name) |
| } |
| } |
| |
| indentStr := strings.Repeat(" ", indent*indentWidth) |
| extraIndentStr := strings.Repeat(" ", (indent+1)*indentWidth) |
| replacer := strings.NewReplacer("\n", "$\n"+extraIndentStr) |
| |
| for i := 0; i < len(assignments); i += 2 { |
| name := assignments[i] |
| value := replacer.Replace(assignments[i+1]) |
| _, err := fmt.Fprintf(w, "%s% *s = %s\n", indentStr, maxNameLen, name, |
| value) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |