blob: 51a167dbe4ce473fb11dbf7c1dd4c54ea957b45c [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// 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
Jamie Gennis1bc967e2014-05-27 16:34:41 -070015package blueprint
16
17import (
Colin Cross6134a5c2015-02-10 11:26:26 -080018 "bytes"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070019 "fmt"
Colin Cross8a401482021-01-21 18:27:14 -080020 "io"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070021 "strings"
22)
23
Jamie Gennis04f106f2014-06-12 19:41:57 -070024const eof = -1
Jamie Gennis1bc967e2014-05-27 16:34:41 -070025
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
Colin Cross2ce594e2020-01-29 12:58:03 -080038type ninjaString interface {
39 Value(pkgNames map[*packageContext]string) string
Colin Cross8a401482021-01-21 18:27:14 -080040 ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, escaper *strings.Replacer)
Colin Cross2ce594e2020-01-29 12:58:03 -080041 Eval(variables map[Variable]ninjaString) (string, error)
42 Variables() []Variable
43}
44
45type varNinjaString struct {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070046 strings []string
47 variables []Variable
48}
49
Colin Cross2ce594e2020-01-29 12:58:03 -080050type literalNinjaString string
51
Jamie Gennis48aed8c2014-06-13 18:25:54 -070052type scope interface {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070053 LookupVariable(name string) (Variable, error)
Jamie Gennis48aed8c2014-06-13 18:25:54 -070054 IsRuleVisible(rule Rule) bool
55 IsPoolVisible(pool Pool) bool
Jamie Gennis1bc967e2014-05-27 16:34:41 -070056}
57
Colin Cross2ce594e2020-01-29 12:58:03 -080058func simpleNinjaString(str string) ninjaString {
59 return literalNinjaString(str)
Jamie Gennis1bc967e2014-05-27 16:34:41 -070060}
61
Colin Crosse4cfdf92014-12-12 15:13:53 -080062type parseState struct {
63 scope scope
64 str string
Colin Cross8de48af2017-05-09 10:03:00 -070065 pendingStr string
Colin Crosse4cfdf92014-12-12 15:13:53 -080066 stringStart int
67 varStart int
Colin Cross2ce594e2020-01-29 12:58:03 -080068 result *varNinjaString
Colin Crosse4cfdf92014-12-12 15:13:53 -080069}
70
Colin Crossb2478932015-04-14 16:10:21 -070071func (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 }
Colin Cross8de48af2017-05-09 10:03:00 -070076 if ps.pendingStr != "" {
77 panic("oops, pushed variable with pending string")
78 }
Colin Crossb2478932015-04-14 16:10:21 -070079 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 }
Colin Cross8de48af2017-05-09 10:03:00 -070086 ps.result.strings = append(ps.result.strings, ps.pendingStr+s)
87 ps.pendingStr = ""
Colin Crossb2478932015-04-14 16:10:21 -070088}
89
Colin Crosse4cfdf92014-12-12 15:13:53 -080090type stateFunc func(*parseState, int, rune) (stateFunc, error)
91
Jamie Gennis1bc967e2014-05-27 16:34:41 -070092// 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.
Colin Cross2ce594e2020-01-29 12:58:03 -080095func parseNinjaString(scope scope, str string) (ninjaString, error) {
Colin Cross8c1c6c02015-04-14 16:28:51 -070096 // naively pre-allocate slices by counting $ signs
97 n := strings.Count(str, "$")
Colin Cross2ce594e2020-01-29 12:58:03 -080098 if n == 0 {
99 if strings.HasPrefix(str, " ") {
100 str = "$" + str
101 }
102 return literalNinjaString(str), nil
103 }
104 result := &varNinjaString{
Colin Cross8c1c6c02015-04-14 16:28:51 -0700105 strings: make([]string, 0, n+1),
106 variables: make([]Variable, 0, n),
107 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700108
Colin Crosse4cfdf92014-12-12 15:13:53 -0800109 parseState := &parseState{
110 scope: scope,
111 str: str,
112 result: result,
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700113 }
114
Colin Cross8de48af2017-05-09 10:03:00 -0700115 state := parseFirstRuneState
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700116 var err error
Colin Crosse4cfdf92014-12-12 15:13:53 -0800117 for i := 0; i < len(str); i++ {
118 r := rune(str[i])
119 state, err = state(parseState, i, r)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700120 if err != nil {
121 return nil, err
122 }
123 }
124
Colin Crosse4cfdf92014-12-12 15:13:53 -0800125 _, err = state(parseState, len(parseState.str), eof)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700126 if err != nil {
127 return nil, err
128 }
129
Colin Crosse4cfdf92014-12-12 15:13:53 -0800130 return result, nil
131}
132
Colin Cross8de48af2017-05-09 10:03:00 -0700133func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
134 if r == ' ' {
135 state.pendingStr += "$"
136 }
137 return parseStringState(state, i, r)
138}
139
Colin Crosse4cfdf92014-12-12 15:13:53 -0800140func 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:
Colin Crossb2478932015-04-14 16:10:21 -0700147 state.pushString(state.str[state.stringStart:i])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800148 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.
Colin Cross63d5d4d2015-04-20 16:41:55 -0700161 state.pushString(state.str[state.stringStart : i-1])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800162 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.
Colin Cross63d5d4d2015-04-20 16:41:55 -0700172 state.pushString(state.str[state.stringStart : i-1])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800173 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
Colin Crossb2478932015-04-14 16:10:21 -0700202 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800203 state.varStart = i + 1
Colin Crossb2478932015-04-14 16:10:21 -0700204 state.stringStart = i
Colin Crosse4cfdf92014-12-12 15:13:53 -0800205
Colin Crossb2478932015-04-14 16:10:21 -0700206 return parseDollarStartState, nil
Colin Crosse4cfdf92014-12-12 15:13:53 -0800207
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
Colin Crossb2478932015-04-14 16:10:21 -0700215 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800216
217 // We always end with a string, even if it's an empty one.
Colin Crossb2478932015-04-14 16:10:21 -0700218 state.pushString("")
Colin Crosse4cfdf92014-12-12 15:13:53 -0800219
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
Colin Crossb2478932015-04-14 16:10:21 -0700230 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800231 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
Colin Crossb2478932015-04-14 16:10:21 -0700256 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800257 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 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700268}
269
Colin Cross2ce594e2020-01-29 12:58:03 -0800270func parseNinjaStrings(scope scope, strs []string) ([]ninjaString,
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700271 error) {
272
273 if len(strs) == 0 {
274 return nil, nil
275 }
Colin Cross2ce594e2020-01-29 12:58:03 -0800276 result := make([]ninjaString, len(strs))
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700277 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
Colin Cross2ce594e2020-01-29 12:58:03 -0800287func (n varNinjaString) Value(pkgNames map[*packageContext]string) string {
Colin Cross8a401482021-01-21 18:27:14 -0800288 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()
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700294}
295
Colin Cross8a401482021-01-21 18:27:14 -0800296func (n varNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
297 escaper *strings.Replacer) {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700298
Colin Cross8a401482021-01-21 18:27:14 -0800299 w.WriteString(escaper.Replace(n.strings[0]))
Colin Cross19ff7272019-06-19 23:25:39 -0700300 for i, v := range n.variables {
Colin Cross8a401482021-01-21 18:27:14 -0800301 w.WriteString("${")
302 w.WriteString(v.fullName(pkgNames))
303 w.WriteString("}")
304 w.WriteString(escaper.Replace(n.strings[i+1]))
Colin Cross19ff7272019-06-19 23:25:39 -0700305 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700306}
307
Colin Cross2ce594e2020-01-29 12:58:03 -0800308func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800309 str := n.strings[0]
310 for i, v := range n.variables {
Christian Zander6e2b2322014-11-21 15:12:08 -0800311 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 }
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800319 str += value + n.strings[i+1]
320 }
Christian Zander6e2b2322014-11-21 15:12:08 -0800321 return str, nil
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800322}
323
Colin Cross2ce594e2020-01-29 12:58:03 -0800324func (n varNinjaString) Variables() []Variable {
325 return n.variables
326}
327
328func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string {
Colin Cross8a401482021-01-21 18:27:14 -0800329 return defaultEscaper.Replace(string(l))
Colin Cross2ce594e2020-01-29 12:58:03 -0800330}
331
Colin Cross8a401482021-01-21 18:27:14 -0800332func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
333 escaper *strings.Replacer) {
334 w.WriteString(escaper.Replace(string(l)))
Colin Cross2ce594e2020-01-29 12:58:03 -0800335}
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
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700345func 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 {
Jamie Gennis6736ec32014-10-05 07:40:16 -0700354
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700355 return fmt.Errorf("%q contains an invalid Ninja name character "+
Jamie Gennis6736ec32014-10-05 07:40:16 -0700356 "%q at byte offset %d", name, r, i)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700357 }
358 }
359 return nil
360}
361
Colin Cross6134a5c2015-02-10 11:26:26 -0800362func 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 {
Jeff Gaston0e907592017-12-01 17:10:52 -0800375 // TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate
376 // names for two different input names
Colin Cross6134a5c2015-02-10 11:26:26 -0800377 ret.WriteRune('_')
378 }
379 }
380
381 return ret.String()
382}
383
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700384var 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}