Merge "Add metrics to expconfigfetcher call"
diff --git a/android/Android.bp b/android/Android.bp
index c072ac2..49d5b91 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -36,6 +36,7 @@
         "bazel_handler.go",
         "bazel_paths.go",
         "config.go",
+        "config_bp2build.go",
         "csuite_config.go",
         "deapexer.go",
         "defaults.go",
@@ -96,6 +97,7 @@
         "bazel_handler_test.go",
         "bazel_test.go",
         "config_test.go",
+        "config_bp2build_test.go",
         "csuite_config_test.go",
         "defaults_test.go",
         "depset_test.go",
diff --git a/android/api_levels.go b/android/api_levels.go
index 27a3b7f..8163894 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"strconv"
 
+	"android/soong/bazel"
 	"android/soong/starlark_fmt"
 )
 
@@ -393,10 +394,10 @@
 }
 
 func StarlarkApiLevelConfigs(config Config) string {
-	return fmt.Sprintf(`# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
+	return fmt.Sprintf(bazel.GeneratedBazelFileWarning+`
 _api_levels = %s
 
 api_levels = _api_levels
 `, printApiLevelsStarlarkDict(config),
 	)
-}
\ No newline at end of file
+}
diff --git a/android/config.go b/android/config.go
index 5c41ee8..5dcb959 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1360,6 +1360,10 @@
 	return "", false
 }
 
+func (c *deviceConfig) ApexGlobalMinSdkVersionOverride() string {
+	return String(c.config.productVariables.ApexGlobalMinSdkVersionOverride)
+}
+
 func (c *config) IntegerOverflowDisabledForPath(path string) bool {
 	if len(c.productVariables.IntegerOverflowExcludePaths) == 0 {
 		return false
diff --git a/android/config_bp2build.go b/android/config_bp2build.go
new file mode 100644
index 0000000..748be62
--- /dev/null
+++ b/android/config_bp2build.go
@@ -0,0 +1,487 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
+
+	"github.com/google/blueprint"
+)
+
+// BazelVarExporter is a collection of configuration variables that can be exported for use in Bazel rules
+type BazelVarExporter interface {
+	// asBazel expands strings of configuration variables into their concrete values
+	asBazel(Config, ExportedStringVariables, ExportedStringListVariables, ExportedConfigDependingVariables) []bazelConstant
+}
+
+// ExportedVariables is a collection of interdependent configuration variables
+type ExportedVariables struct {
+	// Maps containing toolchain variables that are independent of the
+	// environment variables of the build.
+	exportedStringVars         ExportedStringVariables
+	exportedStringListVars     ExportedStringListVariables
+	exportedStringListDictVars ExportedStringListDictVariables
+
+	exportedVariableReferenceDictVars ExportedVariableReferenceDictVariables
+
+	/// Maps containing variables that are dependent on the build config.
+	exportedConfigDependingVars ExportedConfigDependingVariables
+
+	pctx PackageContext
+}
+
+// NewExportedVariables creats an empty ExportedVariables struct with non-nil maps
+func NewExportedVariables(pctx PackageContext) ExportedVariables {
+	return ExportedVariables{
+		exportedStringVars:                ExportedStringVariables{},
+		exportedStringListVars:            ExportedStringListVariables{},
+		exportedStringListDictVars:        ExportedStringListDictVariables{},
+		exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{},
+		exportedConfigDependingVars:       ExportedConfigDependingVariables{},
+		pctx:                              pctx,
+	}
+}
+
+func (ev ExportedVariables) asBazel(config Config,
+	stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := []bazelConstant{}
+	ret = append(ret, ev.exportedStringVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	ret = append(ret, ev.exportedStringListVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	ret = append(ret, ev.exportedStringListDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	// Note: ExportedVariableReferenceDictVars collections can only contain references to other variables and must be printed last
+	ret = append(ret, ev.exportedVariableReferenceDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	return ret
+}
+
+// ExportStringStaticVariable declares a static string variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) {
+	ev.pctx.StaticVariable(name, value)
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringListStaticVariable declares a static variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) {
+	ev.pctx.StaticVariable(name, strings.Join(value, " "))
+	ev.exportedStringListVars.set(name, value)
+}
+
+// ExportVariableConfigMethod declares a variable whose value is evaluated at
+// runtime via a function with access to the Config and exports it to Bazel's
+// toolchain.
+func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
+	ev.exportedConfigDependingVars.set(name, method)
+	return ev.pctx.VariableConfigMethod(name, method)
+}
+
+// ExportSourcePathVariable declares a static "source path" variable and exports
+// it to Bazel's toolchain.
+func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) {
+	ev.pctx.SourcePathVariable(name, value)
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportVariableFuncVariable declares a variable whose value is evaluated at
+// runtime via a function and exports it to Bazel's toolchain.
+func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) {
+	ev.exportedConfigDependingVars.set(name, func(config Config) string {
+		return f()
+	})
+	ev.pctx.VariableFunc(name, func(PackageVarContext) string {
+		return f()
+	})
+}
+
+// ExportString only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportString(name string, value string) {
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringList only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringList(name string, value []string) {
+	ev.exportedStringListVars.set(name, value)
+}
+
+// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) {
+	ev.exportedStringListDictVars.set(name, value)
+}
+
+// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) {
+	ev.exportedVariableReferenceDictVars.set(name, value)
+}
+
+// ExportedConfigDependingVariables is a mapping of variable names to functions
+// of type func(config Config) string which return the runtime-evaluated string
+// value of a particular variable
+type ExportedConfigDependingVariables map[string]interface{}
+
+func (m ExportedConfigDependingVariables) set(k string, v interface{}) {
+	m[k] = v
+}
+
+// Ensure that string s has no invalid characters to be generated into the bzl file.
+func validateCharacters(s string) string {
+	for _, c := range []string{`\n`, `"`, `\`} {
+		if strings.Contains(s, c) {
+			panic(fmt.Errorf("%s contains illegal character %s", s, c))
+		}
+	}
+	return s
+}
+
+type bazelConstant struct {
+	variableName       string
+	internalDefinition string
+	sortLast           bool
+}
+
+// ExportedStringVariables is a mapping of variable names to string values
+type ExportedStringVariables map[string]string
+
+func (m ExportedStringVariables) set(k string, v string) {
+	m[k] = v
+}
+
+func (m ExportedStringVariables) asBazel(config Config,
+	stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, variableValue := range m {
+		expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
+		if err != nil {
+			panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
+		}
+		if len(expandedVar) > 1 {
+			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
+		}
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
+		})
+	}
+	return ret
+}
+
+// ExportedStringListVariables is a mapping of variable names to a list of strings
+type ExportedStringListVariables map[string][]string
+
+func (m ExportedStringListVariables) set(k string, v []string) {
+	m[k] = v
+}
+
+func (m ExportedStringListVariables) asBazel(config Config,
+	stringScope ExportedStringVariables, stringListScope ExportedStringListVariables,
+	exportedVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	// For each exported variable, recursively expand elements in the variableValue
+	// list to ensure that interpolated variables are expanded according to their values
+	// in the variable scope.
+	for k, variableValue := range m {
+		var expandedVars []string
+		for _, v := range variableValue {
+			expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
+			if err != nil {
+				panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
+			}
+			expandedVars = append(expandedVars, expandedVar...)
+		}
+		// Assign the list as a bzl-private variable; this variable will be exported
+		// out through a constants struct later.
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
+		})
+	}
+	return ret
+}
+
+// ExportedStringListDictVariables is a mapping from variable names to a
+// dictionary which maps keys to lists of strings
+type ExportedStringListDictVariables map[string]map[string][]string
+
+func (m ExportedStringListDictVariables) set(k string, v map[string][]string) {
+	m[k] = v
+}
+
+// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
+func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, dict := range m {
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
+		})
+	}
+	return ret
+}
+
+// ExportedVariableReferenceDictVariables is a mapping from variable names to a
+// dictionary which references previously defined variables. This is used to
+// create a Starlark output such as:
+// 		string_var1 = "string1
+// 		var_ref_dict_var1 = {
+// 			"key1": string_var1
+// 		}
+// This type of variable collection must be expanded last so that it recognizes
+// previously defined variables.
+type ExportedVariableReferenceDictVariables map[string]map[string]string
+
+func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) {
+	m[k] = v
+}
+
+func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for n, dict := range m {
+		for k, v := range dict {
+			matches, err := variableReference(v)
+			if err != nil {
+				panic(err)
+			} else if !matches.matches {
+				panic(fmt.Errorf("Expected a variable reference, got %q", v))
+			} else if len(matches.fullVariableReference) != len(v) {
+				panic(fmt.Errorf("Expected only a variable reference, got %q", v))
+			}
+			dict[k] = "_" + matches.variable
+		}
+		ret = append(ret, bazelConstant{
+			variableName:       n,
+			internalDefinition: starlark_fmt.PrintDict(dict, 0),
+			sortLast:           true,
+		})
+	}
+	return ret
+}
+
+// BazelToolchainVars expands an ExportedVariables collection and returns a string
+// of formatted Starlark variable definitions
+func BazelToolchainVars(config Config, exportedVars ExportedVariables) string {
+	results := exportedVars.asBazel(
+		config,
+		exportedVars.exportedStringVars,
+		exportedVars.exportedStringListVars,
+		exportedVars.exportedConfigDependingVars,
+	)
+
+	sort.Slice(results, func(i, j int) bool {
+		if results[i].sortLast != results[j].sortLast {
+			return !results[i].sortLast
+		}
+		return results[i].variableName < results[j].variableName
+	})
+
+	definitions := make([]string, 0, len(results))
+	constants := make([]string, 0, len(results))
+	for _, b := range results {
+		definitions = append(definitions,
+			fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
+		constants = append(constants,
+			fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
+	}
+
+	// Build the exported constants struct.
+	ret := bazel.GeneratedBazelFileWarning
+	ret += "\n\n"
+	ret += strings.Join(definitions, "\n\n")
+	ret += "\n\n"
+	ret += "constants = struct(\n"
+	ret += strings.Join(constants, "\n")
+	ret += "\n)"
+
+	return ret
+}
+
+type match struct {
+	matches               bool
+	fullVariableReference string
+	variable              string
+}
+
+func variableReference(input string) (match, error) {
+	// e.g. "${ExternalCflags}"
+	r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
+
+	matches := r.FindStringSubmatch(input)
+	if len(matches) == 0 {
+		return match{}, nil
+	}
+	if len(matches) != 2 {
+		return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
+	}
+	return match{
+		matches:               true,
+		fullVariableReference: matches[0],
+		// Index 1 of FindStringSubmatch contains the subexpression match
+		// (variable name) of the capture group.
+		variable: matches[1],
+	}, nil
+}
+
+// expandVar recursively expand interpolated variables in the exportedVars scope.
+//
+// We're using a string slice to track the seen variables to avoid
+// stackoverflow errors with infinite recursion. it's simpler to use a
+// string slice than to handle a pass-by-referenced map, which would make it
+// quite complex to track depth-first interpolations. It's also unlikely the
+// interpolation stacks are deep (n > 1).
+func expandVar(config Config, toExpand string, stringScope ExportedStringVariables,
+	stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) {
+
+	// Internal recursive function.
+	var expandVarInternal func(string, map[string]bool) (string, error)
+	expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
+		var ret string
+		remainingString := toExpand
+		for len(remainingString) > 0 {
+			matches, err := variableReference(remainingString)
+			if err != nil {
+				panic(err)
+			}
+			if !matches.matches {
+				return ret + remainingString, nil
+			}
+			matchIndex := strings.Index(remainingString, matches.fullVariableReference)
+			ret += remainingString[:matchIndex]
+			remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
+
+			variable := matches.variable
+			// toExpand contains a variable.
+			if _, ok := seenVars[variable]; ok {
+				return ret, fmt.Errorf(
+					"Unbounded recursive interpolation of variable: %s", variable)
+			}
+			// A map is passed-by-reference. Create a new map for
+			// this scope to prevent variables seen in one depth-first expansion
+			// to be also treated as "seen" in other depth-first traversals.
+			newSeenVars := map[string]bool{}
+			for k := range seenVars {
+				newSeenVars[k] = true
+			}
+			newSeenVars[variable] = true
+			if unexpandedVars, ok := stringListScope[variable]; ok {
+				expandedVars := []string{}
+				for _, unexpandedVar := range unexpandedVars {
+					expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+					if err != nil {
+						return ret, err
+					}
+					expandedVars = append(expandedVars, expandedVar)
+				}
+				ret += strings.Join(expandedVars, " ")
+			} else if unexpandedVar, ok := stringScope[variable]; ok {
+				expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else if unevaluatedVar, ok := exportedVars[variable]; ok {
+				evalFunc := reflect.ValueOf(unevaluatedVar)
+				validateVariableMethod(variable, evalFunc)
+				evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
+				evaluatedValue := evaluatedResult[0].Interface().(string)
+				expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else {
+				return "", fmt.Errorf("Unbound config variable %s", variable)
+			}
+		}
+		return ret, nil
+	}
+	var ret []string
+	stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ')
+	for _, v := range stringFields {
+		val, err := expandVarInternal(v, map[string]bool{})
+		if err != nil {
+			return ret, err
+		}
+		ret = append(ret, val)
+	}
+
+	return ret, nil
+}
+
+// splitStringKeepingQuotedSubstring splits a string on a provided separator,
+// but it will not split substrings inside unescaped double quotes. If the double
+// quotes are escaped, then the returned string will only include the quote, and
+// not the escape.
+func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string {
+	var ret []string
+	quote := byte('"')
+
+	var substring []byte
+	quoted := false
+	escaped := false
+
+	for i := range s {
+		if !quoted && s[i] == delimiter {
+			ret = append(ret, string(substring))
+			substring = []byte{}
+			continue
+		}
+
+		characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote
+		if characterIsEscape {
+			escaped = true
+			continue
+		}
+
+		if s[i] == quote {
+			if !escaped {
+				quoted = !quoted
+			}
+			escaped = false
+		}
+
+		substring = append(substring, s[i])
+	}
+
+	ret = append(ret, string(substring))
+
+	return ret
+}
+
+func validateVariableMethod(name string, methodValue reflect.Value) {
+	methodType := methodValue.Type()
+	if methodType.Kind() != reflect.Func {
+		panic(fmt.Errorf("method given for variable %s is not a function",
+			name))
+	}
+	if n := methodType.NumIn(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
+			name, n))
+	}
+	if n := methodType.NumOut(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
+			name, n))
+	}
+	if kind := methodType.Out(0).Kind(); kind != reflect.String {
+		panic(fmt.Errorf("method for variable %s does not return a string",
+			name))
+	}
+}
diff --git a/android/config_bp2build_test.go b/android/config_bp2build_test.go
new file mode 100644
index 0000000..1a0ba7b
--- /dev/null
+++ b/android/config_bp2build_test.go
@@ -0,0 +1,454 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"android/soong/bazel"
+	"testing"
+)
+
+func TestExpandVars(t *testing.T) {
+	android_arm64_config := TestConfig("out", nil, "", nil)
+	android_arm64_config.BuildOS = Android
+	android_arm64_config.BuildArch = Arm64
+
+	testCases := []struct {
+		description     string
+		config          Config
+		stringScope     ExportedStringVariables
+		stringListScope ExportedStringListVariables
+		configVars      ExportedConfigDependingVariables
+		toExpand        string
+		expectedValues  []string
+	}{
+		{
+			description:    "no expansion for non-interpolated value",
+			toExpand:       "foo",
+			expectedValues: []string{"foo"},
+		},
+		{
+			description: "single level expansion for string var",
+			stringScope: ExportedStringVariables{
+				"foo": "bar",
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "single level expansion with short-name for string var",
+			stringScope: ExportedStringVariables{
+				"foo": "bar",
+			},
+			toExpand:       "${config.foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "single level expansion string list var",
+			stringListScope: ExportedStringListVariables{
+				"foo": []string{"bar"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "mixed level expansion for string list var",
+			stringScope: ExportedStringVariables{
+				"foo": "${bar}",
+				"qux": "hello",
+			},
+			stringListScope: ExportedStringListVariables{
+				"bar": []string{"baz", "${qux}"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz hello"},
+		},
+		{
+			description: "double level expansion",
+			stringListScope: ExportedStringListVariables{
+				"foo": []string{"${bar}"},
+				"bar": []string{"baz"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz"},
+		},
+		{
+			description: "double level expansion with a literal",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b}", "c"},
+				"b": []string{"d"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d c"},
+		},
+		{
+			description: "double level expansion, with two variables in a string",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"d"},
+				"c": []string{"e"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d e"},
+		},
+		{
+			description: "triple level expansion with two variables in a string",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"${c}", "${d}"},
+				"c": []string{"${d}"},
+				"d": []string{"foo"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"foo foo foo"},
+		},
+		{
+			description: "expansion with config depending vars",
+			configVars: ExportedConfigDependingVariables{
+				"a": func(c Config) string { return c.BuildOS.String() },
+				"b": func(c Config) string { return c.BuildArch.String() },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${a}-${b}",
+			expectedValues: []string{"android-arm64"},
+		},
+		{
+			description: "double level multi type expansion",
+			stringListScope: ExportedStringListVariables{
+				"platform": []string{"${os}-${arch}"},
+				"const":    []string{"const"},
+			},
+			configVars: ExportedConfigDependingVariables{
+				"os":   func(c Config) string { return c.BuildOS.String() },
+				"arch": func(c Config) string { return c.BuildArch.String() },
+				"foo":  func(c Config) string { return "foo" },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${const}/${platform}/${foo}",
+			expectedValues: []string{"const/android-arm64/foo"},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
+			if len(output) != len(testCase.expectedValues) {
+				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
+			}
+			for i, actual := range output {
+				expectedValue := testCase.expectedValues[i]
+				if actual != expectedValue {
+					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
+				}
+			}
+		})
+	}
+}
+
+func TestBazelToolchainVars(t *testing.T) {
+	testCases := []struct {
+		name        string
+		config      Config
+		vars        ExportedVariables
+		expectedOut string
+	}{
+		{
+			name: "exports strings",
+			vars: ExportedVariables{
+				exportedStringVars: ExportedStringVariables{
+					"a": "b",
+					"c": "d",
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = "b"
+
+_c = "d"
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports string lists",
+			vars: ExportedVariables{
+				exportedStringListVars: ExportedStringListVariables{
+					"a": []string{"b1", "b2"},
+					"c": []string{"d1", "d2"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = [
+    "b1",
+    "b2",
+]
+
+_c = [
+    "d1",
+    "d2",
+]
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports string lists dicts",
+			vars: ExportedVariables{
+				exportedStringListDictVars: ExportedStringListDictVariables{
+					"a": map[string][]string{"b1": {"b2"}},
+					"c": map[string][]string{"d1": {"d2"}},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "b1": ["b2"],
+}
+
+_c = {
+    "d1": ["d2"],
+}
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports dict with var refs",
+			vars: ExportedVariables{
+				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+					"a": map[string]string{"b1": "${b2}"},
+					"c": map[string]string{"d1": "${config.d2}"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "b1": _b2,
+}
+
+_c = {
+    "d1": _d2,
+}
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "sorts across types with variable references last",
+			vars: ExportedVariables{
+				exportedStringVars: ExportedStringVariables{
+					"b": "b-val",
+					"d": "d-val",
+				},
+				exportedStringListVars: ExportedStringListVariables{
+					"c": []string{"c-val"},
+					"e": []string{"e-val"},
+				},
+				exportedStringListDictVars: ExportedStringListDictVariables{
+					"a": map[string][]string{"a1": {"a2"}},
+					"f": map[string][]string{"f1": {"f2"}},
+				},
+				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+					"aa": map[string]string{"b1": "${b}"},
+					"cc": map[string]string{"d1": "${config.d}"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "a1": ["a2"],
+}
+
+_b = "b-val"
+
+_c = ["c-val"]
+
+_d = "d-val"
+
+_e = ["e-val"]
+
+_f = {
+    "f1": ["f2"],
+}
+
+_aa = {
+    "b1": _b,
+}
+
+_cc = {
+    "d1": _d,
+}
+
+constants = struct(
+    a = _a,
+    b = _b,
+    c = _c,
+    d = _d,
+    e = _e,
+    f = _f,
+    aa = _aa,
+    cc = _cc,
+)`,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			out := BazelToolchainVars(tc.config, tc.vars)
+			if out != tc.expectedOut {
+				t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
+			}
+		})
+	}
+}
+
+func TestSplitStringKeepingQuotedSubstring(t *testing.T) {
+	testCases := []struct {
+		description string
+		s           string
+		delimiter   byte
+		split       []string
+	}{
+		{
+			description: "empty string returns single empty string",
+			s:           "",
+			delimiter:   ' ',
+			split: []string{
+				"",
+			},
+		},
+		{
+			description: "string with single space returns two empty strings",
+			s:           " ",
+			delimiter:   ' ',
+			split: []string{
+				"",
+				"",
+			},
+		},
+		{
+			description: "string with two spaces returns three empty strings",
+			s:           "  ",
+			delimiter:   ' ',
+			split: []string{
+				"",
+				"",
+				"",
+			},
+		},
+		{
+			description: "string with four words returns four word string",
+			s:           "hello world with words",
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				"world",
+				"with",
+				"words",
+			},
+		},
+		{
+			description: "string with words and nested quote returns word strings and quote string",
+			s:           `hello "world with" words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world with"`,
+				"words",
+			},
+		},
+		{
+			description: "string with escaped quote inside real quotes",
+			s:           `hello \"world "with\" words"`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world`,
+				`"with" words"`,
+			},
+		},
+		{
+			description: "string with words and escaped quotes returns word strings",
+			s:           `hello \"world with\" words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world`,
+				`with"`,
+				"words",
+			},
+		},
+		{
+			description: "string which is single quoted substring returns only substring",
+			s:           `"hello world with words"`,
+			delimiter:   ' ',
+			split: []string{
+				`"hello world with words"`,
+			},
+		},
+		{
+			description: "string starting with quote returns quoted string",
+			s:           `"hello world with" words`,
+			delimiter:   ' ',
+			split: []string{
+				`"hello world with"`,
+				"words",
+			},
+		},
+		{
+			description: "string with starting quote and no ending quote returns quote to end of string",
+			s:           `hello "world with words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world with words`,
+			},
+		},
+		{
+			description: "quoted string is treated as a single \"word\" unless separated by delimiter",
+			s:           `hello "world"with words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world"with`,
+				"words",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			split := splitStringKeepingQuotedSubstring(tc.s, tc.delimiter)
+			if len(split) != len(tc.split) {
+				t.Fatalf("number of split string elements (%d) differs from expected (%d): split string (%v), expected (%v)",
+					len(split), len(tc.split), split, tc.split,
+				)
+			}
+			for i := range split {
+				if split[i] != tc.split[i] {
+					t.Errorf("split string element (%d), %v, differs from expected, %v", i, split[i], tc.split[i])
+				}
+			}
+		})
+	}
+}
diff --git a/android/variable.go b/android/variable.go
index 4ed0507..077b810 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -389,6 +389,8 @@
 	CertificateOverrides         []string `json:",omitempty"`
 	PackageNameOverrides         []string `json:",omitempty"`
 
+	ApexGlobalMinSdkVersionOverride *string `json:",omitempty"`
+
 	EnforceSystemCertificate          *bool    `json:",omitempty"`
 	EnforceSystemCertificateAllowList []string `json:",omitempty"`
 
diff --git a/apex/apex.go b/apex/apex.go
index 2fe17da..a7b0a4f 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2450,20 +2450,43 @@
 	android.CheckMinSdkVersion(ctx, minSdkVersion, a.WalkPayloadDeps)
 }
 
+// Returns apex's min_sdk_version string value, honoring overrides
+func (a *apexBundle) minSdkVersionValue(ctx android.EarlyModuleContext) string {
+	// Only override the minSdkVersion value on Apexes which already specify
+	// a min_sdk_version (it's optional for non-updatable apexes), and that its
+	// min_sdk_version value is lower than the one to override with.
+	overrideMinSdkValue := ctx.DeviceConfig().ApexGlobalMinSdkVersionOverride()
+	overrideApiLevel := minSdkVersionFromValue(ctx, overrideMinSdkValue)
+	originalMinApiLevel := minSdkVersionFromValue(ctx, proptools.String(a.properties.Min_sdk_version))
+	isMinSdkSet := a.properties.Min_sdk_version != nil
+	isOverrideValueHigher := overrideApiLevel.CompareTo(originalMinApiLevel) > 0
+	if overrideMinSdkValue != "" && isMinSdkSet && isOverrideValueHigher {
+		return overrideMinSdkValue
+	}
+
+	return proptools.String(a.properties.Min_sdk_version)
+}
+
+// Returns apex's min_sdk_version SdkSpec, honoring overrides
 func (a *apexBundle) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
 	return android.SdkSpec{
 		Kind:     android.SdkNone,
 		ApiLevel: a.minSdkVersion(ctx),
-		Raw:      String(a.properties.Min_sdk_version),
+		Raw:      a.minSdkVersionValue(ctx),
 	}
 }
 
+// Returns apex's min_sdk_version ApiLevel, honoring overrides
 func (a *apexBundle) minSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
-	ver := proptools.String(a.properties.Min_sdk_version)
-	if ver == "" {
+	return minSdkVersionFromValue(ctx, a.minSdkVersionValue(ctx))
+}
+
+// Construct ApiLevel object from min_sdk_version string value
+func minSdkVersionFromValue(ctx android.EarlyModuleContext, value string) android.ApiLevel {
+	if value == "" {
 		return android.NoneApiLevel
 	}
-	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
+	apiLevel, err := android.ApiLevelFromUser(ctx, value)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		return android.NoneApiLevel
@@ -2518,7 +2541,7 @@
 // checkUpdatable enforces APEX and its transitive dep properties to have desired values for updatable APEXes.
 func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
 	if a.Updatable() {
-		if String(a.properties.Min_sdk_version) == "" {
+		if a.minSdkVersionValue(ctx) == "" {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
 		}
 		if a.UsePlatformApis() {
@@ -3398,6 +3421,8 @@
 		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.properties.File_contexts))
 	}
 
+	// TODO(b/219503907) this would need to be set to a.MinSdkVersionValue(ctx) but
+	// given it's coming via config, we probably don't want to put it in here.
 	var minSdkVersion *string
 	if a.properties.Min_sdk_version != nil {
 		minSdkVersion = a.properties.Min_sdk_version
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3e01f26..cfffc14 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -113,6 +113,12 @@
 	})
 }
 
+func withApexGlobalMinSdkVersionOverride(minSdkOverride *string) android.FixturePreparer {
+	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.ApexGlobalMinSdkVersionOverride = minSdkOverride
+	})
+}
+
 var withBinder32bit = android.FixtureModifyProductVariables(
 	func(variables android.FixtureProductVariables) {
 		variables.Binder32bit = proptools.BoolPtr(true)
@@ -168,44 +174,42 @@
 		"system/sepolicy/apex/otherapex-file_contexts":                nil,
 		"system/sepolicy/apex/com.android.vndk-file_contexts":         nil,
 		"system/sepolicy/apex/com.android.vndk.current-file_contexts": nil,
-		"mylib.cpp":                                  nil,
-		"mytest.cpp":                                 nil,
-		"mytest1.cpp":                                nil,
-		"mytest2.cpp":                                nil,
-		"mytest3.cpp":                                nil,
-		"myprebuilt":                                 nil,
-		"my_include":                                 nil,
-		"foo/bar/MyClass.java":                       nil,
-		"prebuilt.jar":                               nil,
-		"prebuilt.so":                                nil,
-		"vendor/foo/devkeys/test.x509.pem":           nil,
-		"vendor/foo/devkeys/test.pk8":                nil,
-		"testkey.x509.pem":                           nil,
-		"testkey.pk8":                                nil,
-		"testkey.override.x509.pem":                  nil,
-		"testkey.override.pk8":                       nil,
-		"vendor/foo/devkeys/testkey.avbpubkey":       nil,
-		"vendor/foo/devkeys/testkey.pem":             nil,
-		"NOTICE":                                     nil,
-		"custom_notice":                              nil,
-		"custom_notice_for_static_lib":               nil,
-		"testkey2.avbpubkey":                         nil,
-		"testkey2.pem":                               nil,
-		"myapex-arm64.apex":                          nil,
-		"myapex-arm.apex":                            nil,
-		"myapex.apks":                                nil,
-		"frameworks/base/api/current.txt":            nil,
-		"framework/aidl/a.aidl":                      nil,
-		"build/make/core/proguard.flags":             nil,
-		"build/make/core/proguard_basic_keeps.flags": nil,
-		"dummy.txt":                                  nil,
-		"baz":                                        nil,
-		"bar/baz":                                    nil,
-		"testdata/baz":                               nil,
-		"AppSet.apks":                                nil,
-		"foo.rs":                                     nil,
-		"libfoo.jar":                                 nil,
-		"libbar.jar":                                 nil,
+		"mylib.cpp":                            nil,
+		"mytest.cpp":                           nil,
+		"mytest1.cpp":                          nil,
+		"mytest2.cpp":                          nil,
+		"mytest3.cpp":                          nil,
+		"myprebuilt":                           nil,
+		"my_include":                           nil,
+		"foo/bar/MyClass.java":                 nil,
+		"prebuilt.jar":                         nil,
+		"prebuilt.so":                          nil,
+		"vendor/foo/devkeys/test.x509.pem":     nil,
+		"vendor/foo/devkeys/test.pk8":          nil,
+		"testkey.x509.pem":                     nil,
+		"testkey.pk8":                          nil,
+		"testkey.override.x509.pem":            nil,
+		"testkey.override.pk8":                 nil,
+		"vendor/foo/devkeys/testkey.avbpubkey": nil,
+		"vendor/foo/devkeys/testkey.pem":       nil,
+		"NOTICE":                               nil,
+		"custom_notice":                        nil,
+		"custom_notice_for_static_lib":         nil,
+		"testkey2.avbpubkey":                   nil,
+		"testkey2.pem":                         nil,
+		"myapex-arm64.apex":                    nil,
+		"myapex-arm.apex":                      nil,
+		"myapex.apks":                          nil,
+		"frameworks/base/api/current.txt":      nil,
+		"framework/aidl/a.aidl":                nil,
+		"dummy.txt":                            nil,
+		"baz":                                  nil,
+		"bar/baz":                              nil,
+		"testdata/baz":                         nil,
+		"AppSet.apks":                          nil,
+		"foo.rs":                               nil,
+		"libfoo.jar":                           nil,
+		"libbar.jar":                           nil,
 	},
 	),
 
@@ -6314,6 +6318,124 @@
 	ensureNotContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.apex")
 }
 
+func TestMinSdkVersionOverride(t *testing.T) {
+	// Override from 29 to 31
+	minSdkOverride31 := "31"
+	ctx := testApex(t, `
+			apex {
+					name: "myapex",
+					key: "myapex.key",
+					native_shared_libs: ["mylib"],
+					updatable: true,
+					min_sdk_version: "29"
+			}
+
+			override_apex {
+					name: "override_myapex",
+					base: "myapex",
+					logging_parent: "com.foo.bar",
+					package_name: "test.overridden.package"
+			}
+
+			apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+			}
+
+			cc_library {
+					name: "mylib",
+					srcs: ["mylib.cpp"],
+					runtime_libs: ["libbar"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+			cc_library {
+					name: "libbar",
+					srcs: ["mylib.cpp"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride31))
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+
+	// Ensure that runtime_libs dep in included
+	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
+
+	// Ensure libraries target overridden min_sdk_version value
+	ensureListContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_shared_apex31")
+}
+
+func TestMinSdkVersionOverrideToLowerVersionNoOp(t *testing.T) {
+	// Attempt to override from 31 to 29, should be a NOOP
+	minSdkOverride29 := "29"
+	ctx := testApex(t, `
+			apex {
+					name: "myapex",
+					key: "myapex.key",
+					native_shared_libs: ["mylib"],
+					updatable: true,
+					min_sdk_version: "31"
+			}
+
+			override_apex {
+					name: "override_myapex",
+					base: "myapex",
+					logging_parent: "com.foo.bar",
+					package_name: "test.overridden.package"
+			}
+
+			apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+			}
+
+			cc_library {
+					name: "mylib",
+					srcs: ["mylib.cpp"],
+					runtime_libs: ["libbar"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+			cc_library {
+					name: "libbar",
+					srcs: ["mylib.cpp"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride29))
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+
+	// Ensure that runtime_libs dep in included
+	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
+
+	// Ensure libraries target the original min_sdk_version value rather than the overridden
+	ensureListContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_shared_apex31")
+}
+
 func TestLegacyAndroid10Support(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
diff --git a/apex/builder.go b/apex/builder.go
index ea61e1a..293f388 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -994,7 +994,7 @@
 		return !externalDep
 	})
 
-	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, proptools.String(a.properties.Min_sdk_version), depInfos)
+	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(ctx).Raw, depInfos)
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Phony,
diff --git a/bazel/constants.go b/bazel/constants.go
index 6beb496..b10f256 100644
--- a/bazel/constants.go
+++ b/bazel/constants.go
@@ -21,7 +21,7 @@
 
 	SoongInjectionDirName = "soong_injection"
 
-	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT"
+	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT."
 )
 
 // String returns the name of the run.
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 1d3b105..a96a3fc 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -580,7 +580,9 @@
 				elements = append(elements, val)
 			}
 		}
