Merge "Revert "Enforce that output files are created in primary ninja execution"" into main
diff --git a/Android.bp b/Android.bp
index 2c0ef47..682711d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -122,12 +122,15 @@
 }
 
 // buildinfo.prop contains common properties for system/build.prop, like ro.build.version.*
+// TODO(b/322090587): merge this to gen_build_prop.py script.
 buildinfo_prop {
     name: "buildinfo.prop",
 
     // not installable because this will be included to system/build.prop
     installable: false,
 
+    product_config: ":product_config",
+
     // Currently, only microdroid can refer to buildinfo.prop
     visibility: ["//packages/modules/Virtualization/microdroid"],
 }
@@ -136,3 +139,8 @@
 all_apex_contributions {
     name: "all_apex_contributions",
 }
+
+product_config {
+    name: "product_config",
+    visibility: ["//device/google/cuttlefish/system_image"],
+}
diff --git a/README.md b/README.md
index 140822b..df428c2 100644
--- a/README.md
+++ b/README.md
@@ -603,10 +603,10 @@
 ## Build logic
 
 The build logic is written in Go using the
-[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
-logic receives module definitions parsed into Go structures using reflection
-and produces build rules.  The build rules are collected by blueprint and
-written to a [ninja](http://ninja-build.org) build file.
+[blueprint](https://android.googlesource.com/platform/build/blueprint)
+framework.  Build logic receives module definitions parsed into Go structures
+using reflection and produces build rules.  The build rules are collected by
+blueprint and written to a [ninja](http://ninja-build.org) build file.
 
 ## Environment Variables Config File
 
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index dac0ae3..9e3d291 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -15,6 +15,8 @@
 package aconfig
 
 import (
+	"path/filepath"
+	"slices"
 	"strings"
 
 	"android/soong/android"
@@ -22,9 +24,15 @@
 	"github.com/google/blueprint"
 )
 
+type AconfigReleaseConfigValue struct {
+	ReleaseConfig string
+	Values        []string `blueprint:"mutated"`
+}
+
 type DeclarationsModule struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	blueprint.IncrementalModule
 
 	// Properties for "aconfig_declarations"
 	properties struct {
@@ -34,8 +42,10 @@
 		// Release config flag package
 		Package string
 
-		// Values from TARGET_RELEASE / RELEASE_ACONFIG_VALUE_SETS
-		Values []string `blueprint:"mutated"`
+		// Values for release configs / RELEASE_ACONFIG_VALUE_SETS
+		// The current release config is `ReleaseConfig: ""`, others
+		// are from RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS.
+		ReleaseConfigValues []AconfigReleaseConfigValue
 
 		// Container(system/vendor/apex) that this module belongs to
 		Container string
@@ -57,6 +67,10 @@
 
 type implicitValuesTagType struct {
 	blueprint.BaseDependencyTag
+
+	// The release config name for these values.
+	// Empty string for the actual current release config.
+	ReleaseConfig string
 }
 
 var implicitValuesTag = implicitValuesTagType{}
@@ -81,6 +95,11 @@
 	if len(valuesFromConfig) > 0 {
 		ctx.AddDependency(ctx.Module(), implicitValuesTag, valuesFromConfig...)
 	}
+	for rcName, valueSets := range ctx.Config().ReleaseAconfigExtraReleaseConfigsValueSets() {
+		if len(valueSets) > 0 {
+			ctx.AddDependency(ctx.Module(), implicitValuesTagType{ReleaseConfig: rcName}, valueSets...)
+		}
+	}
 }
 
 func joinAndPrefix(prefix string, values []string) string {
@@ -101,59 +120,115 @@
 	return sb.String()
 }
 
+// Assemble the actual filename.
+// If `rcName` is not empty, then insert "-{rcName}" into the path before the
+// file extension.
+func assembleFileName(rcName, path string) string {
+	if rcName == "" {
+		return path
+	}
+	dir, file := filepath.Split(path)
+	rcName = "-" + rcName
+	ext := filepath.Ext(file)
+	base := file[:len(file)-len(ext)]
+	return dir + base + rcName + ext
+}
+
 func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
-	valuesFiles := make([]android.Path, 0)
+	// Determine which release configs we are processing.
+	//
+	// We always process the current release config (empty string).
+	// We may have been told to also create artifacts for some others.
+	configs := append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...)
+	slices.Sort(configs)
+
+	values := make(map[string][]string)
+	valuesFiles := make(map[string][]android.Path, 0)
+	providerData := android.AconfigReleaseDeclarationsProviderData{}
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		if depData, ok := android.OtherModuleProvider(ctx, dep, valueSetProviderKey); ok {
-			paths, ok := depData.AvailablePackages[module.properties.Package]
-			if ok {
-				valuesFiles = append(valuesFiles, paths...)
-				for _, path := range paths {
-					module.properties.Values = append(module.properties.Values, path.String())
+			depTag := ctx.OtherModuleDependencyTag(dep)
+			for _, config := range configs {
+				tag := implicitValuesTagType{ReleaseConfig: config}
+				if depTag == tag {
+					paths, ok := depData.AvailablePackages[module.properties.Package]
+					if ok {
+						valuesFiles[config] = append(valuesFiles[config], paths...)
+						for _, path := range paths {
+							values[config] = append(values[config], path.String())
+						}
+					}
 				}
 			}
 		}
 	})
+	for _, config := range configs {
+		module.properties.ReleaseConfigValues = append(module.properties.ReleaseConfigValues, AconfigReleaseConfigValue{
+			ReleaseConfig: config,
+			Values:        values[config],
+		})
 
-	// Intermediate format
-	declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
-	intermediateCacheFilePath := android.PathForModuleOut(ctx, "intermediate.pb")
-	defaultPermission := ctx.Config().ReleaseAconfigFlagDefaultPermission()
-	inputFiles := make([]android.Path, len(declarationFiles))
-	copy(inputFiles, declarationFiles)
-	inputFiles = append(inputFiles, valuesFiles...)
-	args := map[string]string{
-		"release_version":    ctx.Config().ReleaseVersion(),
-		"package":            module.properties.Package,
-		"declarations":       android.JoinPathsWithPrefix(declarationFiles, "--declarations "),
-		"values":             joinAndPrefix(" --values ", module.properties.Values),
-		"default-permission": optionalVariable(" --default-permission ", defaultPermission),
+		// Intermediate format
+		declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
+		intermediateCacheFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.pb"))
+		var defaultPermission string
+		defaultPermission = ctx.Config().ReleaseAconfigFlagDefaultPermission()
+		if config != "" {
+			if confPerm, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_" + config); ok {
+				defaultPermission = confPerm
+			}
+		}
+		inputFiles := make([]android.Path, len(declarationFiles))
+		copy(inputFiles, declarationFiles)
+		inputFiles = append(inputFiles, valuesFiles[config]...)
+		args := map[string]string{
+			"release_version":    ctx.Config().ReleaseVersion(),
+			"package":            module.properties.Package,
+			"declarations":       android.JoinPathsWithPrefix(declarationFiles, "--declarations "),
+			"values":             joinAndPrefix(" --values ", values[config]),
+			"default-permission": optionalVariable(" --default-permission ", defaultPermission),
+		}
+		if len(module.properties.Container) > 0 {
+			args["container"] = "--container " + module.properties.Container
+		}
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        aconfigRule,
+			Output:      intermediateCacheFilePath,
+			Inputs:      inputFiles,
+			Description: "aconfig_declarations",
+			Args:        args,
+		})
+
+		intermediateDumpFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.txt"))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        aconfigTextRule,
+			Output:      intermediateDumpFilePath,
+			Inputs:      android.Paths{intermediateCacheFilePath},
+			Description: "aconfig_text",
+		})
+
+		providerData[config] = android.AconfigDeclarationsProviderData{
+			Package:                     module.properties.Package,
+			Container:                   module.properties.Container,
+			Exportable:                  module.properties.Exportable,
+			IntermediateCacheOutputPath: intermediateCacheFilePath,
+			IntermediateDumpOutputPath:  intermediateDumpFilePath,
+		}
 	}
-	if len(module.properties.Container) > 0 {
-		args["container"] = "--container " + module.properties.Container
-	}
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        aconfigRule,
-		Output:      intermediateCacheFilePath,
-		Inputs:      inputFiles,
-		Description: "aconfig_declarations",
-		Args:        args,
-	})
-
-	intermediateDumpFilePath := android.PathForModuleOut(ctx, "intermediate.txt")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        aconfigTextRule,
-		Output:      intermediateDumpFilePath,
-		Inputs:      android.Paths{intermediateCacheFilePath},
-		Description: "aconfig_text",
-	})
-
-	android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, android.AconfigDeclarationsProviderData{
-		Package:                     module.properties.Package,
-		Container:                   module.properties.Container,
-		Exportable:                  module.properties.Exportable,
-		IntermediateCacheOutputPath: intermediateCacheFilePath,
-		IntermediateDumpOutputPath:  intermediateDumpFilePath,
-	})
+	android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, providerData[""])
+	android.SetProvider(ctx, android.AconfigReleaseDeclarationsProviderKey, providerData)
 }
+
+func (module *DeclarationsModule) BuildActionProviderKeys() []blueprint.AnyProviderKey {
+	return []blueprint.AnyProviderKey{android.AconfigDeclarationsProviderKey}
+}
+
+func (module *DeclarationsModule) PackageContextPath() string {
+	return pkgPath
+}
+
+func (module *DeclarationsModule) CachedRules() []blueprint.Rule {
+	return []blueprint.Rule{aconfigRule, aconfigTextRule}
+}
+
+var _ blueprint.Incremental = &DeclarationsModule{}
diff --git a/aconfig/aconfig_declarations_test.go b/aconfig/aconfig_declarations_test.go
index c37274c..5483295 100644
--- a/aconfig/aconfig_declarations_test.go
+++ b/aconfig/aconfig_declarations_test.go
@@ -15,6 +15,7 @@
 package aconfig
 
 import (
+	"slices"
 	"strings"
 	"testing"
 
@@ -134,3 +135,95 @@
 		})
 	}
 }
+
+func TestAssembleFileName(t *testing.T) {
+	testCases := []struct {
+		name          string
+		releaseConfig string
+		path          string
+		expectedValue string
+	}{
+		{
+			name:          "active release config",
+			path:          "file.path",
+			expectedValue: "file.path",
+		},
+		{
+			name:          "release config FOO",
+			releaseConfig: "FOO",
+			path:          "file.path",
+			expectedValue: "file-FOO.path",
+		},
+	}
+	for _, test := range testCases {
+		actualValue := assembleFileName(test.releaseConfig, test.path)
+		if actualValue != test.expectedValue {
+			t.Errorf("Expected %q found %q", test.expectedValue, actualValue)
+		}
+	}
+}
+
+func TestGenerateAndroidBuildActions(t *testing.T) {
+	testCases := []struct {
+		name         string
+		buildFlags   map[string]string
+		bp           string
+		errorHandler android.FixtureErrorHandler
+	}{
+		{
+			name: "generate extra",
+			buildFlags: map[string]string{
+				"RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": "config2",
+				"RELEASE_ACONFIG_VALUE_SETS":            "aconfig_value_set-config1",
+				"RELEASE_ACONFIG_VALUE_SETS_config2":    "aconfig_value_set-config2",
+			},
+			bp: `
+				aconfig_declarations {
+					name: "module_name",
+					package: "com.example.package",
+					container: "com.android.foo",
+					srcs: [
+						"foo.aconfig",
+						"bar.aconfig",
+					],
+				}
+				aconfig_value_set {
+					name: "aconfig_value_set-config1",
+					values: []
+				}
+				aconfig_value_set {
+					name: "aconfig_value_set-config2",
+					values: []
+				}
+			`,
+		},
+	}
+	for _, test := range testCases {
+		fixture := PrepareForTest(t, addBuildFlagsForTest(test.buildFlags))
+		if test.errorHandler != nil {
+			fixture = fixture.ExtendWithErrorHandler(test.errorHandler)
+		}
+		result := fixture.RunTestWithBp(t, test.bp)
+		module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+		depData, _ := android.SingletonModuleProvider(result, module, android.AconfigReleaseDeclarationsProviderKey)
+		expectedKeys := []string{""}
+		for _, rc := range strings.Split(test.buildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"], " ") {
+			expectedKeys = append(expectedKeys, rc)
+		}
+		slices.Sort(expectedKeys)
+		actualKeys := []string{}
+		for rc := range depData {
+			actualKeys = append(actualKeys, rc)
+		}
+		slices.Sort(actualKeys)
+		android.AssertStringEquals(t, "provider keys", strings.Join(expectedKeys, " "), strings.Join(actualKeys, " "))
+		for _, rc := range actualKeys {
+			if !strings.HasSuffix(depData[rc].IntermediateCacheOutputPath.String(), assembleFileName(rc, "/intermediate.pb")) {
+				t.Errorf("Incorrect intermediates proto path in provider for release config %s: %s", rc, depData[rc].IntermediateCacheOutputPath.String())
+			}
+			if !strings.HasSuffix(depData[rc].IntermediateDumpOutputPath.String(), assembleFileName(rc, "/intermediate.txt")) {
+				t.Errorf("Incorrect intermediates text path in provider for release config %s: %s", rc, depData[rc].IntermediateDumpOutputPath.String())
+			}
+		}
+	}
+}
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index e771d05..0437c26 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"fmt"
+	"slices"
 )
 
 // A singleton module that collects all of the aconfig flags declared in the
@@ -27,70 +28,90 @@
 // ones that are relevant to the product currently being built, so that that infra
 // doesn't need to pull from multiple builds and merge them.
 func AllAconfigDeclarationsFactory() android.Singleton {
-	return &allAconfigDeclarationsSingleton{}
+	return &allAconfigDeclarationsSingleton{releaseMap: make(map[string]allAconfigReleaseDeclarationsSingleton)}
 }
 
-type allAconfigDeclarationsSingleton struct {
+type allAconfigReleaseDeclarationsSingleton struct {
 	intermediateBinaryProtoPath android.OutputPath
 	intermediateTextProtoPath   android.OutputPath
 }
 
+type allAconfigDeclarationsSingleton struct {
+	releaseMap map[string]allAconfigReleaseDeclarationsSingleton
+}
+
+func (this *allAconfigDeclarationsSingleton) sortedConfigNames() []string {
+	var names []string
+	for k := range this.releaseMap {
+		names = append(names, k)
+	}
+	slices.Sort(names)
+	return names
+}
+
 func (this *allAconfigDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	// Find all of the aconfig_declarations modules
-	var packages = make(map[string]int)
-	var cacheFiles android.Paths
-	ctx.VisitAllModules(func(module android.Module) {
-		decl, ok := android.SingletonModuleProvider(ctx, module, android.AconfigDeclarationsProviderKey)
-		if !ok {
-			return
+	for _, rcName := range append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...) {
+		// Find all of the aconfig_declarations modules
+		var packages = make(map[string]int)
+		var cacheFiles android.Paths
+		ctx.VisitAllModules(func(module android.Module) {
+			decl, ok := android.SingletonModuleProvider(ctx, module, android.AconfigReleaseDeclarationsProviderKey)
+			if !ok {
+				return
+			}
+			cacheFiles = append(cacheFiles, decl[rcName].IntermediateCacheOutputPath)
+			packages[decl[rcName].Package]++
+		})
+
+		var numOffendingPkg = 0
+		for pkg, cnt := range packages {
+			if cnt > 1 {
+				fmt.Printf("%d aconfig_declarations found for package %s\n", cnt, pkg)
+				numOffendingPkg++
+			}
 		}
-		cacheFiles = append(cacheFiles, decl.IntermediateCacheOutputPath)
-		packages[decl.Package]++
-	})
 
-	var numOffendingPkg = 0
-	for pkg, cnt := range packages {
-		if cnt > 1 {
-			fmt.Printf("%d aconfig_declarations found for package %s\n", cnt, pkg)
-			numOffendingPkg++
+		if numOffendingPkg > 0 {
+			panic(fmt.Errorf("Only one aconfig_declarations allowed for each package."))
 		}
+
+		// Generate build action for aconfig (binary proto output)
+		paths := allAconfigReleaseDeclarationsSingleton{
+			intermediateBinaryProtoPath: android.PathForIntermediates(ctx, assembleFileName(rcName, "all_aconfig_declarations.pb")),
+			intermediateTextProtoPath:   android.PathForIntermediates(ctx, assembleFileName(rcName, "all_aconfig_declarations.textproto")),
+		}
+		this.releaseMap[rcName] = paths
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        AllDeclarationsRule,
+			Inputs:      cacheFiles,
+			Output:      this.releaseMap[rcName].intermediateBinaryProtoPath,
+			Description: "all_aconfig_declarations",
+			Args: map[string]string{
+				"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
+			},
+		})
+		ctx.Phony("all_aconfig_declarations", this.releaseMap[rcName].intermediateBinaryProtoPath)
+
+		// Generate build action for aconfig (text proto output)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        AllDeclarationsRuleTextProto,
+			Inputs:      cacheFiles,
+			Output:      this.releaseMap[rcName].intermediateTextProtoPath,
+			Description: "all_aconfig_declarations_textproto",
+			Args: map[string]string{
+				"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
+			},
+		})
+		ctx.Phony("all_aconfig_declarations_textproto", this.releaseMap[rcName].intermediateTextProtoPath)
 	}
-
-	if numOffendingPkg > 0 {
-		panic(fmt.Errorf("Only one aconfig_declarations allowed for each package."))
-	}
-
-	// Generate build action for aconfig (binary proto output)
-	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        AllDeclarationsRule,
-		Inputs:      cacheFiles,
-		Output:      this.intermediateBinaryProtoPath,
-		Description: "all_aconfig_declarations",
-		Args: map[string]string{
-			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
-		},
-	})
-	ctx.Phony("all_aconfig_declarations", this.intermediateBinaryProtoPath)
-
-	// Generate build action for aconfig (text proto output)
-	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.textproto")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        AllDeclarationsRuleTextProto,
-		Inputs:      cacheFiles,
-		Output:      this.intermediateTextProtoPath,
-		Description: "all_aconfig_declarations_textproto",
-		Args: map[string]string{
-			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
-		},
-	})
-	ctx.Phony("all_aconfig_declarations_textproto", this.intermediateTextProtoPath)
 }
 
 func (this *allAconfigDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
-	for _, goal := range []string{"docs", "droid", "sdk"} {
-		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "flags.pb")
-		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "flags.textproto")
+	for _, rcName := range this.sortedConfigNames() {
+		ctx.DistForGoal("droid", this.releaseMap[rcName].intermediateBinaryProtoPath)
+		for _, goal := range []string{"docs", "droid", "sdk"} {
+			ctx.DistForGoalWithFilename(goal, this.releaseMap[rcName].intermediateBinaryProtoPath, assembleFileName(rcName, "flags.pb"))
+			ctx.DistForGoalWithFilename(goal, this.releaseMap[rcName].intermediateTextProtoPath, assembleFileName(rcName, "flags.textproto"))
+		}
 	}
 }
diff --git a/aconfig/codegen/Android.bp b/aconfig/codegen/Android.bp
index 0c78b94..5fac0a8 100644
--- a/aconfig/codegen/Android.bp
+++ b/aconfig/codegen/Android.bp
@@ -12,7 +12,6 @@
         "soong",
         "soong-aconfig",
         "soong-android",
-        "soong-bazel",
         "soong-java",
         "soong-rust",
     ],
