Merge "Support multiple library names per target."
diff --git a/android/Android.bp b/android/Android.bp
index da36959..d3540b2 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -16,7 +16,9 @@
         "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 3046edb..7e5736f 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -288,6 +288,8 @@
 		"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 @@
 		"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 10e074c..f10732b 100644
--- a/android/config.go
+++ b/android/config.go
@@ -38,6 +38,7 @@
 	"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 @@
 		}
 	}
 
-	//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 acb9d18..ceb8e45 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -386,6 +386,46 @@
 	})).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 9bf3344..8fe1ff1 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -10,6 +10,7 @@
         "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 09a5057..212b752 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -25,6 +25,8 @@
 	"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 @@
 		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 @@
 // 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 @@
 	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 @@
 	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 @@
 // 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 b14f8b4..a7800e8 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -367,19 +367,19 @@
 
 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 @@
     "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 @@
         "choice3",
     ],
 }`}, {
+			desc: "all vars",
 			defs: Bp2BuildSoongConfigDefinitions{
 				BoolVars: map[string]bool{
 					"bool_var_one": true,
@@ -438,15 +435,15 @@
 					"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 @@
         "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 1a1f22b..fc4bf8a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -398,18 +398,8 @@
 }
 
 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 @@
 
 			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 4dc0e4c..5d00b0b 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -114,7 +114,7 @@
 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 @@
           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 4bcfa61..b904c35 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -28,6 +28,7 @@
         "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 b3bec65..1d3b105 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -27,6 +27,7 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/starlark_fmt"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -559,48 +560,27 @@
 		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 @@
 			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 @@
 		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 @@
 	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 4c4953d..3bf0221 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -2437,3 +2437,18 @@
 	},
 	)
 }
+
+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 dfbb265..d37a523 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -6,6 +6,7 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/starlark_fmt"
 )
 
 // Configurability support for bp2build.
@@ -250,10 +251,10 @@
 	} 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 @@
 // 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 a4999e5..14b2d84 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -20,7 +20,6 @@
 	"strings"
 
 	"android/soong/android"
-	_ "android/soong/cc/config"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -74,7 +73,10 @@
 	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 @@
 	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 @@
 		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 @@
 			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 b56d689..7154905 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -108,6 +108,9 @@
 				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 05923b1..54fd339 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -607,7 +607,7 @@
 
 	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 30c3c50..42fc0e4 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -644,7 +644,7 @@
 
 	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 7b7ee28..e1b0605 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -8,6 +8,7 @@
     deps: [
         "soong-android",
         "soong-remoteexec",
+        "soong-starlark-format",
     ],
     srcs: [
         "bp2build.go",
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
index 982b436..eca5161 100644
--- a/cc/config/bp2build.go
+++ b/cc/config/bp2build.go
@@ -22,14 +22,11 @@
 	"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 @@
 	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 @@
 		// 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 @@
 	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 @@
 	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 @@
 		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 3118df1..4cbf0c6 100644
--- a/cc/config/bp2build_test.go
+++ b/cc/config/bp2build_test.go
@@ -211,15 +211,11 @@
 			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 @@
 			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 00f07ff..d789cde 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 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 29f0593..e32e1bd 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 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 5d40820..708aa10 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -32,7 +32,7 @@
 	"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 @@
 
 	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 @@
 	}
 	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 064e2b8..5d38fba 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -136,7 +136,7 @@
 
 	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 24f6ed4..fdd0b11 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -195,7 +195,7 @@
 
 	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 c928ed9..339a16d 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -339,7 +339,7 @@
 
 	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 @@
 
 	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 f3410bc..3cf1453 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -210,7 +210,7 @@
 	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 d80051c..a0cfbea 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -205,14 +205,7 @@
 	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 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 51aad8d..8e10253 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -280,9 +280,7 @@
 	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 @@
 		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 a5d5b97..7772b70 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -56,8 +56,6 @@
 }
 
 type ManifestFixerParams struct {
-	Ctx                   android.ModuleContext
-	Manifest              android.Path
 	SdkContext            android.SdkContext
 	ClassLoaderContexts   dexpreopt.ClassLoaderContextMap
 	IsLibrary             bool
@@ -70,20 +68,21 @@
 }
 
 // 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 @@
 	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 a36bd6a..5fe409e 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -931,13 +931,13 @@
 	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 ab913d8..8d23803 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -73,13 +73,15 @@
 }
 
 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 0000000..d1cb714
--- /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 6a2a7a8..e794a48 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -21,7 +21,6 @@
 	"reflect"
 	"regexp"
 	"sort"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -2551,8 +2550,14 @@
 		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 @@
 	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 e0e5b56..3500c84 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -182,7 +182,7 @@
 			"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 @@
 			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 @@
 			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 @@
 			`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 @@
 			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 b8fe162..cb50a50 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -1092,7 +1092,7 @@
 
 // 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 @@
 	}
 
 	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) 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 = &notExpr{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 2083121..447f658 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -389,6 +389,10 @@
 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 @@
     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 @@
   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 2fa6a85..5d98d7b 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -101,7 +101,7 @@
 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 617d425..cefaa42 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 2bcdc6f..4c2277e 100644
--- a/sdk/build_release.go
+++ b/sdk/build_release.go
@@ -85,7 +85,7 @@
 
 	// 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 6608be4..6f1ef9e 100644
--- a/sdk/build_release_test.go
+++ b/sdk/build_release_test.go
@@ -60,7 +60,7 @@
 	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 @@
 		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 @@
 
 	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 0000000..8d80ccd
--- /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 0000000..23eee59
--- /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 0000000..90f78ef
--- /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)
+	}
+}