Add package for printing starlark formatted data

Bug: 216168792
Test: build/bazel/ci/bp2build.sh
Change-Id: I3a06b19396f7ffe1c638042cda7e731dd840f1d6
This commit is contained in:
Liz Kammer 2022-02-03 08:42:10 -05:00
parent db07f002b8
commit 72beb34609
15 changed files with 435 additions and 166 deletions

View file

@ -16,7 +16,9 @@ bootstrap_go_package {
"soong-remoteexec", "soong-remoteexec",
"soong-response", "soong-response",
"soong-shared", "soong-shared",
"soong-starlark-format",
"soong-ui-metrics_proto", "soong-ui-metrics_proto",
"golang-protobuf-proto", "golang-protobuf-proto",
"golang-protobuf-encoding-prototext", "golang-protobuf-encoding-prototext",

View file

@ -38,6 +38,7 @@ import (
"android/soong/android/soongconfig" "android/soong/android/soongconfig"
"android/soong/bazel" "android/soong/bazel"
"android/soong/remoteexec" "android/soong/remoteexec"
"android/soong/starlark_fmt"
) )
// Bool re-exports proptools.Bool for the android package. // Bool re-exports proptools.Bool for the android package.
@ -286,14 +287,12 @@ func saveToBazelConfigFile(config *productVariables, outDir string) error {
} }
} }
//TODO(b/216168792) should use common function to print Starlark code nonArchVariantProductVariablesJson := starlark_fmt.PrintStringList(nonArchVariantProductVariables, 0)
nonArchVariantProductVariablesJson, err := json.MarshalIndent(&nonArchVariantProductVariables, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("cannot marshal product variable data: %s", err.Error()) return fmt.Errorf("cannot marshal product variable data: %s", err.Error())
} }
//TODO(b/216168792) should use common function to print Starlark code archVariantProductVariablesJson := starlark_fmt.PrintStringList(archVariantProductVariables, 0)
archVariantProductVariablesJson, err := json.MarshalIndent(&archVariantProductVariables, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("cannot marshal arch variant product variable data: %s", err.Error()) return fmt.Errorf("cannot marshal arch variant product variable data: %s", err.Error())
} }

View file