diff --git a/aconfig/init.go b/aconfig/init.go
index 4655467..256b213 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -15,13 +15,16 @@
 package aconfig
 
 import (
+	"encoding/gob"
+
 	"android/soong/android"
 
 	"github.com/google/blueprint"
 )
 
 var (
-	pctx = android.NewPackageContext("android/soong/aconfig")
+	pkgPath = "android/soong/aconfig"
+	pctx    = android.NewPackageContext(pkgPath)
 
 	// For aconfig_declarations: Generate cache file
 	aconfigRule = pctx.AndroidStaticRule("aconfig",
@@ -106,6 +109,9 @@
 	RegisterBuildComponents(android.InitRegistrationContext)
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("soong_zip", "soong_zip")
+
+	gob.Register(android.AconfigDeclarationsProviderData{})
+	gob.Register(android.ModuleOutPath{})
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
diff --git a/aconfig/testing.go b/aconfig/testing.go
index f6489ec..4ceb6b3 100644
--- a/aconfig/testing.go
+++ b/aconfig/testing.go
@@ -23,7 +23,25 @@
 var PrepareForTestWithAconfigBuildComponents = android.FixtureRegisterWithContext(RegisterBuildComponents)
 
 func runTest(t *testing.T, errorHandler android.FixtureErrorHandler, bp string) *android.TestResult {
-	return android.GroupFixturePreparers(PrepareForTestWithAconfigBuildComponents).
+	return PrepareForTest(t).
 		ExtendWithErrorHandler(errorHandler).
 		RunTestWithBp(t, bp)
 }
+
+func PrepareForTest(t *testing.T, preparers ...android.FixturePreparer) android.FixturePreparer {
+	preparers = append([]android.FixturePreparer{PrepareForTestWithAconfigBuildComponents}, preparers...)
+	return android.GroupFixturePreparers(preparers...)
+}
+
+func addBuildFlagsForTest(buildFlags map[string]string) android.FixturePreparer {
+	return android.GroupFixturePreparers(
+		android.FixtureModifyProductVariables(func(vars android.FixtureProductVariables) {
+			if vars.BuildFlags == nil {
+				vars.BuildFlags = make(map[string]string)
+			}
+			for k, v := range buildFlags {
+				vars.BuildFlags[k] = v
+			}
+		}),
+	)
+}
diff --git a/android/Android.bp b/android/Android.bp
index 9ce8cdc..985ffa9 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -38,7 +38,9 @@
         "arch_list.go",
         "arch_module_context.go",
         "base_module_context.go",
+        "build_prop.go",
         "buildinfo_prop.go",
+        "compliance_metadata.go",
         "config.go",
         "test_config.go",
         "configurable_properties.go",
@@ -83,6 +85,7 @@
         "plugin.go",
         "prebuilt.go",
         "prebuilt_build_tool.go",
+        "product_config.go",
         "proto.go",
         "provider.go",
         "raw_files.go",
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index ee9891d..a47e80f 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -43,6 +43,10 @@
 
 var AconfigDeclarationsProviderKey = blueprint.NewProvider[AconfigDeclarationsProviderData]()
 
+type AconfigReleaseDeclarationsProviderData map[string]AconfigDeclarationsProviderData
+
+var AconfigReleaseDeclarationsProviderKey = blueprint.NewProvider[AconfigReleaseDeclarationsProviderData]()
+
 type ModeInfo struct {
 	Container string
 	Mode      string
@@ -112,6 +116,8 @@
 		if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
 			mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
 		}
+		// If we were generating on-device artifacts for other release configs, we would need to add code here to propagate
+		// those artifacts as well.  See also b/298444886.
 		if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
 			for container, v := range dep.AconfigFiles {
 				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
diff --git a/android/androidmk.go b/android/androidmk.go
index 66f42f9..9699ce5 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -499,6 +499,7 @@
 	Config() Config
 	moduleProvider(module blueprint.Module, provider blueprint.AnyProviderKey) (any, bool)
 	ModuleType(module blueprint.Module) string
+	OtherModulePropertyErrorf(module Module, property string, fmt string, args ...interface{})
 }
 
 func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
@@ -514,7 +515,7 @@
 	if a.Include == "" {
 		a.Include = "$(BUILD_PREBUILT)"
 	}
-	a.Required = append(a.Required, amod.RequiredModuleNames()...)
+	a.Required = append(a.Required, amod.RequiredModuleNames(ctx)...)
 	a.Host_required = append(a.Host_required, amod.HostRequiredModuleNames()...)
 	a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
 
diff --git a/android/apex.go b/android/apex.go
index 2ac6ed0..683e501 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -84,6 +84,9 @@
 
 	// Returns the name of the test apexes that this module is included in.
 	TestApexes []string
+
+	// Returns the name of the overridden apex (com.android.foo)
+	BaseApexName string
 }
 
 // AllApexInfo holds the ApexInfo of all apexes that include this module.
diff --git a/android/build_prop.go b/android/build_prop.go
new file mode 100644
index 0000000..45c17c3
--- /dev/null
+++ b/android/build_prop.go
@@ -0,0 +1,125 @@
+// Copyright 2024 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 (
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	ctx := InitRegistrationContext
+	ctx.RegisterModuleType("build_prop", buildPropFactory)
+}
+
+type buildPropProperties struct {
+	// Output file name. Defaults to "build.prop"
+	Stem *string
+
+	// List of prop names to exclude. This affects not only common build properties but also
+	// properties in prop_files.
+	Block_list []string
+
+	// Path to the input prop files. The contents of the files are directly
+	// emitted to the output
+	Prop_files []string `android:"path"`
+
+	// Files to be appended at the end of build.prop. These files are appended after
+	// post_process_props without any further checking.
+	Footer_files []string `android:"path"`
+
+	// Path to a JSON file containing product configs.
+	Product_config *string `android:"path"`
+}
+
+type buildPropModule struct {
+	ModuleBase
+
+	properties buildPropProperties
+
+	outputFilePath OutputPath
+	installPath    InstallPath
+}
+
+func (p *buildPropModule) stem() string {
+	return proptools.StringDefault(p.properties.Stem, "build.prop")
+}
+
+func (p *buildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
+	if !ctx.Config().KatiEnabled() {
+		WriteFileRule(ctx, p.outputFilePath, "# no build.prop if kati is disabled")
+		return
+	}
+
+	partition := p.PartitionTag(ctx.DeviceConfig())
+	if partition != "system" {
+		ctx.PropertyErrorf("partition", "unsupported partition %q: only \"system\" is supported", partition)
+		return
+	}
+
+	rule := NewRuleBuilder(pctx, ctx)
+
+	config := ctx.Config()
+
+	cmd := rule.Command().BuiltTool("gen_build_prop")
+
+	cmd.FlagWithInput("--build-hostname-file=", config.BuildHostnameFile(ctx))
+	cmd.FlagWithInput("--build-number-file=", config.BuildNumberFile(ctx))
+	// shouldn't depend on BuildFingerprintFile and BuildThumbprintFile to prevent from rebuilding
+	// on every incremental build.
+	cmd.FlagWithArg("--build-fingerprint-file=", config.BuildFingerprintFile(ctx).String())
+	// Export build thumbprint only if the product has specified at least one oem fingerprint property
+	// b/17888863
+	if shouldAddBuildThumbprint(config) {
+		// In the previous make implementation, a dependency was not added on the thumbprint file
+		cmd.FlagWithArg("--build-thumbprint-file=", config.BuildThumbprintFile(ctx).String())
+	}
+	cmd.FlagWithArg("--build-username=", config.Getenv("BUILD_USERNAME"))
+	// shouldn't depend on BUILD_DATETIME_FILE to prevent from rebuilding on every incremental
+	// build.
+	cmd.FlagWithArg("--date-file=", ctx.Config().Getenv("BUILD_DATETIME_FILE"))
+	cmd.FlagWithInput("--platform-preview-sdk-fingerprint-file=", ApiFingerprintPath(ctx))
+	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
+	cmd.FlagWithArg("--partition=", partition)
+	cmd.FlagWithOutput("--out=", p.outputFilePath)
+
+	postProcessCmd := rule.Command().BuiltTool("post_process_props")
+	if ctx.DeviceConfig().BuildBrokenDupSysprop() {
+		postProcessCmd.Flag("--allow-dup")
+	}
+	postProcessCmd.FlagWithArg("--sdk-version ", config.PlatformSdkVersion().String())
+	postProcessCmd.FlagWithInput("--kernel-version-file-for-uffd-gc ", PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt"))
+	postProcessCmd.Text(p.outputFilePath.String())
+	postProcessCmd.Flags(p.properties.Block_list)
+
+	rule.Command().Text("echo").Text(proptools.NinjaAndShellEscape("# end of file")).FlagWithArg(">> ", p.outputFilePath.String())
+
+	rule.Build(ctx.ModuleName(), "generating build.prop")
+
+	p.installPath = PathForModuleInstall(ctx)
+	ctx.InstallFile(p.installPath, p.stem(), p.outputFilePath)
+
+	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
+}
+
+// build_prop module generates {partition}/build.prop file. At first common build properties are
+// printed based on Soong config variables. And then prop_files are printed as-is. Finally,
+// post_process_props tool is run to check if the result build.prop is valid or not.
+func buildPropFactory() Module {
+	module := &buildPropModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
diff --git a/android/buildinfo_prop.go b/android/buildinfo_prop.go
index 083f3ef..defbff0 100644
--- a/android/buildinfo_prop.go
+++ b/android/buildinfo_prop.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"strings"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -29,6 +28,8 @@
 type buildinfoPropProperties struct {
 	// Whether this module is directly installable to one of the partitions. Default: true.
 	Installable *bool
+
+	Product_config *string `android:"path"`
 }
 
 type buildinfoPropModule struct {
@@ -54,24 +55,6 @@
 	return Paths{p.outputFilePath}, nil
 }
 
-func getBuildVariant(config Config) string {
-	if config.Eng() {
-		return "eng"
-	} else if config.Debuggable() {
-		return "userdebug"
-	} else {
-		return "user"
-	}
-}
-
-func getBuildFlavor(config Config) string {
-	buildFlavor := config.DeviceProduct() + "-" + getBuildVariant(config)
-	if InList("address", config.SanitizeDevice()) && !strings.Contains(buildFlavor, "_asan") {
-		buildFlavor += "_asan"
-	}
-	return buildFlavor
-}
-
 func shouldAddBuildThumbprint(config Config) bool {
 	knownOemProperties := []string{
 		"ro.product.brand",
@@ -101,63 +84,27 @@
 	rule := NewRuleBuilder(pctx, ctx)
 
 	config := ctx.Config()
-	buildVariant := getBuildVariant(config)
-	buildFlavor := getBuildFlavor(config)
 
 	cmd := rule.Command().BuiltTool("buildinfo")
 
-	if config.BoardUseVbmetaDigestInFingerprint() {
-		cmd.Flag("--use-vbmeta-digest-in-fingerprint")
-	}
-
-	cmd.FlagWithArg("--build-flavor=", buildFlavor)
 	cmd.FlagWithInput("--build-hostname-file=", config.BuildHostnameFile(ctx))
-	cmd.FlagWithArg("--build-id=", config.BuildId())
-	cmd.FlagWithArg("--build-keys=", config.BuildKeys())
-
 	// Note: depending on BuildNumberFile will cause the build.prop file to be rebuilt
 	// every build, but that's intentional.
 	cmd.FlagWithInput("--build-number-file=", config.BuildNumberFile(ctx))
+	// Export build thumbprint only if the product has specified at least one oem fingerprint property
+	// b/17888863
 	if shouldAddBuildThumbprint(config) {
 		// In the previous make implementation, a dependency was not added on the thumbprint file
 		cmd.FlagWithArg("--build-thumbprint-file=", config.BuildThumbprintFile(ctx).String())
 	}
-
-	cmd.FlagWithArg("--build-type=", config.BuildType())
 	cmd.FlagWithArg("--build-username=", config.Getenv("BUILD_USERNAME"))
-	cmd.FlagWithArg("--build-variant=", buildVariant)
-	cmd.FlagForEachArg("--cpu-abis=", config.DeviceAbi())
-
 	// Technically we should also have a dependency on BUILD_DATETIME_FILE,
 	// but it can be either an absolute or relative path, which is hard to turn into
 	// a Path object. So just rely on the BuildNumberFile always changing to cause
 	// us to rebuild.
 	cmd.FlagWithArg("--date-file=", ctx.Config().Getenv("BUILD_DATETIME_FILE"))
-
-	if len(config.ProductLocales()) > 0 {
-		cmd.FlagWithArg("--default-locale=", config.ProductLocales()[0])
-	}
-
-	cmd.FlagForEachArg("--default-wifi-channels=", config.ProductDefaultWifiChannels())
-	cmd.FlagWithArg("--device=", config.DeviceName())
-	if config.DisplayBuildNumber() {
-		cmd.Flag("--display-build-number")
-	}
-
-	cmd.FlagWithArg("--platform-base-os=", config.PlatformBaseOS())
-	cmd.FlagWithArg("--platform-display-version=", config.PlatformDisplayVersionName())
-	cmd.FlagWithArg("--platform-min-supported-target-sdk-version=", config.PlatformMinSupportedTargetSdkVersion())
 	cmd.FlagWithInput("--platform-preview-sdk-fingerprint-file=", ApiFingerprintPath(ctx))
-	cmd.FlagWithArg("--platform-preview-sdk-version=", config.PlatformPreviewSdkVersion())
-	cmd.FlagWithArg("--platform-sdk-version=", config.PlatformSdkVersion().String())
-	cmd.FlagWithArg("--platform-security-patch=", config.PlatformSecurityPatch())
-	cmd.FlagWithArg("--platform-version=", config.PlatformVersionName())
-	cmd.FlagWithArg("--platform-version-codename=", config.PlatformSdkCodename())
-	cmd.FlagForEachArg("--platform-version-all-codenames=", config.PlatformVersionActiveCodenames())
-	cmd.FlagWithArg("--platform-version-known-codenames=", config.PlatformVersionKnownCodenames())
-	cmd.FlagWithArg("--platform-version-last-stable=", config.PlatformVersionLastStable())
-	cmd.FlagWithArg("--product=", config.DeviceProduct())
-
+	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
 	cmd.FlagWithOutput("--out=", p.outputFilePath)
 
 	rule.Build(ctx.ModuleName(), "generating buildinfo props")
diff --git a/android/compliance_metadata.go b/android/compliance_metadata.go
new file mode 100644
index 0000000..6ea6654
--- /dev/null
+++ b/android/compliance_metadata.go
@@ -0,0 +1,314 @@
+// Copyright 2024 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 (
+	"bytes"
+	"encoding/csv"
+	"fmt"
+	"slices"
+	"strconv"
+	"strings"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	// Constants of property names used in compliance metadata of modules
+	ComplianceMetadataProp = struct {
+		NAME                   string
+		PACKAGE                string
+		MODULE_TYPE            string
+		OS                     string
+		ARCH                   string
+		IS_PRIMARY_ARCH        string
+		VARIANT                string
+		IS_STATIC_LIB          string
+		INSTALLED_FILES        string
+		BUILT_FILES            string
+		STATIC_DEPS            string
+		STATIC_DEP_FILES       string
+		WHOLE_STATIC_DEPS      string
+		WHOLE_STATIC_DEP_FILES string
+		LICENSES               string
+
+		// module_type=package
+		PKG_DEFAULT_APPLICABLE_LICENSES string
+
+		// module_type=license
+		LIC_LICENSE_KINDS string
+		LIC_LICENSE_TEXT  string
+		LIC_PACKAGE_NAME  string
+
+		// module_type=license_kind
+		LK_CONDITIONS string
+		LK_URL        string
+	}{
+		"name",
+		"package",
+		"module_type",
+		"os",
+		"arch",
+		"is_primary_arch",
+		"variant",
+		"is_static_lib",
+		"installed_files",
+		"built_files",
+		"static_deps",
+		"static_dep_files",
+		"whole_static_deps",
+		"whole_static_dep_files",
+		"licenses",
+
+		"pkg_default_applicable_licenses",
+
+		"lic_license_kinds",
+		"lic_license_text",
+		"lic_package_name",
+
+		"lk_conditions",
+		"lk_url",
+	}
+
+	// A constant list of all property names in compliance metadata
+	// Order of properties here is the order of columns in the exported CSV file.
+	COMPLIANCE_METADATA_PROPS = []string{
+		ComplianceMetadataProp.NAME,
+		ComplianceMetadataProp.PACKAGE,
+		ComplianceMetadataProp.MODULE_TYPE,
+		ComplianceMetadataProp.OS,
+		ComplianceMetadataProp.ARCH,
+		ComplianceMetadataProp.VARIANT,
+		ComplianceMetadataProp.IS_STATIC_LIB,
+		ComplianceMetadataProp.IS_PRIMARY_ARCH,
+		// Space separated installed files
+		ComplianceMetadataProp.INSTALLED_FILES,
+		// Space separated built files
+		ComplianceMetadataProp.BUILT_FILES,
+		// Space separated module names of static dependencies
+		ComplianceMetadataProp.STATIC_DEPS,
+		// Space separated file paths of static dependencies
+		ComplianceMetadataProp.STATIC_DEP_FILES,
+		// Space separated module names of whole static dependencies
+		ComplianceMetadataProp.WHOLE_STATIC_DEPS,
+		// Space separated file paths of whole static dependencies
+		ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES,
+		ComplianceMetadataProp.LICENSES,
+		// module_type=package
+		ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES,
+		// module_type=license
+		ComplianceMetadataProp.LIC_LICENSE_KINDS,
+		ComplianceMetadataProp.LIC_LICENSE_TEXT, // resolve to file paths
+		ComplianceMetadataProp.LIC_PACKAGE_NAME,
+		// module_type=license_kind
+		ComplianceMetadataProp.LK_CONDITIONS,
+		ComplianceMetadataProp.LK_URL,
+	}
+)
+
+// ComplianceMetadataInfo provides all metadata of a module, e.g. name, module type, package, license,
+// dependencies, built/installed files, etc. It is a wrapper on a map[string]string with some utility
+// methods to get/set properties' values.
+type ComplianceMetadataInfo struct {
+	properties map[string]string
+}
+
+func NewComplianceMetadataInfo() *ComplianceMetadataInfo {
+	return &ComplianceMetadataInfo{
+		properties: map[string]string{},
+	}
+}
+
+func (c *ComplianceMetadataInfo) SetStringValue(propertyName string, value string) {
+	if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) {
+		panic(fmt.Errorf("Unknown metadata property: %s.", propertyName))
+	}
+	c.properties[propertyName] = value
+}
+
+func (c *ComplianceMetadataInfo) SetListValue(propertyName string, value []string) {
+	c.SetStringValue(propertyName, strings.TrimSpace(strings.Join(value, " ")))
+}
+
+func (c *ComplianceMetadataInfo) getStringValue(propertyName string) string {
+	if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) {
+		panic(fmt.Errorf("Unknown metadata property: %s.", propertyName))
+	}
+	return c.properties[propertyName]
+}
+
+func (c *ComplianceMetadataInfo) getAllValues() map[string]string {
+	return c.properties
+}
+
+var (
+	ComplianceMetadataProvider = blueprint.NewProvider[*ComplianceMetadataInfo]()
+)
+
+// buildComplianceMetadataProvider starts with the ModuleContext.ComplianceMetadataInfo() and fills in more common metadata
+// for different module types without accessing their private fields but through android.Module interface
+// and public/private fields of package android. The final metadata is stored to a module's ComplianceMetadataProvider.
+func buildComplianceMetadataProvider(ctx ModuleContext, m *ModuleBase) {
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.NAME, m.Name())
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.PACKAGE, ctx.ModuleDir())
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.MODULE_TYPE, ctx.ModuleType())
+
+	switch ctx.ModuleType() {
+	case "license":
+		licenseModule := m.module.(*licenseModule)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_KINDS, licenseModule.properties.License_kinds)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_TEXT, PathsForModuleSrc(ctx, licenseModule.properties.License_text).Strings())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LIC_PACKAGE_NAME, String(licenseModule.properties.Package_name))
+	case "license_kind":
+		licenseKindModule := m.module.(*licenseKindModule)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LK_CONDITIONS, licenseKindModule.properties.Conditions)
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LK_URL, licenseKindModule.properties.Url)
+	default:
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.OS, ctx.Os().String())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.ARCH, ctx.Arch().String())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.IS_PRIMARY_ARCH, strconv.FormatBool(ctx.PrimaryArch()))
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.VARIANT, ctx.ModuleSubDir())
+		if m.primaryLicensesProperty != nil && m.primaryLicensesProperty.getName() == "licenses" {
+			complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LICENSES, m.primaryLicensesProperty.getStrings())
+		}
+
+		var installed InstallPaths
+		installed = append(installed, m.module.FilesToInstall()...)
+		installed = append(installed, m.katiInstalls.InstallPaths()...)
+		installed = append(installed, m.katiSymlinks.InstallPaths()...)
+		installed = append(installed, m.katiInitRcInstalls.InstallPaths()...)
+		installed = append(installed, m.katiVintfInstalls.InstallPaths()...)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.INSTALLED_FILES, FirstUniqueStrings(installed.Strings()))
+	}
+	ctx.setProvider(ComplianceMetadataProvider, complianceMetadataInfo)
+}
+
+func init() {
+	RegisterComplianceMetadataSingleton(InitRegistrationContext)
+}
+
+func RegisterComplianceMetadataSingleton(ctx RegistrationContext) {
+	ctx.RegisterParallelSingletonType("compliance_metadata_singleton", complianceMetadataSingletonFactory)
+}
+
+var (
+	// sqlite3 command line tool
+	sqlite3 = pctx.HostBinToolVariable("sqlite3", "sqlite3")
+
+	// Command to import .csv files to sqlite3 database
+	importCsv = pctx.AndroidStaticRule("importCsv",
+		blueprint.RuleParams{
+			Command: `rm -rf $out && ` +
+				`${sqlite3} $out ".import --csv $in modules" && ` +
+				`${sqlite3} $out ".import --csv ${make_metadata} make_metadata" && ` +
+				`${sqlite3} $out ".import --csv ${make_modules} make_modules"`,
+			CommandDeps: []string{"${sqlite3}"},
+		}, "make_metadata", "make_modules")
+)
+
+func complianceMetadataSingletonFactory() Singleton {
+	return &complianceMetadataSingleton{}
+}
+
+type complianceMetadataSingleton struct {
+}
+
+func writerToCsv(csvWriter *csv.Writer, row []string) {
+	err := csvWriter.Write(row)
+	if err != nil {
+		panic(err)
+	}
+}
+
+// Collect compliance metadata from all Soong modules, write to a CSV file and
+// import compliance metadata from Make and Soong to a sqlite3 database.
+func (c *complianceMetadataSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	var buffer bytes.Buffer
+	csvWriter := csv.NewWriter(&buffer)
+
+	// Collect compliance metadata of modules in Soong and write to out/soong/compliance-metadata/<product>/soong-modules.csv file.
+	columnNames := []string{"id"}
+	columnNames = append(columnNames, COMPLIANCE_METADATA_PROPS...)
+	writerToCsv(csvWriter, columnNames)
+
+	rowId := -1
+	ctx.VisitAllModules(func(module Module) {
+		if !module.Enabled(ctx) {
+			return
+		}
+		moduleType := ctx.ModuleType(module)
+		if moduleType == "package" {
+			metadataMap := map[string]string{
+				ComplianceMetadataProp.NAME:                            ctx.ModuleName(module),
+				ComplianceMetadataProp.MODULE_TYPE:                     ctx.ModuleType(module),
+				ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES: strings.Join(module.base().primaryLicensesProperty.getStrings(), " "),
+			}
+			rowId = rowId + 1
+			metadata := []string{strconv.Itoa(rowId)}
+			for _, propertyName := range COMPLIANCE_METADATA_PROPS {
+				metadata = append(metadata, metadataMap[propertyName])
+			}
+			writerToCsv(csvWriter, metadata)
+			return
+		}
+		if provider, ok := ctx.moduleProvider(module, ComplianceMetadataProvider); ok {
+			metadataInfo := provider.(*ComplianceMetadataInfo)
+			rowId = rowId + 1
+			metadata := []string{strconv.Itoa(rowId)}
+			for _, propertyName := range COMPLIANCE_METADATA_PROPS {
+				metadata = append(metadata, metadataInfo.getStringValue(propertyName))
+			}
+			writerToCsv(csvWriter, metadata)
+			return
+		}
+	})
+	csvWriter.Flush()
+
+	deviceProduct := ctx.Config().DeviceProduct()
+	modulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "soong-modules.csv")
+	WriteFileRuleVerbatim(ctx, modulesCsv, buffer.String())
+
+	// Metadata generated in Make
+	makeMetadataCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-metadata.csv")
+	makeModulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-modules.csv")
+
+	// Import metadata from Make and Soong to sqlite3 database
+	complianceMetadataDb := PathForOutput(ctx, "compliance-metadata", deviceProduct, "compliance-metadata.db")
+	ctx.Build(pctx, BuildParams{
+		Rule:  importCsv,
+		Input: modulesCsv,
+		Implicits: []Path{
+			makeMetadataCsv,
+			makeModulesCsv,
+		},
+		Output: complianceMetadataDb,
+		Args: map[string]string{
+			"make_metadata": makeMetadataCsv.String(),
+			"make_modules":  makeModulesCsv.String(),
+		},
+	})
+
+	// Phony rule "compliance-metadata.db". "m compliance-metadata.db" to create the compliance metadata database.
+	ctx.Build(pctx, BuildParams{
+		Rule:   blueprint.Phony,
+		Inputs: []Path{complianceMetadataDb},
+		Output: PathForPhony(ctx, "compliance-metadata.db"),
+	})
+
+}
diff --git a/android/config.go b/android/config.go
index 1124488..cda01f0 100644
--- a/android/config.go
+++ b/android/config.go
@@ -198,6 +198,33 @@
 	return c.config.productVariables.ReleaseAconfigValueSets
 }
 
+func (c Config) ReleaseAconfigExtraReleaseConfigs() []string {
+	result := []string{}
+	if val, ok := c.config.productVariables.BuildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
+		if len(val) > 0 {
+			// Remove any duplicates from the list.
+			found := make(map[string]bool)
+			for _, k := range strings.Split(val, " ") {
+				if !found[k] {
+					found[k] = true
+					result = append(result, k)
+				}
+			}
+		}
+	}
+	return result
+}
+
+func (c Config) ReleaseAconfigExtraReleaseConfigsValueSets() map[string][]string {
+	result := make(map[string][]string)
+	for _, rcName := range c.ReleaseAconfigExtraReleaseConfigs() {
+		if value, ok := c.config.productVariables.BuildFlags["RELEASE_ACONFIG_VALUE_SETS_"+rcName]; ok {
+			result[rcName] = strings.Split(value, " ")
+		}
+	}
+	return result
+}
+
 // The flag default permission value passed to aconfig
 // derived from RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION
 func (c Config) ReleaseAconfigFlagDefaultPermission() string {
@@ -779,6 +806,17 @@
 	return Bool(c.productVariables.DisplayBuildNumber)
 }
 
+// BuildFingerprintFile returns the path to a text file containing metadata
+// representing the current build's fingerprint.
+//
+// Rules that want to reference the build fingerprint should read from this file
+// without depending on it. They will run whenever their other dependencies
+// require them to run and get the current build fingerprint. This ensures they
+// don't rebuild on every incremental build when the build number changes.
+func (c *config) BuildFingerprintFile(ctx PathContext) Path {
+	return PathForArbitraryOutput(ctx, "target", "product", c.DeviceName(), String(c.productVariables.BuildFingerprintFile))
+}
+
 // BuildNumberFile returns the path to a text file containing metadata
 // representing the current build's number.
 //
@@ -1878,6 +1916,10 @@
 	return c.config.productVariables.BuildBrokenDontCheckSystemSdk
 }
 
+func (c *deviceConfig) BuildBrokenDupSysprop() bool {
+	return c.config.productVariables.BuildBrokenDupSysprop
+}
+
 func (c *config) BuildWarningBadOptionalUsesLibsAllowlist() []string {
 	return c.productVariables.BuildWarningBadOptionalUsesLibsAllowlist
 }
diff --git a/android/config_test.go b/android/config_test.go
index 7d327a2..ca7c7f8 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -125,6 +125,43 @@
 	}
 }
 
+func TestReleaseAconfigExtraReleaseConfigs(t *testing.T) {
+	testCases := []struct {
+		name     string
+		flag     string
+		expected []string
+	}{
+		{
+			name:     "empty",
+			flag:     "",
+			expected: []string{},
+		},
+		{
+			name:     "specified",
+			flag:     "bar foo",
+			expected: []string{"bar", "foo"},
+		},
+		{
+			name:     "duplicates",
+			flag:     "foo bar foo",
+			expected: []string{"foo", "bar"},
+		},
+	}
+
+	for _, tc := range testCases {
+		fixture := GroupFixturePreparers(
+			FixtureModifyProductVariables(func(vars FixtureProductVariables) {
+				if vars.BuildFlags == nil {
+					vars.BuildFlags = make(map[string]string)
+				}
+				vars.BuildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"] = tc.flag
+			}),
+		)
+		actual := fixture.RunTest(t).Config.ReleaseAconfigExtraReleaseConfigs()
+		AssertArrayString(t, tc.name, tc.expected, actual)
+	}
+}
+
 func TestConfiguredJarList(t *testing.T) {
 	list1 := CreateTestConfiguredJarList([]string{"apex1:jarA"})
 
diff --git a/android/image.go b/android/image.go
index 9cad056..c278dcd 100644
--- a/android/image.go
+++ b/android/image.go
@@ -19,6 +19,12 @@
 	// ImageMutatorBegin is called before any other method in the ImageInterface.
 	ImageMutatorBegin(ctx BaseModuleContext)
 
+	// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
+	VendorVariantNeeded(ctx BaseModuleContext) bool
+
+	// ProductVariantNeeded should return true if the module needs a product variant (unstalled on the product image).
+	ProductVariantNeeded(ctx BaseModuleContext) bool
+
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
 	CoreVariantNeeded(ctx BaseModuleContext) bool
 
@@ -49,6 +55,14 @@
 }
 
 const (
+	// VendorVariation is the variant name used for /vendor code that does not
+	// compile against the VNDK.
+	VendorVariation string = "vendor"
+
+	// ProductVariation is the variant name used for /product code that does not
+	// compile against the VNDK.
+	ProductVariation string = "product"
+
 	// CoreVariation is the variant used for framework-private libraries, or
 	// SDK libraries. (which framework-private libraries can use), which
 	// will be installed to the system image.
@@ -94,6 +108,12 @@
 		if m.RecoveryVariantNeeded(ctx) {
 			variations = append(variations, RecoveryVariation)
 		}
+		if m.VendorVariantNeeded(ctx) {
+			variations = append(variations, VendorVariation)
+		}
+		if m.ProductVariantNeeded(ctx) {
+			variations = append(variations, ProductVariation)
+		}
 
 		extraVariations := m.ExtraImageVariations(ctx)
 		variations = append(variations, extraVariations...)
diff --git a/android/module.go b/android/module.go
index dc585d2..c08d2f4 100644
--- a/android/module.go
+++ b/android/module.go
@@ -26,8 +26,6 @@
 	"sort"
 	"strings"
 
-	"android/soong/bazel"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -113,7 +111,7 @@
 	// Get information about the properties that can contain visibility rules.
 	visibilityProperties() []visibilityProperty
 
-	RequiredModuleNames() []string
+	RequiredModuleNames(ctx ConfigAndErrorContext) []string
 	HostRequiredModuleNames() []string
 	TargetRequiredModuleNames() []string
 
@@ -422,7 +420,7 @@
 	Vintf_fragments []string `android:"path"`
 
 	// names of other modules to install if this module is installed
-	Required []string `android:"arch_variant"`
+	Required proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// names of other modules to install on host if this module is installed
 	Host_required []string `android:"arch_variant"`
@@ -849,9 +847,6 @@
 	// archPropRoot that is filled with arch specific values by the arch mutator.
 	archProperties [][]interface{}
 
-	// Properties specific to the Blueprint to BUILD migration.
-	bazelTargetModuleProperties bazel.BazelTargetModuleProperties
-
 	// Information about all the properties on the module that contains visibility rules that need
 	// checking.
 	visibilityPropertyInfo []visibilityProperty
@@ -919,6 +914,10 @@
 	// outputFiles stores the output of a module by tag and is used to set
 	// the OutputFilesProvider in GenerateBuildActions
 	outputFiles OutputFilesInfo
+
+	// complianceMetadataInfo is for different module types to dump metadata.
+	// See android.ModuleContext interface.
+	complianceMetadataInfo *ComplianceMetadataInfo
 }
 
 func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
