Merge "cc Bp2build support for genrules that generate .proto file" into main
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 69bcd2e..d37dc02 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -121,6 +121,7 @@
 		"development/sdk":                             Bp2BuildDefaultTrueRecursively,
 
 		"external/aac":                           Bp2BuildDefaultTrueRecursively,
+		"external/abseil-cpp":                    Bp2BuildDefaultTrueRecursively,
 		"external/arm-optimized-routines":        Bp2BuildDefaultTrueRecursively,
 		"external/auto":                          Bp2BuildDefaultTrue,
 		"external/auto/android-annotation-stubs": Bp2BuildDefaultTrueRecursively,
@@ -189,7 +190,11 @@
 		"external/ow2-asm":                       Bp2BuildDefaultTrueRecursively,
 		"external/pcre":                          Bp2BuildDefaultTrueRecursively,
 		"external/protobuf":                      Bp2BuildDefaultTrueRecursively,
+		"external/python/pyyaml/lib/yaml":        Bp2BuildDefaultTrueRecursively,
 		"external/python/six":                    Bp2BuildDefaultTrueRecursively,
+		"external/python/jinja/src":              Bp2BuildDefaultTrueRecursively,
+		"external/python/markupsafe/src":         Bp2BuildDefaultTrueRecursively,
+		"external/python/setuptools":             Bp2BuildDefaultTrueRecursively,
 		"external/rappor":                        Bp2BuildDefaultTrueRecursively,
 		"external/scudo":                         Bp2BuildDefaultTrueRecursively,
 		"external/selinux/checkpolicy":           Bp2BuildDefaultTrueRecursively,
@@ -813,6 +818,8 @@
 		"chre_flatbuffers",
 		"event_logger",
 		"hal_unit_tests",
+
+		"merge_annotation_zips_test",
 	}
 
 	Bp2buildModuleTypeAlwaysConvertList = []string{
@@ -836,6 +843,10 @@
 	// the "prebuilt_" prefix to the name, so that it's differentiable from
 	// the source versions within Soong's module graph.
 	Bp2buildModuleDoNotConvertList = []string{
+		// TODO(b/263326760): Failed already.
+		"minijail_compiler_unittest",
+		"minijail_parser_unittest",
+
 		// Depends on unconverted libandroid, libgui
 		"dvr_buffer_queue-test",
 		"dvr_display-test",
@@ -1516,73 +1527,6 @@
 		"libartd-runtime-gtest",
 	}
 
-	MixedBuildsDisabledList = []string{
-		"libruy_static", "libtflite_kernel_utils", // TODO(b/237315968); Depend on prebuilt stl, not from source
-
-		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
-
-		"libbrotli",               // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
-		"minijail_constants_json", // http://b/200899432, bazel-built cc_genrule does not work in mixed build when it is a dependency of another soong module.
-
-		"cap_names.h",                                  // TODO(b/204913827) runfiles need to be handled in mixed builds
-		"libcap",                                       // TODO(b/204913827) runfiles need to be handled in mixed builds
-		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
-
-		// Depends on libprotobuf-cpp-*
-		"libadb_pairing_connection",
-		"libadb_pairing_connection_static",
-		"libadb_pairing_server", "libadb_pairing_server_static",
-
-		// java_import[_host] issues
-		// tradefed prebuilts depend on libprotobuf
-		"prebuilt_tradefed",
-		"prebuilt_tradefed-test-framework",
-		// handcrafted BUILD.bazel files in //prebuilts/...
-		"prebuilt_r8lib-prebuilt",
-		"prebuilt_sdk-core-lambda-stubs",
-		"prebuilt_android-support-collections-nodeps",
-		"prebuilt_android-arch-core-common-nodeps",
-		"prebuilt_android-arch-lifecycle-common-java8-nodeps",
-		"prebuilt_android-arch-lifecycle-common-nodeps",
-		"prebuilt_android-support-annotations-nodeps",
-		"prebuilt_android-arch-paging-common-nodeps",
-		"prebuilt_android-arch-room-common-nodeps",
-		// TODO(b/217750501) exclude_dirs property not supported
-		"prebuilt_kotlin-reflect",
-		"prebuilt_kotlin-stdlib",
-		"prebuilt_kotlin-stdlib-jdk7",
-		"prebuilt_kotlin-stdlib-jdk8",
-		"prebuilt_kotlin-test",
-		// TODO(b/217750501) exclude_files property not supported
-		"prebuilt_currysrc_org.eclipse",
-
-		// TODO(b/266459895): re-enable libunwindstack
-		"libunwindstack",
-		"libunwindstack_stdout_log",
-		"libunwindstack_no_dex",
-		"libunwindstack_utils",
-		"unwind_reg_info",
-		"libunwindstack_local",
-		"unwind_for_offline",
-		"unwind",
-		"unwind_info",
-		"unwind_symbols",
-		"libEGL",
-		"libGLESv2",
-		"libc_malloc_debug",
-		"libcodec2_hidl@1.0",
-		"libcodec2_hidl@1.1",
-		"libcodec2_hidl@1.2",
-		"libfdtrack",
-		"libgui",
-		"libgui_bufferqueue_static",
-		"libmedia_codecserviceregistrant",
-		"libstagefright_bufferqueue_helper_novndk",
-		"libutils_test",
-		"libutilscallstack",
-		"mediaswcodec",
-	}
-
 	// Bazel prod-mode allowlist. Modules in this list are built by Bazel
 	// in either prod mode or staging mode.
 	ProdMixedBuildsEnabledList = []string{
@@ -1643,4 +1587,14 @@
 		"art_":        DEFAULT_PRIORITIZED_WEIGHT,
 		"ndk_library": DEFAULT_PRIORITIZED_WEIGHT,
 	}
+
+	BazelSandwichTargets = []struct {
+		Label string
+		Host  bool
+	}{
+		{
+			Label: "//build/bazel/examples/partitions:system_image",
+			Host:  false,
+		},
+	}
 )
diff --git a/android/bazel.go b/android/bazel.go
index 0d2c777..df30ff2 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -22,6 +22,7 @@
 
 	"android/soong/ui/metrics/bp2build_metrics_proto"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android/allowlists"
@@ -426,8 +427,23 @@
 	return ModuleIncompatibility
 }
 
+func isGoModule(module blueprint.Module) bool {
+	if _, ok := module.(*bootstrap.GoPackage); ok {
+		return true
+	}
+	if _, ok := module.(*bootstrap.GoBinary); ok {
+		return true
+	}
+	return false
+}
+
 // ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
 func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
+	// Special-case bootstrap_go_package and bootstrap_go_binary
+	// These do not implement Bazelable, but have been converted
+	if isGoModule(module) {
+		return true
+	}
 	b, ok := module.(Bazelable)
 	if !ok {
 		return false
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 94bc88b..fda8a22 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -22,6 +22,7 @@
 	"os"
 	"path"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strings"
@@ -186,6 +187,8 @@
 
 	// Returns the depsets defined in Bazel's aquery response.
 	AqueryDepsets() []bazel.AqueryDepset
+
+	QueueBazelSandwichCqueryRequests(config Config) error
 }
 
 type bazelRunner interface {
@@ -264,6 +267,10 @@
 	m.BazelRequests[key] = true
 }
 
+func (m MockBazelContext) QueueBazelSandwichCqueryRequests(config Config) error {
+	panic("unimplemented")
+}
+
 func (m MockBazelContext) GetOutputFiles(label string, _ configKey) ([]string, error) {
 	result, ok := m.LabelToOutputFiles[label]
 	if !ok {
@@ -424,6 +431,10 @@
 	panic("unimplemented")
 }
 
+func (n noopBazelContext) QueueBazelSandwichCqueryRequests(config Config) error {
+	panic("unimplemented")
+}
+
 func (n noopBazelContext) GetOutputFiles(_ string, _ configKey) ([]string, error) {
 	panic("unimplemented")
 }
@@ -1042,6 +1053,64 @@
 	allBazelCommands = []bazelCommand{aqueryCmd, cqueryCmd, buildCmd}
 )
 
+func GetBazelSandwichCqueryRequests(config Config) ([]cqueryKey, error) {
+	result := make([]cqueryKey, 0, len(allowlists.BazelSandwichTargets))
+	labelRegex := regexp.MustCompile("^@?//([a-zA-Z0-9/_-]+):[a-zA-Z0-9_-]+$")
+	// Note that bazel "targets" are different from soong "targets", the bazel targets are
+	// synonymous with soong modules, and soong targets are a configuration a module is built in.
+	for _, target := range allowlists.BazelSandwichTargets {
+		match := labelRegex.FindStringSubmatch(target.Label)
+		if match == nil {
+			return nil, fmt.Errorf("invalid label, must match `^@?//([a-zA-Z0-9/_-]+):[a-zA-Z0-9_-]+$`: %s", target.Label)
+		}
+		if _, err := os.Stat(absolutePath(match[1])); err != nil {
+			if os.IsNotExist(err) {
+				// Ignore bazel sandwich targets that don't exist.
+				continue
+			} else {
+				return nil, err
+			}
+		}
+
+		var soongTarget Target
+		if target.Host {
+			soongTarget = config.BuildOSTarget
+		} else {
+			soongTarget = config.AndroidCommonTarget
+			if soongTarget.Os.Class != Device {
+				// kernel-build-tools seems to set the AndroidCommonTarget to a linux host
+				// target for some reason, disable device builds in that case.
+				continue
+			}
+		}
+
+		result = append(result, cqueryKey{
+			label:       target.Label,
+			requestType: cquery.GetOutputFiles,
+			configKey: configKey{
+				arch:   soongTarget.Arch.String(),
+				osType: soongTarget.Os,
+			},
+		})
+	}
+	return result, nil
+}
+
+// QueueBazelSandwichCqueryRequests queues cquery requests for all the bazel labels in
+// bazel_sandwich_targets. These will later be given phony targets so that they can be built on the
+// command line.
+func (context *mixedBuildBazelContext) QueueBazelSandwichCqueryRequests(config Config) error {
+	requests, err := GetBazelSandwichCqueryRequests(config)
+	if err != nil {
+		return err
+	}
+	for _, request := range requests {
+		context.QueueBazelRequest(request.label, request.requestType, request.configKey)
+	}
+
+	return nil
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *mixedBuildBazelContext) InvokeBazel(config Config, ctx invokeBazelContext) error {
@@ -1255,6 +1324,11 @@
 
 	executionRoot := path.Join(ctx.Config().BazelContext.OutputBase(), "execroot", "__main__")
 	bazelOutDir := path.Join(executionRoot, "bazel-out")
+	rel, err := filepath.Rel(ctx.Config().OutDir(), executionRoot)
+	if err != nil {
+		ctx.Errorf("%s", err.Error())
+	}
+	dotdotsToOutRoot := strings.Repeat("../", strings.Count(rel, "/")+1)
 	for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
 		// nil build statements are a valid case where we do not create an action because it is
 		// unnecessary or handled by other processing
@@ -1286,7 +1360,8 @@
 					})
 				}
 			}