-		return starlark_fmt.PrintList(elements, indent, "%s"), nil
+		return starlark_fmt.PrintList(elements, indent, func(s string) string {
+			return "%s"
+		}), nil
 
 	case reflect.Struct:
 		// Special cases where the bp2build sends additional information to the codegenerator
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 91e614d..1790dd7 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -7,7 +7,8 @@
 	"strings"
 
 	"android/soong/android"
-	"android/soong/cc/config"
+	cc_config "android/soong/cc/config"
+	java_config "android/soong/java/config"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -22,7 +23,10 @@
 	var files []BazelFile
 
 	files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
-	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars(cfg)))
+	files = append(files, newFile("cc_toolchain", "constants.bzl", cc_config.BazelCcToolchainVars(cfg)))
+
+	files = append(files, newFile("java_toolchain", GeneratedBuildFileName, "")) // Creates a //java_toolchain package.
+	files = append(files, newFile("java_toolchain", "constants.bzl", java_config.BazelJavaToolchainVars(cfg)))
 
 	files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.convertedModules, "\n")))
 
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index d65ece8..e49d855 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -95,6 +95,14 @@
 			basename: "constants.bzl",
 		},
 		{
+			dir:      "java_toolchain",
+			basename: GeneratedBuildFileName,
+		},
+		{
+			dir:      "java_toolchain",
+			basename: "constants.bzl",
+		},
+		{
 			dir:      "metrics",
 			basename: "converted_modules.txt",
 		},
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 4b75e3b..ccc52ef 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -219,3 +219,35 @@
 		},
 	})
 }