@@ -1101,7 +1100,7 @@
 	hostTargets = append(hostTargets, ctx.Config().BuildOSCommonTarget)
 
 	if ctx.Device() {
-		for _, depName := range ctx.Module().RequiredModuleNames() {
+		for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
 			for _, target := range deviceTargets {
 				addDep(target, depName)
 			}
@@ -1114,7 +1113,7 @@
 	}
 
 	if ctx.Host() {
-		for _, depName := range ctx.Module().RequiredModuleNames() {
+		for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
 			for _, target := range hostTargets {
 				// When a host module requires another host module, don't make a
 				// dependency if they have different OSes (i.e. hostcross).
@@ -1235,17 +1234,28 @@
 		// the special tag name which represents that.
 		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
 
+		distFileForTagFromProvider, err := outputFilesForModuleFromProvider(ctx, m.module, tag)
+		if err != OutputFilesProviderNotSet {
+			if err != nil && tag != DefaultDistTag {
+				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
+			} else {
+				distFiles = distFiles.addPathsForTag(tag, distFileForTagFromProvider...)
+				continue
+			}
+		}
+
+		// if the tagged dist file cannot be obtained from OutputFilesProvider,
+		// fall back to use OutputFileProducer
+		// TODO: remove this part after OutputFilesProvider fully replaces OutputFileProducer
 		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
 			// Call the OutputFiles(tag) method to get the paths associated with the tag.
 			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
-
 			// If the tag was not supported and is not DefaultDistTag then it is an error.
 			// Failing to find paths for DefaultDistTag is not an error. It just means
 			// that the module type requires the legacy behavior.
 			if err != nil && tag != DefaultDistTag {
 				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
 			}
-
 			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
 		} else if tag != DefaultDistTag {
 			// If the tag was specified then it is an error if the module does not
@@ -1619,8 +1629,8 @@
 	return m.base().commonProperties.ImageVariation == RecoveryVariation
 }
 
-func (m *ModuleBase) RequiredModuleNames() []string {
-	return m.base().commonProperties.Required
+func (m *ModuleBase) RequiredModuleNames(ctx ConfigAndErrorContext) []string {
+	return m.base().commonProperties.Required.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
 }
 
 func (m *ModuleBase) HostRequiredModuleNames() []string {
@@ -1913,9 +1923,54 @@
 			return
 		}
 
-		m.module.GenerateAndroidBuildActions(ctx)
-		if ctx.Failed() {
-			return
+		incrementalAnalysis := false
+		incrementalEnabled := false
+		var cacheKey *blueprint.BuildActionCacheKey = nil
+		var incrementalModule *blueprint.Incremental = nil
+		if ctx.bp.GetIncrementalEnabled() {
+			if im, ok := m.module.(blueprint.Incremental); ok {
+				incrementalModule = &im
+				incrementalEnabled = im.IncrementalSupported()
+				incrementalAnalysis = ctx.bp.GetIncrementalAnalysis() && incrementalEnabled
+			}
+		}
+		if incrementalEnabled {
+			hash, err := proptools.CalculateHash(m.GetProperties())
+			if err != nil {
+				ctx.ModuleErrorf("failed to calculate properties hash: %s", err)
+				return
+			}
+			cacheInput := new(blueprint.BuildActionCacheInput)
+			cacheInput.PropertiesHash = hash
+			ctx.VisitDirectDeps(func(module Module) {
+				cacheInput.ProvidersHash =
+					append(cacheInput.ProvidersHash, ctx.bp.OtherModuleProviderInitialValueHashes(module))
+			})
+			hash, err = proptools.CalculateHash(&cacheInput)
+			if err != nil {
+				ctx.ModuleErrorf("failed to calculate cache input hash: %s", err)
+				return
+			}
+			cacheKey = &blueprint.BuildActionCacheKey{
+				Id:        ctx.bp.ModuleId(),
+				InputHash: hash,
+			}
+		}
+
+		restored := false
+		if incrementalAnalysis && cacheKey != nil {
+			restored = ctx.bp.RestoreBuildActions(cacheKey, incrementalModule)
+		}
+
+		if !restored {
+			m.module.GenerateAndroidBuildActions(ctx)
+			if ctx.Failed() {
+				return
+			}
+		}
+
+		if incrementalEnabled && cacheKey != nil {
+			ctx.bp.CacheBuildActions(cacheKey, incrementalModule)
 		}
 
 		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
@@ -1992,7 +2047,7 @@
 			TargetDependencies: targetRequired,
 			HostDependencies:   hostRequired,
 			Data:               data,
-			Required:           m.RequiredModuleNames(),
+			Required:           m.RequiredModuleNames(ctx),
 		}
 		SetProvider(ctx, ModuleInfoJSONProvider, m.moduleInfoJSON)
 	}
@@ -2004,6 +2059,8 @@
 	if m.outputFiles.DefaultOutputFiles != nil || m.outputFiles.TaggedOutputFiles != nil {
 		SetProvider(ctx, OutputFilesProvider, m.outputFiles)
 	}
+
+	buildComplianceMetadataProvider(ctx, m)
 }
 
 func SetJarJarPrefixHandler(handler func(ModuleContext)) {
@@ -2208,7 +2265,20 @@
 		variable := condition.Arg(1)
 		if n, ok := ctx.Config().productVariables.VendorVars[namespace]; ok {
 			if v, ok := n[variable]; ok {
-				return proptools.ConfigurableValueString(v)
+				ty := ""
+				if namespaces, ok := ctx.Config().productVariables.VendorVarTypes[namespace]; ok {
+					ty = namespaces[variable]
+				}
+				switch ty {
+				case "":
+					// strings are the default, we don't bother writing them to the soong variables json file
+					return proptools.ConfigurableValueString(v)
+				case "bool":
+					return proptools.ConfigurableValueBool(v == "true")
+				default:
+					panic("unhandled soong config variable type: " + ty)
+				}
+
 			}
 		}
 		return proptools.ConfigurableValueUndefined()
@@ -2454,7 +2524,7 @@
 
 func outputFilesForModule(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
 	outputFilesFromProvider, err := outputFilesForModuleFromProvider(ctx, module, tag)
-	if outputFilesFromProvider != nil || err != nil {
+	if outputFilesFromProvider != nil || err != OutputFilesProviderNotSet {
 		return outputFilesFromProvider, err
 	}
 	if outputFileProducer, ok := module.(OutputFileProducer); ok {
@@ -2479,34 +2549,45 @@
 // *inter-module-communication*.
 // If mctx module is the same as the param module the output files are obtained
 // from outputFiles property of module base, to avoid both setting and
-// reading OutputFilesProvider before  GenerateBuildActions is finished. Also
-// only empty-string-tag is supported in this case.
+// reading OutputFilesProvider before GenerateBuildActions is finished.
 // If a module doesn't have the OutputFilesProvider, nil is returned.
 func outputFilesForModuleFromProvider(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
-	// TODO: support OutputFilesProvider for singletons
-	mctx, ok := ctx.(ModuleContext)
-	if !ok {
-		return nil, nil
-	}
-	if mctx.Module() != module {
-		if outputFilesProvider, ok := OtherModuleProvider(mctx, module, OutputFilesProvider); ok {
-			if tag == "" {
-				return outputFilesProvider.DefaultOutputFiles, nil
-			} else if taggedOutputFiles, hasTag := outputFilesProvider.TaggedOutputFiles[tag]; hasTag {
-				return taggedOutputFiles, nil
-			} else {
-				return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-			}
-		}
-	} else {
-		if tag == "" {
-			return mctx.Module().base().outputFiles.DefaultOutputFiles, nil
+	var outputFiles OutputFilesInfo
+	fromProperty := false
+
+	if mctx, isMctx := ctx.(ModuleContext); isMctx {
+		if mctx.Module() != module {
+			outputFiles, _ = OtherModuleProvider(mctx, module, OutputFilesProvider)
 		} else {
+			outputFiles = mctx.Module().base().outputFiles
+			fromProperty = true
+		}
+	} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
+		providerData, _ := cta.moduleProvider(module, OutputFilesProvider)
+		outputFiles, _ = providerData.(OutputFilesInfo)
+	}
+	// TODO: Add a check for skipped context
+
+	if outputFiles.isEmpty() {
+		// TODO: Add a check for param module not having OutputFilesProvider set
+		return nil, OutputFilesProviderNotSet
+	}
+
+	if tag == "" {
+		return outputFiles.DefaultOutputFiles, nil
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles, nil
+	} else {
+		if fromProperty {
 			return nil, fmt.Errorf("unsupported tag %q for module getting its own output files", tag)
+		} else {
+			return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 		}
 	}
-	// TODO: Add a check for param module not having OutputFilesProvider set
-	return nil, nil
+}
+
+func (o OutputFilesInfo) isEmpty() bool {
+	return o.DefaultOutputFiles == nil && o.TaggedOutputFiles == nil
 }
 
 type OutputFilesInfo struct {
@@ -2519,6 +2600,9 @@
 
 var OutputFilesProvider = blueprint.NewProvider[OutputFilesInfo]()
 
+// This is used to mark the case where OutputFilesProvider is not set on some modules.
+var OutputFilesProviderNotSet = fmt.Errorf("No output files from provider")
+
 // Modules can implement HostToolProvider and return a valid OptionalPath from HostToolPath() to
 // specify that they can be used as a tool by a genrule module.
 type HostToolProvider interface {
diff --git a/android/module_context.go b/android/module_context.go
index 591e270..2e16a24 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -183,7 +183,7 @@
 	InstallInVendor() bool
 	InstallForceOS() (*OsType, *ArchType)
 
-	RequiredModuleNames() []string
+	RequiredModuleNames(ctx ConfigAndErrorContext) []string
 	HostRequiredModuleNames() []string
 	TargetRequiredModuleNames() []string
 
@@ -216,6 +216,11 @@
 	// SetOutputFiles stores the outputFiles to outputFiles property, which is used
 	// to set the OutputFilesProvider later.
 	SetOutputFiles(outputFiles Paths, tag string)
+
+	// ComplianceMetadataInfo returns a ComplianceMetadataInfo instance for different module types to dump metadata,
+	// which usually happens in GenerateAndroidBuildActions() of a module type.
+	// See android.ModuleBase.complianceMetadataInfo
+	ComplianceMetadataInfo() *ComplianceMetadataInfo
 }
 
 type moduleContext struct {
@@ -729,6 +734,15 @@
 	}
 }
 
+func (m *moduleContext) ComplianceMetadataInfo() *ComplianceMetadataInfo {
+	if complianceMetadataInfo := m.module.base().complianceMetadataInfo; complianceMetadataInfo != nil {
+		return complianceMetadataInfo
+	}
+	complianceMetadataInfo := NewComplianceMetadataInfo()
+	m.module.base().complianceMetadataInfo = complianceMetadataInfo
+	return complianceMetadataInfo
+}
+
 // Returns a list of paths expanded from globs and modules referenced using ":module" syntax.  The property must
 // be tagged with `android:"path" to support automatic source module dependency resolution.
 //
@@ -755,8 +769,8 @@
 	return OptionalPath{}
 }
 
-func (m *moduleContext) RequiredModuleNames() []string {
-	return m.module.RequiredModuleNames()
+func (m *moduleContext) RequiredModuleNames(ctx ConfigAndErrorContext) []string {
+	return m.module.RequiredModuleNames(ctx)
 }
 
 func (m *moduleContext) HostRequiredModuleNames() []string {
diff --git a/android/paths.go b/android/paths.go
index edc0700..adbee70 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -15,6 +15,9 @@
 package android
 
 import (
+	"bytes"
+	"encoding/gob"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -1068,6 +1071,28 @@
 	rel  string
 }
 
+func (p basePath) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+	err := errors.Join(encoder.Encode(p.path), encoder.Encode(p.rel))
+	if err != nil {
+		return nil, err
+	}
+
+	return w.Bytes(), nil
+}
+
+func (p *basePath) GobDecode(data []byte) error {
+	r := bytes.NewBuffer(data)
+	decoder := gob.NewDecoder(r)
+	err := errors.Join(decoder.Decode(&p.path), decoder.Decode(&p.rel))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (p basePath) Ext() string {
 	return filepath.Ext(p.path)
 }
@@ -1306,6 +1331,28 @@
 	fullPath string
 }
 
+func (p OutputPath) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+	err := errors.Join(encoder.Encode(p.basePath), encoder.Encode(p.soongOutDir), encoder.Encode(p.fullPath))
+	if err != nil {
+		return nil, err
+	}
+
+	return w.Bytes(), nil
+}
+
+func (p *OutputPath) GobDecode(data []byte) error {
+	r := bytes.NewBuffer(data)
+	decoder := gob.NewDecoder(r)
+	err := errors.Join(decoder.Decode(&p.basePath), decoder.Decode(&p.soongOutDir), decoder.Decode(&p.fullPath))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (p OutputPath) withRel(rel string) OutputPath {
 	p.basePath = p.basePath.withRel(rel)
 	p.fullPath = filepath.Join(p.fullPath, rel)
diff --git a/android/product_config.go b/android/product_config.go
new file mode 100644
index 0000000..20b29a7
--- /dev/null
+++ b/android/product_config.go
@@ -0,0 +1,58 @@
+// Copyright 2024 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 "github.com/google/blueprint/proptools"
+
+func init() {
+	ctx := InitRegistrationContext
+	ctx.RegisterModuleType("product_config", productConfigFactory)
+}
+
+type productConfigModule struct {
+	ModuleBase
+}
+
+func (p *productConfigModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if ctx.ModuleName() != "product_config" || ctx.ModuleDir() != "build/soong" {
+		ctx.ModuleErrorf("There can only be one product_config module in build/soong")
+		return
+	}
+	outputFilePath := PathForModuleOut(ctx, p.Name()+".json").OutputPath
+
+	// DeviceProduct can be null so calling ctx.Config().DeviceProduct() may cause null dereference
+	targetProduct := proptools.String(ctx.Config().config.productVariables.DeviceProduct)
+	if targetProduct != "" {
+		targetProduct += "."
+	}
+	soongVariablesPath := PathForOutput(ctx, "soong."+targetProduct+"variables")
+	extraVariablesPath := PathForOutput(ctx, "soong."+targetProduct+"extra.variables")
+
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().BuiltTool("merge_json").
+		Output(outputFilePath).
+		Input(soongVariablesPath).
+		Input(extraVariablesPath).
+		rule.Build("product_config.json", "building product_config.json")
+
+	ctx.SetOutputFiles(Paths{outputFilePath}, "")
+}
+
+// product_config module exports product variables and extra variables as a JSON file.
+func productConfigFactory() Module {
+	module := &productConfigModule{}
+	InitAndroidModule(module)
+	return module
+}
diff --git a/android/selects_test.go b/android/selects_test.go
index 3093deb..e2dc403 100644
--- a/android/selects_test.go
+++ b/android/selects_test.go
@@ -25,12 +25,13 @@
 
 func TestSelects(t *testing.T) {
 	testCases := []struct {
-		name          string
-		bp            string
-		provider      selectsTestProvider
-		providers     map[string]selectsTestProvider
-		vendorVars    map[string]map[string]string
-		expectedError string
+		name           string
+		bp             string
+		provider       selectsTestProvider
+		providers      map[string]selectsTestProvider
+		vendorVars     map[string]map[string]string
+		vendorVarTypes map[string]map[string]string
+		expectedError  string
 	}{
 		{
 			name: "basic string list",
@@ -584,6 +585,31 @@
 			},
 		},
 		{
+			name: "Select on boolean soong config variable",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					true: "t",
+					false: "f",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "true",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "bool",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("t"),
+			},
+		},
+		{
 			name: "Select on boolean false",
 			bp: `
 			my_module_type {
@@ -813,6 +839,7 @@
 				}),
 				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
 					variables.VendorVars = tc.vendorVars
+					variables.VendorVarTypes = tc.vendorVarTypes
 				}),
 			)
 			if tc.expectedError != "" {
diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp
index 8fe1ff1..5a6df26 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -9,7 +9,6 @@
         "blueprint",
         "blueprint-parser",
         "blueprint-proptools",
-        "soong-bazel",
         "soong-starlark-format",
     ],
     srcs: [
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 87af774..f6046d0 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -824,11 +824,16 @@
 			}
 			field.Set(newField)
 		case reflect.Struct:
-			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
-			if err := s.printfIntoPropertyRecursive(fieldName, field, configValues); err != nil {
-				return err
+			if proptools.IsConfigurable(field.Type()) {
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				return fmt.Errorf("soong_config_variables.%s.%s: list variables are not supported on configurable properties", s.variable, strings.Join(fieldName, "."))
+			} else {
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				if err := s.printfIntoPropertyRecursive(fieldName, field, configValues); err != nil {
+					return err
+				}
+				fieldName = fieldName[:len(fieldName)-1]
 			}
-			fieldName = fieldName[:len(fieldName)-1]
 		default:
 			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
 			return fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, strings.Join(fieldName, "."), kind)
diff --git a/android/testing.go b/android/testing.go
index 6fb2997..18fd3b3 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -224,6 +224,10 @@
 	})
 }
 
+func (ctx *TestContext) OtherModulePropertyErrorf(module Module, property string, fmt_ string, args ...interface{}) {
+	panic(fmt.Sprintf(fmt_, args...))
+}
+
 // registeredComponentOrder defines the order in which a sortableComponent type is registered at
 // runtime and provides support for reordering the components registered for a test in the same
 // way.
@@ -1021,9 +1025,12 @@
 // otherwise returns the result of calling Paths.RelativeToTop
 // on the returned Paths.
 func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
-	// TODO: add non-empty-string tag case and remove OutputFileProducer part
-	if tag == "" && m.module.base().outputFiles.DefaultOutputFiles != nil {
-		return m.module.base().outputFiles.DefaultOutputFiles.RelativeToTop()
+	// TODO: remove OutputFileProducer part
+	outputFiles := m.Module().base().outputFiles
+	if tag == "" && outputFiles.DefaultOutputFiles != nil {
+		return outputFiles.DefaultOutputFiles.RelativeToTop()
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles
 	}
 
 	producer, ok := m.module.(OutputFileProducer)
diff --git a/android/variable.go b/android/variable.go
index 2500140..a331439 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -199,11 +199,12 @@
 	// Suffix to add to generated Makefiles
 	Make_suffix *string `json:",omitempty"`
 
-	BuildId             *string `json:",omitempty"`
-	BuildNumberFile     *string `json:",omitempty"`
-	BuildHostnameFile   *string `json:",omitempty"`
-	BuildThumbprintFile *string `json:",omitempty"`
-	DisplayBuildNumber  *bool   `json:",omitempty"`
+	BuildId              *string `json:",omitempty"`
+	BuildFingerprintFile *string `json:",omitempty"`
+	BuildNumberFile      *string `json:",omitempty"`
+	BuildHostnameFile    *string `json:",omitempty"`
+	BuildThumbprintFile  *string `json:",omitempty"`
+	DisplayBuildNumber   *bool   `json:",omitempty"`
 
 	Platform_display_version_name          *string  `json:",omitempty"`
 	Platform_version_name                  *string  `json:",omitempty"`
@@ -398,7 +399,8 @@
 
 	PlatformSepolicyCompatVersions []string `json:",omitempty"`
 
-	VendorVars map[string]map[string]string `json:",omitempty"`
+	VendorVars     map[string]map[string]string `json:",omitempty"`
+	VendorVarTypes map[string]map[string]string `json:",omitempty"`
 
 	Ndk_abis *bool `json:",omitempty"`
 
@@ -458,6 +460,7 @@
 	BuildBrokenIncorrectPartitionImages bool     `json:",omitempty"`
 	BuildBrokenInputDirModules          []string `json:",omitempty"`
 	BuildBrokenDontCheckSystemSdk       bool     `json:",omitempty"`
+	BuildBrokenDupSysprop               bool     `json:",omitempty"`
 
 	BuildWarningBadOptionalUsesLibsAllowlist []string `json:",omitempty"`
 
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 619be8d..4112108 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -218,7 +218,7 @@
 	var required []string
 	var targetRequired []string
 	var hostRequired []string
-	required = append(required, a.RequiredModuleNames()...)
+	required = append(required, a.required...)
 	targetRequired = append(targetRequired, a.TargetRequiredModuleNames()...)
 	hostRequired = append(hostRequired, a.HostRequiredModuleNames()...)
 	for _, fi := range a.filesInfo {
diff --git a/apex/apex.go b/apex/apex.go
index c19732e..e6815bc 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -489,6 +489,9 @@
 	javaApisUsedByModuleFile     android.ModuleOutPath
 
 	aconfigFiles []android.Path
+
+	// Required modules, filled out during GenerateAndroidBuildActions and used in AndroidMk
+	required []string
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -567,7 +570,7 @@
 	if module != nil {
 		ret.moduleDir = ctx.OtherModuleDir(module)
 		ret.partition = module.PartitionTag(ctx.DeviceConfig())
-		ret.requiredModuleNames = module.RequiredModuleNames()
+		ret.requiredModuleNames = module.RequiredModuleNames(ctx)
 		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
 		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
 		ret.multilib = module.Target().Arch.ArchType.Multilib
@@ -745,9 +748,9 @@
 
 	prefix := android.CoreVariation
 	if a.SocSpecific() || a.DeviceSpecific() {
-		prefix = cc.VendorVariation
+		prefix = android.VendorVariation
 	} else if a.ProductSpecific() {
-		prefix = cc.ProductVariation
+		prefix = android.ProductVariation
 	}
 
 	return prefix, ""
@@ -1040,6 +1043,7 @@
 		InApexModules:     []string{a.Name()}, // could be com.mycompany.android.foo
 		ApexContents:      []*android.ApexContents{apexContents},
 		TestApexes:        testApexes,
+		BaseApexName:      mctx.ModuleName(),
 	}
 	mctx.WalkDeps(func(child, parent android.Module) bool {
 		if !continueApexDepsWalk(child, parent) {
@@ -1168,6 +1172,7 @@
 		"test_com.android.os.statsd",
 		"test_com.android.permission",
 		"test_com.android.wifi",
+		"test_imgdiag_com.android.art",
 		"test_jitzygote_com.android.art",
 		// go/keep-sorted end
 	}
@@ -1366,25 +1371,6 @@
 	return true
 }
 
-var _ android.OutputFileProducer = (*apexBundle)(nil)
-
-// Implements android.OutputFileProducer
-func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "", android.DefaultDistTag:
-		// This is the default dist path.
-		return android.Paths{a.outputFile}, nil
-	case imageApexSuffix:
-		// uncompressed one
-		if a.outputApexFile != nil {
-			return android.Paths{a.outputApexFile}, nil
-		}
-		fallthrough
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 var _ multitree.Exportable = (*apexBundle)(nil)
 
 func (a *apexBundle) Exportable() bool {
@@ -2426,6 +2412,10 @@
 	a.provideApexExportsInfo(ctx)
 
 	a.providePrebuiltInfo(ctx)
+
+	a.required = a.RequiredModuleNames(ctx)
+
+	a.setOutputFiles(ctx)
 }
 
 // Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2454,6 +2444,18 @@
 	})
 }
 
+// Set output files to outputFiles property, which is later used to set the
+// OutputFilesProvider
+func (a *apexBundle) setOutputFiles(ctx android.ModuleContext) {
+	// default dist path
+	ctx.SetOutputFiles(android.Paths{a.outputFile}, "")
+	ctx.SetOutputFiles(android.Paths{a.outputFile}, android.DefaultDistTag)
+	// uncompressed one
+	if a.outputApexFile != nil {
+		ctx.SetOutputFiles(android.Paths{a.outputApexFile}, imageApexSuffix)
+	}
+}
+
 // apexBootclasspathFragmentFiles returns the list of apexFile structures defining the files that
 // the bootclasspath_fragment contributes to the apex.
 func apexBootclasspathFragmentFiles(ctx android.ModuleContext, module blueprint.Module) []apexFile {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index dfc4bb3..3bb3966 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -7125,6 +7125,46 @@
 	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
 }
 
+func TestJavaSDKLibraryOverrideApexes(t *testing.T) {
+	ctx := testApex(t, `
+		override_apex {
+			name: "mycompanyapex",
+			base: "myapex",
+		}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["foo"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			apex_available: [ "myapex" ],
+		}
+
+		prebuilt_apis {
+			name: "sdk",
+			api_dirs: ["100"],
+		}
+	`, withFiles(filesForSdkLibrary))
+
+	// Permission XML should point to the activated path of impl jar of java_sdk_library.
+	// Since override variants (com.mycompany.android.foo) are installed in the same package as the overridden variant
+	// (com.android.foo), the filepath should not contain override apex name.
+	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_mycompanyapex").Output("foo.xml")
+	contents := android.ContentFromFileRuleForTests(t, ctx, sdkLibrary)
+	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
+}
+
 func TestJavaSDKLibrary_WithinApex(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -9997,6 +10037,56 @@
 	}
 }
 
+func TestApexLintBcpFragmentSdkLibDeps(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["foo"],
+			apex_available: ["myapex"],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			compile_dex: true,
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.PrepareForTestWithJacocoInstrumentation,
+		java.FixtureWithLastReleaseApis("foo"),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.SetApiLibraries([]string{"foo"})
+		}),
+		android.FixtureMergeMockFs(fs),
+	).RunTestWithBp(t, bp)
+
+	myapex := result.ModuleForTests("myapex", "android_common_myapex")
+	lintReportInputs := strings.Join(myapex.Output("lint-report-xml.zip").Inputs.Strings(), " ")
+	android.AssertStringDoesContain(t,
+		"myapex lint report expected to contain that of the sdk library impl lib as an input",
+		lintReportInputs, "foo.impl")
+}
+
 // updatable apexes should propagate updatable=true to its apps
 func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
 	bp := `
diff --git a/bazel/Android.bp b/bazel/Android.bp
index 4709f5c..f8273a8 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -6,22 +6,17 @@
     name: "soong-bazel",
     pkgPath: "android/soong/bazel",
     srcs: [
-        "aquery.go",
-        "bazel_proxy.go",
         "configurability.go",
-        "constants.go",
         "properties.go",
         "testing.go",
     ],
     testSrcs: [
-        "aquery_test.go",
         "properties_test.go",
     ],
     pluginFor: [
         "soong_build",
     ],
     deps: [
-        "bazel_analysis_v2_proto",
         "blueprint",
     ],
 }
diff --git a/bazel/aquery.go b/bazel/aquery.go
deleted file mode 100644
index 35942bc..0000000
--- a/bazel/aquery.go
+++ /dev/null
@@ -1,768 +0,0 @@
-// Copyright 2020 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 bazel
-
-import (
-	"crypto/sha256"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"path/filepath"
-	"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"
-)
-
-type artifactId int
-type depsetId int
-type pathFragmentId int
-
-// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
-type KeyValuePair struct {
-	Key   string
-	Value string
-}
-
-// AqueryDepset is a depset definition from Bazel's aquery response. This is
-// akin to the `depSetOfFiles` in the response proto, except:
-//   - direct artifacts are enumerated by full path instead of by ID
-//   - it has a hash of the depset contents, instead of an int ID (for determinism)
-//
-// A depset is a data structure for efficient transitive handling of artifact
-// paths. A single depset consists of one or more artifact paths and one or
-// more "child" depsets.
-type AqueryDepset struct {
-	ContentHash            string
-	DirectArtifacts        []string
-	TransitiveDepSetHashes []string
-}
-
-// BuildStatement contains information to register a build statement corresponding (one to one)
-// with a Bazel action from Bazel's action graph.
-type BuildStatement struct {
-	Command      string
-	Depfile      *string
-	OutputPaths  []string
-	SymlinkPaths []string
-	Env          []*analysis_v2_proto.KeyValuePair
-	Mnemonic     string
-
-	// Inputs of this build statement, either as unexpanded depsets or expanded
-	// input paths. There should be no overlap between these fields; an input
-	// path should either be included as part of an unexpanded depset or a raw
-	// input path string, but not both.
-	InputDepsetHashes []string
-	InputPaths        []string
-	FileContents      string
-	// 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
-	IsExecutable bool
-}
-
-// A helper type for aquery processing which facilitates retrieval of path IDs from their
-// less readable Bazel structures (depset and path fragment).
-type aqueryArtifactHandler struct {
-	// Maps depset id to AqueryDepset, a representation of depset which is
-	// post-processed for middleman artifact handling, unhandled artifact
-	// dropping, content hashing, etc.
-	depsetIdToAqueryDepset map[depsetId]AqueryDepset
-	emptyDepsetIds         map[depsetId]struct{}
-	// Maps content hash to AqueryDepset.
-	depsetHashToAqueryDepset map[string]AqueryDepset
-
-	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
-	// may be an expensive operation.
-	depsetHashToArtifactPathsCache sync.Map
-	// Maps artifact ids to fully expanded paths.
-	artifactIdToPath map[artifactId]string
-}
-
-// The tokens should be substituted with the value specified here, instead of the
-// one returned in 'substitutions' of TemplateExpand action.
-var templateActionOverriddenTokens = map[string]string{
-	// Uses "python3" for %python_binary% instead of the value returned by aquery
-	// which is "py3wrapper.sh". See removePy3wrapperScript.
-	"%python_binary%": "python3",
-}
-
-const (
-	middlemanMnemonic = "Middleman"
-	// The file name of py3wrapper.sh, which is used by py_binary targets.
-	py3wrapperFileName = "/py3wrapper.sh"
-)
-
-func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
-	m := map[K]V{}
-	for _, v := range values {
-		m[keyFn(v)] = v
-	}
-	return m
-}
-
-func newAqueryHandler(aqueryResult *analysis_v2_proto.ActionGraphContainer) (*aqueryArtifactHandler, error) {
-	pathFragments := indexBy(aqueryResult.PathFragments, func(pf *analysis_v2_proto.PathFragment) pathFragmentId {
-		return pathFragmentId(pf.Id)
-	})
-
-	artifactIdToPath := make(map[artifactId]string, len(aqueryResult.Artifacts))
-	for _, artifact := range aqueryResult.Artifacts {
-		artifactPath, err := expandPathFragment(pathFragmentId(artifact.PathFragmentId), pathFragments)
-		if err != nil {
-			return nil, err
-		}
-		if artifact.IsTreeArtifact &&
-			!strings.HasPrefix(artifactPath, "bazel-out/io_bazel_rules_go/") &&
-			!strings.HasPrefix(artifactPath, "bazel-out/rules_java_builtin/") {
-			// Since we're using ninja as an executor, we can't use tree artifacts. Ninja only
-			// considers a file/directory "dirty" when it's mtime changes. Directories' mtimes will
-			// only change when a file in the directory is added/removed, but not when files in
-			// the directory are changed, or when files in subdirectories are changed/added/removed.
-			// Bazel handles this by walking the directory and generating a hash for it after the
-			// action runs, which we would have to do as well if we wanted to support these
-			// artifacts in mixed builds.
-			//
-			// However, there are some bazel built-in rules that use tree artifacts. Allow those,
-			// but keep in mind that they'll have incrementality issues.
-			return nil, fmt.Errorf("tree artifacts are currently not supported in mixed builds: " + artifactPath)
-		}
-		artifactIdToPath[artifactId(artifact.Id)] = artifactPath
-	}
-
-	// Map middleman artifact ContentHash to input artifact depset ID.
-	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
-	// if we find a middleman action which has inputs [foo, bar], and output [baz_middleman], then,
-	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
-	// that action instead.
-	middlemanIdToDepsetIds := map[artifactId][]uint32{}
-	for _, actionEntry := range aqueryResult.Actions {
-		if actionEntry.Mnemonic == middlemanMnemonic {
-			for _, outputId := range actionEntry.OutputIds {
-				middlemanIdToDepsetIds[artifactId(outputId)] = actionEntry.InputDepSetIds
-			}
-		}
-	}
-
-	depsetIdToDepset := indexBy(aqueryResult.DepSetOfFiles, func(d *analysis_v2_proto.DepSetOfFiles) depsetId {
-		return depsetId(d.Id)
-	})
-
-	aqueryHandler := aqueryArtifactHandler{
-		depsetIdToAqueryDepset:         map[depsetId]AqueryDepset{},
-		depsetHashToAqueryDepset:       map[string]AqueryDepset{},
-		depsetHashToArtifactPathsCache: sync.Map{},
-		emptyDepsetIds:                 make(map[depsetId]struct{}, 0),
-		artifactIdToPath:               artifactIdToPath,
-	}
-
-	// Validate and adjust aqueryResult.DepSetOfFiles values.
-	for _, depset := range aqueryResult.DepSetOfFiles {
-		_, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return &aqueryHandler, nil
-}
-
-// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
-// depset.
-func (a *aqueryArtifactHandler) populateDepsetMaps(depset *analysis_v2_proto.DepSetOfFiles, middlemanIdToDepsetIds map[artifactId][]uint32, depsetIdToDepset map[depsetId]*analysis_v2_proto.DepSetOfFiles) (*AqueryDepset, error) {
-	if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depsetId(depset.Id)]; containsDepset {
-		return &aqueryDepset, nil
-	}
-	transitiveDepsetIds := depset.TransitiveDepSetIds
-	directArtifactPaths := make([]string, 0, len(depset.DirectArtifactIds))
-	for _, id := range depset.DirectArtifactIds {
-		aId := artifactId(id)
-		path, pathExists := a.artifactIdToPath[aId]
-		if !pathExists {
-			return nil, fmt.Errorf("undefined input artifactId %d", aId)
-		}
-		// Filter out any inputs which are universally dropped, and swap middleman
-		// artifacts with their corresponding depsets.
-		if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[aId]; isMiddleman {
-			// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
-			transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
-		} else if strings.HasSuffix(path, py3wrapperFileName) ||
-			strings.HasPrefix(path, "../bazel_tools") {
-			continue
-			// Drop these artifacts.
-			// See go/python-binary-host-mixed-build for more details.
-			// 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
-			// TemplateExpandAction handles everything necessary to launch a Pythin application.
-			// 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
-			// containing depset to always be considered newer than their outputs.
-		} else {
-			directArtifactPaths = append(directArtifactPaths, path)
-		}
-	}
-
-	childDepsetHashes := make([]string, 0, len(transitiveDepsetIds))
-	for _, id := range transitiveDepsetIds {
-		childDepsetId := depsetId(id)
-		childDepset, exists := depsetIdToDepset[childDepsetId]
-		if !exists {
-			if _, empty := a.emptyDepsetIds[childDepsetId]; empty {
-				continue
-			} else {
-				return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
-			}
-		}
-		if childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset); err != nil {
-			return nil, err
-		} else if childAqueryDepset == nil {
-			continue
-		} else {
-			childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
-		}
-	}
-	if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
-		a.emptyDepsetIds[depsetId(depset.Id)] = struct{}{}
-		return nil, nil
-	}
-	aqueryDepset := AqueryDepset{
-		ContentHash:            depsetContentHash(directArtifactPaths, childDepsetHashes),
-		DirectArtifacts:        directArtifactPaths,
-		TransitiveDepSetHashes: childDepsetHashes,
-	}
-	a.depsetIdToAqueryDepset[depsetId(depset.Id)] = aqueryDepset
-	a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
-	return &aqueryDepset, nil
-}
-
-// getInputPaths flattens the depsets of the given IDs and returns all transitive
-// input paths contained in these depsets.
-// This is a potentially expensive operation, and should not be invoked except
-// for actions which need specialized input handling.
-func (a *aqueryArtifactHandler) getInputPaths(depsetIds []uint32) ([]string, error) {
-	var inputPaths []string
-
-	for _, id := range depsetIds {
-		inputDepSetId := depsetId(id)
-		depset := a.depsetIdToAqueryDepset[inputDepSetId]
-		inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
-		if err != nil {
-			return nil, err
-		}
-		for _, inputPath := range inputArtifacts {
-			inputPaths = append(inputPaths, inputPath)
-		}
-	}
-
-	return inputPaths, nil
-}
-
-func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
-	if result, exists := a.depsetHashToArtifactPathsCache.Load(depsetHash); exists {
-		return result.([]string), nil
-	}
-	if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
-		result := depset.DirectArtifacts
-		for _, childHash := range depset.TransitiveDepSetHashes {
-			childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, childArtifactIds...)
-		}
-		a.depsetHashToArtifactPathsCache.Store(depsetHash, result)
-		return result, nil
-	} else {
-		return nil, fmt.Errorf("undefined input depset hash %s", depsetHash)
-	}
-}
-
-// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
-// which should be registered (and output to a ninja file) to correspond with Bazel's
-// action graph, as described by the given action graph json proto.
-// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
-// are one-to-one with Bazel's depSetOfFiles objects.
-func AqueryBuildStatements(aqueryJsonProto []byte, eventHandler *metrics.EventHandler) ([]*BuildStatement, []AqueryDepset, error) {
-	aqueryProto := &analysis_v2_proto.ActionGraphContainer{}
-	err := proto.Unmarshal(aqueryJsonProto, aqueryProto)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var aqueryHandler *aqueryArtifactHandler
-	{
-		eventHandler.Begin("init_handler")
-		defer eventHandler.End("init_handler")
-		aqueryHandler, err = newAqueryHandler(aqueryProto)
-		if err != nil {
-			return nil, nil, err
-		}
-	}
-
-	// allocate both length and capacity so each goroutine can write to an index independently without
-	// any need for synchronization for slice access.
-	buildStatements := make([]*BuildStatement, len(aqueryProto.Actions))
-	{
-		eventHandler.Begin("build_statements")
-		defer eventHandler.End("build_statements")
-		wg := sync.WaitGroup{}
-		var errOnce sync.Once
-		id2targets := make(map[uint32]string, len(aqueryProto.Targets))
-		for _, t := range aqueryProto.Targets {
-			id2targets[t.GetId()] = t.GetLabel()
-		}
-		for i, actionEntry := range aqueryProto.Actions {
-			wg.Add(1)
-			go func(i int, actionEntry *analysis_v2_proto.Action) {
-				if strings.HasPrefix(id2targets[actionEntry.TargetId], "@bazel_tools//") {
-					// bazel_tools are removed depsets in `populateDepsetMaps()` so skipping
-					// conversion to build statements as well
-					buildStatements[i] = nil
-				} else if buildStatement, aErr := aqueryHandler.actionToBuildStatement(actionEntry); aErr != nil {
-					errOnce.Do(func() {
-						aErr = fmt.Errorf("%s: [%s] [%s]", aErr.Error(), actionEntry.GetMnemonic(), id2targets[actionEntry.TargetId])
-						err = aErr
-					})
-				} else {
-					// set build statement at an index rather than appending such that each goroutine does not
-					// impact other goroutines
-					buildStatements[i] = buildStatement
-				}
-				wg.Done()
-			}(i, actionEntry)
-		}
-		wg.Wait()
-	}
-	if err != nil {
-		return nil, nil, err
-	}
-
-	depsetsByHash := map[string]AqueryDepset{}
-	depsets := make([]AqueryDepset, 0, len(aqueryHandler.depsetIdToAqueryDepset))
-	{
-		eventHandler.Begin("depsets")
-		defer eventHandler.End("depsets")
-		for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
-			if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
-				// Two depsets collide on hash. Ensure that their contents are identical.
-				if !reflect.DeepEqual(aqueryDepset, prevEntry) {
-					return nil, nil, fmt.Errorf("two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
-				}
-			} else {
-				depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
-				depsets = append(depsets, aqueryDepset)
-			}
-		}
-	}
-
-	eventHandler.Do("build_statement_sort", func() {
-		// Build Statements and depsets must be sorted by their content hash to
-		// preserve determinism between builds (this will result in consistent ninja file
-		// output). Note they are not sorted by their original IDs nor their Bazel ordering,
-		// as Bazel gives nondeterministic ordering / identifiers in aquery responses.
-		sort.Slice(buildStatements, func(i, j int) bool {
-			// Sort all nil statements to the end of the slice
-			if buildStatements[i] == nil {
-				return false
-			} else if buildStatements[j] == nil {
-				return true
-			}
-			//For build statements, compare output lists. In Bazel, each output file
-			// may only have one action which generates it, so this will provide
-			// a deterministic ordering.
-			outputs_i := buildStatements[i].OutputPaths
-			outputs_j := buildStatements[j].OutputPaths
-			if len(outputs_i) != len(outputs_j) {
-				return len(outputs_i) < len(outputs_j)
-			}
-			if len(outputs_i) == 0 {
-				// No outputs for these actions, so compare commands.
-				return buildStatements[i].Command < buildStatements[j].Command
-			}
-			// There may be multiple outputs, but the output ordering is deterministic.
-			return outputs_i[0] < outputs_j[0]
-		})
-	})
-	eventHandler.Do("depset_sort", func() {
-		sort.Slice(depsets, func(i, j int) bool {
-			return depsets[i].ContentHash < depsets[j].ContentHash
-		})
-	})
-	return buildStatements, depsets, nil
-}
-
-// depsetContentHash computes and returns a SHA256 checksum of the contents of
-// the given depset. This content hash may serve as the depset's identifier.
-// Using a content hash for an identifier is superior for determinism. (For example,
-// using an integer identifier which depends on the order in which the depsets are
-// created would result in nondeterministic depset IDs.)
-func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
-	h := sha256.New()
-	// Use newline as delimiter, as paths cannot contain newline.
-	h.Write([]byte(strings.Join(directPaths, "\n")))
-	h.Write([]byte(strings.Join(transitiveDepsetHashes, "")))
-	fullHash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
-	return fullHash
-}
-
-func (a *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []uint32) ([]string, error) {
-	var hashes []string
-	for _, id := range inputDepsetIds {
-		dId := depsetId(id)
-		if aqueryDepset, exists := a.depsetIdToAqueryDepset[dId]; !exists {
-			if _, empty := a.emptyDepsetIds[dId]; !empty {
-				return nil, fmt.Errorf("undefined (not even empty) input depsetId %d", dId)
-			}
-		} else {
-			hashes = append(hashes, aqueryDepset.ContentHash)
-		}
-	}
-	return hashes, nil
-}
-
-// escapes the args received from aquery and creates a command string
-func commandString(actionEntry *analysis_v2_proto.Action) string {
-	argsEscaped := make([]string, len(actionEntry.Arguments))
-	for i, arg := range actionEntry.Arguments {
-		if arg == "" {
-			// If this is an empty string, add ''
-			// And not
-			// 1. (literal empty)
-			// 2. `''\'''\'''` (escaped version of '')
-			//
-			// If we had used (1), then this would appear as a whitespace when we strings.Join
-			argsEscaped[i] = "''"
-		} else {
-			argsEscaped[i] = proptools.ShellEscapeIncludingSpaces(arg)
-		}
-	}
-	return strings.Join(argsEscaped, " ")
-}
-
-func (a *aqueryArtifactHandler) normalActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	command := commandString(actionEntry)
-	inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-
-	buildStatement := &BuildStatement{
-		Command:           command,
-		Depfile:           depfile,
-		OutputPaths:       outputPaths,
-		InputDepsetHashes: inputDepsetHashes,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-	}
-	if buildStatement.Mnemonic == "GoToolchainBinaryBuild" {
-		// Unlike b's execution root, mixed build execution root contains a symlink to prebuilts/go
-		// This causes issues for `GOCACHE=$(mktemp -d) go build ...`
-		// To prevent this, sandbox this action in mixed builds as well
-		buildStatement.ShouldRunInSbox = true
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-	if len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
-	}
-	expandedTemplateContent := expandTemplateContent(actionEntry)
-	// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
-	// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
-	// change \n to space and mess up the format of Python programs.
-	// sed is used to convert \\n back to \n before saving to output file.
-	// See go/python-binary-host-mixed-build for more details.
-	command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
-		escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
-	inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-
-	buildStatement := &BuildStatement{
-		Command:           command,
-		Depfile:           depfile,
-		OutputPaths:       outputPaths,
-		InputDepsetHashes: inputDepsetHashes,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, _, err := a.getOutputPaths(actionEntry)
-	var depsetHashes []string
-	if err == nil {
-		depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &BuildStatement{
-		Depfile:           nil,
-		OutputPaths:       outputPaths,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-		InputDepsetHashes: depsetHashes,
-		FileContents:      actionEntry.FileContents,
-		IsExecutable:      actionEntry.IsExecutable,
-	}, nil
-}
-
-func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, _, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	if len(inputPaths) != 1 || len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
-	}
-	// The actual command is generated in bazelSingleton.GenerateBuildActions
-	return &BuildStatement{
-		Depfile:     nil,
-		OutputPaths: outputPaths,
-		Env:         actionEntry.EnvironmentVariables,
-		Mnemonic:    actionEntry.Mnemonic,
-		InputPaths:  inputPaths,
-	}, 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 {
-		return nil, err
-	}
-
-	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	if len(inputPaths) != 1 || len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
-	}
-	out := outputPaths[0]
-	outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
-	out = proptools.ShellEscapeIncludingSpaces(out)
-	in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
-	// 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, in)
-	symlinkPaths := outputPaths[:]
-
-	buildStatement := &BuildStatement{
-		Command:      command,
-		Depfile:      depfile,
-		OutputPaths:  outputPaths,
-		InputPaths:   inputPaths,
-		Env:          actionEntry.EnvironmentVariables,
-		Mnemonic:     actionEntry.Mnemonic,
-		SymlinkPaths: symlinkPaths,
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) getOutputPaths(actionEntry *analysis_v2_proto.Action) (outputPaths []string, depfile *string, err error) {
-	for _, outputId := range actionEntry.OutputIds {
-		outputPath, exists := a.artifactIdToPath[artifactId(outputId)]
-		if !exists {
-			err = fmt.Errorf("undefined outputId %d", outputId)
-			return
-		}
-		ext := filepath.Ext(outputPath)
-		if ext == ".d" {
-			if depfile != nil {
-				err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
-				return
-			} else {
-				depfile = &outputPath
-			}
-		} else {
-			outputPaths = append(outputPaths, outputPath)
-		}
-	}
-	return
-}
-
-// expandTemplateContent substitutes the tokens in a template.
-func expandTemplateContent(actionEntry *analysis_v2_proto.Action) string {
-	replacerString := make([]string, len(actionEntry.Substitutions)*2)
-	for i, pair := range actionEntry.Substitutions {
-		value := pair.Value
-		if val, ok := templateActionOverriddenTokens[pair.Key]; ok {
-			value = val
-		}
-		replacerString[i*2] = pair.Key
-		replacerString[i*2+1] = value
-	}
-	replacer := strings.NewReplacer(replacerString...)
-	return replacer.Replace(actionEntry.TemplateContent)
-}
-
-// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
-var commandLineArgumentReplacer = strings.NewReplacer(
-	`\`, `\\`,
-	`$`, `\$`,
-	"`", "\\`",
-	`"`, `\"`,
-	"\n", "\\n",
-	`'`, `'"'"'`,
-)
-
-func escapeCommandlineArgument(str string) string {
-	return commandLineArgumentReplacer.Replace(str)
-}
-
-func (a *aqueryArtifactHandler) actionToBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	switch actionEntry.Mnemonic {
-	// Middleman actions are not handled like other actions; they are handled separately as a
-	// preparatory step so that their inputs may be relayed to actions depending on middleman
-	// artifacts.
-	case middlemanMnemonic:
-		return nil, nil
-	// PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
-	case "PythonZipper":
-		return nil, nil
-	// Skip "Fail" actions, which are placeholder actions designed to always fail.
-	case "Fail":
-		return nil, nil
-	case "BaselineCoverage":
-		return nil, nil
-	case "Symlink", "SolibSymlink", "ExecutableSymlink":
-		return a.symlinkActionBuildStatement(actionEntry)
-	case "TemplateExpand":
-		if len(actionEntry.Arguments) < 1 {
-			return a.templateExpandActionBuildStatement(actionEntry)
-		}
-	case "FileWrite", "SourceSymlinkManifest", "RepoMappingManifest":
-		return a.fileWriteActionBuildStatement(actionEntry)
-	case "SymlinkTree":
-		return a.symlinkTreeActionBuildStatement(actionEntry)
-	case "UnresolvedSymlink":
-		return a.unresolvedSymlinkActionBuildStatement(actionEntry)
-	}
-
-	if len(actionEntry.Arguments) < 1 {
-		return nil, errors.New("received action with no command")
-	}
-	return a.normalActionBuildStatement(actionEntry)
-
-}
-
-func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]*analysis_v2_proto.PathFragment) (string, error) {
-	var labels []string
-	currId := id
-	// Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
-	for currId > 0 {
-		currFragment, ok := pathFragmentsMap[currId]
-		if !ok {
-			return "", fmt.Errorf("undefined path fragment id %d", currId)
-		}
-		labels = append([]string{currFragment.Label}, labels...)
-		parentId := pathFragmentId(currFragment.ParentId)
-		if currId == parentId {
-			return "", fmt.Errorf("fragment cannot refer to itself as parent %#v", currFragment)
-		}
-		currId = parentId
-	}
-	return filepath.Join(labels...), nil
-}
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
deleted file mode 100644
index cbd2791..0000000
--- a/bazel/aquery_test.go
+++ /dev/null
@@ -1,1411 +0,0 @@
-// Copyright 2020 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 bazel
-
-import (
-	"encoding/json"
-	"fmt"
-	"reflect"
-	"sort"
-	"testing"
-
-	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
-
-	"github.com/google/blueprint/metrics"
-	"google.golang.org/protobuf/proto"
-)
-
-func TestAqueryMultiArchGenrule(t *testing.T) {
-	// This input string is retrieved from a real build of bionic-related genrules.
-	const inputString = `
-{
- "Artifacts": [
-   { "Id": 1, "path_fragment_id": 1 },
-   { "Id": 2, "path_fragment_id": 6 },
-   { "Id": 3, "path_fragment_id": 8 },
-   { "Id": 4, "path_fragment_id": 12 },
-   { "Id": 5, "path_fragment_id": 19 },
-   { "Id": 6, "path_fragment_id": 20 },
-   { "Id": 7, "path_fragment_id": 21 }],
- "Actions": [{
-   "target_id": 1,
-   "action_key": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [1],
-   "output_ids": [4],
-   "primary_output_id": 4
- }, {
-   "target_id": 2,
-   "action_key": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [2],
-   "output_ids": [5],
-   "primary_output_id": 5
- }, {
-   "target_id": 3,
-   "action_key": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [3],
-   "output_ids": [6],
-   "primary_output_id": 6
- }, {
-   "target_id": 4,
-   "action_key": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [4],
-   "output_ids": [7],
-   "primary_output_id": 7
- }],
- "Targets": [
-   { "Id": 1, "Label": "@sourceroot//bionic/libc:syscalls-arm", "rule_class_id": 1 },
-   { "Id": 2, "Label": "@sourceroot//bionic/libc:syscalls-x86", "rule_class_id": 1 },
-   { "Id": 3, "Label": "@sourceroot//bionic/libc:syscalls-x86_64", "rule_class_id": 1 },
-   { "Id": 4, "Label": "@sourceroot//bionic/libc:syscalls-arm64", "rule_class_id": 1 }],
- "dep_set_of_files": [
-   { "Id": 1, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 2, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 3, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 4, "direct_artifact_ids": [1, 2, 3] }],
- "Configuration": [{
-   "Id": 1,
-   "Mnemonic": "k8-fastbuild",
-   "platform_name": "k8",
-   "Checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046"
- }],
- "rule_classes": [{ "Id": 1, "Name": "genrule"}],
- "path_fragments": [
-   { "Id": 5, "Label": ".." },
-   { "Id": 4, "Label": "sourceroot", "parent_id": 5 },
-   { "Id": 3, "Label": "bionic", "parent_id": 4 },
-   { "Id": 2, "Label": "libc", "parent_id": 3 },
-   { "Id": 1, "Label": "SYSCALLS.TXT", "parent_id": 2 },
-   { "Id": 7, "Label": "tools", "parent_id": 2 },
-   { "Id": 6, "Label": "gensyscalls.py", "parent_id": 7 },
-   { "Id": 11, "Label": "bazel_tools", "parent_id": 5 },
-   { "Id": 10, "Label": "tools", "parent_id": 11 },
-   { "Id": 9, "Label": "genrule", "parent_id": 10 },
-   { "Id": 8, "Label": "genrule-setup.sh", "parent_id": 9 },
-   { "Id": 18, "Label": "bazel-out" },
-   { "Id": 17, "Label": "sourceroot", "parent_id": 18 },
-   { "Id": 16, "Label": "k8-fastbuild", "parent_id": 17 },
-   { "Id": 15, "Label": "bin", "parent_id": 16 },
-   { "Id": 14, "Label": "bionic", "parent_id": 15 },
-   { "Id": 13, "Label": "libc", "parent_id": 14 },
-   { "Id": 12, "Label": "syscalls-arm.S", "parent_id": 13 },
-   { "Id": 19, "Label": "syscalls-x86.S", "parent_id": 13 },
-   { "Id": 20, "Label": "syscalls-x86_64.S", "parent_id": 13 },
-   { "Id": 21, "Label": "syscalls-arm64.S", "parent_id": 13 }]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	var expectedBuildStatements []*BuildStatement
-	for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} {
-		expectedBuildStatements = append(expectedBuildStatements,
-			&BuildStatement{
-				Command: fmt.Sprintf(
-					"/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'",
-					arch, arch),
-				OutputPaths: []string{
-					fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
-				},
-				Env: []*analysis_v2_proto.KeyValuePair{
-					{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
-				},
-				Mnemonic: "Genrule",
-			})
-	}
-	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
-
-	expectedFlattenedInputs := []string{
-		"../sourceroot/bionic/libc/SYSCALLS.TXT",
-		"../sourceroot/bionic/libc/tools/gensyscalls.py",
-	}
-	// In this example, each depset should have the same expected inputs.
-	for _, actualDepset := range actualDepsets {
-		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
-		if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-			t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-		}
-	}
-}
-
-func TestInvalidOutputId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined outputId 3: [X] []")
-}
-
-func TestInvalidInputDepsetIdFromAction(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [2],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "targets": [{
-   "id": 1,
-   "label": "target_x"
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined (not even empty) input depsetId 2: [X] [target_x]")
-}
-
-func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2], "transitive_dep_set_ids": [42] }],
- "path_fragments": [
-   { "id": 1, "label": "one"},
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)")
-}
-
-func TestInvalidInputArtifactId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 3] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined input artifactId 3")
-}
-
-func TestInvalidPathFragmentId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-    { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   {  "id": 1, "label": "one" },
-   {  "id": 2, "label": "two", "parent_id": 3 }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined path fragment id 3")
-}
-
-func TestDepfiles(t *testing.T) {
-	const inputString = `
-{
-  "artifacts": [
-    { "id": 1, "path_fragment_id": 1 },
-    { "id": 2, "path_fragment_id": 2 },
-    { "id": 3, "path_fragment_id": 3 }],
-  "actions": [{
-    "target_Id": 1,
-    "action_Key": "x",
-    "mnemonic": "x",
-    "arguments": ["touch", "foo"],
-    "input_dep_set_ids": [1],
-    "output_ids": [2, 3],
-    "primary_output_id": 2
-  }],
-  "dep_set_of_files": [
-    { "id": 1, "direct_Artifact_Ids": [1, 2, 3] }],
-  "path_fragments": [
-    { "id": 1, "label": "one" },
-    { "id": 2, "label": "two" },
-    { "id": 3, "label": "two.d" }]
-}`
-
-	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
-	}
-	if expected := 1; len(actual) != expected {
-		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
-		return
-	}
-
-	bs := actual[0]
-	expectedDepfile := "two.d"
-	if bs.Depfile == nil {
-		t.Errorf("Expected depfile %q, but there was none found", expectedDepfile)
-	} else if *bs.Depfile != expectedDepfile {
-		t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile)
-	}
-}
-
-func TestMultipleDepfiles(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 },
-   { "id": 4, "path_fragment_id": 4 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [2,3,4],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [{
-   "id": 1,
-   "direct_artifact_ids": [1, 2, 3, 4]
- }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" },
-   { "id": 3, "label": "two.d" },
-   { "id": 4, "label": "other.d" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `found multiple potential depfiles "two.d", "other.d": [X] []`)
-}
-
-func TestTransitiveInputDepsets(t *testing.T) {
-	// The input aquery for this test comes from a proof-of-concept starlark rule which registers
-	// a single action with many inputs given via a deep depset.
-	const inputString = `
-{
- "artifacts": [
-  { "id": 1, "path_fragment_id": 1 },
-  { "id": 2, "path_fragment_id": 7 },
-  { "id": 3, "path_fragment_id": 8 },
-  { "id": 4, "path_fragment_id": 9 },
-  { "id": 5, "path_fragment_id": 10 },
-  { "id": 6, "path_fragment_id": 11 },
-  { "id": 7, "path_fragment_id": 12 },
-  { "id": 8, "path_fragment_id": 13 },
-  { "id": 9, "path_fragment_id": 14 },
-  { "id": 10, "path_fragment_id": 15 },
-  { "id": 11, "path_fragment_id": 16 },
-  { "id": 12, "path_fragment_id": 17 },
-  { "id": 13, "path_fragment_id": 18 },
-  { "id": 14, "path_fragment_id": 19 },
-  { "id": 15, "path_fragment_id": 20 },
-  { "id": 16, "path_fragment_id": 21 },
-  { "id": 17, "path_fragment_id": 22 },
-  { "id": 18, "path_fragment_id": 23 },
-  { "id": 19, "path_fragment_id": 24 },
-  { "id": 20, "path_fragment_id": 25 },
-  { "id": 21, "path_fragment_id": 26 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50",
-   "mnemonic": "Action",
-   "configuration_id": 1,
-   "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"],
-   "input_dep_set_ids": [1],
-   "output_ids": [21],
-   "primary_output_id": 21
- }],
- "dep_set_of_files": [
-   { "id": 3, "direct_artifact_ids": [1, 2, 3, 4, 5] },
-   { "id": 4, "direct_artifact_ids": [6, 7, 8, 9, 10] },
-   { "id": 2, "transitive_dep_set_ids": [3, 4], "direct_artifact_ids": [11, 12, 13, 14, 15] },
-   { "id": 5, "direct_artifact_ids": [16, 17, 18, 19] },
-   { "id": 1, "transitive_dep_set_ids": [2, 5], "direct_artifact_ids": [20] }],
- "path_fragments": [
-   { "id": 6, "label": "bazel-out" },
-   { "id": 5, "label": "sourceroot", "parent_id": 6 },
-   { "id": 4, "label": "k8-fastbuild", "parent_id": 5 },
-   { "id": 3, "label": "bin", "parent_id": 4 },
-   { "id": 2, "label": "testpkg", "parent_id": 3 },
-   { "id": 1, "label": "test_1", "parent_id": 2 },
-   { "id": 7, "label": "test_2", "parent_id": 2 },
-   { "id": 8, "label": "test_3", "parent_id": 2 },
-   { "id": 9, "label": "test_4", "parent_id": 2 },
-   { "id": 10, "label": "test_5", "parent_id": 2 },
-   { "id": 11, "label": "test_6", "parent_id": 2 },
-   { "id": 12, "label": "test_7", "parent_id": 2 },
-	 { "id": 13, "label": "test_8", "parent_id": 2 },
-   { "id": 14, "label": "test_9", "parent_id": 2 },
-   { "id": 15, "label": "test_10", "parent_id": 2 },
-   { "id": 16, "label": "test_11", "parent_id": 2 },
-   { "id": 17, "label": "test_12", "parent_id": 2 },
-   { "id": 18, "label": "test_13", "parent_id": 2 },
-   { "id": 19, "label": "test_14", "parent_id": 2 },
-   { "id": 20, "label": "test_15", "parent_id": 2 },
-   { "id": 21, "label": "test_16", "parent_id": 2 },
-   { "id": 22, "label": "test_17", "parent_id": 2 },
-   { "id": 23, "label": "test_18", "parent_id": 2 },
-   { "id": 24, "label": "test_19", "parent_id": 2 },
-   { "id": 25, "label": "test_root", "parent_id": 2 },
-   { "id": 26,"label": "test_out", "parent_id": 2 }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command:      "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
-			OutputPaths:  []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
-			Mnemonic:     "Action",
-			SymlinkPaths: []string{},
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
-
-	// Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
-	// are given via a deep depset, but the depset is flattened when returned as a
-	// BuildStatement slice.
-	var expectedFlattenedInputs []string
-	for i := 1; i < 20; i++ {
-		expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
-	}
-	expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
-
-	actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes
-	actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets)
-	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-	}
-}
-
-func TestSymlinkTree(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SymlinkTree",
-   "configuration_id": 1,
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" },
-   { "id": 2, "label": "foo.runfiles/MANIFEST" }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }]
-}
-`
-	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{
-		&BuildStatement{
-			Command:      "",
-			OutputPaths:  []string{"foo.runfiles/MANIFEST"},
-			Mnemonic:     "SymlinkTree",
-			InputPaths:   []string{"foo.manifest"},
-			SymlinkPaths: []string{},
-		},
-	}, actual)
-}
-
-func TestBazelToolsRemovalFromInputDepsets(t *testing.T) {
-	const inputString = `{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 10 },
-   { "id": 2, "path_fragment_id": 20 },
-   { "id": 3, "path_fragment_id": 30 },
-   { "id": 4, "path_fragment_id": 40 }],
- "dep_set_of_files": [{
-   "id": 1111,
-   "direct_artifact_ids": [3 , 4]
- }, {
-   "id": 2222,
-   "direct_artifact_ids": [3]
- }],
- "actions": [{
-   "target_id": 100,
-   "action_key": "x",
-   "input_dep_set_ids": [1111, 2222],
-   "mnemonic": "x",
-   "arguments": ["bogus", "command"],
-   "output_ids": [2],
-   "primary_output_id": 1
- }],
- "path_fragments": [
-   { "id": 10, "label": "input" },
-   { "id": 20, "label": "output" },
-   { "id": 30, "label": "dep1", "parent_id": 50 },
-   { "id": 40, "label": "dep2", "parent_id": 60 },
-   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
-   { "id": 60, "label": ".."}
- ]
-}`
-	/* depsets
-	       1111  2222
-	       /  \   |
-	../dep2    ../bazel_tools/dep1
-	*/
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 1 {
-		t.Errorf("expected 1 depset but found %#v", actualDepsets)
-		return
-	}
-	dep2Found := false
-	for _, dep := range flattenDepsets([]string{actualDepsets[0].ContentHash}, actualDepsets) {
-		if dep == "../bazel_tools/dep1" {
-			t.Errorf("dependency %s expected to be removed but still exists", dep)
-		} else if dep == "../dep2" {
-			dep2Found = true
-		}
-	}
-	if !dep2Found {
-		t.Errorf("dependency ../dep2 expected but not found")
-	}
-
-	expectedBuildStatement := &BuildStatement{
-		Command:      "bogus command",
-		OutputPaths:  []string{"output"},
-		Mnemonic:     "x",
-		SymlinkPaths: []string{},
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
-		return
-	}
-}
-
-func TestBazelToolsRemovalFromTargets(t *testing.T) {
-	const inputString = `{
- "artifacts": [{ "id": 1, "path_fragment_id": 10 }],
- "targets": [
-   { "id": 100, "label": "targetX" },
-   { "id": 200, "label": "@bazel_tools//tool_y" }
-],
- "actions": [{
-   "target_id": 100,
-   "action_key": "actionX",
-   "arguments": ["bogus", "command"],
-   "mnemonic" : "x",
-   "output_ids": [1]
- }, {
-   "target_id": 200,
-   "action_key": "y"
- }],
- "path_fragments": [{ "id": 10, "label": "outputX"}]
-}`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 0 {
-		t.Errorf("expected 0 depset but found %#v", actualDepsets)
-		return
-	}
-	expectedBuildStatement := &BuildStatement{
-		Command:      "bogus command",
-		OutputPaths:  []string{"outputX"},
-		Mnemonic:     "x",
-		SymlinkPaths: []string{},
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v build statements", expectedBuildStatement, len(actualBuildStatements))
-		return
-	}
-}
-
-func TestBazelToolsRemovalFromTransitiveInputDepsets(t *testing.T) {
-	const inputString = `{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 10 },
-   { "id": 2, "path_fragment_id": 20 },
-   { "id": 3, "path_fragment_id": 30 }],
- "dep_set_of_files": [{
-   "id": 1111,
-   "transitive_dep_set_ids": [2222]
- }, {
-   "id": 2222,
-   "direct_artifact_ids": [3]
- }, {
-   "id": 3333,
-   "direct_artifact_ids": [3]
- }, {
-   "id": 4444,
-   "transitive_dep_set_ids": [3333]
- }],
- "actions": [{
-   "target_id": 100,
-   "action_key": "x",
-   "input_dep_set_ids": [1111, 4444],
-   "mnemonic": "x",
-   "arguments": ["bogus", "command"],
-   "output_ids": [2],
-   "primary_output_id": 1
- }],
- "path_fragments": [
-   { "id": 10, "label": "input" },
-   { "id": 20, "label": "output" },
-   { "id": 30, "label": "dep", "parent_id": 50 },
-   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
-   { "id": 60, "label": ".."}
- ]
-}`
-	/* depsets
-	    1111    4444
-	     ||      ||
-	    2222    3333
-	      |      |
-	../bazel_tools/dep
-	Note: in dep_set_of_files:
-	  1111 appears BEFORE its dependency,2222 while
-	  4444 appears AFTER its dependency 3333
-	and this test shows that that order doesn't affect empty depset pruning
-	*/
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 0 {
-		t.Errorf("expected 0 depsets but found %#v", actualDepsets)
-		return
-	}
-
-	expectedBuildStatement := &BuildStatement{
-		Command:     "bogus command",
-		OutputPaths: []string{"output"},
-		Mnemonic:    "x",
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
-		return
-	}
-}
-
-func TestMiddlemenAction(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 },
-   { "id": 4, "path_fragment_id": 4 },
-   { "id": 5, "path_fragment_id": 5 },
-   { "id": 6, "path_fragment_id": 6 }],
- "path_fragments": [
-   { "id": 1, "label": "middleinput_one" },
-   { "id": 2, "label": "middleinput_two" },
-   { "id": 3, "label": "middleman_artifact" },
-   { "id": 4, "label": "maininput_one" },
-   { "id": 5, "label": "maininput_two" },
-   { "id": 6, "label": "output" }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] },
-   { "id": 2, "direct_artifact_ids": [3, 4, 5] }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Middleman",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }, {
-   "target_id": 2,
-   "action_key": "y",
-   "mnemonic": "Main action",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [2],
-   "output_ids": [6],
-   "primary_output_id": 6
- }]
-}`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	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{
-		{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"},
-		{"middleinput_one", "middleinput_two"},
-	}
-	assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles)
-
-	bs := actualBuildStatements[0]
-	if len(bs.InputPaths) > 0 {
-		t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
-	}
-
-	expectedOutputs := []string{"output"}
-	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
-		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
-	}
-
-	expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
-	actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets)
-
-	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-	}
-
-	bs = actualBuildStatements[1]
-	if bs != nil {
-		t.Errorf("Expected nil action for skipped")
-	}
-}
-
-// Returns the contents of given depsets in concatenated post order.
-func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string {
-	depsetsByHash := map[string]AqueryDepset{}
-	for _, depset := range allDepsets {
-		depsetsByHash[depset.ContentHash] = depset
-	}
-	var result []string
-	for _, depsetId := range depsetHashesToFlatten {
-		result = append(result, flattenDepset(depsetId, depsetsByHash)...)
-	}
-	return result
-}
-
-// Returns the contents of a given depset in post order.
-func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string {
-	depset := allDepsets[depsetHashToFlatten]
-	var result []string
-	for _, depsetId := range depset.TransitiveDepSetHashes {
-		result = append(result, flattenDepset(depsetId, allDepsets)...)
-	}
-	result = append(result, depset.DirectArtifacts...)
-	return result
-}
-
-func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) {
-	t.Helper()
-	if len(actualDepsets) != len(expectedDepsetFiles) {
-		t.Errorf("Expected %d depsets, but got %d depsets", len(expectedDepsetFiles), len(actualDepsets))
-	}
-	for i, actualDepset := range actualDepsets {
-		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
-		if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) {
-			t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs)
-		}
-	}
-}
-
-func TestSimpleSymlink(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 3 },
-   { "id": 2, "path_fragment_id": 5 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "file_subdir", "parent_id": 1 },
-   { "id": 3, "label": "file", "parent_id": 2 },
-   { "id": 4, "label": "symlink_subdir", "parent_id": 1 },
-   { "id": 5, "label": "symlink", "parent_id": 4 }]
-}`
-	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
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "mkdir -p one/symlink_subdir && " +
-				"rm -f one/symlink_subdir/symlink && " +
-				"ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink",
-			InputPaths:   []string{"one/file_subdir/file"},
-			OutputPaths:  []string{"one/symlink_subdir/symlink"},
-			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
-			Mnemonic:     "Symlink",
-		},
-	}
-	assertBuildStatements(t, actual, expectedBuildStatements)
-}
-
-func TestSymlinkQuotesPaths(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 3 },
-   { "id": 2, "path_fragment_id": 5 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SolibSymlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "file subdir", "parent_id": 1 },
-   { "id": 3, "label": "file", "parent_id": 2 },
-   { "id": 4, "label": "symlink subdir", "parent_id": 1 },
-   { "id": 5, "label": "symlink", "parent_id": 4 }]
-}`
-
-	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
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "mkdir -p 'one/symlink subdir' && " +
-				"rm -f 'one/symlink subdir/symlink' && " +
-				"ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'",
-			InputPaths:   []string{"one/file subdir/file"},
-			OutputPaths:  []string{"one/symlink subdir/symlink"},
-			SymlinkPaths: []string{"one/symlink subdir/symlink"},
-			Mnemonic:     "SolibSymlink",
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actual)
-}
-
-func TestSymlinkMultipleInputs(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }],
- "dep_set_of_files": [{ "id": 1, "direct_artifact_ids": [1,2] }],
- "path_fragments": [
-   { "id": 1, "label": "file" },
-   { "id": 2, "label": "other_file" },
-   { "id": 3, "label": "symlink" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]: [Symlink] []`)
-}
-
-func TestSymlinkMultipleOutputs(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 3, "path_fragment_id": 3 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2,3],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "file" },
-   { "id": 2, "label": "symlink" },
-   { "id": 3,  "label": "other_symlink" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined outputId 2: [Symlink] []")
-}
-
-func TestTemplateExpandActionSubstitutions(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [{
-   "id": 1,
-   "path_fragment_id": 1
- }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "TemplateExpand",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "template_content": "Test template substitutions: %token1%, %python_binary%",
-   "substitutions": [
-     { "key": "%token1%", "value": "abcd" },
-     { "key": "%python_binary%", "value": "python3" }]
- }],
- "path_fragments": [
-   { "id": 1, "label": "template_file" }]
-}`
-
-	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
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " +
-				"chmod a+x template_file'",
-			OutputPaths:  []string{"template_file"},
-			Mnemonic:     "TemplateExpand",
-			SymlinkPaths: []string{},
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actual)
-}
-
-func TestTemplateExpandActionNoOutput(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "TemplateExpand",
-   "configuration_id": 1,
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "templateContent": "Test template substitutions: %token1%, %python_binary%",
-   "substitutions": [
-     { "key": "%token1%", "value": "abcd" },
-     { "key": "%python_binary%", "value": "python3" }]
- }],
- "path_fragments": [
-   { "id": 1, "label": "template_file" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `Expect 1 output to template expand action, got: output []: [TemplateExpand] []`)
-}
-
-func TestFileWrite(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "FileWrite",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "file_contents": "file data\n"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" }]
-}
-`
-	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{
-		&BuildStatement{
-			OutputPaths:  []string{"foo.manifest"},
-			Mnemonic:     "FileWrite",
-			FileContents: "file data\n",
-			SymlinkPaths: []string{},
-		},
-	}, actual)
-}
-
-func TestSourceSymlinkManifest(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SourceSymlinkManifest",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "file_contents": "symlink target\n"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" }]
-}
-`
-	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{
-		&BuildStatement{
-			OutputPaths:  []string{"foo.manifest"},
-			Mnemonic:     "SourceSymlinkManifest",
-			SymlinkPaths: []string{},
-		},
-	}, 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 {
-		t.Errorf("expected error '%s', but got no error", expected)
-	} else if err.Error() != expected {
-		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
-	}
-}
-
-// Asserts that the given actual build statements match the given expected build statements.
-// Build statement equivalence is determined using buildStatementEquals.
-func assertBuildStatements(t *testing.T, expected []*BuildStatement, actual []*BuildStatement) {
-	t.Helper()
-	if len(expected) != len(actual) {
-		t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v",
-			len(expected), len(actual), expected, actual)
-		return
-	}
-	type compareFn = func(i int, j int) bool
-	byCommand := func(slice []*BuildStatement) compareFn {
-		return func(i int, j int) bool {
-			if slice[i] == nil {
-				return false
-			} else if slice[j] == nil {
-				return false
-			}
-			return slice[i].Command < slice[j].Command
-		}
-	}
-	sort.SliceStable(expected, byCommand(expected))
-	sort.SliceStable(actual, byCommand(actual))
-	for i, actualStatement := range actual {
-		expectedStatement := expected[i]
-		if differingField := buildStatementEquals(actualStatement, expectedStatement); differingField != "" {
-			t.Errorf("%s differs\nunexpected build statement %#v.\nexpected: %#v",
-				differingField, actualStatement, expectedStatement)
-			return
-		}
-	}
-}
-
-func buildStatementEquals(first *BuildStatement, second *BuildStatement) string {
-	if (first == nil) != (second == nil) {
-		return "Nil"
-	}
-	if first.Mnemonic != second.Mnemonic {
-		return "Mnemonic"
-	}
-	if first.Command != second.Command {
-		return "Command"
-	}
-	// Ordering is significant for environment variables.
-	if !reflect.DeepEqual(first.Env, second.Env) {
-		return "Env"
-	}
-	// Ordering is irrelevant for input and output paths, so compare sets.
-	if !reflect.DeepEqual(sortedStrings(first.InputPaths), sortedStrings(second.InputPaths)) {
-		return "InputPaths"
-	}
-	if !reflect.DeepEqual(sortedStrings(first.OutputPaths), sortedStrings(second.OutputPaths)) {
-		return "OutputPaths"
-	}
-	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"
-	}
-	return ""
-}
-
-func sortedStrings(stringSlice []string) []string {
-	sorted := make([]string, len(stringSlice))
-	copy(sorted, stringSlice)
-	sort.Strings(sorted)
-	return sorted
-}
-
-// Transform the json format to ActionGraphContainer
-func JsonToActionGraphContainer(inputString string) ([]byte, error) {
-	var aqueryProtoResult analysis_v2_proto.ActionGraphContainer
-	err := json.Unmarshal([]byte(inputString), &aqueryProtoResult)
-	if err != nil {
-		return []byte(""), err
-	}
-	data, _ := proto.Marshal(&aqueryProtoResult)
-	return data, err
-}
diff --git a/bazel/bazel_proxy.go b/bazel/bazel_proxy.go
deleted file mode 100644
index 229818d..0000000
--- a/bazel/bazel_proxy.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2023 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 bazel
-
-import (
-	"bytes"
-	"encoding/gob"
-	"fmt"
-	"net"
-	os_lib "os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-	"time"
-)
-
-// Logs events of ProxyServer.
-type ServerLogger interface {
-	Fatal(v ...interface{})
-	Fatalf(format string, v ...interface{})
-	Println(v ...interface{})
-}
-
-// CmdRequest is a request to the Bazel Proxy server.
-type CmdRequest struct {
-	// Args to the Bazel command.
-	Argv []string
-	// Environment variables to pass to the Bazel invocation. Strings should be of
-	// the form "KEY=VALUE".
-	Env []string
-}
-
-// CmdResponse is a response from the Bazel Proxy server.
-type CmdResponse struct {
-	Stdout      string
-	Stderr      string
-	ErrorString string
-}
-
-// ProxyClient is a client which can issue Bazel commands to the Bazel
-// proxy server. Requests are issued (and responses received) via a unix socket.
-// See ProxyServer for more details.
-type ProxyClient struct {
-	outDir string
-}
-
-// ProxyServer is a server which runs as a background goroutine. Each
-// request to the server describes a Bazel command which the server should run.
-// The server then issues the Bazel command, and returns a response describing
-// the stdout/stderr of the command.
-// Client-server communication is done via a unix socket under the output
-// directory.
-// The server is intended to circumvent sandboxing for subprocesses of the
-// build. The build orchestrator (soong_ui) can launch a server to exist outside
-// of sandboxing, and sandboxed processes (such as soong_build) can issue
-// bazel commands through this socket tunnel. This allows a sandboxed process
-// to issue bazel requests to a bazel that resides outside of sandbox. This
-// is particularly useful to maintain a persistent Bazel server which lives
-// past the duration of a single build.
-// The ProxyServer will only live as long as soong_ui does; the
-// underlying Bazel server will live past the duration of the build.
-type ProxyServer struct {
-	logger          ServerLogger
-	outDir          string
-	workspaceDir    string
-	bazeliskVersion string
-	// The server goroutine will listen on this channel and stop handling requests
-	// once it is written to.
-	done chan struct{}
-}
-
-// NewProxyClient is a constructor for a ProxyClient.
-func NewProxyClient(outDir string) *ProxyClient {
-	return &ProxyClient{
-		outDir: outDir,
-	}
-}
-
-func unixSocketPath(outDir string) string {
-	return filepath.Join(outDir, "bazelsocket.sock")
-}
-
-// IssueCommand issues a request to the Bazel Proxy Server to issue a Bazel
-// request. Returns a response describing the output from the Bazel process
-// (if the Bazel process had an error, then the response will include an error).
-// Returns an error if there was an issue with the connection to the Bazel Proxy
-// server.
-func (b *ProxyClient) IssueCommand(req CmdRequest) (CmdResponse, error) {
-	var resp CmdResponse
-	var err error
-	// Check for connections every 1 second. This is chosen to be a relatively
-	// short timeout, because the proxy server should accept requests quite
-	// quickly.
-	d := net.Dialer{Timeout: 1 * time.Second}
-	var conn net.Conn
-	conn, err = d.Dial("unix", unixSocketPath(b.outDir))
-	if err != nil {
-		return resp, err
-	}
-	defer conn.Close()
-
-	enc := gob.NewEncoder(conn)
-	if err = enc.Encode(req); err != nil {
-		return resp, err
-	}
-	dec := gob.NewDecoder(conn)
-	err = dec.Decode(&resp)
-	return resp, err
-}
-
-// NewProxyServer is a constructor for a ProxyServer.
-func NewProxyServer(logger ServerLogger, outDir string, workspaceDir string, bazeliskVersion string) *ProxyServer {
-	if len(bazeliskVersion) > 0 {
-		logger.Println("** Using Bazelisk for this build, due to env var USE_BAZEL_VERSION=" + bazeliskVersion + " **")
-	}
-
-	return &ProxyServer{
-		logger:          logger,
-		outDir:          outDir,
-		workspaceDir:    workspaceDir,
-		done:            make(chan struct{}),
-		bazeliskVersion: bazeliskVersion,
-	}
-}
-
-func ExecBazel(bazelPath string, workspaceDir string, request CmdRequest) (stdout []byte, stderr []byte, cmdErr error) {
-	bazelCmd := exec.Command(bazelPath, request.Argv...)
-	bazelCmd.Dir = workspaceDir
-	bazelCmd.Env = request.Env
-
-	stderrBuffer := &bytes.Buffer{}
-	bazelCmd.Stderr = stderrBuffer
-
-	if output, err := bazelCmd.Output(); err != nil {
-		cmdErr = fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
-			err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderrBuffer)
-	} else {
-		stdout = output
-	}
-	stderr = stderrBuffer.Bytes()
-	return
-}
-
-func (b *ProxyServer) handleRequest(conn net.Conn) error {
-	defer conn.Close()
-
-	dec := gob.NewDecoder(conn)
-	var req CmdRequest
-	if err := dec.Decode(&req); err != nil {
-		return fmt.Errorf("Error decoding request: %s", err)
-	}
-
-	if len(b.bazeliskVersion) > 0 {
-		req.Env = append(req.Env, "USE_BAZEL_VERSION="+b.bazeliskVersion)
-	}
-	stdout, stderr, cmdErr := ExecBazel("./build/bazel/bin/bazel", b.workspaceDir, req)
-	errorString := ""
-	if cmdErr != nil {
-		errorString = cmdErr.Error()
-	}
-
-	resp := CmdResponse{string(stdout), string(stderr), errorString}
-	enc := gob.NewEncoder(conn)
-	if err := enc.Encode(&resp); err != nil {
-		return fmt.Errorf("Error encoding response: %s", err)
-	}
-	return nil
-}
-
-func (b *ProxyServer) listenUntilClosed(listener net.Listener) error {
-	for {
-		// Check for connections every 1 second. This is a blocking operation, so
-		// if the server is closed, the goroutine will not fully close until this
-		// deadline is reached. Thus, this deadline is short (but not too short
-		// so that the routine churns).
-		listener.(*net.UnixListener).SetDeadline(time.Now().Add(time.Second))
-		conn, err := listener.Accept()
-
-		select {
-		case <-b.done:
-			return nil
-		default:
-		}
-
-		if err != nil {
-			if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
-				// Timeout is normal and expected while waiting for client to establish
-				// a connection.
-				continue
-			} else {
-				b.logger.Fatalf("Listener error: %s", err)
-			}
-		}
-
-		err = b.handleRequest(conn)
-		if err != nil {
-			b.logger.Fatal(err)
-		}
-	}
-}
-
-// Start initializes the server unix socket and (in a separate goroutine)
-// handles requests on the socket until the server is closed. Returns an error
-// if a failure occurs during initialization. Will log any post-initialization
-// errors to the server's logger.
-func (b *ProxyServer) Start() error {
-	unixSocketAddr := unixSocketPath(b.outDir)
-	if err := os_lib.RemoveAll(unixSocketAddr); err != nil {
-		return fmt.Errorf("couldn't remove socket '%s': %s", unixSocketAddr, err)
-	}
-	listener, err := net.Listen("unix", unixSocketAddr)
-
-	if err != nil {
-		return fmt.Errorf("error listening on socket '%s': %s", unixSocketAddr, err)
-	}
-
-	go b.listenUntilClosed(listener)
-	return nil
-}
-
-// Close shuts down the server. This will stop the server from listening for
-// additional requests.
-func (b *ProxyServer) Close() {
-	b.done <- struct{}{}
-}
diff --git a/bazel/constants.go b/bazel/constants.go
deleted file mode 100644
index b10f256..0000000
--- a/bazel/constants.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package bazel
-
-type RunName string
-
-// Below is a list bazel execution run names used through out the
-// Platform Build systems. Each run name represents an unique key
-// to query the bazel metrics.
-const (
-	// Perform a bazel build of the phony root to generate symlink forests
-	// for dependencies of the bazel build.
-	BazelBuildPhonyRootRunName = RunName("bazel-build-phony-root")
-
-	// Perform aquery of the bazel build root to retrieve action information.
-	AqueryBuildRootRunName = RunName("aquery-buildroot")
-
-	// Perform cquery of the Bazel build root and its dependencies.
-	CqueryBuildRootRunName = RunName("cquery-buildroot")
-
-	// Run bazel as a ninja executer
-	BazelNinjaExecRunName = RunName("bazel-ninja-exec")
-
-	SoongInjectionDirName = "soong_injection"
-
-	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT."
-)
-
-// String returns the name of the run.
-func (c RunName) String() string {
-	return string(c)
-}
diff --git a/bazel/cquery/Android.bp b/bazel/cquery/Android.bp
deleted file mode 100644
index 74f7721..0000000
--- a/bazel/cquery/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-cquery",
-    pkgPath: "android/soong/bazel/cquery",
-    srcs: [
-        "request_type.go",
-    ],
-    pluginFor: [
-        "soong_build",
-    ],
-    testSrcs: [
-        "request_type_test.go",
-    ],
-}
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
deleted file mode 100644
index 791c6bc..0000000
--- a/bazel/cquery/request_type.go
+++ /dev/null
@@ -1,426 +0,0 @@
-package cquery
-
-import (
-	"encoding/json"
-	"fmt"
-	"strings"
-)
-
-var (
-	GetOutputFiles      = &getOutputFilesRequestType{}
-	GetCcInfo           = &getCcInfoType{}
-	GetApexInfo         = &getApexInfoType{}
-	GetCcUnstrippedInfo = &getCcUnstrippedInfoType{}
-	GetPrebuiltFileInfo = &getPrebuiltFileInfo{}
-)
-
-type CcAndroidMkInfo struct {
-	LocalStaticLibs      []string
-	LocalWholeStaticLibs []string
-	LocalSharedLibs      []string
-}
-
-type CcInfo struct {
-	CcAndroidMkInfo
-	OutputFiles          []string
-	CcObjectFiles        []string
-	CcSharedLibraryFiles []string
-	CcStaticLibraryFiles []string
-	Includes             []string
-	SystemIncludes       []string
-	Headers              []string
-	// Archives owned by the current target (not by its dependencies). These will
-	// be a subset of OutputFiles. (or static libraries, this will be equal to OutputFiles,
-	// but general cc_library will also have dynamic libraries in output files).
-	RootStaticArchives []string
-	// Dynamic libraries (.so files) created by the current target. These will
-	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
-	// but general cc_library will also have dynamic libraries in output files).
-	RootDynamicLibraries []string
-	TidyFiles            []string
-	TocFile              string
-	UnstrippedOutput     string
-	AbiDiffFiles         []string
-}
-
-type getOutputFilesRequestType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getOutputFilesRequestType) Name() string {
-	return "getOutputFiles"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getOutputFilesRequestType) StarlarkFunctionBody() string {
-	return "return ', '.join([f.path for f in target.files.to_list()])"
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getOutputFilesRequestType) ParseResult(rawString string) []string {
-	return splitOrEmpty(rawString, ", ")
-}
-
-type getCcInfoType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getCcInfoType) Name() string {
-	return "getCcInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getCcInfoType) StarlarkFunctionBody() string {
-	return `
-outputFiles = [f.path for f in target.files.to_list()]
-p = providers(target)
-cc_info = p.get("CcInfo")
-if not cc_info:
-  fail("%s did not provide CcInfo" % id_string)
-
-includes = cc_info.compilation_context.includes.to_list()
-system_includes = cc_info.compilation_context.system_includes.to_list()
-headers = [f.path for f in cc_info.compilation_context.headers.to_list()]
-
-ccObjectFiles = []
-staticLibraries = []
-rootStaticArchives = []
-linker_inputs = cc_info.linking_context.linker_inputs.to_list()
-
-static_info_tag = "//build/bazel/rules/cc:cc_library_static.bzl%CcStaticLibraryInfo"
-if static_info_tag in p:
-  static_info = p[static_info_tag]
-  ccObjectFiles = [f.path for f in static_info.objects]
-  rootStaticArchives = [static_info.root_static_archive.path]
-else:
-  for linker_input in linker_inputs:
-    for library in linker_input.libraries:
-      for object in library.objects:
-        ccObjectFiles += [object.path]
-      if library.static_library:
-        staticLibraries.append(library.static_library.path)
-        if linker_input.owner == target.label:
-          rootStaticArchives.append(library.static_library.path)
-
-sharedLibraries = []
-rootSharedLibraries = []
-
-shared_info_tag = "//build/bazel/rules/cc:cc_library_shared.bzl%CcSharedLibraryOutputInfo"
-stubs_tag = "//build/bazel/rules/cc:cc_stub_library.bzl%CcStubInfo"
-unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
-unstripped = ""
-
-if shared_info_tag in p:
-  shared_info = p[shared_info_tag]
-  path = shared_info.output_file.path
-  sharedLibraries.append(path)
-  rootSharedLibraries += [path]
-  unstripped = path
-  if unstripped_tag in p:
-    unstripped = p[unstripped_tag].unstripped.path
-elif stubs_tag in p:
-  rootSharedLibraries.extend([f.path for f in target.files.to_list()])
-else:
-  for linker_input in linker_inputs:
-    for library in linker_input.libraries:
-      if library.dynamic_library:
-        path = library.dynamic_library.path
-        sharedLibraries.append(path)
-        if linker_input.owner == target.label:
-          rootSharedLibraries.append(path)
-
-toc_file = ""
-toc_file_tag = "//build/bazel/rules/cc:generate_toc.bzl%CcTocInfo"
-if toc_file_tag in p:
-  toc_file = p[toc_file_tag].toc.path
-else:
-  # NOTE: It's OK if there's no ToC, as Soong just uses it for optimization
-  pass
-
-tidy_files = []
-clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-  tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-abi_diff_files = []
-abi_diff_info = p.get("//build/bazel/rules/abi:abi_dump.bzl%AbiDiffInfo")
-if abi_diff_info:
-  abi_diff_files = [f.path for f in abi_diff_info.diff_files.to_list()]
-
-local_static_libs = []
-local_whole_static_libs = []
-local_shared_libs = []
-androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
-if androidmk_tag in p:
-    androidmk_info = p[androidmk_tag]
-    local_static_libs = androidmk_info.local_static_libs
-    local_whole_static_libs = androidmk_info.local_whole_static_libs
-    local_shared_libs = androidmk_info.local_shared_libs
-
-return json.encode({
-    "OutputFiles": outputFiles,
-    "CcObjectFiles": ccObjectFiles,
-    "CcSharedLibraryFiles": sharedLibraries,
-    "CcStaticLibraryFiles": staticLibraries,
-    "Includes": includes,
-    "SystemIncludes": system_includes,
-    "Headers": headers,
-    "RootStaticArchives": rootStaticArchives,
-    "RootDynamicLibraries": rootSharedLibraries,
-    "TidyFiles": [t for t in tidy_files],
-    "TocFile": toc_file,
-    "UnstrippedOutput": unstripped,
-    "AbiDiffFiles": abi_diff_files,
-    "LocalStaticLibs": [l for l in local_static_libs],
-    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
-    "LocalSharedLibs": [l for l in local_shared_libs],
-})`
-
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) {
-	var ccInfo CcInfo
-	if err := parseJson(rawString, &ccInfo); err != nil {
-		return ccInfo, err
-	}
-	return ccInfo, nil
-}
-
-// Query Bazel for the artifacts generated by the apex modules.
-type getApexInfoType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getApexInfoType) Name() string {
-	return "getApexInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information. The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getApexInfoType) StarlarkFunctionBody() string {
-	return `
-info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexInfo")
-if not info:
-  fail("%s did not provide ApexInfo" % id_string)
-bundle_key_info = info.bundle_key_info
-container_key_info = info.container_key_info
-
-signed_compressed_output = "" # no .capex if the apex is not compressible, cannot be None as it needs to be json encoded.
-if info.signed_compressed_output:
-    signed_compressed_output = info.signed_compressed_output.path
-
-mk_info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexMkInfo")
-if not mk_info:
-  fail("%s did not provide ApexMkInfo" % id_string)
-
-tidy_files = []
-clang_tidy_info = providers(target).get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-return json.encode({
-    "signed_output": info.signed_output.path,
-    "signed_compressed_output": signed_compressed_output,
-    "unsigned_output": info.unsigned_output.path,
-    "provides_native_libs": [str(lib) for lib in info.provides_native_libs],
-    "requires_native_libs": [str(lib) for lib in info.requires_native_libs],
-    "bundle_key_info": [bundle_key_info.public_key.path, bundle_key_info.private_key.path],
-    "container_key_info": [container_key_info.pem.path, container_key_info.pk8.path, container_key_info.key_name],
-    "package_name": info.package_name,
-    "symbols_used_by_apex": info.symbols_used_by_apex.path,
-    "java_symbols_used_by_apex": info.java_symbols_used_by_apex.path,
-    "backing_libs": info.backing_libs.path,
-    "bundle_file": info.base_with_config_zip.path,
-    "installed_files": info.installed_files.path,
-    "make_modules_to_install": mk_info.make_modules_to_install,
-    "files_info": mk_info.files_info,
-    "tidy_files": [t for t in tidy_files],
-})`
-}
-
-type ApexInfo struct {
-	// From the ApexInfo provider
-	SignedOutput           string   `json:"signed_output"`
-	SignedCompressedOutput string   `json:"signed_compressed_output"`
-	UnsignedOutput         string   `json:"unsigned_output"`
-	ProvidesLibs           []string `json:"provides_native_libs"`
-	RequiresLibs           []string `json:"requires_native_libs"`
-	BundleKeyInfo          []string `json:"bundle_key_info"`
-	ContainerKeyInfo       []string `json:"container_key_info"`
-	PackageName            string   `json:"package_name"`
-	SymbolsUsedByApex      string   `json:"symbols_used_by_apex"`
-	JavaSymbolsUsedByApex  string   `json:"java_symbols_used_by_apex"`
-	BackingLibs            string   `json:"backing_libs"`
-	BundleFile             string   `json:"bundle_file"`
-	InstalledFiles         string   `json:"installed_files"`
-	TidyFiles              []string `json:"tidy_files"`
-
-	// From the ApexMkInfo provider
-	MakeModulesToInstall []string            `json:"make_modules_to_install"`
-	PayloadFilesInfo     []map[string]string `json:"files_info"`
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getApexInfoType) ParseResult(rawString string) (ApexInfo, error) {
-	var info ApexInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
-
-// getCcUnstrippedInfoType implements cqueryRequest interface. It handles the
-// interaction with `bazel cquery` to retrieve CcUnstrippedInfo provided
-// by the` cc_binary` and `cc_shared_library` rules.
-type getCcUnstrippedInfoType struct{}
-
-func (g getCcUnstrippedInfoType) Name() string {
-	return "getCcUnstrippedInfo"
-}
-
-func (g getCcUnstrippedInfoType) StarlarkFunctionBody() string {
-	return `
-p = providers(target)
-output_path = target.files.to_list()[0].path
-
-unstripped = output_path
-unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
-if unstripped_tag in p:
-    unstripped_info = p[unstripped_tag]
-    unstripped = unstripped_info.unstripped[0].files.to_list()[0].path
-
-local_static_libs = []
-local_whole_static_libs = []
-local_shared_libs = []
-androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
-if androidmk_tag in p:
-    androidmk_info = p[androidmk_tag]
-    local_static_libs = androidmk_info.local_static_libs
-    local_whole_static_libs = androidmk_info.local_whole_static_libs
-    local_shared_libs = androidmk_info.local_shared_libs
-
-tidy_files = []
-clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-return json.encode({
-    "OutputFile":  output_path,
-    "UnstrippedOutput": unstripped,
-    "LocalStaticLibs": [l for l in local_static_libs],
-    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
-    "LocalSharedLibs": [l for l in local_shared_libs],
-    "TidyFiles": [t for t in tidy_files],
-})
-`
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getCcUnstrippedInfoType) ParseResult(rawString string) (CcUnstrippedInfo, error) {
-	var info CcUnstrippedInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
-
-type CcUnstrippedInfo struct {
-	CcAndroidMkInfo
-	OutputFile       string
-	UnstrippedOutput string
-	TidyFiles        []string
-}
-
-// splitOrEmpty is a modification of strings.Split() that returns an empty list
-// if the given string is empty.
-func splitOrEmpty(s string, sep string) []string {
-	if len(s) < 1 {
-		return []string{}
-	} else {
-		return strings.Split(s, sep)
-	}
-}
-
-// parseJson decodes json string into the fields of the receiver.
-// Unknown attribute name causes panic.
-func parseJson(jsonString string, info interface{}) error {
-	decoder := json.NewDecoder(strings.NewReader(jsonString))
-	decoder.DisallowUnknownFields() //useful to detect typos, e.g. in unit tests
-	err := decoder.Decode(info)
-	if err != nil {
-		return fmt.Errorf("cannot parse cquery result '%s': %s", jsonString, err)
-	}
-	return nil
-}
-
-type getPrebuiltFileInfo struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getPrebuiltFileInfo) Name() string {
-	return "getPrebuiltFileInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getPrebuiltFileInfo) StarlarkFunctionBody() string {
-	return `
-p = providers(target)
-prebuilt_file_info = p.get("//build/bazel/rules:prebuilt_file.bzl%PrebuiltFileInfo")
-if not prebuilt_file_info:
-  fail("%s did not provide PrebuiltFileInfo" % id_string)
-
-return json.encode({
-	"Src": prebuilt_file_info.src.path,
-	"Dir": prebuilt_file_info.dir,
-	"Filename": prebuilt_file_info.filename,
-	"Installable": prebuilt_file_info.installable,
-})`
-}
-
-type PrebuiltFileInfo struct {
-	// TODO: b/207489266 - Fully support all properties in prebuilt_file
-	Src         string
-	Dir         string
-	Filename    string
-	Installable bool
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getPrebuiltFileInfo) ParseResult(rawString string) (PrebuiltFileInfo, error) {
-	var info PrebuiltFileInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
deleted file mode 100644
index e772bb7..0000000
--- a/bazel/cquery/request_type_test.go
+++ /dev/null
@@ -1,281 +0,0 @@
-package cquery
-
-import (
-	"encoding/json"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-func TestGetOutputFilesParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput []string
-	}{
-		{
-			description:    "no result",
-			input:          "",
-			expectedOutput: []string{},
-		},
-		{
-			description:    "one result",
-			input:          "test",
-			expectedOutput: []string{"test"},
-		},
-		{
-			description:    "splits on comma with space",
-			input:          "foo, bar",
-			expectedOutput: []string{"foo", "bar"},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput := GetOutputFiles.ParseResult(tc.input)
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcInfoParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		inputCcInfo    CcInfo
-		expectedOutput CcInfo
-	}{
-		{
-			description:    "no result",
-			inputCcInfo:    CcInfo{},
-			expectedOutput: CcInfo{},
-		},
-		{
-			description: "all items set",
-			inputCcInfo: CcInfo{
-				OutputFiles:          []string{"out1", "out2"},
-				CcObjectFiles:        []string{"object1", "object2"},
-				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
-				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
-				Includes:             []string{".", "dir/subdir"},
-				SystemIncludes:       []string{"system/dir", "system/other/dir"},
-				Headers:              []string{"dir/subdir/hdr.h"},
-				RootStaticArchives:   []string{"rootstaticarchive1"},
-				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
-				TocFile:              "lib.so.toc",
-			},
-			expectedOutput: CcInfo{
-				OutputFiles:          []string{"out1", "out2"},
-				CcObjectFiles:        []string{"object1", "object2"},
-				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
-				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
-				Includes:             []string{".", "dir/subdir"},
-				SystemIncludes:       []string{"system/dir", "system/other/dir"},
-				Headers:              []string{"dir/subdir/hdr.h"},
-				RootStaticArchives:   []string{"rootstaticarchive1"},
-				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
-				TocFile:              "lib.so.toc",
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			jsonInput, _ := json.Marshal(tc.inputCcInfo)
-			actualOutput, err := GetCcInfo.ParseResult(string(jsonInput))
-			if err != nil {
-				t.Errorf("error parsing result: %q", err)
-			} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v\n!= actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcInfoParseResultsError(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"toc_file": "dir/file.so.toc"
-}`,
-			expectedError: `json: unknown field "toc_file"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetCcInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
-
-func TestGetApexInfoParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput ApexInfo
-	}{
-		{
-			description:    "no result",
-			input:          "{}",
-			expectedOutput: ApexInfo{},
-		},
-		{
-			description: "one result",
-			input: `{
-	"signed_output":"my.apex",
-	"unsigned_output":"my.apex.unsigned",
-	"requires_native_libs":["//bionic/libc:libc","//bionic/libdl:libdl"],
-	"bundle_key_info":["foo.pem", "foo.privkey"],
-	"container_key_info":["foo.x509.pem", "foo.pk8", "foo"],
-	"package_name":"package.name",
-	"symbols_used_by_apex": "path/to/my.apex_using.txt",
-	"backing_libs":"path/to/backing.txt",
-	"bundle_file": "dir/bundlefile.zip",
-	"installed_files":"path/to/installed-files.txt",
-	"provides_native_libs":[],
-	"make_modules_to_install": ["foo","bar"]
-}`,
-			expectedOutput: ApexInfo{
-				// ApexInfo
-				SignedOutput:      "my.apex",
-				UnsignedOutput:    "my.apex.unsigned",
-				RequiresLibs:      []string{"//bionic/libc:libc", "//bionic/libdl:libdl"},
-				ProvidesLibs:      []string{},
-				BundleKeyInfo:     []string{"foo.pem", "foo.privkey"},
-				ContainerKeyInfo:  []string{"foo.x509.pem", "foo.pk8", "foo"},
-				PackageName:       "package.name",
-				SymbolsUsedByApex: "path/to/my.apex_using.txt",
-				BackingLibs:       "path/to/backing.txt",
-				BundleFile:        "dir/bundlefile.zip",
-				InstalledFiles:    "path/to/installed-files.txt",
-
-				// ApexMkInfo
-				MakeModulesToInstall: []string{"foo", "bar"},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput, err := GetApexInfo.ParseResult(tc.input)
-			if err != nil {
-				t.Errorf("Unexpected error %q", err)
-			}
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetApexInfoParseResultsError(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"fake_field": "path/to/file"
-}`,
-			expectedError: `json: unknown field "fake_field"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetApexInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
-
-func TestGetCcUnstrippedParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput CcUnstrippedInfo
-	}{
-		{
-			description:    "no result",
-			input:          "{}",
-			expectedOutput: CcUnstrippedInfo{},
-		},
-		{
-			description: "one result",
-			input:       `{"OutputFile":"myapp", "UnstrippedOutput":"myapp_unstripped"}`,
-			expectedOutput: CcUnstrippedInfo{
-				OutputFile:       "myapp",
-				UnstrippedOutput: "myapp_unstripped",
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput, err := GetCcUnstrippedInfo.ParseResult(tc.input)
-			if err != nil {
-				t.Errorf("Unexpected error %q", err)
-			}
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcUnstrippedParseResultsErrors(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"fake_field": "path/to/file"
-}`,
-			expectedError: `json: unknown field "fake_field"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetCcUnstrippedInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
diff --git a/bin/soongdbg b/bin/soongdbg
index bfdbbde..a73bdf9 100755
--- a/bin/soongdbg
+++ b/bin/soongdbg
@@ -32,11 +32,13 @@
                 dep.rdeps.add(node)
                 node.dep_tags.setdefault(dep, list()).append(d)
 
-    def find_paths(self, id1, id2):
+    def find_paths(self, id1, id2, tag_filter):
         # Throws KeyError if one of the names isn't found
         def recurse(node1, node2, visited):
             result = set()
             for dep in node1.rdeps:
+                if not matches_tag(dep, node1, tag_filter):
+                    continue
                 if dep == node2:
                     result.add(node2)
                 if dep not in visited:
@@ -214,6 +216,8 @@
                         help="jq query for each module metadata")
     parser.add_argument("--deptags", action="store_true",
                         help="show dependency tags (makes the graph much more complex)")
+    parser.add_argument("--tag", action="append",
+                        help="Limit output to these dependency tags.")
 
     group = parser.add_argument_group("output formats",
                                       "If no format is provided, a dot file will be written to"
@@ -259,13 +263,21 @@
         sys.stdout.write(text)
 
 
-def get_deps(nodes, root, maxdepth, reverse):
+def matches_tag(node, dep, tag_filter):
+    if not tag_filter:
+        return True
+    return not tag_filter.isdisjoint([t.tag_type for t in node.dep_tags[dep]])
+
+
+def get_deps(nodes, root, maxdepth, reverse, tag_filter):
     if root in nodes:
         return
     nodes.add(root)
     if maxdepth != 0:
         for dep in (root.rdeps if reverse else root.deps):
-            get_deps(nodes, dep, maxdepth-1, reverse)
+            if not matches_tag(root, dep, tag_filter):
+                continue
+            get_deps(nodes, dep, maxdepth-1, reverse, tag_filter)
 
 
 def new_module_formatter(args):
@@ -302,7 +314,7 @@
 
     def run(self, args):
         graph = load_graph()
-        print_nodes(args, graph.find_paths(args.module[0], args.module[1]),
+        print_nodes(args, graph.find_paths(args.module[0], args.module[1], set(args.tag)),
                     new_module_formatter(args))
 
 
@@ -328,7 +340,7 @@
                 sys.stderr.write(f"error: Can't find root: {id}\n")
                 err = True
                 continue
-            get_deps(nodes, root, args.depth, args.reverse)
+            get_deps(nodes, root, args.depth, args.reverse, set(args.tag))
         if err:
             sys.exit(1)
         print_nodes(args, nodes, new_module_formatter(args))
diff --git a/bpf/bpf.go b/bpf/bpf.go
index ce00b5b..09262e5 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -104,6 +104,14 @@
 
 func (bpf *bpf) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (bpf *bpf) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return proptools.Bool(bpf.properties.Vendor)
+}
+
+func (bpf *bpf) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
 func (bpf *bpf) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !proptools.Bool(bpf.properties.Vendor)
 }
@@ -125,9 +133,6 @@
 }
 
 func (bpf *bpf) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	if proptools.Bool(bpf.properties.Vendor) {
-		return []string{"vendor"}
-	}
 	return nil
 }
 
diff --git a/cc/binary.go b/cc/binary.go
index 3ff35de..2ac9a45 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -451,7 +451,7 @@
 }
 
 func (binary *binaryDecorator) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (binary *binaryDecorator) setSymlinkList(ctx ModuleContext) {
diff --git a/cc/cc.go b/cc/cc.go
index df0aa6d..3a8932c 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -361,6 +361,8 @@
 	Recovery_available *bool
 
 	// Used by imageMutator, set by ImageMutatorBegin()
+	VendorVariantNeeded        bool `blueprint:"mutated"`
+	ProductVariantNeeded       bool `blueprint:"mutated"`
 	CoreVariantNeeded          bool `blueprint:"mutated"`
 	RamdiskVariantNeeded       bool `blueprint:"mutated"`
 	VendorRamdiskVariantNeeded bool `blueprint:"mutated"`
@@ -981,8 +983,8 @@
 	return c.Properties.HideFromMake
 }
 
-func (c *Module) RequiredModuleNames() []string {
-	required := android.CopyOf(c.ModuleBase.RequiredModuleNames())
+func (c *Module) RequiredModuleNames(ctx android.ConfigAndErrorContext) []string {
+	required := android.CopyOf(c.ModuleBase.RequiredModuleNames(ctx))
 	if c.ImageVariation().Variation == android.CoreVariation {
 		required = append(required, c.Properties.Target.Platform.Required...)
 		required = removeListFromList(required, c.Properties.Target.Platform.Exclude_required)
@@ -2117,8 +2119,58 @@
 		if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
 			moduleInfoJSON.Uninstallable = true
 		}
-
 	}
+
+	buildComplianceMetadataInfo(ctx, c, deps)
+
+	c.setOutputFiles(ctx)
+}
+
+func (c *Module) setOutputFiles(ctx ModuleContext) {
+	if c.outputFile.Valid() {
+		ctx.SetOutputFiles(android.Paths{c.outputFile.Path()}, "")
+	} else {
+		ctx.SetOutputFiles(android.Paths{}, "")
+	}
+	if c.linker != nil {
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), "unstripped")
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), "stripped_all")
+	}
+}
+
+func buildComplianceMetadataInfo(ctx ModuleContext, c *Module, deps PathDeps) {
+	// Dump metadata that can not be done in android/compliance-metadata.go
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	complianceMetadataInfo.SetStringValue(android.ComplianceMetadataProp.IS_STATIC_LIB, strconv.FormatBool(ctx.static()))
+	complianceMetadataInfo.SetStringValue(android.ComplianceMetadataProp.BUILT_FILES, c.outputFile.String())
+
+	// Static deps
+	staticDeps := ctx.GetDirectDepsWithTag(StaticDepTag(false))
+	staticDepNames := make([]string, 0, len(staticDeps))
+	for _, dep := range staticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+
+	staticDepPaths := make([]string, 0, len(deps.StaticLibs))
+	for _, dep := range deps.StaticLibs {
+		staticDepPaths = append(staticDepPaths, dep.String())
+	}
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.FirstUniqueStrings(staticDepPaths))
+
+	// Whole static deps
+	wholeStaticDeps := ctx.GetDirectDepsWithTag(StaticDepTag(true))
+	wholeStaticDepNames := make([]string, 0, len(wholeStaticDeps))
+	for _, dep := range wholeStaticDeps {
+		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
+	}
+
+	wholeStaticDepPaths := make([]string, 0, len(deps.WholeStaticLibs))
+	for _, dep := range deps.WholeStaticLibs {
+		wholeStaticDepPaths = append(wholeStaticDepPaths, dep.String())
+	}
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEPS, android.FirstUniqueStrings(wholeStaticDepNames))
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES, android.FirstUniqueStrings(wholeStaticDepPaths))
 }
 
 func (c *Module) maybeUnhideFromMake() {
@@ -2509,7 +2561,7 @@
 	if c.ImageVariation().Variation == android.CoreVariation && c.Device() &&
 		c.Target().NativeBridge == android.NativeBridgeDisabled {
 		actx.AddVariationDependencies(
-			[]blueprint.Variation{{Mutator: "image", Variation: VendorVariation}},
+			[]blueprint.Variation{{Mutator: "image", Variation: android.VendorVariation}},
 			llndkHeaderLibTag,
 			deps.LlndkHeaderLibs...)
 	}
@@ -3576,28 +3628,6 @@
 	return c.outputFile
 }
 