-			createCommand(rule.Command(), buildStatement, executionRoot, bazelOutDir, ctx, depsetHashToDepset)
+			createCommand(rule.Command(), buildStatement, executionRoot, bazelOutDir, ctx, depsetHashToDepset, dotdotsToOutRoot)
+
 			desc := fmt.Sprintf("%s: %s", buildStatement.Mnemonic, buildStatement.OutputPaths)
 			rule.Build(fmt.Sprintf("bazel %d", index), desc)
 			continue
@@ -1331,6 +1406,24 @@
 			panic(fmt.Sprintf("unhandled build statement: %v", buildStatement))
 		}
 	}
+
+	// Create phony targets for all the bazel sandwich output files
+	requests, err := GetBazelSandwichCqueryRequests(ctx.Config())
+	if err != nil {
+		ctx.Errorf(err.Error())
+	}
+	for _, request := range requests {
+		files, err := ctx.Config().BazelContext.GetOutputFiles(request.label, request.configKey)
+		if err != nil {
+			ctx.Errorf(err.Error())
+		}
+		filesAsPaths := make([]Path, 0, len(files))
+		for _, file := range files {
+			filesAsPaths = append(filesAsPaths, PathForBazelOut(ctx, file))
+		}
+		ctx.Phony("bazel_sandwich", filesAsPaths...)
+	}
+	ctx.Phony("checkbuild", PathForPhony(ctx, "bazel_sandwich"))
 }
 
 // Returns a out dir path for a sandboxed mixed build action
@@ -1344,7 +1437,7 @@
 }
 
 // Register bazel-owned build statements (obtained from the aquery invocation).