+
+func TestJavaLibraryLogTags(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		description:                "Java library - logtags creates separate dependency",
+		moduleTypeUnderTest:        "java_library",
+		moduleTypeUnderTestFactory: java.LibraryFactory,
+		blueprint: `java_library {
+        name: "example_lib",
+        srcs: [
+			"a.java",
+			"b.java",
+			"a.logtag",
+			"b.logtag",
+		],
+        bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("event_log_tags", "example_lib_logtags", attrNameToString{
+				"srcs": `[
+        "a.logtag",
+        "b.logtag",
+    ]`,
+			}),
+			makeBazelTarget("java_library", "example_lib", attrNameToString{
+				"srcs": `[
+        "a.java",
+        "b.java",
+        ":example_lib_logtags",
+    ]`,
+			}),
+		}})
+}
diff --git a/cc/afdo.go b/cc/afdo.go
index c888213..66e9732 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -45,6 +45,8 @@
 }
 
 type AfdoProperties struct {
+	// Afdo allows developers self-service enroll for
+	// automatic feedback-directed optimization using profile data.
 	Afdo bool
 
 	AfdoTarget *string  `blueprint:"mutated"`
diff --git a/cc/config/Android.bp b/cc/config/Android.bp
index e1b0605..1a21c13 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -11,7 +11,6 @@
         "soong-starlark-format",
     ],
     srcs: [
-        "bp2build.go",
         "clang.go",
         "global.go",
         "tidy.go",
@@ -33,7 +32,6 @@
         "arm64_linux_host.go",
     ],
     testSrcs: [
-        "bp2build_test.go",
         "tidy_test.go",
     ],
 }
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index 4d0ae1a..dfe143f 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -98,28 +98,28 @@
 	pctx.SourcePathVariable("Arm64GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/aarch64/aarch64-linux-android-${arm64GccVersion}")
 
-	exportStringListStaticVariable("Arm64Ldflags", arm64Ldflags)
-	exportStringListStaticVariable("Arm64Lldflags", arm64Lldflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Ldflags", arm64Ldflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Lldflags", arm64Lldflags)
 
-	exportStringListStaticVariable("Arm64Cflags", arm64Cflags)
-	exportStringListStaticVariable("Arm64Cppflags", arm64Cppflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Cflags", arm64Cflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Cppflags", arm64Cppflags)
 
-	exportedVariableReferenceDictVars.Set("Arm64ArchVariantCflags", arm64ArchVariantCflagsVar)
-	exportedVariableReferenceDictVars.Set("Arm64CpuVariantCflags", arm64CpuVariantCflagsVar)
-	exportedVariableReferenceDictVars.Set("Arm64CpuVariantLdflags", arm64CpuVariantLdflags)
+	exportedVars.ExportVariableReferenceDict("Arm64ArchVariantCflags", arm64ArchVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("Arm64CpuVariantCflags", arm64CpuVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("Arm64CpuVariantLdflags", arm64CpuVariantLdflags)
 
-	exportStringListStaticVariable("Arm64Armv8ACflags", arm64ArchVariantCflags["armv8-a"])
-	exportStringListStaticVariable("Arm64Armv8ABranchProtCflags", arm64ArchVariantCflags["armv8-a-branchprot"])
-	exportStringListStaticVariable("Arm64Armv82ACflags", arm64ArchVariantCflags["armv8-2a"])
-	exportStringListStaticVariable("Arm64Armv82ADotprodCflags", arm64ArchVariantCflags["armv8-2a-dotprod"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv8ACflags", arm64ArchVariantCflags["armv8-a"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv8ABranchProtCflags", arm64ArchVariantCflags["armv8-a-branchprot"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv82ACflags", arm64ArchVariantCflags["armv8-2a"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv82ADotprodCflags", arm64ArchVariantCflags["armv8-2a-dotprod"])
 
-	exportStringListStaticVariable("Arm64CortexA53Cflags", arm64CpuVariantCflags["cortex-a53"])
-	exportStringListStaticVariable("Arm64CortexA55Cflags", arm64CpuVariantCflags["cortex-a55"])
-	exportStringListStaticVariable("Arm64KryoCflags", arm64CpuVariantCflags["kryo"])
-	exportStringListStaticVariable("Arm64ExynosM1Cflags", arm64CpuVariantCflags["exynos-m1"])
-	exportStringListStaticVariable("Arm64ExynosM2Cflags", arm64CpuVariantCflags["exynos-m2"])
+	exportedVars.ExportStringListStaticVariable("Arm64CortexA53Cflags", arm64CpuVariantCflags["cortex-a53"])
+	exportedVars.ExportStringListStaticVariable("Arm64CortexA55Cflags", arm64CpuVariantCflags["cortex-a55"])
+	exportedVars.ExportStringListStaticVariable("Arm64KryoCflags", arm64CpuVariantCflags["kryo"])
+	exportedVars.ExportStringListStaticVariable("Arm64ExynosM1Cflags", arm64CpuVariantCflags["exynos-m1"])
+	exportedVars.ExportStringListStaticVariable("Arm64ExynosM2Cflags", arm64CpuVariantCflags["exynos-m2"])
 
-	exportStringListStaticVariable("Arm64FixCortexA53Ldflags", []string{"-Wl,--fix-cortex-a53-843419"})
+	exportedVars.ExportStringListStaticVariable("Arm64FixCortexA53Ldflags", []string{"-Wl,--fix-cortex-a53-843419"})
 }
 
 var (
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index 4466632..d702c61 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -178,41 +178,41 @@
 	pctx.SourcePathVariable("ArmGccRoot", "prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}")
 
 	// Just exported. Not created as a Ninja static variable.
-	exportedStringVars.Set("ArmClangTriple", clangTriple)
+	exportedVars.ExportString("ArmClangTriple", clangTriple)
 
-	exportStringListStaticVariable("ArmLdflags", armLdflags)
-	exportStringListStaticVariable("ArmLldflags", armLldflags)
+	exportedVars.ExportStringListStaticVariable("ArmLdflags", armLdflags)
+	exportedVars.ExportStringListStaticVariable("ArmLldflags", armLldflags)
 
-	exportStringListStaticVariable("ArmFixCortexA8LdFlags", armFixCortexA8LdFlags)
-	exportStringListStaticVariable("ArmNoFixCortexA8LdFlags", armNoFixCortexA8LdFlags)
+	exportedVars.ExportStringListStaticVariable("ArmFixCortexA8LdFlags", armFixCortexA8LdFlags)
+	exportedVars.ExportStringListStaticVariable("ArmNoFixCortexA8LdFlags", armNoFixCortexA8LdFlags)
 
 	// Clang cflags
-	exportStringListStaticVariable("ArmToolchainCflags", armToolchainCflags)
-	exportStringListStaticVariable("ArmCflags", armCflags)
-	exportStringListStaticVariable("ArmCppflags", armCppflags)
+	exportedVars.ExportStringListStaticVariable("ArmToolchainCflags", armToolchainCflags)
+	exportedVars.ExportStringListStaticVariable("ArmCflags", armCflags)
+	exportedVars.ExportStringListStaticVariable("ArmCppflags", armCppflags)
 
 	// Clang ARM vs. Thumb instruction set cflags
-	exportStringListStaticVariable("ArmArmCflags", armArmCflags)
-	exportStringListStaticVariable("ArmThumbCflags", armThumbCflags)
+	exportedVars.ExportStringListStaticVariable("ArmArmCflags", armArmCflags)
+	exportedVars.ExportStringListStaticVariable("ArmThumbCflags", armThumbCflags)
 
-	exportedVariableReferenceDictVars.Set("ArmArchVariantCflags", armArchVariantCflagsVar)
-	exportedVariableReferenceDictVars.Set("ArmCpuVariantCflags", armCpuVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("ArmArchVariantCflags", armArchVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("ArmCpuVariantCflags", armCpuVariantCflagsVar)
 
 	// Clang arch variant cflags
-	exportStringListStaticVariable("ArmArmv7ACflags", armArchVariantCflags["armv7-a"])
-	exportStringListStaticVariable("ArmArmv7ANeonCflags", armArchVariantCflags["armv7-a-neon"])
-	exportStringListStaticVariable("ArmArmv8ACflags", armArchVariantCflags["armv8-a"])
-	exportStringListStaticVariable("ArmArmv82ACflags", armArchVariantCflags["armv8-2a"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv7ACflags", armArchVariantCflags["armv7-a"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv7ANeonCflags", armArchVariantCflags["armv7-a-neon"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv8ACflags", armArchVariantCflags["armv8-a"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv82ACflags", armArchVariantCflags["armv8-2a"])
 
 	// Clang cpu variant cflags
-	exportStringListStaticVariable("ArmGenericCflags", armCpuVariantCflags[""])
-	exportStringListStaticVariable("ArmCortexA7Cflags", armCpuVariantCflags["cortex-a7"])
-	exportStringListStaticVariable("ArmCortexA8Cflags", armCpuVariantCflags["cortex-a8"])
-	exportStringListStaticVariable("ArmCortexA15Cflags", armCpuVariantCflags["cortex-a15"])
-	exportStringListStaticVariable("ArmCortexA53Cflags", armCpuVariantCflags["cortex-a53"])
-	exportStringListStaticVariable("ArmCortexA55Cflags", armCpuVariantCflags["cortex-a55"])
-	exportStringListStaticVariable("ArmKraitCflags", armCpuVariantCflags["krait"])
-	exportStringListStaticVariable("ArmKryoCflags", armCpuVariantCflags["kryo"])
+	exportedVars.ExportStringListStaticVariable("ArmGenericCflags", armCpuVariantCflags[""])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA7Cflags", armCpuVariantCflags["cortex-a7"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA8Cflags", armCpuVariantCflags["cortex-a8"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA15Cflags", armCpuVariantCflags["cortex-a15"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA53Cflags", armCpuVariantCflags["cortex-a53"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA55Cflags", armCpuVariantCflags["cortex-a55"])
+	exportedVars.ExportStringListStaticVariable("ArmKraitCflags", armCpuVariantCflags["krait"])
+	exportedVars.ExportStringListStaticVariable("ArmKryoCflags", armCpuVariantCflags["kryo"])
 }
 
 var (
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
deleted file mode 100644
index 73f65f5..0000000
--- a/cc/config/bp2build.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package config
-
-import (
-	"fmt"
-	"reflect"
-	"regexp"
-	"sort"
-	"strings"
-
-	"android/soong/android"
-	"android/soong/starlark_fmt"
-
-	"github.com/google/blueprint"
-)
-
-type bazelVarExporter interface {
-	asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
-}
-
-// Helpers for exporting cc configuration information to Bazel.
-var (
-	// Maps containing toolchain variables that are independent of the
-	// environment variables of the build.
-	exportedStringListVars     = exportedStringListVariables{}
-	exportedStringVars         = exportedStringVariables{}
-	exportedStringListDictVars = exportedStringListDictVariables{}
-	// Note: these can only contain references to other variables and must be printed last
-	exportedVariableReferenceDictVars = exportedVariableReferenceDictVariables{}
-
-	/// Maps containing variables that are dependent on the build config.
-	exportedConfigDependingVars = exportedConfigDependingVariables{}
-)
-
-type exportedConfigDependingVariables map[string]interface{}
-
-func (m exportedConfigDependingVariables) Set(k string, v interface{}) {
-	m[k] = v
-}
-
-// Ensure that string s has no invalid characters to be generated into the bzl file.
-func validateCharacters(s string) string {
-	for _, c := range []string{`\n`, `"`, `\`} {
-		if strings.Contains(s, c) {
-			panic(fmt.Errorf("%s contains illegal character %s", s, c))
-		}
-	}
-	return s
-}
-
-type bazelConstant struct {
-	variableName       string
-	internalDefinition string
-	sortLast           bool
-}
-
-type exportedStringVariables map[string]string
-
-func (m exportedStringVariables) Set(k string, v string) {
-	m[k] = v
-}
-
-func (m exportedStringVariables) asBazel(config android.Config,
-	stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
-	ret := make([]bazelConstant, 0, len(m))
-	for k, variableValue := range m {
-		expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
-		if err != nil {
-			panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
-		}
-		if len(expandedVar) > 1 {
-			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
-		}
-		ret = append(ret, bazelConstant{
-			variableName:       k,
-			internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
-		})
-	}
-	return ret
-}
-
-// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
-func exportStringStaticVariable(name string, value string) {
-	pctx.StaticVariable(name, value)
-	exportedStringVars.Set(name, value)
-}
-
-type exportedStringListVariables map[string][]string
-
-func (m exportedStringListVariables) Set(k string, v []string) {
-	m[k] = v
-}
-
-func (m exportedStringListVariables) asBazel(config android.Config,
-	stringScope exportedStringVariables, stringListScope exportedStringListVariables,
-	exportedVars exportedConfigDependingVariables) []bazelConstant {
-	ret := make([]bazelConstant, 0, len(m))
-	// For each exported variable, recursively expand elements in the variableValue
-	// list to ensure that interpolated variables are expanded according to their values
-	// in the variable scope.
-	for k, variableValue := range m {
-		var expandedVars []string
-		for _, v := range variableValue {
-			expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
-			if err != nil {
-				panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
-			}
-			expandedVars = append(expandedVars, expandedVar...)
-		}
-		// Assign the list as a bzl-private variable; this variable will be exported
-		// out through a constants struct later.
-		ret = append(ret, bazelConstant{
-			variableName:       k,
-			internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
-		})
-	}
-	return ret
-}
-
-// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
-func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
-	exportedConfigDependingVars.Set(name, method)
-	return pctx.VariableConfigMethod(name, method)
-}
-
-// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
-func exportSourcePathVariable(name string, value string) {
-	pctx.SourcePathVariable(name, value)
-	exportedStringVars.Set(name, value)
-}
-
-// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
-func exportStringListStaticVariable(name string, value []string) {
-	pctx.StaticVariable(name, strings.Join(value, " "))
-	exportedStringListVars.Set(name, value)
-}
-
-func ExportStringList(name string, value []string) {
-	exportedStringListVars.Set(name, value)
-}
-
-type exportedStringListDictVariables map[string]map[string][]string
-
-func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
-	m[k] = v
-}
-
-// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
-func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
-	_ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
-	ret := make([]bazelConstant, 0, len(m))
-	for k, dict := range m {
-		ret = append(ret, bazelConstant{
-			variableName:       k,
-			internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
-		})
-	}
-	return ret
-}
-
-type exportedVariableReferenceDictVariables map[string]map[string]string
-
-func (m exportedVariableReferenceDictVariables) Set(k string, v map[string]string) {
-	m[k] = v
-}
-
-func (m exportedVariableReferenceDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
-	_ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
-	ret := make([]bazelConstant, 0, len(m))
-	for n, dict := range m {
-		for k, v := range dict {
-			matches, err := variableReference(v)
-			if err != nil {
-				panic(err)
-			} else if !matches.matches {
-				panic(fmt.Errorf("Expected a variable reference, got %q", v))
-			} else if len(matches.fullVariableReference) != len(v) {
-				panic(fmt.Errorf("Expected only a variable reference, got %q", v))
-			}
-			dict[k] = "_" + matches.variable
-		}
-		ret = append(ret, bazelConstant{
-			variableName:       n,
-			internalDefinition: starlark_fmt.PrintDict(dict, 0),
-			sortLast:           true,
-		})
-	}
-	return ret
-}
-
-// BazelCcToolchainVars generates bzl file content containing variables for
-// Bazel's cc_toolchain configuration.
-func BazelCcToolchainVars(config android.Config) string {
-	return bazelToolchainVars(
-		config,
-		exportedStringListDictVars,
-		exportedStringListVars,
-		exportedStringVars,
-		exportedVariableReferenceDictVars)
-}
-
-func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string {
-	ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
-
-	results := []bazelConstant{}
-	for _, v := range vars {
-		results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...)
-	}
-
-	sort.Slice(results, func(i, j int) bool {
-		if results[i].sortLast != results[j].sortLast {
-			return !results[i].sortLast
-		}
-		return results[i].variableName < results[j].variableName
-	})
-
-	definitions := make([]string, 0, len(results))
-	constants := make([]string, 0, len(results))
-	for _, b := range results {
-		definitions = append(definitions,
-			fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
-		constants = append(constants,
-			fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
-	}
-
-	// Build the exported constants struct.
-	ret += strings.Join(definitions, "\n\n")
-	ret += "\n\n"
-	ret += "constants = struct(\n"
-	ret += strings.Join(constants, "\n")
-	ret += "\n)"
-
-	return ret
-}
-
-type match struct {
-	matches               bool
-	fullVariableReference string
-	variable              string
-}
-
-func variableReference(input string) (match, error) {
-	// e.g. "${ExternalCflags}"
-	r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
-
-	matches := r.FindStringSubmatch(input)
-	if len(matches) == 0 {
-		return match{}, nil
-	}
-	if len(matches) != 2 {
-		return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
-	}
-	return match{
-		matches:               true,
-		fullVariableReference: matches[0],
-		// Index 1 of FindStringSubmatch contains the subexpression match
-		// (variable name) of the capture group.
-		variable: matches[1],
-	}, nil
-}
-
-// expandVar recursively expand interpolated variables in the exportedVars scope.
-//
-// We're using a string slice to track the seen variables to avoid
-// stackoverflow errors with infinite recursion. it's simpler to use a
-// string slice than to handle a pass-by-referenced map, which would make it
-// quite complex to track depth-first interpolations. It's also unlikely the
-// interpolation stacks are deep (n > 1).
-func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables,
-	stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) {
-
-	// Internal recursive function.
-	var expandVarInternal func(string, map[string]bool) (string, error)
-	expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
-		var ret string
-		remainingString := toExpand
-		for len(remainingString) > 0 {
-			matches, err := variableReference(remainingString)
-			if err != nil {
-				panic(err)
-			}
-			if !matches.matches {
-				return ret + remainingString, nil
-			}
-			matchIndex := strings.Index(remainingString, matches.fullVariableReference)
-			ret += remainingString[:matchIndex]
-			remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
-
-			variable := matches.variable
-			// toExpand contains a variable.
-			if _, ok := seenVars[variable]; ok {
-				return ret, fmt.Errorf(
-					"Unbounded recursive interpolation of variable: %s", variable)
-			}
-			// A map is passed-by-reference. Create a new map for
-			// this scope to prevent variables seen in one depth-first expansion
-			// to be also treated as "seen" in other depth-first traversals.
-			newSeenVars := map[string]bool{}
-			for k := range seenVars {
-				newSeenVars[k] = true
-			}
-			newSeenVars[variable] = true
-			if unexpandedVars, ok := stringListScope[variable]; ok {
-				expandedVars := []string{}
-				for _, unexpandedVar := range unexpandedVars {
-					expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
-					if err != nil {
-						return ret, err
-					}
-					expandedVars = append(expandedVars, expandedVar)
-				}
-				ret += strings.Join(expandedVars, " ")
-			} else if unexpandedVar, ok := stringScope[variable]; ok {
-				expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
-				if err != nil {
-					return ret, err
-				}
-				ret += expandedVar
-			} else if unevaluatedVar, ok := exportedVars[variable]; ok {
-				evalFunc := reflect.ValueOf(unevaluatedVar)
-				validateVariableMethod(variable, evalFunc)
-				evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
-				evaluatedValue := evaluatedResult[0].Interface().(string)
-				expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
-				if err != nil {
-					return ret, err
-				}
-				ret += expandedVar
-			} else {
-				return "", fmt.Errorf("Unbound config variable %s", variable)
-			}
-		}
-		return ret, nil
-	}
-	var ret []string
-	for _, v := range strings.Split(toExpand, " ") {
-		val, err := expandVarInternal(v, map[string]bool{})
-		if err != nil {
-			return ret, err
-		}
-		ret = append(ret, val)
-	}
-
-	return ret, nil
-}
-
-func validateVariableMethod(name string, methodValue reflect.Value) {
-	methodType := methodValue.Type()
-	if methodType.Kind() != reflect.Func {
-		panic(fmt.Errorf("method given for variable %s is not a function",
-			name))
-	}
-	if n := methodType.NumIn(); n != 1 {
-		panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
-			name, n))
-	}
-	if n := methodType.NumOut(); n != 1 {
-		panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
-			name, n))
-	}
-	if kind := methodType.Out(0).Kind(); kind != reflect.String {
-		panic(fmt.Errorf("method for variable %s does not return a string",
-			name))
-	}
-}
diff --git a/cc/config/bp2build_test.go b/cc/config/bp2build_test.go
deleted file mode 100644
index 9a8178a..0000000
--- a/cc/config/bp2build_test.go
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package config
-
-import (
-	"testing"
-
-	"android/soong/android"
-)
-
-func TestExpandVars(t *testing.T) {
-	android_arm64_config := android.TestConfig("out", nil, "", nil)
-	android_arm64_config.BuildOS = android.Android
-	android_arm64_config.BuildArch = android.Arm64
-
-	testCases := []struct {
-		description     string
-		config          android.Config
-		stringScope     exportedStringVariables
-		stringListScope exportedStringListVariables
-		configVars      exportedConfigDependingVariables
-		toExpand        string
-		expectedValues  []string
-	}{
-		{
-			description:    "no expansion for non-interpolated value",
-			toExpand:       "foo",
-			expectedValues: []string{"foo"},
-		},
-		{
-			description: "single level expansion for string var",
-			stringScope: exportedStringVariables{
-				"foo": "bar",
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"bar"},
-		},
-		{
-			description: "single level expansion with short-name for string var",
-			stringScope: exportedStringVariables{
-				"foo": "bar",
-			},
-			toExpand:       "${config.foo}",
-			expectedValues: []string{"bar"},
-		},
-		{
-			description: "single level expansion string list var",
-			stringListScope: exportedStringListVariables{
-				"foo": []string{"bar"},
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"bar"},
-		},
-		{
-			description: "mixed level expansion for string list var",
-			stringScope: exportedStringVariables{
-				"foo": "${bar}",
-				"qux": "hello",
-			},
-			stringListScope: exportedStringListVariables{
-				"bar": []string{"baz", "${qux}"},
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"baz hello"},
-		},
-		{
-			description: "double level expansion",
-			stringListScope: exportedStringListVariables{
-				"foo": []string{"${bar}"},
-				"bar": []string{"baz"},
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"baz"},
-		},
-		{
-			description: "double level expansion with a literal",
-			stringListScope: exportedStringListVariables{
-				"a": []string{"${b}", "c"},
-				"b": []string{"d"},
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"d c"},
-		},
-		{
-			description: "double level expansion, with two variables in a string",
-			stringListScope: exportedStringListVariables{
-				"a": []string{"${b} ${c}"},
-				"b": []string{"d"},
-				"c": []string{"e"},
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"d e"},
-		},
-		{
-			description: "triple level expansion with two variables in a string",
-			stringListScope: exportedStringListVariables{
-				"a": []string{"${b} ${c}"},
-				"b": []string{"${c}", "${d}"},
-				"c": []string{"${d}"},
-				"d": []string{"foo"},
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"foo foo foo"},
-		},
-		{
-			description: "expansion with config depending vars",
-			configVars: exportedConfigDependingVariables{
-				"a": func(c android.Config) string { return c.BuildOS.String() },
-				"b": func(c android.Config) string { return c.BuildArch.String() },
-			},
-			config:         android_arm64_config,
-			toExpand:       "${a}-${b}",
-			expectedValues: []string{"android-arm64"},
-		},
-		{
-			description: "double level multi type expansion",
-			stringListScope: exportedStringListVariables{
-				"platform": []string{"${os}-${arch}"},
-				"const":    []string{"const"},
-			},
-			configVars: exportedConfigDependingVariables{
-				"os":   func(c android.Config) string { return c.BuildOS.String() },
-				"arch": func(c android.Config) string { return c.BuildArch.String() },
-				"foo":  func(c android.Config) string { return "foo" },
-			},
-			config:         android_arm64_config,
-			toExpand:       "${const}/${platform}/${foo}",
-			expectedValues: []string{"const/android-arm64/foo"},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.description, func(t *testing.T) {
-			output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
-			if len(output) != len(testCase.expectedValues) {
-				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
-			}
-			for i, actual := range output {
-				expectedValue := testCase.expectedValues[i]
-				if actual != expectedValue {
-					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
-				}
-			}
-		})
-	}
-}
-
-func TestBazelToolchainVars(t *testing.T) {
-	testCases := []struct {
-		name        string
-		config      android.Config
-		vars        []bazelVarExporter
-		expectedOut string
-	}{
-		{
-			name: "exports strings",
-			vars: []bazelVarExporter{
-				exportedStringVariables{
-					"a": "b",
-					"c": "d",
-				},
-			},
-			expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
-
-_a = "b"
-
-_c = "d"
-
-constants = struct(
-    a = _a,
-    c = _c,
-)`,
-		},
-		{
-			name: "exports string lists",
-			vars: []bazelVarExporter{
-				exportedStringListVariables{
-					"a": []string{"b1", "b2"},
-					"c": []string{"d1", "d2"},
-				},
-			},
-			expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
-
-_a = [
-    "b1",
-    "b2",
-]
-
-_c = [
-    "d1",
-    "d2",
-]
-
-constants = struct(
-    a = _a,
-    c = _c,
-)`,
-		},
-		{
-			name: "exports string lists dicts",
-			vars: []bazelVarExporter{
-				exportedStringListDictVariables{
-					"a": map[string][]string{"b1": []string{"b2"}},
-					"c": map[string][]string{"d1": []string{"d2"}},
-				},
-			},
-			expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
-
-_a = {
-    "b1": ["b2"],
-}
-
-_c = {
-    "d1": ["d2"],
-}
-
-constants = struct(
-    a = _a,
-    c = _c,
-)`,
-		},
-		{
-			name: "exports dict with var refs",
-			vars: []bazelVarExporter{
-				exportedVariableReferenceDictVariables{
-					"a": map[string]string{"b1": "${b2}"},
-					"c": map[string]string{"d1": "${config.d2}"},
-				},
-			},
-			expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
-
-_a = {
-    "b1": _b2,
-}
-
-_c = {
-    "d1": _d2,
-}
-
-constants = struct(
-    a = _a,
-    c = _c,
-)`,
-		},
-		{
-			name: "sorts across types with variable references last",
-			vars: []bazelVarExporter{
-				exportedStringVariables{
-					"b": "b-val",
-					"d": "d-val",
-				},
-				exportedStringListVariables{
-					"c": []string{"c-val"},
-					"e": []string{"e-val"},
-				},
-				exportedStringListDictVariables{
-					"a": map[string][]string{"a1": []string{"a2"}},
-					"f": map[string][]string{"f1": []string{"f2"}},
-				},
-				exportedVariableReferenceDictVariables{
-					"aa": map[string]string{"b1": "${b}"},
-					"cc": map[string]string{"d1": "${config.d}"},
-				},
-			},
-			expectedOut: `# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.
-
-_a = {
-    "a1": ["a2"],
-}
-
-_b = "b-val"
-
-_c = ["c-val"]
-
-_d = "d-val"
-
-_e = ["e-val"]
-
-_f = {
-    "f1": ["f2"],
-}
-
-_aa = {
-    "b1": _b,
-}
-
-_cc = {
-    "d1": _d,
-}
-
-constants = struct(
-    a = _a,
-    b = _b,
-    c = _c,
-    d = _d,
-    e = _e,
-    f = _f,
-    aa = _aa,
-    cc = _cc,
-)`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			out := bazelToolchainVars(tc.config, tc.vars...)
-			if out != tc.expectedOut {
-				t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
-			}
-		})
-	}
-}
diff --git a/cc/config/global.go b/cc/config/global.go
index dfb9a66..9c71683 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -23,6 +23,9 @@
 )
 
 var (
+	pctx         = android.NewPackageContext("android/soong/cc/config")
+	exportedVars = android.NewExportedVariables(pctx)
+
 	// Flags used by lots of devices.  Putting them in package static variables
 	// will save bytes in build.ninja so they aren't repeated for every file
 	commonGlobalCflags = []string{
@@ -296,20 +299,28 @@
 	WarningAllowedOldProjects = []string{}
 )
 
-var pctx = android.NewPackageContext("android/soong/cc/config")
+// BazelCcToolchainVars generates bzl file content containing variables for
+// Bazel's cc_toolchain configuration.
+func BazelCcToolchainVars(config android.Config) string {
+	return android.BazelToolchainVars(config, exportedVars)
+}
+
+func ExportStringList(name string, value []string) {
+	exportedVars.ExportStringList(name, value)
+}
 
 func init() {
 	if runtime.GOOS == "linux" {
 		commonGlobalCflags = append(commonGlobalCflags, "-fdebug-prefix-map=/proc/self/cwd=")
 	}
 
-	exportStringListStaticVariable("CommonGlobalConlyflags", commonGlobalConlyflags)
-	exportStringListStaticVariable("DeviceGlobalCppflags", deviceGlobalCppflags)
-	exportStringListStaticVariable("DeviceGlobalLdflags", deviceGlobalLdflags)
-	exportStringListStaticVariable("DeviceGlobalLldflags", deviceGlobalLldflags)
-	exportStringListStaticVariable("HostGlobalCppflags", hostGlobalCppflags)
-	exportStringListStaticVariable("HostGlobalLdflags", hostGlobalLdflags)
-	exportStringListStaticVariable("HostGlobalLldflags", hostGlobalLldflags)
+	exportedVars.ExportStringListStaticVariable("CommonGlobalConlyflags", commonGlobalConlyflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalCppflags", deviceGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalLdflags", deviceGlobalLdflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalLldflags", deviceGlobalLldflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalCppflags", hostGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalLdflags", hostGlobalLdflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalLldflags", hostGlobalLldflags)
 
 	// Export the static default CommonGlobalCflags to Bazel.
 	// TODO(187086342): handle cflags that are set in VariableFuncs.
@@ -320,7 +331,7 @@
 			"-ftrivial-auto-var-init=zero",
 			"-enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang",
 		}...)
