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