-func createCommand(cmd *RuleBuilderCommand, buildStatement *bazel.BuildStatement, executionRoot string, bazelOutDir string, ctx BuilderContext, depsetHashToDepset map[string]bazel.AqueryDepset) {
+func createCommand(cmd *RuleBuilderCommand, buildStatement *bazel.BuildStatement, executionRoot string, bazelOutDir string, ctx BuilderContext, depsetHashToDepset map[string]bazel.AqueryDepset, dotdotsToOutRoot string) {
 	// executionRoot is the action cwd.
 	if buildStatement.ShouldRunInSbox {
 		// mkdir -p ensures that the directory exists when run via sbox
@@ -1367,14 +1460,17 @@
 		cmd.Flag(pair.Key + "=" + pair.Value)
 	}
 
+	command := buildStatement.Command
+	command = strings.ReplaceAll(command, "{DOTDOTS_TO_OUTPUT_ROOT}", dotdotsToOutRoot)
+
 	// The actual Bazel action.
-	if len(buildStatement.Command) > 16*1024 {
+	if len(command) > 16*1024 {
 		commandFile := PathForBazelOut(ctx, buildStatement.OutputPaths[0]+".sh")
-		WriteFileRule(ctx, commandFile, buildStatement.Command)
+		WriteFileRule(ctx, commandFile, command)
 
 		cmd.Text("bash").Text(buildStatement.OutputPaths[0] + ".sh").Implicit(commandFile)
 	} else {
-		cmd.Text(buildStatement.Command)
+		cmd.Text(command)
 	}
 
 	for _, outputPath := range buildStatement.OutputPaths {
@@ -1403,6 +1499,9 @@
 			cmd.Implicit(PathForPhony(ctx, otherDepsetName))
 		}
 	}
+	for _, implicitPath := range buildStatement.ImplicitDeps {
+		cmd.Implicit(PathForArbitraryOutput(ctx, implicitPath))
+	}
 
 	if depfile := buildStatement.Depfile; depfile != nil {
 		// The paths in depfile are relative to `executionRoot`.
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index e08a471..9a3c8fc 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -181,7 +181,7 @@
 
 		cmd := RuleBuilderCommand{}
 		ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
-		createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{})
+		createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{}, "")
 		if actual, expected := cmd.buf.String(), testCase.command; expected != actual {
 			t.Errorf("expected: [%s], actual: [%s]", expected, actual)
 		}
@@ -224,7 +224,7 @@
 
 	cmd := RuleBuilderCommand{}
 	ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
-	createCommand(&cmd, statement, "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{})
+	createCommand(&cmd, statement, "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{}, "")
 	// Assert that the output is generated in an intermediate directory
 	// fe05bcdcdc4928012781a5f1a2a77cbb5398e106 is the sha1 checksum of "one"
 	if actual, expected := cmd.outputs[0].String(), "out/soong/mixed_build_sbox_intermediates/fe05bcdcdc4928012781a5f1a2a77cbb5398e106/test/exec_root/one"; expected != actual {
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index 7568543..b08a4ca 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -431,7 +431,7 @@
 
 func BazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
 	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
-	if !convertedToBazel(ctx, module) {
+	if !convertedToBazel(ctx, module) || isGoModule(module) {
 		return bp2buildModuleLabel(ctx, module)
 	}
 	b, _ := module.(Bazelable)
diff --git a/android/config.go b/android/config.go
index 72ff224..eb89493 100644
--- a/android/config.go
+++ b/android/config.go
@@ -414,6 +414,12 @@
 	return nil
 }
 
+type productVariableStarlarkRepresentation struct {
+	soongType   string
+	selectable  bool
+	archVariant bool
+}
+
 func saveToBazelConfigFile(config *ProductVariables, outDir string) error {
 	dir := filepath.Join(outDir, bazel.SoongInjectionDirName, "product_config")
 	err := createDirIfNonexistent(dir, os.ModePerm)
@@ -421,32 +427,43 @@
 		return fmt.Errorf("Could not create dir %s: %s", dir, err)
 	}
 
-	nonArchVariantProductVariables := []string{}
-	archVariantProductVariables := []string{}
+	allProductVariablesType := reflect.TypeOf((*ProductVariables)(nil)).Elem()
+	productVariablesInfo := make(map[string]productVariableStarlarkRepresentation)
 	p := variableProperties{}
 	t := reflect.TypeOf(p.Product_variables)
 	for i := 0; i < t.NumField(); i++ {
 		f := t.Field(i)
-		nonArchVariantProductVariables = append(nonArchVariantProductVariables, strings.ToLower(f.Name))
-		if proptools.HasTag(f, "android", "arch_variant") {
-			archVariantProductVariables = append(archVariantProductVariables, strings.ToLower(f.Name))
+		if f.Name == "Pdk" {
+			// Pdk is deprecated and has no effect as of aosp/1319667
+			continue
+		}
+		archVariant := proptools.HasTag(f, "android", "arch_variant")
+		if mainProductVariablesStructField, ok := allProductVariablesType.FieldByName(f.Name); ok {
+			productVariablesInfo[f.Name] = productVariableStarlarkRepresentation{
+				soongType:   stringRepresentationOfSimpleType(mainProductVariablesStructField.Type),
+				selectable:  true,
+				archVariant: archVariant,
+			}
+		} else {
+			panic("Unknown variable " + f.Name)
 		}
 	}
 
-	nonArchVariantProductVariablesJson := starlark_fmt.PrintStringList(nonArchVariantProductVariables, 0)
-	if err != nil {
-		return fmt.Errorf("cannot marshal product variable data: %s", err.Error())
-	}
-
-	archVariantProductVariablesJson := starlark_fmt.PrintStringList(archVariantProductVariables, 0)
-	if err != nil {
-		return fmt.Errorf("cannot marshal arch variant product variable data: %s", err.Error())
-	}
-
 	err = pathtools.WriteFileIfChanged(filepath.Join(dir, "product_variable_constants.bzl"), []byte(fmt.Sprintf(`
-product_var_constraints = %s
-arch_variant_product_var_constraints = %s
-`, nonArchVariantProductVariablesJson, archVariantProductVariablesJson)), 0644)
+# product_var_constant_info is a map of product variables to information about them. The fields are:
+# - soongType: The type of the product variable as it appears in soong's ProductVariables struct.
+#              examples are string, bool, int, *bool, *string, []string, etc. This may be an overly
+#              conservative estimation of the type, for example a *bool could oftentimes just be a
+#              bool that defaults to false.
+# - selectable: if this product variable can be selected on in Android.bp/build files. This means
+#               it's listed in the "variableProperties" soong struct. Currently all variables in
+#               this list are selectable because we only need the selectable ones at the moment,
+#               but the list may be expanded later.
+# - archVariant: If the variable is tagged as arch variant in the "variableProperties" struct.
+product_var_constant_info = %s
+product_var_constraints = [k for k, v in product_var_constant_info.items() if v.selectable]
+arch_variant_product_var_constraints = [k for k, v in product_var_constant_info.items() if v.selectable and v.archVariant]
+`, starlark_fmt.PrintAny(productVariablesInfo, 0))), 0644)
 	if err != nil {
 		return fmt.Errorf("Could not write .bzl config file %s", err)
 	}
@@ -459,6 +476,23 @@
 	return nil
 }
 
+func stringRepresentationOfSimpleType(ty reflect.Type) string {
+	switch ty.Kind() {
+	case reflect.String:
+		return "string"
+	case reflect.Bool:
+		return "bool"
+	case reflect.Int:
+		return "int"
+	case reflect.Slice:
+		return "[]" + stringRepresentationOfSimpleType(ty.Elem())
+	case reflect.Pointer:
+		return "*" + stringRepresentationOfSimpleType(ty.Elem())
+	default:
+		panic("unimplemented type: " + ty.Kind().String())
+	}
+}
+
 // NullConfig returns a mostly empty Config for use by standalone tools like dexpreopt_gen that
 // use the android package.
 func NullConfig(outDir, soongOutDir string) Config {
diff --git a/android/module.go b/android/module.go
index 4c781f6..b982019 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1373,15 +1373,15 @@
 		}
 	}
 
-	productConfigEnabledLabels := []bazel.Label{}
+	productConfigEnabledAttribute := bazel.LabelListAttribute{}
 	// TODO(b/234497586): Soong config variables and product variables have different overriding behavior, we
 	// should handle it correctly
 	if !proptools.BoolDefault(enabledProperty.Value, true) && !neitherHostNorDevice {
 		// If the module is not enabled by default, then we can check if a
 		// product variable enables it
-		productConfigEnabledLabels = productVariableConfigEnableLabels(ctx)
+		productConfigEnabledAttribute = productVariableConfigEnableAttribute(ctx)
 
-		if len(productConfigEnabledLabels) > 0 {
+		if len(productConfigEnabledAttribute.ConfigurableValues) > 0 {
 			// In this case, an existing product variable configuration overrides any
 			// module-level `enable: false` definition
 			newValue := true
@@ -1389,10 +1389,6 @@
 		}
 	}
 
-	productConfigEnabledAttribute := bazel.MakeLabelListAttribute(bazel.LabelList{
-		productConfigEnabledLabels, nil,
-	})
-
 	platformEnabledAttribute, err := enabledProperty.ToLabelListAttribute(
 		bazel.LabelList{[]bazel.Label{{Label: "@platforms//:incompatible"}}, nil},
 		bazel.LabelList{[]bazel.Label{}, nil})
@@ -1423,31 +1419,28 @@
 
 // Check product variables for `enabled: true` flag override.
 // Returns a list of the constraint_value targets who enable this override.
-func productVariableConfigEnableLabels(ctx *topDownMutatorContext) []bazel.Label {
+func productVariableConfigEnableAttribute(ctx *topDownMutatorContext) bazel.LabelListAttribute {
+	result := bazel.LabelListAttribute{}
 	productVariableProps := ProductVariableProperties(ctx, ctx.Module())
-	productConfigEnablingTargets := []bazel.Label{}
-	const propName = "Enabled"
-	if productConfigProps, exists := productVariableProps[propName]; exists {
+	if productConfigProps, exists := productVariableProps["Enabled"]; exists {
 		for productConfigProp, prop := range productConfigProps {
 			flag, ok := prop.(*bool)
 			if !ok {
-				ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
+				ctx.ModuleErrorf("Could not convert product variable enabled property")
 			}
 
 			if *flag {
 				axis := productConfigProp.ConfigurationAxis()
-				targetLabel := axis.SelectKey(productConfigProp.SelectKey())
-				productConfigEnablingTargets = append(productConfigEnablingTargets, bazel.Label{
-					Label: targetLabel,
-				})
+				result.SetSelectValue(axis, bazel.ConditionsDefaultConfigKey, bazel.MakeLabelList([]bazel.Label{{Label: "@platforms//:incompatible"}}))
+				result.SetSelectValue(axis, productConfigProp.SelectKey(), bazel.LabelList{Includes: []bazel.Label{}})
 			} else {
 				// TODO(b/210546943): handle negative case where `enabled: false`
-				ctx.ModuleErrorf("`enabled: false` is not currently supported for configuration variables. See b/210546943", proptools.PropertyNameForField(propName))
+				ctx.ModuleErrorf("`enabled: false` is not currently supported for configuration variables. See b/210546943")
 			}
 		}
 	}
 
-	return productConfigEnablingTargets
+	return result
 }
 
 // A ModuleBase object contains the properties that are common to all Android
diff --git a/android/paths.go b/android/paths.go
index e16cb37..325a953 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1029,16 +1029,16 @@
 	return p
 }
 
+func (p basePath) RelativeToTop() Path {
+	ensureTestOnly()
+	return p
+}
+
 // SourcePath is a Path representing a file path rooted from SrcDir
 type SourcePath struct {
 	basePath
 }
 
-func (p SourcePath) RelativeToTop() Path {
-	ensureTestOnly()
-	return p
-}
-
 var _ Path = SourcePath{}
 
 func (p SourcePath) withRel(rel string) SourcePath {
@@ -1126,6 +1126,16 @@
 	return path
 }
 
+// PathForArbitraryOutput creates a path for the given components. Unlike PathForOutput,
+// the path is relative to the root of the output folder, not the out/soong folder.
+func PathForArbitraryOutput(ctx PathContext, pathComponents ...string) Path {
+	p, err := validatePath(pathComponents...)
+	if err != nil {
+		reportPathError(ctx, err)
+	}
+	return basePath{path: filepath.Join(ctx.Config().OutDir(), p)}
+}
+
 // MaybeExistentPathForSource joins the provided path components and validates that the result
 // neither escapes the source dir nor is in the out dir.
 // It does not validate whether the path exists.
diff --git a/android/variable.go b/android/variable.go
index 7fb81b9..03a80c1 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -160,6 +160,7 @@
 			}
 		}
 
+		// Deprecated, has no effect as of aosp/1319667
 		Pdk struct {
 			Enabled *bool `android:"arch_variant"`
 		} `android:"arch_variant"`
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 2c080a1..d77d59a 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -17,15 +17,15 @@
 import (
 	"crypto/sha256"
 	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"path/filepath"
+	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
 	"reflect"
 	"sort"
 	"strings"
 	"sync"
 
-	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
-
 	"github.com/google/blueprint/metrics"
 	"github.com/google/blueprint/proptools"
 	"google.golang.org/protobuf/proto"
@@ -119,6 +119,10 @@
 	// If ShouldRunInSbox is true, Soong will use sbox to created an isolated environment
 	// and run the mixed build action there
 	ShouldRunInSbox bool
+	// A list of files to add as implicit deps to the outputs of this BuildStatement.
+	// Unlike most properties in BuildStatement, these paths must be relative to the root of
+	// the whole out/ folder, instead of relative to ctx.Config().BazelContext.OutputBase()
+	ImplicitDeps []string
 }
 
 // A helper type for aquery processing which facilitates retrieval of path IDs from their
@@ -581,6 +585,72 @@
 	}, nil
 }
 
+type bazelSandwichJson struct {
+	Target         string   `json:"target"`
+	DependOnTarget *bool    `json:"depend_on_target,omitempty"`
+	ImplicitDeps   []string `json:"implicit_deps"`
+}
+
+func (a *aqueryArtifactHandler) unresolvedSymlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
+	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
+	if err != nil {
+		return nil, err
+	}
+	if len(actionEntry.InputDepSetIds) != 0 || len(outputPaths) != 1 {
+		return nil, fmt.Errorf("expected 0 inputs and 1 output to symlink action, got: input %q, output %q", actionEntry.InputDepSetIds, outputPaths)
+	}
+	target := actionEntry.UnresolvedSymlinkTarget
+	if target == "" {
+		return nil, fmt.Errorf("expected an unresolved_symlink_target, but didn't get one")
+	}
+	if filepath.Clean(target) != target {
+		return nil, fmt.Errorf("expected %q, got %q", filepath.Clean(target), target)
+	}
+	if strings.HasPrefix(target, "/") {
+		return nil, fmt.Errorf("no absolute symlinks allowed: %s", target)
+	}
+
+	out := outputPaths[0]
+	outDir := filepath.Dir(out)
+	var implicitDeps []string
+	if strings.HasPrefix(target, "bazel_sandwich:") {
+		j := bazelSandwichJson{}
+		err := json.Unmarshal([]byte(target[len("bazel_sandwich:"):]), &j)
+		if err != nil {
+			return nil, err
+		}
+		if proptools.BoolDefault(j.DependOnTarget, true) {
+			implicitDeps = append(implicitDeps, j.Target)
+		}
+		implicitDeps = append(implicitDeps, j.ImplicitDeps...)
+		dotDotsToReachCwd := ""
+		if outDir != "." {
+			dotDotsToReachCwd = strings.Repeat("../", strings.Count(outDir, "/")+1)
+		}
+		target = proptools.ShellEscapeIncludingSpaces(j.Target)
+		target = "{DOTDOTS_TO_OUTPUT_ROOT}" + dotDotsToReachCwd + target
+	} else {
+		target = proptools.ShellEscapeIncludingSpaces(target)
+	}
+
+	outDir = proptools.ShellEscapeIncludingSpaces(outDir)
+	out = proptools.ShellEscapeIncludingSpaces(out)
+	// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
+	command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, target)
+	symlinkPaths := outputPaths[:]
+
+	buildStatement := &BuildStatement{
+		Command:      command,
+		Depfile:      depfile,
+		OutputPaths:  outputPaths,
+		Env:          actionEntry.EnvironmentVariables,
+		Mnemonic:     actionEntry.Mnemonic,
+		SymlinkPaths: symlinkPaths,
+		ImplicitDeps: implicitDeps,
+	}
+	return buildStatement, nil
+}
+
 func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
 	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
 	if err != nil {
@@ -690,6 +760,8 @@
 		return a.fileWriteActionBuildStatement(actionEntry)
 	case "SymlinkTree":
 		return a.symlinkTreeActionBuildStatement(actionEntry)
+	case "UnresolvedSymlink":
+		return a.unresolvedSymlinkActionBuildStatement(actionEntry)
 	}
 
 	if len(actionEntry.Arguments) < 1 {
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 19a584f..32c87a0 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -357,9 +357,11 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 	if expected := 1; len(actual) != expected {
 		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
+		return
 	}
 
 	bs := actual[0]
@@ -544,6 +546,7 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 	assertBuildStatements(t, []*BuildStatement{
 		&BuildStatement{
@@ -756,9 +759,11 @@
 	actualBuildStatements, actualDepsets, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 	if expected := 2; len(actualBuildStatements) != expected {
 		t.Fatalf("Expected %d build statements, got %d %#v", expected, len(actualBuildStatements), actualBuildStatements)
+		return
 	}
 
 	expectedDepsetFiles := [][]string{
@@ -859,6 +864,7 @@
 
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 
 	expectedBuildStatements := []*BuildStatement{
@@ -907,6 +913,7 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 
 	expectedBuildStatements := []*BuildStatement{
@@ -1017,6 +1024,7 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 
 	expectedBuildStatements := []*BuildStatement{
@@ -1088,6 +1096,7 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 	assertBuildStatements(t, []*BuildStatement{
 		&BuildStatement{
@@ -1126,6 +1135,7 @@
 	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
+		return
 	}
 	assertBuildStatements(t, []*BuildStatement{
 		&BuildStatement{
@@ -1136,6 +1146,126 @@
 	}, actual)
 }
 
+func TestUnresolvedSymlink(t *testing.T) {
+	const inputString = `
+{
+ "artifacts": [
+   { "id": 1, "path_fragment_id": 1 }
+ ],
+ "actions": [{
+   "target_id": 1,
+   "action_key": "x",
+   "mnemonic": "UnresolvedSymlink",
+   "configuration_id": 1,
+   "output_ids": [1],
+   "primary_output_id": 1,
+   "execution_platform": "//build/bazel/platforms:linux_x86_64",
+   "unresolved_symlink_target": "symlink/target"
+ }],
+ "path_fragments": [
+   { "id": 1, "label": "path/to/symlink" }
+ ]
+}
+`
+	data, err := JsonToActionGraphContainer(inputString)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+		return
+	}
+	assertBuildStatements(t, []*BuildStatement{{
+		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf symlink/target path/to/symlink",
+		OutputPaths:  []string{"path/to/symlink"},
+		Mnemonic:     "UnresolvedSymlink",
+		SymlinkPaths: []string{"path/to/symlink"},
+	}}, actual)
+}
+
+func TestUnresolvedSymlinkBazelSandwich(t *testing.T) {
+	const inputString = `
+{
+ "artifacts": [
+   { "id": 1, "path_fragment_id": 1 }
+ ],
+ "actions": [{
+   "target_id": 1,
+   "action_key": "x",
+   "mnemonic": "UnresolvedSymlink",
+   "configuration_id": 1,
+   "output_ids": [1],
+   "primary_output_id": 1,
+   "execution_platform": "//build/bazel/platforms:linux_x86_64",
+   "unresolved_symlink_target": "bazel_sandwich:{\"target\":\"target/product/emulator_x86_64/system\"}"
+ }],
+ "path_fragments": [
+   { "id": 1, "label": "path/to/symlink" }
+ ]
+}
+`
+	data, err := JsonToActionGraphContainer(inputString)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+		return
+	}
+	assertBuildStatements(t, []*BuildStatement{{
+		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
+		OutputPaths:  []string{"path/to/symlink"},
+		Mnemonic:     "UnresolvedSymlink",
+		SymlinkPaths: []string{"path/to/symlink"},
+		ImplicitDeps: []string{"target/product/emulator_x86_64/system"},
+	}}, actual)
+}
+
+func TestUnresolvedSymlinkBazelSandwichWithAlternativeDeps(t *testing.T) {
+	const inputString = `
+{
+ "artifacts": [
+   { "id": 1, "path_fragment_id": 1 }
+ ],
+ "actions": [{
+   "target_id": 1,
+   "action_key": "x",
+   "mnemonic": "UnresolvedSymlink",
+   "configuration_id": 1,
+   "output_ids": [1],
+   "primary_output_id": 1,
+   "execution_platform": "//build/bazel/platforms:linux_x86_64",
+   "unresolved_symlink_target": "bazel_sandwich:{\"depend_on_target\":false,\"implicit_deps\":[\"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp\"],\"target\":\"target/product/emulator_x86_64/system\"}"
+ }],
+ "path_fragments": [
+   { "id": 1, "label": "path/to/symlink" }
+ ]
+}
+`
+	data, err := JsonToActionGraphContainer(inputString)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+		return
+	}
+	assertBuildStatements(t, []*BuildStatement{{
+		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
+		OutputPaths:  []string{"path/to/symlink"},
+		Mnemonic:     "UnresolvedSymlink",
+		SymlinkPaths: []string{"path/to/symlink"},
+		// Note that the target of the symlink, target/product/emulator_x86_64/system, is not listed here
+		ImplicitDeps: []string{"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp"},
+	}}, actual)
+}
+
 func assertError(t *testing.T, err error, expected string) {
 	t.Helper()
 	if err == nil {
@@ -1201,6 +1331,9 @@
 	if !reflect.DeepEqual(sortedStrings(first.SymlinkPaths), sortedStrings(second.SymlinkPaths)) {
 		return "SymlinkPaths"
 	}
+	if !reflect.DeepEqual(sortedStrings(first.ImplicitDeps), sortedStrings(second.ImplicitDeps)) {
+		return "ImplicitDeps"
+	}
 	if first.Depfile != second.Depfile {
 		return "Depfile"
 	}
diff --git a/bazel/configurability.go b/bazel/configurability.go
index d962a1d..671e5c1 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -67,7 +67,7 @@
 
 	ConditionsDefaultSelectKey = "//conditions:default"
 
-	productVariableBazelPackage = "//build/bazel/product_variables"
+	productVariableBazelPackage = "//build/bazel/product_config/config_settings"
 
 	AndroidAndInApex = "android-in_apex"
 	AndroidPlatform  = "system"
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index 84c7ea2..2383247 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -1555,7 +1555,7 @@
 				"file_contexts": `":foo-file_contexts"`,
 				"manifest":      `"apex_manifest.json"`,
 				"min_sdk_version": `select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": "30",
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": "30",
         "//conditions:default": "31",
     })`,
 				"package_name": `"pkg_name"`,
@@ -1564,7 +1564,7 @@
 				"file_contexts":  `":foo-file_contexts"`,
 				"manifest":       `"apex_manifest.json"`,
 				"min_sdk_version": `select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": "30",
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": "30",
         "//conditions:default": "31",
     })`,
 				"package_name": `"override_pkg_name"`,
diff --git a/bp2build/bp2build_product_config.go b/bp2build/bp2build_product_config.go
index f56e6d8..db478db 100644
--- a/bp2build/bp2build_product_config.go
+++ b/bp2build/bp2build_product_config.go
@@ -2,6 +2,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/android/soongconfig"
 	"android/soong/starlark_import"
 	"encoding/json"
 	"fmt"
@@ -51,7 +52,7 @@
 		"{VARIANT}", targetBuildVariant,
 		"{PRODUCT_FOLDER}", currentProductFolder)
 
-	platformMappingContent, err := platformMappingContent(productReplacer.Replace("@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}"), &productVariables)
+	platformMappingContent, err := platformMappingContent(productReplacer.Replace("@soong_injection//{PRODUCT_FOLDER}:{PRODUCT}-{VARIANT}"), &productVariables, ctx.Config().Bp2buildSoongConfigDefinitions)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -97,6 +98,7 @@
 android_product(
     name = "mixed_builds_product-{VARIANT}",
     soong_variables = _soong_variables,
+    extra_constraints = ["@//build/bazel/platforms:mixed_builds"],
 )
 `)),
 		newFile(
@@ -147,20 +149,20 @@
 	return injectionDirFiles, bp2buildDirFiles, nil
 }
 