-	exportedStringListVars.Set("CommonGlobalCflags", bazelCommonGlobalCflags)
+	exportedVars.ExportStringList("CommonGlobalCflags", bazelCommonGlobalCflags)
 
 	pctx.VariableFunc("CommonGlobalCflags", func(ctx android.PackageVarContext) string {
 		flags := commonGlobalCflags
@@ -349,17 +360,17 @@
 
 	// Export the static default DeviceGlobalCflags to Bazel.
 	// TODO(187086342): handle cflags that are set in VariableFuncs.
-	exportedStringListVars.Set("DeviceGlobalCflags", deviceGlobalCflags)
+	exportedVars.ExportStringList("DeviceGlobalCflags", deviceGlobalCflags)
 
 	pctx.VariableFunc("DeviceGlobalCflags", func(ctx android.PackageVarContext) string {
 		return strings.Join(deviceGlobalCflags, " ")
 	})
 
-	exportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
-	exportStringListStaticVariable("NoOverrideGlobalCflags", noOverrideGlobalCflags)
-	exportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
-	exportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
-	exportStringListStaticVariable("ExternalCflags", extraExternalCflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("NoOverrideGlobalCflags", noOverrideGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("ExternalCflags", extraExternalCflags)
 
 	// Everything in these lists is a crime against abstraction and dependency tracking.
 	// Do not add anything to this list.
@@ -374,11 +385,11 @@
 		"frameworks/native/opengl/include",
 		"frameworks/av/include",
 	}
-	exportedStringListVars.Set("CommonGlobalIncludes", commonGlobalIncludes)
+	exportedVars.ExportStringList("CommonGlobalIncludes", commonGlobalIncludes)
 	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I", commonGlobalIncludes)
 
-	exportStringStaticVariable("CLANG_DEFAULT_VERSION", ClangDefaultVersion)
-	exportStringStaticVariable("CLANG_DEFAULT_SHORT_VERSION", ClangDefaultShortVersion)
+	exportedVars.ExportStringStaticVariable("CLANG_DEFAULT_VERSION", ClangDefaultVersion)
+	exportedVars.ExportStringStaticVariable("CLANG_DEFAULT_SHORT_VERSION", ClangDefaultShortVersion)
 
 	pctx.StaticVariableWithEnvOverride("ClangBase", "LLVM_PREBUILTS_BASE", ClangDefaultBase)
 	pctx.StaticVariableWithEnvOverride("ClangVersion", "LLVM_PREBUILTS_VERSION", ClangDefaultVersion)
@@ -418,7 +429,7 @@
 	pctx.StaticVariableWithEnvOverride("REAbiLinkerExecStrategy", "RBE_ABI_LINKER_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
 }
 
-var HostPrebuiltTag = exportVariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
+var HostPrebuiltTag = exportedVars.ExportVariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
 
 func ClangPath(ctx android.PathContext, file string) android.SourcePath {
 	type clangToolKey string
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index 164e7a6..aebda0b 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -91,26 +91,26 @@
 	pctx.SourcePathVariable("X86_64GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/x86/x86_64-linux-android-${x86_64GccVersion}")
 
-	exportStringListStaticVariable("X86_64ToolchainCflags", []string{"-m64"})
-	exportStringListStaticVariable("X86_64ToolchainLdflags", []string{"-m64"})
+	exportedVars.ExportStringListStaticVariable("X86_64ToolchainCflags", []string{"-m64"})
+	exportedVars.ExportStringListStaticVariable("X86_64ToolchainLdflags", []string{"-m64"})
 
-	exportStringListStaticVariable("X86_64Ldflags", x86_64Ldflags)
-	exportStringListStaticVariable("X86_64Lldflags", x86_64Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Ldflags", x86_64Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Lldflags", x86_64Ldflags)
 
 	// Clang cflags
-	exportStringListStaticVariable("X86_64Cflags", x86_64Cflags)
-	exportStringListStaticVariable("X86_64Cppflags", x86_64Cppflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Cflags", x86_64Cflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Cppflags", x86_64Cppflags)
 
 	// Yasm flags
-	exportStringListStaticVariable("X86_64YasmFlags", []string{
+	exportedVars.ExportStringListStaticVariable("X86_64YasmFlags", []string{
 		"-f elf64",
 		"-m amd64",
 	})
 
 	// Extended cflags
 
-	exportedStringListDictVars.Set("X86_64ArchVariantCflags", x86_64ArchVariantCflags)
-	exportedStringListDictVars.Set("X86_64ArchFeatureCflags", x86_64ArchFeatureCflags)
+	exportedVars.ExportStringListDict("X86_64ArchVariantCflags", x86_64ArchVariantCflags)
+	exportedVars.ExportStringListDict("X86_64ArchFeatureCflags", x86_64ArchFeatureCflags)
 
 	// Architecture variant cflags
 	for variant, cflags := range x86_64ArchVariantCflags {
diff --git a/cc/config/x86_device.go b/cc/config/x86_device.go
index e32e1bd..421b083 100644
--- a/cc/config/x86_device.go
+++ b/cc/config/x86_device.go
@@ -98,25 +98,25 @@
 	pctx.SourcePathVariable("X86GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/x86/x86_64-linux-android-${x86GccVersion}")
 
-	exportStringListStaticVariable("X86ToolchainCflags", []string{"-m32"})
-	exportStringListStaticVariable("X86ToolchainLdflags", []string{"-m32"})
+	exportedVars.ExportStringListStaticVariable("X86ToolchainCflags", []string{"-m32"})
+	exportedVars.ExportStringListStaticVariable("X86ToolchainLdflags", []string{"-m32"})
 
-	exportStringListStaticVariable("X86Ldflags", x86Ldflags)
-	exportStringListStaticVariable("X86Lldflags", x86Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86Ldflags", x86Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86Lldflags", x86Ldflags)
 
 	// Clang cflags
-	exportStringListStaticVariable("X86Cflags", x86Cflags)
-	exportStringListStaticVariable("X86Cppflags", x86Cppflags)
+	exportedVars.ExportStringListStaticVariable("X86Cflags", x86Cflags)
+	exportedVars.ExportStringListStaticVariable("X86Cppflags", x86Cppflags)
 
 	// Yasm flags
-	exportStringListStaticVariable("X86YasmFlags", []string{
+	exportedVars.ExportStringListStaticVariable("X86YasmFlags", []string{
 		"-f elf32",
 		"-m x86",
 	})
 
 	// Extended cflags
-	exportedStringListDictVars.Set("X86ArchVariantCflags", x86ArchVariantCflags)
-	exportedStringListDictVars.Set("X86ArchFeatureCflags", x86ArchFeatureCflags)
+	exportedVars.ExportStringListDict("X86ArchVariantCflags", x86ArchVariantCflags)
+	exportedVars.ExportStringListDict("X86ArchFeatureCflags", x86ArchFeatureCflags)
 
 	// Architecture variant cflags
 	for variant, cflags := range x86ArchVariantCflags {
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index e1659d3..4e8fd77 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -121,40 +121,40 @@
 )
 
 func init() {
-	exportStringStaticVariable("LinuxGccVersion", linuxGccVersion)
-	exportStringStaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
+	exportedVars.ExportStringStaticVariable("LinuxGccVersion", linuxGccVersion)
+	exportedVars.ExportStringStaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
 
 	// Most places use the full GCC version. A few only use up to the first two numbers.
 	if p := strings.Split(linuxGccVersion, "."); len(p) > 2 {
-		exportStringStaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
+		exportedVars.ExportStringStaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
 	} else {
-		exportStringStaticVariable("ShortLinuxGccVersion", linuxGccVersion)
+		exportedVars.ExportStringStaticVariable("ShortLinuxGccVersion", linuxGccVersion)
 	}
 
-	exportSourcePathVariable("LinuxGccRoot",
+	exportedVars.ExportSourcePathVariable("LinuxGccRoot",
 		"prebuilts/gcc/linux-x86/host/x86_64-linux-glibc${LinuxGlibcVersion}-${ShortLinuxGccVersion}")
 
-	exportStringListStaticVariable("LinuxGccTriple", []string{"x86_64-linux"})
+	exportedVars.ExportStringListStaticVariable("LinuxGccTriple", []string{"x86_64-linux"})
 
-	exportStringListStaticVariable("LinuxCflags", linuxCflags)
-	exportStringListStaticVariable("LinuxLdflags", linuxLdflags)
-	exportStringListStaticVariable("LinuxLldflags", linuxLdflags)
-	exportStringListStaticVariable("LinuxGlibcCflags", linuxGlibcCflags)
-	exportStringListStaticVariable("LinuxGlibcLdflags", linuxGlibcLdflags)
-	exportStringListStaticVariable("LinuxGlibcLldflags", linuxGlibcLdflags)
-	exportStringListStaticVariable("LinuxMuslCflags", linuxMuslCflags)
-	exportStringListStaticVariable("LinuxMuslLdflags", linuxMuslLdflags)
-	exportStringListStaticVariable("LinuxMuslLldflags", linuxMuslLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxCflags", linuxCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxLdflags", linuxLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxLldflags", linuxLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcCflags", linuxGlibcCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcLdflags", linuxGlibcLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcLldflags", linuxGlibcLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslCflags", linuxMuslCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslLdflags", linuxMuslLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslLldflags", linuxMuslLdflags)
 
-	exportStringListStaticVariable("LinuxX86Cflags", linuxX86Cflags)
-	exportStringListStaticVariable("LinuxX8664Cflags", linuxX8664Cflags)
-	exportStringListStaticVariable("LinuxX86Ldflags", linuxX86Ldflags)
-	exportStringListStaticVariable("LinuxX86Lldflags", linuxX86Ldflags)
-	exportStringListStaticVariable("LinuxX8664Ldflags", linuxX8664Ldflags)
-	exportStringListStaticVariable("LinuxX8664Lldflags", linuxX8664Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX86Cflags", linuxX86Cflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Cflags", linuxX8664Cflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX86Ldflags", linuxX86Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX86Lldflags", linuxX86Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Ldflags", linuxX8664Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Lldflags", linuxX8664Ldflags)
 	// Yasm flags
-	exportStringListStaticVariable("LinuxX86YasmFlags", []string{"-f elf32 -m x86"})
-	exportStringListStaticVariable("LinuxX8664YasmFlags", []string{"-f elf64 -m amd64"})
+	exportedVars.ExportStringListStaticVariable("LinuxX86YasmFlags", []string{"-f elf32 -m x86"})
+	exportedVars.ExportStringListStaticVariable("LinuxX8664YasmFlags", []string{"-f elf64 -m amd64"})
 }
 
 type toolchainLinux struct {
diff --git a/java/app.go b/java/app.go
index 21ee34e..2b52eab 100755
--- a/java/app.go
+++ b/java/app.go
@@ -638,7 +638,21 @@
 	}
 
 	certificates := processMainCert(a.ModuleBase, a.getCertString(ctx), certificateDeps, ctx)
-	a.certificate = certificates[0]
+
+	// This can be reached with an empty certificate list if AllowMissingDependencies is set
+	// and the certificate property for this module is a module reference to a missing module.
+	if len(certificates) > 0 {
+		a.certificate = certificates[0]
+	} else {
+		if !ctx.Config().AllowMissingDependencies() && len(ctx.GetMissingDependencies()) > 0 {
+			panic("Should only get here if AllowMissingDependencies set and there are missing dependencies")
+		}
+		// Set a certificate to avoid panics later when accessing it.
+		a.certificate = Certificate{
+			Key: android.PathForModuleOut(ctx, "missing.pk8"),
+			Pem: android.PathForModuleOut(ctx, "missing.pem"),
+		}
+	}
 
 	// Build a final signed app package.
 	packageFile := android.PathForModuleOut(ctx, a.installApkName+".apk")
diff --git a/java/app_test.go b/java/app_test.go
index 08baf54..6a4508c 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2948,3 +2948,24 @@
 		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
 	}
 }
+
+func TestAppMissingCertificateAllowMissingDependencies(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestWithAllowMissingDependencies,
+		android.PrepareForTestWithAndroidMk,
+	).RunTestWithBp(t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			certificate: ":missing_certificate",
+			sdk_version: "current",
+		}`)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	fooApk := foo.Output("foo.apk")
+	if fooApk.Rule != android.ErrorRule {
+		t.Fatalf("expected ErrorRule for foo.apk, got %s", fooApk.Rule.String())
+	}
+	android.AssertStringDoesContain(t, "expected error rule message", fooApk.Args["error"], "missing dependencies: missing_certificate\n")
+}
diff --git a/java/config/config.go b/java/config/config.go
index 262c531..95b841f 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -26,7 +26,8 @@
 )
 
 var (
-	pctx = android.NewPackageContext("android/soong/java/config")
+	pctx         = android.NewPackageContext("android/soong/java/config")
+	exportedVars = android.NewExportedVariables(pctx)
 
 	LegacyCorePlatformBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
 	LegacyCorePlatformSystemModules          = "legacy-core-platform-api-stubs-system-modules"
@@ -53,25 +54,40 @@
 	}
 )
 
-const (
-	JavaVmFlags  = `-XX:OnError="cat hs_err_pid%p.log" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads`
-	JavacVmFlags = `-J-XX:OnError="cat hs_err_pid%p.log" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1`
+var (
+	JavacVmFlags    = strings.Join(javacVmFlagsList, " ")
+	javaVmFlagsList = []string{
+		`-XX:OnError="cat hs_err_pid%p.log"`,
+		"-XX:CICompilerCount=6",
+		"-XX:+UseDynamicNumberOfGCThreads",
+	}
+	javacVmFlagsList = []string{
+		`-J-XX:OnError="cat hs_err_pid%p.log"`,
+		"-J-XX:CICompilerCount=6",
+		"-J-XX:+UseDynamicNumberOfGCThreads",
+		"-J-XX:+TieredCompilation",
+		"-J-XX:TieredStopAtLevel=1",
+	}
 )
 
 func init() {
 	pctx.Import("github.com/google/blueprint/bootstrap")
 
-	pctx.StaticVariable("JavacHeapSize", "2048M")
-	pctx.StaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
+	exportedVars.ExportStringStaticVariable("JavacHeapSize", "2048M")
+	exportedVars.ExportStringStaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
 
 	// ErrorProne can use significantly more memory than javac alone, give it a higher heap
 	// size (b/221480398).
-	pctx.StaticVariable("ErrorProneHeapSize", "4096M")
-	pctx.StaticVariable("ErrorProneHeapFlags", "-J-Xmx${ErrorProneHeapSize}")
+	exportedVars.ExportStringStaticVariable("ErrorProneHeapSize", "4096M")
+	exportedVars.ExportStringStaticVariable("ErrorProneHeapFlags", "-J-Xmx${ErrorProneHeapSize}")
 
-	pctx.StaticVariable("DexFlags", "-JXX:OnError='cat hs_err_pid%p.log' -JXX:CICompilerCount=6 -JXX:+UseDynamicNumberOfGCThreads")
+	exportedVars.ExportStringListStaticVariable("DexFlags", []string{
+		`-JXX:OnError="cat hs_err_pid%p.log"`,
+		"-JXX:CICompilerCount=6",
+		"-JXX:+UseDynamicNumberOfGCThreads",
+	})
 
-	pctx.StaticVariable("CommonJdkFlags", strings.Join([]string{
+	exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
 		`-Xmaxerrs 9999999`,
 		`-encoding UTF-8`,
 		`-sourcepath ""`,
@@ -85,10 +101,10 @@
 
 		// b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9
 		`-XDstringConcat=inline`,
-	}, " "))
+	})
 
-	pctx.StaticVariable("JavaVmFlags", JavaVmFlags)
-	pctx.StaticVariable("JavacVmFlags", JavacVmFlags)
+	exportedVars.ExportStringListStaticVariable("JavaVmFlags", javaVmFlagsList)
+	exportedVars.ExportStringListStaticVariable("JavacVmFlags", javacVmFlagsList)
 
 	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
 
@@ -184,6 +200,10 @@
 	hostJNIToolVariableWithSdkToolsPrebuilt("SignapkJniLibrary", "libconscrypt_openjdk_jni")
 }
 
+func BazelJavaToolchainVars(config android.Config) string {
+	return android.BazelToolchainVars(config, exportedVars)
+}
+
 func hostBinToolVariableWithSdkToolsPrebuilt(name, tool string) {
 	pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
 		if ctx.Config().AlwaysUsePrebuiltSdks() {
diff --git a/java/config/error_prone.go b/java/config/error_prone.go
index 48681b5..5f853c8 100644
--- a/java/config/error_prone.go
+++ b/java/config/error_prone.go
@@ -16,8 +16,6 @@
 
 import (
 	"strings"
-
-	"android/soong/android"
 )
 
 var (
@@ -31,23 +29,23 @@
 )
 
 // Wrapper that grabs value of val late so it can be initialized by a later module's init function
-func errorProneVar(name string, val *[]string, sep string) {
-	pctx.VariableFunc(name, func(android.PackageVarContext) string {
+func errorProneVar(val *[]string, sep string) func() string {
+	return func() string {
 		return strings.Join(*val, sep)
-	})
+	}
 }
 
 func init() {
-	errorProneVar("ErrorProneClasspath", &ErrorProneClasspath, ":")
-	errorProneVar("ErrorProneChecksError", &ErrorProneChecksError, " ")
-	errorProneVar("ErrorProneChecksWarning", &ErrorProneChecksWarning, " ")
-	errorProneVar("ErrorProneChecksDefaultDisabled", &ErrorProneChecksDefaultDisabled, " ")
-	errorProneVar("ErrorProneChecksOff", &ErrorProneChecksOff, " ")
-	errorProneVar("ErrorProneFlags", &ErrorProneFlags, " ")
-	pctx.StaticVariable("ErrorProneChecks", strings.Join([]string{
+	exportedVars.ExportVariableFuncVariable("ErrorProneClasspath", errorProneVar(&ErrorProneClasspath, ":"))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksError", errorProneVar(&ErrorProneChecksError, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksWarning", errorProneVar(&ErrorProneChecksWarning, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksDefaultDisabled", errorProneVar(&ErrorProneChecksDefaultDisabled, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksOff", errorProneVar(&ErrorProneChecksOff, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneFlags", errorProneVar(&ErrorProneFlags, " "))
+	exportedVars.ExportStringListStaticVariable("ErrorProneChecks", []string{
 		"${ErrorProneChecksOff}",
 		"${ErrorProneChecksError}",
 		"${ErrorProneChecksWarning}",
 		"${ErrorProneChecksDefaultDisabled}",
-	}, " "))
+	})
 }
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 95ded34..44cdfa5 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -994,10 +994,11 @@
 	rule := android.NewRuleBuilder(pctx, ctx)
 	command := rule.Command().
 		BuiltTool("verify_overlaps").
-		Input(monolithicFilePath)
+		FlagWithInput("--monolithic-flags ", monolithicFilePath)
 
 	for _, subset := range csvSubsets {
 		command.
+			Flag("--module-flags ").
 			Textf("%s:%s", subset.CsvFile, subset.SignaturePatternsFile).
 			Implicit(subset.CsvFile).Implicit(subset.SignaturePatternsFile)
 	}
diff --git a/java/java.go b/java/java.go
index 713fe94..b34d6de 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2041,6 +2041,10 @@
 // and also separates dependencies into dynamic dependencies and static dependencies.
 // Each corresponding Bazel target type, can have a different method for handling
 // dynamic vs. static dependencies, and so these are returned to the calling function.
+type eventLogTagsAttributes struct {
+	Srcs bazel.LabelListAttribute
+}
+
 func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) (*javaCommonAttributes, *javaDependencyLabels) {
 	var srcs bazel.LabelListAttribute
 	archVariantProps := m.GetArchVariantProperties(ctx, &CommonProperties{})
@@ -2055,11 +2059,32 @@
 
 	javaSrcPartition := "java"
 	protoSrcPartition := "proto"
+	logtagSrcPartition := "logtag"
 	srcPartitions := bazel.PartitionLabelListAttribute(ctx, &srcs, bazel.LabelPartitions{
-		javaSrcPartition:  bazel.LabelPartition{Extensions: []string{".java"}, Keep_remainder: true},
-		protoSrcPartition: android.ProtoSrcLabelPartition,
+		javaSrcPartition:   bazel.LabelPartition{Extensions: []string{".java"}, Keep_remainder: true},
+		logtagSrcPartition: bazel.LabelPartition{Extensions: []string{".logtags", ".logtag"}},
+		protoSrcPartition:  android.ProtoSrcLabelPartition,
 	})
 
+	javaSrcs := srcPartitions[javaSrcPartition]
+
+	var logtagsSrcs bazel.LabelList
+	if !srcPartitions[logtagSrcPartition].IsEmpty() {
+		logtagsLibName := m.Name() + "_logtags"
+		logtagsSrcs = bazel.MakeLabelList([]bazel.Label{{Label: ":" + logtagsLibName}})
+		ctx.CreateBazelTargetModule(
+			bazel.BazelTargetModuleProperties{
+				Rule_class:        "event_log_tags",
+				Bzl_load_location: "//build/make/tools:event_log_tags.bzl",
+			},
+			android.CommonAttributes{Name: logtagsLibName},
+			&eventLogTagsAttributes{
+				Srcs: srcPartitions[logtagSrcPartition],
+			},
+		)
+	}
+	javaSrcs.Append(bazel.MakeLabelListAttribute(logtagsSrcs))
+
 	var javacopts []string
 	if m.properties.Javacflags != nil {
 		javacopts = append(javacopts, m.properties.Javacflags...)
@@ -2071,7 +2096,7 @@
 	}
 
 	commonAttrs := &javaCommonAttributes{
-		Srcs: srcPartitions[javaSrcPartition],
+		Srcs: javaSrcs,
 		Plugins: bazel.MakeLabelListAttribute(
 			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
 		),
diff --git a/java/testing.go b/java/testing.go
index 82aa29b..4000334 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -70,6 +70,10 @@
 		defaultJavaDir + "/framework/aidl": nil,
 		// Needed for various deps defined in GatherRequiredDepsForTest()
 		defaultJavaDir + "/a.java": nil,
+
+		// Needed for R8 rules on apps
+		"build/make/core/proguard.flags":             nil,
+		"build/make/core/proguard_basic_keeps.flags": nil,
 	}.AddToFixture(),
 	// The java default module definitions.
 	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", gatherRequiredDepsForTest()),
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 0942c28..df18243 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -107,8 +107,8 @@
 	"my-dir":                               &myDirCallParser{},
 	"patsubst":                             &substCallParser{fname: "patsubst"},
 	"product-copy-files-by-pattern":        &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
-	"require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid},
-	"require-artifacts-in-path-relaxed":    &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid},
+	"require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
+	"require-artifacts-in-path-relaxed":    &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addHandle: true},
 	// TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
 	"shell":    &shellCallParser{},
 	"sort":     &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 9c2b392..96132cc 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -775,8 +775,8 @@
   cfg = rblf.cfg(handle)
   rblf.enforce_product_packages_exist("")
   rblf.enforce_product_packages_exist("foo")
-  rblf.require_artifacts_in_path("foo", "bar")
-  rblf.require_artifacts_in_path_relaxed("foo", "bar")
+  rblf.require_artifacts_in_path(handle, "foo", "bar")
+  rblf.require_artifacts_in_path_relaxed(handle, "foo", "bar")
   rblf.mkdist_for_goals(g, "goal", "from:to")
   rblf.add_product_dex_preopt_module_config(handle, "MyModule", "disable")
 `,
diff --git a/scripts/hiddenapi/verify_overlaps.py b/scripts/hiddenapi/verify_overlaps.py
index e5214df..940532b 100755
--- a/scripts/hiddenapi/verify_overlaps.py
+++ b/scripts/hiddenapi/verify_overlaps.py
@@ -23,13 +23,13 @@
 from signature_trie import signature_trie
 
 
-def dict_reader(csvfile):
+def dict_reader(csv_file):
     return csv.DictReader(
-        csvfile, delimiter=",", quotechar="|", fieldnames=["signature"])
+        csv_file, delimiter=",", quotechar="|", fieldnames=["signature"])
 
 
 def read_flag_trie_from_file(file):
-    with open(file, "r") as stream:
+    with open(file, "r", encoding="utf8") as stream:
         return read_flag_trie_from_stream(stream)
 
 
@@ -43,24 +43,24 @@
 
 
 def extract_subset_from_monolithic_flags_as_dict_from_file(
-        monolithicTrie, patternsFile):
+        monolithic_trie, patterns_file):
     """Extract a subset of flags from the dict of monolithic flags.
 
-    :param monolithicFlagsDict: the dict containing all the monolithic flags.
-    :param patternsFile: a file containing a list of signature patterns that
+    :param monolithic_trie: the trie containing all the monolithic flags.
+    :param patterns_file: a file containing a list of signature patterns that
     define the subset.
     :return: the dict from signature to row.
     """
-    with open(patternsFile, "r") as stream:
+    with open(patterns_file, "r", encoding="utf8") as stream:
         return extract_subset_from_monolithic_flags_as_dict_from_stream(
-            monolithicTrie, stream)
+            monolithic_trie, stream)
 
 
 def extract_subset_from_monolithic_flags_as_dict_from_stream(
-        monolithicTrie, stream):
+        monolithic_trie, stream):
     """Extract a subset of flags from the trie of monolithic flags.
 
-    :param monolithicTrie: the trie containing all the monolithic flags.
+    :param monolithic_trie: the trie containing all the monolithic flags.
     :param stream: a stream containing a list of signature patterns that define
     the subset.
     :return: the dict from signature to row.
@@ -68,7 +68,7 @@
     dict_signature_to_row = {}
     for pattern in stream:
         pattern = pattern.rstrip()
-        rows = monolithicTrie.get_matching_rows(pattern)
+        rows = monolithic_trie.get_matching_rows(pattern)
         for row in rows:
             signature = row["signature"]
             dict_signature_to_row[signature] = row
@@ -93,86 +93,90 @@
     return dict_signature_to_row
 
 
-def read_signature_csv_from_file_as_dict(csvFile):
+def read_signature_csv_from_file_as_dict(csv_file):
     """Read the csvFile into a dict.
 
     The first column is assumed to be the signature and used as the
     key.
 
     The whole row is stored as the value.
-    :param csvFile: the csv file to read
+    :param csv_file: the csv file to read
     :return: the dict from signature to row.
     """
-    with open(csvFile, "r") as f:
+    with open(csv_file, "r", encoding="utf8") as f:
         return read_signature_csv_from_stream_as_dict(f)
 
 
-def compare_signature_flags(monolithicFlagsDict, modularFlagsDict):
+def compare_signature_flags(monolithic_flags_dict, modular_flags_dict):
     """Compare the signature flags between the two dicts.
 
-    :param monolithicFlagsDict: the dict containing the subset of the monolithic
-    flags that should be equal to the modular flags.
-    :param modularFlagsDict:the dict containing the flags produced by a single
+    :param monolithic_flags_dict: the dict containing the subset of the
+    monolithic flags that should be equal to the modular flags.
+    :param modular_flags_dict:the dict containing the flags produced by a single
     bootclasspath_fragment module.
     :return: list of mismatches., each mismatch is a tuple where the first item
     is the signature, and the second and third items are lists of the flags from
     modular dict, and monolithic dict respectively.
     """
-    mismatchingSignatures = []
+    mismatching_signatures = []
     # Create a sorted set of all the signatures from both the monolithic and
     # modular dicts.
-    allSignatures = sorted(
-        set(chain(monolithicFlagsDict.keys(), modularFlagsDict.keys())))
-    for signature in allSignatures:
-        monolithicRow = monolithicFlagsDict.get(signature, {})
-        monolithicFlags = monolithicRow.get(None, [])
-        if signature in modularFlagsDict:
-            modularRow = modularFlagsDict.get(signature, {})
-            modularFlags = modularRow.get(None, [])
+    all_signatures = sorted(
+        set(chain(monolithic_flags_dict.keys(), modular_flags_dict.keys())))
+    for signature in all_signatures:
+        monolithic_row = monolithic_flags_dict.get(signature, {})
+        monolithic_flags = monolithic_row.get(None, [])
+        if signature in modular_flags_dict:
+            modular_row = modular_flags_dict.get(signature, {})
+            modular_flags = modular_row.get(None, [])
         else:
-            modularFlags = ["blocked"]
-        if monolithicFlags != modularFlags:
-            mismatchingSignatures.append(
-                (signature, modularFlags, monolithicFlags))
-    return mismatchingSignatures
+            modular_flags = ["blocked"]
+        if monolithic_flags != modular_flags:
+            mismatching_signatures.append(
+                (signature, modular_flags, monolithic_flags))
+    return mismatching_signatures
 
 
 def main(argv):
     args_parser = argparse.ArgumentParser(
         description="Verify that sets of hidden API flags are each a subset of "
         "the monolithic flag file.")
-    args_parser.add_argument("monolithicFlags", help="The monolithic flag file")
     args_parser.add_argument(
-        "modularFlags",
-        nargs=argparse.REMAINDER,
-        help="Flags produced by individual bootclasspath_fragment modules")
+        "--monolithic-flags", help="The monolithic flag file")
+    args_parser.add_argument(
+        "--module-flags",
+        action="append",
+        help="A colon separated pair of paths. The first is a path to a "
+        "filtered set of flags, and the second is a path to a set of "
+        "signature patterns that identify the set of classes belonging to "
+        "a single bootclasspath_fragment module, ")
     args = args_parser.parse_args(argv[1:])
 
     # Read in all the flags into the trie
-    monolithicFlagsPath = args.monolithicFlags
-    monolithicTrie = read_flag_trie_from_file(monolithicFlagsPath)
+    monolithic_flags_path = args.monolithic_flags
+    monolithic_trie = read_flag_trie_from_file(monolithic_flags_path)
 
     # For each subset specified on the command line, create dicts for the flags
     # provided by the subset and the corresponding flags from the complete set
     # of flags and compare them.
     failed = False
-    for modularPair in args.modularFlags:
-        parts = modularPair.split(":")
-        modularFlagsPath = parts[0]
-        modularPatternsPath = parts[1]
-        modularFlagsDict = read_signature_csv_from_file_as_dict(
-            modularFlagsPath)
-        monolithicFlagsSubsetDict = \
+    for modular_pair in args.module_flags:
+        parts = modular_pair.split(":")
+        modular_flags_path = parts[0]
+        modular_patterns_path = parts[1]
+        modular_flags_dict = read_signature_csv_from_file_as_dict(
+            modular_flags_path)
+        monolithic_flags_subset_dict = \
             extract_subset_from_monolithic_flags_as_dict_from_file(
-            monolithicTrie, modularPatternsPath)
-        mismatchingSignatures = compare_signature_flags(
-            monolithicFlagsSubsetDict, modularFlagsDict)
-        if mismatchingSignatures:
+                monolithic_trie, modular_patterns_path)
+        mismatching_signatures = compare_signature_flags(
+            monolithic_flags_subset_dict, modular_flags_dict)
+        if mismatching_signatures:
             failed = True
             print("ERROR: Hidden API flags are inconsistent:")
-            print("< " + modularFlagsPath)
-            print("> " + monolithicFlagsPath)
-            for mismatch in mismatchingSignatures:
+            print("< " + modular_flags_path)
+            print("> " + monolithic_flags_path)
+            for mismatch in mismatching_signatures:
                 signature = mismatch[0]
                 print()
                 print("< " + ",".join([signature] + mismatch[1]))
diff --git a/scripts/hiddenapi/verify_overlaps_test.py b/scripts/hiddenapi/verify_overlaps_test.py
index 8cf2959..ead8a4e 100755
--- a/scripts/hiddenapi/verify_overlaps_test.py
+++ b/scripts/hiddenapi/verify_overlaps_test.py
@@ -17,24 +17,26 @@
 import io
 import unittest
 
-from verify_overlaps import *  #pylint: disable=unused-wildcard-import,wildcard-import
+import verify_overlaps as vo
 
 
-#pylint: disable=line-too-long
 class TestDetectOverlaps(unittest.TestCase):
 
-    def read_flag_trie_from_string(self, csvdata):
+    @staticmethod
+    def read_flag_trie_from_string(csvdata):
         with io.StringIO(csvdata) as f:
-            return read_flag_trie_from_stream(f)
+            return vo.read_flag_trie_from_stream(f)
 
-    def read_signature_csv_from_string_as_dict(self, csvdata):
+    @staticmethod
+    def read_signature_csv_from_string_as_dict(csvdata):
         with io.StringIO(csvdata) as f:
-            return read_signature_csv_from_stream_as_dict(f)
+            return vo.read_signature_csv_from_stream_as_dict(f)
 
+    @staticmethod
     def extract_subset_from_monolithic_flags_as_dict_from_string(
-            self, monolithic, patterns):
+            monolithic, patterns):
         with io.StringIO(patterns) as f:
-            return extract_subset_from_monolithic_flags_as_dict_from_stream(
+            return vo.extract_subset_from_monolithic_flags_as_dict_from_stream(
                 monolithic, f)
 
     extractInput = """
@@ -50,14 +52,14 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'Ljava/lang/Object;->hashCode()I'
+        patterns = "Ljava/lang/Object;->hashCode()I"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Object;->hashCode()I': {
-                None: ['public-api', 'system-api', 'test-api'],
-                'signature': 'Ljava/lang/Object;->hashCode()I',
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
             },
         }
         self.assertEqual(expected, subset)
@@ -66,18 +68,18 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'java/lang/Object'
+        patterns = "java/lang/Object"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Object;->hashCode()I': {
-                None: ['public-api', 'system-api', 'test-api'],
-                'signature': 'Ljava/lang/Object;->hashCode()I',
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
             },
-            'Ljava/lang/Object;->toString()Ljava/lang/String;': {
-                None: ['blocked'],
-                'signature': 'Ljava/lang/Object;->toString()Ljava/lang/String;',
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
             },
         }
         self.assertEqual(expected, subset)
@@ -86,20 +88,20 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'java/lang/Character'
+        patterns = "java/lang/Character"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;':
-                {
-                    None: ['blocked'],
-                    'signature':
-                        'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;',
-                },
-            'Ljava/lang/Character;->serialVersionUID:J': {
-                None: ['sdk'],
-                'signature': 'Ljava/lang/Character;->serialVersionUID:J',
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
             },
         }
         self.assertEqual(expected, subset)
