Merge "Fix run_tool_with_logging_test in CI" into main
diff --git a/Android.bp b/Android.bp
index 682711d..0d1ff02 100644
--- a/Android.bp
+++ b/Android.bp
@@ -132,7 +132,7 @@
     product_config: ":product_config",
 
     // Currently, only microdroid can refer to buildinfo.prop
-    visibility: ["//packages/modules/Virtualization/microdroid"],
+    visibility: ["//packages/modules/Virtualization/build/microdroid"],
 }
 
 // container for apex_contributions selected using build flags
diff --git a/aconfig/init.go b/aconfig/init.go
index 256b213..de155ab 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -47,7 +47,7 @@
 	// For create-device-config-sysprops: Generate aconfig flag value map text file
 	aconfigTextRule = pctx.AndroidStaticRule("aconfig_text",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}'` +
+			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}:{permission}={state:bool}'` +
 				` --cache ${in}` +
 				` --out ${out}.tmp` +
 				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
diff --git a/android/Android.bp b/android/Android.bp
index 774d24a..ce27241 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -93,6 +93,7 @@
         "register.go",
         "rule_builder.go",
         "sandbox.go",
+        "sbom.go",
         "sdk.go",
         "sdk_version.go",
         "shared_properties.go",
diff --git a/android/arch.go b/android/arch.go
index e0c6908..6d896e5 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"reflect"
 	"runtime"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -587,19 +588,21 @@
 	}
 
 	osTargets := mctx.Config().Targets[os]
+
 	image := base.commonProperties.ImageVariation
 	// Filter NativeBridge targets unless they are explicitly supported.
 	// Skip creating native bridge variants for non-core modules.
 	if os == Android && !(base.IsNativeBridgeSupported() && image == CoreVariation) {
+		osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+			return bool(t.NativeBridge)
+		})
+	}
 
-		var targets []Target
-		for _, t := range osTargets {
-			if !t.NativeBridge {
-				targets = append(targets, t)
-			}
-		}
-
-		osTargets = targets
+	// Filter HostCross targets if disabled.
+	if base.HostSupported() && !base.HostCrossSupported() {
+		osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+			return t.HostCross
+		})
 	}
 
 	// only the primary arch in the ramdisk / vendor_ramdisk / recovery partition
diff --git a/android/arch_list.go b/android/arch_list.go
index 4233456..f1289a3 100644
--- a/android/arch_list.go
+++ b/android/arch_list.go
@@ -16,7 +16,6 @@
 
 var archVariants = map[ArchType][]string{
 	Arm: {
-		"armv7-a",
 		"armv7-a-neon",
 		"armv8-a",
 		"armv8-2a",
diff --git a/android/arch_test.go b/android/arch_test.go
index f0a58a9..6134a06 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -332,6 +332,12 @@
 		}
 
 		module {
+			name: "nohostcross",
+			host_supported: true,
+			host_cross_supported: false,
+		}
+
+		module {
 			name: "baz",
 			device_supported: false,
 		}
@@ -355,13 +361,14 @@
 	`
 
 	testCases := []struct {
-		name          string
-		preparer      FixturePreparer
-		fooVariants   []string
-		barVariants   []string
-		bazVariants   []string
-		quxVariants   []string
-		firstVariants []string
+		name                string
+		preparer            FixturePreparer
+		fooVariants         []string
+		barVariants         []string
+		noHostCrossVariants []string
+		bazVariants         []string
+		quxVariants         []string
+		firstVariants       []string
 
 		multiTargetVariants    []string
 		multiTargetVariantsMap map[string][]string
@@ -373,6 +380,7 @@
 			preparer:            nil,
 			fooVariants:         []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			barVariants:         append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
+			noHostCrossVariants: append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
 			bazVariants:         nil,
 			quxVariants:         append(buildOS32Variants, "android_arm_armv7-a-neon"),
 			firstVariants:       append(buildOS64Variants, "android_arm64_armv8-a"),
@@ -390,6 +398,7 @@
 			}),
 			fooVariants:         nil,
 			barVariants:         buildOSVariants,
+			noHostCrossVariants: buildOSVariants,
 			bazVariants:         nil,
 			quxVariants:         buildOS32Variants,
 			firstVariants:       buildOS64Variants,
@@ -406,6 +415,7 @@
 			}),
 			fooVariants:         []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			barVariants:         []string{"linux_musl_x86_64", "linux_musl_arm64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
+			noHostCrossVariants: []string{"linux_musl_x86_64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			bazVariants:         nil,
 			quxVariants:         []string{"linux_musl_x86", "android_arm_armv7-a-neon"},
 			firstVariants:       []string{"linux_musl_x86_64", "linux_musl_arm64", "android_arm64_armv8-a"},
@@ -461,6 +471,10 @@
 				t.Errorf("want bar variants:\n%q\ngot:\n%q\n", w, g)
 			}
 
+			if g, w := enabledVariants(ctx, "nohostcross"), tt.noHostCrossVariants; !reflect.DeepEqual(w, g) {
+				t.Errorf("want nohostcross variants:\n%q\ngot:\n%q\n", w, g)
+			}
+
 			if g, w := enabledVariants(ctx, "baz"), tt.bazVariants; !reflect.DeepEqual(w, g) {
 				t.Errorf("want baz variants:\n%q\ngot:\n%q\n", w, g)
 			}
diff --git a/android/config.go b/android/config.go
index d16377d..cadc929 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1195,6 +1195,10 @@
 	return Bool(c.productVariables.UseGoma)
 }
 
+func (c *config) UseABFS() bool {
+	return Bool(c.productVariables.UseABFS)
+}
+
 func (c *config) UseRBE() bool {
 	return Bool(c.productVariables.UseRBE)
 }
@@ -1356,10 +1360,6 @@
 	return ExistentPathForSource(ctx, "frameworks", "base", "Android.bp").Valid()
 }
 
-func (c *config) VndkSnapshotBuildArtifacts() bool {
-	return Bool(c.productVariables.VndkSnapshotBuildArtifacts)
-}
-
 func (c *config) HasMultilibConflict(arch ArchType) bool {
 	return c.multilibConflicts[arch]
 }
@@ -1423,10 +1423,6 @@
 	return "vendor"
 }
 
-func (c *deviceConfig) RecoverySnapshotVersion() string {
-	return String(c.config.productVariables.RecoverySnapshotVersion)
-}
-
 func (c *deviceConfig) CurrentApiLevelForVendorModules() string {
 	return StringDefault(c.config.productVariables.DeviceCurrentApiLevelForVendorModules, "current")
 }
@@ -1804,22 +1800,6 @@
 	return c.SystemExtSepolicyPrebuiltApiDir() != "" || c.ProductSepolicyPrebuiltApiDir() != ""
 }
 
-func (c *deviceConfig) DirectedVendorSnapshot() bool {
-	return c.config.productVariables.DirectedVendorSnapshot
-}
-
-func (c *deviceConfig) VendorSnapshotModules() map[string]bool {
-	return c.config.productVariables.VendorSnapshotModules
-}
-
-func (c *deviceConfig) DirectedRecoverySnapshot() bool {
-	return c.config.productVariables.DirectedRecoverySnapshot
-}
-
-func (c *deviceConfig) RecoverySnapshotModules() map[string]bool {
-	return c.config.productVariables.RecoverySnapshotModules
-}
-
 func createDirsMap(previous map[string]bool, dirs []string) (map[string]bool, error) {
 	var ret = make(map[string]bool)
 	for _, dir := range dirs {
@@ -1846,40 +1826,6 @@
 	return dirMap.(map[string]bool)
 }
 
-var vendorSnapshotDirsExcludedKey = NewOnceKey("VendorSnapshotDirsExcludedMap")
-
-func (c *deviceConfig) VendorSnapshotDirsExcludedMap() map[string]bool {
-	return c.createDirsMapOnce(vendorSnapshotDirsExcludedKey, nil,
-		c.config.productVariables.VendorSnapshotDirsExcluded)
-}
-
-var vendorSnapshotDirsIncludedKey = NewOnceKey("VendorSnapshotDirsIncludedMap")
-
-func (c *deviceConfig) VendorSnapshotDirsIncludedMap() map[string]bool {
-	excludedMap := c.VendorSnapshotDirsExcludedMap()
-	return c.createDirsMapOnce(vendorSnapshotDirsIncludedKey, excludedMap,
-		c.config.productVariables.VendorSnapshotDirsIncluded)
-}
-
-var recoverySnapshotDirsExcludedKey = NewOnceKey("RecoverySnapshotDirsExcludedMap")
-
-func (c *deviceConfig) RecoverySnapshotDirsExcludedMap() map[string]bool {
-	return c.createDirsMapOnce(recoverySnapshotDirsExcludedKey, nil,
-		c.config.productVariables.RecoverySnapshotDirsExcluded)
-}
-
-var recoverySnapshotDirsIncludedKey = NewOnceKey("RecoverySnapshotDirsIncludedMap")
-
-func (c *deviceConfig) RecoverySnapshotDirsIncludedMap() map[string]bool {
-	excludedMap := c.RecoverySnapshotDirsExcludedMap()
-	return c.createDirsMapOnce(recoverySnapshotDirsIncludedKey, excludedMap,
-		c.config.productVariables.RecoverySnapshotDirsIncluded)
-}
-
-func (c *deviceConfig) HostFakeSnapshotEnabled() bool {
-	return c.config.productVariables.HostFakeSnapshotEnabled
-}
-
 func (c *deviceConfig) ShippingApiLevel() ApiLevel {
 	if c.config.productVariables.Shipping_api_level == nil {
 		return NoneApiLevel
@@ -2127,3 +2073,11 @@
 func (c *config) OemProperties() []string {
 	return c.productVariables.OemProperties
 }
+
+func (c *config) UseDebugArt() bool {
+	if c.productVariables.ArtTargetIncludeDebugBuild != nil {
+		return Bool(c.productVariables.ArtTargetIncludeDebugBuild)
+	}
+
+	return Bool(c.productVariables.Eng)
+}
diff --git a/android/defaults.go b/android/defaults.go
index ff79002..c0a2fc6 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -28,7 +28,7 @@
 var DefaultsDepTag defaultsDependencyTag
 
 type defaultsProperties struct {
-	Defaults []string
+	Defaults proptools.Configurable[[]string]
 }
 
 type DefaultableModuleBase struct {
@@ -278,13 +278,14 @@
 
 func defaultsDepsMutator(ctx BottomUpMutatorContext) {
 	if defaultable, ok := ctx.Module().(Defaultable); ok {
-		ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...)
+		ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults.GetOrDefault(ctx, nil)...)
 	}
 }
 
 func defaultsMutator(ctx TopDownMutatorContext) {
 	if defaultable, ok := ctx.Module().(Defaultable); ok {
-		if len(defaultable.defaults().Defaults) > 0 {
+		defaults := defaultable.defaults().Defaults.GetOrDefault(ctx, nil)
+		if len(defaults) > 0 {
 			var defaultsList []Defaults
 			seen := make(map[Defaults]bool)
 
@@ -294,7 +295,7 @@
 						if !seen[defaults] {
 							seen[defaults] = true
 							defaultsList = append(defaultsList, defaults)
-							return len(defaults.defaults().Defaults) > 0
+							return len(defaults.defaults().Defaults.GetOrDefault(ctx, nil)) > 0
 						}
 					} else {
 						ctx.PropertyErrorf("defaults", "module %s is not an defaults module",
diff --git a/android/module.go b/android/module.go
index dd56031..5c2b1e1 100644
--- a/android/module.go
+++ b/android/module.go
@@ -389,7 +389,7 @@
 	Init_rc []string `android:"arch_variant,path"`
 
 	// VINTF manifest fragments to be installed if this module is installed
-	Vintf_fragments []string `android:"path"`
+	Vintf_fragments proptools.Configurable[[]string] `android:"path"`
 
 	// names of other modules to install if this module is installed
 	Required proptools.Configurable[[]string] `android:"arch_variant"`
@@ -603,6 +603,11 @@
 	Device_supported *bool
 }
 
+type hostCrossProperties struct {
+	// If set to true, build a variant of the module for the host cross.  Defaults to true.
+	Host_cross_supported *bool
+}
+
 type Multilib string
 
 const (
@@ -718,6 +723,10 @@
 		m.AddProperties(&base.hostAndDeviceProperties)
 	}
 
+	if hod&hostCrossSupported != 0 {
+		m.AddProperties(&base.hostCrossProperties)
+	}
+
 	initArchModule(m)
 }
 
@@ -803,6 +812,7 @@
 	distProperties          distProperties
 	variableProperties      interface{}
 	hostAndDeviceProperties hostAndDeviceProperties
+	hostCrossProperties     hostCrossProperties
 
 	// Arch specific versions of structs in GetProperties() prior to
 	// initialization in InitAndroidArchModule, lets call it `generalProperties`.
@@ -1299,7 +1309,11 @@
 	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
 	// value has the hostDefault bit set.
 	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
-	return hod&hostCrossSupported != 0 && hostEnabled
+
+	// Default true for the Host_cross_supported property
+	hostCrossEnabled := proptools.BoolDefault(m.hostCrossProperties.Host_cross_supported, true)
+
+	return hod&hostCrossSupported != 0 && hostEnabled && hostCrossEnabled
 }
 
 func (m *ModuleBase) Platform() bool {
@@ -1839,7 +1853,7 @@
 				}
 			}
 
-			m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments)
+			m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments.GetOrDefault(ctx, nil))
 			vintfDir := PathForModuleInstall(ctx, "etc", "vintf", "manifest")
 			for _, src := range m.vintfFragmentsPaths {
 				installedVintfFragment := vintfDir.Join(ctx, src.Base())
@@ -2199,6 +2213,9 @@
 		switch variable {
 		case "debuggable":
 			return proptools.ConfigurableValueBool(ctx.Config().Debuggable())
+		case "use_debug_art":
+			// TODO(b/234351700): Remove once ART does not have separated debug APEX
+			return proptools.ConfigurableValueBool(ctx.Config().UseDebugArt())
 		default:
 			// TODO(b/323382414): Might add these on a case-by-case basis
 			ctx.OtherModulePropertyErrorf(m, property, fmt.Sprintf("TODO(b/323382414): Product variable %q is not yet supported in selects", variable))
@@ -2504,8 +2521,9 @@
 	} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
 		providerData, _ := cta.moduleProvider(module, OutputFilesProvider)
 		outputFiles, _ = providerData.(OutputFilesInfo)
+	} else {
+		return nil, fmt.Errorf("unsupported context %q in method outputFilesForModuleFromProvider", reflect.TypeOf(ctx))
 	}
-	// TODO: Add a check for skipped context
 
 	if outputFiles.isEmpty() {
 		return nil, OutputFilesProviderNotSet
diff --git a/android/module_test.go b/android/module_test.go
index 922ea21..829c079 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -722,7 +722,6 @@
 				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "x86_64 a"},
 				propInfo{Name: "B", Type: "bool", Value: "true"},
 				propInfo{Name: "C", Type: "string slice", Values: []string{"default_c", "c"}},
-				propInfo{Name: "Defaults", Type: "string slice", Values: []string{"foo_defaults"}},
 				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"},
 				propInfo{Name: "Name", Type: "string", Value: "foo"},
 				propInfo{Name: "Nested.E", Type: "string", Value: "nested e"},
@@ -746,7 +745,6 @@
 			foo := result.ModuleForTests("foo", "").Module().base()
 
 			AssertDeepEquals(t, "foo ", tc.expectedProps, foo.propertiesWithValues())
-
 		})
 	}
 }
diff --git a/android/neverallow.go b/android/neverallow.go
index ef4b8b8..0f363e7 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -212,7 +212,7 @@
 
 func createCcStubsRule() Rule {
 	ccStubsImplementationInstallableProjectsAllowedList := []string{
-		"packages/modules/Virtualization/vm_payload",
+		"packages/modules/Virtualization/libs/libvm_payload",
 	}
 
 	return NeverAllow().
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 85e29bd..8b03124 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -58,6 +58,7 @@
 	sboxInputs       bool
 	sboxManifestPath WritablePath
 	missingDeps      []string
+	args             map[string]string
 }
 
 // NewRuleBuilder returns a newly created RuleBuilder.
@@ -78,6 +79,17 @@
 	return rb
 }
 
+// Set the phony_output argument.
+// This causes the output files to be ignored.
+// If the output isn't created, it's not treated as an error.
+// The build rule is run every time whether or not the output is created.
+func (rb *RuleBuilder) SetPhonyOutput() {
+	if rb.args == nil {
+		rb.args = make(map[string]string)
+	}
+	rb.args["phony_output"] = "true"
+}
+
 // RuleBuilderInstall is a tuple of install from and to locations.
 type RuleBuilderInstall struct {
 	From Path
@@ -726,6 +738,12 @@
 		commandString = proptools.NinjaEscape(commandString)
 	}
 
+	args_vars := make([]string, len(r.args))
+	i := 0
+	for k, _ := range r.args {
+		args_vars[i] = k
+		i++
+	}
 	r.ctx.Build(r.pctx, BuildParams{
 		Rule: r.ctx.Rule(r.pctx, name, blueprint.RuleParams{
 			Command:        commandString,
@@ -734,7 +752,7 @@
 			Rspfile:        proptools.NinjaEscape(rspFile),
 			RspfileContent: rspFileContent,
 			Pool:           pool,
-		}),
+		}, args_vars...),
 		Inputs:          rspFileInputs,
 		Implicits:       inputs,
 		OrderOnly:       r.OrderOnlys(),
@@ -744,6 +762,7 @@
 		Depfile:         depFile,
 		Deps:            depFormat,
 		Description:     desc,
+		Args:            r.args,
 	})
 }
 
diff --git a/android/sbom.go b/android/sbom.go
new file mode 100644
index 0000000..dd2d2fa
--- /dev/null
+++ b/android/sbom.go
@@ -0,0 +1,100 @@
+// 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 (
+	"io"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	// Command line tool to generate SBOM in Soong
+	genSbom = pctx.HostBinToolVariable("genSbom", "gen_sbom")
+
+	// Command to generate SBOM in Soong.
+	genSbomRule = pctx.AndroidStaticRule("genSbomRule", blueprint.RuleParams{
+		Command:     "rm -rf $out && ${genSbom} --output_file ${out} --metadata ${in} --product_out ${productOut} --soong_out ${soongOut} --build_version \"$$(cat ${buildFingerprintFile})\" --product_mfr \"${productManufacturer}\" --json",
+		CommandDeps: []string{"${genSbom}"},
+	}, "productOut", "soongOut", "buildFingerprintFile", "productManufacturer")
+)
+
+func init() {
+	RegisterSbomSingleton(InitRegistrationContext)
+}
+
+func RegisterSbomSingleton(ctx RegistrationContext) {
+	ctx.RegisterParallelSingletonType("sbom_singleton", sbomSingletonFactory)
+}
+
+// sbomSingleton is used to generate build actions of generating SBOM of products.
+type sbomSingleton struct{}
+
+func sbomSingletonFactory() Singleton {
+	return &sbomSingleton{}
+}
+
+// Generates SBOM of products
+func (this *sbomSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	// Get all METADATA files and add them as implicit input
+	metadataFileListFile := PathForArbitraryOutput(ctx, ".module_paths", "METADATA.list")
+	f, err := ctx.Config().fs.Open(metadataFileListFile.String())
+	if err != nil {
+		panic(err)
+	}
+	b, err := io.ReadAll(f)
+	if err != nil {
+		panic(err)
+	}
+	allMetadataFiles := strings.Split(string(b), "\n")
+	implicits := []Path{metadataFileListFile}
+	for _, path := range allMetadataFiles {
+		implicits = append(implicits, PathForSource(ctx, path))
+	}
+	prodVars := ctx.Config().productVariables
+	buildFingerprintFile := PathForArbitraryOutput(ctx, "target", "product", String(prodVars.DeviceName), "build_fingerprint.txt")
+	implicits = append(implicits, buildFingerprintFile)
+
+	// Add installed_files.stamp as implicit input, which depends on all installed files of the product.
+	installedFilesStamp := PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "installed_files.stamp")
+	implicits = append(implicits, installedFilesStamp)
+
+	metadataDb := PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "compliance-metadata.db")
+	sbomFile := PathForOutput(ctx, "sbom", ctx.Config().DeviceProduct(), "sbom.spdx.json")
+	ctx.Build(pctx, BuildParams{
+		Rule:      genSbomRule,
+		Input:     metadataDb,
+		Implicits: implicits,
+		Output:    sbomFile,
+		Args: map[string]string{
+			"productOut":           filepath.Join(ctx.Config().OutDir(), "target", "product", String(prodVars.DeviceName)),
+			"soongOut":             ctx.Config().soongOutDir,
+			"buildFingerprintFile": buildFingerprintFile.String(),
+			"productManufacturer":  ctx.Config().ProductVariables().ProductManufacturer,
+		},
+	})
+
+	// Phony rule "soong-sbom". "m soong-sbom" to generate product SBOM in Soong.
+	ctx.Build(pctx, BuildParams{
+		Rule:   blueprint.Phony,
+		Inputs: []Path{sbomFile},
+		Output: PathForPhony(ctx, "soong-sbom"),
+	})
+}
diff --git a/android/variable.go b/android/variable.go
index 3b02bc7..df0e59c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -239,8 +239,6 @@
 
 	VendorApiLevel *string `json:",omitempty"`
 
-	RecoverySnapshotVersion *string `json:",omitempty"`
-
 	DeviceSecondaryArch        *string  `json:",omitempty"`
 	DeviceSecondaryArchVariant *string  `json:",omitempty"`
 	DeviceSecondaryCpuVariant  *string  `json:",omitempty"`
@@ -296,6 +294,7 @@
 	HostStaticBinaries           *bool    `json:",omitempty"`
 	Binder32bit                  *bool    `json:",omitempty"`
 	UseGoma                      *bool    `json:",omitempty"`
+	UseABFS                      *bool    `json:",omitempty"`
 	UseRBE                       *bool    `json:",omitempty"`
 	UseRBEJAVAC                  *bool    `json:",omitempty"`
 	UseRBER8                     *bool    `json:",omitempty"`
@@ -373,20 +372,6 @@
 
 	PgoAdditionalProfileDirs []string `json:",omitempty"`
 
-	VndkSnapshotBuildArtifacts *bool `json:",omitempty"`
-
-	DirectedVendorSnapshot bool            `json:",omitempty"`
-	VendorSnapshotModules  map[string]bool `json:",omitempty"`
-
-	DirectedRecoverySnapshot bool            `json:",omitempty"`
-	RecoverySnapshotModules  map[string]bool `json:",omitempty"`
-
-	VendorSnapshotDirsIncluded   []string `json:",omitempty"`
-	VendorSnapshotDirsExcluded   []string `json:",omitempty"`
-	RecoverySnapshotDirsExcluded []string `json:",omitempty"`
-	RecoverySnapshotDirsIncluded []string `json:",omitempty"`
-	HostFakeSnapshotEnabled      bool     `json:",omitempty"`
-
 	MultitreeUpdateMeta bool `json:",omitempty"`
 
 	BoardVendorSepolicyDirs      []string `json:",omitempty"`
@@ -520,6 +505,8 @@
 	BoardUseVbmetaDigestInFingerprint *bool `json:",omitempty"`
 
 	OemProperties []string `json:",omitempty"`
+
+	ArtTargetIncludeDebugBuild *bool `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
index db3313d..fb03c23 100644
--- a/androidmk/parser/parser_test.go
+++ b/androidmk/parser/parser_test.go
@@ -86,20 +86,19 @@
 	},
 	{
 		name: "Blank line in rule's command",
-		in:   `all:
+		in: `all:
 	echo first line
 
 	echo second line`,
 		out: []Node{
 			&Rule{
-				Target: SimpleMakeString("all", NoPos),
-				RecipePos: NoPos,
-				Recipe: "echo first line\necho second line",
+				Target:        SimpleMakeString("all", NoPos),
+				RecipePos:     NoPos,
+				Recipe:        "echo first line\necho second line",
 				Prerequisites: SimpleMakeString("", NoPos),
 			},
 		},
 	},
-
 }
 
 func TestParse(t *testing.T) {
diff --git a/apex/apex.go b/apex/apex.go
index f9b30d4..fc0500a 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -86,7 +86,7 @@
 
 	// AndroidManifest.xml file used for the zip container of this APEX bundle. If unspecified,
 	// a default one is automatically generated.
-	AndroidManifest *string `android:"path"`
+	AndroidManifest proptools.Configurable[string] `android:"path,replace_instead_of_append"`
 
 	// Determines the file contexts file for setting the security contexts to files in this APEX
 	// bundle. For platform APEXes, this should points to a file under /system/sepolicy Default:
@@ -104,7 +104,7 @@
 	// path_or_glob is a path or glob pattern for a file or set of files,
 	// uid/gid are numerial values of user ID and group ID, mode is octal value
 	// for the file mode, and cap is hexadecimal value for the capability.
-	Canned_fs_config *string `android:"path"`
+	Canned_fs_config proptools.Configurable[string] `android:"path,replace_instead_of_append"`
 
 	ApexNativeDependencies
 
@@ -117,7 +117,8 @@
 	Bootclasspath_fragments []string
 
 	// List of systemserverclasspath fragments that are embedded inside this APEX bundle.
-	Systemserverclasspath_fragments []string
+	Systemserverclasspath_fragments        proptools.Configurable[[]string]
+	ResolvedSystemserverclasspathFragments []string `blueprint:"mutated"`
 
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
@@ -221,7 +222,8 @@
 	Rust_dyn_libs []string
 
 	// List of native executables that are embedded inside this APEX.
-	Binaries []string
+	Binaries         proptools.Configurable[[]string]
+	ResolvedBinaries []string `blueprint:"mutated"`
 
 	// List of native tests that are embedded inside this APEX.
 	Tests []string
@@ -230,7 +232,8 @@
 	Filesystems []string
 
 	// List of prebuilt_etcs that are embedded inside this APEX bundle.
-	Prebuilts []string
+	Prebuilts         proptools.Configurable[[]string]
+	ResolvedPrebuilts []string `blueprint:"mutated"`
 
 	// List of native libraries to exclude from this APEX.
 	Exclude_native_shared_libs []string
@@ -255,14 +258,14 @@
 }
 
 // Merge combines another ApexNativeDependencies into this one
-func (a *ApexNativeDependencies) Merge(b ApexNativeDependencies) {
+func (a *ApexNativeDependencies) Merge(ctx android.BaseMutatorContext, b ApexNativeDependencies) {
 	a.Native_shared_libs = append(a.Native_shared_libs, b.Native_shared_libs...)
 	a.Jni_libs = append(a.Jni_libs, b.Jni_libs...)
 	a.Rust_dyn_libs = append(a.Rust_dyn_libs, b.Rust_dyn_libs...)
-	a.Binaries = append(a.Binaries, b.Binaries...)
+	a.ResolvedBinaries = append(a.ResolvedBinaries, b.Binaries.GetOrDefault(ctx, nil)...)
 	a.Tests = append(a.Tests, b.Tests...)
 	a.Filesystems = append(a.Filesystems, b.Filesystems...)
-	a.Prebuilts = append(a.Prebuilts, b.Prebuilts...)
+	a.ResolvedPrebuilts = append(a.ResolvedPrebuilts, b.Prebuilts.GetOrDefault(ctx, nil)...)
 
 	a.Exclude_native_shared_libs = append(a.Exclude_native_shared_libs, b.Exclude_native_shared_libs...)
 	a.Exclude_jni_libs = append(a.Exclude_jni_libs, b.Exclude_jni_libs...)
@@ -338,10 +341,10 @@
 // base apex.
 type overridableProperties struct {
 	// List of APKs that are embedded inside this APEX.
-	Apps []string
+	Apps proptools.Configurable[[]string]
 
 	// List of prebuilt files that are embedded inside this APEX bundle.
-	Prebuilts []string
+	Prebuilts proptools.Configurable[[]string]
 
 	// List of BPF programs inside this APEX bundle.
 	Bpfs []string
@@ -703,7 +706,6 @@
 	rustLibVariations := append(
 		target.Variations(), []blueprint.Variation{
 			{Mutator: "rust_libraries", Variation: "dylib"},
-			{Mutator: "link", Variation: ""},
 		}...,
 	)
 
@@ -716,7 +718,7 @@
 	// this module. This is required since arch variant of an APEX bundle is 'common' but it is
 	// 'arm' or 'arm64' for native shared libs.
 	ctx.AddFarVariationDependencies(binVariations, executableTag,
-		android.RemoveListFromList(nativeModules.Binaries, nativeModules.Exclude_binaries)...)
+		android.RemoveListFromList(nativeModules.ResolvedBinaries, nativeModules.Exclude_binaries)...)
 	ctx.AddFarVariationDependencies(binVariations, testTag,
 		android.RemoveListFromList(nativeModules.Tests, nativeModules.Exclude_tests)...)
 	ctx.AddFarVariationDependencies(libVariations, jniLibTag,
@@ -728,7 +730,7 @@
 	ctx.AddFarVariationDependencies(target.Variations(), fsTag,
 		android.RemoveListFromList(nativeModules.Filesystems, nativeModules.Exclude_filesystems)...)
 	ctx.AddFarVariationDependencies(target.Variations(), prebuiltTag,
-		android.RemoveListFromList(nativeModules.Prebuilts, nativeModules.Exclude_prebuilts)...)
+		android.RemoveListFromList(nativeModules.ResolvedPrebuilts, nativeModules.Exclude_prebuilts)...)
 }
 
 func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@@ -783,20 +785,19 @@
 
 		// Add native modules targeting both ABIs. When multilib.* is omitted for
 		// native_shared_libs/jni_libs/tests, it implies multilib.both
-		deps.Merge(a.properties.Multilib.Both)
-		deps.Merge(ApexNativeDependencies{
+		deps.Merge(ctx, a.properties.Multilib.Both)
+		deps.Merge(ctx, ApexNativeDependencies{
 			Native_shared_libs: a.properties.Native_shared_libs,
 			Tests:              a.properties.Tests,
 			Jni_libs:           a.properties.Jni_libs,
-			Binaries:           nil,
 		})
 
 		// Add native modules targeting the first ABI When multilib.* is omitted for
 		// binaries, it implies multilib.first
 		isPrimaryAbi := i == 0
 		if isPrimaryAbi {
-			deps.Merge(a.properties.Multilib.First)
-			deps.Merge(ApexNativeDependencies{
+			deps.Merge(ctx, a.properties.Multilib.First)
+			deps.Merge(ctx, ApexNativeDependencies{
 				Native_shared_libs: nil,
 				Tests:              nil,
 				Jni_libs:           nil,
@@ -807,27 +808,27 @@
 		// Add native modules targeting either 32-bit or 64-bit ABI
 		switch target.Arch.ArchType.Multilib {
 		case "lib32":
-			deps.Merge(a.properties.Multilib.Lib32)
-			deps.Merge(a.properties.Multilib.Prefer32)
+			deps.Merge(ctx, a.properties.Multilib.Lib32)
+			deps.Merge(ctx, a.properties.Multilib.Prefer32)
 		case "lib64":
-			deps.Merge(a.properties.Multilib.Lib64)
+			deps.Merge(ctx, a.properties.Multilib.Lib64)
 			if !has32BitTarget {
-				deps.Merge(a.properties.Multilib.Prefer32)
+				deps.Merge(ctx, a.properties.Multilib.Prefer32)
 			}
 		}
 
 		// Add native modules targeting a specific arch variant
 		switch target.Arch.ArchType {
 		case android.Arm:
-			deps.Merge(a.archProperties.Arch.Arm.ApexNativeDependencies)
+			deps.Merge(ctx, a.archProperties.Arch.Arm.ApexNativeDependencies)
 		case android.Arm64:
-			deps.Merge(a.archProperties.Arch.Arm64.ApexNativeDependencies)
+			deps.Merge(ctx, a.archProperties.Arch.Arm64.ApexNativeDependencies)
 		case android.Riscv64:
-			deps.Merge(a.archProperties.Arch.Riscv64.ApexNativeDependencies)
+			deps.Merge(ctx, a.archProperties.Arch.Riscv64.ApexNativeDependencies)
 		case android.X86:
-			deps.Merge(a.archProperties.Arch.X86.ApexNativeDependencies)
+			deps.Merge(ctx, a.archProperties.Arch.X86.ApexNativeDependencies)
 		case android.X86_64:
-			deps.Merge(a.archProperties.Arch.X86_64.ApexNativeDependencies)
+			deps.Merge(ctx, a.archProperties.Arch.X86_64.ApexNativeDependencies)
 		default:
 			panic(fmt.Errorf("unsupported arch %v\n", ctx.Arch().ArchType))
 		}
@@ -841,11 +842,13 @@
 		}
 	}
 
+	a.properties.ResolvedSystemserverclasspathFragments = a.properties.Systemserverclasspath_fragments.GetOrDefault(ctx, nil)
+
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.properties.Rros...)
 	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments...)
-	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.Systemserverclasspath_fragments...)
+	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.ResolvedSystemserverclasspathFragments...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
@@ -858,9 +861,9 @@
 	}
 
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
-	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps...)
+	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps.GetOrDefault(ctx, nil)...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.overridableProperties.Bpfs...)
-	if prebuilts := a.overridableProperties.Prebuilts; len(prebuilts) > 0 {
+	if prebuilts := a.overridableProperties.Prebuilts.GetOrDefault(ctx, nil); len(prebuilts) > 0 {
 		// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
 		// regardless of the TARGET_PREFER_* setting. See b/144532908
 		arches := ctx.DeviceConfig().Arches()
@@ -1493,7 +1496,6 @@
 					Native_shared_libs: []string{"libclang_rt.hwasan"},
 					Tests:              nil,
 					Jni_libs:           nil,
-					Binaries:           nil,
 				}, target, imageVariation)
 				break
 			}
@@ -2663,18 +2665,13 @@
 	})
 }
 
-// TODO (b/221087384): Remove this allowlist
-var (
-	updatableApexesWithCurrentMinSdkVersionAllowlist = []string{"com.android.profiling"}
-)
-
 // checkUpdatable enforces APEX and its transitive dep properties to have desired values for updatable APEXes.
 func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
 	if a.Updatable() {
 		if a.minSdkVersionValue(ctx) == "" {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
 		}
-		if a.minSdkVersion(ctx).IsCurrent() && !android.InList(ctx.ModuleName(), updatableApexesWithCurrentMinSdkVersionAllowlist) {
+		if a.minSdkVersion(ctx).IsCurrent() {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should not set min_sdk_version to current. Please use a finalized API level or a recognized in-development codename")
 		}
 		if a.UsePlatformApis() {
@@ -2812,7 +2809,7 @@
 func (a *apexBundle) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Deps = append(dpInfo.Deps, a.properties.Java_libs...)
 	dpInfo.Deps = append(dpInfo.Deps, a.properties.Bootclasspath_fragments...)
-	dpInfo.Deps = append(dpInfo.Deps, a.properties.Systemserverclasspath_fragments...)
+	dpInfo.Deps = append(dpInfo.Deps, a.properties.ResolvedSystemserverclasspathFragments...)
 }
 
 var (
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a2dbbfc..261d2ce 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -5244,7 +5244,7 @@
 		myApex := ctx.ModuleForTests("myapex", "android_common_myapex").Module()
 
 		overrideNames := []string{
-			"myapex",
+			"",
 			"myjavalib.myapex",
 			"libfoo.myapex",
 			"libbar.myapex",
@@ -11294,13 +11294,6 @@
 // Test that product packaging installs the selected mainline module (either source or a specific prebuilt)
 // RELEASE_APEX_CONTIRBUTIONS_* build flags will be used to select the correct prebuilt for a specific release config
 func TestInstallationRulesForMultipleApexPrebuilts(t *testing.T) {
-	// check that the LOCAL_MODULE in the generated mk file matches the name used in PRODUCT_PACKAGES
-	// Since the name used in PRODUCT_PACKAGES does not contain prebuilt_ prefix, LOCAL_MODULE should not contain any prefix either
-	checkLocalModuleName := func(t *testing.T, ctx *android.TestContext, soongApexModuleName string, expectedLocalModuleName string) {
-		// Variations are created based on apex_name
-		entries := android.AndroidMkEntriesForTest(t, ctx, ctx.ModuleForTests(soongApexModuleName, "android_common_com.android.foo").Module())
-		android.AssertStringEquals(t, "LOCAL_MODULE of the prebuilt apex must match the name listed in PRODUCT_PACKAGES", expectedLocalModuleName, entries[0].EntryMap["LOCAL_MODULE"][0])
-	}
 	// for a mainline module family, check that only the flagged soong module is visible to make
 	checkHideFromMake := func(t *testing.T, ctx *android.TestContext, visibleModuleName string, hiddenModuleNames []string) {
 		variation := func(moduleName string) string {
@@ -11355,7 +11348,7 @@
 		prebuilt_apex {
 			name: "com.google.android.foo.v2",
 			apex_name: "com.android.foo",
-			source_apex_name: "com.google.android.foo", // source_apex_name becomes LOCAL_MODULE in the generated mk file
+			source_apex_name: "com.google.android.foo",
 			src: "com.android.foo-arm.apex",
 			prefer: true, // prefer is set to true on both the prebuilts to induce an error if flagging is not present
 		}
@@ -11441,11 +11434,6 @@
 		}
 		ctx := testApex(t, bp, preparer)
 
-		// Check that the LOCAL_MODULE of the two prebuilts is com.android.foo
-		// This ensures that product packaging can pick them for installation if it has been flagged by apex_contributions
-		checkLocalModuleName(t, ctx, "prebuilt_com.google.android.foo", "com.google.android.foo")
-		checkLocalModuleName(t, ctx, "prebuilt_com.google.android.foo.v2", "com.google.android.foo")
-
 		// Check that
 		// 1. The contents of the selected apex_contributions are visible to make
 		// 2. The rest of the apexes in the mainline module family (source or other prebuilt) is hidden from make
@@ -11738,3 +11726,121 @@
 		}
 	`)
 }
