refactor Bazel variable export
Most of the variable export code for cc modules can be re-used for
exporting variables for java modules. Refactor this code into a more
composable structure for reuse.
Test: build/bazel/bp2build.sh
Test: manual comparison of
out/soong/soong_injection/cc_toolchain/constants.bzl
with previous output
Change-Id: Ie5a6fee08cc888b7dc69c3e324e5c3f8aa269a8f
diff --git a/android/Android.bp b/android/Android.bp
index c072ac2..49d5b91 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -36,6 +36,7 @@
"bazel_handler.go",
"bazel_paths.go",
"config.go",
+ "config_bp2build.go",
"csuite_config.go",
"deapexer.go",
"defaults.go",
@@ -96,6 +97,7 @@
"bazel_handler_test.go",
"bazel_test.go",
"config_test.go",
+ "config_bp2build_test.go",
"csuite_config_test.go",
"defaults_test.go",
"depset_test.go",
diff --git a/android/api_levels.go b/android/api_levels.go
index 27a3b7f..8163894 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -19,6 +19,7 @@
"fmt"
"strconv"
+ "android/soong/bazel"
"android/soong/starlark_fmt"
)
@@ -393,10 +394,10 @@
}
func StarlarkApiLevelConfigs(config Config) string {
- return fmt.Sprintf(`# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
+ return fmt.Sprintf(bazel.GeneratedBazelFileWarning+`
_api_levels = %s
api_levels = _api_levels
`, printApiLevelsStarlarkDict(config),
)
-}
\ No newline at end of file
+}
diff --git a/android/config_bp2build.go b/android/config_bp2build.go
new file mode 100644
index 0000000..80b09fc
--- /dev/null
+++ b/android/config_bp2build.go
@@ -0,0 +1,435 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "sort"
+ "strings"
+
+ "android/soong/bazel"
+ "android/soong/starlark_fmt"
+
+ "github.com/google/blueprint"
+)
+
+// BazelVarExporter is a collection of configuration variables that can be exported for use in Bazel rules
+type BazelVarExporter interface {
+ // asBazel expands strings of configuration variables into their concrete values
+ asBazel(Config, ExportedStringVariables, ExportedStringListVariables, ExportedConfigDependingVariables) []bazelConstant
+}
+
+// ExportedVariables is a collection of interdependent configuration variables
+type ExportedVariables struct {
+ // Maps containing toolchain variables that are independent of the
+ // environment variables of the build.
+ exportedStringVars ExportedStringVariables
+ exportedStringListVars ExportedStringListVariables
+ exportedStringListDictVars ExportedStringListDictVariables
+
+ exportedVariableReferenceDictVars ExportedVariableReferenceDictVariables
+
+ /// Maps containing variables that are dependent on the build config.
+ exportedConfigDependingVars ExportedConfigDependingVariables
+
+ pctx PackageContext
+}
+
+// NewExportedVariables creats an empty ExportedVariables struct with non-nil maps
+func NewExportedVariables(pctx PackageContext) ExportedVariables {
+ return ExportedVariables{
+ exportedStringVars: ExportedStringVariables{},
+ exportedStringListVars: ExportedStringListVariables{},
+ exportedStringListDictVars: ExportedStringListDictVariables{},
+ exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{},
+ exportedConfigDependingVars: ExportedConfigDependingVariables{},
+ pctx: pctx,
+ }
+}
+
+func (ev ExportedVariables) asBazel(config Config,
+ stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+ ret := []bazelConstant{}
+ ret = append(ret, ev.exportedStringVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+ ret = append(ret, ev.exportedStringListVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+ ret = append(ret, ev.exportedStringListDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+ // Note: ExportedVariableReferenceDictVars collections can only contain references to other variables and must be printed last
+ ret = append(ret, ev.exportedVariableReferenceDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+ return ret
+}
+
+// ExportStringStaticVariable declares a static string variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) {
+ ev.pctx.StaticVariable(name, value)
+ ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringListStaticVariable declares a static variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) {
+ ev.pctx.StaticVariable(name, strings.Join(value, " "))
+ ev.exportedStringListVars.set(name, value)
+}
+
+// ExportVariableConfigMethod declares a variable whose value is evaluated at
+// runtime via a function with access to the Config and exports it to Bazel's
+// toolchain.
+func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
+ ev.exportedConfigDependingVars.set(name, method)
+ return ev.pctx.VariableConfigMethod(name, method)
+}
+
+// ExportSourcePathVariable declares a static "source path" variable and exports
+// it to Bazel's toolchain.
+func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) {
+ ev.pctx.SourcePathVariable(name, value)
+ ev.exportedStringVars.set(name, value)
+}
+
+// ExportString only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportString(name string, value string) {
+ ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringList only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringList(name string, value []string) {
+ ev.exportedStringListVars.set(name, value)
+}
+
+// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) {
+ ev.exportedStringListDictVars.set(name, value)
+}
+
+// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) {
+ ev.exportedVariableReferenceDictVars.set(name, value)
+}
+
+// ExportedConfigDependingVariables is a mapping of variable names to functions
+// of type func(config Config) string which return the runtime-evaluated string
+// value of a particular variable
+type ExportedConfigDependingVariables map[string]interface{}
+
+func (m ExportedConfigDependingVariables) set(k string, v interface{}) {
+ m[k] = v
+}
+
+// Ensure that string s has no invalid characters to be generated into the bzl file.
+func validateCharacters(s string) string {
+ for _, c := range []string{`\n`, `"`, `\`} {
+ if strings.Contains(s, c) {
+ panic(fmt.Errorf("%s contains illegal character %s", s, c))
+ }
+ }
+ return s
+}
+
+type bazelConstant struct {
+ variableName string
+ internalDefinition string
+ sortLast bool
+}
+
+// ExportedStringVariables is a mapping of variable names to string values
+type ExportedStringVariables map[string]string
+
+func (m ExportedStringVariables) set(k string, v string) {
+ m[k] = v
+}
+
+func (m ExportedStringVariables) asBazel(config Config,
+ stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+ ret := make([]bazelConstant, 0, len(m))
+ for k, variableValue := range m {
+ expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
+ if err != nil {
+ panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
+ }
+ if len(expandedVar) > 1 {
+ panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
+ }
+ ret = append(ret, bazelConstant{
+ variableName: k,
+ internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
+ })
+ }
+ return ret
+}
+
+// ExportedStringListVariables is a mapping of variable names to a list of strings
+type ExportedStringListVariables map[string][]string
+
+func (m ExportedStringListVariables) set(k string, v []string) {
+ m[k] = v
+}
+
+func (m ExportedStringListVariables) asBazel(config Config,
+ stringScope ExportedStringVariables, stringListScope ExportedStringListVariables,
+ exportedVars ExportedConfigDependingVariables) []bazelConstant {
+ ret := make([]bazelConstant, 0, len(m))
+ // For each exported variable, recursively expand elements in the variableValue
+ // list to ensure that interpolated variables are expanded according to their values
+ // in the variable scope.
+ for k, variableValue := range m {
+ var expandedVars []string
+ for _, v := range variableValue {
+ expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
+ if err != nil {
+ panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
+ }
+ expandedVars = append(expandedVars, expandedVar...)
+ }
+ // Assign the list as a bzl-private variable; this variable will be exported
+ // out through a constants struct later.
+ ret = append(ret, bazelConstant{
+ variableName: k,
+ internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
+ })
+ }
+ return ret
+}
+
+// ExportedStringListDictVariables is a mapping from variable names to a
+// dictionary which maps keys to lists of strings
+type ExportedStringListDictVariables map[string]map[string][]string
+
+func (m ExportedStringListDictVariables) set(k string, v map[string][]string) {
+ m[k] = v
+}
+
+// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
+func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+ _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+ ret := make([]bazelConstant, 0, len(m))
+ for k, dict := range m {
+ ret = append(ret, bazelConstant{
+ variableName: k,
+ internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
+ })
+ }
+ return ret
+}
+
+// ExportedVariableReferenceDictVariables is a mapping from variable names to a
+// dictionary which references previously defined variables. This is used to
+// create a Starlark output such as:
+// string_var1 = "string1
+// var_ref_dict_var1 = {
+// "key1": string_var1
+// }
+// This type of variable collection must be expanded last so that it recognizes
+// previously defined variables.
+type ExportedVariableReferenceDictVariables map[string]map[string]string
+
+func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) {
+ m[k] = v
+}
+
+func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+ _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+ ret := make([]bazelConstant, 0, len(m))
+ for n, dict := range m {
+ for k, v := range dict {
+ matches, err := variableReference(v)
+ if err != nil {
+ panic(err)
+ } else if !matches.matches {
+ panic(fmt.Errorf("Expected a variable reference, got %q", v))
+ } else if len(matches.fullVariableReference) != len(v) {
+ panic(fmt.Errorf("Expected only a variable reference, got %q", v))
+ }
+ dict[k] = "_" + matches.variable
+ }
+ ret = append(ret, bazelConstant{
+ variableName: n,
+ internalDefinition: starlark_fmt.PrintDict(dict, 0),
+ sortLast: true,
+ })
+ }
+ return ret
+}
+
+// BazelToolchainVars expands an ExportedVariables collection and returns a string
+// of formatted Starlark variable definitions
+func BazelToolchainVars(config Config, exportedVars ExportedVariables) string {
+ results := exportedVars.asBazel(
+ config,
+ exportedVars.exportedStringVars,
+ exportedVars.exportedStringListVars,
+ exportedVars.exportedConfigDependingVars,
+ )
+
+ sort.Slice(results, func(i, j int) bool {
+ if results[i].sortLast != results[j].sortLast {
+ return !results[i].sortLast
+ }
+ return results[i].variableName < results[j].variableName
+ })
+
+ definitions := make([]string, 0, len(results))
+ constants := make([]string, 0, len(results))
+ for _, b := range results {
+ definitions = append(definitions,
+ fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
+ constants = append(constants,
+ fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
+ }
+
+ // Build the exported constants struct.
+ ret := bazel.GeneratedBazelFileWarning
+ ret += "\n\n"
+ ret += strings.Join(definitions, "\n\n")
+ ret += "\n\n"
+ ret += "constants = struct(\n"
+ ret += strings.Join(constants, "\n")
+ ret += "\n)"
+
+ return ret
+}
+
+type match struct {
+ matches bool
+ fullVariableReference string
+ variable string
+}
+
+func variableReference(input string) (match, error) {
+ // e.g. "${ExternalCflags}"
+ r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
+
+ matches := r.FindStringSubmatch(input)
+ if len(matches) == 0 {
+ return match{}, nil
+ }
+ if len(matches) != 2 {
+ return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
+ }
+ return match{
+ matches: true,
+ fullVariableReference: matches[0],
+ // Index 1 of FindStringSubmatch contains the subexpression match
+ // (variable name) of the capture group.
+ variable: matches[1],
+ }, nil
+}
+
+// expandVar recursively expand interpolated variables in the exportedVars scope.
+//
+// We're using a string slice to track the seen variables to avoid
+// stackoverflow errors with infinite recursion. it's simpler to use a
+// string slice than to handle a pass-by-referenced map, which would make it
+// quite complex to track depth-first interpolations. It's also unlikely the
+// interpolation stacks are deep (n > 1).
+func expandVar(config Config, toExpand string, stringScope ExportedStringVariables,
+ stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) {
+
+ // Internal recursive function.
+ var expandVarInternal func(string, map[string]bool) (string, error)
+ expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
+ var ret string
+ remainingString := toExpand
+ for len(remainingString) > 0 {
+ matches, err := variableReference(remainingString)
+ if err != nil {
+ panic(err)
+ }
+ if !matches.matches {
+ return ret + remainingString, nil
+ }
+ matchIndex := strings.Index(remainingString, matches.fullVariableReference)
+ ret += remainingString[:matchIndex]
+ remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
+
+ variable := matches.variable
+ // toExpand contains a variable.
+ if _, ok := seenVars[variable]; ok {
+ return ret, fmt.Errorf(
+ "Unbounded recursive interpolation of variable: %s", variable)
+ }
+ // A map is passed-by-reference. Create a new map for
+ // this scope to prevent variables seen in one depth-first expansion
+ // to be also treated as "seen" in other depth-first traversals.
+ newSeenVars := map[string]bool{}
+ for k := range seenVars {
+ newSeenVars[k] = true
+ }
+ newSeenVars[variable] = true
+ if unexpandedVars, ok := stringListScope[variable]; ok {
+ expandedVars := []string{}
+ for _, unexpandedVar := range unexpandedVars {
+ expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+ if err != nil {
+ return ret, err
+ }
+ expandedVars = append(expandedVars, expandedVar)
+ }
+ ret += strings.Join(expandedVars, " ")
+ } else if unexpandedVar, ok := stringScope[variable]; ok {
+ expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+ if err != nil {
+ return ret, err
+ }
+ ret += expandedVar
+ } else if unevaluatedVar, ok := exportedVars[variable]; ok {
+ evalFunc := reflect.ValueOf(unevaluatedVar)
+ validateVariableMethod(variable, evalFunc)
+ evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
+ evaluatedValue := evaluatedResult[0].Interface().(string)
+ expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
+ if err != nil {
+ return ret, err
+ }
+ ret += expandedVar
+ } else {
+ return "", fmt.Errorf("Unbound config variable %s", variable)
+ }
+ }
+ return ret, nil
+ }
+ var ret []string
+ for _, v := range strings.Split(toExpand, " ") {
+ val, err := expandVarInternal(v, map[string]bool{})
+ if err != nil {
+ return ret, err
+ }
+ ret = append(ret, val)
+ }
+
+ return ret, nil
+}
+
+func validateVariableMethod(name string, methodValue reflect.Value) {
+ methodType := methodValue.Type()
+ if methodType.Kind() != reflect.Func {
+ panic(fmt.Errorf("method given for variable %s is not a function",
+ name))
+ }
+ if n := methodType.NumIn(); n != 1 {
+ panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
+ name, n))
+ }
+ if n := methodType.NumOut(); n != 1 {
+ panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
+ name, n))
+ }
+ if kind := methodType.Out(0).Kind(); kind != reflect.String {
+ panic(fmt.Errorf("method for variable %s does not return a string",
+ name))
+ }
+}
diff --git a/android/config_bp2build_test.go b/android/config_bp2build_test.go
new file mode 100644
index 0000000..05a1798
--- /dev/null
+++ b/android/config_bp2build_test.go
@@ -0,0 +1,323 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "android/soong/bazel"
+ "testing"
+)
+
+func TestExpandVars(t *testing.T) {
+ android_arm64_config := TestConfig("out", nil, "", nil)
+ android_arm64_config.BuildOS = Android
+ android_arm64_config.BuildArch = Arm64
+
+ testCases := []struct {
+ description string
+ config Config
+ stringScope ExportedStringVariables
+ stringListScope ExportedStringListVariables
+ configVars ExportedConfigDependingVariables
+ toExpand string
+ expectedValues []string
+ }{
+ {
+ description: "no expansion for non-interpolated value",
+ toExpand: "foo",
+ expectedValues: []string{"foo"},
+ },
+ {
+ description: "single level expansion for string var",
+ stringScope: ExportedStringVariables{
+ "foo": "bar",
+ },
+ toExpand: "${foo}",
+ expectedValues: []string{"bar"},
+ },
+ {
+ description: "single level expansion with short-name for string var",
+ stringScope: ExportedStringVariables{
+ "foo": "bar",
+ },
+ toExpand: "${config.foo}",
+ expectedValues: []string{"bar"},
+ },
+ {
+ description: "single level expansion string list var",
+ stringListScope: ExportedStringListVariables{
+ "foo": []string{"bar"},
+ },
+ toExpand: "${foo}",
+ expectedValues: []string{"bar"},
+ },
+ {
+ description: "mixed level expansion for string list var",
+ stringScope: ExportedStringVariables{
+ "foo": "${bar}",
+ "qux": "hello",
+ },
+ stringListScope: ExportedStringListVariables{
+ "bar": []string{"baz", "${qux}"},
+ },
+ toExpand: "${foo}",
+ expectedValues: []string{"baz hello"},
+ },
+ {
+ description: "double level expansion",
+ stringListScope: ExportedStringListVariables{
+ "foo": []string{"${bar}"},
+ "bar": []string{"baz"},
+ },
+ toExpand: "${foo}",
+ expectedValues: []string{"baz"},
+ },
+ {
+ description: "double level expansion with a literal",
+ stringListScope: ExportedStringListVariables{
+ "a": []string{"${b}", "c"},
+ "b": []string{"d"},
+ },
+ toExpand: "${a}",
+ expectedValues: []string{"d c"},
+ },
+ {
+ description: "double level expansion, with two variables in a string",
+ stringListScope: ExportedStringListVariables{
+ "a": []string{"${b} ${c}"},
+ "b": []string{"d"},
+ "c": []string{"e"},
+ },
+ toExpand: "${a}",
+ expectedValues: []string{"d e"},
+ },
+ {
+ description: "triple level expansion with two variables in a string",
+ stringListScope: ExportedStringListVariables{
+ "a": []string{"${b} ${c}"},
+ "b": []string{"${c}", "${d}"},
+ "c": []string{"${d}"},
+ "d": []string{"foo"},
+ },
+ toExpand: "${a}",
+ expectedValues: []string{"foo foo foo"},
+ },
+ {
+ description: "expansion with config depending vars",
+ configVars: ExportedConfigDependingVariables{
+ "a": func(c Config) string { return c.BuildOS.String() },
+ "b": func(c Config) string { return c.BuildArch.String() },
+ },
+ config: android_arm64_config,
+ toExpand: "${a}-${b}",
+ expectedValues: []string{"android-arm64"},
+ },
+ {
+ description: "double level multi type expansion",
+ stringListScope: ExportedStringListVariables{
+ "platform": []string{"${os}-${arch}"},
+ "const": []string{"const"},
+ },
+ configVars: ExportedConfigDependingVariables{
+ "os": func(c Config) string { return c.BuildOS.String() },
+ "arch": func(c Config) string { return c.BuildArch.String() },
+ "foo": func(c Config) string { return "foo" },
+ },
+ config: android_arm64_config,
+ toExpand: "${const}/${platform}/${foo}",
+ expectedValues: []string{"const/android-arm64/foo"},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.description, func(t *testing.T) {
+ output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
+ if len(output) != len(testCase.expectedValues) {
+ t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
+ }
+ for i, actual := range output {
+ expectedValue := testCase.expectedValues[i]
+ if actual != expectedValue {
+ t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
+ }
+ }
+ })
+ }
+}
+
+func TestBazelToolchainVars(t *testing.T) {
+ testCases := []struct {
+ name string
+ config Config
+ vars ExportedVariables
+ expectedOut string
+ }{
+ {
+ name: "exports strings",
+ vars: ExportedVariables{
+ exportedStringVars: ExportedStringVariables{
+ "a": "b",
+ "c": "d",
+ },
+ },
+ expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = "b"
+
+_c = "d"
+
+constants = struct(
+ a = _a,
+ c = _c,
+)`,
+ },
+ {
+ name: "exports string lists",
+ vars: ExportedVariables{
+ exportedStringListVars: ExportedStringListVariables{
+ "a": []string{"b1", "b2"},
+ "c": []string{"d1", "d2"},
+ },
+ },
+ expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = [
+ "b1",
+ "b2",
+]
+
+_c = [
+ "d1",
+ "d2",
+]
+
+constants = struct(
+ a = _a,
+ c = _c,
+)`,
+ },
+ {
+ name: "exports string lists dicts",
+ vars: ExportedVariables{
+ exportedStringListDictVars: ExportedStringListDictVariables{
+ "a": map[string][]string{"b1": {"b2"}},
+ "c": map[string][]string{"d1": {"d2"}},
+ },
+ },
+ expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+ "b1": ["b2"],
+}
+
+_c = {
+ "d1": ["d2"],
+}
+
+constants = struct(
+ a = _a,
+ c = _c,
+)`,
+ },
+ {
+ name: "exports dict with var refs",
+ vars: ExportedVariables{
+ exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+ "a": map[string]string{"b1": "${b2}"},
+ "c": map[string]string{"d1": "${config.d2}"},
+ },
+ },
+ expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+ "b1": _b2,
+}
+
+_c = {
+ "d1": _d2,
+}
+
+constants = struct(
+ a = _a,
+ c = _c,
+)`,
+ },
+ {
+ name: "sorts across types with variable references last",
+ vars: ExportedVariables{
+ exportedStringVars: ExportedStringVariables{
+ "b": "b-val",
+ "d": "d-val",
+ },
+ exportedStringListVars: ExportedStringListVariables{
+ "c": []string{"c-val"},
+ "e": []string{"e-val"},
+ },
+ exportedStringListDictVars: ExportedStringListDictVariables{
+ "a": map[string][]string{"a1": {"a2"}},
+ "f": map[string][]string{"f1": {"f2"}},
+ },
+ exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+ "aa": map[string]string{"b1": "${b}"},
+ "cc": map[string]string{"d1": "${config.d}"},
+ },
+ },
+ expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+ "a1": ["a2"],
+}
+
+_b = "b-val"
+
+_c = ["c-val"]
+
+_d = "d-val"
+
+_e = ["e-val"]
+
+_f = {
+ "f1": ["f2"],
+}
+
+_aa = {
+ "b1": _b,
+}
+
+_cc = {
+ "d1": _d,
+}
+
+constants = struct(
+ a = _a,
+ b = _b,
+ c = _c,
+ d = _d,
+ e = _e,
+ f = _f,
+ aa = _aa,
+ cc = _cc,
+)`,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ out := BazelToolchainVars(tc.config, tc.vars)
+ if out != tc.expectedOut {
+ t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
+ }
+ })
+ }
+}