@@ -108,17 +110,17 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'java/lang/Character$UnicodeScript'
+        patterns = "java/lang/Character$UnicodeScript"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;':
-                {
-                    None: ['blocked'],
-                    'signature':
-                        'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;',
-                },
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
         }
         self.assertEqual(expected, subset)
 
@@ -126,32 +128,32 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'java/lang/*'
+        patterns = "java/lang/*"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;':
-                {
-                    None: ['blocked'],
-                    'signature':
-                        'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;',
-                },
-            'Ljava/lang/Character;->serialVersionUID:J': {
-                None: ['sdk'],
-                'signature': 'Ljava/lang/Character;->serialVersionUID:J',
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
             },
-            'Ljava/lang/Object;->hashCode()I': {
-                None: ['public-api', 'system-api', 'test-api'],
-                'signature': 'Ljava/lang/Object;->hashCode()I',
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
             },
-            'Ljava/lang/Object;->toString()Ljava/lang/String;': {
-                None: ['blocked'],
-                'signature': 'Ljava/lang/Object;->toString()Ljava/lang/String;',
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
             },
-            'Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V': {
-                None: ['blocked'],
-                'signature': 'Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V',
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            },
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
             },
         }
         self.assertEqual(expected, subset)
@@ -160,36 +162,36 @@
         monolithic = self.read_flag_trie_from_string(
             TestDetectOverlaps.extractInput)
 
