You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
10 KiB
415 lines
10 KiB
// 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 (
|
|
"io"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
indentWidth = 4
|
|
maxIndentDepth = 2
|
|
lineWidth = 80
|
|
)
|
|
|
|
var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth)
|
|
|
|
type StringWriterWriter interface {
|
|
io.StringWriter
|
|
io.Writer
|
|
}
|
|
|
|
type ninjaWriter struct {
|
|
writer io.StringWriter
|
|
|
|
justDidBlankLine bool // true if the last operation was a BlankLine
|
|
}
|
|
|
|
func newNinjaWriter(writer io.StringWriter) *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 := n.writer.WriteString(line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lineStart = lastSplitPoint
|
|
}
|
|
}
|
|
|
|
if lineStart != len(comment) {
|
|
line := strings.TrimSpace(comment[lineStart:])
|
|
_, err := n.writer.WriteString("# ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString("\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (n *ninjaWriter) Pool(name string) error {
|
|
n.justDidBlankLine = false
|
|
return n.writeStatement("pool", name)
|
|
}
|
|
|
|
func (n *ninjaWriter) Rule(name string) error {
|
|
n.justDidBlankLine = false
|
|
return n.writeStatement("rule", name)
|
|
}
|
|
|
|
func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
|
|
explicitDeps, implicitDeps, orderOnlyDeps, validations []ninjaString,
|
|
pkgNames map[*packageContext]string) error {
|
|
|
|
n.justDidBlankLine = false
|
|
|
|
const lineWrapLen = len(" $")
|
|
const maxLineLen = lineWidth - lineWrapLen
|
|
|
|
wrapper := &ninjaWriterWithWrap{
|
|
ninjaWriter: n,
|
|
maxLineLen: maxLineLen,
|
|
}
|
|
|
|
if comment != "" {
|
|
err := wrapper.Comment(comment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
wrapper.WriteString("build")
|
|
|
|
for _, output := range outputs {
|
|
wrapper.Space()
|
|
output.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
|
|
}
|
|
|
|
if len(implicitOuts) > 0 {
|
|
wrapper.WriteStringWithSpace("|")
|
|
|
|
for _, out := range implicitOuts {
|
|
wrapper.Space()
|
|
out.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
|
|
}
|
|
}
|
|
|
|
wrapper.WriteString(":")
|
|
|
|
wrapper.WriteStringWithSpace(rule)
|
|
|
|
for _, dep := range explicitDeps {
|
|
wrapper.Space()
|
|
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
|
|
}
|
|
|
|
if len(implicitDeps) > 0 {
|
|
wrapper.WriteStringWithSpace("|")
|
|
|
|
for _, dep := range implicitDeps {
|
|
wrapper.Space()
|
|
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
|
|
}
|
|
}
|
|
|
|
if len(orderOnlyDeps) > 0 {
|
|
wrapper.WriteStringWithSpace("||")
|
|
|
|
for _, dep := range orderOnlyDeps {
|
|
wrapper.Space()
|
|
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
|
|
}
|
|
}
|
|
|
|
if len(validations) > 0 {
|
|
wrapper.WriteStringWithSpace("|@")
|
|
|
|
for _, dep := range validations {
|
|
wrapper.Space()
|
|
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
|
|
}
|
|
}
|
|
|
|
return wrapper.Flush()
|
|
}
|
|
|
|
func (n *ninjaWriter) Assign(name, value string) error {
|
|
n.justDidBlankLine = false
|
|
_, err := n.writer.WriteString(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(" = ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString("\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *ninjaWriter) ScopedAssign(name, value string) error {
|
|
n.justDidBlankLine = false
|
|
_, err := n.writer.WriteString(indentString[:indentWidth])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(" = ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString("\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets ...ninjaString) 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.Space()
|
|
target.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
|
|
}
|
|
|
|
return wrapper.Flush()
|
|
}
|
|
|
|
func (n *ninjaWriter) Subninja(file string) error {
|
|
n.justDidBlankLine = false
|
|
return n.writeStatement("subninja", file)
|
|
}
|
|
|
|
func (n *ninjaWriter) BlankLine() (err error) {
|
|
// We don't output multiple blank lines in a row.
|
|
if !n.justDidBlankLine {
|
|
n.justDidBlankLine = true
|
|
_, err = n.writer.WriteString("\n")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (n *ninjaWriter) writeStatement(directive, name string) error {
|
|
_, err := n.writer.WriteString(directive + " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = n.writer.WriteString("\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ninjaWriterWithWrap is an io.StringWriter that writes through to a ninjaWriter, but supports
|
|
// user-readable line wrapping on boundaries when ninjaWriterWithWrap.Space is called.
|
|
// It collects incoming calls to WriteString until either the line length is exceeded, in which case
|
|
// it inserts a wrap before the pending strings and then writes them, or the next call to Space, in
|
|
// which case it writes out the pending strings.
|
|
//
|
|
// WriteString never returns an error, all errors are held until Flush is called. Once an error has
|
|
// occurred all writes become noops.
|
|
type ninjaWriterWithWrap struct {
|
|
*ninjaWriter
|
|
// pending lists the strings that have been written since the last call to Space.
|
|
pending []string
|
|
|
|
// pendingLen accumulates the lengths of the strings in pending.
|
|
pendingLen int
|
|
|
|
// lineLen accumulates the number of bytes on the current line.
|
|
lineLen int
|
|
|
|
// maxLineLen is the length of the line before wrapping.
|
|
maxLineLen int
|
|
|
|
// space is true if the strings in pending should be preceded by a space.
|
|
space bool
|
|
|
|
// err holds any error that has occurred to return in Flush.
|
|
err error
|
|
}
|
|
|
|
// WriteString writes the string to buffer, wrapping on a previous Space call if necessary.
|
|
// It never returns an error, all errors are held until Flush is called.
|
|
func (n *ninjaWriterWithWrap) WriteString(s string) (written int, noError error) {
|
|
// Always return the full length of the string and a nil error.
|
|
// ninjaWriterWithWrap doesn't return errors to the caller, it saves them until Flush()
|
|
written = len(s)
|
|
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
|
|
const spaceLen = 1
|
|
if !n.space {
|
|
// No space is pending, so a line wrap can't be inserted before this, so just write
|
|
// the string.
|
|
n.lineLen += len(s)
|
|
_, n.err = n.writer.WriteString(s)
|
|
} else if n.lineLen+len(s)+spaceLen > n.maxLineLen {
|
|
// A space is pending, and the pending strings plus the current string would exceed the
|
|
// maximum line length. Wrap and indent before the pending space and strings, then write
|
|
// the pending and current strings.
|
|
_, n.err = n.writer.WriteString(" $\n")
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
_, n.err = n.writer.WriteString(indentString[:indentWidth*2])
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
n.lineLen = indentWidth*2 + n.pendingLen
|
|
s = strings.TrimLeftFunc(s, unicode.IsSpace)
|
|
n.pending = append(n.pending, s)
|
|
n.writePending()
|
|
|
|
n.space = false
|
|
} else {
|
|
// A space is pending but the current string would not reach the maximum line length,
|
|
// add it to the pending list.
|
|
n.pending = append(n.pending, s)
|
|
n.pendingLen += len(s)
|
|
n.lineLen += len(s)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Space inserts a space that is also a possible wrapping point into the string.
|
|
func (n *ninjaWriterWithWrap) Space() {
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
if n.space {
|
|
// A space was already pending, and the space plus any strings written after the space did
|
|
// not reach the maxmimum line length, so write out the old space and pending strings.
|
|
_, n.err = n.writer.WriteString(" ")
|
|
n.lineLen++
|
|
n.writePending()
|
|
}
|
|
n.space = true
|
|
}
|
|
|
|
// writePending writes out all the strings stored in pending and resets it.
|
|
func (n *ninjaWriterWithWrap) writePending() {
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
for _, pending := range n.pending {
|
|
_, n.err = n.writer.WriteString(pending)
|
|
if n.err != nil {
|
|
return
|
|
}
|
|
}
|
|
// Reset the length of pending back to 0 without reducing its capacity to avoid reallocating
|
|
// the backing array.
|
|
n.pending = n.pending[:0]
|
|
n.pendingLen = 0
|
|
}
|
|
|
|
// WriteStringWithSpace is a helper that calls Space and WriteString.
|
|
func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
|
|
n.Space()
|
|
_, _ = n.WriteString(s)
|
|
}
|
|
|
|
// Flush writes out any pending space or strings and then a newline. It also returns any errors
|
|
// that have previously occurred.
|
|
func (n *ninjaWriterWithWrap) Flush() error {
|
|
if n.space {
|
|
_, n.err = n.writer.WriteString(" ")
|
|
}
|
|
n.writePending()
|
|
if n.err != nil {
|
|
return n.err
|
|
}
|
|
_, err := n.writer.WriteString("\n")
|
|
return err
|
|
}
|