+
+func TestPrebuiltStubNoinstall(t *testing.T) {
+	testFunc := func(t *testing.T, expectLibfooOnSystemLib bool, fs android.MockFS) {
+		result := android.GroupFixturePreparers(
+			prepareForApexTest,
+			android.PrepareForTestWithAndroidMk,
+			android.PrepareForTestWithMakevars,
+			android.FixtureMergeMockFs(fs),
+		).RunTest(t)
+
+		ldRule := result.ModuleForTests("installedlib", "android_arm64_armv8-a_shared").Rule("ld")
+		android.AssertStringDoesContain(t, "", ldRule.Args["libFlags"], "android_arm64_armv8-a_shared/libfoo.so")
+
+		installRules := result.InstallMakeRulesForTesting(t)
+
+		var installedlibRule *android.InstallMakeRule
+		for i, rule := range installRules {
+			if rule.Target == "out/target/product/test_device/system/lib/installedlib.so" {
+				if installedlibRule != nil {
+					t.Errorf("Duplicate install rules for %s", rule.Target)
+				}
+				installedlibRule = &installRules[i]
+			}
+		}
+		if installedlibRule == nil {
+			t.Errorf("No install rule found for installedlib")
+			return
+		}
+
+		if expectLibfooOnSystemLib {
+			android.AssertStringListContains(t,
+				"installedlib doesn't have install dependency on libfoo impl",
+				installedlibRule.OrderOnlyDeps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+		} else {
+			android.AssertStringListDoesNotContain(t,
+				"installedlib has install dependency on libfoo stub",
+				installedlibRule.Deps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+			android.AssertStringListDoesNotContain(t,
+				"installedlib has order-only install dependency on libfoo stub",
+				installedlibRule.OrderOnlyDeps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+		}
+	}
+
+	prebuiltLibfooBp := []byte(`
+		cc_prebuilt_library {
+			name: "libfoo",
+			prefer: true,
+			srcs: ["libfoo.so"],
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["apexfoo"],
+		}
+	`)
+
+	apexfooBp := []byte(`
+		apex {
+			name: "apexfoo",
+			key: "apexfoo.key",
+			native_shared_libs: ["libfoo"],
+			updatable: false,
+			compile_multilib: "both",
+		}
+		apex_key {
+			name: "apexfoo.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	installedlibBp := []byte(`
+		cc_library {
+			name: "installedlib",
+			shared_libs: ["libfoo"],
+		}
+	`)
+
+	t.Run("prebuilt stub (without source): no install", func(t *testing.T) {
+		testFunc(
+			t,
+			/*expectLibfooOnSystemLib=*/ false,
+			android.MockFS{
+				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
+				"apexfoo/Android.bp":                          apexfooBp,
+				"system/sepolicy/apex/apexfoo-file_contexts":  nil,
+				"Android.bp": installedlibBp,
+			},
+		)
+	})
+
+	disabledSourceLibfooBp := []byte(`
+		cc_library {
+			name: "libfoo",
+			enabled: false,
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["apexfoo"],
+		}
+	`)
+
+	t.Run("prebuilt stub (with disabled source): no install", func(t *testing.T) {
+		testFunc(
+			t,
+			/*expectLibfooOnSystemLib=*/ false,
+			android.MockFS{
+				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
+				"impl/Android.bp":                            disabledSourceLibfooBp,
+				"apexfoo/Android.bp":                         apexfooBp,
+				"system/sepolicy/apex/apexfoo-file_contexts": nil,
+				"Android.bp":                                 installedlibBp,
+			},
+		)
+	})
+}
diff --git a/apex/builder.go b/apex/builder.go
index 763ce4d..bfe1692 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -704,8 +704,9 @@
 		optFlags = append(optFlags, "--override_apk_package_name "+manifestPackageName)
 	}
 
-	if a.properties.AndroidManifest != nil {
-		androidManifestFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.AndroidManifest))
+	androidManifest := a.properties.AndroidManifest.GetOrDefault(ctx, "")
+	if androidManifest != "" {
+		androidManifestFile := android.PathForModuleSrc(ctx, androidManifest)
 
 		if a.testApex {
 			androidManifestFile = markManifestTestOnly(ctx, androidManifestFile)
@@ -1195,8 +1196,9 @@
 	}
 	// Custom fs_config is "appended" to the last so that entries from the file are preferred
 	// over default ones set above.
-	if a.properties.Canned_fs_config != nil {
-		cmd.Text("cat").Input(android.PathForModuleSrc(ctx, *a.properties.Canned_fs_config))
+	customFsConfig := a.properties.Canned_fs_config.GetOrDefault(ctx, "")
+	if customFsConfig != "" {
+		cmd.Text("cat").Input(android.PathForModuleSrc(ctx, customFsConfig))
 	}
 	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
 	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 65c23d3..b9cc09b 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -246,7 +246,6 @@
 			OutputFile:    android.OptionalPathForPath(p.outputApex),
 			Include:       "$(BUILD_PREBUILT)",
 			Host_required: p.hostRequired,
-			OverrideName:  p.BaseModuleName(),
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
diff --git a/bin/afind b/bin/afind
index 080f06a..f5b8319 100755
--- a/bin/afind
+++ b/bin/afind
@@ -14,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# prevent glob expansion in this script
+set -f
+
 dir=${1:-.}
 
 shift
diff --git a/bin/dirmods b/bin/dirmods
index 52d935a..a6d4de3 100755
--- a/bin/dirmods
+++ b/bin/dirmods
@@ -35,6 +35,14 @@
     args = parser.parse_args()
 
     d = os.path.normpath(args.path)
+    # Fix absolute path to be relative to build top
+    if os.path.isabs(d):
+        base = os.environ.get('ANDROID_BUILD_TOP')
+        if base:
+            base = os.path.normpath(base) + os.path.sep
+            if d.startswith(base):
+                d = d[len(base):]
+
     prefix = d + '/'
 
     module_info = modinfo.ReadModuleInfo()
diff --git a/bin/installmod b/bin/installmod
index 1d0d836..1ad5b84 100755
--- a/bin/installmod
+++ b/bin/installmod
@@ -28,7 +28,6 @@
     return 1
 fi
 
-local _path
 _path=$(outmod ${@:$#:1})
 if [ $? -ne 0 ]; then
     return 1
@@ -39,7 +38,7 @@
     echo "Module '$1' does not produce a file ending with .apk (try 'refreshmod' if there have been build changes?)" >&2
     return 1
 fi
-local serial_device=""
+serial_device=""
 if [[ "$1" == "-s" ]]; then
     if [[ $# -le 2 ]]; then
         echo "-s requires an argument" >&2
@@ -48,7 +47,7 @@
     serial_device="-s $2"
     shift 2
 fi
-local length=$(( $# - 1 ))
+length=$(( $# - 1 ))
 echo adb $serial_device install ${@:1:$length} $_path
 adb $serial_device install ${@:1:$length} $_path
 
diff --git a/cc/Android.bp b/cc/Android.bp
index 3bbcaa9..e68e4a3 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -73,7 +73,6 @@
         "ndk_abi.go",
         "ndk_headers.go",
         "ndk_library.go",
-        "ndk_prebuilt.go",
         "ndk_sysroot.go",
 
         "llndk_library.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 4134653..cecaae2 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -451,10 +451,6 @@
 	})
 }
 
-func (c *ndkPrebuiltStlLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	entries.Class = "SHARED_LIBRARIES"
-}
-
 func (p *prebuiltLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 		if p.properties.Check_elf_files != nil {
diff --git a/cc/cc.go b/cc/cc.go
index d307be6..3c276d2 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -48,10 +48,10 @@
 	ctx.RegisterModuleType("cc_defaults", defaultsFactory)
 
 	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sdk", sdkMutator).Parallel()
+		ctx.Transition("sdk", &sdkTransitionMutator{})
 		ctx.BottomUp("llndk", llndkMutator).Parallel()
-		ctx.BottomUp("link", LinkageMutator).Parallel()
-		ctx.BottomUp("version", versionMutator).Parallel()
+		ctx.Transition("link", &linkageTransitionMutator{})
+		ctx.Transition("version", &versionTransitionMutator{})
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 	})
 
@@ -1028,13 +1028,6 @@
 	return ""
 }
 
-func (c *Module) NdkPrebuiltStl() bool {
-	if _, ok := c.linker.(*ndkPrebuiltStlLinker); ok {
-		return true
-	}
-	return false
-}
-
 func (c *Module) StubDecorator() bool {
 	if _, ok := c.linker.(*stubDecorator); ok {
 		return true
@@ -1088,16 +1081,6 @@
 	return false
 }
 
-func (c *Module) IsNdkPrebuiltStl() bool {
-	if c.linker == nil {
-		return false
-	}
-	if _, ok := c.linker.(*ndkPrebuiltStlLinker); ok {
-		return true
-	}
-	return false
-}
-
 func (c *Module) RlibStd() bool {
 	panic(fmt.Errorf("RlibStd called on non-Rust module: %q", c.BaseModuleName()))
 }
@@ -2754,10 +2737,6 @@
 		return
 	}
 	if c, ok := to.(*Module); ok {
-		if c.NdkPrebuiltStl() {
-			// These are allowed, but they don't set sdk_version
-			return
-		}
 		if c.StubDecorator() {
 			// These aren't real libraries, but are the stub shared libraries that are included in
 			// the NDK.
@@ -3927,7 +3906,6 @@
 	headerLibrary
 	testBin // testBinary already declared
 	ndkLibrary
-	ndkPrebuiltStl
 )
 
 func (c *Module) typ() moduleType {
@@ -3966,8 +3944,6 @@
 		return sharedLibrary
 	} else if c.isNDKStubLibrary() {
 		return ndkLibrary
-	} else if c.IsNdkPrebuiltStl() {
-		return ndkPrebuiltStl
 	}
 	return unknownType
 }
@@ -4079,6 +4055,13 @@
 	return c.ModuleBase.BaseModuleName()
 }
 
+func (c *Module) stubsSymbolFilePath() android.Path {
+	if library, ok := c.linker.(*libraryDecorator); ok {
+		return library.stubsSymbolFilePath
+	}
+	return android.OptionalPath{}.Path()
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var BoolPtr = proptools.BoolPtr
diff --git a/cc/cc_test.go b/cc/cc_test.go
index ccdaae5..b1c0945 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2760,7 +2760,7 @@
 		"external/foo/libarm",
 		"external/foo/lib32",
 		"external/foo/libandroid_arm",
-		"defaults/cc/common/ndk_libc++_shared",
+		"defaults/cc/common/ndk_libc++_shared_include_dirs",
 	}
 
 	conly := []string{"-fPIC", "${config.CommonGlobalConlyflags}"}
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index d30abba..469fe31 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -85,9 +85,8 @@
 	moduleDeps := ccDeps{}
 	moduleInfos := map[string]ccIdeInfo{}
 
-	// Track which projects have already had CMakeLists.txt generated to keep the first
-	// variant for each project.
-	seenProjects := map[string]bool{}
+	// Track if best variant (device arch match) has been found.
+	bestVariantFound := map[string]bool{}
 
 	pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
 	moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang)
@@ -96,7 +95,7 @@
 	ctx.VisitAllModules(func(module android.Module) {
 		if ccModule, ok := module.(*Module); ok {
 			if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
-				generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos)
+				generateCLionProjectData(ctx, compiledModule, ccModule, bestVariantFound, moduleInfos)
 			}
 		}
 	})
@@ -180,26 +179,30 @@
 }
 
 func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface,
-	ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) {
+	ccModule *Module, bestVariantFound map[string]bool, moduleInfos map[string]ccIdeInfo) {
+	moduleName := ccModule.ModuleBase.Name()
 	srcs := compiledModule.Srcs()
+
+	// Skip if best variant has already been found.
+	if bestVariantFound[moduleName] {
+		return
+	}
+
+	// Skip if sources are empty.
 	if len(srcs) == 0 {
 		return
 	}
 
-	// Only keep the DeviceArch variant module.
-	if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name {
+	// Check if device arch matches, in which case this is the best variant and takes precedence.
+	if ccModule.Device() && ccModule.ModuleBase.Arch().ArchType.Name == ctx.DeviceConfig().DeviceArch() {
+		bestVariantFound[moduleName] = true
+	} else if _, ok := moduleInfos[moduleName]; ok {
+		// Skip because this isn't the best variant and a previous one has already been added.
+		// Heuristically, ones that appear first are likely to be more relevant.
 		return
 	}
 
-	clionProjectLocation := getCMakeListsForModule(ccModule, ctx)
-	if seenProjects[clionProjectLocation] {
-		return
-	}
-
-	seenProjects[clionProjectLocation] = true
-
-	name := ccModule.ModuleBase.Name()
-	dpInfo := moduleInfos[name]
+	dpInfo := ccIdeInfo{}
 
 	dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule)))
 	dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...)
@@ -216,9 +219,9 @@
 	dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags)
 	dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags)
 
-	dpInfo.Module_name = name
+	dpInfo.Module_name = moduleName
 
-	moduleInfos[name] = dpInfo
+	moduleInfos[moduleName] = dpInfo
 }
 
 type Deal struct {
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index fb2924a..8f3ad96 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -347,8 +347,11 @@
 		if slices.Contains(ignoredSystemLibs, moduleName) {
 			return false // system libs built in-tree for Android
 		}
+		if dep.IsPrebuilt() {
+			return false // prebuilts are not supported
+		}
 		if dep.compiler == nil {
-			return false // unsupported module type (e.g. prebuilt)
+			return false // unsupported module type
 		}
 		isAidlModule := dep.compiler.baseCompilerProps().AidlInterface.Lang != ""
 
diff --git a/cc/compiler.go b/cc/compiler.go
index 03f9899..ed10533 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -53,7 +53,7 @@
 	Cflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of module-specific flags that will be used for C++ compiles
-	Cppflags []string `android:"arch_variant"`
+	Cppflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of module-specific flags that will be used for C compiles
 	Conlyflags []string `android:"arch_variant"`
@@ -367,8 +367,9 @@
 	compiler.srcsBeforeGen = append(compiler.srcsBeforeGen, deps.GeneratedSources...)
 
 	cflags := compiler.Properties.Cflags.GetOrDefault(ctx, nil)
+	cppflags := compiler.Properties.Cppflags.GetOrDefault(ctx, nil)
 	CheckBadCompilerFlags(ctx, "cflags", cflags)
-	CheckBadCompilerFlags(ctx, "cppflags", compiler.Properties.Cppflags)
+	CheckBadCompilerFlags(ctx, "cppflags", cppflags)
 	CheckBadCompilerFlags(ctx, "conlyflags", compiler.Properties.Conlyflags)
 	CheckBadCompilerFlags(ctx, "asflags", compiler.Properties.Asflags)
 	CheckBadCompilerFlags(ctx, "vendor.cflags", compiler.Properties.Target.Vendor.Cflags)
@@ -381,7 +382,7 @@
 	esc := proptools.NinjaAndShellEscapeList
 
 	flags.Local.CFlags = append(flags.Local.CFlags, esc(cflags)...)
-	flags.Local.CppFlags = append(flags.Local.CppFlags, esc(compiler.Properties.Cppflags)...)
+	flags.Local.CppFlags = append(flags.Local.CppFlags, esc(cppflags)...)
 	flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, esc(compiler.Properties.Conlyflags)...)
 	flags.Local.AsFlags = append(flags.Local.AsFlags, esc(compiler.Properties.Asflags)...)
 	flags.Local.YasmFlags = append(flags.Local.YasmFlags, esc(compiler.Properties.Asflags)...)
@@ -808,7 +809,7 @@
 
 	// list of c++ specific clang flags required to correctly interpret the headers.
 	// This is provided primarily to make sure cppflags defined in cc_defaults are pulled in.
-	Cppflags []string `android:"arch_variant"`
+	Cppflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// C standard version to use. Can be a specific version (such as "gnu11"),
 	// "experimental" (which will use draft versions like C1x when available),
diff --git a/cc/library.go b/cc/library.go
index 560b1ae..092b177 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -19,6 +19,7 @@
 	"io"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -63,7 +64,7 @@
 	Stubs struct {
 		// Relative path to the symbol map. The symbol map provides the list of
 		// symbols that are exported for stubs variant of this library.
-		Symbol_file *string `android:"path"`
+		Symbol_file *string `android:"path,arch_variant"`
 
 		// List versions to generate stubs libs for. The version name "current" is always
 		// implicitly added.
@@ -74,7 +75,7 @@
 		// implementation is made available by some other means, e.g. in a Microdroid
 		// virtual machine.
 		Implementation_installable *bool
-	}
+	} `android:"arch_variant"`
 
 	// set the name of the output
 	Stem *string `android:"arch_variant"`
@@ -117,7 +118,7 @@
 
 	// If this is an LLNDK library, properties to describe the LLNDK stubs.  Will be copied from
 	// the module pointed to by llndk_stubs if it is set.
-	Llndk llndkLibraryProperties
+	Llndk llndkLibraryProperties `android:"arch_variant"`
 
 	// If this is a vendor public library, properties to describe the vendor public library stubs.
 	Vendor_public_library vendorPublicLibraryProperties
@@ -427,6 +428,9 @@
 	*baseInstaller
 
 	apiListCoverageXmlPath android.ModuleOutPath
+
+	// Path to the file containing the APIs exported by this library
+	stubsSymbolFilePath android.Path
 }
 
 // linkerProps returns the list of properties structs relevant for this library. (For example, if
@@ -595,6 +599,7 @@
 			ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile)
 			return Objects{}
 		}
+		library.stubsSymbolFilePath = android.PathForModuleSrc(ctx, symbolFile)
 		// b/239274367 --apex and --systemapi filters symbols tagged with # apex and #
 		// systemapi, respectively. The former is for symbols defined in platform libraries
 		// and the latter is for symbols defined in APEXes.
@@ -711,7 +716,7 @@
 	setStubsVersion(string)
 	stubsVersion() string
 
-	stubsVersions(ctx android.BaseMutatorContext) []string
+	stubsVersions(ctx android.BaseModuleContext) []string
 	setAllStubsVersions([]string)
 	allStubsVersions() []string
 
@@ -1903,7 +1908,7 @@
 	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
 }
 
-func (library *libraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (library *libraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
 	if !library.hasStubsVariants() {
 		return nil
 	}
@@ -2064,26 +2069,26 @@
 
 // connects a shared library to a static library in order to reuse its .o files to avoid
 // compiling source files twice.
-func reuseStaticLibrary(mctx android.BottomUpMutatorContext, static, shared *Module) {
-	if staticCompiler, ok := static.compiler.(*libraryDecorator); ok {
-		sharedCompiler := shared.compiler.(*libraryDecorator)
+func reuseStaticLibrary(ctx android.BottomUpMutatorContext, shared *Module) {
+	if sharedCompiler, ok := shared.compiler.(*libraryDecorator); ok {
 
 		// Check libraries in addition to cflags, since libraries may be exporting different
 		// include directories.
-		if len(staticCompiler.StaticProperties.Static.Cflags.GetOrDefault(mctx, nil)) == 0 &&
-			len(sharedCompiler.SharedProperties.Shared.Cflags.GetOrDefault(mctx, nil)) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
+		if len(sharedCompiler.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+			len(sharedCompiler.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Whole_static_libs) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Static_libs) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Static_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Static_libs) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Shared_libs) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Shared_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Shared_libs) == 0 &&
 			// Compare System_shared_libs properties with nil because empty lists are
 			// semantically significant for them.
-			staticCompiler.StaticProperties.Static.System_shared_libs == nil &&
+			sharedCompiler.StaticProperties.Static.System_shared_libs == nil &&
 			sharedCompiler.SharedProperties.Shared.System_shared_libs == nil {
 
-			mctx.AddInterVariantDependency(reuseObjTag, shared, static)
+			// TODO: namespaces?
+			ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, reuseObjTag, ctx.ModuleName())
 			sharedCompiler.baseCompiler.Properties.OriginalSrcs =
 				sharedCompiler.baseCompiler.Properties.Srcs
 			sharedCompiler.baseCompiler.Properties.Srcs = nil
@@ -2091,19 +2096,21 @@
 		}
 
 		// This dep is just to reference static variant from shared variant
-		mctx.AddInterVariantDependency(staticVariantTag, shared, static)
+		ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, staticVariantTag, ctx.ModuleName())
 	}
 }
 
-// LinkageMutator adds "static" or "shared" variants for modules depending
+// linkageTransitionMutator adds "static" or "shared" variants for modules depending
 // on whether the module can be built as a static library or a shared library.
-func LinkageMutator(mctx android.BottomUpMutatorContext) {
+type linkageTransitionMutator struct{}
+
+func (linkageTransitionMutator) Split(ctx android.BaseModuleContext) []string {
 	ccPrebuilt := false
-	if m, ok := mctx.Module().(*Module); ok && m.linker != nil {
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
 		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
 	}
 	if ccPrebuilt {
-		library := mctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
 
 		// Differentiate between header only and building an actual static/shared library
 		buildStatic := library.buildStatic()
@@ -2112,75 +2119,118 @@
 			// Always create both the static and shared variants for prebuilt libraries, and then disable the one
 			// that is not being used.  This allows them to share the name of a cc_library module, which requires that
 			// all the variants of the cc_library also exist on the prebuilt.
-			modules := mctx.CreateLocalVariations("static", "shared")
-			static := modules[0].(*Module)
-			shared := modules[1].(*Module)
-
-			static.linker.(prebuiltLibraryInterface).setStatic()
-			shared.linker.(prebuiltLibraryInterface).setShared()
-
-			if buildShared {
-				mctx.AliasVariation("shared")
-			} else if buildStatic {
-				mctx.AliasVariation("static")
-			}
-
-			if !buildStatic {
-				static.linker.(prebuiltLibraryInterface).disablePrebuilt()
-			}
-			if !buildShared {
-				shared.linker.(prebuiltLibraryInterface).disablePrebuilt()
-			}
+			return []string{"static", "shared"}
 		} else {
 			// Header only
 		}
-
-	} else if library, ok := mctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
 		// Non-cc.Modules may need an empty variant for their mutators.
 		variations := []string{}
 		if library.NonCcVariants() {
 			variations = append(variations, "")
 		}
 		isLLNDK := false
-		if m, ok := mctx.Module().(*Module); ok {
+		if m, ok := ctx.Module().(*Module); ok {
 			isLLNDK = m.IsLlndk()
 		}
 		buildStatic := library.BuildStaticVariant() && !isLLNDK
 		buildShared := library.BuildSharedVariant()
 		if buildStatic && buildShared {
-			variations := append([]string{"static", "shared"}, variations...)
-
-			modules := mctx.CreateLocalVariations(variations...)
-			static := modules[0].(LinkableInterface)
-			shared := modules[1].(LinkableInterface)
-			static.SetStatic()
-			shared.SetShared()
-
-			if _, ok := library.(*Module); ok {
-				reuseStaticLibrary(mctx, static.(*Module), shared.(*Module))
-			}
-			mctx.AliasVariation("shared")
+			variations = append([]string{"static", "shared"}, variations...)
+			return variations
 		} else if buildStatic {
-			variations := append([]string{"static"}, variations...)
-
-			modules := mctx.CreateLocalVariations(variations...)
-			modules[0].(LinkableInterface).SetStatic()
-			mctx.AliasVariation("static")
+			variations = append([]string{"static"}, variations...)
 		} else if buildShared {
-			variations := append([]string{"shared"}, variations...)
-
-			modules := mctx.CreateLocalVariations(variations...)
-			modules[0].(LinkableInterface).SetShared()
-			mctx.AliasVariation("shared")
-		} else if len(variations) > 0 {
-			mctx.CreateLocalVariations(variations...)
-			mctx.AliasVariation(variations[0])
+			variations = append([]string{"shared"}, variations...)
 		}
-		if library.BuildRlibVariant() && library.IsRustFFI() && !buildStatic {
+
+		if len(variations) > 0 {
+			return variations
+		}
+	}
+	return []string{""}
+}
+
+func (linkageTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (linkageTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	ccPrebuilt := false
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+	}
+	if ccPrebuilt {
+		if incomingVariation != "" {
+			return incomingVariation
+		}
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		if library.buildShared() {
+			return "shared"
+		} else if library.buildStatic() {
+			return "static"
+		}
+		return ""
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+		isLLNDK := false
+		if m, ok := ctx.Module().(*Module); ok {
+			isLLNDK = m.IsLlndk()
+		}
+		buildStatic := library.BuildStaticVariant() && !isLLNDK
+		buildShared := library.BuildSharedVariant()
+		if library.BuildRlibVariant() && library.IsRustFFI() && !buildStatic && (incomingVariation == "static" || incomingVariation == "") {
 			// Rust modules do not build static libs, but rlibs are used as if they
 			// were via `static_libs`. Thus we need to alias the BuildRlibVariant
 			// to "static" for Rust FFI libraries.
-			mctx.CreateAliasVariation("static", "")
+			return ""
+		}
+		if incomingVariation != "" {
+			return incomingVariation
+		}
+		if buildShared {
+			return "shared"
+		} else if buildStatic {
+			return "static"
+		}
+		return ""
+	}
+	return ""
+}
+
+func (linkageTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	ccPrebuilt := false
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+	}
+	if ccPrebuilt {
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		if variation == "static" {
+			library.setStatic()
+			if !library.buildStatic() {
+				library.disablePrebuilt()
+			}
+		} else if variation == "shared" {
+			library.setShared()
+			if !library.buildShared() {
+				library.disablePrebuilt()
+			}
+		}
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+		if variation == "static" {
+			library.SetStatic()
+		} else if variation == "shared" {
+			library.SetShared()
+			var isLLNDK bool
+			if m, ok := ctx.Module().(*Module); ok {
+				isLLNDK = m.IsLlndk()
+			}
+			buildStatic := library.BuildStaticVariant() && !isLLNDK
+			buildShared := library.BuildSharedVariant()
+			if buildStatic && buildShared {
+				if _, ok := library.(*Module); ok {
+					reuseStaticLibrary(ctx, library.(*Module))
+				}
+			}
 		}
 	}
 }
@@ -2204,64 +2254,14 @@
 	}
 }
 
-func createVersionVariations(mctx android.BottomUpMutatorContext, versions []string) {
-	// "" is for the non-stubs (implementation) variant for system modules, or the LLNDK variant
-	// for LLNDK modules.
-	variants := append(android.CopyOf(versions), "")
-
-	m := mctx.Module().(*Module)
-	isLLNDK := m.IsLlndk()
-	isVendorPublicLibrary := m.IsVendorPublicLibrary()
-	isImportedApiLibrary := m.isImportedApiLibrary()
-
-	modules := mctx.CreateLocalVariations(variants...)
-	for i, m := range modules {
-
-		if variants[i] != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
-			// A stubs or LLNDK stubs variant.
-			c := m.(*Module)
-			if c.sanitize != nil {
-				c.sanitize.Properties.ForceDisable = true
-			}
-			if c.stl != nil {
-				c.stl.Properties.Stl = StringPtr("none")
-			}
-			c.Properties.PreventInstall = true
-			lib := moduleLibraryInterface(m)
-			isLatest := i == (len(versions) - 1)
-			lib.setBuildStubs(isLatest)
-
-			if variants[i] != "" {
-				// A non-LLNDK stubs module is hidden from make and has a dependency from the
-				// implementation module to the stubs module.
-				c.Properties.HideFromMake = true
-				lib.setStubsVersion(variants[i])
-				mctx.AddInterVariantDependency(stubImplDepTag, modules[len(modules)-1], modules[i])
-			}
-		}
-	}
-	mctx.AliasVariation("")
-	latestVersion := ""
-	if len(versions) > 0 {
-		latestVersion = versions[len(versions)-1]
-	}
-	mctx.CreateAliasVariation("latest", latestVersion)
-}
-
-func createPerApiVersionVariations(mctx android.BottomUpMutatorContext, minSdkVersion string) {
+func perApiVersionVariations(mctx android.BaseModuleContext, minSdkVersion string) []string {
 	from, err := nativeApiLevelFromUser(mctx, minSdkVersion)
 	if err != nil {
 		mctx.PropertyErrorf("min_sdk_version", err.Error())
-		return
+		return []string{""}
 	}
 
-	versionStrs := ndkLibraryVersions(mctx, from)
-	modules := mctx.CreateLocalVariations(versionStrs...)
-
-	for i, module := range modules {
-		module.(*Module).Properties.Sdk_version = StringPtr(versionStrs[i])
-		module.(*Module).Properties.Min_sdk_version = StringPtr(versionStrs[i])
-	}
+	return ndkLibraryVersions(mctx, from)
 }
 
 func canBeOrLinkAgainstVersionVariants(module interface {
@@ -2291,7 +2291,7 @@
 }
 
 // setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
-func setStubsVersions(mctx android.BottomUpMutatorContext, library libraryInterface, module *Module) {
+func setStubsVersions(mctx android.BaseModuleContext, library libraryInterface, module *Module) {
 	if !library.buildShared() || !canBeVersionVariant(module) {
 		return
 	}
@@ -2304,25 +2304,98 @@
 	library.setAllStubsVersions(versions)
 }
 
-// versionMutator splits a module into the mandatory non-stubs variant
+// versionTransitionMutator splits a module into the mandatory non-stubs variant
 // (which is unnamed) and zero or more stubs variants.
-func versionMutator(mctx android.BottomUpMutatorContext) {
-	if mctx.Os() != android.Android {
-		return
+type versionTransitionMutator struct{}
+
+func (versionTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if ctx.Os() != android.Android {
+		return []string{""}
 	}
 
-	m, ok := mctx.Module().(*Module)
-	if library := moduleLibraryInterface(mctx.Module()); library != nil && canBeVersionVariant(m) {
-		setStubsVersions(mctx, library, m)
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		setStubsVersions(ctx, library, m)
 
-		createVersionVariations(mctx, library.allStubsVersions())
-		return
+		return append(slices.Clone(library.allStubsVersions()), "")
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		return perApiVersionVariations(ctx, m.MinSdkVersion())
 	}
 
-	if ok {
-		if m.SplitPerApiLevel() && m.IsSdkVariant() {
-			createPerApiVersionVariations(mctx, m.MinSdkVersion())
+	return []string{""}
+}
+
+func (versionTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (versionTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if ctx.Os() != android.Android {
+		return ""
+	}
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		if incomingVariation == "latest" {
+			latestVersion := ""
+			versions := library.allStubsVersions()
+			if len(versions) > 0 {
+				latestVersion = versions[len(versions)-1]
+			}
+			return latestVersion
 		}
+		return incomingVariation
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		// If this module only has variants with versions and the incoming dependency doesn't specify which one
+		// is needed then assume the latest version.
+		if incomingVariation == "" {
+			return android.FutureApiLevel.String()
+		}
+		return incomingVariation
+	}
+
+	return ""
+}
+
+func (versionTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	// Optimization: return early if this module can't be affected.
+	if ctx.Os() != android.Android {
+		return
+	}
+
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		isLLNDK := m.IsLlndk()
+		isVendorPublicLibrary := m.IsVendorPublicLibrary()
+		isImportedApiLibrary := m.isImportedApiLibrary()
+
+		if variation != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
+			// A stubs or LLNDK stubs variant.
+			if m.sanitize != nil {
+				m.sanitize.Properties.ForceDisable = true
+			}
+			if m.stl != nil {
+				m.stl.Properties.Stl = StringPtr("none")
+			}
+			m.Properties.PreventInstall = true
+			lib := moduleLibraryInterface(m)
+			allStubsVersions := library.allStubsVersions()
+			isLatest := len(allStubsVersions) > 0 && variation == allStubsVersions[len(allStubsVersions)-1]
+			lib.setBuildStubs(isLatest)
+		}
+		if variation != "" {
+			// A non-LLNDK stubs module is hidden from make
+			library.setStubsVersion(variation)
+			m.Properties.HideFromMake = true
+		} else {
+			// A non-LLNDK implementation module has a dependency to all stubs versions
+			for _, version := range library.allStubsVersions() {
+				ctx.AddVariationDependencies([]blueprint.Variation{{"version", version}},
+					stubImplDepTag, ctx.ModuleName())
+			}
+		}
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		m.Properties.Sdk_version = StringPtr(variation)
+		m.Properties.Min_sdk_version = StringPtr(variation)
 	}
 }
 
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 1f71c19..e8a9827 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -406,6 +406,9 @@
 	if len(libInfo.StubsVersions) > 0 {
 		stubsSet := outputProperties.AddPropertySet("stubs")
 		stubsSet.AddProperty("versions", libInfo.StubsVersions)
+		// The symbol file will be copied next to the Android.bp file
+		stubsSet.AddProperty("symbol_file", libInfo.StubsSymbolFilePath.Base())
+		builder.CopyToSnapshot(libInfo.StubsSymbolFilePath, libInfo.StubsSymbolFilePath.Base())
 	}
 }
 
@@ -481,6 +484,9 @@
 	// is written to does not vary by arch so cannot be android specific.
 	StubsVersions []string `sdk:"ignored-on-host"`
 
+	// The symbol file containing the APIs exported by this library.
+	StubsSymbolFilePath android.Path `sdk:"ignored-on-host"`
+
 	// Value of SanitizeProperties.Sanitize. Several - but not all - of these
 	// affect the expanded variants. All are propagated to avoid entangling the
 	// sanitizer logic with the snapshot generation.
@@ -549,6 +555,11 @@
 				// the versioned stub libs are retained in the prebuilt tree; currently only
 				// the stub corresponding to ccModule.StubsVersion() is.
 				p.StubsVersions = lib.allStubsVersions()
+				if lib.buildStubs() && ccModule.stubsSymbolFilePath() == nil {
+					ctx.ModuleErrorf("Could not determine symbol_file")
+				} else {
+					p.StubsSymbolFilePath = ccModule.stubsSymbolFilePath()
+				}
 			}
 		}
 		p.SystemSharedLibs = specifiedDeps.systemSharedLibs
diff --git a/cc/library_stub.go b/cc/library_stub.go
index 6f06333..6367825 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -303,7 +303,7 @@
 	return d.hasApexStubs()
 }
 
-func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
 	m, ok := ctx.Module().(*Module)
 
 	if !ok {
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index 85c3edf..5ece78a 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -30,7 +30,7 @@
 type llndkLibraryProperties struct {
 	// Relative path to the symbol map.
 	// An example file can be seen here: TODO(danalbert): Make an example.
-	Symbol_file *string
+	Symbol_file *string `android:"path,arch_variant"`
 
 	// Whether to export any headers as -isystem instead of -I. Mainly for use by
 	// bionic/libc.
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index f326068..b822e5c 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -133,7 +133,7 @@
 	return strings.TrimSuffix(name, ndkLibrarySuffix)
 }
 
-func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string {
+func ndkLibraryVersions(ctx android.BaseModuleContext, from android.ApiLevel) []string {
 	var versions []android.ApiLevel
 	versionStrs := []string{}
 	for _, version := range ctx.Config().AllSupportedApiLevels() {
@@ -147,7 +147,7 @@
 	return versionStrs
 }
 
-func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (this *stubDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
 	if !ctx.Module().Enabled(ctx) {
 		return nil
 	}
diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go
deleted file mode 100644
index f503982..0000000
--- a/cc/ndk_prebuilt.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2016 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 cc
-
-import (
-	"strings"
-
-	"android/soong/android"
-)
-
-func init() {
-	android.RegisterModuleType("ndk_prebuilt_static_stl", NdkPrebuiltStaticStlFactory)
-	android.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
-}
-
-// NDK prebuilt libraries.
-//
-// These differ from regular prebuilts in that they aren't stripped and usually aren't installed
-// either (with the exception of the shared STLs, which are installed to the app's directory rather
-// than to the system image).
-
-type ndkPrebuiltStlLinker struct {
-	*libraryDecorator
-}
-
-func (ndk *ndkPrebuiltStlLinker) linkerProps() []interface{} {
-	return append(ndk.libraryDecorator.linkerProps(), &ndk.Properties, &ndk.flagExporter.Properties)
-}
-
-func (*ndkPrebuiltStlLinker) linkerDeps(ctx DepsContext, deps Deps) Deps {
-	// NDK libraries can't have any dependencies
-	return deps
-}
-
-func (*ndkPrebuiltStlLinker) availableFor(what string) bool {
-	// ndk prebuilt objects are available to everywhere
-	return true
-}
-
-// ndk_prebuilt_shared_stl exports a precompiled ndk shared standard template
-// library (stl) library for linking operation. The soong's module name format
-// is ndk_<NAME>.so where the library is located under
-// ./prebuilts/ndk/current/sources/cxx-stl/llvm-libc++/libs/$(HOST_ARCH)/<NAME>.so.
-func NdkPrebuiltSharedStlFactory() android.Module {
-	module, library := NewLibrary(android.DeviceSupported)
-	library.BuildOnlyShared()
-	module.compiler = nil
-	module.linker = &ndkPrebuiltStlLinker{
-		libraryDecorator: library,
-	}
-	module.installer = nil
-	module.Properties.Sdk_version = StringPtr("minimum")
-	module.Properties.AlwaysSdk = true
-	module.stl.Properties.Stl = StringPtr("none")
-	return module.Init()
-}
-
-// ndk_prebuilt_static_stl exports a precompiled ndk static standard template
-// library (stl) library for linking operation. The soong's module name format
-// is ndk_<NAME>.a where the library is located under
-// ./prebuilts/ndk/current/sources/cxx-stl/llvm-libc++/libs/$(HOST_ARCH)/<NAME>.a.
-func NdkPrebuiltStaticStlFactory() android.Module {
-	module, library := NewLibrary(android.DeviceSupported)
-	library.BuildOnlyStatic()
-	module.compiler = nil
-	module.linker = &ndkPrebuiltStlLinker{
-		libraryDecorator: library,
-	}
-	module.installer = nil
-	module.Properties.Sdk_version = StringPtr("minimum")
-	module.Properties.HideFromMake = true
-	module.Properties.AlwaysSdk = true
-	module.Properties.Sdk_version = StringPtr("current")
-	module.stl.Properties.Stl = StringPtr("none")
-	return module.Init()
-}
-
-const (
-	libDir = "current/sources/cxx-stl/llvm-libc++/libs"
-)
-
-func getNdkStlLibDir(ctx android.ModuleContext) android.SourcePath {
-	return android.PathForSource(ctx, ctx.ModuleDir(), libDir).Join(ctx, ctx.Arch().Abi[0])
-}
-
-func (ndk *ndkPrebuiltStlLinker) link(ctx ModuleContext, flags Flags,
-	deps PathDeps, objs Objects) android.Path {
-	// A null build step, but it sets up the output path.
-	if !strings.HasPrefix(ctx.ModuleName(), "ndk_lib") {
-		ctx.ModuleErrorf("NDK prebuilt libraries must have an ndk_lib prefixed name")
-	}
-
-	ndk.libraryDecorator.flagExporter.exportIncludesAsSystem(ctx)
-
-	libName := strings.TrimPrefix(ctx.ModuleName(), "ndk_")
-	libExt := flags.Toolchain.ShlibSuffix()
-	if ndk.static() {
-		libExt = staticLibraryExtension
-	}
-
-	libDir := getNdkStlLibDir(ctx)
-	lib := libDir.Join(ctx, libName+libExt)
-
-	ndk.libraryDecorator.flagExporter.setProvider(ctx)
-
-	if ndk.static() {
-		depSet := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).Direct(lib).Build()
-		android.SetProvider(ctx, StaticLibraryInfoProvider, StaticLibraryInfo{
-			StaticLibrary: lib,
-
-			TransitiveStaticLibrariesForOrdering: depSet,
-		})
-	} else {
-		android.SetProvider(ctx, SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary: lib,
-			Target:        ctx.Target(),
-		})
-	}
-
-	return lib
-}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index e9f790f..e023a32 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -205,17 +205,6 @@
 				TableOfContents: p.tocFile,
 			})
 
-			// TODO(b/220898484): Mainline module sdk prebuilts of stub libraries use a stub
-			// library as their source and must not be installed, but other prebuilts like
-			// libclang_rt.* libraries set `stubs` property because they are LLNDK libraries,
-			// but use an implementation library as their source and need to be installed.
-			// This discrepancy should be resolved without the prefix hack below.
-			isModuleSdkPrebuilts := android.HasAnyPrefix(ctx.ModuleDir(), []string{
-				"prebuilts/runtime/mainline/", "prebuilts/module_sdk/"})
-			if p.hasStubsVariants() && !p.buildStubs() && !ctx.Host() && isModuleSdkPrebuilts {
-				ctx.Module().MakeUninstallable()
-			}
-
 			return outputFile
 		}
 	}
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 71b7e43..86e6af9 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -385,112 +385,6 @@
 	assertString(t, static2.OutputFile().Path().Base(), "libf.hwasan.a")
 }
 
-func TestPrebuiltStubNoinstall(t *testing.T) {
-	testFunc := func(t *testing.T, expectLibfooOnSystemLib bool, fs android.MockFS) {
-		result := android.GroupFixturePreparers(
-			prepareForPrebuiltTest,
-			android.PrepareForTestWithMakevars,
-			android.FixtureMergeMockFs(fs),
-		).RunTest(t)
-
-		ldRule := result.ModuleForTests("installedlib", "android_arm64_armv8-a_shared").Rule("ld")
-		android.AssertStringDoesContain(t, "", ldRule.Args["libFlags"], "android_arm64_armv8-a_shared/libfoo.so")
-
-		installRules := result.InstallMakeRulesForTesting(t)
-		var installedlibRule *android.InstallMakeRule
-		for i, rule := range installRules {
-			if rule.Target == "out/target/product/test_device/system/lib/installedlib.so" {
-				if installedlibRule != nil {
-					t.Errorf("Duplicate install rules for %s", rule.Target)
-				}
-				installedlibRule = &installRules[i]
-			}
-		}
-		if installedlibRule == nil {
-			t.Errorf("No install rule found for installedlib")
-			return
-		}
-
-		if expectLibfooOnSystemLib {
-			android.AssertStringListContains(t,
-				"installedlib doesn't have install dependency on libfoo impl",
-				installedlibRule.OrderOnlyDeps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-		} else {
-			android.AssertStringListDoesNotContain(t,
-				"installedlib has install dependency on libfoo stub",
-				installedlibRule.Deps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-			android.AssertStringListDoesNotContain(t,
-				"installedlib has order-only install dependency on libfoo stub",
-				installedlibRule.OrderOnlyDeps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-		}
-	}
-
-	prebuiltLibfooBp := []byte(`
-		cc_prebuilt_library {
-			name: "libfoo",
-			prefer: true,
-			srcs: ["libfoo.so"],
-			stubs: {
-				versions: ["1"],
-			},
-		}
-	`)
-
-	installedlibBp := []byte(`
-		cc_library {
-			name: "installedlib",
-			shared_libs: ["libfoo"],
-		}
-	`)
-
-	t.Run("prebuilt stub (without source): no install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ false,
-			android.MockFS{
-				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
-				"Android.bp": installedlibBp,
-			},
-		)
-	})
-
-	disabledSourceLibfooBp := []byte(`
-		cc_library {
-			name: "libfoo",
-			enabled: false,
-			stubs: {
-				versions: ["1"],
-			},
-		}
-	`)
-
-	t.Run("prebuilt stub (with disabled source): no install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ false,
-			android.MockFS{
-				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
-				"impl/Android.bp": disabledSourceLibfooBp,
-				"Android.bp":      installedlibBp,
-			},
-		)
-	})
-
-	t.Run("prebuilt impl (with `stubs` property set): install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ true,
-			android.MockFS{
-				"impl/Android.bp": prebuiltLibfooBp,
-				"Android.bp":      installedlibBp,
-			},
-		)
-	})
-}
-
 func TestPrebuiltBinaryNoSrcsNoError(t *testing.T) {
 	const bp = `
 cc_prebuilt_binary {
diff --git a/cc/proto.go b/cc/proto.go
index 4d72f26..93142b9 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -19,6 +19,8 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+
+	"strings"
 )
 
 const (
@@ -35,13 +37,21 @@
 		srcSuffix = ".c"
 	}
 
+	srcInfix := "pb"
+	for _, value := range flags.proto.Flags {
+		if strings.HasPrefix(value, "--plugin=") && strings.HasSuffix(value, "protoc-gen-grpc-cpp-plugin") {
+			srcInfix = "grpc.pb"
+			break
+		}
+	}
+
 	if flags.proto.CanonicalPathFromRoot {
-		ccFile = android.GenPathWithExt(ctx, "proto", protoFile, "pb"+srcSuffix)
-		headerFile = android.GenPathWithExt(ctx, "proto", protoFile, "pb.h")
+		ccFile = android.GenPathWithExt(ctx, "proto", protoFile, srcInfix+srcSuffix)
+		headerFile = android.GenPathWithExt(ctx, "proto", protoFile, srcInfix+".h")
 	} else {
 		rel := protoFile.Rel()
-		ccFile = android.PathForModuleGen(ctx, "proto", pathtools.ReplaceExtension(rel, "pb"+srcSuffix))
-		headerFile = android.PathForModuleGen(ctx, "proto", pathtools.ReplaceExtension(rel, "pb.h"))
+		ccFile = android.PathForModuleGen(ctx, "proto", pathtools.ReplaceExtension(rel, srcInfix+srcSuffix))
+		headerFile = android.PathForModuleGen(ctx, "proto", pathtools.ReplaceExtension(rel, srcInfix+".h"))
 	}
 
 	protoDeps := flags.proto.Deps
diff --git a/cc/proto_test.go b/cc/proto_test.go
index abcb273..a905ea8 100644
--- a/cc/proto_test.go
+++ b/cc/proto_test.go
@@ -68,4 +68,36 @@
 		}
 	})
 
+	t.Run("grpc-cpp-plugin", func(t *testing.T) {
+		ctx := testCc(t, `
+                cc_binary_host {
+                        name: "protoc-gen-grpc-cpp-plugin",
+                        stl: "none",
+                }
+
+                cc_library_shared {
+                        name: "libgrpc",
+                        srcs: ["a.proto"],
+                        proto: {
+                                plugin: "grpc-cpp-plugin",
+                        },
+                }`)
+
+		buildOS := ctx.Config().BuildOS.String()
+
+		proto := ctx.ModuleForTests("libgrpc", "android_arm_armv7-a-neon_shared").Output("proto/a.grpc.pb.cc")
+		grpcCppPlugin := ctx.ModuleForTests("protoc-gen-grpc-cpp-plugin", buildOS+"_x86_64")
+
+		cmd := proto.RuleParams.Command
+		if w := "--grpc-cpp-plugin_out="; !strings.Contains(cmd, w) {
+			t.Errorf("expected %q in %q", w, cmd)
+		}
+
+		grpcCppPluginPath := grpcCppPlugin.Module().(android.HostToolProvider).HostToolPath().RelativeToTop().String()
+
+		if w := "--plugin=protoc-gen-grpc-cpp-plugin=" + grpcCppPluginPath; !strings.Contains(cmd, w) {
+			t.Errorf("expected %q in %q", w, cmd)
+		}
+	})
+
 }
diff --git a/cc/sdk.go b/cc/sdk.go
index 4925ce1..dc1261d 100644
--- a/cc/sdk.go
+++ b/cc/sdk.go
@@ -19,12 +19,86 @@
 	"android/soong/genrule"
 )
 
