diff options
44 files changed, 691 insertions, 305 deletions
diff --git a/android/Android.bp b/android/Android.bp index da369592a..d3540b211 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -16,7 +16,9 @@ bootstrap_go_package { "soong-remoteexec", "soong-response", "soong-shared", + "soong-starlark-format", "soong-ui-metrics_proto", + "golang-protobuf-proto", "golang-protobuf-encoding-prototext", diff --git a/android/bazel.go b/android/bazel.go index 3046edb65..7e5736f58 100644 --- a/android/bazel.go +++ b/android/bazel.go @@ -288,6 +288,8 @@ var ( "development/samples/WiFiDirectDemo": Bp2BuildDefaultTrue, "development/sdk": Bp2BuildDefaultTrueRecursively, "external/arm-optimized-routines": Bp2BuildDefaultTrueRecursively, + "external/auto/common": Bp2BuildDefaultTrueRecursively, + "external/auto/service": Bp2BuildDefaultTrueRecursively, "external/boringssl": Bp2BuildDefaultTrueRecursively, "external/bouncycastle": Bp2BuildDefaultTrue, "external/brotli": Bp2BuildDefaultTrue, @@ -300,6 +302,7 @@ var ( "external/icu": Bp2BuildDefaultTrueRecursively, "external/icu/android_icu4j": Bp2BuildDefaultFalse, // java rules incomplete "external/icu/icu4j": Bp2BuildDefaultFalse, // java rules incomplete + "external/javapoet": Bp2BuildDefaultTrueRecursively, "external/jemalloc_new": Bp2BuildDefaultTrueRecursively, "external/jsoncpp": Bp2BuildDefaultTrueRecursively, "external/libcap": Bp2BuildDefaultTrueRecursively, diff --git a/android/config.go b/android/config.go index 10e074cb1..f10732bd7 100644 --- a/android/config.go +++ b/android/config.go @@ -38,6 +38,7 @@ import ( "android/soong/android/soongconfig" "android/soong/bazel" "android/soong/remoteexec" + "android/soong/starlark_fmt" ) // 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, err := json.MarshalIndent(&nonArchVariantProductVariables, "", " ") + nonArchVariantProductVariablesJson := starlark_fmt.PrintStringList(nonArchVariantProductVariables, 0) if err != nil { return fmt.Errorf("cannot marshal product variable data: %s", err.Error()) } - //TODO(b/216168792) should use common function to print Starlark code - archVariantProductVariablesJson, err := json.MarshalIndent(&archVariantProductVariables, "", " ") + archVariantProductVariablesJson := starlark_fmt.PrintStringList(archVariantProductVariables, 0) if err != nil { return fmt.Errorf("cannot marshal arch variant product variable data: %s", err.Error()) } diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go index acb9d180e..ceb8e45a5 100644 --- a/android/soong_config_modules_test.go +++ b/android/soong_config_modules_test.go @@ -386,6 +386,46 @@ func TestNonExistentPropertyInSoongConfigModule(t *testing.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 { config := TestConfig(buildDir, nil, bp, fs) diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp index 9bf334480..8fe1ff1eb 100644 --- a/android/soongconfig/Android.bp +++ b/android/soongconfig/Android.bp @@ -10,6 +10,7 @@ bootstrap_go_package { "blueprint-parser", "blueprint-proptools", "soong-bazel", + "soong-starlark-format", ], srcs: [ "config.go", diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go index 09a505722..212b752d6 100644 --- a/android/soongconfig/modules.go +++ b/android/soongconfig/modules.go @@ -25,6 +25,8 @@ import ( "github.com/google/blueprint" "github.com/google/blueprint/parser" "github.com/google/blueprint/proptools" + + "android/soong/starlark_fmt" ) 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")} } + vals := make(map[string]bool, len(stringProps.Values)) for _, name := range stringProps.Values { if err := checkVariableName(name); err != nil { 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{ @@ -235,7 +241,12 @@ type SoongConfigDefinition struct { // string vars, bool vars and value vars created by every // soong_config_module_type in this build. 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 ValueVars map[string]bool } @@ -253,7 +264,7 @@ func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) defer bp2buildSoongConfigVarsLock.Unlock() if defs.StringVars == nil { - defs.StringVars = make(map[string]map[string]bool) + defs.StringVars = make(map[string][]string) } if defs.BoolVars == nil { defs.BoolVars = make(map[string]bool) @@ -261,15 +272,24 @@ func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) if defs.ValueVars == nil { defs.ValueVars = make(map[string]bool) } + if defs.varCache == nil { + defs.varCache = make(map[string]bool) + } for _, moduleType := range mtDef.ModuleTypes { for _, v := range moduleType.Variables { 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 _, ok := defs.StringVars[key]; !ok { - defs.StringVars[key] = make(map[string]bool, 0) - } for _, value := range strVar.values { - defs.StringVars[key][value] = true + defs.StringVars[key] = append(defs.StringVars[key], value) } } else if _, ok := v.(*boolVariable); ok { defs.BoolVars[key] = true @@ -302,29 +322,16 @@ func sortedStringKeys(m interface{}) []string { // String emits the Soong config variable definitions as Starlark dictionaries. func (defs Bp2BuildSoongConfigDefinitions) String() string { ret := "" - ret += "soong_config_bool_variables = {\n" - for _, boolVar := range sortedStringKeys(defs.BoolVars) { - ret += fmt.Sprintf(" \"%s\": True,\n", boolVar) - } - ret += "}\n" - ret += "\n" + ret += "soong_config_bool_variables = " + ret += starlark_fmt.PrintBoolDict(defs.BoolVars, 0) + ret += "\n\n" - ret += "soong_config_value_variables = {\n" - for _, valueVar := range sortedStringKeys(defs.ValueVars) { - ret += fmt.Sprintf(" \"%s\": True,\n", valueVar) - } - ret += "}\n" - ret += "\n" + ret += "soong_config_value_variables = " + ret += starlark_fmt.PrintBoolDict(defs.ValueVars, 0) + ret += "\n\n" - ret += "soong_config_string_variables = {\n" - for _, stringVar := range sortedStringKeys(defs.StringVars) { - ret += fmt.Sprintf(" \"%s\": [\n", stringVar) - for _, choice := range sortedStringKeys(defs.StringVars[stringVar]) { - ret += fmt.Sprintf(" \"%s\",\n", choice) - } - ret += fmt.Sprintf(" ],\n") - } - ret += "}" + ret += "soong_config_string_variables = " + ret += starlark_fmt.PrintStringListDict(defs.StringVars, 0) return ret } diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go index b14f8b43b..a7800e8ef 100644 --- a/android/soongconfig/modules_test.go +++ b/android/soongconfig/modules_test.go @@ -367,19 +367,19 @@ func Test_PropertiesToApply(t *testing.T) { func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) { testCases := []struct { + desc string defs Bp2BuildSoongConfigDefinitions expected string }{ { + desc: "all empty", 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{ BoolVars: map[string]bool{ "bool_var": true, @@ -389,39 +389,35 @@ soong_config_string_variables = { "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{ ValueVars: map[string]bool{ "value_var": true, }, }, - expected: `soong_config_bool_variables = { -} + expected: `soong_config_bool_variables = {} soong_config_value_variables = { "value_var": True, } -soong_config_string_variables = { -}`}, { +soong_config_string_variables = {}`}, { + desc: "only string vars", defs: Bp2BuildSoongConfigDefinitions{ - StringVars: map[string]map[string]bool{ - "string_var": map[string]bool{ - "choice1": true, - "choice2": true, - "choice3": true, + StringVars: map[string][]string{ + "string_var": []string{ + "choice1", + "choice2", + "choice3", }, }, }, - expected: `soong_config_bool_variables = { -} + expected: `soong_config_bool_variables = {} -soong_config_value_variables = { -} +soong_config_value_variables = {} soong_config_string_variables = { "string_var": [ @@ -430,6 +426,7 @@ soong_config_string_variables = { "choice3", ], }`}, { + desc: "all vars", defs: Bp2BuildSoongConfigDefinitions{ BoolVars: map[string]bool{ "bool_var_one": true, @@ -438,15 +435,15 @@ soong_config_string_variables = { "value_var_one": true, "value_var_two": true, }, - StringVars: map[string]map[string]bool{ - "string_var_one": map[string]bool{ - "choice1": true, - "choice2": true, - "choice3": true, + StringVars: map[string][]string{ + "string_var_one": []string{ + "choice1", + "choice2", + "choice3", }, - "string_var_two": map[string]bool{ - "foo": true, - "bar": true, + "string_var_two": []string{ + "foo", + "bar", }, }, }, @@ -466,15 +463,17 @@ soong_config_string_variables = { "choice3", ], "string_var_two": [ - "bar", "foo", + "bar", ], }`}, } for _, test := range testCases { - actual := test.defs.String() - if actual != test.expected { - t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual) - } + t.Run(test.desc, func(t *testing.T) { + actual := test.defs.String() + if actual != test.expected { + t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual) + } + }) } } diff --git a/apex/builder.go b/apex/builder.go index 1a1f22be2..fc4bf8a22 100644 --- a/apex/builder.go +++ b/apex/builder.go @@ -398,18 +398,8 @@ func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Output } func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path { - return java.ManifestFixer(java.ManifestFixerParams{ - Ctx: ctx, - Manifest: androidManifestFile, - SdkContext: nil, - ClassLoaderContexts: nil, - IsLibrary: false, - UseEmbeddedNativeLibs: false, - UsesNonSdkApis: false, - UseEmbeddedDex: false, - HasNoCode: false, - TestOnly: true, - LoggingParent: "", + return java.ManifestFixer(ctx, androidManifestFile, java.ManifestFixerParams{ + TestOnly: true, }) } @@ -618,6 +608,8 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { implicitInputs = append(implicitInputs, androidManifestFile) optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String()) + } else if a.testApex { + optFlags = append(optFlags, "--test_only") } // Determine target/min sdk version from the context diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go index 4dc0e4cd1..5d00b0b29 100644 --- a/bazel/cquery/request_type.go +++ b/bazel/cquery/request_type.go @@ -114,7 +114,7 @@ staticLibraries = [] rootStaticArchives = [] linker_inputs = cc_info.linking_context.linker_inputs.to_list() -static_info_tag = "//build/bazel/rules:cc_library_static.bzl%CcStaticLibraryInfo" +static_info_tag = "//build/bazel/rules/cc:cc_library_static.bzl%CcStaticLibraryInfo" if static_info_tag in providers(target): static_info = providers(target)[static_info_tag] ccObjectFiles = [f.path for f in static_info.objects] @@ -149,7 +149,7 @@ else: rootSharedLibraries.append(path) toc_file = "" -toc_file_tag = "//build/bazel/rules:generate_toc.bzl%CcTocInfo" +toc_file_tag = "//build/bazel/rules/cc:generate_toc.bzl%CcTocInfo" if toc_file_tag in providers(target): toc_file = providers(target)[toc_file_tag].toc.path else: diff --git a/bp2build/Android.bp b/bp2build/Android.bp index 4bcfa6110..b904c3533 100644 --- a/bp2build/Android.bp +++ b/bp2build/Android.bp @@ -28,6 +28,7 @@ bootstrap_go_package { "soong-genrule", "soong-python", "soong-sh", + "soong-starlark-format", "soong-ui-metrics", ], testSrcs: [ diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go index b3bec65da..1d3b10550 100644 --- a/bp2build/build_conversion.go +++ b/bp2build/build_conversion.go @@ -27,6 +27,7 @@ import ( "android/soong/android" "android/soong/bazel" + "android/soong/starlark_fmt" "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -559,48 +560,27 @@ func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) ( return "", nil } - var ret string switch propertyValue.Kind() { case reflect.String: - ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())) + return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil 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: - ret = fmt.Sprintf("%v", propertyValue.Interface()) + return fmt.Sprintf("%v", propertyValue.Interface()), nil case reflect.Ptr: return prettyPrint(propertyValue.Elem(), indent, emitZeroValues) case reflect.Slice: - if propertyValue.Len() == 0 { - return "[]", nil - } - - if propertyValue.Len() == 1 { - // Single-line list for list with only 1 element - ret += "[" - indexedValue, err := prettyPrint(propertyValue.Index(0), indent, emitZeroValues) + elements := make([]string, 0, propertyValue.Len()) + for i := 0; i < propertyValue.Len(); i++ { + val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues) if err != nil { return "", err } - ret += indexedValue - ret += "]" - } 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" - } + if val != "" { + elements = append(elements, val) } - ret += makeIndent(indent) - ret += "]" } + return starlark_fmt.PrintList(elements, indent, "%s"), nil case reflect.Struct: // 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 } - ret = "{\n" // Sort and print the struct props by the key. structProps := extractStructProperties(propertyValue, indent) if len(structProps) == 0 { return "", nil } - for _, k := range android.SortedStringKeys(structProps) { - ret += makeIndent(indent + 1) - ret += fmt.Sprintf("%q: %s,\n", k, structProps[k]) - } - ret += makeIndent(indent) - ret += "}" + return starlark_fmt.PrintDict(structProps, indent), nil case reflect.Interface: // TODO(b/164227191): implement pretty print for interfaces. // 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( "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, @@ -736,13 +709,6 @@ func escapeString(s string) string { 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 { name := "" if c.ModuleSubDir(logicModule) != "" { diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go index 4c4953d70..3bf022126 100644 --- a/bp2build/cc_library_conversion_test.go +++ b/bp2build/cc_library_conversion_test.go @@ -2437,3 +2437,18 @@ cc_library { }, ) } + +func TestCcLibraryEscapeLdflags(t *testing.T) { + runCcLibraryTestCase(t, bp2buildTestCase{ + moduleTypeUnderTest: "cc_library", + moduleTypeUnderTestFactory: cc.LibraryFactory, + blueprint: soongCcProtoPreamble + `cc_library { + name: "foo", + ldflags: ["-Wl,--rpath,${ORIGIN}"], + include_build_directory: false, +}`, + expectedBazelTargets: makeCcLibraryTargets("foo", attrNameToString{ + "linkopts": `["-Wl,--rpath,$${ORIGIN}"]`, + }), + }) +} diff --git a/bp2build/configurability.go b/bp2build/configurability.go index dfbb265d2..d37a52394 100644 --- a/bp2build/configurability.go +++ b/bp2build/configurability.go @@ -6,6 +6,7 @@ import ( "android/soong/android" "android/soong/bazel" + "android/soong/starlark_fmt" ) // Configurability support for bp2build. @@ -250,10 +251,10 @@ func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *stri } else if defaultValue != nil { // Print an explicit empty list (the default value) even if the value is // 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 += "})" 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 // with a provided key. 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) if err != nil { return "", err diff --git a/bpf/bpf.go b/bpf/bpf.go index a4999e5e4..14b2d84c5 100644 --- a/bpf/bpf.go +++ b/bpf/bpf.go @@ -20,7 +20,6 @@ import ( "strings" "android/soong/android" - _ "android/soong/cc/config" "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -74,7 +73,10 @@ type BpfProperties struct { Include_dirs []string Sub_dir string // If set to true, generate BTF debug info for maps & programs - Btf *bool + Btf *bool + Vendor *bool + + VendorInternal bool `blueprint:"mutated"` } type bpf struct { @@ -85,6 +87,41 @@ type bpf struct { objs android.Paths } +var _ android.ImageInterface = (*bpf)(nil) + +func (bpf *bpf) ImageMutatorBegin(ctx android.BaseModuleContext) {} + +func (bpf *bpf) CoreVariantNeeded(ctx android.BaseModuleContext) bool { + return !proptools.Bool(bpf.properties.Vendor) +} + +func (bpf *bpf) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool { + return false +} + +func (bpf *bpf) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { + return false +} + +func (bpf *bpf) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { + return false +} + +func (bpf *bpf) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool { + return false +} + +func (bpf *bpf) ExtraImageVariations(ctx android.BaseModuleContext) []string { + if proptools.Bool(bpf.properties.Vendor) { + return []string{"vendor"} + } + return nil +} + +func (bpf *bpf) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) { + bpf.properties.VendorInternal = variation == "vendor" +} + func (bpf *bpf) GenerateAndroidBuildActions(ctx android.ModuleContext) { cflags := []string{ "-nostdlibinc", @@ -132,8 +169,8 @@ func (bpf *bpf) GenerateAndroidBuildActions(ctx android.ModuleContext) { if proptools.Bool(bpf.properties.Btf) { objStripped := android.ObjPathWithExt(ctx, "", src, "o") ctx.Build(pctx, android.BuildParams{ - Rule: stripRule, - Input: obj, + Rule: stripRule, + Input: obj, Output: objStripped, Args: map[string]string{ "stripCmd": "${config.ClangBin}/llvm-strip", @@ -154,7 +191,12 @@ func (bpf *bpf) AndroidMk() android.AndroidMkData { fmt.Fprintln(w) fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir) fmt.Fprintln(w) - localModulePath := "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf" + var localModulePath string + if bpf.properties.VendorInternal { + localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_ETC)/bpf" + } else { + localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf" + } if len(bpf.properties.Sub_dir) > 0 { localModulePath += "/" + bpf.properties.Sub_dir } diff --git a/cc/androidmk.go b/cc/androidmk.go index b56d689d6..715490572 100644 --- a/cc/androidmk.go +++ b/cc/androidmk.go @@ -108,6 +108,9 @@ func (c *Module) AndroidMkEntries() []android.AndroidMkEntries { if len(c.Properties.AndroidMkHeaderLibs) > 0 { entries.AddStrings("LOCAL_HEADER_LIBRARIES", c.Properties.AndroidMkHeaderLibs...) } + if len(c.Properties.AndroidMkRuntimeLibs) > 0 { + entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...) + } entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType) if c.UseVndk() { entries.SetBool("LOCAL_USE_VNDK", true) diff --git a/cc/binary.go b/cc/binary.go index 05923b1b8..54fd339c2 100644 --- a/cc/binary.go +++ b/cc/binary.go @@ -607,7 +607,7 @@ func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) { ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{ Rule_class: "cc_binary", - Bzl_load_location: "//build/bazel/rules:cc_binary.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_binary.bzl", }, android.CommonAttributes{Name: m.Name()}, attrs) diff --git a/cc/bp2build.go b/cc/bp2build.go index 30c3c509b..42fc0e494 100644 --- a/cc/bp2build.go +++ b/cc/bp2build.go @@ -644,7 +644,7 @@ func (la *linkerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversion var linkerFlags []string if len(props.Ldflags) > 0 { - linkerFlags = append(linkerFlags, props.Ldflags...) + linkerFlags = append(linkerFlags, proptools.NinjaEscapeList(props.Ldflags)...) // binaries remove static flag if -shared is in the linker flags if isBinary && android.InList("-shared", linkerFlags) { axisFeatures = append(axisFeatures, "-static_flag") diff --git a/cc/config/Android.bp b/cc/config/Android.bp index 7b7ee2849..e1b06057b 100644 --- a/cc/config/Android.bp +++ b/cc/config/Android.bp @@ -8,6 +8,7 @@ bootstrap_go_package { deps: [ "soong-android", "soong-remoteexec", + "soong-starlark-format", ], srcs: [ "bp2build.go", diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go index 982b43648..eca516107 100644 --- a/cc/config/bp2build.go +++ b/cc/config/bp2build.go @@ -22,14 +22,11 @@ import ( "strings" "android/soong/android" + "android/soong/starlark_fmt" "github.com/google/blueprint" ) -const ( - bazelIndent = 4 -) - type bazelVarExporter interface { asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant } @@ -73,21 +70,6 @@ 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(config android.Config, stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant { ret := make([]bazelConstant, 0, len(m)) @@ -139,7 +121,7 @@ func (m exportedStringListVariables) asBazel(config android.Config, // out through a constants struct later. ret = append(ret, bazelConstant{ variableName: k, - internalDefinition: printBazelList(expandedVars, 0), + internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0), }) } return ret @@ -173,17 +155,6 @@ 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(_ android.Config, _ exportedStringVariables, _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant { @@ -191,7 +162,7 @@ func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStr for k, dict := range m { ret = append(ret, bazelConstant{ variableName: k, - internalDefinition: printBazelStringListDict(dict), + internalDefinition: starlark_fmt.PrintStringListDict(dict, 0), }) } return ret @@ -223,7 +194,7 @@ func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string 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)) + fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName)) } // Build the exported constants struct. diff --git a/cc/config/bp2build_test.go b/cc/config/bp2build_test.go index 3118df1f8..4cbf0c6f3 100644 --- a/cc/config/bp2build_test.go +++ b/cc/config/bp2build_test.go @@ -211,15 +211,11 @@ constants = struct( expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT. _a = { - "b1": [ - "b2", - ], + "b1": ["b2"], } _c = { - "d1": [ - "d2", - ], + "d1": ["d2"], } constants = struct( @@ -246,27 +242,19 @@ constants = struct( expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT. _a = { - "a1": [ - "a2", - ], + "a1": ["a2"], } _b = "b-val" -_c = [ - "c-val", -] +_c = ["c-val"] _d = "d-val" -_e = [ - "e-val", -] +_e = ["e-val"] _f = { - "f1": [ - "f2", - ], + "f1": ["f2"], } constants = struct( diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go index 00f07ff26..d789cde5c 100644 --- a/cc/config/x86_64_device.go +++ b/cc/config/x86_64_device.go @@ -15,6 +15,7 @@ package config import ( + "fmt" "strings" "android/soong/android" @@ -190,6 +191,11 @@ func (toolchainX86_64) LibclangRuntimeLibraryArch() string { } func x86_64ToolchainFactory(arch android.Arch) Toolchain { + // Error now rather than having a confusing Ninja error + if _, ok := x86_64ArchVariantCflags[arch.ArchVariant]; !ok { + panic(fmt.Sprintf("Unknown x86_64 architecture version: %q", arch.ArchVariant)) + } + toolchainCflags := []string{ "${config.X86_64ToolchainCflags}", "${config.X86_64" + arch.ArchVariant + "VariantCflags}", diff --git a/cc/config/x86_device.go b/cc/config/x86_device.go index 29f059303..e32e1bde7 100644 --- a/cc/config/x86_device.go +++ b/cc/config/x86_device.go @@ -15,6 +15,7 @@ package config import ( + "fmt" "strings" "android/soong/android" @@ -186,6 +187,11 @@ func (toolchainX86) LibclangRuntimeLibraryArch() string { } func x86ToolchainFactory(arch android.Arch) Toolchain { + // Error now rather than having a confusing Ninja error + if _, ok := x86ArchVariantCflags[arch.ArchVariant]; !ok { + panic(fmt.Sprintf("Unknown x86 architecture version: %q", arch.ArchVariant)) + } + toolchainCflags := []string{ "${config.X86ToolchainCflags}", "${config.X86" + arch.ArchVariant + "VariantCflags}", diff --git a/cc/library.go b/cc/library.go index 5d408203e..708aa1045 100644 --- a/cc/library.go +++ b/cc/library.go @@ -32,7 +32,7 @@ import ( "github.com/google/blueprint/pathtools" ) -// LibraryProperties is a collection of properties shared by cc library rules. +// LibraryProperties is a collection of properties shared by cc library rules/cc. type LibraryProperties struct { // local file name to pass to the linker as -unexported_symbols_list Unexported_symbols_list *string `android:"path,arch_variant"` @@ -403,11 +403,11 @@ func libraryBp2Build(ctx android.TopDownMutatorContext, m *Module) { staticProps := bazel.BazelTargetModuleProperties{ Rule_class: "cc_library_static", - Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_library_static.bzl", } sharedProps := bazel.BazelTargetModuleProperties{ Rule_class: "cc_library_shared", - Bzl_load_location: "//build/bazel/rules:cc_library_shared.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_library_shared.bzl", } ctx.CreateBazelTargetModuleWithRestrictions(staticProps, @@ -2552,7 +2552,7 @@ func sharedOrStaticLibraryBp2Build(ctx android.TopDownMutatorContext, module *Mo } props := bazel.BazelTargetModuleProperties{ Rule_class: modType, - Bzl_load_location: fmt.Sprintf("//build/bazel/rules:%s.bzl", modType), + Bzl_load_location: fmt.Sprintf("//build/bazel/rules/cc:%s.bzl", modType), } ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs) diff --git a/cc/library_headers.go b/cc/library_headers.go index 064e2b807..5d38fba03 100644 --- a/cc/library_headers.go +++ b/cc/library_headers.go @@ -136,7 +136,7 @@ func libraryHeadersBp2Build(ctx android.TopDownMutatorContext, module *Module) { props := bazel.BazelTargetModuleProperties{ Rule_class: "cc_library_headers", - Bzl_load_location: "//build/bazel/rules:cc_library_headers.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_library_headers.bzl", } ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs) diff --git a/cc/object.go b/cc/object.go index 24f6ed455..fdd0b113c 100644 --- a/cc/object.go +++ b/cc/object.go @@ -195,7 +195,7 @@ func objectBp2Build(ctx android.TopDownMutatorContext, m *Module) { props := bazel.BazelTargetModuleProperties{ Rule_class: "cc_object", - Bzl_load_location: "//build/bazel/rules:cc_object.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_object.bzl", } ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs) diff --git a/cc/prebuilt.go b/cc/prebuilt.go index c928ed960..339a16d9d 100644 --- a/cc/prebuilt.go +++ b/cc/prebuilt.go @@ -339,7 +339,7 @@ func prebuiltLibraryStaticBp2Build(ctx android.TopDownMutatorContext, module *Mo props := bazel.BazelTargetModuleProperties{ Rule_class: "prebuilt_library_static", - Bzl_load_location: "//build/bazel/rules:prebuilt_library_static.bzl", + Bzl_load_location: "//build/bazel/rules/cc:prebuilt_library_static.bzl", } name := android.RemoveOptionalPrebuiltPrefix(module.Name()) @@ -359,7 +359,7 @@ func prebuiltLibrarySharedBp2Build(ctx android.TopDownMutatorContext, module *Mo props := bazel.BazelTargetModuleProperties{ Rule_class: "prebuilt_library_shared", - Bzl_load_location: "//build/bazel/rules:prebuilt_library_shared.bzl", + Bzl_load_location: "//build/bazel/rules/cc:prebuilt_library_shared.bzl", } name := android.RemoveOptionalPrebuiltPrefix(module.Name()) diff --git a/cc/proto.go b/cc/proto.go index f3410bc2b..3cf1453c8 100644 --- a/cc/proto.go +++ b/cc/proto.go @@ -210,7 +210,7 @@ func bp2buildProto(ctx android.Bp2buildMutatorContext, m *Module, protoSrcs baze ctx.CreateBazelTargetModule( bazel.BazelTargetModuleProperties{ Rule_class: rule_class, - Bzl_load_location: "//build/bazel/rules:cc_proto.bzl", + Bzl_load_location: "//build/bazel/rules/cc:cc_proto.bzl", }, android.CommonAttributes{Name: name}, &protoAttrs) diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go index d80051c77..a0cfbea5d 100644 --- a/cmd/soong_ui/main.go +++ b/cmd/soong_ui/main.go @@ -205,14 +205,7 @@ func main() { buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v", config.Parallel(), config.RemoteParallel(), config.HighmemParallel()) - { - var limits syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits) - if err != nil { - buildCtx.Verbosef("Failed to get file limit:", err) - } - buildCtx.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max) - } + setMaxFiles(buildCtx) { // The order of the function calls is important. The last defer function call @@ -614,3 +607,24 @@ func populateExternalDistDirHelper(ctx build.Context, config build.Config, inter } } } + +func setMaxFiles(ctx build.Context) { + var limits syscall.Rlimit + + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits) + if err != nil { + ctx.Println("Failed to get file limit:", err) + return + } + + ctx.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max) + if limits.Cur == limits.Max { + return + } + + limits.Cur = limits.Max + err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits) + if err != nil { + ctx.Println("Failed to increase file limit:", err) + } +} diff --git a/java/aar.go b/java/aar.go index 51aad8da0..8e1025361 100644 --- a/java/aar.go +++ b/java/aar.go @@ -280,9 +280,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkCon manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml") manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile) - manifestPath := ManifestFixer(ManifestFixerParams{ - Ctx: ctx, - Manifest: manifestSrcPath, + manifestPath := ManifestFixer(ctx, manifestSrcPath, ManifestFixerParams{ SdkContext: sdkContext, ClassLoaderContexts: classLoaderContexts, IsLibrary: a.isLibrary, @@ -290,7 +288,6 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkCon UsesNonSdkApis: a.usesNonSdkApis, UseEmbeddedDex: a.useEmbeddedDex, HasNoCode: a.hasNoCode, - TestOnly: false, LoggingParent: a.LoggingParent, }) diff --git a/java/android_manifest.go b/java/android_manifest.go index a5d5b97a0..7772b7090 100644 --- a/java/android_manifest.go +++ b/java/android_manifest.go @@ -56,8 +56,6 @@ func targetSdkVersionForManifestFixer(ctx android.ModuleContext, sdkContext andr } type ManifestFixerParams struct { - Ctx android.ModuleContext - Manifest android.Path SdkContext android.SdkContext ClassLoaderContexts dexpreopt.ClassLoaderContextMap IsLibrary bool @@ -70,20 +68,21 @@ type ManifestFixerParams struct { } // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml -func ManifestFixer(params ManifestFixerParams) android.Path { +func ManifestFixer(ctx android.ModuleContext, manifest android.Path, + params ManifestFixerParams) android.Path { var args []string if params.IsLibrary { args = append(args, "--library") } else if params.SdkContext != nil { - minSdkVersion, err := params.SdkContext.MinSdkVersion(params.Ctx).EffectiveVersion(params.Ctx) + minSdkVersion, err := params.SdkContext.MinSdkVersion(ctx).EffectiveVersion(ctx) if err != nil { - params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) } if minSdkVersion.FinalOrFutureInt() >= 23 { args = append(args, fmt.Sprintf("--extract-native-libs=%v", !params.UseEmbeddedNativeLibs)) } else if params.UseEmbeddedNativeLibs { - params.Ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it", + ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it", minSdkVersion) } } @@ -124,38 +123,38 @@ func ManifestFixer(params ManifestFixerParams) android.Path { var argsMapper = make(map[string]string) if params.SdkContext != nil { - targetSdkVersion := targetSdkVersionForManifestFixer(params.Ctx, params.SdkContext) + targetSdkVersion := targetSdkVersionForManifestFixer(ctx, params.SdkContext) args = append(args, "--targetSdkVersion ", targetSdkVersion) - if UseApiFingerprint(params.Ctx) && params.Ctx.ModuleName() != "framework-res" { - targetSdkVersion = params.Ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(params.Ctx).String()) - deps = append(deps, ApiFingerprintPath(params.Ctx)) + if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" { + targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String()) + deps = append(deps, ApiFingerprintPath(ctx)) } - minSdkVersion, err := params.SdkContext.MinSdkVersion(params.Ctx).EffectiveVersionString(params.Ctx) + minSdkVersion, err := params.SdkContext.MinSdkVersion(ctx).EffectiveVersionString(ctx) if err != nil { - params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) } - if UseApiFingerprint(params.Ctx) && params.Ctx.ModuleName() != "framework-res" { - minSdkVersion = params.Ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(params.Ctx).String()) - deps = append(deps, ApiFingerprintPath(params.Ctx)) + if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" { + minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String()) + deps = append(deps, ApiFingerprintPath(ctx)) } if err != nil { - params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) } args = append(args, "--minSdkVersion ", minSdkVersion) args = append(args, "--raise-min-sdk-version") } - fixedManifest := android.PathForModuleOut(params.Ctx, "manifest_fixer", "AndroidManifest.xml") + fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml") argsMapper["args"] = strings.Join(args, " ") - params.Ctx.Build(pctx, android.BuildParams{ + ctx.Build(pctx, android.BuildParams{ Rule: manifestFixerRule, Description: "fix manifest", - Input: params.Manifest, + Input: manifest, Implicits: deps, Output: fixedManifest, Args: argsMapper, diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index a36bd6a63..5fe409e25 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -931,13 +931,13 @@ type bootclasspathFragmentSdkMemberProperties struct { All_flags_path android.OptionalPath `supported_build_releases:"S"` // The path to the generated signature-patterns.csv file. - Signature_patterns_path android.OptionalPath `supported_build_releases:"T+"` + Signature_patterns_path android.OptionalPath `supported_build_releases:"Tiramisu+"` // The path to the generated filtered-stub-flags.csv file. - Filtered_stub_flags_path android.OptionalPath `supported_build_releases:"T+"` + Filtered_stub_flags_path android.OptionalPath `supported_build_releases:"Tiramisu+"` // The path to the generated filtered-flags.csv file. - Filtered_flags_path android.OptionalPath `supported_build_releases:"T+"` + Filtered_flags_path android.OptionalPath `supported_build_releases:"Tiramisu+"` } func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { diff --git a/java/proto.go b/java/proto.go index ab913d868..8d2380322 100644 --- a/java/proto.go +++ b/java/proto.go @@ -73,13 +73,15 @@ func genProto(ctx android.ModuleContext, protoFiles android.Paths, flags android } func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) { + const unspecifiedProtobufPluginType = "" if String(p.Proto.Plugin) == "" { switch String(p.Proto.Type) { + case "stream": // does not require additional dependencies case "micro": ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-micro") case "nano": ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-nano") - case "lite", "": + case "lite", unspecifiedProtobufPluginType: ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-lite") case "full": if ctx.Host() || ctx.BazelConversionMode() { diff --git a/java/proto_test.go b/java/proto_test.go new file mode 100644 index 000000000..d1cb71448 --- /dev/null +++ b/java/proto_test.go @@ -0,0 +1,53 @@ +// 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 java + +import ( + "strings" + "testing" + + "android/soong/android" +) + +const protoModules = ` +java_library_static { + name: "libprotobuf-java-lite", +} +` + +func TestProtoStream(t *testing.T) { + bp := ` + java_library { + name: "java-stream-protos", + proto: { + type: "stream", + }, + srcs: [ + "a.proto", + "b.proto", + ], + } + ` + + ctx := android.GroupFixturePreparers( + PrepareForIntegrationTestWithJava, + ).RunTestWithBp(t, protoModules+bp) + + proto0 := ctx.ModuleForTests("java-stream-protos", "android_common").Output("proto/proto0.srcjar") + + if cmd := proto0.RuleParams.Command; !strings.Contains(cmd, "--javastream_out=") { + t.Errorf("expected '--javastream_out' in %q", cmd) + } +} diff --git a/java/sdk_library.go b/java/sdk_library.go index 6a2a7a845..e794a48c1 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -21,7 +21,6 @@ import ( "reflect" "regexp" "sort" - "strconv" "strings" "sync" @@ -2551,8 +2550,14 @@ func formattedOptionalSdkLevelAttribute(ctx android.ModuleContext, attrName stri ctx.PropertyErrorf(strings.ReplaceAll(attrName, "-", "_"), err.Error()) return "" } - intStr := strconv.Itoa(apiLevel.FinalOrPreviewInt()) - return formattedOptionalAttribute(attrName, &intStr) + if apiLevel.IsCurrent() { + // passing "current" would always mean a future release, never the current (or the current in + // progress) which means some conditions would never be triggered. + ctx.PropertyErrorf(strings.ReplaceAll(attrName, "-", "_"), + `"current" is not an allowed value for this attribute`) + return "" + } + return formattedOptionalAttribute(attrName, value) } // formats an attribute for the xml permissions file if the value is not null @@ -2808,7 +2813,7 @@ type scopeProperties struct { StubsSrcJar android.Path CurrentApiFile android.Path RemovedApiFile android.Path - AnnotationsZip android.Path `supported_build_releases:"T+"` + AnnotationsZip android.Path `supported_build_releases:"Tiramisu+"` SdkVersion string } diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go index e0e5b5697..3500c84d2 100644 --- a/java/sdk_library_test.go +++ b/java/sdk_library_test.go @@ -182,7 +182,7 @@ func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) { "30": {"foo", "fooUpdatable", "fooUpdatableErr"}, }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { - variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W"} + variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W", "X"} }), ).RunTestWithBp(t, ` @@ -193,7 +193,7 @@ func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) { on_bootclasspath_since: "U", on_bootclasspath_before: "V", min_device_sdk: "W", - max_device_sdk: "current", + max_device_sdk: "X", min_sdk_version: "S", } java_sdk_library { @@ -202,12 +202,13 @@ func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) { api_packages: ["foo"], } `) + // test that updatability attributes are passed on correctly fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Rule("java_sdk_xml") - android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-since=\"9001\"`) - android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-before=\"9002\"`) - android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `min-device-sdk=\"9003\"`) - android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `max-device-sdk=\"10000\"`) + android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-since=\"U\"`) + android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-before=\"V\"`) + android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `min-device-sdk=\"W\"`) + android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `max-device-sdk=\"X\"`) // double check that updatability attributes are not written if they don't exist in the bp file // the permissions file for the foo library defined above @@ -230,7 +231,7 @@ func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) { `on_bootclasspath_since: "aaa" could not be parsed as an integer and is not a recognized codename`, `on_bootclasspath_before: "bbc" could not be parsed as an integer and is not a recognized codename`, `min_device_sdk: "ccc" could not be parsed as an integer and is not a recognized codename`, - `max_device_sdk: "ddd" could not be parsed as an integer and is not a recognized codename`, + `max_device_sdk: "current" is not an allowed value for this attribute`, })).RunTestWithBp(t, ` java_sdk_library { @@ -240,7 +241,7 @@ func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) { on_bootclasspath_since: "aaa", on_bootclasspath_before: "bbc", min_device_sdk: "ccc", - max_device_sdk: "ddd", + max_device_sdk: "current", } `) } diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go index b8fe16239..cb50a5014 100644 --- a/mk2rbc/mk2rbc.go +++ b/mk2rbc/mk2rbc.go @@ -1092,7 +1092,7 @@ func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr { // Given an if statement's directive and the left/right starlarkExprs, // check if the starlarkExprs are one of a few hardcoded special cases -// that can be converted to a simpler equalify expression than simply comparing +// that can be converted to a simpler equality expression than simply comparing // the two. func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr, right starlarkExpr) (starlarkExpr, bool) { @@ -1121,8 +1121,8 @@ func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, } switch call.name { - case baseName + ".filter", baseName + ".filter-out": - return ctx.parseCompareFilterFuncResult(directive, call, value, isEq), true + case baseName + ".filter": + return ctx.parseCompareFilterFuncResult(directive, call, value, isEq) case baseName + ".expand_wildcard": return ctx.parseCompareWildcardFuncResult(directive, call, value, !isEq), true case baseName + ".findstring": @@ -1134,68 +1134,39 @@ func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, } func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive, - filterFuncCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr { + filterFuncCall *callExpr, xValue starlarkExpr, negate bool) (starlarkExpr, bool) { // We handle: // * ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...] // * ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...] - // * ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...) becomes if VAR in/not in ["v1", "v2"] - // TODO(Asmundak): check the last case works for filter-out, too. + if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" { + return nil, false + } xPattern := filterFuncCall.args[0] xText := filterFuncCall.args[1] var xInList *stringLiteralExpr var expr starlarkExpr var ok bool - switch x := xValue.(type) { - case *stringLiteralExpr: - if x.literal != "" { - return ctx.newBadExpr(cond, "filter comparison to non-empty value: %s", xValue) - } - // Either pattern or text should be const, and the - // non-const one should be varRefExpr - if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList { - expr = xText - } else if xInList, ok = xText.(*stringLiteralExpr); ok { - expr = xPattern - } else { - expr = &callExpr{ - object: nil, - name: filterFuncCall.name, - args: filterFuncCall.args, - returnType: starlarkTypeBool, - } - if negate { - expr = ¬Expr{expr: expr} - } - return expr - } - case *variableRefExpr: - if v, ok := xPattern.(*variableRefExpr); ok { - if xInList, ok = xText.(*stringLiteralExpr); ok && v.ref.name() == x.ref.name() { - // ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...), flip negate, - // it's the opposite to what is done when comparing to empty. - expr = xPattern - negate = !negate - } - } + if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList { + expr = xText + } else if xInList, ok = xText.(*stringLiteralExpr); ok { + expr = xPattern + } else { + return nil, false } - if expr != nil && xInList != nil { - slExpr := newStringListExpr(strings.Fields(xInList.literal)) - // Generate simpler code for the common cases: - if expr.typ() == starlarkTypeList { - if len(slExpr.items) == 1 { - // Checking that a string belongs to list - return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]} - } else { - // TODO(asmundak): - panic("TBD") - } - } else if len(slExpr.items) == 1 { - return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate} + slExpr := newStringListExpr(strings.Fields(xInList.literal)) + // Generate simpler code for the common cases: + if expr.typ() == starlarkTypeList { + if len(slExpr.items) == 1 { + // Checking that a string belongs to list + return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}, true } else { - return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr} + return nil, false } + } else if len(slExpr.items) == 1 { + return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}, true + } else { + return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}, true } - return ctx.newBadExpr(cond, "filter arguments are too complex: %s", cond.Dump()) } func (ctx *parseContext) parseCompareWildcardFuncResult(directive *mkparser.Directive, diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go index 2083121ab..447f658ee 100644 --- a/mk2rbc/mk2rbc_test.go +++ b/mk2rbc/mk2rbc_test.go @@ -389,6 +389,10 @@ ifneq (,$(filter plaf,$(PLATFORM_LIST))) endif ifeq ($(TARGET_BUILD_VARIANT), $(filter $(TARGET_BUILD_VARIANT), userdebug eng)) endif +ifneq (, $(filter $(TARGET_BUILD_VARIANT), userdebug eng)) +endif +ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) +endif ifneq (,$(filter true, $(v1)$(v2))) endif ifeq (,$(filter barbet coral%,$(TARGET_PRODUCT))) @@ -407,8 +411,12 @@ def init(g, handle): pass if "plaf" in g.get("PLATFORM_LIST", []): pass + if g["TARGET_BUILD_VARIANT"] == " ".join(rblf.filter(g["TARGET_BUILD_VARIANT"], "userdebug eng")): + pass if g["TARGET_BUILD_VARIANT"] in ["userdebug", "eng"]: pass + if rblf.filter("userdebug eng", g["TARGET_BUILD_VARIANT"]): + pass if rblf.filter("true", "%s%s" % (_v1, _v2)): pass if not rblf.filter("barbet coral%", g["TARGET_PRODUCT"]): @@ -1119,7 +1127,7 @@ def init(g, handle): rblf.inherit(handle, "foo/font", _font_init) # There's some space and even this comment between the include_top and the inherit-product rblf.inherit(handle, "foo/font", _font_init) - rblf.mkwarning("product.mk:11", "Including a path with a non-constant prefix, please convert this to a simple literal to generate cleaner starlark.") + rblf.mkwarning("product.mk:11", "Please avoid starting an include path with a variable. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.") _entry = { "foo/font.mk": ("foo/font", _font_init), "bar/font.mk": ("bar/font", _font1_init), diff --git a/mk2rbc/node.go b/mk2rbc/node.go index 2fa6a85c0..5d98d7bc1 100644 --- a/mk2rbc/node.go +++ b/mk2rbc/node.go @@ -101,7 +101,7 @@ func (i inheritedDynamicModule) entryName() string { func (i inheritedDynamicModule) emitSelect(gctx *generationContext) { if i.needsWarning { gctx.newLine() - gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Including a path with a non-constant prefix, please convert this to a simple literal to generate cleaner starlark. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.") + gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Please avoid starting an include path with a variable. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.") } gctx.newLine() gctx.writef("_entry = {") diff --git a/scripts/rustfmt.toml b/scripts/rustfmt.toml index 617d42585..cefaa42a3 100644 --- a/scripts/rustfmt.toml +++ b/scripts/rustfmt.toml @@ -1,5 +1,5 @@ # Android Format Style -edition = "2018" +edition = "2021" use_small_heuristics = "Max" newline_style = "Unix" diff --git a/sdk/build_release.go b/sdk/build_release.go index 2bcdc6f57..4c2277e85 100644 --- a/sdk/build_release.go +++ b/sdk/build_release.go @@ -85,7 +85,7 @@ var ( // Add the build releases from oldest to newest. buildReleaseS = initBuildRelease("S") - buildReleaseT = initBuildRelease("T") + buildReleaseT = initBuildRelease("Tiramisu") ) // initBuildRelease creates a new build release with the specified name. diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go index 6608be4f8..6f1ef9e30 100644 --- a/sdk/build_release_test.go +++ b/sdk/build_release_test.go @@ -60,7 +60,7 @@ func TestParseBuildReleaseSet(t *testing.T) { t.Run("closed range", func(t *testing.T) { set, err := parseBuildReleaseSet("S-F1") android.AssertDeepEquals(t, "errors", nil, err) - android.AssertStringEquals(t, "set", "[S,T,F1]", set.String()) + android.AssertStringEquals(t, "set", "[S,Tiramisu,F1]", set.String()) }) invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String() t.Run("invalid release", func(t *testing.T) { @@ -79,7 +79,7 @@ func TestParseBuildReleaseSet(t *testing.T) { android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) }) t.Run("invalid release in closed range end", func(t *testing.T) { - set, err := parseBuildReleaseSet("T-A") + set, err := parseBuildReleaseSet("Tiramisu-A") android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) }) @@ -128,13 +128,13 @@ func TestPropertyPrunerByBuildRelease(t *testing.T) { type mapped struct { Default string - T_only string `supported_build_releases:"T"` + T_only string `supported_build_releases:"Tiramisu"` } type testBuildReleasePruner struct { Default string - S_and_T_only string `supported_build_releases:"S-T"` - T_later string `supported_build_releases:"T+"` + S_and_T_only string `supported_build_releases:"S-Tiramisu"` + T_later string `supported_build_releases:"Tiramisu+"` Nested nested Mapped map[string]*mapped } diff --git a/starlark_fmt/Android.bp b/starlark_fmt/Android.bp new file mode 100644 index 000000000..8d80ccdca --- /dev/null +++ b/starlark_fmt/Android.bp @@ -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", + ], +} diff --git a/starlark_fmt/format.go b/starlark_fmt/format.go new file mode 100644 index 000000000..23eee59b3 --- /dev/null +++ b/starlark_fmt/format.go @@ -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)) +} diff --git a/starlark_fmt/format_test.go b/starlark_fmt/format_test.go new file mode 100644 index 000000000..90f78ef7a --- /dev/null +++ b/starlark_fmt/format_test.go @@ -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) + } +} |