-func (c *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if c.outputFile.Valid() {
-			return android.Paths{c.outputFile.Path()}, nil
-		}
-		return android.Paths{}, nil
-	case "unstripped":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), nil
-		}
-		return nil, nil
-	case "stripped_all":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), nil
-		}
-		return nil, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (c *Module) static() bool {
 	if static, ok := c.linker.(interface {
 		static() bool
diff --git a/cc/cc_test.go b/cc/cc_test.go
index c2bb25a..ccdaae5 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -300,13 +300,9 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
-	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Errorf("Expected cc_test to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testBinary := testingModule.Module().(*Module).linker.(*testBinary)
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 		return
@@ -356,12 +352,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -1407,12 +1401,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -2908,8 +2900,6 @@
 				PrepareForIntegrationTestWithCc,
 				android.FixtureAddTextFile("external/foo/Android.bp", bp),
 			).RunTest(t)
-			// Use the arm variant instead of the arm64 variant so that it gets headers from
-			// ndk_libandroid_support to test LateStaticLibs.
 			cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
 
 			var includes []string
@@ -3120,12 +3110,8 @@
  `
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared").Module()
-	outputFile, err := module.(android.OutputFileProducer).OutputFiles("stripped_all")
-	if err != nil {
-		t.Errorf("Expected cc_library to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared")
+	outputFile := testingModule.OutputFiles(t, "stripped_all")
 	if !strings.HasSuffix(outputFile.Strings()[0], "/stripped_all/test_lib.so") {
 		t.Errorf("Unexpected output file: %s", outputFile.Strings()[0])
 		return
diff --git a/cc/cmake_ext_add_aidl_library.txt b/cc/cmake_ext_add_aidl_library.txt
index af5bdf6..aa3235e3 100644
--- a/cc/cmake_ext_add_aidl_library.txt
+++ b/cc/cmake_ext_add_aidl_library.txt
@@ -25,7 +25,7 @@
         endif()
 
         set(DEPFILE_ARG)
-        if (NOT ${CMAKE_GENERATOR} MATCHES "Unix Makefiles")
+        if (NOT ${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
             set(DEPFILE_ARG DEPFILE "${GEN_SOURCE}.d")
         endif()
 
@@ -57,7 +57,7 @@
         "${GEN_DIR}/include"
     )
 
-    if (${LANG} MATCHES "ndk")
+    if (${LANG} STREQUAL "ndk")
         set(BINDER_LIB_NAME "libbinder_ndk_sdk")
     else()
         set(BINDER_LIB_NAME "libbinder_sdk")
diff --git a/cc/cmake_main.txt b/cc/cmake_main.txt
index e9177d6..f6e21a6 100644
--- a/cc/cmake_main.txt
+++ b/cc/cmake_main.txt
@@ -11,7 +11,11 @@
     set(ANDROID_BUILD_TOP "${CMAKE_CURRENT_SOURCE_DIR}")
 endif()
 
-set(PREBUILTS_BIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prebuilts/host/linux-x86/bin")
+if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" MATCHES "^(arm|aarch)")
+    set(PREBUILTS_BIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prebuilts/host/linux_musl-arm64/bin")
+else()
+    set(PREBUILTS_BIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prebuilts/host/linux-x86/bin")
+endif()
 if (NOT AIDL_BIN)
     find_program(AIDL_BIN aidl REQUIRED HINTS "${PREBUILTS_BIN_DIR}")
 endif()
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index b4d1268..c0f0571 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -15,7 +15,6 @@
 package cc
 
 import (
-	"android/soong/android"
 	"bytes"
 	_ "embed"
 	"fmt"
@@ -25,6 +24,8 @@
 	"strings"
 	"text/template"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -61,8 +62,13 @@
 }
 
 var ignoredSystemLibs []string = []string{
+	"crtbegin_dynamic",
+	"crtend_android",
+	"libc",
 	"libc++",
 	"libc++_static",
+	"libdl",
+	"libm",
 	"prebuilt_libclang_rt.builtins",
 	"prebuilt_libclang_rt.ubsan_minimal",
 }
@@ -141,11 +147,7 @@
 			return list.String()
 		},
 		"toStrings": func(files android.Paths) []string {
-			strings := make([]string, len(files))
-			for idx, file := range files {
-				strings[idx] = file.String()
-			}
-			return strings
+			return files.Strings()
 		},
 		"concat5": func(list1 []string, list2 []string, list3 []string, list4 []string, list5 []string) []string {
 			return append(append(append(append(list1, list2...), list3...), list4...), list5...)
@@ -271,7 +273,11 @@
 		{"arch", "x86_64"},
 	}
 	ctx.AddVariationDependencies(variations, cmakeSnapshotModuleTag, m.Properties.Modules...)
-	ctx.AddVariationDependencies(variations, cmakeSnapshotPrebuiltTag, m.Properties.Prebuilts...)
+
+	if len(m.Properties.Prebuilts) > 0 {
+		prebuilts := append(m.Properties.Prebuilts, "libc++")
+		ctx.AddVariationDependencies(variations, cmakeSnapshotPrebuiltTag, prebuilts...)
+	}
 }
 
 func (m *CmakeSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -389,7 +395,8 @@
 
 	// Merging CMakeLists.txt contents for every module directory
 	var makefilesList android.Paths
-	for moduleDir, fragments := range moduleDirs {
+	for _, moduleDir := range android.SortedKeys(moduleDirs) {
+		fragments := moduleDirs[moduleDir]
 		moduleCmakePath := android.PathForModuleGen(ctx, moduleDir, "CMakeLists.txt")
 		makefilesList = append(makefilesList, moduleCmakePath)
 		sort.Strings(fragments)
@@ -429,8 +436,9 @@
 	// Packaging all sources into the zip file
 	if m.Properties.Include_sources {
 		var sourcesList android.Paths
-		for _, file := range sourceFiles {
-			sourcesList = append(sourcesList, file)
+		for _, file := range android.SortedKeys(sourceFiles) {
+			path := sourceFiles[file]
+			sourcesList = append(sourcesList, path)
 		}
 
 		sourcesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_sources.rsp")
@@ -462,15 +470,8 @@
 
 	// Finish generating the final zip file
 	zipRule.Build(m.zipPath.String(), "archiving "+ctx.ModuleName())
-}
 
-func (m *CmakeSnapshot) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{m.zipPath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
+	ctx.SetOutputFiles(android.Paths{m.zipPath}, "")
 }
 
 func (m *CmakeSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/cc/genrule.go b/cc/genrule.go
index cabf787..fe3b127 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -79,6 +79,14 @@
 
 func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (g *GenruleExtraProperties) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return Bool(g.Vendor_available) || Bool(g.Odm_available) || ctx.SocSpecific() || ctx.DeviceSpecific()
+}
+
+func (g *GenruleExtraProperties) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return Bool(g.Product_available) || ctx.ProductSpecific()
+}
+
 func (g *GenruleExtraProperties) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !(ctx.SocSpecific() || ctx.DeviceSpecific() || ctx.ProductSpecific())
 }
@@ -102,18 +110,7 @@
 }
 
 func (g *GenruleExtraProperties) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	var variants []string
-	vendorVariantRequired := Bool(g.Vendor_available) || Bool(g.Odm_available) || ctx.SocSpecific() || ctx.DeviceSpecific()
-	productVariantRequired := Bool(g.Product_available) || ctx.ProductSpecific()
-
-	if vendorVariantRequired {
-		variants = append(variants, VendorVariation)
-	}
-	if productVariantRequired {
-		variants = append(variants, ProductVariation)
-	}
-
-	return variants
+	return nil
 }
 
 func (g *GenruleExtraProperties) SetImageVariation(ctx android.BaseModuleContext, variation string) {
diff --git a/cc/image.go b/cc/image.go
index 48a9174..2e52ccc 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -39,18 +39,10 @@
 )
 
 const (
-	// VendorVariation is the variant name used for /vendor code that does not
-	// compile against the VNDK.
-	VendorVariation = "vendor"
-
 	// VendorVariationPrefix is the variant prefix used for /vendor code that compiles
 	// against the VNDK.
 	VendorVariationPrefix = "vendor."
 
-	// ProductVariation is the variant name used for /product code that does not
-	// compile against the VNDK.
-	ProductVariation = "product"
-
 	// ProductVariationPrefix is the variant prefix used for /product code that compiles
 	// against the VNDK.
 	ProductVariationPrefix = "product."
@@ -117,12 +109,12 @@
 
 // Returns true if the module is "product" variant. Usually these modules are installed in /product
 func (c *Module) InProduct() bool {
-	return c.Properties.ImageVariation == ProductVariation
+	return c.Properties.ImageVariation == android.ProductVariation
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
 func (c *Module) InVendor() bool {
-	return c.Properties.ImageVariation == VendorVariation
+	return c.Properties.ImageVariation == android.VendorVariation
 }
 
 // Returns true if the module is "vendor" or "product" variant. This replaces previous UseVndk usages
@@ -207,6 +199,12 @@
 
 	// SetCoreVariantNeeded sets whether the Core Variant is needed.
 	SetCoreVariantNeeded(b bool)
+
+	// SetProductVariantNeeded sets whether the Product Variant is needed.
+	SetProductVariantNeeded(b bool)
+
+	// SetVendorVariantNeeded sets whether the Vendor Variant is needed.
+	SetVendorVariantNeeded(b bool)
 }
 
 var _ ImageMutatableModule = (*Module)(nil)
@@ -267,6 +265,14 @@
 	m.Properties.CoreVariantNeeded = b
 }
 
+func (m *Module) SetProductVariantNeeded(b bool) {
+	m.Properties.ProductVariantNeeded = b
+}
+
+func (m *Module) SetVendorVariantNeeded(b bool) {
+	m.Properties.VendorVariantNeeded = b
+}
+
 func (m *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
 	if snapshot, ok := m.linker.(SnapshotInterface); ok {
 		return snapshot.Version()
@@ -319,41 +325,34 @@
 		}
 	}
 
+	var vendorVariantNeeded bool = false
+	var productVariantNeeded bool = false
 	var coreVariantNeeded bool = false
 	var ramdiskVariantNeeded bool = false
 	var vendorRamdiskVariantNeeded bool = false
 	var recoveryVariantNeeded bool = false
 
-	var vendorVariants []string
-	var productVariants []string
-
-	needVndkVersionVendorVariantForLlndk := false
-
 	if m.NeedsLlndkVariants() {
 		// This is an LLNDK library.  The implementation of the library will be on /system,
 		// and vendor and product variants will be created with LLNDK stubs.
 		// The LLNDK libraries need vendor variants even if there is no VNDK.
 		coreVariantNeeded = true
-		vendorVariants = append(vendorVariants, "")
-		productVariants = append(productVariants, "")
-		// Generate vendor variants for boardVndkVersion only if the VNDK snapshot does not
-		// provide the LLNDK stub libraries.
-		if needVndkVersionVendorVariantForLlndk {
-			vendorVariants = append(vendorVariants, "")
-		}
+		vendorVariantNeeded = true
+		productVariantNeeded = true
+
 	} else if m.NeedsVendorPublicLibraryVariants() {
 		// A vendor public library has the implementation on /vendor, with stub variants
 		// for system and product.
 		coreVariantNeeded = true
-		vendorVariants = append(vendorVariants, "")
-		productVariants = append(productVariants, "")
+		vendorVariantNeeded = true
+		productVariantNeeded = true
 	} else if m.IsSnapshotPrebuilt() {
 		// Make vendor variants only for the versions in BOARD_VNDK_VERSION and
 		// PRODUCT_EXTRA_VNDK_VERSIONS.
 		if m.InstallInRecovery() {
 			recoveryVariantNeeded = true
 		} else {
-			vendorVariants = append(vendorVariants, m.SnapshotVersion(mctx))
+			m.AppendExtraVariant(VendorVariationPrefix + m.SnapshotVersion(mctx))
 		}
 	} else if m.HasNonSystemVariants() {
 		// This will be available to /system unless it is product_specific
@@ -364,16 +363,16 @@
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, or
 		// PLATFORM_VNDK_VERSION.
 		if m.HasVendorVariant() {
-			vendorVariants = append(vendorVariants, "")
+			vendorVariantNeeded = true
 		}
 
 		// product_available modules are available to /product.
 		if m.HasProductVariant() {
-			productVariants = append(productVariants, "")
+			productVariantNeeded = true
 		}
 	} else if vendorSpecific && m.SdkVersion() == "" {
 		// This will be available in /vendor (or /odm) only
-		vendorVariants = append(vendorVariants, "")
+		vendorVariantNeeded = true
 	} else {
 		// This is either in /system (or similar: /data), or is a
 		// module built with the NDK. Modules built with the NDK
@@ -384,7 +383,7 @@
 	if coreVariantNeeded && productSpecific && m.SdkVersion() == "" {
 		// The module has "product_specific: true" that does not create core variant.
 		coreVariantNeeded = false
-		productVariants = append(productVariants, "")
+		productVariantNeeded = true
 	}
 
 	if m.RamdiskAvailable() {
@@ -414,36 +413,32 @@
 		coreVariantNeeded = false
 	}
 
-	for _, variant := range android.FirstUniqueStrings(vendorVariants) {
-		if variant == "" {
-			m.AppendExtraVariant(VendorVariation)
-		} else {
-			m.AppendExtraVariant(VendorVariationPrefix + variant)
-		}
-	}
-
-	for _, variant := range android.FirstUniqueStrings(productVariants) {
-		if variant == "" {
-			m.AppendExtraVariant(ProductVariation)
-		} else {
-			m.AppendExtraVariant(ProductVariationPrefix + variant)
-		}
-	}
-
 	m.SetRamdiskVariantNeeded(ramdiskVariantNeeded)
 	m.SetVendorRamdiskVariantNeeded(vendorRamdiskVariantNeeded)
 	m.SetRecoveryVariantNeeded(recoveryVariantNeeded)
 	m.SetCoreVariantNeeded(coreVariantNeeded)
+	m.SetProductVariantNeeded(productVariantNeeded)
+	m.SetVendorVariantNeeded(vendorVariantNeeded)
 
 	// Disable the module if no variants are needed.
 	if !ramdiskVariantNeeded &&
 		!recoveryVariantNeeded &&
 		!coreVariantNeeded &&
+		!productVariantNeeded &&
+		!vendorVariantNeeded &&
 		len(m.ExtraVariants()) == 0 {
 		m.Disable()
 	}
 }
 
+func (c *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return c.Properties.VendorVariantNeeded
+}
+
+func (c *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return c.Properties.ProductVariantNeeded
+}
+
 func (c *Module) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return c.Properties.CoreVariantNeeded
 }
@@ -537,15 +532,15 @@
 	} else if variant == android.RecoveryVariation {
 		c.MakeAsPlatform()
 		squashRecoverySrcs(c)
-	} else if strings.HasPrefix(variant, VendorVariation) {
-		c.Properties.ImageVariation = VendorVariation
+	} else if strings.HasPrefix(variant, android.VendorVariation) {
+		c.Properties.ImageVariation = android.VendorVariation
 
 		if strings.HasPrefix(variant, VendorVariationPrefix) {
 			c.Properties.VndkVersion = strings.TrimPrefix(variant, VendorVariationPrefix)
 		}
 		squashVendorSrcs(c)
-	} else if strings.HasPrefix(variant, ProductVariation) {
-		c.Properties.ImageVariation = ProductVariation
+	} else if strings.HasPrefix(variant, android.ProductVariation) {
+		c.Properties.ImageVariation = android.ProductVariation
 		if strings.HasPrefix(variant, ProductVariationPrefix) {
 			c.Properties.VndkVersion = strings.TrimPrefix(variant, ProductVariationPrefix)
 		}
diff --git a/cc/library_stub.go b/cc/library_stub.go
index 9643ec2..6f06333 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -494,6 +494,12 @@
 
 // Implement ImageInterface to generate image variants
 func (v *CcApiVariant) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (v *CcApiVariant) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return String(v.properties.Variant) == "llndk"
+}
+func (v *CcApiVariant) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return String(v.properties.Variant) == "llndk"
+}
 func (v *CcApiVariant) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return inList(String(v.properties.Variant), []string{"ndk", "apex"})
 }
@@ -501,15 +507,6 @@
 func (v *CcApiVariant) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { return false }
 func (v *CcApiVariant) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool  { return false }
 func (v *CcApiVariant) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool      { return false }
-func (v *CcApiVariant) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	var variations []string
-
-	if String(v.properties.Variant) == "llndk" {
-		variations = append(variations, VendorVariation)
-		variations = append(variations, ProductVariation)
-	}
-
-	return variations
-}
+func (v *CcApiVariant) ExtraImageVariations(ctx android.BaseModuleContext) []string   { return nil }
 func (v *CcApiVariant) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 }
diff --git a/cc/object.go b/cc/object.go
index 6c0391f..8b23295 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -220,7 +220,7 @@
 }
 
 func (object *objectLinker) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (object *objectLinker) nativeCoverage() bool {
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index 6911e54..6d71d93 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -226,8 +226,16 @@
 			config.PriorStagesMap[priorStage] = true
 		}
 		myDirsMap[contrib.DeclarationIndex] = true
-		if config.AconfigFlagsOnly && len(contrib.FlagValues) > 0 {
-			return fmt.Errorf("%s does not allow build flag overrides", config.Name)
+		if config.AconfigFlagsOnly {
+			// AconfigFlagsOnly allows very very few build flag values, all of them are part of aconfig flags.
+			allowedFlags := map[string]bool{
+				"RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": true,
+			}
+			for _, fv := range contrib.FlagValues {
+				if !allowedFlags[*fv.proto.Name] {
+					return fmt.Errorf("%s does not allow build flag overrides", config.Name)
+				}
+			}
 		}
 		for _, value := range contrib.FlagValues {
 			name := *value.proto.Name
@@ -256,7 +264,7 @@
 	myAconfigValueSets := []string{}
 	myAconfigValueSetsMap := map[string]bool{}
 	for _, v := range strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ") {
-		if myAconfigValueSetsMap[v] {
+		if v == "" || myAconfigValueSetsMap[v] {
 			continue
 		}
 		myAconfigValueSetsMap[v] = true
@@ -320,6 +328,23 @@
 	makeVars := make(map[string]string)
 
 	myFlagArtifacts := config.FlagArtifacts.Clone()
+
+	// Add any RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS variables.
+	var extraAconfigReleaseConfigs []string
+	if extraAconfigValueSetsValue, ok := config.FlagArtifacts["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
+		if val := MarshalValue(extraAconfigValueSetsValue.Value); len(val) > 0 {
+			extraAconfigReleaseConfigs = strings.Split(val, " ")
+		}
+	}
+	for _, rcName := range extraAconfigReleaseConfigs {
+		rc, err := configs.GetReleaseConfig(rcName)
+		if err != nil {
+			return err
+		}
+		myFlagArtifacts["RELEASE_ACONFIG_VALUE_SETS_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
+		myFlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION"]
+	}
+
 	// Sort the flags by name first.
 	names := myFlagArtifacts.SortedFlagNames()
 	partitions := make(map[string][]string)
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 3dac8bd..a8be7ec 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"flag"
 	"fmt"
@@ -28,11 +29,11 @@
 	"android/soong/android/allowlists"
 	"android/soong/bp2build"
 	"android/soong/shared"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/metrics"
+	"github.com/google/blueprint/proptools"
 	androidProtobuf "google.golang.org/protobuf/android"
 )
 
@@ -49,6 +50,14 @@
 	cmdlineArgs android.CmdArgs
 )
 
+const configCacheFile = "config.cache"
+
+type ConfigCache struct {
+	EnvDepsHash                  uint64
+	ProductVariableFileTimestamp int64
+	SoongBuildFileTimestamp      int64
+}
+
 func init() {
 	// Flags that make sense in every mode
 	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
@@ -82,6 +91,7 @@
 	// Flags that probably shouldn't be flags of soong_build, but we haven't found
 	// the time to remove them yet
 	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
+	flag.BoolVar(&cmdlineArgs.IncrementalBuildActions, "incremental-build-actions", false, "generate build actions incrementally")
 
 	// Disable deterministic randomization in the protobuf package, so incremental
 	// builds with unrelated Soong changes don't trigger large rebuilds (since we
@@ -218,6 +228,60 @@
 	maybeQuit(err, "error writing depfile '%s'", depFile)
 }
 
+// Check if there are changes to the environment file, product variable file and
+// soong_build binary, in which case no incremental will be performed.
+func incrementalValid(config android.Config, configCacheFile string) (*ConfigCache, bool) {
+	var newConfigCache ConfigCache
+	data, err := os.ReadFile(shared.JoinPath(topDir, usedEnvFile))
+	if err != nil {
+		// Clean build
+		if os.IsNotExist(err) {
+			data = []byte{}
+		} else {
+			maybeQuit(err, "")
+		}
+	}
+
+	newConfigCache.EnvDepsHash, err = proptools.CalculateHash(data)
+	newConfigCache.ProductVariableFileTimestamp = getFileTimestamp(filepath.Join(topDir, cmdlineArgs.SoongVariables))
+	newConfigCache.SoongBuildFileTimestamp = getFileTimestamp(filepath.Join(topDir, config.HostToolDir(), "soong_build"))
+	//TODO(b/344917959): out/soong/dexpreopt.config might need to be checked as well.
+
+	file, err := os.Open(configCacheFile)
+	if err != nil && os.IsNotExist(err) {
+		return &newConfigCache, false
+	}
+	maybeQuit(err, "")
+	defer file.Close()
+
+	var configCache ConfigCache
+	decoder := json.NewDecoder(file)
+	err = decoder.Decode(&configCache)
+	maybeQuit(err, "")
+
+	return &newConfigCache, newConfigCache == configCache
+}
+
+func getFileTimestamp(file string) int64 {
+	stat, err := os.Stat(file)
+	if err == nil {
+		return stat.ModTime().UnixMilli()
+	} else if !os.IsNotExist(err) {
+		maybeQuit(err, "")
+	}
+	return 0
+}
+
+func writeConfigCache(configCache *ConfigCache, configCacheFile string) {
+	file, err := os.Create(configCacheFile)
+	maybeQuit(err, "")
+	defer file.Close()
+
+	encoder := json.NewEncoder(file)
+	err = encoder.Encode(*configCache)
+	maybeQuit(err, "")
+}
+
 // runSoongOnlyBuild runs the standard Soong build in a number of different modes.
 func runSoongOnlyBuild(ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("soong_build")
@@ -319,8 +383,26 @@
 	ctx := newContext(configuration)
 	android.StartBackgroundMetrics(configuration)
 
+	var configCache *ConfigCache
+	configFile := filepath.Join(topDir, ctx.Config().OutDir(), configCacheFile)
+	incremental := false
+	ctx.SetIncrementalEnabled(cmdlineArgs.IncrementalBuildActions)
+	if cmdlineArgs.IncrementalBuildActions {
+		configCache, incremental = incrementalValid(ctx.Config(), configFile)
+	}
+	ctx.SetIncrementalAnalysis(incremental)
+
 	ctx.Register()
 	finalOutputFile := runSoongOnlyBuild(ctx, extraNinjaDeps)
+
+	if ctx.GetIncrementalEnabled() {
+		data, err := shared.EnvFileContents(configuration.EnvDeps())
+		maybeQuit(err, "")
+		configCache.EnvDepsHash, err = proptools.CalculateHash(data)
+		maybeQuit(err, "")
+		writeConfigCache(configCache, configFile)
+	}
+
 	writeMetrics(configuration, ctx.EventHandler, metricsDir)
 
 	writeUsedEnvironmentFile(configuration)
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 2075488..5a4818f 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -216,6 +216,14 @@
 
 func (p *PrebuiltEtc) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (p *PrebuiltEtc) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (p *PrebuiltEtc) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
 func (p *PrebuiltEtc) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !p.ModuleBase.InstallInRecovery() && !p.ModuleBase.InstallInRamdisk() &&
 		!p.ModuleBase.InstallInVendorRamdisk() && !p.ModuleBase.InstallInDebugRamdisk()
diff --git a/genrule/genrule.go b/genrule/genrule.go
index b2353036..c0942d3 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -643,6 +643,8 @@
 type noopImageInterface struct{}
 
 func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
+func (x noopImageInterface) VendorVariantNeeded(android.BaseModuleContext) bool          { return false }
+func (x noopImageInterface) ProductVariantNeeded(android.BaseModuleContext) bool         { return false }
 func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
 func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
 func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool   { return false }
diff --git a/go.mod b/go.mod
index 13834fc..aa43066 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,5 @@
 require (
 	github.com/google/blueprint v0.0.0
 	google.golang.org/protobuf v0.0.0
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0
 	go.starlark.net v0.0.0
 )
diff --git a/go.work b/go.work
index 9a7e6db..46a135b 100644
--- a/go.work
+++ b/go.work
@@ -5,8 +5,6 @@
 	../../external/go-cmp
 	../../external/golang-protobuf
 	../../external/starlark-go
-	../../prebuilts/bazel/common/proto/analysis_v2
-	../../prebuilts/bazel/common/proto/build
 	../blueprint
 )
 
@@ -15,7 +13,5 @@
 	github.com/google/blueprint v0.0.0 => ../blueprint
 	github.com/google/go-cmp v0.0.0 => ../../external/go-cmp
 	google.golang.org/protobuf v0.0.0 => ../../external/golang-protobuf
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0 => ../../prebuilts/bazel/common/proto/analysis_v2
-	prebuilts/bazel/common/proto/build v0.0.0 => ../../prebuilts/bazel/common/proto/build
 	go.starlark.net v0.0.0 => ../../external/starlark-go
 )
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 8599003..0c77968 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -71,12 +71,15 @@
 	return targetSdkVersionLevel.IsPreview() && (ctx.Config().UnbundledBuildApps() || includedInMts(ctx.Module()))
 }
 
-// Helper function that casts android.Module to java.androidTestApp
-// If this type conversion is possible, it queries whether the test app is included in an MTS suite
+// Helper function that returns true if android_test, android_test_helper_app, java_test are in an MTS suite.
 func includedInMts(module android.Module) bool {
 	if test, ok := module.(androidTestApp); ok {
 		return test.includedInTestSuite("mts")
 	}
+	// java_test
+	if test, ok := module.(*Test); ok {
+		return android.PrefixInList(test.testProperties.Test_suites, "mts")
+	}
 	return false
 }
 
diff --git a/java/app.go b/java/app.go
index 739ef1a..f35e4c3 100644
--- a/java/app.go
+++ b/java/app.go
@@ -345,7 +345,35 @@
 	}
 }
 
+// TODO(b/156476221): Remove this allowlist
+var (
+	missingMinSdkVersionMtsAllowlist = []string{
+		"CellBroadcastReceiverGoogleUnitTests",
+		"CellBroadcastReceiverUnitTests",
+		"CtsBatterySavingTestCases",
+		"CtsDeviceAndProfileOwnerApp23",
+		"CtsDeviceAndProfileOwnerApp30",
+		"CtsIntentSenderApp",
+		"CtsJobSchedulerTestCases",
+		"CtsMimeMapTestCases",
+		"CtsTareTestCases",
+		"LibStatsPullTests",
+		"MediaProviderClientTests",
+		"TeleServiceTests",
+		"TestExternalImsServiceApp",
+		"TestSmsRetrieverApp",
+		"TetheringPrivilegedTests",
+	}
+)
+
+func checkMinSdkVersionMts(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
+	if includedInMts(ctx.Module()) && !minSdkVersion.Specified() && !android.InList(ctx.ModuleName(), missingMinSdkVersionMtsAllowlist) {
+		ctx.PropertyErrorf("min_sdk_version", "min_sdk_version is a required property for tests included in MTS")
+	}
+}
+
 func (a *AndroidTestHelperApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	applicationId := a.appTestHelperAppProperties.Manifest_values.ApplicationId
 	if applicationId != nil {
 		if a.overridableAppProperties.Package_name != nil {
@@ -1366,6 +1394,7 @@
 }
 
 func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	var configs []tradefed.Config
 	if a.appTestProperties.Instrumentation_target_package != nil {
 		a.additionalAaptFlags = append(a.additionalAaptFlags,
diff --git a/java/app_test.go b/java/app_test.go
index 1a862fa..9e2d19e 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4146,6 +4146,7 @@
 	bpTemplate := `
 	%v {
 		name: "mytest",
+		min_sdk_version: "34",
 		target_sdk_version: "%v",
 		test_suites: ["othersuite", "%v"],
 	}
diff --git a/java/dex.go b/java/dex.go
index 32546d9..c75e774 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -180,7 +180,7 @@
 		"$r8Template": &remoteexec.REParams{
 			Labels:          map[string]string{"type": "compile", "compiler": "r8"},
 			Inputs:          []string{"$implicits", "${config.R8Jar}"},
-			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}"},
+			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}", "${outR8ArtProfile}"},
 			ExecStrategy:    "${config.RER8ExecStrategy}",
 			ToolchainInputs: []string{"${config.JavaCmd}"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
@@ -200,7 +200,7 @@
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
 	}, []string{"outDir", "outDict", "outConfig", "outUsage", "outUsageZip", "outUsageDir",
-		"r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput"}, []string{"implicits"})
+		"r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput", "outR8ArtProfile"}, []string{"implicits"})
 
 func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
 	dexParams *compileDexParams) (flags []string, deps android.Paths) {
@@ -463,13 +463,6 @@
 			proguardConfiguration,
 		}
 		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams)