-        patterns = 'java/**'
+        patterns = "java/**"
 
         subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
             monolithic, patterns)
         expected = {
-            'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;':
-                {
-                    None: ['blocked'],
-                    'signature':
-                        'Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;',
-                },
-            'Ljava/lang/Character;->serialVersionUID:J': {
-                None: ['sdk'],
-                'signature': 'Ljava/lang/Character;->serialVersionUID:J',
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
             },
-            'Ljava/lang/Object;->hashCode()I': {
-                None: ['public-api', 'system-api', 'test-api'],
-                'signature': 'Ljava/lang/Object;->hashCode()I',
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
             },
-            'Ljava/lang/Object;->toString()Ljava/lang/String;': {
-                None: ['blocked'],
-                'signature': 'Ljava/lang/Object;->toString()Ljava/lang/String;',
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
             },
-            'Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V': {
-                None: ['blocked'],
-                'signature': 'Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V',
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
             },
-            'Ljava/util/zip/ZipFile;-><clinit>()V': {
-                None: ['blocked'],
-                'signature': 'Ljava/util/zip/ZipFile;-><clinit>()V',
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            },
+            "Ljava/util/zip/ZipFile;-><clinit>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/util/zip/ZipFile;-><clinit>()V",
             },
         }
         self.assertEqual(expected, subset)
