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, implicitOuts,
    107 	explicitDeps, implicitDeps, 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 	if len(implicitOuts) > 0 {
    130 		wrapper.WriteStringWithSpace("|")
    131 
    132 		for _, out := range implicitOuts {
    133 			wrapper.WriteStringWithSpace(out)
    134 		}
    135 	}
    136 
    137 	wrapper.WriteString(":")
    138 
    139 	wrapper.WriteStringWithSpace(rule)
    140 
    141 	for _, dep := range explicitDeps {
    142 		wrapper.WriteStringWithSpace(dep)
    143 	}
    144 
    145 	if len(implicitDeps) > 0 {
    146 		wrapper.WriteStringWithSpace("|")
    147 
    148 		for _, dep := range implicitDeps {
    149 			wrapper.WriteStringWithSpace(dep)
    150 		}
    151 	}
    152 
    153 	if len(orderOnlyDeps) > 0 {
    154 		wrapper.WriteStringWithSpace("||")
    155 
    156 		for _, dep := range orderOnlyDeps {
    157 			wrapper.WriteStringWithSpace(dep)
    158 		}
    159 	}
    160 
    161 	return wrapper.Flush()
    162 }
    163 
    164 func (n *ninjaWriter) Assign(name, value string) error {
    165 	n.justDidBlankLine = false
    166 	_, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value)
    167 	return err
    168 }
    169 
    170 func (n *ninjaWriter) ScopedAssign(name, value string) error {
    171 	n.justDidBlankLine = false
    172 	_, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indentString[:indentWidth], name, value)
    173 	return err
    174 }
    175 
    176 func (n *ninjaWriter) Default(targets ...string) error {
    177 	n.justDidBlankLine = false
    178 
    179 	const lineWrapLen = len(" $")
    180 	const maxLineLen = lineWidth - lineWrapLen
    181 
    182 	wrapper := ninjaWriterWithWrap{
    183 		ninjaWriter: n,
    184 		maxLineLen:  maxLineLen,
    185 	}
    186 
    187 	wrapper.WriteString("default")
    188 
    189 	for _, target := range targets {
    190 		wrapper.WriteString(" " + target)
    191 	}
    192 
    193 	return wrapper.Flush()
    194 }
    195 
    196 func (n *ninjaWriter) BlankLine() (err error) {
    197 	// We don't output multiple blank lines in a row.
    198 	if !n.justDidBlankLine {
    199 		n.justDidBlankLine = true
    200 		_, err = io.WriteString(n.writer, "\n")
    201 	}
    202 	return err
    203 }
    204 
    205 type ninjaWriterWithWrap struct {
    206 	*ninjaWriter
    207 	maxLineLen int
    208 	writtenLen int
    209 	err        error
    210 }
    211 
    212 func (n *ninjaWriterWithWrap) writeString(s string, space bool) {
    213 	if n.err != nil {
    214 		return
    215 	}
    216 
    217 	spaceLen := 0
    218 	if space {
    219 		spaceLen = 1
    220 	}
    221 
    222 	if n.writtenLen+len(s)+spaceLen > n.maxLineLen {
    223 		_, n.err = io.WriteString(n.writer, " $\n")
    224 		if n.err != nil {
    225 			return
    226 		}
    227 		_, n.err = io.WriteString(n.writer, indentString[:indentWidth*2])
    228 		if n.err != nil {
    229 			return
    230 		}
    231 		n.writtenLen = indentWidth * 2
    232 		s = strings.TrimLeftFunc(s, unicode.IsSpace)
    233 	} else if space {
    234 		io.WriteString(n.writer, " ")
    235 		n.writtenLen++
    236 	}
    237 
    238 	_, n.err = io.WriteString(n.writer, s)
    239 	n.writtenLen += len(s)
    240 }
    241 
    242 func (n *ninjaWriterWithWrap) WriteString(s string) {
    243 	n.writeString(s, false)
    244 }
    245 
    246 func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
    247 	n.writeString(s, true)
    248 }
    249 
    250 func (n *ninjaWriterWithWrap) Flush() error {
    251 	if n.err != nil {
    252 		return n.err
    253 	}
    254 	_, err := io.WriteString(n.writer, "\n")
    255 	return err
    256 }
    257