-		if r8ArtProfileOutputPath != nil {
-			artProfileOutputPath = r8ArtProfileOutputPath
-			implicitOutputs = append(
-				implicitOutputs,
-				artProfileOutputPath,
-			)
-		}
 		rule := r8
 		args := map[string]string{
 			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
@@ -482,6 +475,17 @@
 			"outDir":         outDir.String(),
 			"mergeZipsFlags": mergeZipsFlags,
 		}
+		if r8ArtProfileOutputPath != nil {
+			artProfileOutputPath = r8ArtProfileOutputPath
+			implicitOutputs = append(
+				implicitOutputs,
+				artProfileOutputPath,
+			)
+			// Add the implicit r8 Art profile output to args so that r8RE knows
+			// about this implicit output
+			args["outR8ArtProfile"] = artProfileOutputPath.String()
+		}
+
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") {
 			rule = r8RE
 			args["implicits"] = strings.Join(r8Deps.Strings(), ",")
diff --git a/java/java.go b/java/java.go
index 08fb678..a2fc5fb 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1501,7 +1501,7 @@
 		InstalledFiles:      j.data,
 		OutputFile:          j.outputFile,
 		TestConfig:          j.testConfig,
-		RequiredModuleNames: j.RequiredModuleNames(),
+		RequiredModuleNames: j.RequiredModuleNames(ctx),
 		TestSuites:          j.testProperties.Test_suites,
 		IsHost:              true,
 		LocalSdkVersion:     j.sdkVersion.String(),