-// sdkMutator sets a creates a platform and an SDK variant for modules
+// sdkTransitionMutator creates a platform and an SDK variant for modules
 // that set sdk_version, and ignores sdk_version for the platform
 // variant.  The SDK variant will be used for embedding in APKs
 // that may be installed on older platforms.  Apexes use their own
 // variants that enforce backwards compatibility.
-func sdkMutator(ctx android.BottomUpMutatorContext) {
+type sdkTransitionMutator struct{}
+
+func (sdkTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if ctx.Os() != android.Android {
+		return []string{""}
+	}
+
+	switch m := ctx.Module().(type) {
+	case LinkableInterface:
+		if m.AlwaysSdk() {
+			if !m.UseSdk() && !m.SplitPerApiLevel() {
+				ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+			}
+			return []string{"sdk"}
+		} else if m.UseSdk() || m.SplitPerApiLevel() {
+			return []string{"", "sdk"}
+		} else {
+			return []string{""}
+		}
+	case *genrule.Module:
+		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+			if String(p.Sdk_version) != "" {
+				return []string{"", "sdk"}
+			} else {
+				return []string{""}
+			}
+		}
+	case *CcApiVariant:
+		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+		if String(ccApiVariant.properties.Variant) == "ndk" {
+			return []string{"sdk"}
+		} else {
+			return []string{""}
+		}
+	}
+
+	return []string{""}
+}
+
+func (sdkTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return sourceVariation
+}
+
+func (sdkTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if ctx.Os() != android.Android {
+		return ""
+	}
+	switch m := ctx.Module().(type) {
+	case LinkableInterface:
+		if m.AlwaysSdk() {
+			return "sdk"
+		} else if m.UseSdk() || m.SplitPerApiLevel() {
+			return incomingVariation
+		}
+	case *genrule.Module:
+		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+			if String(p.Sdk_version) != "" {
+				return incomingVariation
+			}
+		}
+	case *CcApiVariant:
+		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+		if String(ccApiVariant.properties.Variant) == "ndk" {
+			return "sdk"
+		}
+	}
+
+	if ctx.IsAddingDependency() {
+		return incomingVariation
+	} else {
+		return ""
+	}
+}
+
+func (sdkTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
 	if ctx.Os() != android.Android {
 		return
 	}
@@ -33,59 +107,45 @@
 	case LinkableInterface:
 		ccModule, isCcModule := ctx.Module().(*Module)
 		if m.AlwaysSdk() {
-			if !m.UseSdk() && !m.SplitPerApiLevel() {
-				ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+			if variation != "sdk" {
+				ctx.ModuleErrorf("tried to create variation %q for module with AlwaysSdk set, expected \"sdk\"", variation)
 			}
-			modules := ctx.CreateVariations("sdk")
-			modules[0].(*Module).Properties.IsSdkVariant = true
+
+			ccModule.Properties.IsSdkVariant = true
 		} else if m.UseSdk() || m.SplitPerApiLevel() {
-			modules := ctx.CreateVariations("", "sdk")
+			if variation == "" {
+				// Clear the sdk_version property for the platform (non-SDK) variant so later code
+				// doesn't get confused by it.
+				ccModule.Properties.Sdk_version = nil
+			} else {
+				// Mark the SDK variant.
+				ccModule.Properties.IsSdkVariant = true
 
-			// Clear the sdk_version property for the platform (non-SDK) variant so later code
-			// doesn't get confused by it.
-			modules[0].(*Module).Properties.Sdk_version = nil
-
-			// Mark the SDK variant.
-			modules[1].(*Module).Properties.IsSdkVariant = true
+				// SDK variant never gets installed because the variant is to be embedded in
+				// APKs, not to be installed to the platform.
+				ccModule.Properties.PreventInstall = true
+			}
 
 			if ctx.Config().UnbundledBuildApps() {
-				// For an unbundled apps build, hide the platform variant from Make
-				// so that other Make modules don't link against it, but against the
-				// SDK variant.
-				modules[0].(*Module).Properties.HideFromMake = true
+				if variation == "" {
+					// For an unbundled apps build, hide the platform variant from Make
+					// so that other Make modules don't link against it, but against the
+					// SDK variant.
+					ccModule.Properties.HideFromMake = true
+				}
 			} else {
-				// For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
-				// exposed to Make.
-				modules[1].(*Module).Properties.SdkAndPlatformVariantVisibleToMake = true
+				if variation == "sdk" {
+					// For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
+					// exposed to Make.
+					ccModule.Properties.SdkAndPlatformVariantVisibleToMake = true
+				}
 			}
-			// SDK variant never gets installed because the variant is to be embedded in
-			// APKs, not to be installed to the platform.
-			modules[1].(*Module).Properties.PreventInstall = true
-			ctx.AliasVariation("")
 		} else {
 			if isCcModule {
 				// Clear the sdk_version property for modules that don't have an SDK variant so
 				// later code doesn't get confused by it.
 				ccModule.Properties.Sdk_version = nil
 			}
-			ctx.CreateVariations("")
-			ctx.AliasVariation("")
-		}
-	case *genrule.Module:
-		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
-			if String(p.Sdk_version) != "" {
-				ctx.CreateVariations("", "sdk")
-			} else {
-				ctx.CreateVariations("")
-			}
-			ctx.AliasVariation("")
-		}
-	case *CcApiVariant:
-		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
-		if String(ccApiVariant.properties.Variant) == "ndk" {
-			ctx.CreateVariations("sdk")
-		} else {
-			ctx.CreateVariations("")
 		}
 	}
 }