@ -386,6 +386,46 @@ func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
})).RunTest(t) })).RunTest(t)
} }
func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) {
bp := `
soong_config_string_variable {
name: "board",
values: ["soc_a", "soc_b", "soc_c", "soc_a"],
}
soong_config_module_type {
name: "acme_test",
module_type: "test",
config_namespace: "acme",
variables: ["board"],
properties: ["cflags", "srcs", "defaults"],
}
`
fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
variables.VendorVars = vars
})
}
GroupFixturePreparers(
fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
PrepareForTestWithDefaults,
FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
}),
FixtureWithRootAndroidBp(bp),
).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
// TODO(b/171232169): improve the error message for non-existent properties
`Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`,
})).RunTest(t)
}
func testConfigWithVendorVars(buildDir, bp string, fs map[string][]byte, vendorVars map[string]map[string]string) Config { func testConfigWithVendorVars(buildDir, bp string, fs map[string][]byte, vendorVars map[string]map[string]string) Config {
config := TestConfig(buildDir, nil, bp, fs) config := TestConfig(buildDir, nil, bp, fs)

View file

@ -10,6 +10,7 @@ bootstrap_go_package {
"blueprint-parser", "blueprint-parser",
"blueprint-proptools", "blueprint-proptools",
"soong-bazel", "soong-bazel",
"soong-starlark-format",
], ],
srcs: [ srcs: [
"config.go", "config.go",

View file

@ -25,6 +25,8 @@ import (
"github.com/google/blueprint" "github.com/google/blueprint"
"github.com/google/blueprint/parser" "github.com/google/blueprint/parser"
"github.com/google/blueprint/proptools" "github.com/google/blueprint/proptools"
"android/soong/starlark_fmt"
) )
const conditionsDefault = "conditions_default" const conditionsDefault = "conditions_default"
@ -177,10 +179,14 @@ func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (err
return []error{fmt.Errorf("values property must be set")} return []error{fmt.Errorf("values property must be set")}
} }
vals := make(map[string]bool, len(stringProps.Values))
for _, name := range stringProps.Values { for _, name := range stringProps.Values {
if err := checkVariableName(name); err != nil { if err := checkVariableName(name); err != nil {
return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)} return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
} else if _, ok := vals[name]; ok {
return []error{fmt.Errorf("soong_config_string_variable: values property error: duplicate value: %q", name)}
} }
vals[name] = true
} }
v.variables[base.variable] = &stringVariable{ v.variables[base.variable] = &stringVariable{
@ -235,7 +241,12 @@ type SoongConfigDefinition struct {
// string vars, bool vars and value vars created by every // string vars, bool vars and value vars created by every
// soong_config_module_type in this build. // soong_config_module_type in this build.
type Bp2BuildSoongConfigDefinitions struct { type Bp2BuildSoongConfigDefinitions struct {
StringVars map[string]map[string]bool // varCache contains a cache of string variables namespace + property
// The same variable may be used in multiple module types (for example, if need support
// for cc_default and java_default), only need to process once
varCache map[string]bool
StringVars map[string][]string
BoolVars map[string]bool BoolVars map[string]bool
ValueVars map[string]bool ValueVars map[string]bool
} }
@ -253,7 +264,7 @@ func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition)
defer bp2buildSoongConfigVarsLock.Unlock() defer bp2buildSoongConfigVarsLock.Unlock()
if defs.StringVars == nil { if defs.StringVars == nil {
defs.StringVars = make(map[string]map[string]bool) defs.StringVars = make(map[string][]string)
} }
if defs.BoolVars == nil { if defs.BoolVars == nil {
defs.BoolVars = make(map[string]bool) defs.BoolVars = make(map[string]bool)
@ -261,15 +272,24 @@ func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition)
if defs.ValueVars == nil { if defs.ValueVars == nil {
defs.ValueVars = make(map[string]bool) defs.ValueVars = make(map[string]bool)
} }
if defs.varCache == nil {
defs.varCache = make(map[string]bool)
}
for _, moduleType := range mtDef.ModuleTypes { for _, moduleType := range mtDef.ModuleTypes {
for _, v := range moduleType.Variables { for _, v := range moduleType.Variables {
key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__") key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
// The same variable may be used in multiple module types (for example, if need support
// for cc_default and java_default), only need to process once
if _, keyInCache := defs.varCache[key]; keyInCache {
continue
} else {
defs.varCache[key] = true
}
if strVar, ok := v.(*stringVariable); ok { if strVar, ok := v.(*stringVariable); ok {
if _, ok := defs.StringVars[key]; !ok {
defs.StringVars[key] = make(map[string]bool, 0)
}
for _, value := range strVar.values { for _, value := range strVar.values {
defs.StringVars[key][value] = true defs.StringVars[key] = append(defs.StringVars[key], value)
} }
} else if _, ok := v.(*boolVariable); ok { } else if _, ok := v.(*boolVariable); ok {
defs.BoolVars[key] = true defs.BoolVars[key] = true
@ -302,29 +322,16 @@ func sortedStringKeys(m interface{}) []string {
// String emits the Soong config variable definitions as Starlark dictionaries. // String emits the Soong config variable definitions as Starlark dictionaries.
func (defs Bp2BuildSoongConfigDefinitions) String() string { func (defs Bp2BuildSoongConfigDefinitions) String() string {
ret := "" ret := ""
ret += "soong_config_bool_variables = {\n" ret += "soong_config_bool_variables = "
for _, boolVar := range sortedStringKeys(defs.BoolVars) { ret += starlark_fmt.PrintBoolDict(defs.BoolVars, 0)
ret += fmt.Sprintf(" \"%s\": True,\n", boolVar) ret += "\n\n"
}
ret += "}\n"
ret += "\n"
ret += "soong_config_value_variables = {\n" ret += "soong_config_value_variables = "
for _, valueVar := range sortedStringKeys(defs.ValueVars) { ret += starlark_fmt.PrintBoolDict(defs.ValueVars, 0)
ret += fmt.Sprintf(" \"%s\": True,\n", valueVar) ret += "\n\n"
}
ret += "}\n"
ret += "\n"
ret += "soong_config_string_variables = {\n" ret += "soong_config_string_variables = "
for _, stringVar := range sortedStringKeys(defs.StringVars) { ret += starlark_fmt.PrintStringListDict(defs.StringVars, 0)
ret += fmt.Sprintf(" \"%s\": [\n", stringVar)
for _, choice := range sortedStringKeys(defs.StringVars[stringVar]) {
ret += fmt.Sprintf(" \"%s\",\n", choice)
}
ret += fmt.Sprintf(" ],\n")
}
ret += "}"
return ret return ret
} }

View file

@ -367,19 +367,19 @@ func Test_PropertiesToApply(t *testing.T) {
func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) { func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string
defs Bp2BuildSoongConfigDefinitions defs Bp2BuildSoongConfigDefinitions
expected string expected string
}{ }{
{ {
desc: "all empty",
defs: Bp2BuildSoongConfigDefinitions{}, defs: Bp2BuildSoongConfigDefinitions{},
expected: `soong_config_bool_variables = { expected: `soong_config_bool_variables = {}
}
soong_config_value_variables = { soong_config_value_variables = {}
}
soong_config_string_variables = { soong_config_string_variables = {}`}, {
}`}, { desc: "only bool",
defs: Bp2BuildSoongConfigDefinitions{ defs: Bp2BuildSoongConfigDefinitions{
BoolVars: map[string]bool{ BoolVars: map[string]bool{
"bool_var": true, "bool_var": true,
@ -389,39 +389,35 @@ soong_config_string_variables = {
"bool_var": True, "bool_var": True,
} }
soong_config_value_variables = { soong_config_value_variables = {}
}
soong_config_string_variables = { soong_config_string_variables = {}`}, {
}`}, { desc: "only value vars",
defs: Bp2BuildSoongConfigDefinitions{ defs: Bp2BuildSoongConfigDefinitions{
ValueVars: map[string]bool{ ValueVars: map[string]bool{
"value_var": true, "value_var": true,
}, },
}, },
expected: `soong_config_bool_variables = { expected: `soong_config_bool_variables = {}
}
soong_config_value_variables = { soong_config_value_variables = {
"value_var": True, "value_var": True,
} }
soong_config_string_variables = { soong_config_string_variables = {}`}, {
}`}, { desc: "only string vars",
defs: Bp2BuildSoongConfigDefinitions{ defs: Bp2BuildSoongConfigDefinitions{
StringVars: map[string]map[string]bool{ StringVars: map[string][]string{
"string_var": map[string]bool{ "string_var": []string{
"choice1": true, "choice1",
"choice2": true, "choice2",
"choice3": true, "choice3",
}, },
}, },
}, },
expected: `soong_config_bool_variables = { expected: `soong_config_bool_variables = {}
}
soong_config_value_variables = { soong_config_value_variables = {}
}
soong_config_string_variables = { soong_config_string_variables = {
"string_var": [ "string_var": [
@ -430,6 +426,7 @@ soong_config_string_variables = {
"choice3", "choice3",
], ],
}`}, { }`}, {
desc: "all vars",
defs: Bp2BuildSoongConfigDefinitions{ defs: Bp2BuildSoongConfigDefinitions{
BoolVars: map[string]bool{ BoolVars: map[string]bool{
"bool_var_one": true, "bool_var_one": true,
@ -438,15 +435,15 @@ soong_config_string_variables = {
"value_var_one": true, "value_var_one": true,
"value_var_two": true, "value_var_two": true,
}, },
StringVars: map[string]map[string]bool{ StringVars: map[string][]string{
"string_var_one": map[string]bool{ "string_var_one": []string{
"choice1": true, "choice1",
"choice2": true, "choice2",
"choice3": true, "choice3",
}, },
"string_var_two": map[string]bool{ "string_var_two": []string{
"foo": true, "foo",
"bar": true, "bar",
}, },
}, },
}, },
@ -466,15 +463,17 @@ soong_config_string_variables = {
"choice3", "choice3",
], ],
"string_var_two": [ "string_var_two": [
"bar",
"foo", "foo",
"bar",
], ],
}`}, }`},
} }
for _, test := range testCases { for _, test := range testCases {
actual := test.defs.String() t.Run(test.desc, func(t *testing.T) {
if actual != test.expected { actual := test.defs.String()
t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual) if actual != test.expected {
} t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual)
}
})
} }
} }

View file

@ -28,6 +28,7 @@ bootstrap_go_package {
"soong-genrule", "soong-genrule",
"soong-python", "soong-python",
"soong-sh", "soong-sh",
"soong-starlark-format",
"soong-ui-metrics", "soong-ui-metrics",
], ],
testSrcs: [ testSrcs: [

View file

@ -27,6 +27,7 @@ import (
"android/soong/android" "android/soong/android"
"android/soong/bazel" "android/soong/bazel"
"android/soong/starlark_fmt"
"github.com/google/blueprint" "github.com/google/blueprint"
"github.com/google/blueprint/proptools" "github.com/google/blueprint/proptools"
@ -559,48 +560,27 @@ func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (
return "", nil return "", nil
} }
var ret string
switch propertyValue.Kind() { switch propertyValue.Kind() {
case reflect.String: case reflect.String:
ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())) return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
case reflect.Bool: case reflect.Bool:
ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface())) return starlark_fmt.PrintBool(propertyValue.Bool()), nil
case reflect.Int, reflect.Uint, reflect.Int64: case reflect.Int, reflect.Uint, reflect.Int64:
ret = fmt.Sprintf("%v", propertyValue.Interface()) return fmt.Sprintf("%v", propertyValue.Interface()), nil
case reflect.Ptr: case reflect.Ptr:
return prettyPrint(propertyValue.Elem(), indent, emitZeroValues) return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
case reflect.Slice: case reflect.Slice:
if propertyValue.Len() == 0 { elements := make([]string, 0, propertyValue.Len())
return "[]", nil for i := 0; i < propertyValue.Len(); i++ {
} val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
if propertyValue.Len() == 1 {
// Single-line list for list with only 1 element
ret += "["
indexedValue, err := prettyPrint(propertyValue.Index(0), indent, emitZeroValues)
if err != nil { if err != nil {
return "", err return "", err
} }
ret += indexedValue if val != "" {
ret += "]" elements = append(elements, val)
} else {
// otherwise, use a multiline list.
ret += "[\n"
for i := 0; i < propertyValue.Len(); i++ {
indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1, emitZeroValues)
if err != nil {
return "", err
}
if indexedValue != "" {
ret += makeIndent(indent + 1)
ret += indexedValue
ret += ",\n"
}
} }
ret += makeIndent(indent)
ret += "]"
} }
return starlark_fmt.PrintList(elements, indent, "%s"), nil
case reflect.Struct: case reflect.Struct:
// Special cases where the bp2build sends additional information to the codegenerator // Special cases where the bp2build sends additional information to the codegenerator
@ -611,18 +591,12 @@ func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (
return fmt.Sprintf("%q", label.Label), nil return fmt.Sprintf("%q", label.Label), nil
} }
ret = "{\n"
// Sort and print the struct props by the key. // Sort and print the struct props by the key.
structProps := extractStructProperties(propertyValue, indent) structProps := extractStructProperties(propertyValue, indent)
if len(structProps) == 0 { if len(structProps) == 0 {
return "", nil return "", nil
} }
for _, k := range android.SortedStringKeys(structProps) { return starlark_fmt.PrintDict(structProps, indent), nil
ret += makeIndent(indent + 1)
ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
}
ret += makeIndent(indent)
ret += "}"
case reflect.Interface: case reflect.Interface:
// TODO(b/164227191): implement pretty print for interfaces. // TODO(b/164227191): implement pretty print for interfaces.
// Interfaces are used for for arch, multilib and target properties. // Interfaces are used for for arch, multilib and target properties.
@ -631,7 +605,6 @@ func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (
return "", fmt.Errorf( return "", fmt.Errorf(
"unexpected kind for property struct field: %s", propertyValue.Kind()) "unexpected kind for property struct field: %s", propertyValue.Kind())
} }
return ret, nil
} }
// Converts a reflected property struct value into a map of property names and property values, // Converts a reflected property struct value into a map of property names and property values,
@ -736,13 +709,6 @@ func escapeString(s string) string {
return strings.ReplaceAll(s, "\"", "\\\"") return strings.ReplaceAll(s, "\"", "\\\"")
} }
func makeIndent(indent int) string {
if indent < 0 {
panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
}
return strings.Repeat(" ", indent)
}
func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string { func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
name := "" name := ""
if c.ModuleSubDir(logicModule) != "" { if c.ModuleSubDir(logicModule) != "" {

View file

@ -6,6 +6,7 @@ import (
"android/soong/android" "android/soong/android"
"android/soong/bazel" "android/soong/bazel"
"android/soong/starlark_fmt"
) )
// Configurability support for bp2build. // Configurability support for bp2build.
@ -250,10 +251,10 @@ func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *stri
} else if defaultValue != nil { } else if defaultValue != nil {
// Print an explicit empty list (the default value) even if the value is // Print an explicit empty list (the default value) even if the value is
// empty, to avoid errors about not finding a configuration that matches. // empty, to avoid errors about not finding a configuration that matches.
ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), bazel.ConditionsDefaultSelectKey, *defaultValue) ret += fmt.Sprintf("%s\"%s\": %s,\n", starlark_fmt.Indention(indent+1), bazel.ConditionsDefaultSelectKey, *defaultValue)
} }
ret += makeIndent(indent) ret += starlark_fmt.Indention(indent)
ret += "})" ret += "})"
return ret, nil return ret, nil
@ -262,7 +263,7 @@ func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *stri
// prettyPrintSelectEntry converts a reflect.Value into an entry in a select map // prettyPrintSelectEntry converts a reflect.Value into an entry in a select map
// with a provided key. // with a provided key.
func prettyPrintSelectEntry(value reflect.Value, key string, indent int, emitZeroValues bool) (string, error) { func prettyPrintSelectEntry(value reflect.Value, key string, indent int, emitZeroValues bool) (string, error) {
s := makeIndent(indent + 1) s := starlark_fmt.Indention(indent + 1)
v, err := prettyPrint(value, indent+1, emitZeroValues) v, err := prettyPrint(value, indent+1, emitZeroValues)
if err != nil { if err != nil {
return "", err return "", err

View file

@ -8,6 +8,7 @@ bootstrap_go_package {
deps: [ deps: [
"soong-android", "soong-android",
"soong-remoteexec", "soong-remoteexec",
"soong-starlark-format",
], ],
srcs: [ srcs: [
"bp2build.go", "bp2build.go",

View file

@ -22,14 +22,11 @@ import (
"strings" "strings"
"android/soong/android" "android/soong/android"
"android/soong/starlark_fmt"
"github.com/google/blueprint" "github.com/google/blueprint"
) )
const (
bazelIndent = 4
)
type bazelVarExporter interface { type bazelVarExporter interface {
asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
} }
@ -73,21 +70,6 @@ func (m exportedStringVariables) Set(k string, v string) {
m[k] = v 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(config android.Config, func (m exportedStringVariables) asBazel(config android.Config,
stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant { stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
ret := make([]bazelConstant, 0, len(m)) ret := make([]bazelConstant, 0, len(m))
@ -139,7 +121,7 @@ func (m exportedStringListVariables) asBazel(config android.Config,
// out through a constants struct later. // out through a constants struct later.
ret = append(ret, bazelConstant{ ret = append(ret, bazelConstant{
variableName: k, variableName: k,
internalDefinition: printBazelList(expandedVars, 0), internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
}) })
} }
return ret return ret
@ -173,17 +155,6 @@ func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
m[k] = v 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 // Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables, func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
_ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant { _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
@ -191,7 +162,7 @@ func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStr
for k, dict := range m { for k, dict := range m {
ret = append(ret, bazelConstant{ ret = append(ret, bazelConstant{
variableName: k, variableName: k,
internalDefinition: printBazelStringListDict(dict), internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
}) })
} }
return ret return ret
@ -223,7 +194,7 @@ func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string
definitions = append(definitions, definitions = append(definitions,
fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition)) fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
constants = append(constants, constants = append(constants,
fmt.Sprintf("%[1]s%[2]s = _%[2]s,", bazelIndention(1), b.variableName)) fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
} }
// Build the exported constants struct. // Build the exported constants struct.

View file

@ -211,15 +211,11 @@ constants = struct(
expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT. expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
_a = { _a = {
"b1": [ "b1": ["b2"],
"b2",
],
} }
_c = { _c = {
"d1": [ "d1": ["d2"],
"d2",
],
} }
constants = struct( constants = struct(
@ -246,27 +242,19 @@ constants = struct(
expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT. expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
_a = { _a = {
"a1": [ "a1": ["a2"],
"a2",
],
} }
_b = "b-val" _b = "b-val"
_c = [ _c = ["c-val"]
"c-val",
]
_d = "d-val" _d = "d-val"
_e = [ _e = ["e-val"]
"e-val",
]
_f = { _f = {
"f1": [ "f1": ["f2"],
"f2",
],
} }
constants = struct( constants = struct(

28
starlark_fmt/Android.bp Normal file
View file

@ -0,0 +1,28 @@
// Copyright 2022 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 {
default_applicable_licenses: ["Android-Apache-2.0"],
}
bootstrap_go_package {
name: "soong-starlark-format",
pkgPath: "android/soong/starlark_fmt",
srcs: [
"format.go",
],
testSrcs: [
"format_test.go",
],
}

96
starlark_fmt/format.go Normal file
View file

@ -0,0 +1,96 @@
// Copyright 2022 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 starlark_fmt
import (
"fmt"
"sort"
"strings"
)
const (
indent = 4
)
// Indention returns an indent string of the specified level.
func Indention(level int) string {
if level < 0 {
panic(fmt.Errorf("indent level cannot be less than 0, but got %d", level))
}
return strings.Repeat(" ", level*indent)
}
// PrintBool returns a Starlark compatible bool string.
func PrintBool(item bool) string {
return strings.Title(fmt.Sprintf("%t", item))
}
// PrintsStringList returns a Starlark-compatible string of a list of Strings/Labels.
func PrintStringList(items []string, indentLevel int) string {
return PrintList(items, indentLevel, `"%s"`)
}
// PrintList returns a Starlark-compatible string of list formmated as requested.
func PrintList(items []string, indentLevel int, formatString string) string {
if len(items) == 0 {
return "[]"
} else if len(items) == 1 {
return fmt.Sprintf("["+formatString+"]", items[0])
}
list := make([]string, 0, len(items)+2)
list = append(list, "[")
innerIndent := Indention(indentLevel + 1)
for _, item := range items {
list = append(list, fmt.Sprintf(`%s`+formatString+`,`, innerIndent, item))
}
list = append(list, Indention(indentLevel)+"]")
return strings.Join(list, "\n")
}
// PrintStringListDict returns a Starlark-compatible string formatted as dictionary with
// string keys and list of string values.
func PrintStringListDict(dict map[string][]string, indentLevel int) string {
formattedValueDict := make(map[string]string, len(dict))
for k, v := range dict {
formattedValueDict[k] = PrintStringList(v, indentLevel+1)
}
return PrintDict(formattedValueDict, indentLevel)
}
// PrintBoolDict returns a starlark-compatible string containing a dictionary with string keys and
// values printed with no additional formatting.
func PrintBoolDict(dict map[string]bool, indentLevel int) string {
formattedValueDict := make(map[string]string, len(dict))
for k, v := range dict {
formattedValueDict[k] = PrintBool(v)
}
return PrintDict(formattedValueDict, indentLevel)
}
// PrintDict returns a starlark-compatible string containing a dictionary with string keys and
// values printed with no additional formatting.
func PrintDict(dict map[string]string, indentLevel int) string {
if len(dict) == 0 {
return "{}"
}
items := make([]string, 0, len(dict))
for k, v := range dict {
items = append(items, fmt.Sprintf(`%s"%s": %s,`, Indention(indentLevel+1), k, v))
}
sort.Strings(items)
return fmt.Sprintf(`{
%s
%s}`, strings.Join(items, "\n"), Indention(indentLevel))
}

169
starlark_fmt/format_test.go Normal file
View file

@ -0,0 +1,169 @@
// Copyright 2022 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 starlark_fmt
import (
"testing"
)
func TestPrintEmptyStringList(t *testing.T) {
in := []string{}
indentLevel := 0
out := PrintStringList(in, indentLevel)
expectedOut := "[]"
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintSingleElementStringList(t *testing.T) {
in := []string{"a"}
indentLevel := 0
out := PrintStringList(in, indentLevel)
expectedOut := `["a"]`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintMultiElementStringList(t *testing.T) {
in := []string{"a", "b"}
indentLevel := 0
out := PrintStringList(in, indentLevel)
expectedOut := `[
"a",
"b",
]`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintEmptyList(t *testing.T) {
in := []string{}
indentLevel := 0
out := PrintList(in, indentLevel, "%s")
expectedOut := "[]"
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintSingleElementList(t *testing.T) {
in := []string{"1"}
indentLevel := 0
out := PrintList(in, indentLevel, "%s")
expectedOut := `[1]`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintMultiElementList(t *testing.T) {
in := []string{"1", "2"}
indentLevel := 0
out := PrintList(in, indentLevel, "%s")
expectedOut := `[
1,
2,
]`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestListWithNonZeroIndent(t *testing.T) {
in := []string{"1", "2"}
indentLevel := 1
out := PrintList(in, indentLevel, "%s")
expectedOut := `[
1,
2,
]`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestStringListDictEmpty(t *testing.T) {
in := map[string][]string{}
indentLevel := 0
out := PrintStringListDict(in, indentLevel)
expectedOut := `{}`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestStringListDict(t *testing.T) {
in := map[string][]string{
"key1": []string{},
"key2": []string{"a"},
"key3": []string{"1", "2"},
}
indentLevel := 0
out := PrintStringListDict(in, indentLevel)
expectedOut := `{
"key1": [],
"key2": ["a"],
"key3": [
"1",
"2",
],
}`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintDict(t *testing.T) {
in := map[string]string{
"key1": `""`,
"key2": `"a"`,
"key3": `[
1,
2,
]`,
}
indentLevel := 0
out := PrintDict(in, indentLevel)
expectedOut := `{
"key1": "",
"key2": "a",
"key3": [
1,
2,
],
}`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}
func TestPrintDictWithIndent(t *testing.T) {
in := map[string]string{
"key1": `""`,
"key2": `"a"`,
}
indentLevel := 1
out := PrintDict(in, indentLevel)
expectedOut := `{
"key1": "",
"key2": "a",
}`
if out != expectedOut {
t.Errorf("Expected %q, got %q", expectedOut, out)
}
}