-func platformMappingContent(mainProductLabel string, mainProductVariables *android.ProductVariables) (string, error) {
+func platformMappingContent(mainProductLabel string, mainProductVariables *android.ProductVariables, soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions) (string, error) {
 	productsForTesting, err := starlark_import.GetStarlarkValue[map[string]map[string]starlark.Value]("products_for_testing")
 	if err != nil {
 		return "", err
 	}
 	var result strings.Builder
 	result.WriteString("platforms:\n")
-	platformMappingSingleProduct(mainProductLabel, mainProductVariables, &result)
+	platformMappingSingleProduct(mainProductLabel, mainProductVariables, soongConfigDefinitions, &result)
 	for product, productVariablesStarlark := range productsForTesting {
 		productVariables, err := starlarkMapToProductVariables(productVariablesStarlark)
 		if err != nil {
 			return "", err
 		}
-		platformMappingSingleProduct("@//build/bazel/tests/products:"+product, &productVariables, &result)
+		platformMappingSingleProduct("@//build/bazel/tests/products:"+product, &productVariables, soongConfigDefinitions, &result)
 	}
 	return result.String(), nil
 }
@@ -179,7 +181,7 @@
 	"_windows_x86_64",
 }
 
-func platformMappingSingleProduct(label string, productVariables *android.ProductVariables, result *strings.Builder) {
+func platformMappingSingleProduct(label string, productVariables *android.ProductVariables, soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions, result *strings.Builder) {
 	targetBuildVariant := "user"
 	if proptools.Bool(productVariables.Eng) {
 		targetBuildVariant = "eng"
@@ -193,27 +195,72 @@
 		result.WriteString(suffix)
 		result.WriteString("\n")
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:always_use_prebuilt_sdks=%t\n", proptools.Bool(productVariables.Always_use_prebuilt_sdks)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:arc=%t\n", proptools.Bool(productVariables.Arc)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:apex_global_min_sdk_version_override=%s\n", proptools.String(productVariables.ApexGlobalMinSdkVersionOverride)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:binder32bit=%t\n", proptools.Bool(productVariables.Binder32bit)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:build_from_text_stub=%t\n", proptools.Bool(productVariables.Build_from_text_stub)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:build_id=%s\n", proptools.String(productVariables.BuildId)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:build_version_tags=%s\n", strings.Join(productVariables.BuildVersionTags, ",")))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:certificate_overrides=%s\n", strings.Join(productVariables.CertificateOverrides, ",")))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:cfi_exclude_paths=%s\n", strings.Join(productVariables.CFIExcludePaths, ",")))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:cfi_include_paths=%s\n", strings.Join(productVariables.CFIIncludePaths, ",")))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:compressed_apex=%t\n", proptools.Bool(productVariables.CompressedApex)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:debuggable=%t\n", proptools.Bool(productVariables.Debuggable)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:default_app_certificate=%s\n", proptools.String(productVariables.DefaultAppCertificate)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:device_abi=%s\n", strings.Join(productVariables.DeviceAbi, ",")))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:device_max_page_size_supported=%s\n", proptools.String(productVariables.DeviceMaxPageSizeSupported)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:device_name=%s\n", proptools.String(productVariables.DeviceName)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:device_page_size_agnostic=%t\n", proptools.Bool(productVariables.Device_page_size_agnostic)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:device_product=%s\n", proptools.String(productVariables.DeviceProduct)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:enable_cfi=%t\n", proptools.BoolDefault(productVariables.EnableCFI, true)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:enforce_vintf_manifest=%t\n", proptools.Bool(productVariables.Enforce_vintf_manifest)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:eng=%t\n", proptools.Bool(productVariables.Eng)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:malloc_not_svelte=%t\n", proptools.Bool(productVariables.Malloc_not_svelte)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:malloc_pattern_fill_contents=%t\n", proptools.Bool(productVariables.Malloc_pattern_fill_contents)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:malloc_zero_contents=%t\n", proptools.Bool(productVariables.Malloc_zero_contents)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:manifest_package_name_overrides=%s\n", strings.Join(productVariables.ManifestPackageNameOverrides, ",")))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:native_coverage=%t\n", proptools.Bool(productVariables.Native_coverage)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:platform_version_name=%s\n", proptools.String(productVariables.Platform_version_name)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:product_brand=%s\n", productVariables.ProductBrand))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:product_manufacturer=%s\n", productVariables.ProductManufacturer))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:platform_sdk_version=%d\n", *productVariables.Platform_sdk_version))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:safestack=%t\n", proptools.Bool(productVariables.Safestack)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:target_build_variant=%s\n", targetBuildVariant))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:treble_linker_namespaces=%t\n", proptools.Bool(productVariables.Treble_linker_namespaces)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:tidy_checks=%s\n", proptools.String(productVariables.TidyChecks)))
+		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:uml=%t\n", proptools.Bool(productVariables.Uml)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:unbundled_build=%t\n", proptools.Bool(productVariables.Unbundled_build)))
 		result.WriteString(fmt.Sprintf("    --//build/bazel/product_config:unbundled_build_apps=%s\n", strings.Join(productVariables.Unbundled_build_apps, ",")))
