blob: 80b09fc3177206df9b7c5e2bf6e69995fb2251ff [file] [log] [blame]
Sam Delmerico7f889562022-03-25 14:55:40 +00001// Copyright 2021 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 android
16
17import (
18 "fmt"
19 "reflect"
20 "regexp"
21 "sort"
22 "strings"
23
24 "android/soong/bazel"
25 "android/soong/starlark_fmt"
26
27 "github.com/google/blueprint"
28)
29
30// BazelVarExporter is a collection of configuration variables that can be exported for use in Bazel rules
31type BazelVarExporter interface {
32 // asBazel expands strings of configuration variables into their concrete values
33 asBazel(Config, ExportedStringVariables, ExportedStringListVariables, ExportedConfigDependingVariables) []bazelConstant
34}
35
36// ExportedVariables is a collection of interdependent configuration variables
37type ExportedVariables struct {
38 // Maps containing toolchain variables that are independent of the
39 // environment variables of the build.
40 exportedStringVars ExportedStringVariables
41 exportedStringListVars ExportedStringListVariables
42 exportedStringListDictVars ExportedStringListDictVariables
43
44 exportedVariableReferenceDictVars ExportedVariableReferenceDictVariables
45
46 /// Maps containing variables that are dependent on the build config.
47 exportedConfigDependingVars ExportedConfigDependingVariables
48
49 pctx PackageContext
50}
51
52// NewExportedVariables creats an empty ExportedVariables struct with non-nil maps
53func NewExportedVariables(pctx PackageContext) ExportedVariables {
54 return ExportedVariables{
55 exportedStringVars: ExportedStringVariables{},
56 exportedStringListVars: ExportedStringListVariables{},
57 exportedStringListDictVars: ExportedStringListDictVariables{},
58 exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{},
59 exportedConfigDependingVars: ExportedConfigDependingVariables{},
60 pctx: pctx,
61 }
62}
63
64func (ev ExportedVariables) asBazel(config Config,
65 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
66 ret := []bazelConstant{}
67 ret = append(ret, ev.exportedStringVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
68 ret = append(ret, ev.exportedStringListVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
69 ret = append(ret, ev.exportedStringListDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
70 // Note: ExportedVariableReferenceDictVars collections can only contain references to other variables and must be printed last
71 ret = append(ret, ev.exportedVariableReferenceDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
72 return ret
73}
74
75// ExportStringStaticVariable declares a static string variable and exports it to
76// Bazel's toolchain.
77func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) {
78 ev.pctx.StaticVariable(name, value)
79 ev.exportedStringVars.set(name, value)
80}
81
82// ExportStringListStaticVariable declares a static variable and exports it to
83// Bazel's toolchain.
84func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) {
85 ev.pctx.StaticVariable(name, strings.Join(value, " "))
86 ev.exportedStringListVars.set(name, value)
87}
88
89// ExportVariableConfigMethod declares a variable whose value is evaluated at
90// runtime via a function with access to the Config and exports it to Bazel's
91// toolchain.
92func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
93 ev.exportedConfigDependingVars.set(name, method)
94 return ev.pctx.VariableConfigMethod(name, method)
95}
96
97// ExportSourcePathVariable declares a static "source path" variable and exports
98// it to Bazel's toolchain.
99func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) {
100 ev.pctx.SourcePathVariable(name, value)
101 ev.exportedStringVars.set(name, value)
102}
103
104// ExportString only exports a variable to Bazel, but does not declare it in Soong
105func (ev ExportedVariables) ExportString(name string, value string) {
106 ev.exportedStringVars.set(name, value)
107}
108
109// ExportStringList only exports a variable to Bazel, but does not declare it in Soong
110func (ev ExportedVariables) ExportStringList(name string, value []string) {
111 ev.exportedStringListVars.set(name, value)
112}
113
114// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong
115func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) {
116 ev.exportedStringListDictVars.set(name, value)
117}
118
119// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong
120func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) {
121 ev.exportedVariableReferenceDictVars.set(name, value)
122}
123
124// ExportedConfigDependingVariables is a mapping of variable names to functions
125// of type func(config Config) string which return the runtime-evaluated string
126// value of a particular variable
127type ExportedConfigDependingVariables map[string]interface{}
128
129func (m ExportedConfigDependingVariables) set(k string, v interface{}) {
130 m[k] = v
131}
132
133// Ensure that string s has no invalid characters to be generated into the bzl file.
134func validateCharacters(s string) string {
135 for _, c := range []string{`\n`, `"`, `\`} {
136 if strings.Contains(s, c) {
137 panic(fmt.Errorf("%s contains illegal character %s", s, c))
138 }
139 }
140 return s
141}
142
143type bazelConstant struct {
144 variableName string
145 internalDefinition string
146 sortLast bool
147}
148
149// ExportedStringVariables is a mapping of variable names to string values
150type ExportedStringVariables map[string]string
151
152func (m ExportedStringVariables) set(k string, v string) {
153 m[k] = v
154}
155
156func (m ExportedStringVariables) asBazel(config Config,
157 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
158 ret := make([]bazelConstant, 0, len(m))
159 for k, variableValue := range m {
160 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
161 if err != nil {
162 panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
163 }
164 if len(expandedVar) > 1 {
165 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
166 }
167 ret = append(ret, bazelConstant{
168 variableName: k,
169 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
170 })
171 }
172 return ret
173}
174
175// ExportedStringListVariables is a mapping of variable names to a list of strings
176type ExportedStringListVariables map[string][]string
177
178func (m ExportedStringListVariables) set(k string, v []string) {
179 m[k] = v
180}
181
182func (m ExportedStringListVariables) asBazel(config Config,
183 stringScope ExportedStringVariables, stringListScope ExportedStringListVariables,
184 exportedVars ExportedConfigDependingVariables) []bazelConstant {
185 ret := make([]bazelConstant, 0, len(m))
186 // For each exported variable, recursively expand elements in the variableValue
187 // list to ensure that interpolated variables are expanded according to their values
188 // in the variable scope.
189 for k, variableValue := range m {
190 var expandedVars []string
191 for _, v := range variableValue {
192 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
193 if err != nil {
194 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
195 }
196 expandedVars = append(expandedVars, expandedVar...)
197 }
198 // Assign the list as a bzl-private variable; this variable will be exported
199 // out through a constants struct later.
200 ret = append(ret, bazelConstant{
201 variableName: k,
202 internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
203 })
204 }
205 return ret
206}
207
208// ExportedStringListDictVariables is a mapping from variable names to a
209// dictionary which maps keys to lists of strings
210type ExportedStringListDictVariables map[string]map[string][]string
211
212func (m ExportedStringListDictVariables) set(k string, v map[string][]string) {
213 m[k] = v
214}
215
216// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
217func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables,
218 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
219 ret := make([]bazelConstant, 0, len(m))
220 for k, dict := range m {
221 ret = append(ret, bazelConstant{
222 variableName: k,
223 internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
224 })
225 }
226 return ret
227}
228
229// ExportedVariableReferenceDictVariables is a mapping from variable names to a
230// dictionary which references previously defined variables. This is used to
231// create a Starlark output such as:
232// string_var1 = "string1
233// var_ref_dict_var1 = {
234// "key1": string_var1
235// }
236// This type of variable collection must be expanded last so that it recognizes
237// previously defined variables.
238type ExportedVariableReferenceDictVariables map[string]map[string]string
239
240func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) {
241 m[k] = v
242}
243
244func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables,
245 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
246 ret := make([]bazelConstant, 0, len(m))
247 for n, dict := range m {
248 for k, v := range dict {
249 matches, err := variableReference(v)
250 if err != nil {
251 panic(err)
252 } else if !matches.matches {
253 panic(fmt.Errorf("Expected a variable reference, got %q", v))
254 } else if len(matches.fullVariableReference) != len(v) {
255 panic(fmt.Errorf("Expected only a variable reference, got %q", v))
256 }
257 dict[k] = "_" + matches.variable
258 }
259 ret = append(ret, bazelConstant{
260 variableName: n,
261 internalDefinition: starlark_fmt.PrintDict(dict, 0),
262 sortLast: true,
263 })
264 }
265 return ret
266}
267
268// BazelToolchainVars expands an ExportedVariables collection and returns a string
269// of formatted Starlark variable definitions
270func BazelToolchainVars(config Config, exportedVars ExportedVariables) string {
271 results := exportedVars.asBazel(
272 config,
273 exportedVars.exportedStringVars,
274 exportedVars.exportedStringListVars,
275 exportedVars.exportedConfigDependingVars,
276 )
277
278 sort.Slice(results, func(i, j int) bool {
279 if results[i].sortLast != results[j].sortLast {
280 return !results[i].sortLast
281 }
282 return results[i].variableName < results[j].variableName
283 })
284
285 definitions := make([]string, 0, len(results))
286 constants := make([]string, 0, len(results))
287 for _, b := range results {
288 definitions = append(definitions,
289 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
290 constants = append(constants,
291 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
292 }
293
294 // Build the exported constants struct.
295 ret := bazel.GeneratedBazelFileWarning
296 ret += "\n\n"
297 ret += strings.Join(definitions, "\n\n")
298 ret += "\n\n"
299 ret += "constants = struct(\n"
300 ret += strings.Join(constants, "\n")
301 ret += "\n)"
302
303 return ret
304}
305
306type match struct {
307 matches bool
308 fullVariableReference string
309 variable string
310}
311
312func variableReference(input string) (match, error) {
313 // e.g. "${ExternalCflags}"
314 r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
315
316 matches := r.FindStringSubmatch(input)
317 if len(matches) == 0 {
318 return match{}, nil
319 }
320 if len(matches) != 2 {
321 return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
322 }
323 return match{
324 matches: true,
325 fullVariableReference: matches[0],
326 // Index 1 of FindStringSubmatch contains the subexpression match
327 // (variable name) of the capture group.
328 variable: matches[1],
329 }, nil
330}
331
332// expandVar recursively expand interpolated variables in the exportedVars scope.
333//
334// We're using a string slice to track the seen variables to avoid
335// stackoverflow errors with infinite recursion. it's simpler to use a
336// string slice than to handle a pass-by-referenced map, which would make it
337// quite complex to track depth-first interpolations. It's also unlikely the
338// interpolation stacks are deep (n > 1).
339func expandVar(config Config, toExpand string, stringScope ExportedStringVariables,
340 stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) {
341
342 // Internal recursive function.
343 var expandVarInternal func(string, map[string]bool) (string, error)
344 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
345 var ret string
346 remainingString := toExpand
347 for len(remainingString) > 0 {
348 matches, err := variableReference(remainingString)
349 if err != nil {
350 panic(err)
351 }
352 if !matches.matches {
353 return ret + remainingString, nil
354 }
355 matchIndex := strings.Index(remainingString, matches.fullVariableReference)
356 ret += remainingString[:matchIndex]
357 remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
358
359 variable := matches.variable
360 // toExpand contains a variable.
361 if _, ok := seenVars[variable]; ok {
362 return ret, fmt.Errorf(
363 "Unbounded recursive interpolation of variable: %s", variable)
364 }
365 // A map is passed-by-reference. Create a new map for
366 // this scope to prevent variables seen in one depth-first expansion
367 // to be also treated as "seen" in other depth-first traversals.
368 newSeenVars := map[string]bool{}
369 for k := range seenVars {
370 newSeenVars[k] = true
371 }
372 newSeenVars[variable] = true
373 if unexpandedVars, ok := stringListScope[variable]; ok {
374 expandedVars := []string{}
375 for _, unexpandedVar := range unexpandedVars {
376 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
377 if err != nil {
378 return ret, err
379 }
380 expandedVars = append(expandedVars, expandedVar)
381 }
382 ret += strings.Join(expandedVars, " ")
383 } else if unexpandedVar, ok := stringScope[variable]; ok {
384 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
385 if err != nil {
386 return ret, err
387 }
388 ret += expandedVar
389 } else if unevaluatedVar, ok := exportedVars[variable]; ok {
390 evalFunc := reflect.ValueOf(unevaluatedVar)
391 validateVariableMethod(variable, evalFunc)
392 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
393 evaluatedValue := evaluatedResult[0].Interface().(string)
394 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
395 if err != nil {
396 return ret, err
397 }
398 ret += expandedVar
399 } else {
400 return "", fmt.Errorf("Unbound config variable %s", variable)
401 }
402 }
403 return ret, nil
404 }
405 var ret []string
406 for _, v := range strings.Split(toExpand, " ") {
407 val, err := expandVarInternal(v, map[string]bool{})
408 if err != nil {
409 return ret, err
410 }
411 ret = append(ret, val)
412 }
413
414 return ret, nil
415}
416
417func validateVariableMethod(name string, methodValue reflect.Value) {
418 methodType := methodValue.Type()
419 if methodType.Kind() != reflect.Func {
420 panic(fmt.Errorf("method given for variable %s is not a function",
421 name))
422 }
423 if n := methodType.NumIn(); n != 1 {
424 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
425 name, n))
426 }
427 if n := methodType.NumOut(); n != 1 {
428 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
429 name, n))
430 }
431 if kind := methodType.Out(0).Kind(); kind != reflect.String {
432 panic(fmt.Errorf("method for variable %s does not return a string",
433 name))
434 }
435}