diff --git a/cc/stl.go b/cc/stl.go
index de2066f..8c4ef0b 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -177,7 +177,7 @@
 		} else {
 			deps.StaticLibs = append(deps.StaticLibs, stl.Properties.SelectedStl, "ndk_libc++abi")
 		}
-		deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
+		deps.StaticLibs = append(deps.StaticLibs, "libunwind")
 	default:
 		panic(fmt.Errorf("Unknown stl: %q", stl.Properties.SelectedStl))
 	}
diff --git a/cc/testing.go b/cc/testing.go
index 02f9924..ed567af 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -38,8 +38,6 @@
 	ctx.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
 	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
-	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
-	ctx.RegisterModuleType("ndk_prebuilt_static_stl", NdkPrebuiltStaticStlFactory)
 	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
 	ctx.RegisterModuleType("ndk_headers", NdkHeadersFactory)
 }
@@ -312,6 +310,25 @@
 			],
 		}
 		cc_library {
+			name: "ndk_libc++_shared",
+			export_include_dirs: ["ndk_libc++_shared_include_dirs"],
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+			vendor_available: true,
+			vendor_ramdisk_available: true,
+			product_available: true,
+			recovery_available: true,
+			host_supported: false,
+			sdk_version: "minimum",
+			double_loadable: true,
+			apex_available: [
+				"//apex_available:platform",
+				"//apex_available:anyapex",
+			],
+		}
+		cc_library {
 			name: "libc++demangle",
 			no_libcrt: true,
 			nocrt: true,
@@ -397,13 +414,6 @@
 			name: "libprotobuf-cpp-lite",
 		}
 
-		cc_library {
-			name: "ndk_libunwind",
-			sdk_version: "minimum",
-			stl: "none",
-			system_shared_libs: [],
-		}
-
 		ndk_library {
 			name: "libc",
 			first_version: "minimum",
@@ -422,11 +432,6 @@
 			symbol_file: "libdl.map.txt",
 		}
 
-		ndk_prebuilt_shared_stl {
-			name: "ndk_libc++_shared",
-			export_include_dirs: ["ndk_libc++_shared"],
-		}
-
 		cc_library_static {
 			name: "libgoogle-benchmark",
 			sdk_version: "current",
@@ -557,13 +562,6 @@
 
 		RegisterLlndkLibraryTxtType(ctx)
 	}),
-
-	// Additional files needed in tests that disallow non-existent source files.
-	// This includes files that are needed by all, or at least most, instances of a cc module type.
-	android.MockFS{
-		// Needed for ndk_prebuilt_(shared|static)_stl.
-		"defaults/cc/common/current/sources/cxx-stl/llvm-libc++/libs": nil,
-	}.AddToFixture(),
 )
 
 // Preparer that will define default cc modules, e.g. standard prebuilt modules.
@@ -572,17 +570,17 @@
 
 	// Additional files needed in tests that disallow non-existent source.
 	android.MockFS{
-		"defaults/cc/common/libc.map.txt":                nil,
-		"defaults/cc/common/libdl.map.txt":               nil,
-		"defaults/cc/common/libft2.map.txt":              nil,
-		"defaults/cc/common/libm.map.txt":                nil,
-		"defaults/cc/common/ndk_libc++_shared":           nil,
-		"defaults/cc/common/crtbegin_so.c":               nil,
-		"defaults/cc/common/crtbegin.c":                  nil,
-		"defaults/cc/common/crtend_so.c":                 nil,
-		"defaults/cc/common/crtend.c":                    nil,
-		"defaults/cc/common/crtbrand.c":                  nil,
-		"external/compiler-rt/lib/cfi/cfi_blocklist.txt": nil,
+		"defaults/cc/common/libc.map.txt":                   nil,
+		"defaults/cc/common/libdl.map.txt":                  nil,
+		"defaults/cc/common/libft2.map.txt":                 nil,
+		"defaults/cc/common/libm.map.txt":                   nil,
+		"defaults/cc/common/ndk_libc++_shared_include_dirs": nil,
+		"defaults/cc/common/crtbegin_so.c":                  nil,
+		"defaults/cc/common/crtbegin.c":                     nil,
+		"defaults/cc/common/crtend_so.c":                    nil,
+		"defaults/cc/common/crtend.c":                       nil,
+		"defaults/cc/common/crtbrand.c":                     nil,
+		"external/compiler-rt/lib/cfi/cfi_blocklist.txt":    nil,
 
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm64.a": nil,
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm.a":   nil,
diff --git a/cmd/release_config/release_config_lib/flag_declaration.go b/cmd/release_config/release_config_lib/flag_declaration.go
index 97d4d4c..22001bf 100644
--- a/cmd/release_config/release_config_lib/flag_declaration.go
+++ b/cmd/release_config/release_config_lib/flag_declaration.go
@@ -18,10 +18,21 @@
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
 )
 