+		for namespace, namespaceContents := range productVariables.VendorVars {
+			for variable, value := range namespaceContents {
+				key := namespace + "__" + variable
+				_, hasBool := soongConfigDefinitions.BoolVars[key]
+				_, hasString := soongConfigDefinitions.StringVars[key]
+				_, hasValue := soongConfigDefinitions.ValueVars[key]
+				if !hasBool && !hasString && !hasValue {
+					// Not all soong config variables are defined in Android.bp files. For example,
+					// prebuilt_bootclasspath_fragment uses soong config variables in a nonstandard
+					// way, that causes them to be present in the soong.variables file but not
+					// defined in an Android.bp file. There's also nothing stopping you from setting
+					// a variable in make that doesn't exist in soong. We only generate build
+					// settings for the ones that exist in soong, so skip all others.
+					continue
+				}
+				if hasBool && hasString || hasBool && hasValue || hasString && hasValue {
+					panic(fmt.Sprintf("Soong config variable %s:%s appears to be of multiple types. bool? %t, string? %t, value? %t", namespace, variable, hasBool, hasString, hasValue))
+				}
+				if hasBool {
+					// Logic copied from soongConfig.Bool()
+					value = strings.ToLower(value)
+					if value == "1" || value == "y" || value == "yes" || value == "on" || value == "true" {
+						value = "true"
+					} else {
+						value = "false"
+					}
+				}
+				result.WriteString(fmt.Sprintf("    --//build/bazel/product_config/soong_config_variables:%s=%s\n", strings.ToLower(key), value))
+			}
+		}
 	}
 }
 
@@ -224,12 +271,20 @@
 		field := productVarsReflect.Field(i)
 		fieldType := productVarsReflect.Type().Field(i)
 		name := fieldType.Name
-		if name == "BootJars" || name == "ApexBootJars" || name == "VendorVars" ||
-			name == "VendorSnapshotModules" || name == "RecoverySnapshotModules" {
+		if name == "BootJars" || name == "ApexBootJars" || name == "VendorSnapshotModules" ||
+			name == "RecoverySnapshotModules" {
 			// These variables have more complicated types, and we don't need them right now
 			continue
 		}
 		if _, ok := in[name]; ok {
+			if name == "VendorVars" {
+				vendorVars, err := starlark_import.Unmarshal[map[string]map[string]string](in[name])
+				if err != nil {
+					return result, err
+				}
+				field.Set(reflect.ValueOf(vendorVars))
+				continue
+			}
 			switch field.Type().Kind() {
 			case reflect.Bool:
 				val, err := starlark_import.Unmarshal[bool](in[name])
@@ -281,5 +336,9 @@
 		}
 	}
 
+	result.Native_coverage = proptools.BoolPtr(
+		proptools.Bool(result.GcovCoverage) ||
+			proptools.Bool(result.ClangCoverage))
+
 	return result, nil
 }
diff --git a/bp2build/bp2build_product_config_test.go b/bp2build/bp2build_product_config_test.go
index 3dd53ce..02d83b4 100644
--- a/bp2build/bp2build_product_config_test.go
+++ b/bp2build/bp2build_product_config_test.go
@@ -69,6 +69,7 @@
 			t.Error(err)
 			continue
 		}
+		testCase.result.Native_coverage = proptools.BoolPtr(false)
 		if !reflect.DeepEqual(testCase.result, productVariables) {
 			expected, err := json.Marshal(testCase.result)
 			if err != nil {
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 3de5213..09a9d04 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -744,10 +744,12 @@
 				targets, targetErrs = generateBazelTargetsGoPackage(bpCtx, glib, nameToGoLibMap)
 				errs = append(errs, targetErrs...)
 				metrics.IncrementRuleClassCount("go_library")
+				metrics.AddConvertedModule(glib, "go_library", dir)
 			} else if gbin, ok := m.(*bootstrap.GoBinary); ok {
 				targets, targetErrs = generateBazelTargetsGoBinary(bpCtx, gbin, nameToGoLibMap)
 				errs = append(errs, targetErrs...)
 				metrics.IncrementRuleClassCount("go_binary")
+				metrics.AddConvertedModule(gbin, "go_binary", dir)
 			} else {
 				metrics.AddUnconvertedModule(m, moduleType, dir, android.UnconvertedReason{
 					ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_TYPE_UNSUPPORTED),
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index e127fd5..4d1d171 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -640,7 +640,10 @@
 }`,
 			ExpectedBazelTargets: []string{
 				MakeBazelTargetNoRestrictions("custom", "foo", AttrNameToString{
-					"target_compatible_with": `["//build/bazel/product_variables:unbundled_build"]`,
+					"target_compatible_with": `select({
+        "//build/bazel/product_config/config_settings:unbundled_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    })`,
 				}),
 			},
 		},
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 490cd91..6b17fc4 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -1260,14 +1260,14 @@
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": [":arm_static_lib_excludes_bp2build_cc_library_static"],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": [],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": [],
         "//conditions:default": [":malloc_not_svelte_static_lib_excludes_bp2build_cc_library_static"],
     })`,
 			"implementation_dynamic_deps": `select({
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": [":arm_shared_lib_excludes"],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_shared_lib"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": [":malloc_not_svelte_shared_lib"],
         "//conditions:default": [],
     })`,
 			"srcs_c": `["common.c"]`,
@@ -1275,7 +1275,7 @@
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": [":arm_whole_static_lib_excludes_bp2build_cc_library_static"],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib_bp2build_cc_library_static"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib_bp2build_cc_library_static"],
         "//conditions:default": [":malloc_not_svelte_whole_static_lib_excludes_bp2build_cc_library_static"],
     })`,
 		}),