@@ -200,7 +202,7 @@
 Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
 Ljava/lang/Object;->hashCode()I,blocked
 """)
-        self.assertTrue('Duplicate signature: Ljava/lang/Object;->hashCode()I'
+        self.assertTrue("Duplicate signature: Ljava/lang/Object;->hashCode()I"
                         in str(context.exception))
 
     def test_read_trie_missing_member(self):
@@ -209,8 +211,8 @@
 Ljava/lang/Object,public-api,system-api,test-api
 """)
         self.assertTrue(
-            'Invalid signature: Ljava/lang/Object, does not identify a specific member'
-            in str(context.exception))
+            "Invalid signature: Ljava/lang/Object, "
+            "does not identify a specific member" in str(context.exception))
 
     def test_match(self):
         monolithic = self.read_signature_csv_from_string_as_dict("""
@@ -219,7 +221,7 @@
         modular = self.read_signature_csv_from_string_as_dict("""
 Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
 """)
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = []
         self.assertEqual(expected, mismatches)
 
@@ -230,12 +232,12 @@
         modular = self.read_signature_csv_from_string_as_dict("""
 Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
 """)
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = [
             (
-                'Ljava/lang/Object;->toString()Ljava/lang/String;',
-                ['public-api', 'system-api', 'test-api'],
-                ['public-api'],
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                ["public-api"],
             ),
         ]
         self.assertEqual(expected, mismatches)
@@ -247,12 +249,12 @@
         modular = self.read_signature_csv_from_string_as_dict("""
 Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
 """)
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = [
             (
-                'Ljava/lang/Object;->toString()Ljava/lang/String;',
-                ['public-api', 'system-api', 'test-api'],
-                ['blocked'],
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                ["blocked"],
             ),
         ]
         self.assertEqual(expected, mismatches)
@@ -264,26 +266,26 @@
         modular = self.read_signature_csv_from_string_as_dict("""
 Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
 """)
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = [
             (
-                'Ljava/lang/Object;->toString()Ljava/lang/String;',
-                ['blocked'],
-                ['public-api', 'system-api', 'test-api'],
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["blocked"],
+                ["public-api", "system-api", "test-api"],
             ),
         ]
         self.assertEqual(expected, mismatches)
 
     def test_match_treat_missing_from_modular_as_blocked(self):
-        monolithic = self.read_signature_csv_from_string_as_dict('')
+        monolithic = self.read_signature_csv_from_string_as_dict("")
         modular = self.read_signature_csv_from_string_as_dict("""
 Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
 """)
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = [
             (
-                'Ljava/lang/Object;->toString()Ljava/lang/String;',
-                ['public-api', 'system-api', 'test-api'],
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
                 [],
             ),
         ]
@@ -294,12 +296,12 @@
 Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
 """)
         modular = {}
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = [
             (
-                'Ljava/lang/Object;->hashCode()I',
-                ['blocked'],
-                ['public-api', 'system-api', 'test-api'],
+                "Ljava/lang/Object;->hashCode()I",
+                ["blocked"],
+                ["public-api", "system-api", "test-api"],
             ),
         ]
         self.assertEqual(expected, mismatches)