@@ -1510,6 +1510,7 @@
 }
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, j.MinSdkVersion(ctx))
 	j.generateAndroidBuildActionsWithConfig(ctx, nil)
 	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
@@ -2285,10 +2286,10 @@
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
-	migratingNullability := String(al.properties.Previous_api) != ""
-	if migratingNullability {
-		previousApi := android.PathForModuleSrc(ctx, String(al.properties.Previous_api))
-		cmd.FlagWithInput("--migrate-nullness ", previousApi)
+	previousApi := String(al.properties.Previous_api)
+	if previousApi != "" {
+		previousApiFiles := android.PathsForModuleSrc(ctx, []string{previousApi})
+		cmd.FlagForEachInput("--migrate-nullness ", previousApiFiles)
 	}
 
 	al.addValidation(ctx, cmd, al.validationPaths)
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 49756dd..67ed84e 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -15,7 +15,6 @@
 package java
 
 import (
-	"fmt"
 	"path/filepath"
 
 	"android/soong/android"
@@ -61,6 +60,8 @@
 	installDirPath android.InstallPath
 	configFile     android.OutputPath
 	metadataFile   android.OutputPath
+
+	installConfigFile android.InstallPath
 }
 
 func (p *platformCompatConfig) compatConfigMetadata() android.Path {
@@ -106,21 +107,15 @@
 		FlagWithOutput("--merged-config ", p.metadataFile)
 
 	p.installDirPath = android.PathForModuleInstall(ctx, "etc", "compatconfig")
+	p.installConfigFile = android.PathForModuleInstall(ctx, "etc", "compatconfig", p.configFile.Base())
 	rule.Build(configFileName, "Extract compat/compat_config.xml and install it")
-
+	ctx.InstallFile(p.installDirPath, p.configFile.Base(), p.configFile)
 }
 
 func (p *platformCompatConfig) AndroidMkEntries() []android.AndroidMkEntries {
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "ETC",
 		OutputFile: android.OptionalPathForPath(p.configFile),
-		Include:    "$(BUILD_PREBUILT)",
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
-				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base())
-			},
-		},
 	}}
 }
 