@@ -1307,7 +1307,7 @@
 `,
 		ExpectedBazelTargets: makeCcLibraryTargets("foo_static", AttrNameToString{
 			"implementation_deps": `select({
-        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_header_lib"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": [":malloc_not_svelte_header_lib"],
         "//conditions:default": [],
     })`,
 			"srcs_c":                 `["common.c"]`,
@@ -4631,7 +4631,7 @@
         "-Wextra",
         "-DDEBUG_ONLY_CODE=0",
     ] + select({
-        "//build/bazel/product_variables:eng": [
+        "//build/bazel/product_config/config_settings:eng": [
             "-UDEBUG_ONLY_CODE",
             "-DDEBUG_ONLY_CODE=1",
         ],
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index c1eed96..26baf89 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -1188,13 +1188,13 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_static", "foo_static", AttrNameToString{
 				"copts": `select({
-        "//build/bazel/product_variables:binder32bit": ["-Wbinder32bit"],
+        "//build/bazel/product_config/config_settings:binder32bit": ["-Wbinder32bit"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": ["-Wmalloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
+        "//build/bazel/product_config/config_settings:malloc_zero_contents": ["-Wmalloc_zero_contents"],
         "//conditions:default": [],
     })`,
 				"srcs_c": `["common.c"]`,
@@ -1248,19 +1248,19 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_static", "foo_static", AttrNameToString{
 				"copts": `select({
-        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte": ["-Wmalloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte-android": ["-Wandroid_malloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte-android": ["-Wandroid_malloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte-arm": ["-Wlib32_malloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte-arm": ["-Wlib32_malloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte-arm64": ["-Warm64_malloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte-arm64": ["-Warm64_malloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
+        "//build/bazel/product_config/config_settings:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
         "//conditions:default": [],
     })`,
 				"srcs_c": `["common.c"]`,
@@ -1287,7 +1287,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_static", "foo_static", AttrNameToString{
 				"asflags": `select({
-        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//build/bazel/product_config/config_settings:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
     })`,
 				"srcs_as": `["common.S"]`,
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index eab84e1..ecfcb5a 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -200,7 +200,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_object", "foo", AttrNameToString{
 				"asflags": `select({
-        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//build/bazel/product_config/config_settings:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
     })`,
 				"copts":               `["-fno-addrsig"]`,
diff --git a/bp2build/prebuilt_etc_conversion_test.go b/bp2build/prebuilt_etc_conversion_test.go
index aa0a5b7..5b2d609 100644
--- a/bp2build/prebuilt_etc_conversion_test.go
+++ b/bp2build/prebuilt_etc_conversion_test.go
@@ -149,7 +149,7 @@
 			MakeBazelTarget("prebuilt_file", "apex_tz_version", AttrNameToString{
 				"filename": `"tz_version"`,
 				"src": `select({
-        "//build/bazel/product_variables:native_coverage": "src1",
+        "//build/bazel/product_config/config_settings:native_coverage": "src1",
         "//conditions:default": "version/tz_version",
     })`,
 				"dir": `"etc"`,
@@ -318,7 +318,7 @@
 				"dir":      `"etc"`,
 				"src": `select({
         "//build/bazel/platforms/arch:arm": "armSrc",
-        "//build/bazel/product_variables:native_coverage-arm": "nativeCoverageArmSrc",
+        "//build/bazel/product_config/config_settings:native_coverage-arm": "nativeCoverageArmSrc",
         "//conditions:default": None,
     })`,
 			})}})
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
index 143597d..1ff47f2 100644
--- a/bp2build/soong_config_module_type_conversion_test.go
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -91,7 +91,7 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//build/bazel/product_config/config_settings:acme__feature1": ["-DFEATURE1"],
         "//conditions:default": ["-DDEFAULT1"],
     }),
     local_includes = ["."],
@@ -140,7 +140,7 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//build/bazel/product_config/config_settings:acme__feature1": ["-DFEATURE1"],
         "//conditions:default": ["-DDEFAULT1"],
     }),
     local_includes = ["."],
@@ -191,9 +191,9 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
-        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
-        "//build/bazel/product_variables:acme__board__soc_c": [],
+        "//build/bazel/product_config/config_settings:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_b": ["-DSOC_B"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_c": [],
         "//conditions:default": ["-DSOC_DEFAULT"],
     }),
     local_includes = ["."],
@@ -240,7 +240,7 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//build/bazel/product_config/config_settings:acme__feature1": ["-DFEATURE1"],
         "//conditions:default": ["-DDEFAULT1"],
     }),
     local_includes = ["."],
@@ -310,15 +310,15 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
-        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
-        "//build/bazel/product_variables:acme__board__soc_c": [],
+        "//build/bazel/product_config/config_settings:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_b": ["-DSOC_B"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_c": [],
         "//conditions:default": ["-DSOC_DEFAULT"],
     }) + select({
-        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//build/bazel/product_config/config_settings:acme__feature1": ["-DFEATURE1"],
         "//conditions:default": ["-DDEFAULT1"],
     }) + select({
-        "//build/bazel/product_variables:acme__feature2": ["-DFEATURE2"],
+        "//build/bazel/product_config/config_settings:acme__feature2": ["-DFEATURE2"],
         "//conditions:default": ["-DDEFAULT2"],
     }),
     local_includes = ["."],
@@ -380,15 +380,15 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "foo",
     copts = select({
-        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
-        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
-        "//build/bazel/product_variables:acme__board__soc_c": [],
+        "//build/bazel/product_config/config_settings:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_b": ["-DSOC_B"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_c": [],
         "//conditions:default": ["-DSOC_DEFAULT"],
     }),
     implementation_deps = select({
-        "//build/bazel/product_variables:acme__board__soc_a": ["//foo/bar:soc_a_dep"],
-        "//build/bazel/product_variables:acme__board__soc_b": ["//foo/bar:soc_b_dep"],
-        "//build/bazel/product_variables:acme__board__soc_c": [],
+        "//build/bazel/product_config/config_settings:acme__board__soc_a": ["//foo/bar:soc_a_dep"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_b": ["//foo/bar:soc_b_dep"],
+        "//build/bazel/product_config/config_settings:acme__board__soc_c": [],
         "//conditions:default": ["//foo/bar:soc_default_static_dep"],
     }),
     local_includes = ["."],
@@ -446,7 +446,7 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "lib",
     copts = select({
-        "//build/bazel/product_variables:vendor_foo__feature": [
+        "//build/bazel/product_config/config_settings:vendor_foo__feature": [
             "-cflag_feature_2",
             "-cflag_feature_1",
         ],
@@ -527,11 +527,11 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "lib",
     asflags = select({
-        "//build/bazel/product_variables:acme__feature": ["-asflag_bar"],
+        "//build/bazel/product_config/config_settings:acme__feature": ["-asflag_bar"],
         "//conditions:default": ["-asflag_default_bar"],
     }),
     copts = select({
-        "//build/bazel/product_variables:acme__feature": [
+        "//build/bazel/product_config/config_settings:acme__feature": [
             "-cflag_foo",
             "-cflag_bar",
         ],
@@ -546,11 +546,11 @@
 			`cc_library_static(
     name = "lib2",
     asflags = select({
-        "//build/bazel/product_variables:acme__feature": ["-asflag_bar"],
+        "//build/bazel/product_config/config_settings:acme__feature": ["-asflag_bar"],
         "//conditions:default": ["-asflag_default_bar"],
     }),
     copts = select({
-        "//build/bazel/product_variables:acme__feature": [
+        "//build/bazel/product_config/config_settings:acme__feature": [
             "-cflag_bar",
             "-cflag_foo",
         ],
@@ -643,13 +643,13 @@
 		ExpectedBazelTargets: []string{`cc_library_static(
     name = "lib",
     copts = select({
-        "//build/bazel/product_variables:vendor_bar__feature": ["-DVENDOR_BAR_FEATURE"],
+        "//build/bazel/product_config/config_settings:vendor_bar__feature": ["-DVENDOR_BAR_FEATURE"],
         "//conditions:default": ["-DVENDOR_BAR_DEFAULT"],
     }) + select({
-        "//build/bazel/product_variables:vendor_foo__feature": ["-DVENDOR_FOO_FEATURE"],
+        "//build/bazel/product_config/config_settings:vendor_foo__feature": ["-DVENDOR_FOO_FEATURE"],
         "//conditions:default": ["-DVENDOR_FOO_DEFAULT"],
     }) + select({
-        "//build/bazel/product_variables:vendor_qux__feature": ["-DVENDOR_QUX_FEATURE"],
+        "//build/bazel/product_config/config_settings:vendor_qux__feature": ["-DVENDOR_QUX_FEATURE"],
         "//conditions:default": ["-DVENDOR_QUX_DEFAULT"],
     }),
     local_includes = ["."],
@@ -697,7 +697,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("custom", "foo", AttrNameToString{
 				"string_literal_prop": `select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": "29",
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": "29",
         "//conditions:default": "30",
     })`,
 			}),
@@ -779,7 +779,7 @@
 		ExpectedBazelTargets: []string{`cc_binary(
     name = "library_linking_strategy_sample_binary",
     dynamic_deps = select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [],
         "//conditions:default": [
             "//foo/bar:lib_b",
             "//foo/bar:lib_a",
@@ -868,7 +868,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTargetNoRestrictions("cc_binary", "library_linking_strategy_sample_binary", AttrNameToString{
 				"dynamic_deps": `select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [],
         "//conditions:default": [
             "//foo/bar:lib_b",
             "//foo/bar:lib_c",
@@ -877,7 +877,7 @@
 			}),
 			MakeBazelTargetNoRestrictions("cc_binary", "library_linking_strategy_sample_binary_with_excludes", AttrNameToString{
 				"dynamic_deps": `select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [],
         "//conditions:default": ["//foo/bar:lib_b"],
     })`,
 			}),
@@ -965,14 +965,14 @@
 		ExpectedBazelTargets: []string{`cc_binary(
     name = "library_linking_strategy_sample_binary",
     deps = select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [
             "//foo/bar:lib_b_bp2build_cc_library_static",
             "//foo/bar:lib_a_bp2build_cc_library_static",
         ],
         "//conditions:default": [],
     }),
     dynamic_deps = select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [],
         "//conditions:default": [
             "//foo/bar:lib_b",
             "//foo/bar:lib_a",
@@ -1046,14 +1046,14 @@
 		ExpectedBazelTargets: []string{`cc_binary(
     name = "library_linking_strategy_sample_binary",
     deps = select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [
             "//foo/bar:lib_a_bp2build_cc_library_static",
             "//foo/bar:lib_b_bp2build_cc_library_static",
         ],
         "//conditions:default": [],
     }),
     dynamic_deps = select({
-        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//build/bazel/product_config/config_settings:android__library_linking_strategy__prefer_static": [],
         "//conditions:default": [
             "//foo/bar:lib_a",
             "//foo/bar:lib_b",
@@ -1134,13 +1134,13 @@
 		ExpectedBazelTargets: []string{`cc_binary(
     name = "alphabet_binary",
     deps = select({
-        "//build/bazel/product_variables:android__alphabet__a": [],
-        "//build/bazel/product_variables:android__alphabet__b": [],
+        "//build/bazel/product_config/config_settings:android__alphabet__a": [],
+        "//build/bazel/product_config/config_settings:android__alphabet__b": [],
         "//conditions:default": ["//foo/bar:lib_default_bp2build_cc_library_static"],
     }),
     dynamic_deps = select({
-        "//build/bazel/product_variables:android__alphabet__a": ["//foo/bar:lib_a"],
-        "//build/bazel/product_variables:android__alphabet__b": ["//foo/bar:lib_b"],
+        "//build/bazel/product_config/config_settings:android__alphabet__a": ["//foo/bar:lib_a"],
+        "//build/bazel/product_config/config_settings:android__alphabet__b": ["//foo/bar:lib_b"],
         "//conditions:default": [],
     }),
     local_includes = ["."],
@@ -1199,7 +1199,7 @@
     name = "alphabet_binary",
     local_includes = ["."],
     srcs = ["main.cc"],
-    target_compatible_with = ["//build/bazel/product_variables:alphabet_module__special_build"] + select({
+    target_compatible_with = select({
         "//build/bazel/platforms/os_arch:android_x86_64": ["@platforms//:incompatible"],
         "//build/bazel/platforms/os_arch:darwin_arm64": ["@platforms//:incompatible"],
         "//build/bazel/platforms/os_arch:darwin_x86_64": ["@platforms//:incompatible"],
@@ -1208,6 +1208,9 @@
         "//build/bazel/platforms/os_arch:linux_musl_x86_64": ["@platforms//:incompatible"],
         "//build/bazel/platforms/os_arch:windows_x86_64": ["@platforms//:incompatible"],
         "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_config/config_settings:alphabet_module__special_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
     }),
 )`}})
 }
@@ -1252,7 +1255,10 @@
     name = "alphabet_binary",
     local_includes = ["."],
     srcs = ["main.cc"],
-    target_compatible_with = ["//build/bazel/product_variables:alphabet_module__special_build"],
+    target_compatible_with = select({
+        "//build/bazel/product_config/config_settings:alphabet_module__special_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
 )`}})
 }
 
@@ -1389,16 +1395,16 @@
         "//build/bazel/platforms/os:android": ["-DFOO"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:my_namespace__my_bool_variable__android": ["-DBAR"],
-        "//build/bazel/product_variables:my_namespace__my_bool_variable__conditions_default__android": ["-DBAZ"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_bool_variable__android": ["-DBAR"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_bool_variable__conditions_default__android": ["-DBAZ"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:my_namespace__my_string_variable__value1": ["-DVALUE1_NOT_ANDROID"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_string_variable__value1": ["-DVALUE1_NOT_ANDROID"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/product_variables:my_namespace__my_string_variable__conditions_default__android": ["-DSTRING_VAR_CONDITIONS_DEFAULT"],
-        "//build/bazel/product_variables:my_namespace__my_string_variable__value1__android": ["-DVALUE1"],
-        "//build/bazel/product_variables:my_namespace__my_string_variable__value2__android": ["-DVALUE2"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_string_variable__conditions_default__android": ["-DSTRING_VAR_CONDITIONS_DEFAULT"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_string_variable__value1__android": ["-DVALUE1"],
+        "//build/bazel/product_config/config_settings:my_namespace__my_string_variable__value2__android": ["-DVALUE2"],
         "//conditions:default": [],
     }),
     local_includes = ["."],
diff --git a/cc/lto.go b/cc/lto.go
index 44361db..df9ca0a 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -135,10 +135,14 @@
 			ltoLdFlags = append(ltoLdFlags, cachePolicyFormat+policy)
 		}
 
-		// If the module does not have a profile, be conservative and limit cross TU inline
-		// limit to 5 LLVM IR instructions, to balance binary size increase and performance.
-		if !ctx.Darwin() && !ctx.isPgoCompile() && !ctx.isAfdoCompile() {
-			ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=5")
+		// Reduce the inlining threshold for a better balance of binary size and
+		// performance.
+		if !ctx.Darwin() {
+			if ctx.isPgoCompile() || ctx.isAfdoCompile() {
+				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=40")
+			} else {
+				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=5")
+			}
 		}
 
 		flags.Local.CFlags = append(flags.Local.CFlags, ltoCFlags...)
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index f0b7cc5..9281aeb 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -31,9 +31,8 @@
 
 func init() {
 	pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
-	pctx.HostBinToolVariable("abidiff", "abidiff")
-	pctx.HostBinToolVariable("abitidy", "abitidy")
-	pctx.HostBinToolVariable("abidw", "abidw")
+	pctx.HostBinToolVariable("stg", "stg")
+	pctx.HostBinToolVariable("stgdiff", "stgdiff")
 }
 
 var (
@@ -44,28 +43,20 @@
 			CommandDeps: []string{"$ndkStubGenerator"},
 		}, "arch", "apiLevel", "apiMap", "flags")
 
-	abidw = pctx.AndroidStaticRule("abidw",
+	stg = pctx.AndroidStaticRule("stg",
 		blueprint.RuleParams{
-			Command: "$abidw --type-id-style hash --no-corpus-path " +
-				"--no-show-locs --no-comp-dir-path -w $symbolList " +
-				"$in --out-file $out",
-			CommandDeps: []string{"$abidw"},
+			Command:     "$stg -S :$symbolList --elf $in -o $out",
+			CommandDeps: []string{"$stg"},
 		}, "symbolList")
 
-	abitidy = pctx.AndroidStaticRule("abitidy",
-		blueprint.RuleParams{
-			Command:     "$abitidy --all $flags -i $in -o $out",
-			CommandDeps: []string{"$abitidy"},
-		}, "flags")
-
-	abidiff = pctx.AndroidStaticRule("abidiff",
+	stgdiff = pctx.AndroidStaticRule("stgdiff",
 		blueprint.RuleParams{
 			// Need to create *some* output for ninja. We don't want to use tee
 			// because we don't want to spam the build output with "nothing
 			// changed" messages, so redirect output message to $out, and if
 			// changes were detected print the output and fail.
-			Command:     "$abidiff $args $in > $out || (cat $out && false)",
-			CommandDeps: []string{"$abidiff"},
+			Command:     "$stgdiff $args --stg $in -o $out || (cat $out && false)",
+			CommandDeps: []string{"$stgdiff"},
 		}, "args")
 
 	ndkLibrarySuffix = ".ndk"
@@ -107,12 +98,6 @@
 	// https://github.com/android-ndk/ndk/issues/265.
 	Unversioned_until *string
 
-	// If true, does not emit errors when APIs lacking type information are
-	// found. This is false by default and should not be enabled outside bionic,
-	// where it is enabled pending a fix for http://b/190554910 (no debug info
-	// for asm implemented symbols).
-	Allow_untyped_symbols *bool
-
 	// Headers presented by this library to the Public API Surface
 	Export_header_libs []string
 }
@@ -326,7 +311,7 @@
 	apiLevel android.ApiLevel) android.OptionalPath {
 
 	subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(),
-		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml")
+		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.stg")
 	return android.ExistentPathForSource(ctx, subpath)
 }
 
@@ -359,34 +344,17 @@
 
 func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
 	implementationLibrary := this.findImplementationLibrary(ctx)
-	abiRawPath := getNdkAbiDumpInstallBase(ctx).Join(ctx,
-		this.apiLevel.String(), ctx.Arch().ArchType.String(),
-		this.libraryName(ctx), "abi.raw.xml")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        abidw,
-		Description: fmt.Sprintf("abidw %s", implementationLibrary),
-		Input:       implementationLibrary,
-		Output:      abiRawPath,
-		Implicit:    symbolList,
-		Args: map[string]string{
-			"symbolList": symbolList.String(),
-		},
-	})
-
 	this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx,
 		this.apiLevel.String(), ctx.Arch().ArchType.String(),
-		this.libraryName(ctx), "abi.xml")
-	untypedFlag := "--abort-on-untyped-symbols"
-	if proptools.BoolDefault(this.properties.Allow_untyped_symbols, false) {
-		untypedFlag = ""
-	}
+		this.libraryName(ctx), "abi.stg")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        abitidy,
-		Description: fmt.Sprintf("abitidy %s", implementationLibrary),
-		Input:       abiRawPath,
+		Rule:        stg,
+		Description: fmt.Sprintf("stg %s", implementationLibrary),
+		Input:       implementationLibrary,
+		Implicit:    symbolList,
 		Output:      this.abiDumpPath,
 		Args: map[string]string{
-			"flags": untypedFlag,
+			"symbolList": symbolList.String(),
 		},
 	})
 }
