blob: 7ab0e1553b81f88753c45abdd03cc33313d1c249 [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// 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
Jamie Gennis1bc967e2014-05-27 16:34:41 -070015package blueprint
16
17import (
18 "fmt"
19 "io"
20 "strings"
21 "unicode"
22)
23
24const (
25 indentWidth = 4
26 lineWidth = 80
27)
28
29type ninjaWriter struct {
30 writer io.Writer
31
32 justDidBlankLine bool // true if the last operation was a BlankLine
33}
34
35func newNinjaWriter(writer io.Writer) *ninjaWriter {
36 return &ninjaWriter{
37 writer: writer,
38 }
39}
40
41func (n *ninjaWriter) Comment(comment string) error {
42 n.justDidBlankLine = false
43
44 const lineHeaderLen = len("# ")
45 const maxLineLen = lineWidth - lineHeaderLen
46
47 var lineStart, lastSplitPoint int
48 for i, r := range comment {
49 if unicode.IsSpace(r) {
50 // We know we can safely split the line here.
51 lastSplitPoint = i + 1
52 }
53
54 var line string
55 var writeLine bool
56 switch {
57 case r == '\n':
58 // Output the line without trimming the left so as to allow comments
59 // to contain their own indentation.
60 line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace)
61 writeLine = true
62
63 case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart):
64 // The line has grown too long and is splittable. Split it at the
65 // last split point.
66 line = strings.TrimSpace(comment[lineStart:lastSplitPoint])
67 writeLine = true
68 }
69
70 if writeLine {
71 line = strings.TrimSpace("# "+line) + "\n"
72 _, err := io.WriteString(n.writer, line)
73 if err != nil {
74 return err
75 }
76 lineStart = lastSplitPoint
77 }
78 }
79
80 if lineStart != len(comment) {
81 line := strings.TrimSpace(comment[lineStart:])
82 _, err := fmt.Fprintf(n.writer, "# %s\n", line)
83 if err != nil {
84 return err
85 }
86 }
87
88 return nil
89}
90
91func (n *ninjaWriter) Pool(name string) error {
92 n.justDidBlankLine = false
93 _, err := fmt.Fprintf(n.writer, "pool %s\n", name)
94 return err
95}
96
97func (n *ninjaWriter) Rule(name string) error {
98 n.justDidBlankLine = false
99 _, err := fmt.Fprintf(n.writer, "rule %s\n", name)
100 return err
101}
102
103func (n *ninjaWriter) Build(rule string, outputs, explicitDeps, implicitDeps,
104 orderOnlyDeps []string) error {
105
106 n.justDidBlankLine = false
107
108 const lineWrapLen = len(" $")
109 const maxLineLen = lineWidth - lineWrapLen
110
111 line := "build"
112
113 appendWithWrap := func(s string) (err error) {
114 if len(line)+len(s) > maxLineLen {
115 _, err = fmt.Fprintf(n.writer, "%s $\n", line)
116 line = strings.Repeat(" ", indentWidth*2)
117 s = strings.TrimLeftFunc(s, unicode.IsSpace)
118 }
119 line += s
120 return
121 }
122
123 for _, output := range outputs {
124 err := appendWithWrap(" " + output)
125 if err != nil {
126 return err
127 }
128 }
129
130 err := appendWithWrap(":")
131 if err != nil {
132 return err
133 }
134
135 err = appendWithWrap(" " + rule)
136 if err != nil {
137 return err
138 }
139
140 for _, dep := range explicitDeps {
141 err := appendWithWrap(" " + dep)
142 if err != nil {
143 return err
144 }
145 }
146
147 if len(implicitDeps) > 0 {
148 err := appendWithWrap(" |")
149 if err != nil {
150 return err
151 }
152
153 for _, dep := range implicitDeps {
154 err := appendWithWrap(" " + dep)
155 if err != nil {
156 return err
157 }
158 }
159 }
160
161 if len(orderOnlyDeps) > 0 {
162 err := appendWithWrap(" ||")
163 if err != nil {
164 return err
165 }
166
167 for _, dep := range orderOnlyDeps {
168 err := appendWithWrap(" " + dep)
169 if err != nil {
170 return err
171 }
172 }
173 }
174
175 _, err = fmt.Fprintln(n.writer, line)
176 return err
177}
178
179func (n *ninjaWriter) Assign(name, value string) error {
180 n.justDidBlankLine = false
181 _, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value)
182 return err
183}
184
185func (n *ninjaWriter) ScopedAssign(name, value string) error {
186 n.justDidBlankLine = false
187 indent := strings.Repeat(" ", indentWidth)
188 _, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indent, name, value)
189 return err
190}
191
192func (n *ninjaWriter) Default(targets ...string) error {
193 n.justDidBlankLine = false
194
195 const lineWrapLen = len(" $")
196 const maxLineLen = lineWidth - lineWrapLen
197
198 line := "default"
199
200 appendWithWrap := func(s string) (err error) {
201 if len(line)+len(s) > maxLineLen {
202 _, err = fmt.Fprintf(n.writer, "%s $\n", line)
203 line = strings.Repeat(" ", indentWidth*2)
204 s = strings.TrimLeftFunc(s, unicode.IsSpace)
205 }
206 line += s
207 return
208 }
209
210 for _, target := range targets {
211 err := appendWithWrap(" " + target)
212 if err != nil {
213 return err
214 }
215 }
216
217 _, err := fmt.Fprintln(n.writer, line)
218 return err
219}
220
221func (n *ninjaWriter) BlankLine() (err error) {
222 // We don't output multiple blank lines in a row.
223 if !n.justDidBlankLine {
224 n.justDidBlankLine = true
225 _, err = io.WriteString(n.writer, "\n")
226 }
227 return err
228}
229
230func writeAssignments(w io.Writer, indent int, assignments ...string) error {
231 var maxNameLen int
232 for i := 0; i < len(assignments); i += 2 {
233 name := assignments[i]
234 err := validateNinjaName(name)
235 if err != nil {
236 return err
237 }
238 if maxNameLen < len(name) {
239 maxNameLen = len(name)
240 }
241 }
242
243 indentStr := strings.Repeat(" ", indent*indentWidth)
244 extraIndentStr := strings.Repeat(" ", (indent+1)*indentWidth)
245 replacer := strings.NewReplacer("\n", "$\n"+extraIndentStr)
246
247 for i := 0; i < len(assignments); i += 2 {
248 name := assignments[i]
249 value := replacer.Replace(assignments[i+1])
250 _, err := fmt.Fprintf(w, "%s% *s = %s\n", indentStr, maxNameLen, name,
251 value)
252 if err != nil {
253 return err
254 }
255 }
256
257 return nil
258}