Home | History | Annotate | Download | only in blueprint
      1 // Copyright 2014 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package blueprint
     16 
     17 import (
     18 	"fmt"
     19 	"io"
     20 	"strings"
     21 	"unicode"
     22 )
     23 
     24 const (
     25 	indentWidth    = 4
     26 	maxIndentDepth = 2
     27 	lineWidth      = 80
     28 )
     29 
     30 var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth)
     31 
     32 type ninjaWriter struct {
     33 	writer io.Writer
     34 
     35 	justDidBlankLine bool // true if the last operation was a BlankLine
     36 }
     37 
     38 func newNinjaWriter(writer io.Writer) *ninjaWriter {
     39 	return &ninjaWriter{
     40 		writer: writer,
     41 	}
     42 }
     43 
     44 func (n *ninjaWriter) Comment(comment string) error {
     45 	n.justDidBlankLine = false
     46 
     47 	const lineHeaderLen = len("# ")
     48 	const maxLineLen = lineWidth - lineHeaderLen
     49 
     50 	var lineStart, lastSplitPoint int
     51 	for i, r := range comment {
     52 		if unicode.IsSpace(r) {
     53 			// We know we can safely split the line here.
     54 			lastSplitPoint = i + 1
     55 		}
     56 
     57 		var line string
     58 		var writeLine bool
     59 		switch {
     60 		case r == '\n':
     61 			// Output the line without trimming the left so as to allow comments
     62 			// to contain their own indentation.
     63 			line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace)
     64 			writeLine = true
     65 
     66 		case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart):
     67 			// The line has grown too long and is splittable.  Split it at the
     68 			// last split point.
     69 			line = strings.TrimSpace(comment[lineStart:lastSplitPoint])
     70 			writeLine = true
     71 		}
     72 
     73 		if writeLine {
     74 			line = strings.TrimSpace("# "+line) + "\n"
     75 			_, err := io.WriteString(n.writer, line)
     76 			if err != nil {
     77 				return err
     78 			}
     79 			lineStart = lastSplitPoint
     80 		}
     81 	}
     82 
     83 	if lineStart != len(comment) {
     84 		line := strings.TrimSpace(comment[lineStart:])
     85 		_, err := fmt.Fprintf(n.writer, "# %s\n", line)
     86 		if err != nil {
     87 			return err
     88 		}
     89 	}
     90 
     91 	return nil
     92 }
     93 
     94 func (n *ninjaWriter) Pool(name string) error {
     95 	n.justDidBlankLine = false
     96 	_, err := fmt.Fprintf(n.writer, "pool %s\n", name)
     97 	return err
     98 }
     99 
    100 func (n *ninjaWriter) Rule(name string) error {
    101 	n.justDidBlankLine = false
    102 	_, err := fmt.Fprintf(n.writer, "rule %s\n", name)
    103 	return err
    104 }
    105 
    106 func (n *ninjaWriter) Build(comment string, rule string, outputs, explicitDeps, implicitDeps,
    107 	orderOnlyDeps []string) error {
    108 
    109 	n.justDidBlankLine = false
    110 
    111 	const lineWrapLen = len(" $")
    112 	const maxLineLen = lineWidth - lineWrapLen
    113 
    114 	wrapper := ninjaWriterWithWrap{
    115 		ninjaWriter: n,
    116 		maxLineLen:  maxLineLen,
    117 	}
    118 
    119 	if comment != "" {
    120 		wrapper.Comment(comment)
    121 	}
    122 
    123 	wrapper.WriteString("build")
    124 
    125 	for _, output := range outputs {
    126 		wrapper.WriteStringWithSpace(output)
    127 	}
    128 
    129 	wrapper.WriteString(":")
    130 
    131 	wrapper.WriteStringWithSpace(rule)
    132 
    133 	for _, dep := range explicitDeps {
    134 		wrapper.WriteStringWithSpace(dep)
    135 	}
    136 
    137 	if len(implicitDeps) > 0 {
    138 		wrapper.WriteStringWithSpace("|")
    139 
    140 		for _, dep := range implicitDeps {
    141 			wrapper.WriteStringWithSpace(dep)
    142 		}
    143 	}
    144 
    145 	if len(orderOnlyDeps) > 0 {
    146 		wrapper.WriteStringWithSpace("||")
    147 
    148 		for _, dep := range orderOnlyDeps {
    149 			wrapper.WriteStringWithSpace(dep)
    150 		}
    151 	}
    152 
    153 	return wrapper.Flush()
    154 }
    155 
    156 func (n *ninjaWriter) Assign(name, value string) error {
    157 	n.justDidBlankLine = false
    158 	_, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value)
    159 	return err
    160 }
    161 
    162 func (n *ninjaWriter) ScopedAssign(name, value string) error {
    163 	n.justDidBlankLine = false
    164 	_, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indentString[:indentWidth], name, value)
    165 	return err
    166 }
    167 
    168 func (n *ninjaWriter) Default(targets ...string) error {
    169 	n.justDidBlankLine = false
    170 
    171 	const lineWrapLen = len(" $")
    172 	const maxLineLen = lineWidth - lineWrapLen
    173 
    174 	wrapper := ninjaWriterWithWrap{
    175 		ninjaWriter: n,
    176 		maxLineLen:  maxLineLen,
    177 	}
    178 
    179 	wrapper.WriteString("default")
    180 
    181 	for _, target := range targets {
    182 		wrapper.WriteString(" " + target)
    183 	}
    184 
    185 	return wrapper.Flush()
    186 }
    187 
    188 func (n *ninjaWriter) BlankLine() (err error) {
    189 	// We don't output multiple blank lines in a row.
    190 	if !n.justDidBlankLine {
    191 		n.justDidBlankLine = true
    192 		_, err = io.WriteString(n.writer, "\n")
    193 	}
    194 	return err
    195 }
    196 
    197 type ninjaWriterWithWrap struct {
    198 	*ninjaWriter
    199 	maxLineLen int
    200 	writtenLen int
    201 	err        error
    202 }
    203 
    204 func (n *ninjaWriterWithWrap) writeString(s string, space bool) {
    205 	if n.err != nil {
    206 		return
    207 	}
    208 
    209 	spaceLen := 0
    210 	if space {
    211 		spaceLen = 1
    212 	}
    213 
    214 	if n.writtenLen+len(s)+spaceLen > n.maxLineLen {
    215 		_, n.err = io.WriteString(n.writer, " $\n")
    216 		if n.err != nil {
    217 			return
    218 		}
    219 		_, n.err = io.WriteString(n.writer, indentString[:indentWidth*2])
    220 		if n.err != nil {
    221 			return
    222 		}
    223 		n.writtenLen = indentWidth * 2
    224 		s = strings.TrimLeftFunc(s, unicode.IsSpace)
    225 	} else if space {
    226 		io.WriteString(n.writer, " ")
    227 		n.writtenLen++
    228 	}
    229 
    230 	_, n.err = io.WriteString(n.writer, s)
    231 	n.writtenLen += len(s)
    232 }
    233 
    234 func (n *ninjaWriterWithWrap) WriteString(s string) {
    235 	n.writeString(s, false)
    236 }
    237 
    238 func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
    239 	n.writeString(s, true)
    240 }
    241 
    242 func (n *ninjaWriterWithWrap) Flush() error {
    243 	if n.err != nil {
    244 		return n.err
    245 	}
    246 	_, err := io.WriteString(n.writer, "\n")
    247 	return err
    248 }
    249