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 "io" 19 "strings" 20 "unicode" 21) 22 23const ( 24 indentWidth = 4 25 maxIndentDepth = 2 26 lineWidth = 80 27) 28 29var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth) 30 31type StringWriterWriter interface { 32 io.StringWriter 33 io.Writer 34} 35 36type ninjaWriter struct { 37 writer StringWriterWriter 38 39 justDidBlankLine bool // true if the last operation was a BlankLine 40} 41 42func newNinjaWriter(writer StringWriterWriter) *ninjaWriter { 43 return &ninjaWriter{ 44 writer: writer, 45 } 46} 47 48func (n *ninjaWriter) Comment(comment string) error { 49 n.justDidBlankLine = false 50 51 const lineHeaderLen = len("# ") 52 const maxLineLen = lineWidth - lineHeaderLen 53 54 var lineStart, lastSplitPoint int 55 for i, r := range comment { 56 if unicode.IsSpace(r) { 57 // We know we can safely split the line here. 58 lastSplitPoint = i + 1 59 } 60 61 var line string 62 var writeLine bool 63 switch { 64 case r == '\n': 65 // Output the line without trimming the left so as to allow comments 66 // to contain their own indentation. 67 line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace) 68 writeLine = true 69 70 case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart): 71 // The line has grown too long and is splittable. Split it at the 72 // last split point. 73 line = strings.TrimSpace(comment[lineStart:lastSplitPoint]) 74 writeLine = true 75 } 76 77 if writeLine { 78 line = strings.TrimSpace("# "+line) + "\n" 79 _, err := n.writer.WriteString(line) 80 if err != nil { 81 return err 82 } 83 lineStart = lastSplitPoint 84 } 85 } 86 87 if lineStart != len(comment) { 88 line := strings.TrimSpace(comment[lineStart:]) 89 _, err := n.writer.WriteString("# ") 90 if err != nil { 91 return err 92 } 93 _, err = n.writer.WriteString(line) 94 if err != nil { 95 return err 96 } 97 _, err = n.writer.WriteString("\n") 98 if err != nil { 99 return err 100 } 101 } 102 103 return nil 104} 105 106func (n *ninjaWriter) Pool(name string) error { 107 n.justDidBlankLine = false 108 return n.writeStatement("pool", name) 109} 110 111func (n *ninjaWriter) Rule(name string) error { 112 n.justDidBlankLine = false 113 return n.writeStatement("rule", name) 114} 115 116func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts, 117 explicitDeps, implicitDeps, orderOnlyDeps, validations []*ninjaString, 118 outputStrings, implicitOutStrings, explicitDepStrings, 119 implicitDepStrings, orderOnlyDepStrings, validationStrings []string, 120 nameTracker *nameTracker) error { 121 122 n.justDidBlankLine = false 123 124 const lineWrapLen = len(" $") 125 const maxLineLen = lineWidth - lineWrapLen 126 127 wrapper := &ninjaWriterWithWrap{ 128 ninjaWriter: n, 129 maxLineLen: maxLineLen, 130 } 131 132 if comment != "" { 133 err := wrapper.Comment(comment) 134 if err != nil { 135 return err 136 } 137 } 138 139 wrapper.WriteString("build") 140 141 for _, output := range outputStrings { 142 wrapper.Space() 143 outputEscaper.WriteString(wrapper, output) 144 } 145 for _, output := range outputs { 146 wrapper.Space() 147 output.ValueWithEscaper(wrapper, nameTracker, outputEscaper) 148 } 149 150 if len(implicitOuts) > 0 || len(implicitOutStrings) > 0 { 151 wrapper.WriteStringWithSpace("|") 152 153 for _, out := range implicitOutStrings { 154 wrapper.Space() 155 outputEscaper.WriteString(wrapper, out) 156 } 157 for _, out := range implicitOuts { 158 wrapper.Space() 159 out.ValueWithEscaper(wrapper, nameTracker, outputEscaper) 160 } 161 } 162 163 wrapper.WriteString(":") 164 165 wrapper.WriteStringWithSpace(rule) 166 167 for _, dep := range explicitDepStrings { 168 wrapper.Space() 169 inputEscaper.WriteString(wrapper, dep) 170 } 171 for _, dep := range explicitDeps { 172 wrapper.Space() 173 dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper) 174 } 175 176 if len(implicitDeps) > 0 || len(implicitDepStrings) > 0 { 177 wrapper.WriteStringWithSpace("|") 178 179 for _, dep := range implicitDepStrings { 180 wrapper.Space() 181 inputEscaper.WriteString(wrapper, dep) 182 } 183 for _, dep := range implicitDeps { 184 wrapper.Space() 185 dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper) 186 } 187 } 188 189 if len(orderOnlyDeps) > 0 || len(orderOnlyDepStrings) > 0 { 190 wrapper.WriteStringWithSpace("||") 191 192 for _, dep := range orderOnlyDepStrings { 193 wrapper.Space() 194 inputEscaper.WriteString(wrapper, dep) 195 } 196 for _, dep := range orderOnlyDeps { 197 wrapper.Space() 198 dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper) 199 } 200 } 201 202 if len(validations) > 0 || len(validationStrings) > 0 { 203 wrapper.WriteStringWithSpace("|@") 204 205 for _, dep := range validationStrings { 206 wrapper.Space() 207 inputEscaper.WriteString(wrapper, dep) 208 } 209 for _, dep := range validations { 210 wrapper.Space() 211 dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper) 212 } 213 } 214 215 return wrapper.Flush() 216} 217 218func (n *ninjaWriter) Assign(name, value string) error { 219 n.justDidBlankLine = false 220 _, err := n.writer.WriteString(name) 221 if err != nil { 222 return err 223 } 224 _, err = n.writer.WriteString(" = ") 225 if err != nil { 226 return err 227 } 228 _, err = n.writer.WriteString(value) 229 if err != nil { 230 return err 231 } 232 _, err = n.writer.WriteString("\n") 233 if err != nil { 234 return err 235 } 236 return nil 237} 238 239func (n *ninjaWriter) ScopedAssign(name, value string) error { 240 n.justDidBlankLine = false 241 _, err := n.writer.WriteString(indentString[:indentWidth]) 242 if err != nil { 243 return err 244 } 245 _, err = n.writer.WriteString(name) 246 if err != nil { 247 return err 248 } 249 _, err = n.writer.WriteString(" = ") 250 if err != nil { 251 return err 252 } 253 _, err = n.writer.WriteString(value) 254 if err != nil { 255 return err 256 } 257 _, err = n.writer.WriteString("\n") 258 if err != nil { 259 return err 260 } 261 return nil 262} 263 264func (n *ninjaWriter) Default(nameTracker *nameTracker, targets []*ninjaString, targetStrings []string) error { 265 n.justDidBlankLine = false 266 267 const lineWrapLen = len(" $") 268 const maxLineLen = lineWidth - lineWrapLen 269 270 wrapper := &ninjaWriterWithWrap{ 271 ninjaWriter: n, 272 maxLineLen: maxLineLen, 273 } 274 275 wrapper.WriteString("default") 276 277 for _, target := range targetStrings { 278 wrapper.Space() 279 outputEscaper.WriteString(wrapper, target) 280 } 281 for _, target := range targets { 282 wrapper.Space() 283 target.ValueWithEscaper(wrapper, nameTracker, outputEscaper) 284 } 285 286 return wrapper.Flush() 287} 288 289func (n *ninjaWriter) Subninja(file string) error { 290 n.justDidBlankLine = false 291 return n.writeStatement("subninja", file) 292} 293 294func (n *ninjaWriter) BlankLine() (err error) { 295 // We don't output multiple blank lines in a row. 296 if !n.justDidBlankLine { 297 n.justDidBlankLine = true 298 _, err = n.writer.WriteString("\n") 299 } 300 return err 301} 302 303func (n *ninjaWriter) writeStatement(directive, name string) error { 304 _, err := n.writer.WriteString(directive + " ") 305 if err != nil { 306 return err 307 } 308 _, err = n.writer.WriteString(name) 309 if err != nil { 310 return err 311 } 312 _, err = n.writer.WriteString("\n") 313 if err != nil { 314 return err 315 } 316 return nil 317} 318 319// ninjaWriterWithWrap is an io.StringWriter that writes through to a ninjaWriter, but supports 320// user-readable line wrapping on boundaries when ninjaWriterWithWrap.Space is called. 321// It collects incoming calls to WriteString until either the line length is exceeded, in which case 322// it inserts a wrap before the pending strings and then writes them, or the next call to Space, in 323// which case it writes out the pending strings. 324// 325// WriteString never returns an error, all errors are held until Flush is called. Once an error has 326// occurred all writes become noops. 327type ninjaWriterWithWrap struct { 328 *ninjaWriter 329 // pending lists the strings that have been written since the last call to Space. 330 pending []string 331 332 // pendingLen accumulates the lengths of the strings in pending. 333 pendingLen int 334 335 // lineLen accumulates the number of bytes on the current line. 336 lineLen int 337 338 // maxLineLen is the length of the line before wrapping. 339 maxLineLen int 340 341 // space is true if the strings in pending should be preceded by a space. 342 space bool 343 344 // err holds any error that has occurred to return in Flush. 345 err error 346} 347 348// WriteString writes the string to buffer, wrapping on a previous Space call if necessary. 349// It never returns an error, all errors are held until Flush is called. 350func (n *ninjaWriterWithWrap) WriteString(s string) (written int, noError error) { 351 // Always return the full length of the string and a nil error. 352 // ninjaWriterWithWrap doesn't return errors to the caller, it saves them until Flush() 353 written = len(s) 354 355 if n.err != nil { 356 return 357 } 358 359 const spaceLen = 1 360 if !n.space { 361 // No space is pending, so a line wrap can't be inserted before this, so just write 362 // the string. 363 n.lineLen += len(s) 364 _, n.err = n.writer.WriteString(s) 365 } else if n.lineLen+len(s)+spaceLen > n.maxLineLen { 366 // A space is pending, and the pending strings plus the current string would exceed the 367 // maximum line length. Wrap and indent before the pending space and strings, then write 368 // the pending and current strings. 369 _, n.err = n.writer.WriteString(" $\n") 370 if n.err != nil { 371 return 372 } 373 _, n.err = n.writer.WriteString(indentString[:indentWidth*2]) 374 if n.err != nil { 375 return 376 } 377 n.lineLen = indentWidth*2 + n.pendingLen 378 s = strings.TrimLeftFunc(s, unicode.IsSpace) 379 n.pending = append(n.pending, s) 380 n.lineLen += len(s) 381 n.writePending() 382 383 n.space = false 384 } else { 385 // A space is pending but the current string would not reach the maximum line length, 386 // add it to the pending list. 387 n.pending = append(n.pending, s) 388 n.pendingLen += len(s) 389 n.lineLen += len(s) 390 } 391 392 return 393} 394 395func (n *ninjaWriterWithWrap) Write(p []byte) (written int, noError error) { 396 // Write is rarely used, implement it via WriteString. 397 return n.WriteString(string(p)) 398} 399 400// Space inserts a space that is also a possible wrapping point into the string. 401func (n *ninjaWriterWithWrap) Space() { 402 if n.err != nil { 403 return 404 } 405 if n.space { 406 // A space was already pending, and the space plus any strings written after the space did 407 // not reach the maxmimum line length, so write out the old space and pending strings. 408 _, n.err = n.writer.WriteString(" ") 409 n.lineLen++ 410 n.writePending() 411 } 412 n.space = true 413} 414 415// writePending writes out all the strings stored in pending and resets it. 416func (n *ninjaWriterWithWrap) writePending() { 417 if n.err != nil { 418 return 419 } 420 for _, pending := range n.pending { 421 _, n.err = n.writer.WriteString(pending) 422 if n.err != nil { 423 return 424 } 425 } 426 // Reset the length of pending back to 0 without reducing its capacity to avoid reallocating 427 // the backing array. 428 n.pending = n.pending[:0] 429 n.pendingLen = 0 430} 431 432// WriteStringWithSpace is a helper that calls Space and WriteString. 433func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) { 434 n.Space() 435 _, _ = n.WriteString(s) 436} 437 438// Flush writes out any pending space or strings and then a newline. It also returns any errors 439// that have previously occurred. 440func (n *ninjaWriterWithWrap) Flush() error { 441 if n.space { 442 _, n.err = n.writer.WriteString(" ") 443 } 444 n.writePending() 445 if n.err != nil { 446 return n.err 447 } 448 _, err := n.writer.WriteString("\n") 449 return err 450} 451