@@ -284,32 +279,23 @@
 	android.ModuleBase
 
 	properties globalCompatConfigProperties
-
-	outputFilePath android.OutputPath
 }
 
 func (c *globalCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	filename := String(c.properties.Filename)
 
 	inputPath := platformCompatConfigPath(ctx)
-	c.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
+	outputFilePath := android.PathForModuleOut(ctx, filename).OutputPath
 
 	// This ensures that outputFilePath has the correct name for others to
 	// use, as the source file may have a different name.
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
-		Output: c.outputFilePath,
+		Output: outputFilePath,
 		Input:  inputPath,
 	})
-}
 
-func (h *globalCompatConfig) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{h.outputFilePath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
+	ctx.SetOutputFiles(android.Paths{outputFilePath}, "")
 }
 
 // global_compat_config provides access to the merged compat config xml file generated by the build.
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e9fa83a..e6cb6c4 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -118,9 +118,6 @@
 	// The tag to use to depend on the stubs source module (if separate from the API module).
 	stubsSourceTag scopeDependencyTag
 
-	// The tag to use to depend on the API file generating module (if separate from the stubs source module).
-	apiFileTag scopeDependencyTag
-
 	// The tag to use to depend on the stubs source and API module.
 	stubsSourceAndApiTag scopeDependencyTag
 
@@ -195,11 +192,6 @@
 		apiScope:         scope,
 		depInfoExtractor: (*scopePaths).extractStubsSourceInfoFromDep,
 	}
-	scope.apiFileTag = scopeDependencyTag{
-		name:             name + "-api",
-		apiScope:         scope,
-		depInfoExtractor: (*scopePaths).extractApiInfoFromDep,
-	}
 	scope.stubsSourceAndApiTag = scopeDependencyTag{
 		name:             name + "-stubs-source-and-api",
 		apiScope:         scope,
@@ -804,12 +796,6 @@
 	return combinedError
 }
 
-func (paths *scopePaths) extractApiInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
-	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		return paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-	})
-}
-
 func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsSrcProvider, stubsType StubsType) error {
 	stubsSrcJar, err := provider.StubsSrcJar(stubsType)
 	if err == nil {
@@ -819,22 +805,23 @@
 }
 
 func (paths *scopePaths) extractStubsSourceInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
+	if ctx.Config().ReleaseHiddenApiExportableStubs() {
+		stubsType = Exportable
+	}
 	return paths.treatDepAsApiStubsSrcProvider(dep, func(provider ApiStubsSrcProvider) error {
-		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 	})
 }
 
 func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
 	if ctx.Config().ReleaseHiddenApiExportableStubs() {
-		return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-			extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Exportable)
-			extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Exportable)
-			return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
-		})
+		stubsType = Exportable
 	}
 	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, stubsType)
+		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 		return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
 	})
 }
@@ -1679,6 +1666,7 @@
 		module.dexer.proguardDictionary = module.implLibraryModule.dexer.proguardDictionary
 		module.dexer.proguardUsageZip = module.implLibraryModule.dexer.proguardUsageZip
 		module.linter.reports = module.implLibraryModule.linter.reports
+		module.linter.outputs.depSets = module.implLibraryModule.LintDepSets()
 
 		if !module.Host() {
 			module.hostdexInstallFile = module.implLibraryModule.hostdexInstallFile
@@ -3279,7 +3267,7 @@
 		// TODO(b/146468504): ApexVariationName() is only a soong module name, not apex name.
 		// In most cases, this works fine. But when apex_name is set or override_apex is used
 		// this can be wrong.
-		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apexInfo.ApexVariationName, implName)
+		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apexInfo.BaseApexName, implName)
 	}
 	partition := "system"
 	if module.SocSpecific() {
diff --git a/multitree/api_surface.go b/multitree/api_surface.go
index f739a24..0f605d8 100644
--- a/multitree/api_surface.go
+++ b/multitree/api_surface.go
@@ -16,8 +16,6 @@
 
 import (
 	"android/soong/android"
-	"fmt"
-
 	"github.com/google/blueprint"
 )
 
@@ -40,7 +38,6 @@
 	ExportableModuleBase
 	properties apiSurfaceProperties
 
-	allOutputs    android.Paths
 	taggedOutputs map[string]android.Paths
 }
 
@@ -86,15 +83,9 @@
 		Inputs: allOutputs,
 	})
 
-	surface.allOutputs = allOutputs
 	surface.taggedOutputs = contributionFiles
-}
 
-func (surface *ApiSurface) OutputFiles(tag string) (android.Paths, error) {
-	if tag != "" {
-		return nil, fmt.Errorf("unknown tag: %q", tag)
-	}
-	return surface.allOutputs, nil
+	ctx.SetOutputFiles(allOutputs, "")
 }
 
 func (surface *ApiSurface) TaggedOutputs() map[string]android.Paths {
@@ -105,7 +96,6 @@
 	return true
 }
 
-var _ android.OutputFileProducer = (*ApiSurface)(nil)
 var _ Exportable = (*ApiSurface)(nil)
 
 type ApiContribution interface {
diff --git a/multitree/export.go b/multitree/export.go
index aecade5..8be8f70 100644
--- a/multitree/export.go
+++ b/multitree/export.go
@@ -50,7 +50,6 @@
 
 type ExportableModule interface {
 	android.Module
-	android.OutputFileProducer
 	Exportable
 }
 
diff --git a/phony/phony.go b/phony/phony.go
index 5469238..b421176 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -49,7 +49,7 @@
 }
 
 func (p *phony) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.requiredModuleNames = ctx.RequiredModuleNames()
+	p.requiredModuleNames = ctx.RequiredModuleNames(ctx)
 	p.hostRequiredModuleNames = ctx.HostRequiredModuleNames()
 	p.targetRequiredModuleNames = ctx.TargetRequiredModuleNames()
 }
diff --git a/python/binary.go b/python/binary.go
index b935aba..5f60761 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -103,6 +103,7 @@
 	p.buildBinary(ctx)
 	p.installedDest = ctx.InstallFile(installDir(ctx, "bin", "", ""),
 		p.installSource.Base(), p.installSource)
+	ctx.SetOutputFiles(android.Paths{p.installSource}, "")
 }
 
 func (p *PythonBinaryModule) buildBinary(ctx android.ModuleContext) {
@@ -187,16 +188,6 @@
 	return android.OptionalPathForPath(p.installedDest)
 }
 
-// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
-func (p *PythonBinaryModule) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{p.installSource}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (p *PythonBinaryModule) isEmbeddedLauncherEnabled() bool {
 	return BoolDefault(p.properties.Embedded_launcher, true)
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 83a6e95..dbc3697 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -29,7 +29,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r510928"
+	bindgenClangVersion = "clang-r522817"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
diff --git a/rust/image.go b/rust/image.go
index fec6d92..26929b1 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -77,6 +77,14 @@
 	mod.Properties.CoreVariantNeeded = b
 }
 
+func (mod *Module) SetProductVariantNeeded(b bool) {
+	mod.Properties.ProductVariantNeeded = b
+}
+
+func (mod *Module) SetVendorVariantNeeded(b bool) {
+	mod.Properties.VendorVariantNeeded = b
+}
+
 func (mod *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
 	if snapshot, ok := mod.compiler.(cc.SnapshotInterface); ok {
 		return snapshot.Version()
@@ -86,6 +94,14 @@
 	}
 }
 
+func (mod *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return mod.Properties.VendorVariantNeeded
+}
+
+func (mod *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return mod.Properties.ProductVariantNeeded
+}
+
 func (mod *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
 	return mod.Properties.VendorRamdiskVariantNeeded
 }
@@ -184,12 +200,12 @@
 }
 
 func (mod *Module) InProduct() bool {
-	return mod.Properties.ImageVariation == cc.ProductVariation
+	return mod.Properties.ImageVariation == android.ProductVariation
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
 func (mod *Module) InVendor() bool {
-	return mod.Properties.ImageVariation == cc.VendorVariation
+	return mod.Properties.ImageVariation == android.VendorVariation
 }
 
 // Returns true if the module is "vendor" or "product" variant.
@@ -202,13 +218,13 @@
 		mod.MakeAsPlatform()
 	} else if variant == android.RecoveryVariation {
 		mod.MakeAsPlatform()
-	} else if strings.HasPrefix(variant, cc.VendorVariation) {
-		mod.Properties.ImageVariation = cc.VendorVariation
+	} else if strings.HasPrefix(variant, android.VendorVariation) {
+		mod.Properties.ImageVariation = android.VendorVariation
 		if strings.HasPrefix(variant, cc.VendorVariationPrefix) {
 			mod.Properties.VndkVersion = strings.TrimPrefix(variant, cc.VendorVariationPrefix)
 		}
-	} else if strings.HasPrefix(variant, cc.ProductVariation) {
-		mod.Properties.ImageVariation = cc.ProductVariation
+	} else if strings.HasPrefix(variant, android.ProductVariation) {
+		mod.Properties.ImageVariation = android.ProductVariation
 		if strings.HasPrefix(variant, cc.ProductVariationPrefix) {
 			mod.Properties.VndkVersion = strings.TrimPrefix(variant, cc.ProductVariationPrefix)
 		}
diff --git a/rust/rust.go b/rust/rust.go
index 7cd9df4..3a3ca4d 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"android/soong/bloaty"
@@ -80,6 +81,8 @@
 	RustSubName string `blueprint:"mutated"`
 
 	// Set by imageMutator
+	ProductVariantNeeded       bool     `blueprint:"mutated"`
+	VendorVariantNeeded        bool     `blueprint:"mutated"`
 	CoreVariantNeeded          bool     `blueprint:"mutated"`
 	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
 	RamdiskVariantNeeded       bool     `blueprint:"mutated"`
@@ -206,27 +209,6 @@
 	return false
 }
 
-func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
-			return mod.sourceProvider.Srcs(), nil
-		} else {
-			if mod.OutputFile().Valid() {
-				return android.Paths{mod.OutputFile().Path()}, nil
-			}
-			return android.Paths{}, nil
-		}
-	case "unstripped":
-		if mod.compiler != nil {
-			return android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), nil
-		}
-		return nil, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (mod *Module) SelectedStl() string {
 	return ""
 }
@@ -986,6 +968,61 @@
 	if mod.testModule {
 		android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 	}