+var (
+	// Allowlist: these flags may have duplicate (identical) declarations
+	// without generating an error.  This will be removed once all such
+	// declarations have been fixed.
+	DuplicateDeclarationAllowlist = map[string]bool{}
+)
+
 func FlagDeclarationFactory(protoPath string) (fd *rc_proto.FlagDeclaration) {
 	fd = &rc_proto.FlagDeclaration{}
 	if protoPath != "" {
 		LoadMessage(protoPath, fd)
 	}
+	// If the input didn't specify a value, create one (== UnspecifiedValue).
+	if fd.Value == nil {
+		fd.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
+	}
 	return fd
 }
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index a4c884c..97eb8f1 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -276,6 +276,20 @@
 		configs.Aliases[name] = alias.Target
 	}
 	var err error
+	// Temporarily allowlist duplicate flag declaration files to prevent
+	// more from entering the tree while we work to clean up the duplicates
+	// that already exist.
+	dupFlagFile := filepath.Join(dir, "duplicate_allowlist.txt")
+	data, err := os.ReadFile(dupFlagFile)
+	if err == nil {
+		for _, flag := range strings.Split(string(data), "\n") {
+			flag = strings.TrimSpace(flag)
+			if strings.HasPrefix(flag, "//") || strings.HasPrefix(flag, "#") {
+				continue
+			}
+			DuplicateDeclarationAllowlist[flag] = true
+		}
+	}
 	err = WalkTextprotoFiles(dir, "flag_declarations", func(path string, d fs.DirEntry, err error) error {
 		flagDeclaration := FlagDeclarationFactory(path)
 		// Container must be specified.
@@ -289,14 +303,6 @@
 			}
 		}
 
-		// TODO: once we have namespaces initialized, we can throw an error here.
-		if flagDeclaration.Namespace == nil {
-			flagDeclaration.Namespace = proto.String("android_UNKNOWN")
-		}
-		// If the input didn't specify a value, create one (== UnspecifiedValue).
-		if flagDeclaration.Value == nil {
-			flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
-		}
 		m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration)
 		name := *flagDeclaration.Name
 		if name == "RELEASE_ACONFIG_VALUE_SETS" {
@@ -304,8 +310,8 @@
 		}
 		if def, ok := configs.FlagArtifacts[name]; !ok {
 			configs.FlagArtifacts[name] = &FlagArtifact{FlagDeclaration: flagDeclaration, DeclarationIndex: ConfigDirIndex}
-		} else if !proto.Equal(def.FlagDeclaration, flagDeclaration) {
-			return fmt.Errorf("Duplicate definition of %s", *flagDeclaration.Name)
+		} else if !proto.Equal(def.FlagDeclaration, flagDeclaration) || !DuplicateDeclarationAllowlist[name] {
+			return fmt.Errorf("Duplicate definition of %s in %s", *flagDeclaration.Name, path)
 		}
 		// Set the initial value in the flag artifact.
 		configs.FilesUsedMap[path] = true
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 201515f..5616483 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -244,6 +244,7 @@
 	}
 
 	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
+	odexSymbolsPath := odexPath.ReplaceExtension(ctx, "symbols.odex")
 	odexInstallPath := ToOdexPath(module.DexLocation, arch)
 	if odexOnSystemOther(module, global) {
 		odexInstallPath = filepath.Join(SystemOtherPartition, odexInstallPath)
@@ -258,7 +259,8 @@
 	systemServerClasspathJars := global.AllSystemServerClasspathJars(ctx)
 
 	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
-	rule.Command().FlagWithOutput("rm -f ", odexPath)
+	rule.Command().FlagWithOutput("rm -f ", odexPath).
+		FlagWithArg("rm -f ", odexSymbolsPath.String())
 
 	if jarIndex := systemServerJars.IndexOfJar(module.Name); jarIndex >= 0 {
 		// System server jars should be dexpreopted together: class loader context of each jar
@@ -386,7 +388,9 @@
 		FlagWithArg("--instruction-set=", arch.String()).
 		FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]).
 		FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]).
-		Flag("--no-generate-debug-info").
+		FlagWithOutput("--oat-symbols=", odexSymbolsPath).
+		Flag("--generate-debug-info").
+		Flag("--strip").
 		Flag("--generate-build-id").
 		Flag("--abort-on-hard-verifier-error").
 		Flag("--force-determinism").
@@ -532,7 +536,7 @@
 	}
 
 	for _, f := range global.PatternsOnSystemOther {
-		if makefileMatch("/" + f, dexLocation) || makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
+		if makefileMatch("/"+f, dexLocation) || makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
 			return true
 		}
 	}
diff --git a/docs/resources.md b/docs/resources.md
new file mode 100644
index 0000000..c7cb0cf
--- /dev/null
+++ b/docs/resources.md
@@ -0,0 +1,89 @@
+## Soong Android Resource Compilation
+
+The Android build process involves several steps to compile resources into a format that the Android app can use
+efficiently in android_library, android_app and android_test modules.  See the
+[resources documentation](https://developer.android.com/guide/topics/resources/providing-resources) for general
+information on resources (with a focus on building with Gradle).
+
+For all modules, AAPT2 compiles resources provided by directories listed in the resource_dirs directory (which is
+implicitly set to `["res"]` if unset, but can be overridden by setting the `resource_dirs` property).
+
+## android_library with resource processor
+For an android_library with resource processor enabled (currently by setting `use_resource_processor: true`, but will be
+enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_library module.  `package-res.apk` files from transitive dependencies are passed to AAPT2 with the `-I` flag to
+resolve references to resources from dependencies.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_library module.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_library and produces an `R.jar` with an
+`R.class` in the package listed in the android_library's `AndroidManifest.xml` file that contains java fields for each
+resource ID.  The resource IDs are non-final, as the final IDs will not be known until the resource table of the final
+android_app or android_test module is built.
+- The android_library's java and/or kotlin code is compiled with the generated `R.jar` in the classpath, along with the
+`R.jar` files from all transitive android_library dependencies.
+
+## android_app or android_test with resource processor
+For an android_app or android_test with resource processor enabled (currently by setting `use_resource_processor: true`,
+but will be enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app or android_test, as well as all transitive android_library modules referenced via `static_libs`.  The
+current module is overlaid on dependencies so that resources from the current module replace resources from dependencies
+in the case of conflicts.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_app or android_test, as
+well as all transitive android_library modules referenced via `static_libs`.  The R.txt file contains the final resource
+ID for each resource.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_app or android_test, as well as all transitive
+android_library modules referenced via `static_libs`, and produces an `R.jar` with an `R.class` in the package listed in
+the android_app or android_test's `AndroidManifest.xml` file that contains java fields for all local or transitive
+resource IDs.  In addition, it creates an `R.class` in the package listed in each android_library dependency's
+`AndroidManifest.xml` file that contains final resource IDs for the resources that were found in that library.
+- The android_app or android_test's java and/or kotlin code is compiled with the current module's `R.jar` in the
+classpath, but not the `R.jar` files from transitive android_library dependencies.  The `R.jar` file is also merged into
+the program  classes that are dexed and placed in the final APK.
+
+## android_app, android_test or android_library without resource processor
+For an android_app, android_test or android_library without resource processor enabled (current the default, or
+explicitly set with `use_resource_processor: false`):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app, android_test or android_library module, as well as all transitive android_library modules referenced via
+`static_libs`.  The current module is overlaid on dependencies so that resources from the current module replace
+resources from dependencies in the case of conflicts.
+- AAPT2 generates an `R.java` file in the package listed in each the current module's `AndroidManifest.xml` file that
+contains resource IDs for all resources from the current module as well as all transitive android_library modules
+referenced via `static_libs`.  The same `R.java` containing all local and transitive resources is also duplicated into
+every package listed in an `AndroidManifest.xml` file in any static `android_library` dependency.
+- The module's java and/or kotlin code is compiled along with all the generated `R.java` files.
+
+
+## Downsides of legacy resource compilation without resource processor
+
+Compiling resources without using the resource processor results in a generated R.java source file for every transitive
+package that contains every transitive resource.  For modules with large transitive dependency trees this can be tens of
+thousands of resource IDs duplicated in tens to a hundred java sources.  These java sources all have to be compiled in
+every successive module in the dependency tree, and then the final R8 step has to drop hundreds of thousands of
+unreferenced fields.  This results in significant build time and disk usage increases over building with resource
+processor.
+
+## Converting to compilation with resource processor
+
+### Reference resources using the package name of the module that includes them.
+Converting an android_library module to build with resource processor requires fixing any references to resources
+provided by android_library dependencies to reference the R classes using the package name found in the
+`AndroidManifest.xml` file of the dependency.  For example, when referencing an androidx resource:
+```java
+View.inflate(mContext, R.layout.preference, null));
+```
+must be replaced with:
+```java
+View.inflate(mContext, androidx.preference.R.layout.preference, null));
+```
+
+### Use unique package names for each module in `AndroidManifest.xml`
+
+Each module will produce an `R.jar` containing an `R.class` in the package specified in it's `AndroidManifest.xml`.
+If multiple modules use the same package name they will produce conflicting `R.class` files, which can cause some
+resource IDs to appear to be missing.
+
+If existing code has multiple modules that contribute resources to the same package, one option is to move all the
+resources into a single resources-only `android_library` module with no code, and then depend on that from all the other
+modules.
\ No newline at end of file
diff --git a/elf/Android.bp b/elf/Android.bp
index 6450be1..6d3f4f0 100644
--- a/elf/Android.bp
+++ b/elf/Android.bp
@@ -20,6 +20,7 @@
     name: "soong-elf",
     pkgPath: "android/soong/elf",
     srcs: [
+        "build_id_dir.go",
         "elf.go",
     ],
     testSrcs: [
diff --git a/elf/build_id_dir.go b/elf/build_id_dir.go
new file mode 100644
index 0000000..5fb7dda
--- /dev/null
+++ b/elf/build_id_dir.go
@@ -0,0 +1,172 @@
+// 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 elf
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+)
+
+func UpdateBuildIdDir(path string) error {
+	path = filepath.Clean(path)
+	buildIdPath := path + "/.build-id"
+
+	// Collect the list of files and build-id symlinks. If the symlinks are
+	// up to date (newer than the symbol files), there is nothing to do.
+	var buildIdFiles, symbolFiles []string
+	var buildIdMtime, symbolsMtime time.Time
+	filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error {
+		if entry == nil || entry.IsDir() {
+			return nil
+		}
+		info, err := entry.Info()
+		if err != nil {
+			return err
+		}
+		mtime := info.ModTime()
+		if strings.HasPrefix(path, buildIdPath) {
+			if buildIdMtime.Compare(mtime) < 0 {
+				buildIdMtime = mtime
+			}
+			buildIdFiles = append(buildIdFiles, path)
+		} else {
+			if symbolsMtime.Compare(mtime) < 0 {
+				symbolsMtime = mtime
+			}
+			symbolFiles = append(symbolFiles, path)
+		}
+		return nil
+	})
+	if symbolsMtime.Compare(buildIdMtime) < 0 {
+		return nil
+	}
+
+	// Collect build-id -> file mapping from ELF files in the symbols directory.
+	concurrency := 8
+	done := make(chan error)
+	buildIdToFile := make(map[string]string)
+	var mu sync.Mutex
+	for i := 0; i != concurrency; i++ {
+		go func(paths []string) {
+			for _, path := range paths {
+				id, err := Identifier(path, true)
+				if err != nil {
+					done <- err
+					return
+				}
+				if id == "" {
+					continue
+				}
+				mu.Lock()
+				oldPath := buildIdToFile[id]
+				if oldPath == "" || oldPath > path {
+					buildIdToFile[id] = path
+				}
+				mu.Unlock()
+			}
+			done <- nil
+		}(symbolFiles[len(symbolFiles)*i/concurrency : len(symbolFiles)*(i+1)/concurrency])
+	}
+
+	// Collect previously generated build-id -> file mapping from the .build-id directory.
+	// We will use this for incremental updates. If we see anything in the .build-id
+	// directory that we did not expect, we'll delete it and start over.
+	prevBuildIdToFile := make(map[string]string)
+out:
+	for _, buildIdFile := range buildIdFiles {
+		if !strings.HasSuffix(buildIdFile, ".debug") {
+			prevBuildIdToFile = nil
+			break
+		}
+		buildId := buildIdFile[len(buildIdPath)+1 : len(buildIdFile)-6]
+		for i, ch := range buildId {
+			if i == 2 {
+				if ch != '/' {
+					prevBuildIdToFile = nil
+					break out
+				}
+			} else {
+				if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') {
+					prevBuildIdToFile = nil
+					break out
+				}
+			}
+		}
+		target, err := os.Readlink(buildIdFile)
+		if err != nil || !strings.HasPrefix(target, "../../") {
+			prevBuildIdToFile = nil
+			break
+		}
+		prevBuildIdToFile[buildId[0:2]+buildId[3:]] = path + target[5:]
+	}
+	if prevBuildIdToFile == nil {
+		err := os.RemoveAll(buildIdPath)
+		if err != nil {
+			return err
+		}
+		prevBuildIdToFile = make(map[string]string)
+	}
+
+	// Wait for build-id collection from ELF files to finish.
+	for i := 0; i != concurrency; i++ {
+		err := <-done
+		if err != nil {
+			return err
+		}
+	}
+
+	// Delete old symlinks.
+	for id, _ := range prevBuildIdToFile {
+		if buildIdToFile[id] == "" {
+			symlinkDir := buildIdPath + "/" + id[:2]
+			symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+			if err := os.Remove(symlinkPath); err != nil {
+				return err
+			}
+		}
+	}
+
+	// Add new symlinks and update changed symlinks.
+	for id, path := range buildIdToFile {
+		prevPath := prevBuildIdToFile[id]
+		if prevPath == path {
+			continue
+		}
+		symlinkDir := buildIdPath + "/" + id[:2]
+		symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+		if prevPath == "" {
+			if err := os.MkdirAll(symlinkDir, 0755); err != nil {
+				return err
+			}
+		} else {
+			if err := os.Remove(symlinkPath); err != nil {
+				return err
+			}
+		}
+
+		target, err := filepath.Rel(symlinkDir, path)
+		if err != nil {
+			return err
+		}
+		if err := os.Symlink(target, symlinkPath); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 0c6e7f4..3a9a64d 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -59,7 +59,7 @@
 
 	// List of filesystem modules that this vbmeta has descriptors for. The filesystem modules
 	// have to be signed (use_avb: true).
-	Partitions []string
+	Partitions proptools.Configurable[[]string]
 
 	// List of chained partitions that this vbmeta deletages the verification.
 	Chained_partitions []chainedPartitionProperties
@@ -110,7 +110,7 @@
 var vbmetaPartitionDep = vbmetaDep{kind: "partition"}
 
 func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions...)
+	ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions.GetOrDefault(ctx, nil)...)
 }
 
 func (v *vbmeta) installFileName() string {
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 5b40768..2557922 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -139,7 +139,8 @@
 	Export_include_dirs []string
 
 	// list of input files
-	Srcs []string `android:"path,arch_variant"`
+	Srcs         proptools.Configurable[[]string] `android:"path,arch_variant"`
+	ResolvedSrcs []string                         `blueprint:"mutated"`
 
 	// input files to exclude
 	Exclude_srcs []string `android:"path,arch_variant"`
@@ -382,7 +383,8 @@
 		}
 		return srcFiles
 	}
-	srcFiles := addLabelsForInputs("srcs", g.properties.Srcs, g.properties.Exclude_srcs)
+	g.properties.ResolvedSrcs = g.properties.Srcs.GetOrDefault(ctx, nil)
+	srcFiles := addLabelsForInputs("srcs", g.properties.ResolvedSrcs, g.properties.Exclude_srcs)
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcFiles.Strings()})
 
 	var copyFrom android.Paths
@@ -589,7 +591,7 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
-	for _, src := range g.properties.Srcs {
+	for _, src := range g.properties.ResolvedSrcs {
 		if strings.HasPrefix(src, ":") {
 			src = strings.Trim(src, ":")
 			dpInfo.Deps = append(dpInfo.Deps, src)
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index fba9aec..444aedb 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -694,7 +694,7 @@
 	android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0])
 
 	expectedSrcs := []string{"in1"}
-	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs)
+	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.ResolvedSrcs)
 }
 
 func TestGenruleAllowMissingDependencies(t *testing.T) {
diff --git a/java/Android.bp b/java/Android.bp
index a941754..9603815 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -101,6 +101,7 @@
         "hiddenapi_singleton_test.go",
         "jacoco_test.go",
         "java_test.go",
+        "jarjar_test.go",
         "jdeps_test.go",
         "kotlin_test.go",
         "lint_test.go",
diff --git a/java/aapt2.go b/java/aapt2.go
index f704fc6..61cf373 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -69,7 +69,7 @@
 
 // aapt2Compile compiles resources and puts the results in the requested directory.
 func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
-	flags []string, productToFilter string) android.WritablePaths {
+	flags []string, productToFilter string, featureFlagsPaths android.Paths) android.WritablePaths {
 	if productToFilter != "" && productToFilter != "default" {
 		// --filter-product leaves only product-specific resources. Product-specific resources only exist
 		// in value resources (values/*.xml), so filter value resource files only. Ignore other types of
@@ -85,6 +85,10 @@
 		flags = append([]string{"--filter-product " + productToFilter}, flags...)
 	}
 
+	for _, featureFlagsPath := range android.SortedUniquePaths(featureFlagsPaths) {
+		flags = append(flags, "--feature-flags", "@"+featureFlagsPath.String())
+	}
+
 	// Shard the input paths so that they can be processed in parallel. If we shard them into too
 	// small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
 	// current shard size, 100, seems to be a good balance between the added cost and the gain.
@@ -112,6 +116,7 @@
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        aapt2CompileRule,
 			Description: "aapt2 compile " + dir.String() + shardDesc,
+			Implicits:   featureFlagsPaths,
 			Inputs:      shard,
 			Outputs:     outPaths,
 			Args: map[string]string{
diff --git a/java/aar.go b/java/aar.go
index 2f49a95..b69b7c2 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -440,7 +440,8 @@
 	var compiledResDirs []android.Paths
 	for _, dir := range resDirs {
 		a.resourceFiles = append(a.resourceFiles, dir.files...)
-		compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths())
+		compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files,
+			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths())
 	}
 
 	for i, zip := range resZips {
@@ -499,7 +500,8 @@
 	}
 
 	for _, dir := range overlayDirs {
-		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths()...)
+		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
+			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
 	}
 
 	var splitPackages android.WritablePaths
diff --git a/java/app_import.go b/java/app_import.go
index fa87997..045a89a 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -431,6 +431,9 @@
 	var extraArgs []string
 	if a.Privileged() {
 		extraArgs = append(extraArgs, "--privileged")
+		if ctx.Config().UncompressPrivAppDex() {
+			extraArgs = append(extraArgs, "--uncompress-priv-app-dex")
+		}
 	}
 	if proptools.Bool(a.properties.Skip_preprocessed_apk_checks) {
 		extraArgs = append(extraArgs, "--skip-preprocessed-apk-checks")
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 496fc13..54a5e75 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -777,30 +777,79 @@
 }
 
 func TestAndroidAppImport_Preprocessed(t *testing.T) {
-	ctx, _ := testJava(t, `
-		android_app_import {
-			name: "foo",
-			apk: "prebuilts/apk/app.apk",
-			presigned: true,
-			preprocessed: true,
-		}
-		`)
+	for _, dontUncompressPrivAppDexs := range []bool{false, true} {
+		name := fmt.Sprintf("dontUncompressPrivAppDexs:%t", dontUncompressPrivAppDexs)
+		t.Run(name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.UncompressPrivAppDex = proptools.BoolPtr(!dontUncompressPrivAppDexs)
+				}),
+			).RunTestWithBp(t, `
+				android_app_import {
+					name: "foo",
+					apk: "prebuilts/apk/app.apk",
+					presigned: true,
+					preprocessed: true,
+				}
 
-	apkName := "foo.apk"
-	variant := ctx.ModuleForTests("foo", "android_common")
-	outputBuildParams := variant.Output(apkName).BuildParams
-	if outputBuildParams.Rule.String() != android.Cp.String() {
-		t.Errorf("Unexpected prebuilt android_app_import rule: " + outputBuildParams.Rule.String())
-	}
+				android_app_import {
+					name: "bar",
+					apk: "prebuilts/apk/app.apk",
+					presigned: true,
+					privileged: true,
+					preprocessed: true,
+				}
+			`)
 
-	// Make sure compression and aligning were validated.
-	if outputBuildParams.Validation == nil {
-		t.Errorf("Expected validation rule, but was not found")
-	}
+			// non-privileged app
+			apkName := "foo.apk"
+			variant := result.ModuleForTests("foo", "android_common")
+			outputBuildParams := variant.Output(apkName).BuildParams
+			if outputBuildParams.Rule.String() != android.Cp.String() {
+				t.Errorf("Unexpected prebuilt android_app_import rule: " + outputBuildParams.Rule.String())
+			}
 
-	validationBuildParams := variant.Output("validated-prebuilt/check.stamp").BuildParams
-	if validationBuildParams.Rule.String() != checkPresignedApkRule.String() {
-		t.Errorf("Unexpected validation rule: " + validationBuildParams.Rule.String())
+			// Make sure compression and aligning were validated.
+			if outputBuildParams.Validation == nil {
+				t.Errorf("Expected validation rule, but was not found")
+			}
+
+			validationBuildParams := variant.Output("validated-prebuilt/check.stamp").BuildParams
+			if validationBuildParams.Rule.String() != checkPresignedApkRule.String() {
+				t.Errorf("Unexpected validation rule: " + validationBuildParams.Rule.String())
+			}
+
+			expectedScriptArgs := "--preprocessed"
+			actualScriptArgs := validationBuildParams.Args["extraArgs"]
+			android.AssertStringEquals(t, "check script extraArgs", expectedScriptArgs, actualScriptArgs)
+
+			// privileged app
+			apkName = "bar.apk"
+			variant = result.ModuleForTests("bar", "android_common")
+			outputBuildParams = variant.Output(apkName).BuildParams
+			if outputBuildParams.Rule.String() != android.Cp.String() {
+				t.Errorf("Unexpected prebuilt android_app_import rule: " + outputBuildParams.Rule.String())
+			}
+
+			// Make sure compression and aligning were validated.
+			if outputBuildParams.Validation == nil {
+				t.Errorf("Expected validation rule, but was not found")
+			}
+
+			validationBuildParams = variant.Output("validated-prebuilt/check.stamp").BuildParams
+			if validationBuildParams.Rule.String() != checkPresignedApkRule.String() {
+				t.Errorf("Unexpected validation rule: " + validationBuildParams.Rule.String())
+			}
+
+			expectedScriptArgs = "--privileged"
+			if !dontUncompressPrivAppDexs {
+				expectedScriptArgs += " --uncompress-priv-app-dex"
+			}
+			expectedScriptArgs += " --preprocessed"
+			actualScriptArgs = validationBuildParams.Args["extraArgs"]
+			android.AssertStringEquals(t, "check script extraArgs", expectedScriptArgs, actualScriptArgs)
+		})
 	}
 }
 
diff --git a/java/app_test.go b/java/app_test.go
index e878ccf..6b7d522 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -4364,7 +4364,16 @@
 }
 
 func TestAppFlagsPackages(t *testing.T) {
-	ctx := testApp(t, `
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.FixtureMergeMockFs(
+			map[string][]byte{
+				"res/layout/layout.xml":         nil,
+				"res/values/strings.xml":        nil,
+				"res/values-en-rUS/strings.xml": nil,
+			},
+		),
+	).RunTestWithBp(t, `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -4396,10 +4405,10 @@
 
 	// android_app module depends on aconfig_declarations listed in flags_packages
 	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
-		CheckModuleHasDependency(t, ctx, "foo", "android_common", "bar"))
+		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "bar"))
 
 	android.AssertBoolEquals(t, "foo expected to depend on baz", true,
-		CheckModuleHasDependency(t, ctx, "foo", "android_common", "baz"))
+		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "baz"))
 
 	aapt2LinkRule := foo.Rule("android/soong/java.aapt2Link")
 	linkInFlags := aapt2LinkRule.Args["inFlags"]
