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 "bytes" 19 "fmt" 20 "io" 21 "strings" 22) 23 24const eof = -1 25 26var ( 27 defaultEscaper = strings.NewReplacer( 28 "\n", "$\n") 29 inputEscaper = strings.NewReplacer( 30 "\n", "$\n", 31 " ", "$ ") 32 outputEscaper = strings.NewReplacer( 33 "\n", "$\n", 34 " ", "$ ", 35 ":", "$:") 36) 37 38type ninjaString interface { 39 Value(pkgNames map[*packageContext]string) string 40 ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, escaper *strings.Replacer) 41 Eval(variables map[Variable]ninjaString) (string, error) 42 Variables() []Variable 43} 44 45type varNinjaString struct { 46 strings []string 47 variables []Variable 48} 49 50type literalNinjaString string 51 52type scope interface { 53 LookupVariable(name string) (Variable, error) 54 IsRuleVisible(rule Rule) bool 55 IsPoolVisible(pool Pool) bool 56} 57 58func simpleNinjaString(str string) ninjaString { 59 return literalNinjaString(str) 60} 61 62type parseState struct { 63 scope scope 64 str string 65 pendingStr string 66 stringStart int 67 varStart int 68 result *varNinjaString 69} 70 71func (ps *parseState) pushVariable(v Variable) { 72 if len(ps.result.variables) == len(ps.result.strings) { 73 // Last push was a variable, we need a blank string separator 74 ps.result.strings = append(ps.result.strings, "") 75 } 76 if ps.pendingStr != "" { 77 panic("oops, pushed variable with pending string") 78 } 79 ps.result.variables = append(ps.result.variables, v) 80} 81 82func (ps *parseState) pushString(s string) { 83 if len(ps.result.strings) != len(ps.result.variables) { 84 panic("oops, pushed string after string") 85 } 86 ps.result.strings = append(ps.result.strings, ps.pendingStr+s) 87 ps.pendingStr = "" 88} 89 90type stateFunc func(*parseState, int, rune) (stateFunc, error) 91 92// parseNinjaString parses an unescaped ninja string (i.e. all $<something> 93// occurrences are expected to be variables or $$) and returns a list of the 94// variable names that the string references. 95func parseNinjaString(scope scope, str string) (ninjaString, error) { 96 // naively pre-allocate slices by counting $ signs 97 n := strings.Count(str, "$") 98 if n == 0 { 99 if strings.HasPrefix(str, " ") { 100 str = "$" + str 101 } 102 return literalNinjaString(str), nil 103 } 104 result := &varNinjaString{ 105 strings: make([]string, 0, n+1), 106 variables: make([]Variable, 0, n), 107 } 108 109 parseState := &parseState{ 110 scope: scope, 111 str: str, 112 result: result, 113 } 114 115 state := parseFirstRuneState 116 var err error 117 for i := 0; i < len(str); i++ { 118 r := rune(str[i]) 119 state, err = state(parseState, i, r) 120 if err != nil { 121 return nil, err 122 } 123 } 124 125 _, err = state(parseState, len(parseState.str), eof) 126 if err != nil { 127 return nil, err 128 } 129 130 return result, nil 131} 132 133func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) { 134 if r == ' ' { 135 state.pendingStr += "$" 136 } 137 return parseStringState(state, i, r) 138} 139 140func parseStringState(state *parseState, i int, r rune) (stateFunc, error) { 141 switch { 142 case r == '$': 143 state.varStart = i + 1 144 return parseDollarStartState, nil 145 146 case r == eof: 147 state.pushString(state.str[state.stringStart:i]) 148 return nil, nil 149 150 default: 151 return parseStringState, nil 152 } 153} 154 155func parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) { 156 switch { 157 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 158 r >= '0' && r <= '9', r == '_', r == '-': 159 // The beginning of a of the variable name. Output the string and 160 // keep going. 161 state.pushString(state.str[state.stringStart : i-1]) 162 return parseDollarState, nil 163 164 case r == '$': 165 // Just a "$$". Go back to parseStringState without changing 166 // state.stringStart. 167 return parseStringState, nil 168 169 case r == '{': 170 // This is a bracketted variable name (e.g. "${blah.blah}"). Output 171 // the string and keep going. 172 state.pushString(state.str[state.stringStart : i-1]) 173 state.varStart = i + 1 174 return parseBracketsState, nil 175 176 case r == eof: 177 return nil, fmt.Errorf("unexpected end of string after '$'") 178 179 default: 180 // This was some arbitrary character following a dollar sign, 181 // which is not allowed. 182 return nil, fmt.Errorf("invalid character after '$' at byte "+ 183 "offset %d", i) 184 } 185} 186 187func parseDollarState(state *parseState, i int, r rune) (stateFunc, error) { 188 switch { 189 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 190 r >= '0' && r <= '9', r == '_', r == '-': 191 // A part of the variable name. Keep going. 192 return parseDollarState, nil 193 194 case r == '$': 195 // A dollar after the variable name (e.g. "$blah$"). Output the 196 // variable we have and start a new one. 197 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 198 if err != nil { 199 return nil, err 200 } 201 202 state.pushVariable(v) 203 state.varStart = i + 1 204 state.stringStart = i 205 206 return parseDollarStartState, nil 207 208 case r == eof: 209 // This is the end of the variable name. 210 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 211 if err != nil { 212 return nil, err 213 } 214 215 state.pushVariable(v) 216 217 // We always end with a string, even if it's an empty one. 218 state.pushString("") 219 220 return nil, nil 221 222 default: 223 // We've just gone past the end of the variable name, so record what 224 // we have. 225 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 226 if err != nil { 227 return nil, err 228 } 229 230 state.pushVariable(v) 231 state.stringStart = i 232 return parseStringState, nil 233 } 234} 235 236func parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) { 237 switch { 238 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 239 r >= '0' && r <= '9', r == '_', r == '-', r == '.': 240 // A part of the variable name. Keep going. 241 return parseBracketsState, nil 242 243 case r == '}': 244 if state.varStart == i { 245 // The brackets were immediately closed. That's no good. 246 return nil, fmt.Errorf("empty variable name at byte offset %d", 247 i) 248 } 249 250 // This is the end of the variable name. 251 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 252 if err != nil { 253 return nil, err 254 } 255 256 state.pushVariable(v) 257 state.stringStart = i + 1 258 return parseStringState, nil 259 260 case r == eof: 261 return nil, fmt.Errorf("unexpected end of string in variable name") 262 263 default: 264 // This character isn't allowed in a variable name. 265 return nil, fmt.Errorf("invalid character in variable name at "+ 266 "byte offset %d", i) 267 } 268} 269 270func parseNinjaStrings(scope scope, strs []string) ([]ninjaString, 271 error) { 272 273 if len(strs) == 0 { 274 return nil, nil 275 } 276 result := make([]ninjaString, len(strs)) 277 for i, str := range strs { 278 ninjaStr, err := parseNinjaString(scope, str) 279 if err != nil { 280 return nil, fmt.Errorf("error parsing element %d: %s", i, err) 281 } 282 result[i] = ninjaStr 283 } 284 return result, nil 285} 286 287func (n varNinjaString) Value(pkgNames map[*packageContext]string) string { 288 if len(n.strings) == 1 { 289 return defaultEscaper.Replace(n.strings[0]) 290 } 291 str := &strings.Builder{} 292 n.ValueWithEscaper(str, pkgNames, defaultEscaper) 293 return str.String() 294} 295 296func (n varNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, 297 escaper *strings.Replacer) { 298 299 w.WriteString(escaper.Replace(n.strings[0])) 300 for i, v := range n.variables { 301 w.WriteString("${") 302 w.WriteString(v.fullName(pkgNames)) 303 w.WriteString("}") 304 w.WriteString(escaper.Replace(n.strings[i+1])) 305 } 306} 307 308func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) { 309 str := n.strings[0] 310 for i, v := range n.variables { 311 variable, ok := variables[v] 312 if !ok { 313 return "", fmt.Errorf("no such global variable: %s", v) 314 } 315 value, err := variable.Eval(variables) 316 if err != nil { 317 return "", err 318 } 319 str += value + n.strings[i+1] 320 } 321 return str, nil 322} 323 324func (n varNinjaString) Variables() []Variable { 325 return n.variables 326} 327 328func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string { 329 return defaultEscaper.Replace(string(l)) 330} 331 332func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, 333 escaper *strings.Replacer) { 334 w.WriteString(escaper.Replace(string(l))) 335} 336 337func (l literalNinjaString) Eval(variables map[Variable]ninjaString) (string, error) { 338 return string(l), nil 339} 340 341func (l literalNinjaString) Variables() []Variable { 342 return nil 343} 344 345func validateNinjaName(name string) error { 346 for i, r := range name { 347 valid := (r >= 'a' && r <= 'z') || 348 (r >= 'A' && r <= 'Z') || 349 (r >= '0' && r <= '9') || 350 (r == '_') || 351 (r == '-') || 352 (r == '.') 353 if !valid { 354 355 return fmt.Errorf("%q contains an invalid Ninja name character "+ 356 "%q at byte offset %d", name, r, i) 357 } 358 } 359 return nil 360} 361 362func toNinjaName(name string) string { 363 ret := bytes.Buffer{} 364 ret.Grow(len(name)) 365 for _, r := range name { 366 valid := (r >= 'a' && r <= 'z') || 367 (r >= 'A' && r <= 'Z') || 368 (r >= '0' && r <= '9') || 369 (r == '_') || 370 (r == '-') || 371 (r == '.') 372 if valid { 373 ret.WriteRune(r) 374 } else { 375 // TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate 376 // names for two different input names 377 ret.WriteRune('_') 378 } 379 } 380 381 return ret.String() 382} 383 384var builtinRuleArgs = []string{"out", "in"} 385 386func validateArgName(argName string) error { 387 err := validateNinjaName(argName) 388 if err != nil { 389 return err 390 } 391 392 // We only allow globals within the rule's package to be used as rule 393 // arguments. A global in another package can always be mirrored into 394 // the rule's package by defining a new variable, so this doesn't limit 395 // what's possible. This limitation prevents situations where a Build 396 // invocation in another package must use the rule-defining package's 397 // import name for a 3rd package in order to set the rule's arguments. 398 if strings.ContainsRune(argName, '.') { 399 return fmt.Errorf("%q contains a '.' character", argName) 400 } 401 402 for _, builtin := range builtinRuleArgs { 403 if argName == builtin { 404 return fmt.Errorf("%q conflicts with Ninja built-in", argName) 405 } 406 } 407 408 return nil 409} 410 411func validateArgNames(argNames []string) error { 412 for _, argName := range argNames { 413 err := validateArgName(argName) 414 if err != nil { 415 return err 416 } 417 } 418 419 return nil 420} 421