• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15package blueprint
16
17import (
18	"fmt"
19	"io"
20	"strings"
21	"unicode"
22)
23
24const (
25	indentWidth    = 4
26	maxIndentDepth = 2
27	lineWidth      = 80
28)
29
30var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth)
31
32type ninjaWriter struct {
33	writer io.Writer
34
35	justDidBlankLine bool // true if the last operation was a BlankLine
36}
37
38func newNinjaWriter(writer io.Writer) *ninjaWriter {
39	return &ninjaWriter{
40		writer: writer,
41	}
42}
43
44func (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
94func (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
100func (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
106func (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
164func (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
170func (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
176func (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
196func (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
205type ninjaWriterWithWrap struct {
206	*ninjaWriter
207	maxLineLen int
208	writtenLen int
209	err        error
210}
211
212func (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
242func (n *ninjaWriterWithWrap) WriteString(s string) {
243	n.writeString(s, false)
244}
245
246func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
247	n.writeString(s, true)
248}
249
250func (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