@@ -4408,6 +4417,14 @@
 		linkInFlags,
 		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
 	)
+
+	aapt2CompileRule := foo.Rule("android/soong/java.aapt2Compile")
+	compileFlags := aapt2CompileRule.Args["cFlags"]
+	android.AssertStringDoesContain(t,
+		"aapt2 compile command expected to pass feature flags arguments",
+		compileFlags,
+		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
+	)
 }
 
 func TestAppFlagsPackagesPropagation(t *testing.T) {
diff --git a/java/base.go b/java/base.go
index 02dc3e3..02df147 100644
--- a/java/base.go
+++ b/java/base.go
@@ -91,6 +91,10 @@
 	// if not blank, run jarjar using the specified rules file
 	Jarjar_rules *string `android:"path,arch_variant"`
 
+	// java class names to rename with jarjar when a reverse dependency has a jarjar_prefix
+	// property.
+	Jarjar_rename []string
+
 	// if not blank, used as prefix to generate repackage rule
 	Jarjar_prefix *string
 
@@ -550,6 +554,10 @@
 	// java_aconfig_library or java_library modules that are statically linked
 	// to this module. Does not contain cache files from all transitive dependencies.
 	aconfigCacheFiles android.Paths
+
+	// List of soong module dependencies required to compile the current module.
+	// This information is printed out to `Dependencies` field in module_bp_java_deps.json
+	compileDepNames []string
 }
 
 var _ android.InstallableModule = (*Module)(nil)
@@ -2061,10 +2069,7 @@
 }
 
 func (j *Module) CompilerDeps() []string {
-	jdeps := []string{}
-	jdeps = append(jdeps, j.properties.Libs...)
-	jdeps = append(jdeps, j.properties.Static_libs...)
-	return jdeps
+	return j.compileDepNames
 }
 
 func (j *Module) hasCode(ctx android.ModuleContext) bool {
@@ -2408,6 +2413,11 @@
 			}
 		}
 
+		if android.InList(tag, compileDependencyTags) {
+			// Add the dependency name to compileDepNames so that it can be recorded in module_bp_java_deps.json
+			j.compileDepNames = append(j.compileDepNames, otherName)
+		}
+
 		addCLCFromDep(ctx, module, j.classLoaderContexts)
 		addMissingOptionalUsesLibsFromDep(ctx, module, &j.usesLibrary)
 	})
@@ -2649,8 +2659,7 @@
 	// Gather repackage information from deps
 	result := collectDirectDepsProviders(ctx)
 
-	// Update that with entries we've stored for ourself
-	for orig, renamed := range module.jarjarRenameRules {
+	add := func(orig string, renamed string) {
 		if result == nil {
 			result = &JarJarProviderData{
 				Rename: make(map[string]string),
@@ -2659,12 +2668,22 @@
 		if renamed != "" {
 			if preexisting, exists := (*result).Rename[orig]; exists && preexisting != renamed {
 				ctx.ModuleErrorf("Conflicting jarjar rules inherited for class: %s (%s and %s)", orig, renamed, preexisting)
-				continue
+				return
 			}
 		}
 		(*result).Rename[orig] = renamed
 	}
 
+	// Update that with entries we've stored for ourself
+	for orig, renamed := range module.jarjarRenameRules {
+		add(orig, renamed)
+	}
+
+	// Update that with entries given in the jarjar_rename property.
+	for _, orig := range module.properties.Jarjar_rename {
+		add(orig, "")
+	}
+
 	// If there are no renamings, then jarjar_prefix does nothing, so skip the extra work.
 	if result == nil {
 		return nil
diff --git a/java/config/config.go b/java/config/config.go
index 2bb50f6..66e857c 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -47,6 +47,7 @@
 		"services",
 		"android.car",
 		"android.car7",
+		"android.car.builtin",
 		"conscrypt",
 		"core-icu4j",
 		"core-oj",
diff --git a/java/droidstubs.go b/java/droidstubs.go
index a8e0a22..d622903 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -197,6 +197,10 @@
 	// a list of aconfig_declarations module names that the stubs generated in this module
 	// depend on.
 	Aconfig_declarations []string
+
+	// List of hard coded filegroups containing Metalava config files that are passed to every
+	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
+	ConfigFiles []string `android:"path" blueprint:"mutated"`
 }
 
 // Used by xsd_config
@@ -259,6 +263,7 @@
 
 	module.AddProperties(&module.properties,
 		&module.Javadoc.properties)
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
 	module.initModuleAndImport(module)
 
 	InitDroiddocModule(module, android.HostAndDeviceSupported)
@@ -279,6 +284,7 @@
 	module.AddProperties(&module.properties,
 		&module.Javadoc.properties)
 
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
 	InitDroiddocModule(module, android.HostSupported)
 	return module
 }
@@ -694,7 +700,7 @@
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
-	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams) *android.RuleBuilderCommand {
+	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams, configFiles android.Paths) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -738,9 +744,26 @@
 
 	cmd.Flag(config.MetalavaFlags)
 
+	addMetalavaConfigFilesToCmd(cmd, configFiles)
+
 	return cmd
 }
 
+// MetalavaConfigFilegroup is the name of the filegroup in build/soong/java/metalava that lists
+// the configuration files to pass to Metalava.
+const MetalavaConfigFilegroup = "metalava-config-files"
+
+// Get a reference to the MetalavaConfigFilegroup suitable for use in a property.
+func getMetalavaConfigFilegroupReference() []string {
+	return []string{":" + MetalavaConfigFilegroup}
+}
+
+// addMetalavaConfigFilesToCmd adds --config-file options to use the config files list in the
+// MetalavaConfigFilegroup filegroup.
+func addMetalavaConfigFilesToCmd(cmd *android.RuleBuilderCommand, configFiles android.Paths) {
+	cmd.FlagForEachInput("--config-file ", configFiles)
+}
+
 // Pass flagged apis related flags to metalava. When aconfig_declarations property is not
 // defined for a module, simply revert all flagged apis annotations. If aconfig_declarations
 // property is defined, apply transformations and only revert the flagged apis that are not
@@ -812,7 +835,10 @@
 	srcJarList := zipSyncCmd(ctx, rule, params.srcJarDir, d.Javadoc.srcJars)
 
 	homeDir := android.PathForModuleOut(ctx, params.stubConfig.stubsType.String(), "home")
-	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig)
+
+	configFiles := android.PathsForModuleSrc(ctx, d.properties.ConfigFiles)
+
+	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig, configFiles)
 	cmd.Implicits(d.Javadoc.implicits)
 
 	d.stubsFlags(ctx, cmd, params.stubsDir, params.stubConfig.stubsType, params.stubConfig.checkApi)
diff --git a/java/jarjar_test.go b/java/jarjar_test.go
new file mode 100644
index 0000000..82bfa2b
--- /dev/null
+++ b/java/jarjar_test.go
@@ -0,0 +1,85 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+func AssertJarJarRename(t *testing.T, result *android.TestResult, libName, original, expectedRename string) {
+	module := result.ModuleForTests(libName, "android_common")
+
+	provider, found := android.OtherModuleProvider(result.OtherModuleProviderAdaptor(), module.Module(), JarJarProvider)
+	android.AssertBoolEquals(t, fmt.Sprintf("found provider (%s)", libName), true, found)
+
+	renamed, found := provider.Rename[original]
+	android.AssertBoolEquals(t, fmt.Sprintf("found rename (%s)", libName), true, found)
+	android.AssertStringEquals(t, fmt.Sprintf("renamed (%s)", libName), expectedRename, renamed)
+}
+
+func TestJarJarRenameDifferentModules(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "their_lib",
+			jarjar_rename: ["com.example.a"],
+		}
+
+		java_library {
+			name: "boundary_lib",
+			jarjar_prefix: "RENAME",
+			static_libs: ["their_lib"],
+		}
+
+		java_library {
+			name: "my_lib",
+			static_libs: ["boundary_lib"],
+		}
+	`)
+
+	original := "com.example.a"
+	renamed := "RENAME.com.example.a"
+	AssertJarJarRename(t, result, "their_lib", original, "")
+	AssertJarJarRename(t, result, "boundary_lib", original, renamed)
+	AssertJarJarRename(t, result, "my_lib", original, renamed)
+}
+
+func TestJarJarRenameSameModule(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "their_lib",
+			jarjar_rename: ["com.example.a"],
+			jarjar_prefix: "RENAME",
+		}
+
+		java_library {
+			name: "my_lib",
+			static_libs: ["their_lib"],
+		}
+	`)
+
+	original := "com.example.a"
+	renamed := "RENAME.com.example.a"
+	AssertJarJarRename(t, result, "their_lib", original, renamed)
+	AssertJarJarRename(t, result, "my_lib", original, renamed)
+}
diff --git a/java/java.go b/java/java.go
index 88b31b5..b320732 100644
--- a/java/java.go
+++ b/java/java.go
@@ -269,7 +269,7 @@
 	ImplementationAndResourcesJars android.Paths
 
 	// ImplementationJars is a list of jars that contain the implementations of classes in the
-	//module.
+	// module.
 	ImplementationJars android.Paths
 
 	// ResourceJars is a list of jars that contain the resources included in the module.
@@ -443,6 +443,30 @@
 	usesLibCompat30OptTag   = makeUsesLibraryDependencyTag(30, true)
 )
 
+// A list of tags for deps used for compiling a module.
+// Any dependency tags that modifies the following properties of `deps` in `Module.collectDeps` should be
+// added to this list:
+// - bootClasspath
+// - classpath
+// - java9Classpath
+// - systemModules
+// - kotlin deps...
+var (
+	compileDependencyTags = []blueprint.DependencyTag{
+		sdkLibTag,
+		libTag,
+		staticLibTag,
+		bootClasspathTag,
+		systemModulesTag,
+		java9LibTag,
+		kotlinStdlibTag,
+		kotlinAnnotationsTag,
+		kotlinPluginTag,
+		syspropPublicStubDepTag,
+		instrumentationForTag,
+	}
+)
+
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
 	return depTag == libTag || depTag == sdkLibTag
 }
@@ -2015,12 +2039,17 @@
 	// List of aconfig_declarations module names that the stubs generated in this module
 	// depend on.
 	Aconfig_declarations []string
+
+	// List of hard coded filegroups containing Metalava config files that are passed to every
+	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
+	ConfigFiles []string `android:"path" blueprint:"mutated"`
 }
 
 func ApiLibraryFactory() android.Module {
 	module := &ApiLibrary{}
-	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.AddProperties(&module.properties)
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.initModuleAndImport(module)
 	android.InitDefaultableModule(module)
 	return module
@@ -2036,7 +2065,7 @@
 
 func metalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
 	srcs android.Paths, homeDir android.WritablePath,
-	classpath android.Paths) *android.RuleBuilderCommand {
+	classpath android.Paths, configFiles android.Paths) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -2075,6 +2104,8 @@
 		FlagWithArg("--hide ", "InvalidNullabilityOverride").
 		FlagWithArg("--hide ", "ChangedDefault")
 
+	addMetalavaConfigFilesToCmd(cmd, configFiles)
+
 	if len(classpath) == 0 {
 		// The main purpose of the `--api-class-resolution api` option is to force metalava to ignore
 		// classes on the classpath when an API file contains missing classes. However, as this command
@@ -2286,7 +2317,9 @@
 		ctx.ModuleErrorf("Error: %s has an empty api file.", ctx.ModuleName())
 	}
 
-	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, systemModulesPaths)
+	configFiles := android.PathsForModuleSrc(ctx, al.properties.ConfigFiles)
+
+	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, systemModulesPaths, configFiles)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
@@ -2386,10 +2419,35 @@
 	return android.FutureApiLevel
 }
 
+func (al *ApiLibrary) IDEInfo(i *android.IdeInfo) {
+	i.Deps = append(i.Deps, al.ideDeps()...)
+	i.Libs = append(i.Libs, al.properties.Libs...)
+	i.Static_libs = append(i.Static_libs, al.properties.Static_libs...)
+	i.SrcJars = append(i.SrcJars, al.stubsSrcJar.String())
+}
+
+// deps of java_api_library for module_bp_java_deps.json
+func (al *ApiLibrary) ideDeps() []string {
+	ret := []string{}
+	ret = append(ret, al.properties.Libs...)
+	ret = append(ret, al.properties.Static_libs...)
+	if al.properties.System_modules != nil {
+		ret = append(ret, proptools.String(al.properties.System_modules))
+	}
+	if al.properties.Full_api_surface_stub != nil {
+		ret = append(ret, proptools.String(al.properties.Full_api_surface_stub))
+	}
+	// Other non java_library dependencies like java_api_contribution are ignored for now.
+	return ret
+}
+
 // implement the following interfaces for hiddenapi processing
 var _ hiddenAPIModule = (*ApiLibrary)(nil)
 var _ UsesLibraryDependency = (*ApiLibrary)(nil)
 
+// implement the following interface for IDE completion.
+var _ android.IDEInfo = (*ApiLibrary)(nil)
+
 //
 // Java prebuilts
 //
diff --git a/java/jdeps_test.go b/java/jdeps_test.go
index 874d1d7..e180224 100644
--- a/java/jdeps_test.go
+++ b/java/jdeps_test.go
@@ -22,28 +22,46 @@
 )
 
 func TestCollectJavaLibraryPropertiesAddLibsDeps(t *testing.T) {
-	expected := []string{"Foo", "Bar"}
-	module := LibraryFactory().(*Library)
-	module.properties.Libs = append(module.properties.Libs, expected...)
+	ctx, _ := testJava(t,
+		`
+		java_library {name: "Foo"}
+		java_library {name: "Bar"}
+		java_library {
+			name: "javalib",
+			libs: ["Foo", "Bar"],
+		}
+	`)
+	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
 	dpInfo := &android.IdeInfo{}
 
 	module.IDEInfo(dpInfo)
 
-	if !reflect.DeepEqual(dpInfo.Deps, expected) {
-		t.Errorf("Library.IDEInfo() Deps = %v, want %v", dpInfo.Deps, expected)
+	for _, expected := range []string{"Foo", "Bar"} {
+		if !android.InList(expected, dpInfo.Deps) {
+			t.Errorf("Library.IDEInfo() Deps = %v, %v not found", dpInfo.Deps, expected)
+		}
 	}
 }
 
 func TestCollectJavaLibraryPropertiesAddStaticLibsDeps(t *testing.T) {
-	expected := []string{"Foo", "Bar"}
-	module := LibraryFactory().(*Library)
-	module.properties.Static_libs = append(module.properties.Static_libs, expected...)
+	ctx, _ := testJava(t,
+		`
+		java_library {name: "Foo"}
+		java_library {name: "Bar"}
+		java_library {
+			name: "javalib",
+			static_libs: ["Foo", "Bar"],
+		}
+	`)
+	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
 	dpInfo := &android.IdeInfo{}
 
 	module.IDEInfo(dpInfo)
 
-	if !reflect.DeepEqual(dpInfo.Deps, expected) {
-		t.Errorf("Library.IDEInfo() Deps = %v, want %v", dpInfo.Deps, expected)
+	for _, expected := range []string{"Foo", "Bar"} {
+		if !android.InList(expected, dpInfo.Deps) {
+			t.Errorf("Library.IDEInfo() Deps = %v, %v not found", dpInfo.Deps, expected)
+		}
 	}
 }
 
diff --git a/java/metalava/Android.bp b/java/metalava/Android.bp
new file mode 100644
index 0000000..ccbd191
--- /dev/null
+++ b/java/metalava/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+filegroup {
+    name: "metalava-config-files",
+    srcs: ["*-config.xml"],
+}
diff --git a/java/metalava/OWNERS b/java/metalava/OWNERS
new file mode 100644
index 0000000..e8c438e
--- /dev/null
+++ b/java/metalava/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 463936
+
+file:platform/tools/metalava:/OWNERS
diff --git a/java/metalava/main-config.xml b/java/metalava/main-config.xml
new file mode 100644
index 0000000..c61196f
--- /dev/null
+++ b/java/metalava/main-config.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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.
+  -->
+
+<config xmlns="http://www.google.com/tools/metalava/config"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.google.com/tools/metalava/config ../../../../tools/metalava/metalava/src/main/resources/schemas/config.xsd"/>
diff --git a/java/metalava/source-model-selection-config.xml b/java/metalava/source-model-selection-config.xml
new file mode 100644
index 0000000..c61196f
--- /dev/null
+++ b/java/metalava/source-model-selection-config.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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.
+  -->
+
+<config xmlns="http://www.google.com/tools/metalava/config"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.google.com/tools/metalava/config ../../../../tools/metalava/metalava/src/main/resources/schemas/config.xsd"/>
diff --git a/java/ravenwood.go b/java/ravenwood.go
index 908619d..84c285c 100644
--- a/java/ravenwood.go
+++ b/java/ravenwood.go
@@ -33,6 +33,8 @@
 var ravenwoodLibContentTag = dependencyTag{name: "ravenwoodlibcontent"}
 var ravenwoodUtilsTag = dependencyTag{name: "ravenwoodutils"}
 var ravenwoodRuntimeTag = dependencyTag{name: "ravenwoodruntime"}
+var ravenwoodDataTag = dependencyTag{name: "ravenwooddata"}
+var ravenwoodTestResourceApkTag = dependencyTag{name: "ravenwoodtestresapk"}
 
 const ravenwoodUtilsName = "ravenwood-utils"
 const ravenwoodRuntimeName = "ravenwood-runtime"
@@ -53,6 +55,13 @@
 
 type ravenwoodTestProperties struct {
 	Jni_libs []string
+
+	// Specify another android_app module here to copy it to the test directory, so that
+	// the ravenwood test can access it.
+	// TODO: For now, we simply refer to another android_app module and copy it to the
+	// test directory. Eventually, android_ravenwood_test should support all the resource
+	// related properties and build resources from the `res/` directory.
+	Resource_apk *string
 }
 
 type ravenwoodTest struct {
@@ -114,6 +123,11 @@
 	for _, lib := range r.ravenwoodTestProperties.Jni_libs {
 		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
 	}
+
+	// Resources APK
+	if resourceApk := proptools.String(r.ravenwoodTestProperties.Resource_apk); resourceApk != "" {
+		ctx.AddVariationDependencies(nil, ravenwoodTestResourceApkTag, resourceApk)
+	}
 }
 
 func (r *ravenwoodTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -175,6 +189,14 @@
 		installDeps = append(installDeps, installJni)
 	}
 
+	resApkInstallPath := installPath.Join(ctx, "ravenwood-res-apks")
+	if resApk := ctx.GetDirectDepsWithTag(ravenwoodTestResourceApkTag); len(resApk) > 0 {
+		for _, installFile := range resApk[0].FilesToInstall() {
+			installResApk := ctx.InstallFile(resApkInstallPath, "ravenwood-res.apk", installFile)
+			installDeps = append(installDeps, installResApk)
+		}
+	}
+
 	// Install our JAR with all dependencies
 	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
 }
@@ -198,6 +220,9 @@
 	Libs []string
 
 	Jni_libs []string
+
+	// We use this to copy framework-res.apk to the ravenwood runtime directory.
+	Data []string
 }
 
 type ravenwoodLibgroup struct {
@@ -236,6 +261,9 @@
 	for _, lib := range r.ravenwoodLibgroupProperties.Jni_libs {
 		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
 	}
+	for _, data := range r.ravenwoodLibgroupProperties.Data {
+		ctx.AddVariationDependencies(nil, ravenwoodDataTag, data)
+	}
 }
 
 func (r *ravenwoodLibgroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -266,6 +294,13 @@
 		ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path)
 	}
 
+	dataInstallPath := installPath.Join(ctx, "ravenwood-data")
+	for _, data := range r.ravenwoodLibgroupProperties.Data {
+		libModule := ctx.GetDirectDepWithTag(data, ravenwoodDataTag)
+		file := android.OutputFileForModule(ctx, libModule, "")
+		ctx.InstallFile(dataInstallPath, file.Base(), file)
+	}
+
 	// Normal build should perform install steps
 	ctx.Phony(r.BaseModuleName(), android.PathForPhony(ctx, r.BaseModuleName()+"-install"))
 }
diff --git a/java/ravenwood_test.go b/java/ravenwood_test.go
index 5961264..d26db93 100644
--- a/java/ravenwood_test.go
+++ b/java/ravenwood_test.go
@@ -57,6 +57,14 @@
 			name: "framework-rules.ravenwood",
 			srcs: ["Rules.java"],
 		}
+		android_app {
+			name: "app1",
+            sdk_version: "current",
+		}
+		android_app {
+			name: "app2",
+            sdk_version: "current",
+		}
 		android_ravenwood_libgroup {
 			name: "ravenwood-runtime",
 			libs: [
@@ -67,6 +75,9 @@
 				"ravenwood-runtime-jni1",
 				"ravenwood-runtime-jni2",
 			],
+			data: [
+				"app1",
+			],
 		}
 		android_ravenwood_libgroup {
 			name: "ravenwood-utils",
@@ -102,6 +113,7 @@
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni1.so")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/libred.so")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni3.so")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/ravenwood-data/app1.apk")
 	utils := ctx.ModuleForTests("ravenwood-utils", "android_common")
 	utils.Output(installPathPrefix + "/ravenwood-utils/framework-rules.ravenwood.jar")
 }
@@ -143,6 +155,7 @@
 				"jni-lib2",
 				"ravenwood-runtime-jni2",
 			],
