Support exporting device arch toolchain info

Test: bp2build.sh
Bug: 187084665
Change-Id: I49e75f603accab519784dbaf25d85db0010587a3
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
index e7e94a8..d19f5ac 100644
--- a/cc/config/bp2build.go
+++ b/cc/config/bp2build.go
@@ -15,98 +15,182 @@
 package config
 
 import (
-	"android/soong/android"
 	"fmt"
 	"regexp"
+	"sort"
 	"strings"
 )
 
+const (
+	bazelIndent = 4
+)
+
+type bazelVarExporter interface {
+	asBazel(exportedStringVariables, exportedStringListVariables) []bazelConstant
+}
+
 // Helpers for exporting cc configuration information to Bazel.
 var (
 	// Map containing toolchain variables that are independent of the
 	// environment variables of the build.
-	exportedStringListVars = exportedStringListVariables{}
-	exportedStringVars     = exportedStringVariables{}
+	exportedStringListVars     = exportedStringListVariables{}
+	exportedStringVars         = exportedStringVariables{}
+	exportedStringListDictVars = exportedStringListDictVariables{}
 )
 
+// 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
+}
+
 type exportedStringVariables map[string]string
-type exportedStringListVariables map[string][]string
 
 func (m exportedStringVariables) Set(k string, v string) {
 	m[k] = v
 }
 
+func bazelIndention(level int) string {
+	return strings.Repeat(" ", level*bazelIndent)
+}
+
+func printBazelList(items []string, indentLevel int) string {
+	list := make([]string, 0, len(items)+2)
+	list = append(list, "[")
+	innerIndent := bazelIndention(indentLevel + 1)
+	for _, item := range items {
+		list = append(list, fmt.Sprintf(`%s"%s",`, innerIndent, item))
+	}
+	list = append(list, bazelIndention(indentLevel)+"]")
+	return strings.Join(list, "\n")
+}
+
+func (m exportedStringVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, variableValue := range m {
+		expandedVar := expandVar(variableValue, exportedStringVars, exportedStringListVars)
+		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
+}
+
 // Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
 func exportStringStaticVariable(name string, value string) {
 	pctx.StaticVariable(name, value)
 	exportedStringVars.Set(name, value)
 }
 
+type exportedStringListVariables map[string][]string
+
 func (m exportedStringListVariables) Set(k string, v []string) {
 	m[k] = v
 }
 
+func (m exportedStringListVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []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 {
+			expandedVars = append(expandedVars, expandVar(v, stringScope, stringListScope)...)
+		}
+		// 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: printBazelList(expandedVars, 0),
+		})
+	}
+	return ret
+}
+
 // Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
 func exportStringListStaticVariable(name string, value []string) {
 	pctx.StaticVariable(name, strings.Join(value, " "))
 	exportedStringListVars.Set(name, value)
 }
 
+type exportedStringListDictVariables map[string]map[string][]string
+
+func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
+	m[k] = v
+}
+
+func printBazelStringListDict(dict map[string][]string) string {
+	bazelDict := make([]string, 0, len(dict)+2)
+	bazelDict = append(bazelDict, "{")
+	for k, v := range dict {
+		bazelDict = append(bazelDict,
+			fmt.Sprintf(`%s"%s": %s,`, bazelIndention(1), k, printBazelList(v, 1)))
+	}
+	bazelDict = append(bazelDict, "}")
+	return strings.Join(bazelDict, "\n")
+}
+
+// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
+func (m exportedStringListDictVariables) asBazel(_ exportedStringVariables, _ exportedStringListVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, dict := range m {
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: printBazelStringListDict(dict),
+		})
+	}
+	return ret
+}
+
 // BazelCcToolchainVars generates bzl file content containing variables for
 // Bazel's cc_toolchain configuration.
 func BazelCcToolchainVars() string {
+	return bazelToolchainVars(
+		exportedStringListDictVars,
+		exportedStringListVars,
+		exportedStringVars)
+}
+
+func bazelToolchainVars(vars ...bazelVarExporter) string {
 	ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
 
-	// Ensure that string s has no invalid characters to be generated into the bzl file.
-	validateCharacters := func(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
+	results := []bazelConstant{}
+	for _, v := range vars {
+		results = append(results, v.asBazel(exportedStringVars, exportedStringListVars)...)
 	}
 
-	// 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 := range android.SortedStringKeys(exportedStringListVars) {
-		variableValue := exportedStringListVars[k]
-		var expandedVars []string
-		for _, v := range variableValue {
-			expandedVars = append(expandedVars, expandVar(v, exportedStringVars, exportedStringListVars)...)
-		}
-		// Build the list for this variable.
-		list := "["
-		for _, flag := range expandedVars {
-			list += fmt.Sprintf("\n    \"%s\",", validateCharacters(flag))
-		}
-		list += "\n]"
-		// Assign the list as a bzl-private variable; this variable will be exported
-		// out through a constants struct later.
-		ret += fmt.Sprintf("_%s = %s\n", k, list)
-		ret += "\n"
-	}
+	sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
 
-	for _, k := range android.SortedStringKeys(exportedStringVars) {
-		variableValue := exportedStringVars[k]
-		expandedVar := expandVar(variableValue, exportedStringVars, exportedStringListVars)
-		if len(expandedVar) > 1 {
-			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
-		}
-		ret += fmt.Sprintf("_%s = \"%s\"\n", k, validateCharacters(expandedVar[0]))
-		ret += "\n"
+	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,", bazelIndention(1), b.variableName))
 	}
 
 	// Build the exported constants struct.
+	ret += strings.Join(definitions, "\n\n")
+	ret += "\n\n"
 	ret += "constants = struct(\n"
-	for _, k := range android.SortedStringKeys(exportedStringVars) {
-		ret += fmt.Sprintf("    %s = _%s,\n", k, k)
-	}
-	for _, k := range android.SortedStringKeys(exportedStringListVars) {
-		ret += fmt.Sprintf("    %s = _%s,\n", k, k)
-	}
-	ret += ")"
+	ret += strings.Join(constants, "\n")
+	ret += "\n)"
+
 	return ret
 }