blob: 7ab0e1553b81f88753c45abdd03cc33313d1c249 [file] [log] [blame]
// 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
}