@@ -405,7 +373,7 @@
 func (this *stubDecorator) diffAbi(ctx ModuleContext) {
 	// Catch any ABI changes compared to the checked-in definition of this API
 	// level.
-	abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp")
+	abiDiffPath := android.PathForModuleOut(ctx, "stgdiff.timestamp")
 	prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel)
 	missingPrebuiltError := fmt.Sprintf(
 		"Did not find prebuilt ABI dump for %q (%q). Generate with "+
@@ -421,11 +389,14 @@
 		})
 	} else {
 		ctx.Build(pctx, android.BuildParams{
-			Rule: abidiff,
-			Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump,
+			Rule: stgdiff,
+			Description: fmt.Sprintf("Comparing ABI %s %s", prebuiltAbiDump,
 				this.abiDumpPath),
 			Output: abiDiffPath,
 			Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath},
+			Args: map[string]string{
+				"args": "--format=small",
+			},
 		})
 	}
 	this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath)
@@ -452,13 +423,13 @@
 			})
 		} else {
 			ctx.Build(pctx, android.BuildParams{
-				Rule: abidiff,
+				Rule: stgdiff,
 				Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath,
 					nextAbiDump),
 				Output: nextAbiDiffPath,
 				Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()},
 				Args: map[string]string{
-					"args": "--no-added-syms",
+					"args": "--format=small --ignore=interface_addition",
 				},
 			})
 		}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index c1ef970..30bce9b 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -87,6 +87,8 @@
 
 	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
 	deviceOnlySanitizeFlags = []string{"-fsanitize-trap=all", "-ftrap-function=abort"}
