blob: 3492da2feaa2978092016440f8b2c50a99a87694 [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"
20 "strings"
21)
22
Jamie Gennis04f106f2014-06-12 19:41:57 -070023const eof = -1
Jamie Gennis1bc967e2014-05-27 16:34:41 -070024
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
Jamie Gennis48aed8c2014-06-13 18:25:54 -070042type scope interface {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070043 LookupVariable(name string) (Variable, error)
Jamie Gennis48aed8c2014-06-13 18:25:54 -070044 IsRuleVisible(rule Rule) bool
45 IsPoolVisible(pool Pool) bool
Jamie Gennis1bc967e2014-05-27 16:34:41 -070046}
47
48func simpleNinjaString(str string) *ninjaString {
49 return &ninjaString{
50 strings: []string{str},
51 }
52}
53
Colin Crosse4cfdf92014-12-12 15:13:53 -080054type parseState struct {
55 scope scope
56 str string
57 stringStart int
58 varStart int
59 result *ninjaString
60}
61
Colin Crossb2478932015-04-14 16:10:21 -070062func (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
Colin Crosse4cfdf92014-12-12 15:13:53 -080077type stateFunc func(*parseState, int, rune) (stateFunc, error)
78
Jamie Gennis1bc967e2014-05-27 16:34:41 -070079// 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.
Jamie Gennis48aed8c2014-06-13 18:25:54 -070082func parseNinjaString(scope scope, str string) (*ninjaString, error) {
Colin Cross8c1c6c02015-04-14 16:28:51 -070083 // 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 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -070089
Colin Crosse4cfdf92014-12-12 15:13:53 -080090 parseState := &parseState{
91 scope: scope,
92 str: str,
93 result: result,
Jamie Gennis1bc967e2014-05-27 16:34:41 -070094 }
95
Colin Crosse4cfdf92014-12-12 15:13:53 -080096 state := parseStringState
Jamie Gennis1bc967e2014-05-27 16:34:41 -070097 var err error
Colin Crosse4cfdf92014-12-12 15:13:53 -080098 for i := 0; i < len(str); i++ {
99 r := rune(str[i])
100 state, err = state(parseState, i, r)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700101 if err != nil {
102 return nil, err
103 }
104 }
105
Colin Crosse4cfdf92014-12-12 15:13:53 -0800106 _, err = state(parseState, len(parseState.str), eof)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700107 if err != nil {
108 return nil, err
109 }
110
Colin Crosse4cfdf92014-12-12 15:13:53 -0800111 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:
Colin Crossb2478932015-04-14 16:10:21 -0700121 state.pushString(state.str[state.stringStart:i])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800122 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.
Colin Cross63d5d4d2015-04-20 16:41:55 -0700135 state.pushString(state.str[state.stringStart : i-1])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800136 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.
Colin Cross63d5d4d2015-04-20 16:41:55 -0700146 state.pushString(state.str[state.stringStart : i-1])
Colin Crosse4cfdf92014-12-12 15:13:53 -0800147 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
Colin Crossb2478932015-04-14 16:10:21 -0700176 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800177 state.varStart = i + 1
Colin Crossb2478932015-04-14 16:10:21 -0700178 state.stringStart = i
Colin Crosse4cfdf92014-12-12 15:13:53 -0800179
Colin Crossb2478932015-04-14 16:10:21 -0700180 return parseDollarStartState, nil
Colin Crosse4cfdf92014-12-12 15:13:53 -0800181
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
Colin Crossb2478932015-04-14 16:10:21 -0700189 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800190
191 // We always end with a string, even if it's an empty one.
Colin Crossb2478932015-04-14 16:10:21 -0700192 state.pushString("")
Colin Crosse4cfdf92014-12-12 15:13:53 -0800193
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
Colin Crossb2478932015-04-14 16:10:21 -0700204 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800205 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
Colin Crossb2478932015-04-14 16:10:21 -0700230 state.pushVariable(v)
Colin Crosse4cfdf92014-12-12 15:13:53 -0800231 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 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700242}
243
Jamie Gennis48aed8c2014-06-13 18:25:54 -0700244func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700245 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
Jamie Gennis2fb20952014-10-03 02:49:58 -0700261func (n *ninjaString) Value(pkgNames map[*PackageContext]string) string {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700262 return n.ValueWithEscaper(pkgNames, defaultEscaper)
263}
264
Jamie Gennis2fb20952014-10-03 02:49:58 -0700265func (n *ninjaString) ValueWithEscaper(pkgNames map[*PackageContext]string,
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700266 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
Christian Zander6e2b2322014-11-21 15:12:08 -0800276func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800277 str := n.strings[0]
278 for i, v := range n.variables {
Christian Zander6e2b2322014-11-21 15:12:08 -0800279 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 }
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800287 str += value + n.strings[i+1]
288 }
Christian Zander6e2b2322014-11-21 15:12:08 -0800289 return str, nil
Jamie Gennisdf935ac2014-11-09 11:57:57 -0800290}
291
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700292func 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 {
Jamie Gennis6736ec32014-10-05 07:40:16 -0700301
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700302 return fmt.Errorf("%q contains an invalid Ninja name character "+
Jamie Gennis6736ec32014-10-05 07:40:16 -0700303 "%q at byte offset %d", name, r, i)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700304 }
305 }
306 return nil
307}
308
Colin Cross6134a5c2015-02-10 11:26:26 -0800309func 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
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700329var 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}