+			resource_apk: "app2",
 			sdk_version: "test_current",
 		}
 	`)
@@ -169,6 +182,7 @@
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/jni-lib1.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libblue.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libpink.so")
+	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-res-apks/ravenwood-res.apk")
 
 	// ravenwood-runtime*.so are included in the runtime, so it shouldn't be emitted.
 	for _, o := range module.AllOutputs() {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 1eb7ab8..3931456 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1516,6 +1516,13 @@
 
 // Add other dependencies as normal.
 func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// If the module does not create an implementation library or defaults to stubs,
+	// mark the top level sdk library as stubs module as the module will provide stubs via
+	// "magic" when listed as a dependency in the Android.bp files.
+	notCreateImplLib := proptools.Bool(module.sdkLibraryProperties.Api_only)
+	preferStubs := proptools.Bool(module.sdkLibraryProperties.Default_to_stubs)
+	module.properties.Is_stubs_module = proptools.BoolPtr(notCreateImplLib || preferStubs)
+
 	var missingApiModules []string
 	for _, apiScope := range module.getGeneratedApiScopes(ctx) {
 		if apiScope.unstable {
diff --git a/java/testing.go b/java/testing.go
index 5ae326d..7a42e4c 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -52,6 +52,8 @@
 	android.MockFS{
 		// Needed for linter used by java_library.
 		"build/soong/java/lint_defaults.txt": nil,
+		// Needed for java components that invoke Metalava.
+		"build/soong/java/metalava/Android.bp": []byte(`filegroup {name: "metalava-config-files"}`),
 		// Needed for apps that do not provide their own.
 		"build/make/target/product/security": nil,
 		// Required to generate Java used-by API coverage
diff --git a/phony/phony.go b/phony/phony.go
index b421176..807b95b 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -20,6 +20,8 @@
 	"strings"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -88,14 +90,15 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 
-	properties PhonyProperties
+	phonyDepsModuleNames []string
+	properties           PhonyProperties
 }
 
 type PhonyProperties struct {
 	// The Phony_deps is the set of all dependencies for this target,
 	// and it can function similarly to .PHONY in a makefile.
 	// Additionally, dependencies within it can even include genrule.
-	Phony_deps []string
+	Phony_deps proptools.Configurable[[]string]
 }
 
 // The phony_rule provides functionality similar to the .PHONY in a makefile.
@@ -109,13 +112,14 @@
 }
 
 func (p *PhonyRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.phonyDepsModuleNames = p.properties.Phony_deps.GetOrDefault(ctx, nil)
 }
 
 func (p *PhonyRule) AndroidMk() android.AndroidMkData {
 	return android.AndroidMkData{
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-			if len(p.properties.Phony_deps) > 0 {
-				depModulesStr := strings.Join(p.properties.Phony_deps, " ")
+			if len(p.phonyDepsModuleNames) > 0 {
+				depModulesStr := strings.Join(p.phonyDepsModuleNames, " ")
 				fmt.Fprintln(w, ".PHONY:", name)
 				fmt.Fprintln(w, name, ":", depModulesStr)
 			}
diff --git a/python/python.go b/python/python.go
index 1ee533f..8726f02 100644
--- a/python/python.go
+++ b/python/python.go
@@ -38,7 +38,7 @@
 
 // Exported to support other packages using Python modules in tests.
 func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
+	ctx.Transition("python_version", &versionSplitTransitionMutator{})
 }
 
 // the version-specific properties that apply to python modules.
@@ -245,7 +245,6 @@
 	protoExt                 = ".proto"
 	pyVersion2               = "PY2"
 	pyVersion3               = "PY3"
-	pyVersion2And3           = "PY2ANDPY3"
 	internalPath             = "internal"
 )
 
@@ -253,46 +252,68 @@
 	getBaseProperties() *BaseProperties
 }
 
-// versionSplitMutator creates version variants for modules and appends the version-specific
-// properties for a given variant to the properties in the variant module
-func versionSplitMutator() func(android.BottomUpMutatorContext) {
-	return func(mctx android.BottomUpMutatorContext) {
-		if base, ok := mctx.Module().(basePropertiesProvider); ok {
-			props := base.getBaseProperties()
-			var versionNames []string
-			// collect version specific properties, so that we can merge version-specific properties
-			// into the module's overall properties
-			var versionProps []VersionProperties
-			// PY3 is first so that we alias the PY3 variant rather than PY2 if both
-			// are available
-			if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
-				versionNames = append(versionNames, pyVersion3)
-				versionProps = append(versionProps, props.Version.Py3)
+type versionSplitTransitionMutator struct{}
+
+func (versionSplitTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if base, ok := ctx.Module().(basePropertiesProvider); ok {
+		props := base.getBaseProperties()
+		var variants []string
+		// PY3 is first so that we alias the PY3 variant rather than PY2 if both
+		// are available
+		if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
+			variants = append(variants, pyVersion3)
+		}
+		if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
+			if !ctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
+				ctx.ModuleName() != "py2-cmd" &&
+				ctx.ModuleName() != "py2-stdlib" {
+				ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
 			}
-			if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
-				if !mctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
-					mctx.ModuleName() != "py2-cmd" &&
-					mctx.ModuleName() != "py2-stdlib" {
-					mctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
-				}
-				versionNames = append(versionNames, pyVersion2)
-				versionProps = append(versionProps, props.Version.Py2)
-			}
-			modules := mctx.CreateLocalVariations(versionNames...)
-			// Alias module to the first variant
-			if len(versionNames) > 0 {
-				mctx.AliasVariation(versionNames[0])
-			}
-			for i, v := range versionNames {
-				// set the actual version for Python module.
-				newProps := modules[i].(basePropertiesProvider).getBaseProperties()
-				newProps.Actual_version = v
-				// append versioned properties for the Python module to the overall properties
-				err := proptools.AppendMatchingProperties([]interface{}{newProps}, &versionProps[i], nil)
-				if err != nil {
-					panic(err)
-				}
-			}
+			variants = append(variants, pyVersion2)
+		}
+		return variants
+	}
+	return []string{""}
+}
+
+func (versionSplitTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (versionSplitTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if incomingVariation != "" {
+		return incomingVariation
+	}
+	if base, ok := ctx.Module().(basePropertiesProvider); ok {
+		props := base.getBaseProperties()
+		if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
+			return pyVersion3
+		} else {
+			return pyVersion2
+		}
+	}
+
+	return ""
+}
+
+func (versionSplitTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if variation == "" {
+		return
+	}
+	if base, ok := ctx.Module().(basePropertiesProvider); ok {
+		props := base.getBaseProperties()
+		props.Actual_version = variation
+
+		var versionProps *VersionProperties
+		if variation == pyVersion3 {
+			versionProps = &props.Version.Py3
+		} else if variation == pyVersion2 {
+			versionProps = &props.Version.Py2
+		}
+
+		err := proptools.AppendMatchingProperties([]interface{}{props}, versionProps, nil)
+		if err != nil {
+			panic(err)
 		}
 	}
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index dbc3697..d412ea1 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-r522817"
+	bindgenClangVersion = "clang-r530567"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -285,7 +285,7 @@
 	if isCpp {
 		cflags = append(cflags, "-x c++")
 		// Add any C++ only flags.
-		cflags = append(cflags, esc(b.ClangProperties.Cppflags)...)
+		cflags = append(cflags, esc(b.ClangProperties.Cppflags.GetOrDefault(ctx, nil))...)
 	} else {
 		cflags = append(cflags, "-x c")
 	}
diff --git a/rust/coverage.go b/rust/coverage.go
index e0e919c..91a7806 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -47,7 +47,7 @@
 
 		// no_std modules are missing libprofiler_builtins which provides coverage, so we need to add it as a dependency.
 		if rustModule, ok := ctx.Module().(*Module); ok && rustModule.compiler.noStdlibs() {
-			ctx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}, {Mutator: "link", Variation: ""}}, rlibDepTag, ProfilerBuiltins)
+			ctx.AddVariationDependencies([]blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}, rlibDepTag, ProfilerBuiltins)
 		}
 	}
 
diff --git a/rust/library.go b/rust/library.go
index ba73f27..50d5a72 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -20,6 +20,8 @@
 	"regexp"
 	"strings"
 
+	"github.com/google/blueprint"
+
 	"android/soong/android"
 	"android/soong/cc"
 )
@@ -692,31 +694,28 @@
 	}
 }
 
-// LibraryMutator mutates the libraries into variants according to the
-// build{Rlib,Dylib} attributes.
-func LibraryMutator(mctx android.BottomUpMutatorContext) {
-	// Only mutate on Rust libraries.
-	m, ok := mctx.Module().(*Module)
+type libraryTransitionMutator struct{}
+
+func (libraryTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	m, ok := ctx.Module().(*Module)
 	if !ok || m.compiler == nil {
-		return
+		return []string{""}
 	}
 	library, ok := m.compiler.(libraryInterface)
 	if !ok {
-		return
+		return []string{""}
 	}
 
 	// Don't produce rlib/dylib/source variants for shared or static variants
 	if library.shared() || library.static() {
-		return
+		return []string{""}
 	}
 
 	var variants []string
 	// The source variant is used for SourceProvider modules. The other variants (i.e. rlib and dylib)
 	// depend on this variant. It must be the first variant to be declared.
-	sourceVariant := false
 	if m.sourceProvider != nil {
-		variants = append(variants, "source")
-		sourceVariant = true
+		variants = append(variants, sourceVariation)
 	}
 	if library.buildRlib() {
 		variants = append(variants, rlibVariation)
@@ -726,92 +725,134 @@
 	}
 
 	if len(variants) == 0 {
-		return
+		return []string{""}
 	}
-	modules := mctx.CreateLocalVariations(variants...)
 
-	// The order of the variations (modules) matches the variant names provided. Iterate
-	// through the new variation modules and set their mutated properties.
-	var emptyVariant = false
-	var rlibVariant = false
-	for i, v := range modules {
-		switch variants[i] {
-		case rlibVariation:
-			v.(*Module).compiler.(libraryInterface).setRlib()
-			rlibVariant = true
-		case dylibVariation:
-			v.(*Module).compiler.(libraryInterface).setDylib()
-			if v.(*Module).ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
-				// TODO(b/165791368)
-				// Disable dylib Vendor Ramdisk variations until we support these.
-				v.(*Module).Disable()
-			}
+	return variants
+}
 
-		case "source":
-			v.(*Module).compiler.(libraryInterface).setSource()
-			// The source variant does not produce any library.
-			// Disable the compilation steps.
-			v.(*Module).compiler.SetDisabled()
-		case "":
-			emptyVariant = true
+func (libraryTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (libraryTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	m, ok := ctx.Module().(*Module)
+	if !ok || m.compiler == nil {
+		return ""
+	}
+	library, ok := m.compiler.(libraryInterface)
+	if !ok {
+		return ""
+	}
+
+	if incomingVariation == "" {
+		if m.sourceProvider != nil {
+			return sourceVariation
+		}
+		if library.shared() {
+			return ""
+		}
+		if library.buildRlib() {
+			return rlibVariation
+		}
+		if library.buildDylib() {
+			return dylibVariation
 		}
 	}
+	return incomingVariation
+}
 
-	if rlibVariant && library.isFFILibrary() {
-		// If an rlib variant is set and this is an FFI library, make it the
-		// default variant so CC can link against it appropriately.
-		mctx.AliasVariation(rlibVariation)
-	} else if emptyVariant {
-		// If there's an empty variant, alias it so it is the default variant
-		mctx.AliasVariation("")
+func (libraryTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	m, ok := ctx.Module().(*Module)
+	if !ok || m.compiler == nil {
+		return
+	}
+	library, ok := m.compiler.(libraryInterface)
+	if !ok {
+		return
+	}
+
+	switch variation {
+	case rlibVariation:
+		library.setRlib()
+	case dylibVariation:
+		library.setDylib()
+		if m.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+			// TODO(b/165791368)
+			// Disable dylib Vendor Ramdisk variations until we support these.
+			m.Disable()
+		}
+
+	case sourceVariation:
+		library.setSource()
+		// The source variant does not produce any library.
+		// Disable the compilation steps.
+		m.compiler.SetDisabled()
 	}
 
 	// If a source variant is created, add an inter-variant dependency
 	// between the other variants and the source variant.
-	if sourceVariant {
-		sv := modules[0]
-		for _, v := range modules[1:] {
-			if !v.Enabled(mctx) {
-				continue
-			}
-			mctx.AddInterVariantDependency(sourceDepTag, v, sv)
-		}
-		// Alias the source variation so it can be named directly in "srcs" properties.
-		mctx.AliasVariation("source")
+	if m.sourceProvider != nil && variation != sourceVariation {
+		ctx.AddVariationDependencies(
+			[]blueprint.Variation{
+				{"rust_libraries", sourceVariation},
+			},
+			sourceDepTag, ctx.ModuleName())
 	}
 }
 
-func LibstdMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
-		switch library := m.compiler.(type) {
-		case libraryInterface:
-			// Only create a variant if a library is actually being built.
-			if library.rlib() && !library.sysroot() {
-				// If this is a rust_ffi variant it only needs rlib-std
-				if library.isFFILibrary() {
-					variants := []string{"rlib-std"}
-					modules := mctx.CreateLocalVariations(variants...)
-					rlib := modules[0].(*Module)
-					rlib.compiler.(libraryInterface).setRlibStd()
-					rlib.Properties.RustSubName += RlibStdlibSuffix
-					mctx.AliasVariation("rlib-std")
-				} else {
-					variants := []string{"rlib-std", "dylib-std"}
-					modules := mctx.CreateLocalVariations(variants...)
+type libstdTransitionMutator struct{}
 
-					rlib := modules[0].(*Module)
-					dylib := modules[1].(*Module)
-					rlib.compiler.(libraryInterface).setRlibStd()
-					dylib.compiler.(libraryInterface).setDylibStd()
-					if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
-						// TODO(b/165791368)
-						// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
-						// variants are properly supported.
-						dylib.Disable()
-					}
-					rlib.Properties.RustSubName += RlibStdlibSuffix
+func (libstdTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+		// Only create a variant if a library is actually being built.
+		if library, ok := m.compiler.(libraryInterface); ok {
+			if library.rlib() && !library.sysroot() {
+				if library.isFFILibrary() {
+					return []string{"rlib-std"}
+				} else {
+					return []string{"rlib-std", "dylib-std"}
 				}
 			}
 		}
 	}
+	return []string{""}
+}
+
+func (libstdTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (libstdTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+		if library, ok := m.compiler.(libraryInterface); ok {
+			if library.shared() {
+				return ""
+			}
+			if library.rlib() && !library.sysroot() {
+				if incomingVariation != "" {
+					return incomingVariation
+				}
+				return "rlib-std"
+			}
+		}
+	}
+	return ""
+}
+
+func (libstdTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if variation == "rlib-std" {
+		rlib := ctx.Module().(*Module)
+		rlib.compiler.(libraryInterface).setRlibStd()
+		rlib.Properties.RustSubName += RlibStdlibSuffix
+	} else if variation == "dylib-std" {
+		dylib := ctx.Module().(*Module)
+		dylib.compiler.(libraryInterface).setDylibStd()
+		if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+			// TODO(b/165791368)
+			// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
+			// variants are properly supported.
+			dylib.Disable()
+		}
+	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index 9dae75e..3402adc 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -37,20 +37,24 @@
 
 func init() {
 	android.RegisterModuleType("rust_defaults", defaultsFactory)
-	android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
-		ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
-		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
-	})
-	android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
-	})
+	android.PreDepsMutators(registerPreDepsMutators)
+	android.PostDepsMutators(registerPostDepsMutators)
 	pctx.Import("android/soong/android")
 	pctx.Import("android/soong/rust/config")
 	pctx.ImportAs("cc_config", "android/soong/cc/config")
 	android.InitRegistrationContext.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
 }
 
+func registerPreDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.Transition("rust_libraries", &libraryTransitionMutator{})
+	ctx.Transition("rust_stdlinkage", &libstdTransitionMutator{})
+	ctx.BottomUp("rust_begin", BeginMutator).Parallel()
+}
+
+func registerPostDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+}
+
 type Flags struct {
 	GlobalRustFlags []string // Flags that apply globally to rust
 	GlobalLinkFlags []string // Flags that apply globally to linker
@@ -1128,10 +1132,11 @@
 }
 
 var (
-	rlibVariation  = "rlib"
-	dylibVariation = "dylib"
-	rlibAutoDep    = autoDep{variation: rlibVariation, depTag: rlibDepTag}
-	dylibAutoDep   = autoDep{variation: dylibVariation, depTag: dylibDepTag}
+	sourceVariation = "source"
+	rlibVariation   = "rlib"
+	dylibVariation  = "dylib"
+	rlibAutoDep     = autoDep{variation: rlibVariation, depTag: rlibDepTag}
+	dylibAutoDep    = autoDep{variation: dylibVariation, depTag: dylibDepTag}
 )
 
 type autoDeppable interface {
@@ -1613,7 +1618,6 @@
 	}
 
 	rlibDepVariations := commonDepVariations
-	rlibDepVariations = append(rlibDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
 
 	if lib, ok := mod.compiler.(libraryInterface); !ok || !lib.sysroot() {
 		rlibDepVariations = append(rlibDepVariations,
@@ -1629,7 +1633,6 @@
 
 	// dylibs
 	dylibDepVariations := append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: dylibVariation})
-	dylibDepVariations = append(dylibDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
 
 	for _, lib := range deps.Dylibs {
 		actx.AddVariationDependencies(dylibDepVariations, dylibDepTag, lib)
@@ -1650,7 +1653,6 @@
 					// otherwise select the rlib variant.
 					autoDepVariations := append(commonDepVariations,
 						blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation})
-					autoDepVariations = append(autoDepVariations, blueprint.Variation{Mutator: "link", Variation: ""})
 					if actx.OtherModuleDependencyVariantExists(autoDepVariations, lib) {
 						actx.AddVariationDependencies(autoDepVariations, autoDep.depTag, lib)
 
@@ -1664,8 +1666,7 @@
 		} else if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
 			for _, lib := range deps.Rustlibs {
 				srcProviderVariations := append(commonDepVariations,
-					blueprint.Variation{Mutator: "rust_libraries", Variation: "source"})
-				srcProviderVariations = append(srcProviderVariations, blueprint.Variation{Mutator: "link", Variation: ""})
+					blueprint.Variation{Mutator: "rust_libraries", Variation: sourceVariation})
 
 				// Only add rustlib dependencies if they're source providers themselves.
 				// This is used to track which crate names need to be added to the source generated
@@ -1681,7 +1682,7 @@
 	if deps.Stdlibs != nil {
 		if mod.compiler.stdLinkage(ctx) == RlibLinkage {
 			for _, lib := range deps.Stdlibs {
-				actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}, {Mutator: "link", Variation: ""}}...),
+				actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}...),
 					rlibDepTag, lib)
 			}
 		} else {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 0d005d0..eeedf3f 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -60,6 +60,7 @@
 // testRust returns a TestContext in which a basic environment has been setup.
 // This environment contains a few mocked files. See rustMockedFiles for the list of these files.
 func testRust(t *testing.T, bp string) *android.TestContext {
+	t.Helper()
 	skipTestIfOsNotSupported(t)
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
diff --git a/rust/testing.go b/rust/testing.go
index 6ee49a9..32cc823 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -200,15 +200,8 @@
 	ctx.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	ctx.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
 	ctx.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
-	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		// rust mutators
-		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
-		ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
-		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
-	})
+	ctx.PreDepsMutators(registerPreDepsMutators)
 	ctx.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
 	ctx.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
-	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
-	})
+	ctx.PostDepsMutators(registerPostDepsMutators)
 }
diff --git a/scripts/buildinfo.py b/scripts/buildinfo.py
index db99209..8a24b63 100755
--- a/scripts/buildinfo.py
+++ b/scripts/buildinfo.py
@@ -132,7 +132,7 @@
 
       # Dev. branches should have DISPLAY_BUILD_NUMBER set
       if option.display_build_number:
-        print(f"ro.build.display.id?={option.build_id} {build_number} {option.build_keys}")
+        print(f"ro.build.display.id?={option.build_id}.{build_number} {option.build_keys}")
       else:
         print(f"ro.build.display.id?={option.build_id} {option.build_keys}")
     else:
diff --git a/scripts/check_prebuilt_presigned_apk.py b/scripts/check_prebuilt_presigned_apk.py
index abedfb7..abab2e1 100755
--- a/scripts/check_prebuilt_presigned_apk.py
+++ b/scripts/check_prebuilt_presigned_apk.py
@@ -37,7 +37,7 @@
                     sys.exit(args.apk + ': Contains compressed JNI libraries')
                 return True
             # It's ok for non-privileged apps to have compressed dex files, see go/gms-uncompressed-jni-slides
-            if args.privileged:
+            if args.privileged and args.uncompress_priv_app_dex:
                 if info.filename.endswith('.dex') and info.compress_type != zipfile.ZIP_STORED:
                     if fail:
                         sys.exit(args.apk + ': Contains compressed dex files and is privileged')
@@ -52,6 +52,7 @@
     parser.add_argument('--skip-preprocessed-apk-checks', action = 'store_true', help = "the value of the soong property with the same name")
     parser.add_argument('--preprocessed', action = 'store_true', help = "the value of the soong property with the same name")
     parser.add_argument('--privileged', action = 'store_true', help = "the value of the soong property with the same name")
+    parser.add_argument('--uncompress-priv-app-dex', action = 'store_true', help = "the value of the product variable with the same name")
     parser.add_argument('apk', help = "the apk to check")
     parser.add_argument('stampfile', help = "a file to touch if successful")
     args = parser.parse_args()
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index 799e00b..9ea56cb 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -188,7 +188,7 @@
 
     # Dev. branches should have DISPLAY_BUILD_NUMBER set
     if config["DisplayBuildNumber"]:
-      print(f"ro.build.display.id?={config['BuildId']} {config['BuildNumber']} {config['BuildKeys']}")
+      print(f"ro.build.display.id?={config['BuildId']}.{config['BuildNumber']} {config['BuildKeys']}")
     else:
       print(f"ro.build.display.id?={config['BuildId']} {config['BuildKeys']}")
   else:
diff --git a/scripts/run-ckati.sh b/scripts/run-ckati.sh
index b670c8a..70f5a7a 100755
--- a/scripts/run-ckati.sh
+++ b/scripts/run-ckati.sh
@@ -73,12 +73,12 @@
   --writable out/ \
   -f build/make/core/main.mk \
   "${tracing[@]}" \
-  ANDROID_JAVA_HOME=prebuilts/jdk/jdk17/linux-x86 \
+  ANDROID_JAVA_HOME=prebuilts/jdk/jdk21/linux-x86 \
   ASAN_SYMBOLIZER_PATH=$PWD/prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-symbolizer \
   BUILD_DATETIME_FILE="$timestamp_file" \
   BUILD_HOSTNAME=$(hostname) \
   BUILD_USERNAME="$USER" \
-  JAVA_HOME=$PWD/prebuilts/jdk/jdk17/linux-x86 \
+  JAVA_HOME=$PWD/prebuilts/jdk/jdk21/linux-x86 \
   KATI_PACKAGE_MK_DIR="{$out}/target/product/${target_device}/CONFIG/kati_packaging" \
   OUT_DIR="$out" \
   PATH="$PWD/prebuilts/build-tools/path/linux-x86:$PWD/${out}/.path" \
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 5d76930..25839b8 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -2205,6 +2205,7 @@
             "3",
             "current",
         ],
+        symbol_file: "stubslib.map.txt",
     },
     arch: {
         arm64: {
@@ -2268,6 +2269,7 @@
             "3",
             "current",
         ],
+        symbol_file: "stubslib.map.txt",
     },
     target: {
         host: {
diff --git a/snapshot/Android.bp b/snapshot/Android.bp
index 6cb318e..c384f8a 100644
--- a/snapshot/Android.bp
+++ b/snapshot/Android.bp
@@ -14,7 +14,6 @@
     // Source file name convention is to include _snapshot as a
     // file suffix for files that are generating snapshots.
     srcs: [
-        "host_fake_snapshot.go",
         "host_snapshot.go",
         "snapshot_base.go",
         "util.go",
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
deleted file mode 100644
index 278247e..0000000
--- a/snapshot/host_fake_snapshot.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2021 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.
-
-package snapshot
-
-import (
-	"encoding/json"
-	"path/filepath"
-
-	"android/soong/android"
-)
-
-// The host_snapshot module creates a snapshot of host tools to be used
-// in a minimal source tree.   In order to create the host_snapshot the
-// user must explicitly list the modules to be included.  The
-// host-fake-snapshot, defined in this file, is a utility to help determine
-// which host modules are being used in the minimal source tree.
-//
-// The host-fake-snapshot is designed to run in a full source tree and
-// will result in a snapshot that contains an empty file for each host
-// tool found in the tree.  The fake snapshot is only used to determine
-// the host modules that the minimal source tree depends on, hence the
-// snapshot uses an empty file for each module and saves on having to
-// actually build any tool to generate the snapshot.  The fake snapshot
-// is compatible with an actual host_snapshot and is installed into a
-// minimal source tree via the development/vendor_snapshot/update.py
-// script.
-//
-// After generating the fake snapshot and installing into the minimal
-// source tree, the dependent modules are determined via the
-// development/vendor_snapshot/update.py script (see script for more
-// information).  These modules are then used to define the actual
-// host_snapshot to be used.  This is a similar process to the other
-// snapshots (vendor, recovery,...)
-//
-// Example
-//
-// Full source tree:
-//   1/ Generate fake host snapshot
-//
-// Minimal source tree:
-//   2/ Install the fake host snapshot
-//   3/ List the host modules used from the snapshot
-//   4/ Remove fake host snapshot
-//
-// Full source tree:
-//   4/ Create host_snapshot with modules identified in step 3
-//
-// Minimal source tree:
-//   5/ Install host snapshot
-//   6/ Build
-//
-// The host-fake-snapshot is a singleton module, that will be built
-// if HOST_FAKE_SNAPSHOT_ENABLE=true.
-
-func init() {
-	registerHostSnapshotComponents(android.InitRegistrationContext)
-}
-
-// Add prebuilt information to snapshot data
-type hostSnapshotFakeJsonFlags struct {
-	SnapshotJsonFlags
-	Prebuilt bool `json:",omitempty"`
-}
-
-func registerHostSnapshotComponents(ctx android.RegistrationContext) {
-	ctx.RegisterParallelSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton)
-}
-
-type hostFakeSingleton struct {
-	snapshotDir string
-	zipFile     android.OptionalPath
-}
-
-func (c *hostFakeSingleton) init() {
-	c.snapshotDir = "host-fake-snapshot"
-
-}
-func HostToolsFakeAndroidSingleton() android.Singleton {
-	singleton := &hostFakeSingleton{}
-	singleton.init()
-	return singleton
-}
-
-func (c *hostFakeSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !ctx.DeviceConfig().HostFakeSnapshotEnabled() {
-		return
-	}
-	// Find all host binary modules add 'fake' versions to snapshot
-	var outputs android.Paths
-	seen := make(map[string]bool)
-	var jsonData []hostSnapshotFakeJsonFlags
-	prebuilts := make(map[string]bool)
-
-	ctx.VisitAllModules(func(module android.Module) {
-		if module.Target().Os != ctx.Config().BuildOSTarget.Os {
-			return
-		}
-		if module.Target().Arch.ArchType != ctx.Config().BuildOSTarget.Arch.ArchType {
-			return
-		}
-
-		if android.IsModulePrebuilt(module) {
-			// Add non-prebuilt module name to map of prebuilts
-			prebuilts[android.RemoveOptionalPrebuiltPrefix(module.Name())] = true
-			return
-		}
-		if !module.Enabled(ctx) || module.IsHideFromMake() {
-			return
-		}
-		apexInfo, _ := android.SingletonModuleProvider(ctx, module, android.ApexInfoProvider)
-		if !apexInfo.IsForPlatform() {
-			return
-		}
-		path := hostToolPath(module)
-		if path.Valid() && path.String() != "" {
-			outFile := filepath.Join(c.snapshotDir, path.String())
-			if !seen[outFile] {
-				seen[outFile] = true
-				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
-				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(ctx, module), false})
-			}
-		}
-	})
-	// Update any module prebuilt information
-	for idx := range jsonData {
-		if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
-			// Prebuilt exists for this module
-			jsonData[idx].Prebuilt = true
-		}
-	}
-	marsh, err := json.Marshal(jsonData)
-	if err != nil {
-		ctx.Errorf("host fake snapshot json marshal failure: %#v", err)
-		return
-	}
-	outputs = append(outputs, WriteStringToFileRule(ctx, string(marsh), filepath.Join(c.snapshotDir, "host_snapshot.json")))
-	c.zipFile = zipSnapshot(ctx, c.snapshotDir, c.snapshotDir, outputs)
-
-}
-func (c *hostFakeSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if !c.zipFile.Valid() {
-		return
-	}
-	ctx.Phony(
-		"host-fake-snapshot",
-		c.zipFile.Path())
-
-	ctx.DistForGoal(
-		"host-fake-snapshot",
-		c.zipFile.Path())
-
-}
diff --git a/snapshot/host_test.go b/snapshot/host_test.go
index ab9fedd..c68fdaf 100644
--- a/snapshot/host_test.go
+++ b/snapshot/host_test.go
@@ -107,17 +107,6 @@
 var prepareForFakeHostTest = android.GroupFixturePreparers(
 	prepareForHostTest,
 	android.FixtureWithRootAndroidBp(hostTestBp),
-	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-		registerHostSnapshotComponents(ctx)
-	}),
-)
-
-// Prepare for fake host snapshot test enabled
-var prepareForFakeHostTestEnabled = android.GroupFixturePreparers(
-	prepareForFakeHostTest,
-	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-		variables.HostFakeSnapshotEnabled = true
-	}),
 )
 
 // Validate that a hostSnapshot object is created containing zip files and JSON file
@@ -140,31 +129,3 @@
 
 	}
 }
-
-// Validate fake host snapshot contains binary modules as well as the JSON meta file
-func TestFakeHostSnapshotEnable(t *testing.T) {
-	result := prepareForFakeHostTestEnabled.RunTest(t)
-	t.Helper()
-	bins := []string{"foo", "bar"}
-	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
-	if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", "host_snapshot.json")).Rule == nil {
-		t.Error("Manifest file not found")
-	}
-	for _, bin := range bins {
-		if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", hostTestBinOut(bin))).Rule == nil {
-			t.Error("Binary file ", bin, "not found")
-		}
-
-	}
-}
-
-// Validate not fake host snapshot if HostFakeSnapshotEnabled has not been set to true
-func TestFakeHostSnapshotDisable(t *testing.T) {
-	result := prepareForFakeHostTest.RunTest(t)
-	t.Helper()
-	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
-	if len(ctx.AllOutputs()) != 0 {
-		t.Error("Fake host snapshot not empty when disabled")
-	}
-
-}
diff --git a/tests/sbom_test.sh b/tests/sbom_test.sh
index 8dc1630..794003d 100755
--- a/tests/sbom_test.sh
+++ b/tests/sbom_test.sh
@@ -70,13 +70,14 @@
   # m droid, build sbom later in case additional dependencies might be built and included in partition images.
   run_soong "${out_dir}" "droid dump.erofs lz4"
 
+  soong_sbom_out=$out_dir/soong/sbom/$target_product
   product_out=$out_dir/target/product/vsoc_x86_64
   sbom_test=$product_out/sbom_test
   mkdir -p $sbom_test
   cp $product_out/*.img $sbom_test
 
-  # m sbom
-  run_soong "${out_dir}" sbom
+  # m sbom soong-sbom
+  run_soong "${out_dir}" "sbom soong-sbom"
 
   # Generate installed file list from .img files in PRODUCT_OUT
   dump_erofs=$out_dir/host/linux-x86/bin/dump.erofs
@@ -118,6 +119,7 @@
     partition_name=$(basename $f | cut -d. -f1)
     file_list_file="${sbom_test}/sbom-${partition_name}-files.txt"
     files_in_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-spdx.txt"
+    files_in_soong_spdx_file="${sbom_test}/soong-sbom-${partition_name}-files-in-spdx.txt"
     rm "$file_list_file" > /dev/null 2>&1 || true
     all_dirs="/"
     while [ ! -z "$all_dirs" ]; do
@@ -145,6 +147,7 @@
     done
     sort -n -o "$file_list_file" "$file_list_file"
 
+    # Diff the file list from image and file list in SBOM created by Make
     grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' > "$files_in_spdx_file"
     if [ "$partition_name" = "system" ]; then
       # system partition is mounted to /, so include FileName starts with /root/ too.
@@ -154,6 +157,17 @@
 
     echo ============ Diffing files in $f and SBOM
     diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name" ""
+
+    # Diff the file list from image and file list in SBOM created by Soong
+    grep "FileName: /${partition_name}/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: //' > "$files_in_soong_spdx_file"
+        if [ "$partition_name" = "system" ]; then
+          # system partition is mounted to /, so include FileName starts with /root/ too.
+          grep "FileName: /root/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: \/root//' >> "$files_in_soong_spdx_file"
+        fi
+        sort -n -o "$files_in_soong_spdx_file" "$files_in_soong_spdx_file"
+
+        echo ============ Diffing files in $f and SBOM created by Soong
+        diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
   done
 
   RAMDISK_IMAGES="$product_out/ramdisk.img"
@@ -161,6 +175,7 @@
     partition_name=$(basename $f | cut -d. -f1)
     file_list_file="${sbom_test}/sbom-${partition_name}-files.txt"
     files_in_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-spdx.txt"
+    files_in_soong_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-soong-spdx.txt"
     # lz4 decompress $f to stdout
     # cpio list all entries like ls -l
     # grep filter normal files and symlinks
@@ -170,11 +185,19 @@
 
     grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' | sort -n > "$files_in_spdx_file"
 
+    grep "FileName: /${partition_name}/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: //' | sort -n > "$files_in_soong_spdx_file"
+
     echo ============ Diffing files in $f and SBOM
     diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name" ""
+
+    echo ============ Diffing files in $f and SBOM created by Soong
+    diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
   done
 
   verify_package_verification_code "$product_out/sbom.spdx"
+  verify_package_verification_code "$soong_sbom_out/sbom.spdx"
+
+  verify_packages_licenses "$soong_sbom_out/sbom.spdx"
 
   # Teardown
   cleanup "${out_dir}"
@@ -213,6 +236,41 @@
   fi
 }
 
+function verify_packages_licenses {
+  local sbom_file="$1"; shift
+
+  num_of_packages=$(grep 'PackageName:' $sbom_file | wc -l)
+  num_of_declared_licenses=$(grep 'PackageLicenseDeclared:' $sbom_file | wc -l)
+  if [ "$num_of_packages" = "$num_of_declared_licenses" ]
+  then
+    echo "Number of packages with declared license is correct."
+  else
+    echo "Number of packages with declared license is WRONG."
+    exit 1
+  fi
+
+  # PRODUCT and 7 prebuilt packages have "PackageLicenseDeclared: NOASSERTION"
+  # All other packages have declared licenses
+  num_of_packages_with_noassertion_license=$(grep 'PackageLicenseDeclared: NOASSERTION' $sbom_file | wc -l)
+  if [ $num_of_packages_with_noassertion_license = 15 ]
+  then
+    echo "Number of packages with NOASSERTION license is correct."
+  else
+    echo "Number of packages with NOASSERTION license is WRONG."
+    exit 1
+  fi
+
+  num_of_files=$(grep 'FileName:' $sbom_file | wc -l)
+  num_of_concluded_licenses=$(grep 'LicenseConcluded:' $sbom_file | wc -l)
+  if [ "$num_of_files" = "$num_of_concluded_licenses" ]
+  then
+    echo "Number of files with concluded license is correct."
+  else
+    echo "Number of files with concluded license is WRONG."
+    exit 1
+  fi
+}
+
 function test_sbom_unbundled_apex {
   # Setup
   out_dir="$(setup)"
@@ -274,7 +332,7 @@
 
 target_product=aosp_cf_x86_64_phone
 target_release=trunk_staging
-target_build_variant=userdebug
+target_build_variant=eng
 for i in "$@"; do
   case $i in
     TARGET_PRODUCT=*)
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index ee286f6..fcf29c5 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -36,6 +36,7 @@
         "blueprint-bootstrap",
         "blueprint-microfactory",
         "soong-android",
+        "soong-elf",
         "soong-finder",
         "soong-remoteexec",
         "soong-shared",
diff --git a/ui/build/build.go b/ui/build/build.go
index 03d8392..49ac791 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -22,6 +22,7 @@
 	"sync"
 	"text/template"
 
+	"android/soong/elf"
 	"android/soong/ui/metrics"
 )
 
@@ -210,9 +211,38 @@
 	}
 }
 
+func abfsBuildStarted(ctx Context, config Config) {
+	abfsBox := config.PrebuiltBuildTool("abfsbox")
+	cmdArgs := []string{"build-started", "--"}
+	cmdArgs = append(cmdArgs, config.Arguments()...)
+	cmd := Command(ctx, config, "abfsbox", abfsBox, cmdArgs...)
+	cmd.Sandbox = noSandbox
+	cmd.RunAndPrintOrFatal()
+}
+
+func abfsBuildFinished(ctx Context, config Config, finished bool) {
+	var errMsg string
+	if !finished {
+		errMsg = "build was interrupted"
+	}
+	abfsBox := config.PrebuiltBuildTool("abfsbox")
+	cmdArgs := []string{"build-finished", "-e", errMsg, "--"}
+	cmdArgs = append(cmdArgs, config.Arguments()...)
+	cmd := Command(ctx, config, "abfsbox", abfsBox, cmdArgs...)
+	cmd.RunAndPrintOrFatal()
+}
+
 // Build the tree. Various flags in `config` govern which components of
 // the build to run.
 func Build(ctx Context, config Config) {
+	done := false
+	if config.UseABFS() {
+		abfsBuildStarted(ctx, config)
+		defer func() {
+			abfsBuildFinished(ctx, config, done)
+		}()
+	}
+
 	ctx.Verboseln("Starting build with args:", config.Arguments())
 	ctx.Verboseln("Environment:", config.Environment().Environ())
 
@@ -344,11 +374,23 @@
 			installCleanIfNecessary(ctx, config)
 		}
 		runNinjaForBuild(ctx, config)
+		updateBuildIdDir(ctx, config)
 	}
 
 	if what&RunDistActions != 0 {
 		runDistActions(ctx, config)
 	}
+	done = true
+}
+
+func updateBuildIdDir(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunShutdownTool, "update_build_id_dir")
+	defer ctx.EndTrace()
+
+	symbolsDir := filepath.Join(config.ProductOut(), "symbols")
+	if err := elf.UpdateBuildIdDir(symbolsDir); err != nil {
+		ctx.Printf("failed to update %s/.build-id: %v", symbolsDir, err)
+	}
 }
 
 func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int {
diff --git a/ui/build/config.go b/ui/build/config.go
index 2470f84..631b76f 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -1270,9 +1270,25 @@
 	if !c.StubbyExists() && strings.Contains(authType, "use_google_prod_creds") {
 		return false
 	}
+	if c.UseABFS() {
+		return false
+	}
 	return true
 }
 
+func (c *configImpl) UseABFS() bool {
+	if v, ok := c.environ.Get("NO_ABFS"); ok {
+		v = strings.ToLower(strings.TrimSpace(v))
+		if v == "true" || v == "1" {
+			return false
+		}
+	}
+
+	abfsBox := c.PrebuiltBuildTool("abfsbox")
+	err := exec.Command(abfsBox, "hash", srcDirFileCheck).Run()
+	return err == nil
+}
+
 func (c *configImpl) UseRBE() bool {
 	// These alternate modes of running Soong do not use RBE / reclient.
 	if c.Queryview() || c.JsonModuleGraph() {
@@ -1585,6 +1601,23 @@
 	}
 }
 
+func (c *configImpl) KatiBin() string {
+	binName := "ckati"
+	if c.UseABFS() {
+		binName = "ckati-wrap"
+	}
+
+	return c.PrebuiltBuildTool(binName)
+}
+
+func (c *configImpl) NinjaBin() string {
+	binName := "ninja"
+	if c.UseABFS() {
+		binName = "ninjago"
+	}
+	return c.PrebuiltBuildTool(binName)
+}
+
 func (c *configImpl) PrebuiltBuildTool(name string) string {
 	if v, ok := c.environ.Get("SANITIZE_HOST"); ok {
 		if sanitize := strings.Fields(v); inList("address", sanitize) {
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index b1222fe..b42edb0 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -22,6 +22,7 @@
 	"os"
 	"path/filepath"
 	"reflect"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -1043,12 +1044,13 @@
 			},
 		},
 		{
+			// RBE is only supported on linux.
 			name:    "use rbe",
 			environ: Environment{"USE_RBE=1"},
 			expectedBuildConfig: &smpb.BuildConfig{
 				ForceUseGoma:          proto.Bool(false),
 				UseGoma:               proto.Bool(false),
-				UseRbe:                proto.Bool(true),
+				UseRbe:                proto.Bool(runtime.GOOS == "linux"),
 				NinjaWeightListSource: smpb.BuildConfig_NOT_USED.Enum(),
 			},
 		},
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index e77df44..5df3a95 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -93,7 +93,7 @@
 	defer tool.Finish()
 
 	cmd := Command(ctx, config, "dumpvars",
-		config.PrebuiltBuildTool("ckati"),
+		config.KatiBin(),
 		"-f", "build/make/core/config.mk",
 		"--color_warnings",
 		"--kati_stats",
diff --git a/ui/build/kati.go b/ui/build/kati.go
index d599c99..a0efd2c 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -84,7 +84,7 @@
 // arguments, and a custom function closure to mutate the environment Kati runs
 // in.
 func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {
-	executable := config.PrebuiltBuildTool("ckati")
+	executable := config.KatiBin()
 	// cKati arguments.
 	args = append([]string{
 		// Instead of executing commands directly, generate a Ninja file.
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 1935e72..b5e74b4 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -49,7 +49,7 @@
 	nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
 	defer nr.Close()
 
-	executable := config.PrebuiltBuildTool("ninja")
+	executable := config.NinjaBin()
 	args := []string{
 		"-d", "keepdepfile",
 		"-d", "keeprsp",
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 81c678d..6c9a1eb 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -86,28 +86,28 @@
 // This list specifies whether a particular binary from $PATH is allowed to be
 // run during the build. For more documentation, see path_interposer.go .
 var Configuration = map[string]PathConfig{
-	"bash":           Allowed,
-	"diff":           Allowed,
-	"dlv":            Allowed,
-	"expr":           Allowed,
-	"fuser":          Allowed,
-	"gcert":          Allowed,
-	"gcertstatus":    Allowed,
-	"gcloud":         Allowed,
-	"git":            Allowed,
-	"hexdump":        Allowed,
-	"jar":            Allowed,
-	"java":           Allowed,
-	"javap":          Allowed,
-	"lsof":           Allowed,
-	"openssl":        Allowed,
-	"pstree":         Allowed,
-	"rsync":          Allowed,
-	"sh":             Allowed,
-	"stubby":         Allowed,
-	"tr":             Allowed,
-	"unzip":          Allowed,
-	"zip":            Allowed,
+	"bash":        Allowed,
+	"diff":        Allowed,
+	"dlv":         Allowed,
+	"expr":        Allowed,
+	"fuser":       Allowed,
+	"gcert":       Allowed,
+	"gcertstatus": Allowed,
+	"gcloud":      Allowed,
+	"git":         Allowed,
+	"hexdump":     Allowed,
+	"jar":         Allowed,
+	"java":        Allowed,
+	"javap":       Allowed,
+	"lsof":        Allowed,
+	"openssl":     Allowed,
+	"pstree":      Allowed,
+	"rsync":       Allowed,
+	"sh":          Allowed,
+	"stubby":      Allowed,
+	"tr":          Allowed,
+	"unzip":       Allowed,
+	"zip":         Allowed,
 
 	// Host toolchain is removed. In-tree toolchain should be used instead.
 	// GCC also can't find cc1 with this implementation.
diff --git a/ui/build/rbe_test.go b/ui/build/rbe_test.go
index 266f76b..d1b8e26 100644
--- a/ui/build/rbe_test.go
+++ b/ui/build/rbe_test.go
@@ -19,6 +19,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -26,6 +27,10 @@
 )
 
 func TestDumpRBEMetrics(t *testing.T) {
+	// RBE is only supported on linux.
+	if runtime.GOOS != "linux" {
+		t.Skip("RBE is only supported on linux")
+	}
 	ctx := testContext()
 	tests := []struct {
 		description string
@@ -82,6 +87,10 @@
 }
 
 func TestDumpRBEMetricsErrors(t *testing.T) {
+	// RBE is only supported on linux.
+	if runtime.GOOS != "linux" {
+		t.Skip("RBE is only supported on linux")
+	}
 	ctx := testContext()
 	tests := []struct {
 		description      string
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index edb3b66..5c3fec1 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -200,6 +200,9 @@
 		// Only log important warnings / errors
 		"-q",
 	}
+	if c.config.UseABFS() {
+		sandboxArgs = append(sandboxArgs, "-B", "{ABFS_DIR}")
+	}
 
 	// Mount srcDir RW allowlists as Read-Write
 	if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index e18cc25..2ccdfec 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -661,7 +661,7 @@
 		}
 
 		ninjaArgs = append(ninjaArgs, targets...)
-		ninjaCmd := config.PrebuiltBuildTool("ninja")
+		ninjaCmd := config.NinjaBin()
 		if config.useN2 {
 			ninjaCmd = config.PrebuiltBuildTool("n2")
 		}
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 687ad6f..3faa94d 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -15,14 +15,16 @@
 package build
 
 import (
-	"android/soong/ui/metrics"
-	"android/soong/ui/status"
 	"bufio"
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strings"
+
+	"android/soong/ui/metrics"
+	"android/soong/ui/status"
 )
 
 // Checks for files in the out directory that have a rule that depends on them but no rule to
@@ -84,6 +86,10 @@
 	// before running soong and ninja.
 	releaseConfigDir := filepath.Join(outDir, "soong", "release-config")
 
+	// out/target/product/<xxxxx>/build_fingerprint.txt is a source file created in sysprop.mk
+	// ^out/target/product/[^/]+/build_fingerprint.txt$
+	buildFingerPrintFilePattern := regexp.MustCompile("^" + filepath.Join(outDir, "target", "product") + "/[^/]+/build_fingerprint.txt$")
+
 	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
@@ -100,7 +106,8 @@
 			line == dexpreoptConfigFilePath ||
 			line == buildDatetimeFilePath ||
 			line == bpglob ||
-			strings.HasPrefix(line, releaseConfigDir) {
+			strings.HasPrefix(line, releaseConfigDir) ||
+			buildFingerPrintFilePattern.MatchString(line) {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
 			// full build rules in the primary build.ninja file.
 			continue
diff --git a/ui/terminal/format.go b/ui/terminal/format.go
index 241a1dd..01f8b0d 100644
--- a/ui/terminal/format.go
+++ b/ui/terminal/format.go
@@ -23,26 +23,28 @@
 )
 
 type formatter struct {
-	format string
-	quiet  bool
-	start  time.Time
+	colorize bool
+	format   string
+	quiet    bool
+	start    time.Time
 }
 
 // newFormatter returns a formatter for formatting output to
 // the terminal in a format similar to Ninja.
 // format takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func newFormatter(format string, quiet bool) formatter {
+func newFormatter(colorize bool, format string, quiet bool) formatter {
 	return formatter{
-		format: format,
-		quiet:  quiet,
-		start:  time.Now(),
+		colorize: colorize,
+		format:   format,
+		quiet:    quiet,
+		start:    time.Now(),
 	}
 }
 
 func (s formatter) message(level status.MsgLevel, message string) string {
 	if level >= status.ErrorLvl {
-		return fmt.Sprintf("FAILED: %s", message)
+		return fmt.Sprintf("%s %s", s.failedString(), message)
 	} else if level > status.StatusLvl {
 		return fmt.Sprintf("%s%s", level.Prefix(), message)
 	} else if level == status.StatusLvl {
@@ -127,9 +129,9 @@
 	if result.Error != nil {
 		targets := strings.Join(result.Outputs, " ")
 		if s.quiet || result.Command == "" {
-			ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
+			ret = fmt.Sprintf("%s %s\n%s", s.failedString(), targets, result.Output)
 		} else {
-			ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
+			ret = fmt.Sprintf("%s %s\n%s\n%s", s.failedString(), targets, result.Command, result.Output)
 		}
 	} else if result.Output != "" {
 		ret = result.Output
@@ -141,3 +143,11 @@
 
 	return ret
 }
+
+func (s formatter) failedString() string {
+	failed := "FAILED:"
+	if s.colorize {
+		failed = ansi.red() + ansi.bold() + failed + ansi.regular()
+	}
+	return failed
+}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2ad174f..92f2994 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -27,9 +27,10 @@
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
 func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
-	formatter := newFormatter(statusFormat, quietBuild)
+	canUseSmartFormatting := !forceSimpleOutput && isSmartTerminal(w)
+	formatter := newFormatter(canUseSmartFormatting, statusFormat, quietBuild)
 
-	if !forceSimpleOutput && isSmartTerminal(w) {
+	if canUseSmartFormatting {
 		return NewSmartStatusOutput(w, formatter)
 	} else {
 		return NewSimpleStatusOutput(w, formatter, forceKeepANSI)
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index 8dd1809..991eca0 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -58,7 +58,7 @@
 		{
 			name:   "action with error",
 			calls:  actionsWithError,
-			smart:  "\r\x1b[1m[  0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
+			smart:  "\r\x1b[1m[  0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
 			simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
 		},
 		{
@@ -70,7 +70,7 @@
 		{
 			name:   "messages",
 			calls:  actionsWithMessages,
-			smart:  "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\nFAILED: error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
+			smart:  "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\n\x1b[31m\x1b[1mFAILED:\x1b[0m error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
 			simple: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
 		},
 		{
@@ -362,7 +362,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -407,7 +407,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -445,7 +445,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nFAILED: \nOutput2\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput2\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)