• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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// ExportVariableFuncVariable declares a variable whose value is evaluated at
105// runtime via a function and exports it to Bazel's toolchain.
106func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) {
107	ev.exportedConfigDependingVars.set(name, func(config Config) string {
108		return f()
109	})
110	ev.pctx.VariableFunc(name, func(PackageVarContext) string {
111		return f()
112	})
113}
114
115// ExportString only exports a variable to Bazel, but does not declare it in Soong
116func (ev ExportedVariables) ExportString(name string, value string) {
117	ev.exportedStringVars.set(name, value)
118}
119
120// ExportStringList only exports a variable to Bazel, but does not declare it in Soong
121func (ev ExportedVariables) ExportStringList(name string, value []string) {
122	ev.exportedStringListVars.set(name, value)
123}
124
125// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong
126func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) {
127	ev.exportedStringListDictVars.set(name, value)
128}
129
130// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong
131func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) {
132	ev.exportedVariableReferenceDictVars.set(name, value)
133}
134
135// ExportedConfigDependingVariables is a mapping of variable names to functions
136// of type func(config Config) string which return the runtime-evaluated string
137// value of a particular variable
138type ExportedConfigDependingVariables map[string]interface{}
139
140func (m ExportedConfigDependingVariables) set(k string, v interface{}) {
141	m[k] = v
142}
143
144// Ensure that string s has no invalid characters to be generated into the bzl file.
145func validateCharacters(s string) string {
146	for _, c := range []string{`\n`, `"`, `\`} {
147		if strings.Contains(s, c) {
148			panic(fmt.Errorf("%s contains illegal character %s", s, c))
149		}
150	}
151	return s
152}
153
154type bazelConstant struct {
155	variableName       string
156	internalDefinition string
157	sortLast           bool
158}
159
160// ExportedStringVariables is a mapping of variable names to string values
161type ExportedStringVariables map[string]string
162
163func (m ExportedStringVariables) set(k string, v string) {
164	m[k] = v
165}
166
167func (m ExportedStringVariables) asBazel(config Config,
168	stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
169	ret := make([]bazelConstant, 0, len(m))
170	for k, variableValue := range m {
171		expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
172		if err != nil {
173			panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
174		}
175		if len(expandedVar) > 1 {
176			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
177		}
178		ret = append(ret, bazelConstant{
179			variableName:       k,
180			internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
181		})
182	}
183	return ret
184}
185
186// ExportedStringListVariables is a mapping of variable names to a list of strings
187type ExportedStringListVariables map[string][]string
188
189func (m ExportedStringListVariables) set(k string, v []string) {
190	m[k] = v
191}
192
193func (m ExportedStringListVariables) asBazel(config Config,
194	stringScope ExportedStringVariables, stringListScope ExportedStringListVariables,
195	exportedVars ExportedConfigDependingVariables) []bazelConstant {
196	ret := make([]bazelConstant, 0, len(m))
197	// For each exported variable, recursively expand elements in the variableValue
198	// list to ensure that interpolated variables are expanded according to their values
199	// in the variable scope.
200	for k, variableValue := range m {
201		var expandedVars []string
202		for _, v := range variableValue {
203			expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
204			if err != nil {
205				panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
206			}
207			expandedVars = append(expandedVars, expandedVar...)
208		}
209		// Assign the list as a bzl-private variable; this variable will be exported
210		// out through a constants struct later.
211		ret = append(ret, bazelConstant{
212			variableName:       k,
213			internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
214		})
215	}
216	return ret
217}
218
219// ExportedStringListDictVariables is a mapping from variable names to a
220// dictionary which maps keys to lists of strings
221type ExportedStringListDictVariables map[string]map[string][]string
222
223func (m ExportedStringListDictVariables) set(k string, v map[string][]string) {
224	m[k] = v
225}
226
227// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
228func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables,
229	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
230	ret := make([]bazelConstant, 0, len(m))
231	for k, dict := range m {
232		ret = append(ret, bazelConstant{
233			variableName:       k,
234			internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
235		})
236	}
237	return ret
238}
239
240// ExportedVariableReferenceDictVariables is a mapping from variable names to a
241// dictionary which references previously defined variables. This is used to
242// create a Starlark output such as:
243// 		string_var1 = "string1
244// 		var_ref_dict_var1 = {
245// 			"key1": string_var1
246// 		}
247// This type of variable collection must be expanded last so that it recognizes
248// previously defined variables.
249type ExportedVariableReferenceDictVariables map[string]map[string]string
250
251func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) {
252	m[k] = v
253}
254
255func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables,
256	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
257	ret := make([]bazelConstant, 0, len(m))
258	for n, dict := range m {
259		for k, v := range dict {
260			matches, err := variableReference(v)
261			if err != nil {
262				panic(err)
263			} else if !matches.matches {
264				panic(fmt.Errorf("Expected a variable reference, got %q", v))
265			} else if len(matches.fullVariableReference) != len(v) {
266				panic(fmt.Errorf("Expected only a variable reference, got %q", v))
267			}
268			dict[k] = "_" + matches.variable
269		}
270		ret = append(ret, bazelConstant{
271			variableName:       n,
272			internalDefinition: starlark_fmt.PrintDict(dict, 0),
273			sortLast:           true,
274		})
275	}
276	return ret
277}
278
279// BazelToolchainVars expands an ExportedVariables collection and returns a string
280// of formatted Starlark variable definitions
281func BazelToolchainVars(config Config, exportedVars ExportedVariables) string {
282	results := exportedVars.asBazel(
283		config,
284		exportedVars.exportedStringVars,
285		exportedVars.exportedStringListVars,
286		exportedVars.exportedConfigDependingVars,
287	)
288
289	sort.Slice(results, func(i, j int) bool {
290		if results[i].sortLast != results[j].sortLast {
291			return !results[i].sortLast
292		}
293		return results[i].variableName < results[j].variableName
294	})
295
296	definitions := make([]string, 0, len(results))
297	constants := make([]string, 0, len(results))
298	for _, b := range results {
299		definitions = append(definitions,
300			fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
301		constants = append(constants,
302			fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
303	}
304
305	// Build the exported constants struct.
306	ret := bazel.GeneratedBazelFileWarning
307	ret += "\n\n"
308	ret += strings.Join(definitions, "\n\n")
309	ret += "\n\n"
310	ret += "constants = struct(\n"
311	ret += strings.Join(constants, "\n")
312	ret += "\n)"
313
314	return ret
315}
316
317type match struct {
318	matches               bool
319	fullVariableReference string
320	variable              string
321}
322
323func variableReference(input string) (match, error) {
324	// e.g. "${ExternalCflags}"
325	r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
326
327	matches := r.FindStringSubmatch(input)
328	if len(matches) == 0 {
329		return match{}, nil
330	}
331	if len(matches) != 2 {
332		return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
333	}
334	return match{
335		matches:               true,
336		fullVariableReference: matches[0],
337		// Index 1 of FindStringSubmatch contains the subexpression match
338		// (variable name) of the capture group.
339		variable: matches[1],
340	}, nil
341}
342
343// expandVar recursively expand interpolated variables in the exportedVars scope.
344//
345// We're using a string slice to track the seen variables to avoid
346// stackoverflow errors with infinite recursion. it's simpler to use a
347// string slice than to handle a pass-by-referenced map, which would make it
348// quite complex to track depth-first interpolations. It's also unlikely the
349// interpolation stacks are deep (n > 1).
350func expandVar(config Config, toExpand string, stringScope ExportedStringVariables,
351	stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) {
352
353	// Internal recursive function.
354	var expandVarInternal func(string, map[string]bool) (string, error)
355	expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
356		var ret string
357		remainingString := toExpand
358		for len(remainingString) > 0 {
359			matches, err := variableReference(remainingString)
360			if err != nil {
361				panic(err)
362			}
363			if !matches.matches {
364				return ret + remainingString, nil
365			}
366			matchIndex := strings.Index(remainingString, matches.fullVariableReference)
367			ret += remainingString[:matchIndex]
368			remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
369
370			variable := matches.variable
371			// toExpand contains a variable.
372			if _, ok := seenVars[variable]; ok {
373				return ret, fmt.Errorf(
374					"Unbounded recursive interpolation of variable: %s", variable)
375			}
376			// A map is passed-by-reference. Create a new map for
377			// this scope to prevent variables seen in one depth-first expansion
378			// to be also treated as "seen" in other depth-first traversals.
379			newSeenVars := map[string]bool{}
380			for k := range seenVars {
381				newSeenVars[k] = true
382			}
383			newSeenVars[variable] = true
384			if unexpandedVars, ok := stringListScope[variable]; ok {
385				expandedVars := []string{}
386				for _, unexpandedVar := range unexpandedVars {
387					expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
388					if err != nil {
389						return ret, err
390					}
391					expandedVars = append(expandedVars, expandedVar)
392				}
393				ret += strings.Join(expandedVars, " ")
394			} else if unexpandedVar, ok := stringScope[variable]; ok {
395				expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
396				if err != nil {
397					return ret, err
398				}
399				ret += expandedVar
400			} else if unevaluatedVar, ok := exportedVars[variable]; ok {
401				evalFunc := reflect.ValueOf(unevaluatedVar)
402				validateVariableMethod(variable, evalFunc)
403				evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
404				evaluatedValue := evaluatedResult[0].Interface().(string)
405				expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
406				if err != nil {
407					return ret, err
408				}
409				ret += expandedVar
410			} else {
411				return "", fmt.Errorf("Unbound config variable %s", variable)
412			}
413		}
414		return ret, nil
415	}
416	var ret []string
417	stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ')
418	for _, v := range stringFields {
419		val, err := expandVarInternal(v, map[string]bool{})
420		if err != nil {
421			return ret, err
422		}
423		ret = append(ret, val)
424	}
425
426	return ret, nil
427}
428
429// splitStringKeepingQuotedSubstring splits a string on a provided separator,
430// but it will not split substrings inside unescaped double quotes. If the double
431// quotes are escaped, then the returned string will only include the quote, and
432// not the escape.
433func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string {
434	var ret []string
435	quote := byte('"')
436
437	var substring []byte
438	quoted := false
439	escaped := false
440
441	for i := range s {
442		if !quoted && s[i] == delimiter {
443			ret = append(ret, string(substring))
444			substring = []byte{}
445			continue
446		}
447
448		characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote
449		if characterIsEscape {
450			escaped = true
451			continue
452		}
453
454		if s[i] == quote {
455			if !escaped {
456				quoted = !quoted
457			}
458			escaped = false
459		}
460
461		substring = append(substring, s[i])
462	}
463
464	ret = append(ret, string(substring))
465
466	return ret
467}
468
469func validateVariableMethod(name string, methodValue reflect.Value) {
470	methodType := methodValue.Type()
471	if methodType.Kind() != reflect.Func {
472		panic(fmt.Errorf("method given for variable %s is not a function",
473			name))
474	}
475	if n := methodType.NumIn(); n != 1 {
476		panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
477			name, n))
478	}
479	if n := methodType.NumOut(); n != 1 {
480		panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
481			name, n))
482	}
483	if kind := methodType.Out(0).Kind(); kind != reflect.String {
484		panic(fmt.Errorf("method for variable %s does not return a string",
485			name))
486	}
487}
488