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