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