+
+	noSanitizeLinkRuntimeFlag = "-fno-sanitize-link-runtime"
 )
 
 type SanitizerType int
@@ -407,6 +409,8 @@
 	exportedVars.ExportStringListStaticVariable("HostOnlySanitizeFlags", hostOnlySanitizeFlags)
 	exportedVars.ExportStringList("DeviceOnlySanitizeFlags", deviceOnlySanitizeFlags)
 
+	exportedVars.ExportStringList("MinimalRuntimeFlags", minimalRuntimeFlags)
+
 	// Leave out "-flto" from the slices exported to bazel, as we will use the
 	// dedicated LTO feature for this. For C Flags and Linker Flags, also leave
 	// out the cross DSO flag which will be added separately under the correct conditions.
@@ -422,6 +426,8 @@
 	exportedVars.ExportString("CfiExportsMapFilename", cfiExportsMapFilename)
 	exportedVars.ExportString("CfiAssemblySupportFlag", cfiAssemblySupportFlag)
 
+	exportedVars.ExportString("NoSanitizeLinkRuntimeFlag", noSanitizeLinkRuntimeFlag)
+
 	android.RegisterMakeVarsProvider(pctx, cfiMakeVarsProvider)
 	android.RegisterMakeVarsProvider(pctx, hwasanMakeVarsProvider)
 }
@@ -923,7 +929,7 @@
 			// Bionic and musl sanitizer runtimes have already been added as dependencies so that
 			// the right variant of the runtime will be used (with the "-android" or "-musl"
 			// suffixes), so don't let clang the runtime library.
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-link-runtime")
+			flags.Local.LdFlags = append(flags.Local.LdFlags, noSanitizeLinkRuntimeFlag)
 		} else {
 			// Host sanitizers only link symbols in the final executable, so
 			// there will always be undefined symbols in intermediate libraries.
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 5ea84bc..62b3333 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -121,6 +121,10 @@
 	defer ctx.EventHandler.End("mixed_build")
 
 	bazelHook := func() error {
+		err := ctx.Config().BazelContext.QueueBazelSandwichCqueryRequests(ctx.Config())
+		if err != nil {
+			return err
+		}
 		return ctx.Config().BazelContext.InvokeBazel(ctx.Config(), ctx)
 	}
 	ctx.SetBeforePrepareBuildActionsHook(bazelHook)
diff --git a/java/aapt2.go b/java/aapt2.go
index 7845a0b..4cff8a7 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -150,7 +150,8 @@
 			`${config.Aapt2Cmd} link -o $out $flags --java $genDir --proguard $proguardOptions ` +
 			`--output-text-symbols ${rTxt} $inFlags && ` +
 			`${config.SoongZipCmd} -write_if_changed -jar -o $genJar -C $genDir -D $genDir &&` +
-			`${config.ExtractJarPackagesCmd} -i $genJar -o $extraPackages --prefix '--extra-packages '`,
+			`${config.ExtractJarPackagesCmd} -i $genJar -o $extraPackages --prefix '--extra-packages ' && ` +
+			`rm -rf $genDir`,
 
 		CommandDeps: []string{
 			"${config.Aapt2Cmd}",
diff --git a/python/bp2build.go b/python/bp2build.go
index cd3f2a1..60cabc4 100644
--- a/python/bp2build.go
+++ b/python/bp2build.go
@@ -223,7 +223,8 @@
 
 	props := bazel.BazelTargetModuleProperties{
 		// Use the native py_binary rule.
-		Rule_class: "py_test",
+		Rule_class:        "py_test",
+		Bzl_load_location: "//build/bazel/rules/python:py_test.bzl",
 	}
 
 	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
diff --git a/starlark_fmt/format.go b/starlark_fmt/format.go
index 4209507..0224bcf 100644
--- a/starlark_fmt/format.go
+++ b/starlark_fmt/format.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"reflect"
 	"sort"
 	"strconv"
 	"strings"
@@ -33,6 +34,72 @@
 	return strings.Repeat(" ", level*indent)
 }
 
+func PrintAny(value any, indentLevel int) string {
+	return printAnyRecursive(reflect.ValueOf(value), indentLevel)
+}
+
+func printAnyRecursive(value reflect.Value, indentLevel int) string {
+	switch value.Type().Kind() {
+	case reflect.String:
+		val := value.String()
+		if strings.Contains(val, "\"") || strings.Contains(val, "\n") {
+			return `'''` + val + `'''`
+		}
+		return `"` + val + `"`
+	case reflect.Bool:
+		if value.Bool() {
+			return "True"
+		} else {
+			return "False"
+		}
+	case reflect.Int:
+		return fmt.Sprintf("%d", value.Int())
+	case reflect.Slice:
+		if value.Len() == 0 {
+			return "[]"
+		} else if value.Len() == 1 {
+			return "[" + printAnyRecursive(value.Index(0), indentLevel) + "]"
+		}
+		list := make([]string, 0, value.Len()+2)
+		list = append(list, "[")
+		innerIndent := Indention(indentLevel + 1)
+		for i := 0; i < value.Len(); i++ {
+			list = append(list, innerIndent+printAnyRecursive(value.Index(i), indentLevel+1)+`,`)
+		}
+		list = append(list, Indention(indentLevel)+"]")
+		return strings.Join(list, "\n")
+	case reflect.Map:
+		if value.Len() == 0 {
+			return "{}"
+		}
+		items := make([]string, 0, value.Len())
+		for _, key := range value.MapKeys() {
+			items = append(items, fmt.Sprintf(`%s%s: %s,`, Indention(indentLevel+1), printAnyRecursive(key, indentLevel+1), printAnyRecursive(value.MapIndex(key), indentLevel+1)))
+		}
+		sort.Strings(items)
+		return fmt.Sprintf(`{
+%s
+%s}`, strings.Join(items, "\n"), Indention(indentLevel))
+	case reflect.Struct:
+		if value.NumField() == 0 {
+			return "struct()"
+		}
+		items := make([]string, 0, value.NumField()+2)
+		items = append(items, "struct(")
+		for i := 0; i < value.NumField(); i++ {
+			if value.Type().Field(i).Anonymous {
+				panic("anonymous fields aren't supported")
+			}
+			name := value.Type().Field(i).Name
+			items = append(items, fmt.Sprintf(`%s%s = %s,`, Indention(indentLevel+1), name, printAnyRecursive(value.Field(i), indentLevel+1)))
+		}
+		items = append(items, Indention(indentLevel)+")")
+		return strings.Join(items, "\n")
+	default:
+		panic("Unhandled kind: " + value.Kind().String())
+	}
+}
+
 // PrintBool returns a Starlark compatible bool string.
 func PrintBool(item bool) string {
 	if item {
diff --git a/tests/lib.sh b/tests/lib.sh
index 40b317b..f337c74 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -52,6 +52,10 @@
   cp -R "$REAL_TOP/$dir" "$MOCK_TOP/$parent"
 }
 
+function delete_directory {
+  rm -rf "$MOCK_TOP/$1"
+}
+
 function symlink_file {
   local file="$1"
 
@@ -138,6 +142,9 @@
   copy_directory build/bazel
   copy_directory build/bazel_common_rules
 
+  # This requires pulling more tools into the mock top to build partitions
+  delete_directory build/bazel/examples/partitions
+
   symlink_directory packages/modules/common/build
   symlink_directory prebuilts/bazel
   symlink_directory prebuilts/clang
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 48f654e..6b9ff8b 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -22,7 +22,5 @@
 "$TOP/build/soong/tests/apex_cc_module_arch_variant_tests.sh" "aosp_arm" "armv7-a"
 "$TOP/build/soong/tests/apex_cc_module_arch_variant_tests.sh" "aosp_cf_arm64_phone" "armv8-a" "cortex-a53"
 
-"$TOP/build/soong/tests/sbom_test.sh"
-
 "$TOP/build/bazel/ci/b_test.sh"
 
diff --git a/tests/sbom_test.sh b/tests/sbom_test.sh
index afec6b1..2534b20 100755
--- a/tests/sbom_test.sh
+++ b/tests/sbom_test.sh
@@ -238,10 +238,45 @@
     diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name"
   done
 
+  verify_package_verification_code "$product_out/sbom.spdx"
+
   # Teardown
   cleanup "${out_dir}"
 }
 
+function verify_package_verification_code {
+  local sbom_file="$1"; shift
+
+  local -a file_checksums
+  local package_product_found=
+  while read -r line;
+  do
+    if grep -q 'PackageVerificationCode' <<<"$line"
+    then
+      package_product_found=true
+    fi
+    if [ -n "$package_product_found" ]
+    then
+      if grep -q 'FileChecksum' <<< "$line"
+      then
+        checksum=$(echo $line | sed 's/^.*: //')
+        file_checksums+=("$checksum")
+      fi
+    fi
+  done <<< "$(grep -E 'PackageVerificationCode|FileChecksum' $sbom_file)"
+  IFS=$'\n' file_checksums=($(sort <<<"${file_checksums[*]}")); unset IFS
+  IFS= expected_package_verification_code=$(printf "${file_checksums[*]}" | sha1sum | sed 's/[[:space:]]*-//'); unset IFS
+
+  actual_package_verification_code=$(grep PackageVerificationCode $sbom_file | sed 's/PackageVerificationCode: //g')
+  if [ $actual_package_verification_code = $expected_package_verification_code ]
+  then
+    echo "Package verification code is correct."
+  else
+    echo "Unexpected package verification code."
+    exit 1
+  fi
+}
+
 function test_sbom_unbundled_apex {
   # Setup
   out_dir="$(setup)"