@@ -309,12 +311,10 @@
 Ljava/lang/Object;->hashCode()I,blocked
 """)
         modular = {}
-        mismatches = compare_signature_flags(monolithic, modular)
+        mismatches = vo.compare_signature_flags(monolithic, modular)
         expected = []
         self.assertEqual(expected, mismatches)
 
 
-#pylint: enable=line-too-long
-
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main(verbosity=2)
diff --git a/starlark_fmt/format.go b/starlark_fmt/format.go
index 23eee59..3e51fa1 100644
--- a/starlark_fmt/format.go
+++ b/starlark_fmt/format.go
@@ -39,21 +39,26 @@
 
 // PrintsStringList returns a Starlark-compatible string of a list of Strings/Labels.
 func PrintStringList(items []string, indentLevel int) string {
-	return PrintList(items, indentLevel, `"%s"`)
+	return PrintList(items, indentLevel, func(s string) string {
+		if strings.Contains(s, "\"") {
+			return `'''%s'''`
+		}
+		return `"%s"`
+	})
 }
 
 // PrintList returns a Starlark-compatible string of list formmated as requested.
-func PrintList(items []string, indentLevel int, formatString string) string {
+func PrintList(items []string, indentLevel int, formatString func(string) string) string {
 	if len(items) == 0 {
 		return "[]"
 	} else if len(items) == 1 {
-		return fmt.Sprintf("["+formatString+"]", items[0])
+		return fmt.Sprintf("["+formatString(items[0])+"]", 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, fmt.Sprintf(`%s`+formatString(item)+`,`, innerIndent, item))
 	}
 	list = append(list, Indention(indentLevel)+"]")
 	return strings.Join(list, "\n")
diff --git a/starlark_fmt/format_test.go b/starlark_fmt/format_test.go
index 90f78ef..9450a31 100644
--- a/starlark_fmt/format_test.go
+++ b/starlark_fmt/format_test.go
@@ -18,6 +18,10 @@
 	"testing"
 )
 
+func simpleFormat(s string) string {
+	return "%s"
+}
+
 func TestPrintEmptyStringList(t *testing.T) {
 	in := []string{}
 	indentLevel := 0
@@ -54,7 +58,7 @@
 func TestPrintEmptyList(t *testing.T) {
 	in := []string{}
 	indentLevel := 0
-	out := PrintList(in, indentLevel, "%s")
+	out := PrintList(in, indentLevel, simpleFormat)
 	expectedOut := "[]"
 	if out != expectedOut {
 		t.Errorf("Expected %q, got %q", expectedOut, out)
@@ -64,7 +68,7 @@
 func TestPrintSingleElementList(t *testing.T) {
 	in := []string{"1"}
 	indentLevel := 0
-	out := PrintList(in, indentLevel, "%s")
+	out := PrintList(in, indentLevel, simpleFormat)
 	expectedOut := `[1]`
 	if out != expectedOut {
 		t.Errorf("Expected %q, got %q", expectedOut, out)
@@ -74,7 +78,7 @@
 func TestPrintMultiElementList(t *testing.T) {
 	in := []string{"1", "2"}
 	indentLevel := 0
-	out := PrintList(in, indentLevel, "%s")
+	out := PrintList(in, indentLevel, simpleFormat)
 	expectedOut := `[
     1,
     2,
@@ -87,7 +91,7 @@
 func TestListWithNonZeroIndent(t *testing.T) {
 	in := []string{"1", "2"}
 	indentLevel := 1
-	out := PrintList(in, indentLevel, "%s")
+	out := PrintList(in, indentLevel, simpleFormat)
 	expectedOut := `[
         1,
         2,
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index f935f06..88ef615 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -98,9 +98,6 @@
 
 		"build/soong/scripts/jar-wrapper.sh": nil,
 
-		"build/make/core/proguard.flags":             nil,
-		"build/make/core/proguard_basic_keeps.flags": nil,
-
 		"jdk8/jre/lib/jce.jar": nil,
 		"jdk8/jre/lib/rt.jar":  nil,
 		"jdk8/lib/tools.jar":   nil,