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