+
+	mod.setOutputFiles(ctx)
+
+	buildComplianceMetadataInfo(ctx, mod, deps)
+}
+
+func (mod *Module) setOutputFiles(ctx ModuleContext) {
+	if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
+		ctx.SetOutputFiles(mod.sourceProvider.Srcs(), "")
+	} else if mod.OutputFile().Valid() {
+		ctx.SetOutputFiles(android.Paths{mod.OutputFile().Path()}, "")
+	} else {
+		ctx.SetOutputFiles(android.Paths{}, "")
+	}
+	if mod.compiler != nil {
+		ctx.SetOutputFiles(android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), "unstripped")
+	}
+}
+
+func buildComplianceMetadataInfo(ctx *moduleContext, mod *Module, deps PathDeps) {
+	// Dump metadata that can not be done in android/compliance-metadata.go
+	metadataInfo := ctx.ComplianceMetadataInfo()
+	metadataInfo.SetStringValue(android.ComplianceMetadataProp.IS_STATIC_LIB, strconv.FormatBool(mod.Static()))
+	metadataInfo.SetStringValue(android.ComplianceMetadataProp.BUILT_FILES, mod.outputFile.String())
+
+	// Static libs
+	staticDeps := ctx.GetDirectDepsWithTag(rlibDepTag)
+	staticDepNames := make([]string, 0, len(staticDeps))
+	for _, dep := range staticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+	ccStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(false))
+	for _, dep := range ccStaticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+
+	staticDepPaths := make([]string, 0, len(deps.StaticLibs)+len(deps.RLibs))
+	// C static libraries
+	for _, dep := range deps.StaticLibs {
+		staticDepPaths = append(staticDepPaths, dep.String())
+	}
+	// Rust static libraries
+	for _, dep := range deps.RLibs {
+		staticDepPaths = append(staticDepPaths, dep.Path.String())
+	}
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.FirstUniqueStrings(staticDepPaths))
+
+	// C Whole static libs
+	ccWholeStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(true))
+	wholeStaticDepNames := make([]string, 0, len(ccWholeStaticDeps))
+	for _, dep := range ccStaticDeps {
+		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
+	}
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
 }
 
 func (mod *Module) deps(ctx DepsContext) Deps {
@@ -1469,7 +1506,7 @@
 
 	var srcProviderDepFiles android.Paths
 	for _, dep := range directSrcProvidersDeps {
-		srcs, _ := dep.OutputFiles("")
+		srcs := android.OutputFilesForModule(ctx, dep, "")
 		srcProviderDepFiles = append(srcProviderDepFiles, srcs...)
 	}
 	for _, dep := range directSrcDeps {
@@ -1856,5 +1893,3 @@
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
 var StringPtr = proptools.StringPtr
-
-var _ android.OutputFileProducer = (*Module)(nil)
diff --git a/rust/test_test.go b/rust/test_test.go
index 6d0ebcf..dc796c8 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -106,12 +106,9 @@
 
 	ctx := testRust(t, bp)
 
-	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
-	testBinary := module.(*Module).compiler.(*testDecorator)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
-	}
+	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	testBinary := testingModule.Module().(*Module).compiler.(*testDecorator)
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -168,12 +165,10 @@
  `
 
 	ctx := testRust(t, bp)
-	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	module := testingModule.Module()
 	testBinary := module.(*Module).compiler.(*testDecorator)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 80cd935..91aa195 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -292,6 +292,20 @@
 }
 
 python_binary_host {
+    name: "merge_json",
+    main: "merge_json.py",
+    srcs: [
+        "merge_json.py",
+    ],
+}
+
+python_binary_host {
+    name: "gen_build_prop",
+    main: "gen_build_prop.py",
+    srcs: ["gen_build_prop.py"],
+}
+
+python_binary_host {
     name: "buildinfo",
     main: "buildinfo.py",
     srcs: ["buildinfo.py"],
diff --git a/scripts/buildinfo.py b/scripts/buildinfo.py
index e4fb0da..db99209 100755
--- a/scripts/buildinfo.py
+++ b/scripts/buildinfo.py
@@ -18,55 +18,90 @@
 
 import argparse
 import contextlib
+import json
+import os
 import subprocess
 
+TEST_KEY_DIR = "build/make/target/product/security"
+
+def get_build_variant(product_config):
+  if product_config["Eng"]:
+    return "eng"
+  elif product_config["Debuggable"]:
+    return "userdebug"
+  else:
+    return "user"
+
+def get_build_flavor(product_config):
+  build_flavor = product_config["DeviceProduct"] + "-" + get_build_variant(product_config)
+  if "address" in product_config.get("SanitizeDevice", []) and "_asan" not in build_flavor:
+    build_flavor += "_asan"
+  return build_flavor
+
+def get_build_keys(product_config):
+  default_cert = product_config.get("DefaultAppCertificate", "")
+  if default_cert == "" or default_cert == os.path.join(TEST_KEY_DIR, "testKey"):
+    return "test-keys"
+  return "dev-keys"
+
 def parse_args():
   """Parse commandline arguments."""
   parser = argparse.ArgumentParser()
-  parser.add_argument('--use-vbmeta-digest-in-fingerprint', action='store_true')
-  parser.add_argument('--build-flavor', required=True)
   parser.add_argument('--build-hostname-file', required=True, type=argparse.FileType('r')),
-  parser.add_argument('--build-id', required=True)
-  parser.add_argument('--build-keys', required=True)
   parser.add_argument('--build-number-file', required=True, type=argparse.FileType('r'))
   parser.add_argument('--build-thumbprint-file', type=argparse.FileType('r'))
-  parser.add_argument('--build-type', required=True)
   parser.add_argument('--build-username', required=True)
-  parser.add_argument('--build-variant', required=True)
-  parser.add_argument('--cpu-abis', action='append', required=True)
   parser.add_argument('--date-file', required=True, type=argparse.FileType('r'))
-  parser.add_argument('--default-locale')
-  parser.add_argument('--default-wifi-channels', action='append', default=[])
-  parser.add_argument('--device', required=True)
-  parser.add_argument("--display-build-number", action='store_true')
-  parser.add_argument('--platform-base-os', required=True)
-  parser.add_argument('--platform-display-version', required=True)
-  parser.add_argument('--platform-min-supported-target-sdk-version', required=True)
   parser.add_argument('--platform-preview-sdk-fingerprint-file',
                       required=True,
                       type=argparse.FileType('r'))
-  parser.add_argument('--platform-preview-sdk-version', required=True)
-  parser.add_argument('--platform-sdk-version', required=True)
-  parser.add_argument('--platform-security-patch', required=True)
-  parser.add_argument('--platform-version', required=True)
-  parser.add_argument('--platform-version-codename',required=True)
-  parser.add_argument('--platform-version-all-codenames', action='append', required=True)
-  parser.add_argument('--platform-version-known-codenames', required=True)
-  parser.add_argument('--platform-version-last-stable', required=True)
-  parser.add_argument('--product', required=True)
-
+  parser.add_argument('--product-config', required=True, type=argparse.FileType('r'))
   parser.add_argument('--out', required=True, type=argparse.FileType('w'))
 
-  return parser.parse_args()
+  option = parser.parse_args()
+
+  product_config = json.load(option.product_config)
+  build_flags = product_config["BuildFlags"]
+
+  option.build_flavor = get_build_flavor(product_config)
+  option.build_keys = get_build_keys(product_config)
+  option.build_id = product_config["BuildId"]
+  option.build_type = product_config["BuildType"]
+  option.build_variant = get_build_variant(product_config)
+  option.build_version_tags = product_config["BuildVersionTags"]
+  option.cpu_abis = product_config["DeviceAbi"]
+  option.default_locale = None
+  if len(product_config.get("ProductLocales", [])) > 0:
+    option.default_locale = product_config["ProductLocales"][0]
+  option.default_wifi_channels = product_config.get("ProductDefaultWifiChannels", [])
+  option.device = product_config["DeviceName"]
+  option.display_build_number = product_config["DisplayBuildNumber"]
+  option.platform_base_os = product_config["Platform_base_os"]
+  option.platform_display_version = product_config["Platform_display_version_name"]
+  option.platform_min_supported_target_sdk_version = build_flags["RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION"]
+  option.platform_preview_sdk_version = product_config["Platform_preview_sdk_version"]
+  option.platform_sdk_version = product_config["Platform_sdk_version"]
+  option.platform_security_patch = product_config["Platform_security_patch"]
+  option.platform_version = product_config["Platform_version_name"]
+  option.platform_version_codename = product_config["Platform_sdk_codename"]
+  option.platform_version_all_codenames = product_config["Platform_version_active_codenames"]
+  option.platform_version_known_codenames = product_config["Platform_version_known_codenames"]
+  option.platform_version_last_stable = product_config["Platform_version_last_stable"]
+  option.product = product_config["DeviceProduct"]
+  option.use_vbmeta_digest_in_fingerprint = product_config["BoardUseVbmetaDigestInFingerprint"]
+
+  return option
 
 def main():
   option = parse_args()
 
   build_hostname = option.build_hostname_file.read().strip()
   build_number = option.build_number_file.read().strip()
-  build_version_tags = option.build_keys
+  build_version_tags_list = option.build_version_tags
   if option.build_type == "debug":
-    build_version_tags = "debug," + build_version_tags
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(option.build_keys)
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
 
   raw_date = option.date_file.read().strip()
   date = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index 47eae07..bb88cce 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -3,52 +3,7 @@
 
 ###################################################
 # core-libart.jar & core-oj.jar
-java\.awt\.font
-java\.beans
-java\.io
-java\.lang
-java\.lang\.annotation
-java\.lang\.constant
-java\.lang\.invoke
-java\.lang\.ref
-java\.lang\.reflect
-java\.lang\.runtime
-java\.math
-java\.net
-java\.nio
-java\.nio\.file
-java\.nio\.file\.spi
-java\.nio\.file\.attribute
-java\.nio\.channels
-java\.nio\.channels\.spi
-java\.nio\.charset
-java\.nio\.charset\.spi
-java\.security
-java\.security\.acl
-java\.security\.cert
-java\.security\.interfaces
-java\.security\.spec
-java\.sql
-java\.text
-java\.text\.spi
-java\.time
-java\.time\.chrono
-java\.time\.format
-java\.time\.temporal
-java\.time\.zone
-java\.util
-java\.util\.concurrent
-java\.util\.concurrent\.atomic
-java\.util\.concurrent\.locks
-java\.util\.function
-java\.util\.jar
-java\.util\.logging
-java\.util\.prefs
-java\.util\.random
-java\.util\.regex
-java\.util\.spi
-java\.util\.stream
-java\.util\.zip
+java(\..*)?
 # TODO: Remove javax.annotation.processing if possible, see http://b/132338110:
 javax\.annotation\.processing
 javax\.crypto
@@ -72,18 +27,7 @@
 javax\.xml\.transform\.stream
 javax\.xml\.validation
 javax\.xml\.xpath
-jdk\.internal
-jdk\.internal\.access
-jdk\.internal\.math
-jdk\.internal\.misc
-jdk\.internal\.ref
-jdk\.internal\.reflect
-jdk\.internal\.util
-jdk\.internal\.util\.jar
-jdk\.internal\.util\.random
-jdk\.internal\.vm\.annotation
-jdk\.net
-jdk\.random
+jdk\..*
 org\.w3c\.dom
 org\.w3c\.dom\.ls
 org\.w3c\.dom\.traversal
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
new file mode 100644
index 0000000..42e3207
--- /dev/null
+++ b/scripts/gen_build_prop.py
@@ -0,0 +1,562 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# 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.
+#
+"""A tool for generating {partition}/build.prop"""
+
+import argparse
+import contextlib
+import json
+import subprocess
+import sys
+
+def get_build_variant(product_config):
+  if product_config["Eng"]:
+    return "eng"
+  elif product_config["Debuggable"]:
+    return "userdebug"
+  else:
+    return "user"
+
+def get_build_flavor(product_config):
+  build_flavor = product_config["DeviceProduct"] + "-" + get_build_variant(product_config)
+  if "address" in product_config.get("SanitizeDevice", []) and "_asan" not in build_flavor:
+    build_flavor += "_asan"
+  return build_flavor
+
+def get_build_keys(product_config):
+  default_cert = product_config.get("DefaultAppCertificate", "")
+  if default_cert == "" or default_cert == os.path.join(TEST_KEY_DIR, "testKey"):
+    return "test-keys"
+  return "dev-keys"
+
+def parse_args():
+  """Parse commandline arguments."""
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--build-fingerprint-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-hostname-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-number-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-thumbprint-file", type=argparse.FileType("r"))
+  parser.add_argument("--build-username", required=True)
+  parser.add_argument("--date-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--platform-preview-sdk-fingerprint-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--prop-files", action="append", type=argparse.FileType("r"), default=[])
+  parser.add_argument("--product-config", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--partition", required=True)
+  parser.add_argument("--build-broken-dup-sysprop", action="store_true", default=False)
+
+  parser.add_argument("--out", required=True, type=argparse.FileType("w"))
+
+  args = parser.parse_args()
+
+  # post process parse_args requiring manual handling
+  args.config = json.load(args.product_config)
+  config = args.config
+
+  config["BuildFlavor"] = get_build_flavor(config)
+  config["BuildKeys"] = get_build_keys(config)
+  config["BuildVariant"] = get_build_variant(config)
+
+  config["BuildFingerprint"] = args.build_fingerprint_file.read().strip()
+  config["BuildHostname"] = args.build_hostname_file.read().strip()
+  config["BuildNumber"] = args.build_number_file.read().strip()
+  config["BuildUsername"] = args.build_username
+
+  build_version_tags_list = config["BuildVersionTags"]
+  if config["BuildType"] == "debug":
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(config["BuildKeys"])
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
+  config["BuildVersionTags"] = build_version_tags
+
+  raw_date = args.date_file.read().strip()
+  config["Date"] = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
+  config["DateUtc"] = subprocess.check_output(["date", "-d", f"@{raw_date}", "+%s"], text=True).strip()
+
+  # build_desc is human readable strings that describe this build. This has the same info as the
+  # build fingerprint.
+  # e.g. "aosp_cf_x86_64_phone-userdebug VanillaIceCream MAIN eng.20240319.143939 test-keys"
+  config["BuildDesc"] = f"{config['DeviceProduct']}-{config['BuildVariant']} " \
+                        f"{config['Platform_version_name']} {config['BuildId']} " \
+                        f"{config['BuildNumber']} {config['BuildVersionTags']}"
+
+  config["PlatformPreviewSdkFingerprint"] = args.platform_preview_sdk_fingerprint_file.read().strip()
+
+  if args.build_thumbprint_file:
+    config["BuildThumbprint"] = args.build_thumbprint_file.read().strip()
+
+  append_additional_system_props(args)
+  append_additional_vendor_props(args)
+  append_additional_product_props(args)
+
+  return args
+
+def generate_common_build_props(args):
+  print("####################################")
+  print("# from generate_common_build_props")
+  print("# These properties identify this partition image.")
+  print("####################################")
+
+  config = args.config
+  partition = args.partition
+
+  if partition == "system":
+    print(f"ro.product.{partition}.brand={config['SystemBrand']}")
+    print(f"ro.product.{partition}.device={config['SystemDevice']}")
+    print(f"ro.product.{partition}.manufacturer={config['SystemManufacturer']}")
+    print(f"ro.product.{partition}.model={config['SystemModel']}")
+    print(f"ro.product.{partition}.name={config['SystemName']}")
+  else:
+    print(f"ro.product.{partition}.brand={config['ProductBrand']}")
+    print(f"ro.product.{partition}.device={config['DeviceName']}")
+    print(f"ro.product.{partition}.manufacturer={config['ProductManufacturer']}")
+    print(f"ro.product.{partition}.model={config['ProductModel']}")
+    print(f"ro.product.{partition}.name={config['DeviceProduct']}")
+
+  if partition != "system":
+    if config["ModelForAttestation"]:
+        print(f"ro.product.model_for_attestation={config['ModelForAttestation']}")
+    if config["BrandForAttestation"]:
+        print(f"ro.product.brand_for_attestation={config['BrandForAttestation']}")
+    if config["NameForAttestation"]:
+        print(f"ro.product.name_for_attestation={config['NameForAttestation']}")
+    if config["DeviceForAttestation"]:
+        print(f"ro.product.device_for_attestation={config['DeviceForAttestation']}")
+    if config["ManufacturerForAttestation"]:
+        print(f"ro.product.manufacturer_for_attestation={config['ManufacturerForAttestation']}")
+
+  if config["ZygoteForce64"]:
+    if partition == "vendor":
+      print(f"ro.{partition}.product.cpu.abilist={config['DeviceAbiList64']}")
+      print(f"ro.{partition}.product.cpu.abilist32=")
+      print(f"ro.{partition}.product.cpu.abilist64={config['DeviceAbiList64']}")
+  else:
+    if partition == "system" or partition == "vendor" or partition == "odm":
+      print(f"ro.{partition}.product.cpu.abilist={config['DeviceAbiList']}")
+      print(f"ro.{partition}.product.cpu.abilist32={config['DeviceAbiList32']}")
+      print(f"ro.{partition}.product.cpu.abilist64={config['DeviceAbiList64']}")
+
+  print(f"ro.{partition}.build.date={config['Date']}")
+  print(f"ro.{partition}.build.date.utc={config['DateUtc']}")
+  # Allow optional assignments for ARC forward-declarations (b/249168657)
+  # TODO: Remove any tag-related inconsistencies once the goals from
+  # go/arc-android-sigprop-changes have been achieved.
+  print(f"ro.{partition}.build.fingerprint?={config['BuildFingerprint']}")
+  print(f"ro.{partition}.build.id?={config['BuildId']}")
+  print(f"ro.{partition}.build.tags?={config['BuildVersionTags']}")
+  print(f"ro.{partition}.build.type={config['BuildVariant']}")
+  print(f"ro.{partition}.build.version.incremental={config['BuildNumber']}")
+  print(f"ro.{partition}.build.version.release={config['Platform_version_last_stable']}")
+  print(f"ro.{partition}.build.version.release_or_codename={config['Platform_version_name']}")
+  print(f"ro.{partition}.build.version.sdk={config['Platform_sdk_version']}")
+
+def generate_build_info(args):
+  print()
+  print("####################################")
+  print("# from gen_build_prop.py:generate_build_info")
+  print("####################################")
+  print("# begin build properties")
+
+  config = args.config
+  build_flags = config["BuildFlags"]
+
+  # The ro.build.id will be set dynamically by init, by appending the unique vbmeta digest.
+  if config["BoardUseVbmetaDigestInFingerprint"]:
+    print(f"ro.build.legacy.id={config['BuildId']}")
+  else:
+    print(f"ro.build.id?={config['BuildId']}")
+
+  # ro.build.display.id is shown under Settings -> About Phone
+  if config["BuildVariant"] == "user":
+    # User builds should show:
+    # release build number or branch.buld_number non-release builds
+
+    # Dev. branches should have DISPLAY_BUILD_NUMBER set
+    if config["DisplayBuildNumber"]:
+      print(f"ro.build.display.id?={config['BuildId']} {config['BuildNumber']} {config['BuildKeys']}")
+    else:
+      print(f"ro.build.display.id?={config['BuildId']} {config['BuildKeys']}")
+  else:
+    # Non-user builds should show detailed build information (See build desc above)
+    print(f"ro.build.display.id?={config['BuildDesc']}")
+  print(f"ro.build.version.incremental={config['BuildNumber']}")
+  print(f"ro.build.version.sdk={config['Platform_sdk_version']}")
+  print(f"ro.build.version.preview_sdk={config['Platform_preview_sdk_version']}")
+  print(f"ro.build.version.preview_sdk_fingerprint={config['PlatformPreviewSdkFingerprint']}")
+  print(f"ro.build.version.codename={config['Platform_sdk_codename']}")
+  print(f"ro.build.version.all_codenames={','.join(config['Platform_version_active_codenames'])}")
+  print(f"ro.build.version.known_codenames={config['Platform_version_known_codenames']}")
+  print(f"ro.build.version.release={config['Platform_version_last_stable']}")
+  print(f"ro.build.version.release_or_codename={config['Platform_version_name']}")
+  print(f"ro.build.version.release_or_preview_display={config['Platform_display_version_name']}")
+  print(f"ro.build.version.security_patch={config['Platform_security_patch']}")
+  print(f"ro.build.version.base_os={config['Platform_base_os']}")
+  print(f"ro.build.version.min_supported_target_sdk={build_flags['RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION']}")
+  print(f"ro.build.date={config['Date']}")
+  print(f"ro.build.date.utc={config['DateUtc']}")
+  print(f"ro.build.type={config['BuildVariant']}")
+  print(f"ro.build.user={config['BuildUsername']}")
+  print(f"ro.build.host={config['BuildHostname']}")
+  # TODO: Remove any tag-related optional property declarations once the goals
+  # from go/arc-android-sigprop-changes have been achieved.
+  print(f"ro.build.tags?={config['BuildVersionTags']}")
+  # ro.build.flavor are used only by the test harness to distinguish builds.
+  # Only add _asan for a sanitized build if it isn't already a part of the
+  # flavor (via a dedicated lunch config for example).
+  print(f"ro.build.flavor={config['BuildFlavor']}")
+
+  # These values are deprecated, use "ro.product.cpu.abilist"
+  # instead (see below).
+  print(f"# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,")
+  print(f"# use ro.product.cpu.abilist instead.")
+  print(f"ro.product.cpu.abi={config['DeviceAbi'][0]}")
+  if len(config["DeviceAbi"]) > 1:
+    print(f"ro.product.cpu.abi2={config['DeviceAbi'][1]}")
+
+  if config["ProductLocales"]:
+    print(f"ro.product.locale={config['ProductLocales'][0]}")
+  print(f"ro.wifi.channels={' '.join(config['ProductDefaultWifiChannels'])}")
+
+  print(f"# ro.build.product is obsolete; use ro.product.device")
+  print(f"ro.build.product={config['DeviceName']}")
+
+  print(f"# Do not try to parse description or thumbprint")
+  print(f"ro.build.description?={config['BuildDesc']}")
+  if "build_thumbprint" in config:
+    print(f"ro.build.thumbprint={config['BuildThumbprint']}")
+
+  print(f"# end build properties")
+
+def write_properties_from_file(file):
+  print()
+  print("####################################")
+  print(f"# from {file.name}")
+  print("####################################")
+  print(file.read(), end="")
+
+def write_properties_from_variable(name, props, build_broken_dup_sysprop):
+  print()
+  print("####################################")
+  print(f"# from variable {name}")
+  print("####################################")
+
+  # Implement the legacy behavior when BUILD_BROKEN_DUP_SYSPROP is on.
+  # Optional assignments are all converted to normal assignments and
+  # when their duplicates the first one wins.
+  if build_broken_dup_sysprop:
+    processed_props = []
+    seen_props = set()
+    for line in props:
+      line = line.replace("?=", "=")
+      key, value = line.split("=", 1)
+      if key in seen_props:
+        continue
+      seen_props.add(key)
+      processed_props.append(line)
+    props = processed_props
+
+  for line in props:
+    print(line)
+
+def append_additional_system_props(args):
+  props = []
+
+  config = args.config
+
+  # Add the product-defined properties to the build properties.
+  if config["PropertySplitEnabled"] or config["VendorImageFileSystemType"]:
+    if "PRODUCT_PROPERTY_OVERRIDES" in config:
+      props += config["PRODUCT_PROPERTY_OVERRIDES"]
+
+  props.append(f"ro.treble.enabled={'true' if config['FullTreble'] else 'false'}")
+  # Set ro.llndk.api_level to show the maximum vendor API level that the LLNDK
+  # in the system partition supports.
+  if config["VendorApiLevel"]:
+    props.append(f"ro.llndk.api_level={config['VendorApiLevel']}")
+
+  # Sets ro.actionable_compatible_property.enabled to know on runtime whether
+  # the allowed list of actionable compatible properties is enabled or not.
+  props.append("ro.actionable_compatible_property.enabled=true")
+
+  # Enable core platform API violation warnings on userdebug and eng builds.
+  if config["BuildVariant"] != "user":
+    props.append("persist.debug.dalvik.vm.core_platform_api_policy=just-warn")
+
+  # Define ro.sanitize.<name> properties for all global sanitizers.
+  for sanitize_target in config["SanitizeDevice"]:
+    props.append(f"ro.sanitize.{sanitize_target}=true")
+
+  # Sets the default value of ro.postinstall.fstab.prefix to /system.
+  # Device board config should override the value to /product when needed by:
+  #
+  #     PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product
+  #
+  # It then uses ${ro.postinstall.fstab.prefix}/etc/fstab.postinstall to
+  # mount system_other partition.
+  props.append("ro.postinstall.fstab.prefix=/system")
+
+  enable_target_debugging = True
+  if config["BuildVariant"] == "user" or config["BuildVariant"] == "userdebug":
+    # Target is secure in user builds.
+    props.append("ro.secure=1")
+    props.append("security.perf_harden=1")
+
+    if config["BuildVariant"] == "user":
+      # Disable debugging in plain user builds.
+      props.append("ro.adb.secure=1")
+      enable_target_debugging = False
+
+    # Disallow mock locations by default for user builds
+    props.append("ro.allow.mock.location=0")
+  else:
+    # Turn on checkjni for non-user builds.
+    props.append("ro.kernel.android.checkjni=1")
+    # Set device insecure for non-user builds.
+    props.append("ro.secure=0")
+    # Allow mock locations by default for non user builds
+    props.append("ro.allow.mock.location=1")
+
+  if enable_target_debugging:
+    # Enable Dalvik lock contention logging.
+    props.append("dalvik.vm.lockprof.threshold=500")
+
+    # Target is more debuggable and adbd is on by default
+    props.append("ro.debuggable=1")
+  else:
+    # Target is less debuggable and adbd is off by default
+    props.append("ro.debuggable=0")
+
+  if config["BuildVariant"] == "eng":
+    if "ro.setupwizard.mode=ENABLED" in props:
+      # Don't require the setup wizard on eng builds
+      props = list(filter(lambda x: not x.startswith("ro.setupwizard.mode="), props))
+      props.append("ro.setupwizard.mode=OPTIONAL")
+
+    if not config["SdkBuild"]:
+      # To speedup startup of non-preopted builds, don't verify or compile the boot image.
+      props.append("dalvik.vm.image-dex2oat-filter=extract")
+    # b/323566535
+    props.append("init.svc_debug.no_fatal.zygote=true")
+
+  if config["SdkBuild"]:
+    props.append("xmpp.auto-presence=true")
+    props.append("ro.config.nocheckin=yes")
+
+  props.append("net.bt.name=Android")
+
+  # This property is set by flashing debug boot image, so default to false.
+  props.append("ro.force.debuggable=0")
+
+  config["ADDITIONAL_SYSTEM_PROPERTIES"] = props
+
+def append_additional_vendor_props(args):
+  props = []
+
+  config = args.config
+  build_flags = config["BuildFlags"]
+
+  # Add cpu properties for bionic and ART.
+  props.append(f"ro.bionic.arch={config['DeviceArch']}")
+  props.append(f"ro.bionic.cpu_variant={config['DeviceCpuVariantRuntime']}")
+  props.append(f"ro.bionic.2nd_arch={config['DeviceSecondaryArch']}")
+  props.append(f"ro.bionic.2nd_cpu_variant={config['DeviceSecondaryCpuVariantRuntime']}")
+
+  props.append(f"persist.sys.dalvik.vm.lib.2=libart.so")
+  props.append(f"dalvik.vm.isa.{config['DeviceArch']}.variant={config['Dex2oatTargetCpuVariantRuntime']}")
+  if config["Dex2oatTargetInstructionSetFeatures"]:
+    props.append(f"dalvik.vm.isa.{config['DeviceArch']}.features={config['Dex2oatTargetInstructionSetFeatures']}")
+
+  if config["DeviceSecondaryArch"]:
+    props.append(f"dalvik.vm.isa.{config['DeviceSecondaryArch']}.variant={config['SecondaryDex2oatCpuVariantRuntime']}")
+    if config["SecondaryDex2oatInstructionSetFeatures"]:
+      props.append(f"dalvik.vm.isa.{config['DeviceSecondaryArch']}.features={config['SecondaryDex2oatInstructionSetFeatures']}")
+
+  # Although these variables are prefixed with TARGET_RECOVERY_, they are also needed under charger
+  # mode (via libminui).
+  if config["RecoveryDefaultRotation"]:
+    props.append(f"ro.minui.default_rotation={config['RecoveryDefaultRotation']}")
+
+  if config["RecoveryOverscanPercent"]:
+    props.append(f"ro.minui.overscan_percent={config['RecoveryOverscanPercent']}")
+
+  if config["RecoveryPixelFormat"]:
+    props.append(f"ro.minui.pixel_format={config['RecoveryPixelFormat']}")
+
+  if "UseDynamicPartitions" in config:
+    props.append(f"ro.boot.dynamic_partitions={'true' if config['UseDynamicPartitions'] else 'false'}")
+
+  if "RetrofitDynamicPartitions" in config:
+    props.append(f"ro.boot.dynamic_partitions_retrofit={'true' if config['RetrofitDynamicPartitions'] else 'false'}")
+
+  if config["ShippingApiLevel"]:
+    props.append(f"ro.product.first_api_level={config['ShippingApiLevel']}")
+
+  if config["ShippingVendorApiLevel"]:
+    props.append(f"ro.vendor.api_level={config['ShippingVendorApiLevel']}")
+
+  if config["BuildVariant"] != "user" and config["BuildDebugfsRestrictionsEnabled"]:
+    props.append(f"ro.product.debugfs_restrictions.enabled=true")
+
+  # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
+  # This must not be defined for the non-GRF devices.
+  # The values of the GRF properties will be verified by post_process_props.py
+  if config["BoardShippingApiLevel"]:
+    props.append(f"ro.board.first_api_level={config['ProductShippingApiLevel']}")
+
+  # Build system set BOARD_API_LEVEL to show the api level of the vendor API surface.
+  # This must not be altered outside of build system.
+  if config["VendorApiLevel"]:
+    props.append(f"ro.board.api_level={config['VendorApiLevel']}")
+
+  # RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen.
+  if build_flags["RELEASE_BOARD_API_LEVEL_FROZEN"]:
+    props.append(f"ro.board.api_frozen=true")
+
+  # Set build prop. This prop is read by ota_from_target_files when generating OTA,
+  # to decide if VABC should be disabled.
+  if config["DontUseVabcOta"]:
+    props.append(f"ro.vendor.build.dont_use_vabc=true")
+
+  # Set the flag in vendor. So VTS would know if the new fingerprint format is in use when
+  # the system images are replaced by GSI.
+  if config["BoardUseVbmetaDigestInFingerprint"]:
+    props.append(f"ro.vendor.build.fingerprint_has_digest=1")
+
+  props.append(f"ro.vendor.build.security_patch={config['VendorSecurityPatch']}")
+  props.append(f"ro.product.board={config['BootloaderBoardName']}")
+  props.append(f"ro.board.platform={config['BoardPlatform']}")
+  props.append(f"ro.hwui.use_vulkan={'true' if config['UsesVulkan'] else 'false'}")
+
+  if config["ScreenDensity"]:
+    props.append(f"ro.sf.lcd_density={config['ScreenDensity']}")
+
+  if "AbOtaUpdater" in config:
+    props.append(f"ro.build.ab_update={'true' if config['AbOtaUpdater'] else 'false'}")
+    if config["AbOtaUpdater"]:
+      props.append(f"ro.vendor.build.ab_ota_partitions={config['AbOtaPartitions']}")
+
+  config["ADDITIONAL_VENDOR_PROPERTIES"] = props
+
+def append_additional_product_props(args):
+  props = []
+
+  config = args.config
+
+  # Add the system server compiler filter if they are specified for the product.
+  if config["SystemServerCompilerFilter"]:
+    props.append(f"dalvik.vm.systemservercompilerfilter={config['SystemServerCompilerFilter']}")
+
+  # Add the 16K developer args if it is defined for the product.
+  props.append(f"ro.product.build.16k_page.enabled={'true' if config['Product16KDeveloperOption'] else 'false'}")
+
+  props.append(f"ro.build.characteristics={config['AAPTCharacteristics']}")
+
+  if "AbOtaUpdater" in config and config["AbOtaUpdater"]:
+    props.append(f"ro.product.ab_ota_partitions={config['AbOtaPartitions']}")
+
+  # Set this property for VTS to skip large page size tests on unsupported devices.
+  props.append(f"ro.product.cpu.pagesize.max={config['DeviceMaxPageSizeSupported']}")
+
+  if config["NoBionicPageSizeMacro"]:
+    props.append(f"ro.product.build.no_bionic_page_size_macro=true")
+
+  # If the value is "default", it will be mangled by post_process_props.py.
+  props.append(f"ro.dalvik.vm.enable_uffd_gc={config['EnableUffdGc']}")
+
+  config["ADDITIONAL_PRODUCT_PROPERTIES"] = props
+
+def build_system_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = [
+    "ADDITIONAL_SYSTEM_PROPERTIES",
+    "PRODUCT_SYSTEM_PROPERTIES",
+    # TODO(b/117892318): deprecate this
+    "PRODUCT_SYSTEM_DEFAULT_PROPERTIES",
+  ]
+
+  if not config["PropertySplitEnabled"]:
+    variables += [
+      "ADDITIONAL_VENDOR_PROPERTIES",
+      "PRODUCT_VENDOR_PROPERTIES",
+    ]
+
+  build_prop(args, gen_build_info=True, gen_common_build_props=True, variables=variables)
+
+'''
+def build_vendor_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = []
+  if config["PropertySplitEnabled"]:
+    variables += [
+      "ADDITIONAL_VENDOR_PROPERTIES",
+      "PRODUCT_VENDOR_PROPERTIES",
+      # TODO(b/117892318): deprecate this
+      "PRODUCT_DEFAULT_PROPERTY_OVERRIDES",
+      "PRODUCT_PROPERTY_OVERRIDES",
+    ]
+
+  build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
+
+def build_product_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = [
+    "ADDITIONAL_PRODUCT_PROPERTIES",
+    "PRODUCT_PRODUCT_PROPERTIES",
+  ]
+  build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
+'''
+
+def build_prop(args, gen_build_info, gen_common_build_props, variables):
+  config = args.config
+
+  if gen_common_build_props:
+    generate_common_build_props(args)
+
+  if gen_build_info:
+    generate_build_info(args)
+
+  for prop_file in args.prop_files:
+    write_properties_from_file(prop_file)
+
+  for variable in variables:
+    if variable in config:
+      write_properties_from_variable(variable, config[variable], args.build_broken_dup_sysprop)
+
+def main():
+  args = parse_args()
+
+  with contextlib.redirect_stdout(args.out):
+    if args.partition == "system":
+      build_system_prop(args)
+      '''
+    elif args.partition == "vendor":
+      build_vendor_prop(args)
+    elif args.partition == "product":
+      build_product_prop(args)
+      '''
+    else:
+      sys.exit(f"not supported partition {args.partition}")
+
+if __name__ == "__main__":
+  main()
diff --git a/scripts/merge_json.py b/scripts/merge_json.py
new file mode 100644
index 0000000..7e2f6eb
--- /dev/null
+++ b/scripts/merge_json.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# Copyright 2024 The Android Open Source Project
+#
+# 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.
+#
+"""A tool for merging two or more JSON files."""
+
+import argparse
+import logging
+import json
+import sys
+
+def parse_args():
+  """Parse commandline arguments."""
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument("output", help="output JSON file", type=argparse.FileType("w"))
+  parser.add_argument("input", help="input JSON files", nargs="+", type=argparse.FileType("r"))
+  return parser.parse_args()
+
+def main():
+  """Program entry point."""
+  args = parse_args()
+  merged_dict = {}
+  has_error = False
+  logger = logging.getLogger(__name__)
+
+  for json_file in args.input:
+    try:
+      data = json.load(json_file)
+    except json.JSONDecodeError as e:
+      logger.error(f"Error parsing JSON in file: {json_file.name}. Reason: {e}")
+      has_error = True
+      continue
+
+    for key, value in data.items():
+      if key not in merged_dict:
+        merged_dict[key] = value
+      elif merged_dict[key] == value:
+        logger.warning(f"Duplicate key '{key}' with identical values found.")
+      else:
+        logger.error(f"Conflicting values for key '{key}': {merged_dict[key]} != {value}")
+        has_error = True
+
+  if has_error:
+    sys.exit(1)
+
+  json.dump(merged_dict, args.output)
+
+if __name__ == "__main__":
+  main()
diff --git a/sdk/sdk.go b/sdk/sdk.go
index fd16ab6..5b644fd 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -193,6 +193,10 @@
 		// Generate the snapshot from the member info.
 		s.buildSnapshot(ctx, sdkVariants)
 	}
+
+	if s.snapshotFile.Valid() {
+		ctx.SetOutputFiles([]android.Path{s.snapshotFile.Path()}, "")
+	}
 }
 
 func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
@@ -222,18 +226,6 @@
 	}}
 }
 
-func (s *sdk) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if s.snapshotFile.Valid() {
-			return []android.Path{s.snapshotFile.Path()}, nil
-		}
-		return nil, fmt.Errorf("snapshot file not defined. This is most likely because this isn't the common_os variant of this module")
-	default:
-		return nil, fmt.Errorf("unknown tag %q", tag)
-	}
-}
-
 // gatherTraits gathers the traits from the dynamically generated trait specific properties.
 //
 // Returns a map from member name to the set of required traits.
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 48a442d..2e48d83 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -212,6 +212,14 @@
 
 func (s *ShBinary) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (s *ShBinary) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return s.InstallInVendor()
+}
+
+func (s *ShBinary) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return s.InstallInProduct()
+}
+
 func (s *ShBinary) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !s.InstallInRecovery() && !s.InstallInRamdisk() && !s.InstallInVendorRamdisk() && !s.ModuleBase.InstallInVendor()
 }
@@ -233,14 +241,7 @@
 }
 
 func (s *ShBinary) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	extraVariations := []string{}
-	if s.InstallInProduct() {
-		extraVariations = append(extraVariations, cc.ProductVariation)
-	}
-	if s.InstallInVendor() {
-		extraVariations = append(extraVariations, cc.VendorVariation)
-	}
-	return extraVariations
+	return nil
 }
 
 func (s *ShBinary) SetImageVariation(ctx android.BaseModuleContext, variation string) {
@@ -306,7 +307,7 @@
 func (s *ShBinary) GetSubname(ctx android.ModuleContext) string {
 	ret := ""
 	if s.properties.ImageVariation != "" {
-		if s.properties.ImageVariation != cc.VendorVariation {
+		if s.properties.ImageVariation != android.VendorVariation {
 			ret = "." + s.properties.ImageVariation
 		}
 	}
diff --git a/shared/Android.bp b/shared/Android.bp
index 3c84f55..d5e8614 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -15,7 +15,6 @@
         "paths_test.go",
     ],
     deps: [
-        "soong-bazel",
         "golang-protobuf-proto",
     ],
 }
diff --git a/shared/paths.go b/shared/paths.go
index fca8b4c..1ee66d5 100644
--- a/shared/paths.go
+++ b/shared/paths.go
@@ -18,8 +18,6 @@
 
 import (
 	"path/filepath"
-
-	"android/soong/bazel"
 )
 
 // A SharedPaths represents a list of paths that are shared between
@@ -49,11 +47,3 @@
 func TempDirForOutDir(outDir string) (tempPath string) {
 	return filepath.Join(outDir, ".temp")
 }
-
-// BazelMetricsFilename returns the bazel profile filename based
-// on the action name. This is to help to store a set of bazel
-// profiles since bazel may execute multiple times during a single
-// build.
-func BazelMetricsFilename(s SharedPaths, actionName bazel.RunName) string {
-	return filepath.Join(s.BazelMetricsDir(), actionName.String()+"_bazel_profile.gz")
-}
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
index b416ebd..278247e 100644
--- a/snapshot/host_fake_snapshot.go
+++ b/snapshot/host_fake_snapshot.go
@@ -129,12 +129,12 @@
 			if !seen[outFile] {
 				seen[outFile] = true
 				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
-				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(module), false})
+				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(ctx, module), false})
 			}
 		}
 	})
 	// Update any module prebuilt information
-	for idx, _ := range jsonData {
+	for idx := range jsonData {
 		if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
 			// Prebuilt exists for this module
 			jsonData[idx].Prebuilt = true
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index edcc163..1ecab7d 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -101,7 +101,7 @@
 
 	// Create JSON file based on the direct dependencies
 	ctx.VisitDirectDeps(func(dep android.Module) {
-		desc := hostJsonDesc(dep)
+		desc := hostJsonDesc(ctx, dep)
 		if desc != nil {
 			jsonData = append(jsonData, *desc)
 		}
@@ -209,7 +209,7 @@
 
 // Create JSON description for given module, only create descriptions for binary modules
 // and rust_proc_macro modules which provide a valid HostToolPath
-func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+func hostJsonDesc(ctx android.ConfigAndErrorContext, m android.Module) *SnapshotJsonFlags {
 	path := hostToolPath(m)
 	relPath := hostRelativePathString(m)
 	procMacro := false
@@ -226,7 +226,7 @@
 		props := &SnapshotJsonFlags{
 			ModuleStemName:      moduleStem,
 			Filename:            path.String(),
-			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
+			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames(ctx)...),
 			RelativeInstallPath: relPath,
 			RustProcMacro:       procMacro,
 			CrateName:           crateName,
diff --git a/ui/build/config.go b/ui/build/config.go
index feded1c..c4a6797 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -85,6 +85,7 @@
 	skipMetricsUpload        bool
 	buildStartedTime         int64 // For metrics-upload-only - manually specify a build-started time
 	buildFromSourceStub      bool
+	incrementalBuildActions  bool
 	ensureAllowlistIntegrity bool // For CI builds - make sure modules are mixed-built
 
 	// From the product config
@@ -811,6 +812,8 @@
 			}
 		} else if arg == "--build-from-source-stub" {
 			c.buildFromSourceStub = true
+		} else if arg == "--incremental-build-actions" {
+			c.incrementalBuildActions = true
 		} else if strings.HasPrefix(arg, "--build-command=") {
 			buildCmd := strings.TrimPrefix(arg, "--build-command=")
 			// remove quotations
@@ -1243,6 +1246,11 @@
 }
 
 func (c *configImpl) canSupportRBE() bool {
+	// Only supported on linux
+	if runtime.GOOS != "linux" {
+		return false
+	}
+
 	// Do not use RBE with prod credentials in scenarios when stubby doesn't exist, since
 	// its unlikely that we will be able to obtain necessary creds without stubby.
 	authType, _ := c.rbeAuth()
@@ -1488,6 +1496,15 @@
 	}
 }
 
+func (c *configImpl) SoongExtraVarsFile() string {
+	targetProduct, err := c.TargetProductOrErr()
+	if err != nil {
+		return filepath.Join(c.SoongOutDir(), "soong.extra.variables")
+	} else {
+		return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+".extra.variables")
+	}
+}
+
 func (c *configImpl) SoongNinjaFile() string {
 	targetProduct, err := c.TargetProductOrErr()
 	if err != nil {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 2f3150d..77fee0a 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -27,7 +27,6 @@
 
 	"android/soong/ui/tracer"
 
-	"android/soong/bazel"
 	"android/soong/ui/metrics"
 	"android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
@@ -315,6 +314,9 @@
 	if config.ensureAllowlistIntegrity {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--ensure-allowlist-integrity")
 	}
+	if config.incrementalBuildActions {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-build-actions")
+	}
 
 	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
 
@@ -600,10 +602,6 @@
 
 		checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(soongBuildTag))
 
-		// Remove bazel files in the event that bazel is disabled for the build.
-		// These files may have been left over from a previous bazel-enabled build.
-		cleanBazelFiles(config)
-
 		if config.JsonModuleGraph() {
 			checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
 		}
@@ -694,6 +692,7 @@
 		}
 	}
 	distFile(ctx, config, config.SoongVarsFile(), "soong")
+	distFile(ctx, config, config.SoongExtraVarsFile(), "soong")
 
 	if !config.SkipKati() {
 		distGzipFile(ctx, config, config.SoongAndroidMk(), "soong")
@@ -754,18 +753,6 @@
 	}
 }
 
-func cleanBazelFiles(config Config) {
-	files := []string{
-		shared.JoinPath(config.SoongOutDir(), "bp2build"),
-		shared.JoinPath(config.SoongOutDir(), "workspace"),
-		shared.JoinPath(config.SoongOutDir(), bazel.SoongInjectionDirName),
-		shared.JoinPath(config.OutDir(), "bazel")}
-
-	for _, f := range files {
-		os.RemoveAll(f)
-	}
-}
-
 func runMicrofactory(ctx Context, config Config, name string, pkg string, mapping map[string]string) {
 	ctx.BeginTrace(metrics.RunSoong, name)
 	defer ctx.EndTrace()
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 24ad082..687ad6f 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -64,7 +64,8 @@
 	outDir := config.OutDir()
 	modulePathsDir := filepath.Join(outDir, ".module_paths")
 	rawFilesDir := filepath.Join(outDir, "soong", "raw")
-	variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
+	variablesFilePath := config.SoongVarsFile()
+	extraVariablesFilePath := config.SoongExtraVarsFile()
 
 	// dexpreopt.config is an input to the soong_docs action, which runs the
 	// soong_build primary builder. However, this file is created from $(shell)
@@ -95,6 +96,7 @@
 		if strings.HasPrefix(line, modulePathsDir) ||
 			strings.HasPrefix(line, rawFilesDir) ||
 			line == variablesFilePath ||
+			line == extraVariablesFilePath ||
 			line == dexpreoptConfigFilePath ||
 			line == buildDatetimeFilePath ||
 			line == bpglob ||