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 io.StringWriter 38 39 justDidBlankLine bool // true if the last operation was a BlankLine 40} 41 42func newNinjaWriter(writer io.StringWriter) *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 pkgNames map[*packageContext]string) error { 119 120 n.justDidBlankLine = false 121 122 const lineWrapLen = len(" $") 123 const maxLineLen = lineWidth - lineWrapLen 124 125 wrapper := &ninjaWriterWithWrap{ 126 ninjaWriter: n, 127 maxLineLen: maxLineLen, 128 } 129 130 if comment != "" { 131 err := wrapper.Comment(comment) 132 if err != nil { 133 return err 134 } 135 } 136 137 wrapper.WriteString("build") 138 139 for _, output := range outputs { 140 wrapper.Space() 141 output.ValueWithEscaper(wrapper, pkgNames, outputEscaper) 142 } 143 144 if len(implicitOuts) > 0 { 145 wrapper.WriteStringWithSpace("|") 146 147 for _, out := range implicitOuts { 148 wrapper.Space() 149 out.ValueWithEscaper(wrapper, pkgNames, outputEscaper) 150 } 151 } 152 153 wrapper.WriteString(":") 154 155 wrapper.WriteStringWithSpace(rule) 156 157 for _, dep := range explicitDeps { 158 wrapper.Space() 159 dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) 160 } 161 162 if len(implicitDeps) > 0 { 163 wrapper.WriteStringWithSpace("|") 164 165 for _, dep := range implicitDeps { 166 wrapper.Space() 167 dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) 168 } 169 } 170 171 if len(orderOnlyDeps) > 0 { 172 wrapper.WriteStringWithSpace("||") 173 174 for _, dep := range orderOnlyDeps { 175 wrapper.Space() 176 dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) 177 } 178 } 179 180 if len(validations) > 0 { 181 wrapper.WriteStringWithSpace("|@") 182 183 for _, dep := range validations { 184 wrapper.Space() 185 dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) 186 } 187 } 188 189 return wrapper.Flush() 190} 191 192func (n *ninjaWriter) Assign(name, value string) error { 193 n.justDidBlankLine = false 194 _, err := n.writer.WriteString(name) 195 if err != nil { 196 return err 197 } 198 _, err = n.writer.WriteString(" = ") 199 if err != nil { 200 return err 201 } 202 _, err = n.writer.WriteString(value) 203 if err != nil { 204 return err 205 } 206 _, err = n.writer.WriteString("\n") 207 if err != nil { 208 return err 209 } 210 return nil 211} 212 213func (n *ninjaWriter) ScopedAssign(name, value string) error { 214 n.justDidBlankLine = false 215 _, err := n.writer.WriteString(indentString[:indentWidth]) 216 if err != nil { 217 return err 218 } 219 _, err = n.writer.WriteString(name) 220 if err != nil { 221 return err 222 } 223 _, err = n.writer.WriteString(" = ") 224 if err != nil { 225 return err 226 } 227 _, err = n.writer.WriteString(value) 228 if err != nil { 229 return err 230 } 231 _, err = n.writer.WriteString("\n") 232 if err != nil { 233 return err 234 } 235 return nil 236} 237 238func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets ...ninjaString) error { 239 n.justDidBlankLine = false 240 241 const lineWrapLen = len(" $") 242 const maxLineLen = lineWidth - lineWrapLen 243 244 wrapper := &ninjaWriterWithWrap{ 245 ninjaWriter: n, 246 maxLineLen: maxLineLen, 247 } 248 249 wrapper.WriteString("default") 250 251 for _, target := range targets { 252 wrapper.Space() 253 target.ValueWithEscaper(wrapper, pkgNames, outputEscaper) 254 } 255 256 return wrapper.Flush() 257} 258 259func (n *ninjaWriter) Subninja(file string) error { 260 n.justDidBlankLine = false 261 return n.writeStatement("subninja", file) 262} 263 264func (n *ninjaWriter) BlankLine() (err error) { 265 // We don't output multiple blank lines in a row. 266 if !n.justDidBlankLine { 267 n.justDidBlankLine = true 268 _, err = n.writer.WriteString("\n") 269 } 270 return err 271} 272 273func (n *ninjaWriter) writeStatement(directive, name string) error { 274 _, err := n.writer.WriteString(directive + " ") 275 if err != nil { 276 return err 277 } 278 _, err = n.writer.WriteString(name) 279 if err != nil { 280 return err 281 } 282 _, err = n.writer.WriteString("\n") 283 if err != nil { 284 return err 285 } 286 return nil 287} 288 289// ninjaWriterWithWrap is an io.StringWriter that writes through to a ninjaWriter, but supports 290// user-readable line wrapping on boundaries when ninjaWriterWithWrap.Space is called. 291// It collects incoming calls to WriteString until either the line length is exceeded, in which case 292// it inserts a wrap before the pending strings and then writes them, or the next call to Space, in 293// which case it writes out the pending strings. 294// 295// WriteString never returns an error, all errors are held until Flush is called. Once an error has 296// occurred all writes become noops. 297type ninjaWriterWithWrap struct { 298 *ninjaWriter 299 // pending lists the strings that have been written since the last call to Space. 300 pending []string 301 302 // pendingLen accumulates the lengths of the strings in pending. 303 pendingLen int 304 305 // lineLen accumulates the number of bytes on the current line. 306 lineLen int 307 308 // maxLineLen is the length of the line before wrapping. 309 maxLineLen int 310 311 // space is true if the strings in pending should be preceded by a space. 312 space bool 313 314 // err holds any error that has occurred to return in Flush. 315 err error 316} 317 318// WriteString writes the string to buffer, wrapping on a previous Space call if necessary. 319// It never returns an error, all errors are held until Flush is called. 320func (n *ninjaWriterWithWrap) WriteString(s string) (written int, noError error) { 321 // Always return the full length of the string and a nil error. 322 // ninjaWriterWithWrap doesn't return errors to the caller, it saves them until Flush() 323 written = len(s) 324 325 if n.err != nil { 326 return 327 } 328 329 const spaceLen = 1 330 if !n.space { 331 // No space is pending, so a line wrap can't be inserted before this, so just write 332 // the string. 333 n.lineLen += len(s) 334 _, n.err = n.writer.WriteString(s) 335 } else if n.lineLen+len(s)+spaceLen > n.maxLineLen { 336 // A space is pending, and the pending strings plus the current string would exceed the 337 // maximum line length. Wrap and indent before the pending space and strings, then write 338 // the pending and current strings. 339 _, n.err = n.writer.WriteString(" $\n") 340 if n.err != nil { 341 return 342 } 343 _, n.err = n.writer.WriteString(indentString[:indentWidth*2]) 344 if n.err != nil { 345 return 346 } 347 n.lineLen = indentWidth*2 + n.pendingLen 348 s = strings.TrimLeftFunc(s, unicode.IsSpace) 349 n.pending = append(n.pending, s) 350 n.writePending() 351 352 n.space = false 353 } else { 354 // A space is pending but the current string would not reach the maximum line length, 355 // add it to the pending list. 356 n.pending = append(n.pending, s) 357 n.pendingLen += len(s) 358 n.lineLen += len(s) 359 } 360 361 return 362} 363 364// Space inserts a space that is also a possible wrapping point into the string. 365func (n *ninjaWriterWithWrap) Space() { 366 if n.err != nil { 367 return 368 } 369 if n.space { 370 // A space was already pending, and the space plus any strings written after the space did 371 // not reach the maxmimum line length, so write out the old space and pending strings. 372 _, n.err = n.writer.WriteString(" ") 373 n.lineLen++ 374 n.writePending() 375 } 376 n.space = true 377} 378 379// writePending writes out all the strings stored in pending and resets it. 380func (n *ninjaWriterWithWrap) writePending() { 381 if n.err != nil { 382 return 383 } 384 for _, pending := range n.pending { 385 _, n.err = n.writer.WriteString(pending) 386 if n.err != nil { 387 return 388 } 389 } 390 // Reset the length of pending back to 0 without reducing its capacity to avoid reallocating 391 // the backing array. 392 n.pending = n.pending[:0] 393 n.pendingLen = 0 394} 395 396// WriteStringWithSpace is a helper that calls Space and WriteString. 397func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) { 398 n.Space() 399 _, _ = n.WriteString(s) 400} 401 402// Flush writes out any pending space or strings and then a newline. It also returns any errors 403// that have previously occurred. 404func (n *ninjaWriterWithWrap) Flush() error { 405 if n.space { 406 _, n.err = n.writer.WriteString(" ") 407 } 408 n.writePending() 409 if n.err != nil { 410 return n.err 411 } 412 _, err := n.writer.WriteString("\n") 413 return err 414} 415