Merge "Enforce exclusive release config component directories" into main
diff --git a/Android.bp b/Android.bp
index ab8e4a0..d71bcec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -142,12 +142,10 @@
     visibility: ["//visibility:public"],
 }
 
-// TODO(b/365670526): remove the cuttlefish visibility once it is fully removed
 product_config {
     name: "product_config",
     visibility: [
         "//build/make/target/product/generic",
-        "//device/google/cuttlefish/system_image",
     ],
 }
 
@@ -158,7 +156,6 @@
     // Currently, only microdroid and cf system image can refer to system-build.prop
     visibility: [
         "//build/make/target/product/generic",
-        "//device/google/cuttlefish/system_image",
         "//packages/modules/Virtualization/build/microdroid",
     ],
 }
diff --git a/aconfig/build_flags/Android.bp b/aconfig/build_flags/Android.bp
index b3c7339..139aeac 100644
--- a/aconfig/build_flags/Android.bp
+++ b/aconfig/build_flags/Android.bp
@@ -13,10 +13,11 @@
         "soong-android",
     ],
     srcs: [
-        "all_build_flag_declarations.go",
         "build_flags.go",
+        "build_flags_singleton.go",
         "declarations.go",
         "init.go",
+        "release_configs.go",
     ],
     testSrcs: [
     ],
diff --git a/aconfig/build_flags/all_build_flag_declarations.go b/aconfig/build_flags/all_build_flag_declarations.go
deleted file mode 100644
index 5f02912..0000000
--- a/aconfig/build_flags/all_build_flag_declarations.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package build_flags
-
-import (
-	"android/soong/android"
-)
-
-// A singleton module that collects all of the build flags declared in the
-// tree into a single combined file for export to the external flag setting
-// server (inside Google it's Gantry).
-//
-// Note that this is ALL build_declarations modules present in the tree, not just
-// ones that are relevant to the product currently being built, so that that infra
-// doesn't need to pull from multiple builds and merge them.
-func AllBuildFlagDeclarationsFactory() android.Singleton {
-	return &allBuildFlagDeclarationsSingleton{}
-}
-
-type allBuildFlagDeclarationsSingleton struct {
-	intermediateBinaryProtoPath android.OutputPath
-	intermediateTextProtoPath   android.OutputPath
-}
-
-func (this *allBuildFlagDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	// Find all of the build_flag_declarations modules
-	var intermediateFiles android.Paths
-	ctx.VisitAllModules(func(module android.Module) {
-		decl, ok := android.OtherModuleProvider(ctx, module, BuildFlagDeclarationsProviderKey)
-		if !ok {
-			return
-		}
-		intermediateFiles = append(intermediateFiles, decl.IntermediateCacheOutputPath)
-	})
-
-	// Generate build action for build_flag (binary proto output)
-	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.pb")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        allDeclarationsRule,
-		Inputs:      intermediateFiles,
-		Output:      this.intermediateBinaryProtoPath,
-		Description: "all_build_flag_declarations",
-		Args: map[string]string{
-			"intermediates": android.JoinPathsWithPrefix(intermediateFiles, "--intermediate "),
-		},
-	})
-	ctx.Phony("all_build_flag_declarations", this.intermediateBinaryProtoPath)
-
-	// Generate build action for build_flag (text proto output)
-	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.textproto")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        allDeclarationsRuleTextProto,
-		Input:       this.intermediateBinaryProtoPath,
-		Output:      this.intermediateTextProtoPath,
-		Description: "all_build_flag_declarations_textproto",
-	})
-	ctx.Phony("all_build_flag_declarations_textproto", this.intermediateTextProtoPath)
-}
-
-func (this *allBuildFlagDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
-	for _, goal := range []string{"docs", "droid", "sdk"} {
-		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "build_flags/all_flags.pb")
-		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "build_flags/all_flags.textproto")
-	}
-}
diff --git a/aconfig/build_flags/build_flags_singleton.go b/aconfig/build_flags/build_flags_singleton.go
new file mode 100644
index 0000000..3b40755
--- /dev/null
+++ b/aconfig/build_flags/build_flags_singleton.go
@@ -0,0 +1,123 @@
+// Copyright 2023 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package build_flags
+
+import (
+	"android/soong/android"
+)
+
+// A singleton module that collects all of the build flags declared in the
+// tree into a single combined file for export to the external flag setting
+// server (inside Google it's Gantry).
+//
+// Note that this is ALL build_declarations modules present in the tree, not just
+// ones that are relevant to the product currently being built, so that that infra
+// doesn't need to pull from multiple builds and merge them.
+func AllBuildFlagDeclarationsFactory() android.Singleton {
+	return &allBuildFlagDeclarationsSingleton{}
+}
+
+type allBuildFlagDeclarationsSingleton struct {
+	flagsBinaryProtoPath   android.OutputPath
+	flagsTextProtoPath     android.OutputPath
+	configsBinaryProtoPath android.OutputPath
+	configsTextProtoPath   android.OutputPath
+}
+
+func (this *allBuildFlagDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// Find all of the build_flag_declarations modules
+	var flagsFiles android.Paths
+	// Find all of the release_config_contribution modules
+	var contributionDirs android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		decl, ok := android.OtherModuleProvider(ctx, module, BuildFlagDeclarationsProviderKey)
+		if ok {
+			flagsFiles = append(flagsFiles, decl.IntermediateCacheOutputPath)
+		}
+
+		contrib, ok := android.OtherModuleProvider(ctx, module, ReleaseConfigContributionsProviderKey)
+		if ok {
+			contributionDirs = append(contributionDirs, contrib.ContributionDir)
+		}
+	})
+
+	// Generate build action for build_flag (binary proto output)
+	this.flagsBinaryProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.pb")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRule,
+		Inputs:      flagsFiles,
+		Output:      this.flagsBinaryProtoPath,
+		Description: "all_build_flag_declarations",
+		Args: map[string]string{
+			"intermediates": android.JoinPathsWithPrefix(flagsFiles, "--intermediate "),
+		},
+	})
+	ctx.Phony("all_build_flag_declarations", this.flagsBinaryProtoPath)
+
+	// Generate build action for build_flag (text proto output)
+	this.flagsTextProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRuleTextProto,
+		Input:       this.flagsBinaryProtoPath,
+		Output:      this.flagsTextProtoPath,
+		Description: "all_build_flag_declarations_textproto",
+	})
+	ctx.Phony("all_build_flag_declarations_textproto", this.flagsTextProtoPath)
+
+	// Generate build action for release_configs (binary proto output)
+	this.configsBinaryProtoPath = android.PathForIntermediates(ctx, "all_release_config_contributions.pb")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allReleaseConfigContributionsRule,
+		Inputs:      contributionDirs,
+		Output:      this.configsBinaryProtoPath,
+		Description: "all_release_config_contributions",
+		Args: map[string]string{
+			"dirs":   android.JoinPathsWithPrefix(contributionDirs, "--dir "),
+			"format": "pb",
+		},
+	})
+	ctx.Phony("all_release_config_contributions", this.configsBinaryProtoPath)
+
+	this.configsTextProtoPath = android.PathForIntermediates(ctx, "all_release_config_contributions.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allReleaseConfigContributionsRule,
+		Inputs:      contributionDirs,
+		Output:      this.configsTextProtoPath,
+		Description: "all_release_config_contributions_textproto",
+		Args: map[string]string{
+			"dirs":   android.JoinPathsWithPrefix(contributionDirs, "--dir "),
+			"format": "textproto",
+		},
+	})
+	ctx.Phony("all_release_config_contributions_textproto", this.configsTextProtoPath)
+
+	// Add a simple target for ci/build_metadata to use.
+	ctx.Phony("release_config_metadata",
+		this.flagsBinaryProtoPath,
+		this.flagsTextProtoPath,
+		this.configsBinaryProtoPath,
+		this.configsTextProtoPath,
+	)
+}
+
+func (this *allBuildFlagDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droid", this.flagsBinaryProtoPath)
+	for _, goal := range []string{"docs", "droid", "sdk", "release_config_metadata"} {
+		ctx.DistForGoalWithFilename(goal, this.flagsBinaryProtoPath, "build_flags/all_flags.pb")
+		ctx.DistForGoalWithFilename(goal, this.flagsTextProtoPath, "build_flags/all_flags.textproto")
+		ctx.DistForGoalWithFilename(goal, this.configsBinaryProtoPath, "build_flags/all_release_config_contributions.pb")
+		ctx.DistForGoalWithFilename(goal, this.configsTextProtoPath, "build_flags/all_release_config_contributions.textproto")
+	}
+}
diff --git a/aconfig/build_flags/declarations.go b/aconfig/build_flags/declarations.go
index e927db2..4a54269 100644
--- a/aconfig/build_flags/declarations.go
+++ b/aconfig/build_flags/declarations.go
@@ -35,7 +35,7 @@
 
 	// Properties for "aconfig_declarations"
 	properties struct {
-		// aconfig files, relative to this Android.bp file
+		// build flag declaration files, relative to this Android.bp file
 		Srcs []string `android:"path"`
 	}
 }
diff --git a/aconfig/build_flags/init.go b/aconfig/build_flags/init.go
index dc1369c..a7575e8 100644
--- a/aconfig/build_flags/init.go
+++ b/aconfig/build_flags/init.go
@@ -65,15 +65,32 @@
 				"${buildFlagDeclarations}",
 			},
 		})
+
+	allReleaseConfigContributionsRule = pctx.AndroidStaticRule("all-release-config-contributions-dump",
+		blueprint.RuleParams{
+			Command: `${releaseConfigContributions} ${dirs} --format ${format} --output ${out}`,
+			CommandDeps: []string{
+				"${releaseConfigContributions}",
+			},
+		}, "dirs", "format")
+	allReleaseConfigContributionsRuleText = pctx.AndroidStaticRule("all-release-config-contributions-dumptext",
+		blueprint.RuleParams{
+			Command: `${releaseConfigContributions} ${dirs} --format ${format} --output ${out}`,
+			CommandDeps: []string{
+				"${releaseConfigContributions}",
+			},
+		}, "dirs", "format")
 )
 
 func init() {
 	RegisterBuildComponents(android.InitRegistrationContext)
 	pctx.Import("android/soong/android")
 	pctx.HostBinToolVariable("buildFlagDeclarations", "build-flag-declarations")
+	pctx.HostBinToolVariable("releaseConfigContributions", "release-config-contributions")
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("build_flag_declarations", DeclarationsFactory)
+	ctx.RegisterModuleType("release_config_contributions", ReleaseConfigContributionsFactory)
 	ctx.RegisterParallelSingletonType("all_build_flag_declarations", AllBuildFlagDeclarationsFactory)
 }
diff --git a/aconfig/build_flags/release_configs.go b/aconfig/build_flags/release_configs.go
new file mode 100644
index 0000000..3fa8a7c
--- /dev/null
+++ b/aconfig/build_flags/release_configs.go
@@ -0,0 +1,78 @@
+// Copyright 2023 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package build_flags
+
+import (
+	"path/filepath"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+type ReleaseConfigContributionsProviderData struct {
+	ContributionDir android.SourcePath
+}
+
+var ReleaseConfigContributionsProviderKey = blueprint.NewProvider[ReleaseConfigContributionsProviderData]()
+
+// Soong uses `release_config_contributions` modules to produce the
+// `build_flags/all_release_config_contributions.*` artifacts, listing *all* of
+// the directories in the source tree that contribute to each release config,
+// whether or not they are actually used for the lunch product.
+//
+// This artifact helps flagging automation determine in which directory a flag
+// should be placed by default.
+type ReleaseConfigContributionsModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	// Properties for "release_config_contributions"
+	properties struct {
+		// The `release_configs/*.textproto` files provided by this
+		// directory, relative to this Android.bp file
+		Srcs []string `android:"path"`
+	}
+}
+
+func ReleaseConfigContributionsFactory() android.Module {
+	module := &ReleaseConfigContributionsModule{}
+
+	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
+	module.AddProperties(&module.properties)
+
+	return module
+}
+
+func (module *ReleaseConfigContributionsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	srcs := android.PathsForModuleSrc(ctx, module.properties.Srcs)
+	if len(srcs) == 0 {
+		return
+	}
+	contributionDir := filepath.Dir(filepath.Dir(srcs[0].String()))
+	for _, file := range srcs {
+		if filepath.Dir(filepath.Dir(file.String())) != contributionDir {
+			ctx.ModuleErrorf("Cannot include %s with %s contributions", file, contributionDir)
+		}
+		if filepath.Base(filepath.Dir(file.String())) != "release_configs" || file.Ext() != ".textproto" {
+			ctx.ModuleErrorf("Invalid contribution file %s", file)
+		}
+	}
+	android.SetProvider(ctx, ReleaseConfigContributionsProviderKey, ReleaseConfigContributionsProviderData{
+		ContributionDir: android.PathForSource(ctx, contributionDir),
+	})
+
+}
diff --git a/android/Android.bp b/android/Android.bp
index c2bef0b..eb8c64d 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -71,6 +71,7 @@
         "module.go",
         "module_context.go",
         "module_info_json.go",
+        "module_proxy.go",
         "mutator.go",
         "namespace.go",
         "neverallow.go",
diff --git a/android/base_module_context.go b/android/base_module_context.go
index c7d7573..670537f 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -33,6 +33,8 @@
 
 	blueprintBaseModuleContext() blueprint.BaseModuleContext
 
+	EqualModules(m1, m2 Module) bool
+
 	// OtherModuleName returns the name of another Module.  See BaseModuleContext.ModuleName for more information.
 	// It is intended for use inside the visit functions of Visit* and WalkDeps.
 	OtherModuleName(m blueprint.Module) string
@@ -130,6 +132,14 @@
 	// function, it may be invalidated by future mutators.
 	VisitDirectDepsAllowDisabled(visit func(Module))
 
+	// VisitDirectDepsProxyAllowDisabled calls visit for each direct dependency.  If there are
+	// multiple direct dependencies on the same module visit will be called multiple times on
+	// that module and OtherModuleDependencyTag will return a different tag for each.
+	//
+	// The Module passed to the visit function should not be retained outside of the visit function, it may be
+	// invalidated by future mutators.
+	VisitDirectDepsProxyAllowDisabled(visit func(proxy Module))
+
 	VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module))
 
 	// VisitDirectDepsIf calls pred for each direct dependency, and if pred returns true calls visit.  If there are
@@ -155,6 +165,16 @@
 	// invalidated by future mutators.
 	WalkDeps(visit func(child, parent Module) bool)
 
+	// WalkDeps calls visit for each transitive dependency, traversing the dependency tree in top down order.  visit may
+	// be called multiple times for the same (child, parent) pair if there are multiple direct dependencies between the
+	// child and parent with different tags.  OtherModuleDependencyTag will return the tag for the currently visited
+	// (child, parent) pair.  If visit returns false WalkDeps will not continue recursing down to child.  It skips
+	// any dependencies that are not an android.Module.
+	//
+	// The Modules passed to the visit function should not be retained outside of the visit function, they may be
+	// invalidated by future mutators.
+	WalkDepsProxy(visit func(child, parent Module) bool)
+
 	// GetWalkPath is supposed to be called in visit function passed in WalkDeps()
 	// and returns a top-down dependency path from a start module to current child module.
 	GetWalkPath() []Module
@@ -214,15 +234,26 @@
 
 }
 
+func getWrappedModule(module blueprint.Module) blueprint.Module {
+	if mp, isProxy := module.(ModuleProxy); isProxy {
+		return mp.module
+	}
+	return module
+}
+
+func (b *baseModuleContext) EqualModules(m1, m2 Module) bool {
+	return b.bp.EqualModules(getWrappedModule(m1), getWrappedModule(m2))
+}
+
 func (b *baseModuleContext) OtherModuleName(m blueprint.Module) string {
-	return b.bp.OtherModuleName(m)
+	return b.bp.OtherModuleName(getWrappedModule(m))
 }
 func (b *baseModuleContext) OtherModuleDir(m blueprint.Module) string { return b.bp.OtherModuleDir(m) }
 func (b *baseModuleContext) OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) {
 	b.bp.OtherModuleErrorf(m, fmt, args...)
 }
 func (b *baseModuleContext) OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag {
-	return b.bp.OtherModuleDependencyTag(m)
+	return b.bp.OtherModuleDependencyTag(getWrappedModule(m))
 }
 func (b *baseModuleContext) OtherModuleExists(name string) bool { return b.bp.OtherModuleExists(name) }
 func (b *baseModuleContext) OtherModuleDependencyVariantExists(variations []blueprint.Variation, name string) bool {
@@ -395,6 +426,14 @@
 	})
 }
 
+func (b *baseModuleContext) VisitDirectDepsProxyAllowDisabled(visit func(proxy Module)) {
+	b.bp.VisitDirectDepsProxy(func(module blueprint.ModuleProxy) {
+		visit(ModuleProxy{
+			module: module,
+		})
+	})
+}
+
 func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) {
 	b.bp.VisitDirectDeps(func(module blueprint.Module) {
 		if b.bp.OtherModuleDependencyTag(module) == tag {
@@ -466,6 +505,23 @@
 	})
 }
 
+func (b *baseModuleContext) WalkDepsProxy(visit func(Module, Module) bool) {
+	b.walkPath = []Module{ModuleProxy{blueprint.CreateModuleProxy(b.Module())}}
+	b.tagPath = []blueprint.DependencyTag{}
+	b.bp.WalkDepsProxy(func(child, parent blueprint.ModuleProxy) bool {
+		childAndroidModule := ModuleProxy{child}
+		parentAndroidModule := ModuleProxy{parent}
+		// record walkPath before visit
+		for b.walkPath[len(b.walkPath)-1] != parentAndroidModule {
+			b.walkPath = b.walkPath[0 : len(b.walkPath)-1]
+			b.tagPath = b.tagPath[0 : len(b.tagPath)-1]
+		}
+		b.walkPath = append(b.walkPath, childAndroidModule)
+		b.tagPath = append(b.tagPath, b.OtherModuleDependencyTag(childAndroidModule))
+		return visit(childAndroidModule, parentAndroidModule)
+	})
+}
+
 func (b *baseModuleContext) GetWalkPath() []Module {
 	return b.walkPath
 }
diff --git a/android/compliance_metadata.go b/android/compliance_metadata.go
index 38f1382..d28831e 100644
--- a/android/compliance_metadata.go
+++ b/android/compliance_metadata.go
@@ -17,7 +17,6 @@
 import (
 	"bytes"
 	"encoding/csv"
-	"encoding/gob"
 	"fmt"
 	"slices"
 	"strconv"
@@ -126,32 +125,32 @@
 	properties map[string]string
 }
 
+type complianceMetadataInfoGob struct {
+	Properties map[string]string
+}
+
 func NewComplianceMetadataInfo() *ComplianceMetadataInfo {
 	return &ComplianceMetadataInfo{
 		properties: map[string]string{},
 	}
 }
 
-func (c *ComplianceMetadataInfo) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := encoder.Encode(c.properties)
-	if err != nil {
-		return nil, err
+func (m *ComplianceMetadataInfo) ToGob() *complianceMetadataInfoGob {
+	return &complianceMetadataInfoGob{
+		Properties: m.properties,
 	}
+}
 
-	return w.Bytes(), nil
+func (m *ComplianceMetadataInfo) FromGob(data *complianceMetadataInfoGob) {
+	m.properties = data.Properties
+}
+
+func (c *ComplianceMetadataInfo) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[complianceMetadataInfoGob](c)
 }
 
 func (c *ComplianceMetadataInfo) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := decoder.Decode(&c.properties)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[complianceMetadataInfoGob](data, c)
 }
 
 func (c *ComplianceMetadataInfo) SetStringValue(propertyName string, value string) {
diff --git a/android/config.go b/android/config.go
index 10e43ce..06d71c0 100644
--- a/android/config.go
+++ b/android/config.go
@@ -287,6 +287,10 @@
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_READ_FROM_NEW_STORAGE")
 }
 
+func (c Config) ReleaseCreateAconfigStorageFile() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_CREATE_ACONFIG_STORAGE_FILE")
+}
+
 // A DeviceConfig object represents the configuration for a particular device
 // being built. For now there will only be one of these, but in the future there
 // may be multiple devices being built.
@@ -2086,6 +2090,10 @@
 	return PathsForSource(ctx, c.productVariables.OdmPropFiles)
 }
 
+func (c *config) ExtraAllowedDepsTxt() string {
+	return String(c.productVariables.ExtraAllowedDepsTxt)
+}
+
 func (c *config) EnableUffdGc() string {
 	return String(c.productVariables.EnableUffdGc)
 }
@@ -2105,3 +2113,10 @@
 func (c *config) BoardAvbSystemAddHashtreeFooterArgs() []string {
 	return c.productVariables.BoardAvbSystemAddHashtreeFooterArgs
 }
+
+// Returns true if RELEASE_INSTALL_APEX_SYSTEMSERVER_DEXPREOPT_SAME_PARTITION is set to true.
+// If true, dexpreopt files of apex system server jars will be installed in the same partition as the parent apex.
+// If false, all these files will be installed in /system partition.
+func (c Config) InstallApexSystemServerDexpreoptSamePartition() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_INSTALL_APEX_SYSTEMSERVER_DEXPREOPT_SAME_PARTITION")
+}
diff --git a/android/depset_generic.go b/android/depset_generic.go
index 690987a..d04f88b 100644
--- a/android/depset_generic.go
+++ b/android/depset_generic.go
@@ -15,10 +15,9 @@
 package android
 
 import (
-	"bytes"
-	"encoding/gob"
-	"errors"
 	"fmt"
+
+	"github.com/google/blueprint"
 )
 
 // DepSet is designed to be conceptually compatible with Bazel's depsets:
@@ -68,28 +67,38 @@
 	transitive []*DepSet[T]
 }
 
-func (d *DepSet[T]) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(d.preorder), encoder.Encode(d.reverse),
-		encoder.Encode(d.order), encoder.Encode(d.direct), encoder.Encode(d.transitive))
-	if err != nil {
-		return nil, err
-	}
+type depSetGob[T depSettableType] struct {
+	Preorder   bool
+	Reverse    bool
+	Order      DepSetOrder
+	Direct     []T
+	Transitive []*DepSet[T]
+}
 
-	return w.Bytes(), nil
+func (d *DepSet[T]) ToGob() *depSetGob[T] {
+	return &depSetGob[T]{
+		Preorder:   d.preorder,
+		Reverse:    d.reverse,
+		Order:      d.order,
+		Direct:     d.direct,
+		Transitive: d.transitive,
+	}
+}
+
+func (d *DepSet[T]) FromGob(data *depSetGob[T]) {
+	d.preorder = data.Preorder
+	d.reverse = data.Reverse
+	d.order = data.Order
+	d.direct = data.Direct
+	d.transitive = data.Transitive
+}
+
+func (d *DepSet[T]) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[depSetGob[T]](d)
 }
 
 func (d *DepSet[T]) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&d.preorder), decoder.Decode(&d.reverse),
-		decoder.Decode(&d.order), decoder.Decode(&d.direct), decoder.Decode(&d.transitive))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[depSetGob[T]](data, d)
 }
 
 // NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
diff --git a/android/init.go b/android/init.go
index b462292..1ace344 100644
--- a/android/init.go
+++ b/android/init.go
@@ -17,7 +17,12 @@
 import "encoding/gob"
 
 func init() {
+	gob.Register(extraFilesZip{})
+	gob.Register(InstallPath{})
+	gob.Register(ModuleGenPath{})
 	gob.Register(ModuleOutPath{})
+	gob.Register(OutputPath{})
 	gob.Register(PhonyPath{})
+	gob.Register(SourcePath{})
 	gob.Register(unstableInfo{})
 }
diff --git a/android/module.go b/android/module.go
index 20caae2..44f7583 100644
--- a/android/module.go
+++ b/android/module.go
@@ -15,9 +15,6 @@
 package android
 
 import (
-	"bytes"
-	"encoding/gob"
-	"errors"
 	"fmt"
 	"net/url"
 	"path/filepath"
@@ -81,6 +78,7 @@
 	InstallInOdm() bool
 	InstallInProduct() bool
 	InstallInVendor() bool
+	InstallInSystemExt() bool
 	InstallForceOS() (*OsType, *ArchType)
 	PartitionTag(DeviceConfig) string
 	HideFromMake()
@@ -1005,14 +1003,6 @@
 			return
 		}
 
-		// Do not create a dependency from common variant to arch variant for `common_first` modules
-		if multilib, _ := decodeMultilib(ctx, ctx.Module().base()); multilib == string(MultilibCommonFirst) {
-			commonVariant := ctx.Arch().ArchType.Multilib == ""
-			if bothInAndroid && commonVariant && InList(target.Arch.ArchType.Multilib, []string{"lib32", "lib64"}) {
-				return
-			}
-		}
-
 		variation := target.Variations()
 		if ctx.OtherModuleFarDependencyVariantExists(variation, depName) {
 			ctx.AddFarVariationDependencies(variation, RequiredDepTag, depName)
@@ -1514,6 +1504,10 @@
 	return Bool(m.commonProperties.Vendor) || Bool(m.commonProperties.Soc_specific) || Bool(m.commonProperties.Proprietary)
 }
 
+func (m *ModuleBase) InstallInSystemExt() bool {
+	return Bool(m.commonProperties.System_ext_specific)
+}
+
 func (m *ModuleBase) InstallInRoot() bool {
 	return false
 }
@@ -1801,6 +1795,26 @@
 
 var FinalModuleBuildTargetsProvider = blueprint.NewProvider[FinalModuleBuildTargetsInfo]()
 
+type CommonPropertiesProviderData struct {
+	Enabled bool
+	// Whether the module has been replaced by a prebuilt
+	ReplacedByPrebuilt bool
+}
+
+var CommonPropertiesProviderKey = blueprint.NewProvider[CommonPropertiesProviderData]()
+
+type PrebuiltModuleProviderData struct {
+	// Empty for now
+}
+
+var PrebuiltModuleProviderKey = blueprint.NewProvider[PrebuiltModuleProviderData]()
+
+type HostToolProviderData struct {
+	HostToolPath OptionalPath
+}
+
+var HostToolProviderKey = blueprint.NewProvider[HostToolProviderData]()
+
 func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) {
 	ctx := &moduleContext{
 		module:            m.module,
@@ -2046,6 +2060,23 @@
 		})
 	}
 	buildComplianceMetadataProvider(ctx, m)
+
+	commonData := CommonPropertiesProviderData{
+		ReplacedByPrebuilt: m.commonProperties.ReplacedByPrebuilt,
+	}
+	if m.commonProperties.ForcedDisabled {
+		commonData.Enabled = false
+	} else {
+		commonData.Enabled = m.commonProperties.Enabled.GetOrDefault(m.ConfigurableEvaluator(ctx), !m.Os().DefaultDisabled)
+	}
+	SetProvider(ctx, CommonPropertiesProviderKey, commonData)
+	if p, ok := m.module.(PrebuiltInterface); ok && p.Prebuilt() != nil {
+		SetProvider(ctx, PrebuiltModuleProviderKey, PrebuiltModuleProviderData{})
+	}
+	if h, ok := m.module.(HostToolProvider); ok {
+		SetProvider(ctx, HostToolProviderKey, HostToolProviderData{
+			HostToolPath: h.HostToolPath()})
+	}
 }
 
 func SetJarJarPrefixHandler(handler func(ModuleContext)) {
@@ -2125,36 +2156,47 @@
 	orderOnlyDeps Paths
 	executable    bool
 	extraFiles    *extraFilesZip
-
-	absFrom string
+	absFrom       string
 }
 
-func (p *katiInstall) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.from), encoder.Encode(p.to),
-		encoder.Encode(p.implicitDeps), encoder.Encode(p.orderOnlyDeps),
-		encoder.Encode(p.executable), encoder.Encode(p.extraFiles),
-		encoder.Encode(p.absFrom))
-	if err != nil {
-		return nil, err
-	}
-
-	return w.Bytes(), nil
+type katiInstallGob struct {
+	From          Path
+	To            InstallPath
+	ImplicitDeps  Paths
+	OrderOnlyDeps Paths
+	Executable    bool
+	ExtraFiles    *extraFilesZip
+	AbsFrom       string
 }
 
-func (p *katiInstall) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.from), decoder.Decode(&p.to),
-		decoder.Decode(&p.implicitDeps), decoder.Decode(&p.orderOnlyDeps),
-		decoder.Decode(&p.executable), decoder.Decode(&p.extraFiles),
-		decoder.Decode(&p.absFrom))
-	if err != nil {
-		return err
+func (k *katiInstall) ToGob() *katiInstallGob {
+	return &katiInstallGob{
+		From:          k.from,
+		To:            k.to,
+		ImplicitDeps:  k.implicitDeps,
+		OrderOnlyDeps: k.orderOnlyDeps,
+		Executable:    k.executable,
+		ExtraFiles:    k.extraFiles,
+		AbsFrom:       k.absFrom,
 	}
+}
 
-	return nil
+func (k *katiInstall) FromGob(data *katiInstallGob) {
+	k.from = data.From
+	k.to = data.To
+	k.implicitDeps = data.ImplicitDeps
+	k.orderOnlyDeps = data.OrderOnlyDeps
+	k.executable = data.Executable
+	k.extraFiles = data.ExtraFiles
+	k.absFrom = data.AbsFrom
+}
+
+func (k *katiInstall) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[katiInstallGob](k)
+}
+
+func (k *katiInstall) GobDecode(data []byte) error {
+	return blueprint.CustomGobDecode[katiInstallGob](data, k)
 }
 
 type extraFilesZip struct {
@@ -2162,26 +2204,29 @@
 	dir InstallPath
 }
 
-func (p *extraFilesZip) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.zip), encoder.Encode(p.dir))
-	if err != nil {
-		return nil, err
-	}
-
-	return w.Bytes(), nil
+type extraFilesZipGob struct {
+	Zip Path
+	Dir InstallPath
 }
 
-func (p *extraFilesZip) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.zip), decoder.Decode(&p.dir))
-	if err != nil {
-		return err
+func (e *extraFilesZip) ToGob() *extraFilesZipGob {
+	return &extraFilesZipGob{
+		Zip: e.zip,
+		Dir: e.dir,
 	}
+}
 
-	return nil
+func (e *extraFilesZip) FromGob(data *extraFilesZipGob) {
+	e.zip = data.Zip
+	e.dir = data.Dir
+}
+
+func (e *extraFilesZip) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[extraFilesZipGob](e)
+}
+
+func (e *extraFilesZip) GobDecode(data []byte) error {
+	return blueprint.CustomGobDecode[extraFilesZipGob](data, e)
 }
 
 type katiInstalls []katiInstall
diff --git a/android/module_proxy.go b/android/module_proxy.go
new file mode 100644
index 0000000..bc5090e
--- /dev/null
+++ b/android/module_proxy.go
@@ -0,0 +1,203 @@
+package android
+
+import (
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type ModuleProxy struct {
+	module blueprint.ModuleProxy
+}
+
+func (m ModuleProxy) Name() string {
+	return m.module.Name()
+}
+
+func (m ModuleProxy) GenerateBuildActions(context blueprint.ModuleContext) {
+	m.module.GenerateBuildActions(context)
+}
+
+func (m ModuleProxy) GenerateAndroidBuildActions(context ModuleContext) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) ComponentDepsMutator(ctx BottomUpMutatorContext) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) DepsMutator(context BottomUpMutatorContext) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) base() *ModuleBase {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) Disable() {
+
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) Enabled(ctx ConfigurableEvaluatorContext) bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) Target() Target {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) MultiTargets() []Target {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) ImageVariation() blueprint.Variation {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) Owner() string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInData() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInTestcases() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInSanitizerDir() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInRamdisk() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInVendorRamdisk() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInDebugRamdisk() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInRecovery() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInRoot() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInOdm() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInProduct() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInVendor() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInSystemExt() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallForceOS() (*OsType, *ArchType) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) PartitionTag(d DeviceConfig) string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) HideFromMake() {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) IsHideFromMake() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) IsSkipInstall() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) MakeUninstallable() {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) ReplacedByPrebuilt() {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) IsReplacedByPrebuilt() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) ExportedToMake() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) EffectiveLicenseKinds() []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) EffectiveLicenseFiles() Paths {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) AddProperties(props ...interface{}) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) GetProperties() []interface{} {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) BuildParamsForTests() []BuildParams {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) VariablesForTests() map[string]string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) String() string {
+	return m.module.Name()
+}
+
+func (m ModuleProxy) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) visibilityProperties() []visibilityProperty {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) RequiredModuleNames(ctx ConfigurableEvaluatorContext) []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) HostRequiredModuleNames() []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) TargetRequiredModuleNames() []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) VintfFragmentModuleNames(ctx ConfigurableEvaluatorContext) []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) ConfigurableEvaluator(ctx ConfigurableEvaluatorContext) proptools.ConfigurableEvaluator {
+	panic("method is not implemented on ModuleProxy")
+}
diff --git a/android/mutator.go b/android/mutator.go
index a8b5c7d..8265458 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -32,11 +32,11 @@
 // collateGloballyRegisteredMutators constructs the list of mutators that have been registered
 // with the InitRegistrationContext and will be used at runtime.
 func collateGloballyRegisteredMutators() sortableComponents {
-	return collateRegisteredMutators(preArch, preDeps, postDeps, finalDeps)
+	return collateRegisteredMutators(preArch, preDeps, postDeps, postApex, finalDeps)
 }
 
 // collateRegisteredMutators constructs a single list of mutators from the separate lists.
-func collateRegisteredMutators(preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc) sortableComponents {
+func collateRegisteredMutators(preArch, preDeps, postDeps, postApex, finalDeps []RegisterMutatorFunc) sortableComponents {
 	mctx := &registerMutatorsContext{}
 
 	register := func(funcs []RegisterMutatorFunc) {
@@ -53,6 +53,8 @@
 
 	register(postDeps)
 
+	register(postApex)
+
 	mctx.finalPhase = true
 	register(finalDeps)
 
@@ -166,6 +168,8 @@
 	RegisterOverridePostDepsMutators,
 }
 
+var postApex = []RegisterMutatorFunc{}
+
 var finalDeps = []RegisterMutatorFunc{}
 
 func PreArchMutators(f RegisterMutatorFunc) {
@@ -180,6 +184,10 @@
 	postDeps = append(postDeps, f)
 }
 
+func PostApexMutators(f RegisterMutatorFunc) {
+	postApex = append(postApex, f)
+}
+
 func FinalDepsMutators(f RegisterMutatorFunc) {
 	finalDeps = append(finalDeps, f)
 }
diff --git a/android/neverallow.go b/android/neverallow.go
index b89d150..e135f57 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -58,7 +58,6 @@
 	AddNeverAllowRules(createInitFirstStageRules()...)
 	AddNeverAllowRules(createProhibitFrameworkAccessRules()...)
 	AddNeverAllowRules(createCcStubsRule())
-	AddNeverAllowRules(createJavaExcludeStaticLibsRule())
 	AddNeverAllowRules(createProhibitHeaderOnlyRule())
 	AddNeverAllowRules(createLimitNdkExportRule()...)
 }
@@ -253,14 +252,6 @@
 	}
 }
 
-func createJavaExcludeStaticLibsRule() Rule {
-	return NeverAllow().
-		NotIn("build/soong", "libcore", "frameworks/base/api").
-		ModuleType("java_library").
-		WithMatcher("exclude_static_libs", isSetMatcherInstance).
-		Because("exclude_static_libs property is only allowed for java modules defined in build/soong, libcore, and frameworks/base/api")
-}
-
 func createProhibitHeaderOnlyRule() Rule {
 	return NeverAllow().
 		Without("name", "framework-minus-apex-headers").
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index b2620ef..192c924 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -344,23 +344,6 @@
 			`module "outside_allowed_list": violates neverallow`,
 		},
 	},
-	// Test for the rule restricting use of exclude_static_libs
-	{
-		name: `"exclude_static_libs" outside allowed directory`,
-		fs: map[string][]byte{
-			"a/b/Android.bp": []byte(`
-				java_library {
-					name: "baz",
-					exclude_static_libs: [
-						"bar",
-					],
-				}
-			`),
-		},
-		expectedErrors: []string{
-			`exclude_static_libs property is only allowed for java modules defined in build/soong, libcore, and frameworks/base/api`,
-		},
-	},
 	// Test for only allowing headers_only for framework-minus-apex-headers
 	{
 		name: `"headers_only" outside framework-minus-apex-headers modules`,
diff --git a/android/packaging.go b/android/packaging.go
index 0909936..3c64d56 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -15,9 +15,6 @@
 package android
 
 import (
-	"bytes"
-	"encoding/gob"
-	"errors"
 	"fmt"
 	"path/filepath"
 	"sort"
@@ -67,34 +64,53 @@
 	owner string
 }
 
-func (p *PackagingSpec) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.relPathInPackage), encoder.Encode(p.srcPath),
-		encoder.Encode(p.symlinkTarget), encoder.Encode(p.executable),
-		encoder.Encode(p.effectiveLicenseFiles), encoder.Encode(p.partition),
-		encoder.Encode(p.skipInstall), encoder.Encode(p.aconfigPaths),
-		encoder.Encode(p.archType))
-	if err != nil {
-		return nil, err
-	}
+type packagingSpecGob struct {
+	RelPathInPackage string
+	SrcPath          Path
+	SymlinkTarget    string
+	Executable       bool
+	Partition        string
+	SkipInstall      bool
+	AconfigPaths     *Paths
+	ArchType         ArchType
+	Overrides        *[]string
+	Owner            string
+}
 
-	return w.Bytes(), nil
+func (p *PackagingSpec) ToGob() *packagingSpecGob {
+	return &packagingSpecGob{
+		RelPathInPackage: p.relPathInPackage,
+		SrcPath:          p.srcPath,
+		SymlinkTarget:    p.symlinkTarget,
+		Executable:       p.executable,
+		Partition:        p.partition,
+		SkipInstall:      p.skipInstall,
+		AconfigPaths:     p.aconfigPaths,
+		ArchType:         p.archType,
+		Overrides:        p.overrides,
+		Owner:            p.owner,
+	}
+}
+
+func (p *PackagingSpec) FromGob(data *packagingSpecGob) {
+	p.relPathInPackage = data.RelPathInPackage
+	p.srcPath = data.SrcPath
+	p.symlinkTarget = data.SymlinkTarget
+	p.executable = data.Executable
+	p.partition = data.Partition
+	p.skipInstall = data.SkipInstall
+	p.aconfigPaths = data.AconfigPaths
+	p.archType = data.ArchType
+	p.overrides = data.Overrides
+	p.owner = data.Owner
+}
+
+func (p *PackagingSpec) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[packagingSpecGob](p)
 }
 
 func (p *PackagingSpec) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.relPathInPackage), decoder.Decode(&p.srcPath),
-		decoder.Decode(&p.symlinkTarget), decoder.Decode(&p.executable),
-		decoder.Decode(&p.effectiveLicenseFiles), decoder.Decode(&p.partition),
-		decoder.Decode(&p.skipInstall), decoder.Decode(&p.aconfigPaths),
-		decoder.Decode(&p.archType))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[packagingSpecGob](data, p)
 }
 
 func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
diff --git a/android/paths.go b/android/paths.go
index 1c8258e..9c2df65 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -15,9 +15,6 @@
 package android
 
 import (
-	"bytes"
-	"encoding/gob"
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -342,6 +339,11 @@
 	invalidReason string // Not applicable if path != nil. "" if the reason is unknown.
 }
 
+type optionalPathGob struct {
+	Path          Path
+	InvalidReason string
+}
+
 // OptionalPathForPath returns an OptionalPath containing the path.
 func OptionalPathForPath(path Path) OptionalPath {
 	return OptionalPath{path: path}
@@ -353,6 +355,26 @@
 	return OptionalPath{invalidReason: reason}
 }
 
+func (p *OptionalPath) ToGob() *optionalPathGob {
+	return &optionalPathGob{
+		Path:          p.path,
+		InvalidReason: p.invalidReason,
+	}
+}
+
+func (p *OptionalPath) FromGob(data *optionalPathGob) {
+	p.path = data.Path
+	p.invalidReason = data.InvalidReason
+}
+
+func (p OptionalPath) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[optionalPathGob](&p)
+}
+
+func (p *OptionalPath) GobDecode(data []byte) error {
+	return blueprint.CustomGobDecode[optionalPathGob](data, p)
+}
+
 // Valid returns whether there is a valid path
 func (p OptionalPath) Valid() bool {
 	return p.path != nil
@@ -1065,26 +1087,29 @@
 	rel  string
 }
 
-func (p basePath) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.path), encoder.Encode(p.rel))
-	if err != nil {
-		return nil, err
-	}
+type basePathGob struct {
+	Path string
+	Rel  string
+}
 
-	return w.Bytes(), nil
+func (p *basePath) ToGob() *basePathGob {
+	return &basePathGob{
+		Path: p.path,
+		Rel:  p.rel,
+	}
+}
+
+func (p *basePath) FromGob(data *basePathGob) {
+	p.path = data.Path
+	p.rel = data.Rel
+}
+
+func (p basePath) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[basePathGob](&p)
 }
 
 func (p *basePath) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.path), decoder.Decode(&p.rel))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[basePathGob](data, p)
 }
 
 func (p basePath) Ext() string {
@@ -1337,26 +1362,32 @@
 	fullPath string
 }
 
-func (p OutputPath) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.basePath), encoder.Encode(p.outDir), encoder.Encode(p.fullPath))
-	if err != nil {
-		return nil, err
-	}
+type outputPathGob struct {
+	basePath
+	OutDir   string
+	FullPath string
+}
 
-	return w.Bytes(), nil
+func (p *OutputPath) ToGob() *outputPathGob {
+	return &outputPathGob{
+		basePath: p.basePath,
+		OutDir:   p.outDir,
+		FullPath: p.fullPath,
+	}
+}
+
+func (p *OutputPath) FromGob(data *outputPathGob) {
+	p.basePath = data.basePath
+	p.outDir = data.OutDir
+	p.fullPath = data.FullPath
+}
+
+func (p OutputPath) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[outputPathGob](&p)
 }
 
 func (p *OutputPath) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.basePath), decoder.Decode(&p.outDir), decoder.Decode(&p.fullPath))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[outputPathGob](data, p)
 }
 
 func (p OutputPath) withRel(rel string) OutputPath {
@@ -1756,30 +1787,41 @@
 	fullPath string
 }
 
-func (p *InstallPath) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
-	encoder := gob.NewEncoder(w)
-	err := errors.Join(encoder.Encode(p.basePath), encoder.Encode(p.soongOutDir),
-		encoder.Encode(p.partitionDir), encoder.Encode(p.partition),
-		encoder.Encode(p.makePath), encoder.Encode(p.fullPath))
-	if err != nil {
-		return nil, err
-	}
+type installPathGob struct {
+	basePath
+	SoongOutDir  string
+	PartitionDir string
+	Partition    string
+	MakePath     bool
+	FullPath     string
+}
 
-	return w.Bytes(), nil
+func (p *InstallPath) ToGob() *installPathGob {
+	return &installPathGob{
+		basePath:     p.basePath,
+		SoongOutDir:  p.soongOutDir,
+		PartitionDir: p.partitionDir,
+		Partition:    p.partition,
+		MakePath:     p.makePath,
+		FullPath:     p.fullPath,
+	}
+}
+
+func (p *InstallPath) FromGob(data *installPathGob) {
+	p.basePath = data.basePath
+	p.soongOutDir = data.SoongOutDir
+	p.partitionDir = data.PartitionDir
+	p.partition = data.Partition
+	p.makePath = data.MakePath
+	p.fullPath = data.FullPath
+}
+
+func (p InstallPath) GobEncode() ([]byte, error) {
+	return blueprint.CustomGobEncode[installPathGob](&p)
 }
 
 func (p *InstallPath) GobDecode(data []byte) error {
-	r := bytes.NewBuffer(data)
-	decoder := gob.NewDecoder(r)
-	err := errors.Join(decoder.Decode(&p.basePath), decoder.Decode(&p.soongOutDir),
-		decoder.Decode(&p.partitionDir), decoder.Decode(&p.partition),
-		decoder.Decode(&p.makePath), decoder.Decode(&p.fullPath))
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return blueprint.CustomGobDecode[installPathGob](data, p)
 }
 
 // Will panic if called from outside a test environment.
diff --git a/android/provider.go b/android/provider.go
index 5ded4cc..81d17a1 100644
--- a/android/provider.go
+++ b/android/provider.go
@@ -24,7 +24,7 @@
 // OtherModuleProviderContext is a helper interface that accepts ModuleContext, BottomUpMutatorContext, or
 // TopDownMutatorContext.
 func OtherModuleProvider[K any](ctx OtherModuleProviderContext, module blueprint.Module, provider blueprint.ProviderKey[K]) (K, bool) {
-	value, ok := ctx.otherModuleProvider(module, provider)
+	value, ok := ctx.otherModuleProvider(getWrappedModule(module), provider)
 	if !ok {
 		var k K
 		return k, false
diff --git a/android/register.go b/android/register.go
index eb6a35e..2ce6025 100644
--- a/android/register.go
+++ b/android/register.go
@@ -235,6 +235,7 @@
 
 	PreDepsMutators(f RegisterMutatorFunc)
 	PostDepsMutators(f RegisterMutatorFunc)
+	PostApexMutators(f RegisterMutatorFunc)
 	FinalDepsMutators(f RegisterMutatorFunc)
 }
 
@@ -326,6 +327,10 @@
 	PostDepsMutators(f)
 }
 
+func (ctx *initRegistrationContext) PostApexMutators(f RegisterMutatorFunc) {
+	PostApexMutators(f)
+}
+
 func (ctx *initRegistrationContext) FinalDepsMutators(f RegisterMutatorFunc) {
 	FinalDepsMutators(f)
 }
diff --git a/android/testing.go b/android/testing.go
index 196b22e..7440869 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -197,8 +197,8 @@
 
 type TestContext struct {
 	*Context
-	preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
-	NameResolver                          *NameResolver
+	preArch, preDeps, postDeps, postApex, finalDeps []RegisterMutatorFunc
+	NameResolver                                    *NameResolver
 
 	// The list of singletons registered for the test.
 	singletons sortableComponents
@@ -229,6 +229,10 @@
 	ctx.postDeps = append(ctx.postDeps, f)
 }
 
+func (ctx *TestContext) PostApexMutators(f RegisterMutatorFunc) {
+	ctx.postApex = append(ctx.postApex, f)
+}
+
 func (ctx *TestContext) FinalDepsMutators(f RegisterMutatorFunc) {
 	ctx.finalDeps = append(ctx.finalDeps, f)
 }
@@ -449,7 +453,7 @@
 func (ctx *TestContext) Register() {
 	globalOrder := globallyRegisteredComponentsOrder()
 
-	mutators := collateRegisteredMutators(ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.finalDeps)
+	mutators := collateRegisteredMutators(ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.postApex, ctx.finalDeps)
 	// Ensure that the mutators used in the test are in the same order as they are used at runtime.
 	globalOrder.mutatorOrder.enforceOrdering(mutators)
 	mutators.registerAll(ctx.Context)
diff --git a/android/variable.go b/android/variable.go
index 1aaa70a..417ba89 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -522,6 +522,10 @@
 	DeviceProductCompatibilityMatrixFile   []string `json:",omitempty"`
 
 	PartitionVarsForSoongMigrationOnlyDoNotUse PartitionVariables
+
+	ExtraAllowedDepsTxt *string `json:",omitempty"`
+
+	AdbKeys *string `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
diff --git a/apex/apex.go b/apex/apex.go
index 63b8d38..d3e7eee 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -29,6 +29,7 @@
 	"android/soong/android"
 	"android/soong/bpf"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/java"
@@ -395,7 +396,7 @@
 
 	// Apex Container package name. Override value for attribute package:name in
 	// AndroidManifest.xml
-	Package_name string
+	Package_name proptools.Configurable[string]
 
 	// A txt file containing list of files that are allowed to be included in this APEX.
 	Allowed_files *string `android:"path"`
@@ -1110,23 +1111,6 @@
 	if am, ok := mctx.Module().(android.ApexModule); ok {
 		android.ApexInfoMutator(mctx, am)
 	}
-	enforceAppUpdatability(mctx)
-}
-
-// enforceAppUpdatability propagates updatable=true to apps of updatable apexes
-func enforceAppUpdatability(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled(mctx) {
-		return
-	}
-	if apex, ok := mctx.Module().(*apexBundle); ok && apex.Updatable() {
-		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
-		mctx.VisitDirectDeps(func(module android.Module) {
-			// ignore android_test_app
-			if app, ok := module.(*java.AndroidApp); ok {
-				app.SetUpdatable(true)
-			}
-		})
-	}
 }
 
 // TODO: b/215736885 Whittle the denylist
@@ -1872,6 +1856,7 @@
 	a.CheckMinSdkVersion(ctx)
 	a.checkStaticLinkingToStubLibraries(ctx)
 	a.checkStaticExecutables(ctx)
+	a.enforceAppUpdatability(ctx)
 	if len(a.properties.Tests) > 0 && !a.testApex {
 		ctx.PropertyErrorf("tests", "property allowed only in apex_test module type")
 		return false
@@ -1935,6 +1920,32 @@
 	})
 }
 
+// enforcePartitionTagOnApexSystemServerJar checks that the partition tags of an apex system server jar  matches
+// the partition tags of the top-level apex.
+// e.g. if the top-level apex sets system_ext_specific to true, the javalib must set this property to true as well.
+// This check ensures that the dexpreopt artifacts of the apex system server jar is installed in the same partition
+// as the apex.
+func (a *apexBundle) enforcePartitionTagOnApexSystemServerJar(ctx android.ModuleContext) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	ctx.VisitDirectDepsWithTag(sscpfTag, func(child android.Module) {
+		info, ok := android.OtherModuleProvider(ctx, child, java.LibraryNameToPartitionInfoProvider)
+		if !ok {
+			ctx.ModuleErrorf("Could not find partition info of apex system server jars.")
+		}
+		apexPartition := ctx.Module().PartitionTag(ctx.DeviceConfig())
+		for javalib, javalibPartition := range info.LibraryNameToPartition {
+			if !global.AllApexSystemServerJars(ctx).ContainsJar(javalib) {
+				continue // not an apex system server jar
+			}
+			if apexPartition != javalibPartition {
+				ctx.ModuleErrorf(`
+%s is an apex systemserver jar, but its partition does not match the partition of its containing apex. Expected %s, Got %s`,
+					javalib, apexPartition, javalibPartition)
+			}
+		}
+	})
+}
+
 func (a *apexBundle) depVisitor(vctx *visitorContext, ctx android.ModuleContext, child, parent android.Module) bool {
 	depTag := ctx.OtherModuleDependencyTag(child)
 	if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
@@ -2357,6 +2368,7 @@
 	a.required = append(a.required, a.VintfFragmentModuleNames(ctx)...)
 
 	a.setOutputFiles(ctx)
+	a.enforcePartitionTagOnApexSystemServerJar(ctx)
 }
 
 // Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2397,6 +2409,24 @@
 	}
 }
 
+// enforceAppUpdatability propagates updatable=true to apps of updatable apexes
+func (a *apexBundle) enforceAppUpdatability(mctx android.ModuleContext) {
+	if !a.Enabled(mctx) {
+		return
+	}
+	if a.Updatable() {
+		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
+		mctx.VisitDirectDeps(func(module android.Module) {
+			if appInfo, ok := android.OtherModuleProvider(mctx, module, java.AppInfoProvider); ok {
+				// ignore android_test_app
+				if !appInfo.TestHelperApp && !appInfo.Updatable {
+					mctx.ModuleErrorf("app dependency %s must have updatable: true", mctx.OtherModuleName(module))
+				}
+			}
+		})
+	}
+}
+
 // apexBootclasspathFragmentFiles returns the list of apexFile structures defining the files that
 // the bootclasspath_fragment contributes to the apex.
 func apexBootclasspathFragmentFiles(ctx android.ModuleContext, module blueprint.Module) []apexFile {
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index f405cb2..00dd446 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -18,6 +18,7 @@
 
 import (
 	"encoding/json"
+	"strings"
 
 	"github.com/google/blueprint"
 
@@ -58,9 +59,9 @@
 
 	// Diff two given lists while ignoring comments in the allowed deps file.
 	diffAllowedApexDepsInfoRule = pctx.AndroidStaticRule("diffAllowedApexDepsInfoRule", blueprint.RuleParams{
-		Description: "Diff ${allowed_deps} and ${new_allowed_deps}",
+		Description: "Diff ${allowed_deps_list} and ${new_allowed_deps}",
 		Command: `
-			if grep -v '^#' ${allowed_deps} | diff -B - ${new_allowed_deps}; then
+			if grep -v -h '^#' ${allowed_deps_list} | sort -u -f| diff -B -u - ${new_allowed_deps}; then
 			   touch ${out};
 			else
 				echo -e "\n******************************";
@@ -81,10 +82,15 @@
 				exit 1;
 			fi;
 		`,
-	}, "allowed_deps", "new_allowed_deps")
+	}, "allowed_deps_list", "new_allowed_deps")
 )
 
 func (s *apexDepsInfoSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	allowedDepsSources := []android.OptionalPath{android.ExistentPathForSource(ctx, "packages/modules/common/build/allowed_deps.txt")}
+	extraAllowedDepsPath := ctx.Config().ExtraAllowedDepsTxt()
+	if extraAllowedDepsPath != "" {
+		allowedDepsSources = append(allowedDepsSources, android.ExistentPathForSource(ctx, extraAllowedDepsPath))
+	}
 	updatableFlatLists := android.Paths{}
 	ctx.VisitAllModules(func(module android.Module) {
 		if binaryInfo, ok := module.(android.ApexBundleDepsInfoIntf); ok {
@@ -96,37 +102,42 @@
 			}
 		}
 	})
-
-	allowedDepsSource := android.ExistentPathForSource(ctx, "packages/modules/common/build/allowed_deps.txt")
 	newAllowedDeps := android.PathForOutput(ctx, "apex", "depsinfo", "new-allowed-deps.txt")
 	s.allowedApexDepsInfoCheckResult = android.PathForOutput(ctx, newAllowedDeps.Rel()+".check")
-
-	if !allowedDepsSource.Valid() {
+	hasOneValidDepsPath := false
+	for _, allowedDepsSource := range allowedDepsSources {
+		if allowedDepsSource.Valid() {
+			hasOneValidDepsPath = true
+			updatableFlatLists = append(updatableFlatLists, allowedDepsSource.Path())
+		}
+	}
+	allowedDepsStrList := make([]string, len(allowedDepsSources))
+	for _, value := range allowedDepsSources {
+		allowedDepsStrList = append(allowedDepsStrList, value.String())
+	}
+	allowedDepsListString := strings.Join(allowedDepsStrList, " ")
+	if !hasOneValidDepsPath {
 		// Unbundled projects may not have packages/modules/common/ checked out; ignore those.
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Touch,
 			Output: s.allowedApexDepsInfoCheckResult,
 		})
 	} else {
-		allowedDeps := allowedDepsSource.Path()
-
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   generateApexDepsInfoFilesRule,
-			Inputs: append(updatableFlatLists, allowedDeps),
+			Inputs: updatableFlatLists,
 			Output: newAllowedDeps,
 		})
-
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   diffAllowedApexDepsInfoRule,
 			Input:  newAllowedDeps,
 			Output: s.allowedApexDepsInfoCheckResult,
 			Args: map[string]string{
-				"allowed_deps":     allowedDeps.String(),
-				"new_allowed_deps": newAllowedDeps.String(),
+				"allowed_deps_list": allowedDepsListString,
+				"new_allowed_deps":  newAllowedDeps.String(),
 			},
 		})
 	}
-
 	ctx.Phony("apex-allowed-deps-check", s.allowedApexDepsInfoCheckResult)
 }
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index b372d7f..1d2f3fb 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2185,6 +2185,151 @@
 		flatlist, "yourlib(minSdkVersion:29)")
 }
 
+func TestTrackCustomAllowedDepsInvalidDefaultTxt(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: true,
+			native_shared_libs: [
+				"mylib",
+				"yourlib",
+			],
+			min_sdk_version: "29",
+		}
+
+		apex {
+			name: "myapex2",
+			key: "myapex.key",
+			updatable: false,
+			native_shared_libs: ["yourlib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libbar"],
+			min_sdk_version: "29",
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "libbar",
+			stubs: { versions: ["29", "30"] },
+		}
+
+		cc_library {
+			name: "yourlib",
+			srcs: ["mylib.cpp"],
+			min_sdk_version: "29",
+			apex_available: ["myapex", "myapex2", "//apex_available:platform"],
+		}
+	`, withFiles(android.MockFS{
+		"packages/modules/common/build/custom_allowed_deps.txt": nil,
+	}),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.ExtraAllowedDepsTxt = proptools.StringPtr("packages/modules/common/build/custom_allowed_deps.txt")
+			},
+		))
+
+	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
+	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
+	android.AssertStringListContains(t, "updatable myapex should generate depsinfo file", inputs,
+		"out/soong/.intermediates/myapex/android_common_myapex/depsinfo/flatlist.txt")
+	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
+		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
+
+	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
+	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
+		myapex.Output("depsinfo/flatlist.txt")), "\n")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
+		flatlist, "mylib:(minSdkVersion:29)")
+	android.AssertStringListContains(t, "track platform-available lib",
+		flatlist, "yourlib(minSdkVersion:29)")
+}
+
+func TestTrackCustomAllowedDepsWithDefaultTxt(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: true,
+			native_shared_libs: [
+				"mylib",
+				"yourlib",
+			],
+			min_sdk_version: "29",
+		}
+
+		apex {
+			name: "myapex2",
+			key: "myapex.key",
+			updatable: false,
+			native_shared_libs: ["yourlib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libbar"],
+			min_sdk_version: "29",
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "libbar",
+			stubs: { versions: ["29", "30"] },
+		}
+
+		cc_library {
+			name: "yourlib",
+			srcs: ["mylib.cpp"],
+			min_sdk_version: "29",
+			apex_available: ["myapex", "myapex2", "//apex_available:platform"],
+		}
+	`, withFiles(android.MockFS{
+		"packages/modules/common/build/custom_allowed_deps.txt": nil,
+		"packages/modules/common/build/allowed_deps.txt":        nil,
+	}),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.ExtraAllowedDepsTxt = proptools.StringPtr("packages/modules/common/build/custom_allowed_deps.txt")
+			},
+		))
+
+	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
+	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
+	android.AssertStringListContains(t, "updatable myapex should generate depsinfo file", inputs,
+		"out/soong/.intermediates/myapex/android_common_myapex/depsinfo/flatlist.txt")
+	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
+		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
+
+	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
+	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
+		myapex.Output("depsinfo/flatlist.txt")), "\n")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
+		flatlist, "mylib:(minSdkVersion:29)")
+	android.AssertStringListContains(t, "track platform-available lib",
+		flatlist, "yourlib(minSdkVersion:29)")
+}
+
 func TestTrackAllowedDeps_SkipWithoutAllowedDepsTxt(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -5450,7 +5595,7 @@
 			android.PrepareForTestWithAllowMissingDependencies,
 			android.FixtureMergeMockFs(map[string][]byte{
 				"build/soong/scripts/check_boot_jars/package_allowed_list.txt": nil,
-				"frameworks/base/boot/boot-profile.txt":                      nil,
+				"frameworks/base/boot/boot-profile.txt":                        nil,
 			}),
 		)
 
@@ -9876,7 +10021,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			updatable: %v,
+			updatable: true,
 			apps: [
 				"myapp",
 			],
@@ -9887,7 +10032,6 @@
 		}
 		android_app {
 			name: "myapp",
-			updatable: %v,
 			apex_available: [
 				"myapex",
 			],
@@ -9895,42 +10039,10 @@
 			min_sdk_version: "30",
 		}
 		`
-	testCases := []struct {
-		name                      string
-		apex_is_updatable_bp      bool
-		app_is_updatable_bp       bool
-		app_is_updatable_expected bool
-	}{
-		{
-			name:                      "Non-updatable apex respects updatable property of non-updatable app",
-			apex_is_updatable_bp:      false,
-			app_is_updatable_bp:       false,
-			app_is_updatable_expected: false,
-		},
-		{
-			name:                      "Non-updatable apex respects updatable property of updatable app",
-			apex_is_updatable_bp:      false,
-			app_is_updatable_bp:       true,
-			app_is_updatable_expected: true,
-		},
-		{
-			name:                      "Updatable apex respects updatable property of updatable app",
-			apex_is_updatable_bp:      true,
-			app_is_updatable_bp:       true,
-			app_is_updatable_expected: true,
-		},
-		{
-			name:                      "Updatable apex sets updatable=true on non-updatable app",
-			apex_is_updatable_bp:      true,
-			app_is_updatable_bp:       false,
-			app_is_updatable_expected: true,
-		},
-	}
-	for _, testCase := range testCases {
-		result := testApex(t, fmt.Sprintf(bp, testCase.apex_is_updatable_bp, testCase.app_is_updatable_bp))
-		myapp := result.ModuleForTests("myapp", "android_common").Module().(*java.AndroidApp)
-		android.AssertBoolEquals(t, testCase.name, testCase.app_is_updatable_expected, myapp.Updatable())
-	}
+	_ = android.GroupFixturePreparers(
+		prepareForApexTest,
+	).ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern("app dependency myapp must have updatable: true")).
+		RunTestWithBp(t, bp)
 }
 
 func TestTrimmedApex(t *testing.T) {
@@ -11699,6 +11811,52 @@
 			sdk_version: "core_current",
 			min_sdk_version: "30",
 			manifest: "AndroidManifest.xml",
+			updatable: true,
 		}
        `)
 }
+
+// If an apex sets system_ext_specific: true, its systemserverclasspath libraries must set this property as well.
+func TestApexSSCPJarMustBeInSamePartitionAsApex(t *testing.T) {
+	testApexError(t, `foo is an apex systemserver jar, but its partition does not match the partition of its containing apex`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			min_sdk_version: "29",
+			updatable: true,
+			system_ext_specific: true,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			min_sdk_version: "29",
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+			sdk_version: "current",
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+}
diff --git a/apex/builder.go b/apex/builder.go
index 4db20e9..371d7d5 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -1074,8 +1074,9 @@
 		}
 		return ""
 	}
-	if a.overridableProperties.Package_name != "" {
-		return a.overridableProperties.Package_name
+	packageNameFromProp := a.overridableProperties.Package_name.GetOrDefault(ctx, "")
+	if packageNameFromProp != "" {
+		return packageNameFromProp
 	}
 	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
 	if overridden {
diff --git a/apex/vndk.go b/apex/vndk.go
index 3ececc5..5e630c0 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -20,6 +20,7 @@
 	"android/soong/android"
 	"android/soong/cc"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -66,7 +67,14 @@
 		vndkApexName := "com.android.vndk." + vndkVersion
 
 		if mctx.OtherModuleExists(vndkApexName) {
-			mctx.AddReverseDependency(mctx.Module(), sharedLibTag, vndkApexName)
+			// Reverse dependencies must exactly specify the variant they want, starting from the
+			// current module's variant. But unlike cc modules, the vndk apex doesn't have
+			// arch/image/link variations, so we explicitly remove them here.
+			mctx.AddReverseVariationDependency([]blueprint.Variation{
+				{Mutator: "arch", Variation: "common"},
+				{Mutator: "image", Variation: ""},
+				{Mutator: "link", Variation: ""},
+			}, sharedLibTag, vndkApexName)
 		}
 	} else if a, ok := mctx.Module().(*apexBundle); ok && a.vndkApex {
 		if a.IsNativeBridgeSupported() {
diff --git a/bazel/Android.bp b/bazel/Android.bp
deleted file mode 100644
index f8273a8..0000000
--- a/bazel/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-bazel",
-    pkgPath: "android/soong/bazel",
-    srcs: [
-        "configurability.go",
-        "properties.go",
-        "testing.go",
-    ],
-    testSrcs: [
-        "properties_test.go",
-    ],
-    pluginFor: [
-        "soong_build",
-    ],
-    deps: [
-        "blueprint",
-    ],
-}
diff --git a/bazel/configurability.go b/bazel/configurability.go
deleted file mode 100644
index 3a65614..0000000
--- a/bazel/configurability.go
+++ /dev/null
@@ -1,391 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bazel
-
-import (
-	"fmt"
-	"math"
-	"sort"
-	"strings"
-)
-
-const (
-	// ArchType names in arch.go
-	archArm     = "arm"
-	archArm64   = "arm64"
-	archRiscv64 = "riscv64"
-	archX86     = "x86"
-	archX86_64  = "x86_64"
-
-	// OsType names in arch.go
-	OsAndroid     = "android"
-	OsDarwin      = "darwin"
-	OsLinux       = "linux_glibc"
-	osLinuxMusl   = "linux_musl"
-	osLinuxBionic = "linux_bionic"
-	OsWindows     = "windows"
-
-	// Targets in arch.go
-	osArchAndroidArm        = "android_arm"
-	OsArchAndroidArm64      = "android_arm64"
-	osArchAndroidRiscv64    = "android_riscv64"
-	osArchAndroidX86        = "android_x86"
-	osArchAndroidX86_64     = "android_x86_64"
-	osArchDarwinArm64       = "darwin_arm64"
-	osArchDarwinX86_64      = "darwin_x86_64"
-	osArchLinuxX86          = "linux_glibc_x86"
-	osArchLinuxX86_64       = "linux_glibc_x86_64"
-	osArchLinuxMuslArm      = "linux_musl_arm"
-	osArchLinuxMuslArm64    = "linux_musl_arm64"
-	osArchLinuxMuslX86      = "linux_musl_x86"
-	osArchLinuxMuslX86_64   = "linux_musl_x86_64"
-	osArchLinuxBionicArm64  = "linux_bionic_arm64"
-	osArchLinuxBionicX86_64 = "linux_bionic_x86_64"
-	osArchWindowsX86        = "windows_x86"
-	osArchWindowsX86_64     = "windows_x86_64"
-
-	// This is the string representation of the default condition wherever a
-	// configurable attribute is used in a select statement, i.e.
-	// //conditions:default for Bazel.
-	//
-	// This is consistently named "conditions_default" to mirror the Soong
-	// config variable default key in an Android.bp file, although there's no
-	// integration with Soong config variables (yet).
-	ConditionsDefaultConfigKey = "conditions_default"
-
-	ConditionsDefaultSelectKey = "//conditions:default"
-
-	productVariableBazelPackage = "//build/bazel/product_config/config_settings"
-
-	AndroidAndInApex = "android-in_apex"
-	AndroidPlatform  = "system"
-	Unbundled_app    = "unbundled_app"
-
-	InApex  = "in_apex"
-	NonApex = "non_apex"
-
-	ErrorproneDisabled = "errorprone_disabled"
-	// TODO: b/294868620 - Remove when completing the bug
-	SanitizersEnabled = "sanitizers_enabled"
-)
-
-func PowerSetWithoutEmptySet[T any](items []T) [][]T {
-	resultSize := int(math.Pow(2, float64(len(items))))
-	powerSet := make([][]T, 0, resultSize-1)
-	for i := 1; i < resultSize; i++ {
-		combination := make([]T, 0)
-		for j := 0; j < len(items); j++ {
-			if (i>>j)%2 == 1 {
-				combination = append(combination, items[j])
-			}
-		}
-		powerSet = append(powerSet, combination)
-	}
-	return powerSet
-}
-
-func createPlatformArchMap() map[string]string {
-	// Copy of archFeatures from android/arch_list.go because the bazel
-	// package can't access the android package
-	archFeatures := map[string][]string{
-		"arm": {},
-		"arm64": {
-			"dotprod",
-		},
-		"riscv64": {},
-		"x86": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"avx512",
-			"popcnt",
-			"movbe",
-		},
-		"x86_64": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"avx512",
-			"popcnt",
-		},
-	}
-	result := make(map[string]string)
-	for arch, allFeatures := range archFeatures {
-		result[arch] = "//build/bazel_common_rules/platforms/arch:" + arch
-		// Sometimes we want to select on multiple features being active, so
-		// add the power set of all possible features to the map. More details
-		// in android.ModuleBase.GetArchVariantProperties
-		for _, features := range PowerSetWithoutEmptySet(allFeatures) {
-			sort.Strings(features)
-			archFeaturesName := arch + "-" + strings.Join(features, "-")
-			result[archFeaturesName] = "//build/bazel/platforms/arch/variants:" + archFeaturesName
-		}
-	}
-	result[ConditionsDefaultConfigKey] = ConditionsDefaultSelectKey
-	return result
-}
-
-var (
-	// These are the list of OSes and architectures with a Bazel config_setting
-	// and constraint value equivalent. These exist in arch.go, but the android
-	// package depends on the bazel package, so a cyclic dependency prevents
-	// using those variables here.
-
-	// A map of architectures to the Bazel label of the constraint_value
-	// for the @platforms//cpu:cpu constraint_setting
-	platformArchMap = createPlatformArchMap()
-
-	// A map of target operating systems to the Bazel label of the
-	// constraint_value for the @platforms//os:os constraint_setting
-	platformOsMap = map[string]string{
-		OsAndroid:                  "//build/bazel_common_rules/platforms/os:android",
-		OsDarwin:                   "//build/bazel_common_rules/platforms/os:darwin",
-		OsLinux:                    "//build/bazel_common_rules/platforms/os:linux_glibc",
-		osLinuxMusl:                "//build/bazel_common_rules/platforms/os:linux_musl",
-		osLinuxBionic:              "//build/bazel_common_rules/platforms/os:linux_bionic",
-		OsWindows:                  "//build/bazel_common_rules/platforms/os:windows",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
-	}
-
-	platformOsArchMap = map[string]string{
-		osArchAndroidArm:           "//build/bazel_common_rules/platforms/os_arch:android_arm",
-		OsArchAndroidArm64:         "//build/bazel_common_rules/platforms/os_arch:android_arm64",
-		osArchAndroidRiscv64:       "//build/bazel_common_rules/platforms/os_arch:android_riscv64",
-		osArchAndroidX86:           "//build/bazel_common_rules/platforms/os_arch:android_x86",
-		osArchAndroidX86_64:        "//build/bazel_common_rules/platforms/os_arch:android_x86_64",
-		osArchDarwinArm64:          "//build/bazel_common_rules/platforms/os_arch:darwin_arm64",
-		osArchDarwinX86_64:         "//build/bazel_common_rules/platforms/os_arch:darwin_x86_64",
-		osArchLinuxX86:             "//build/bazel_common_rules/platforms/os_arch:linux_glibc_x86",
-		osArchLinuxX86_64:          "//build/bazel_common_rules/platforms/os_arch:linux_glibc_x86_64",
-		osArchLinuxMuslArm:         "//build/bazel_common_rules/platforms/os_arch:linux_musl_arm",
-		osArchLinuxMuslArm64:       "//build/bazel_common_rules/platforms/os_arch:linux_musl_arm64",
-		osArchLinuxMuslX86:         "//build/bazel_common_rules/platforms/os_arch:linux_musl_x86",
-		osArchLinuxMuslX86_64:      "//build/bazel_common_rules/platforms/os_arch:linux_musl_x86_64",
-		osArchLinuxBionicArm64:     "//build/bazel_common_rules/platforms/os_arch:linux_bionic_arm64",
-		osArchLinuxBionicX86_64:    "//build/bazel_common_rules/platforms/os_arch:linux_bionic_x86_64",
-		osArchWindowsX86:           "//build/bazel_common_rules/platforms/os_arch:windows_x86",
-		osArchWindowsX86_64:        "//build/bazel_common_rules/platforms/os_arch:windows_x86_64",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
-	}
-
-	// Map where keys are OsType names, and values are slices containing the archs
-	// that that OS supports.
-	// These definitions copied from arch.go.
-	// TODO(cparsons): Source from arch.go; this task is nontrivial, as it currently results
-	// in a cyclic dependency.
-	osToArchMap = map[string][]string{
-		OsAndroid:     {archArm, archArm64, archRiscv64, archX86, archX86_64},
-		OsLinux:       {archX86, archX86_64},
-		osLinuxMusl:   {archX86, archX86_64},
-		OsDarwin:      {archArm64, archX86_64},
-		osLinuxBionic: {archArm64, archX86_64},
-		// TODO(cparsons): According to arch.go, this should contain archArm, archArm64, as well.
-		OsWindows: {archX86, archX86_64},
-	}
-
-	osAndInApexMap = map[string]string{
-		AndroidAndInApex:           "//build/bazel/rules/apex:android-in_apex",
-		AndroidPlatform:            "//build/bazel/rules/apex:system",
-		Unbundled_app:              "//build/bazel/rules/apex:unbundled_app",
-		OsDarwin:                   "//build/bazel_common_rules/platforms/os:darwin",
-		OsLinux:                    "//build/bazel_common_rules/platforms/os:linux_glibc",
-		osLinuxMusl:                "//build/bazel_common_rules/platforms/os:linux_musl",
-		osLinuxBionic:              "//build/bazel_common_rules/platforms/os:linux_bionic",
-		OsWindows:                  "//build/bazel_common_rules/platforms/os:windows",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey,
-	}
-
-	inApexMap = map[string]string{
-		InApex:                     "//build/bazel/rules/apex:in_apex",
-		NonApex:                    "//build/bazel/rules/apex:non_apex",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey,
-	}
-
-	errorProneMap = map[string]string{
-		ErrorproneDisabled:         "//build/bazel/rules/java/errorprone:errorprone_globally_disabled",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey,
-	}
-
-	// TODO: b/294868620 - Remove when completing the bug
-	sanitizersEnabledMap = map[string]string{
-		SanitizersEnabled:          "//build/bazel/rules/cc:sanitizers_enabled",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey,
-	}
-)
-
-// basic configuration types
-type configurationType int
-
-const (
-	noConfig configurationType = iota
-	arch
-	os
-	osArch
-	productVariables
-	osAndInApex
-	inApex
-	errorProneDisabled
-	// TODO: b/294868620 - Remove when completing the bug
-	sanitizersEnabled
-)
-
-func osArchString(os string, arch string) string {
-	return fmt.Sprintf("%s_%s", os, arch)
-}
-
-func (ct configurationType) String() string {
-	return map[configurationType]string{
-		noConfig:           "no_config",
-		arch:               "arch",
-		os:                 "os",
-		osArch:             "arch_os",
-		productVariables:   "product_variables",
-		osAndInApex:        "os_in_apex",
-		inApex:             "in_apex",
-		errorProneDisabled: "errorprone_disabled",
-		// TODO: b/294868620 - Remove when completing the bug
-		sanitizersEnabled: "sanitizers_enabled",
-	}[ct]
-}
-
-func (ct configurationType) validateConfig(config string) {
-	switch ct {
-	case noConfig:
-		if config != "" {
-			panic(fmt.Errorf("Cannot specify config with %s, but got %s", ct, config))
-		}
-	case arch:
-		if _, ok := platformArchMap[config]; !ok {
-			panic(fmt.Errorf("Unknown arch: %s", config))
-		}
-	case os:
-		if _, ok := platformOsMap[config]; !ok {
-			panic(fmt.Errorf("Unknown os: %s", config))
-		}
-	case osArch:
-		if _, ok := platformOsArchMap[config]; !ok {
-			panic(fmt.Errorf("Unknown os+arch: %s", config))
-		}
-	case productVariables:
-		// do nothing
-	case osAndInApex:
-		// do nothing
-		// this axis can contain additional per-apex keys
-	case inApex:
-		if _, ok := inApexMap[config]; !ok {
-			panic(fmt.Errorf("Unknown in_apex config: %s", config))
-		}
-	case errorProneDisabled:
-		if _, ok := errorProneMap[config]; !ok {
-			panic(fmt.Errorf("Unknown errorprone config: %s", config))
-		}
-	// TODO: b/294868620 - Remove when completing the bug
-	case sanitizersEnabled:
-		if _, ok := sanitizersEnabledMap[config]; !ok {
-			panic(fmt.Errorf("Unknown sanitizers_enabled config: %s", config))
-		}
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
-	}
-}
-
-// SelectKey returns the Bazel select key for a given configurationType and config string.
-func (ca ConfigurationAxis) SelectKey(config string) string {
-	ca.validateConfig(config)
-	switch ca.configurationType {
-	case noConfig:
-		panic(fmt.Errorf("SelectKey is unnecessary for noConfig ConfigurationType "))
-	case arch:
-		return platformArchMap[config]
-	case os:
-		return platformOsMap[config]
-	case osArch:
-		return platformOsArchMap[config]
-	case productVariables:
-		if config == ConditionsDefaultConfigKey {
-			return ConditionsDefaultSelectKey
-		}
-		return fmt.Sprintf("%s:%s", productVariableBazelPackage, config)
-	case osAndInApex:
-		if ret, exists := osAndInApexMap[config]; exists {
-			return ret
-		}
-		return config
-	case inApex:
-		return inApexMap[config]
-	case errorProneDisabled:
-		return errorProneMap[config]
-	// TODO: b/294868620 - Remove when completing the bug
-	case sanitizersEnabled:
-		return sanitizersEnabledMap[config]
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ca.configurationType))
-	}
-}
-
-var (
-	// Indicating there is no configuration axis
-	NoConfigAxis = ConfigurationAxis{configurationType: noConfig}
-	// An axis for architecture-specific configurations
-	ArchConfigurationAxis = ConfigurationAxis{configurationType: arch}
-	// An axis for os-specific configurations
-	OsConfigurationAxis = ConfigurationAxis{configurationType: os}
-	// An axis for arch+os-specific configurations
-	OsArchConfigurationAxis = ConfigurationAxis{configurationType: osArch}
-	// An axis for os+in_apex-specific configurations
-	OsAndInApexAxis = ConfigurationAxis{configurationType: osAndInApex}
-	// An axis for in_apex-specific configurations
-	InApexAxis = ConfigurationAxis{configurationType: inApex}
-
-	ErrorProneAxis = ConfigurationAxis{configurationType: errorProneDisabled}
-
-	// TODO: b/294868620 - Remove when completing the bug
-	SanitizersEnabledAxis = ConfigurationAxis{configurationType: sanitizersEnabled}
-)
-
-// ProductVariableConfigurationAxis returns an axis for the given product variable
-func ProductVariableConfigurationAxis(archVariant bool, variable string) ConfigurationAxis {
-	return ConfigurationAxis{
-		configurationType: productVariables,
-		subType:           variable,
-		archVariant:       archVariant,
-	}
-}
-
-// ConfigurationAxis is an independent axis for configuration, there should be no overlap between
-// elements within an axis.
-type ConfigurationAxis struct {
-	configurationType
-	// some configuration types (e.g. productVariables) have multiple independent axes, subType helps
-	// distinguish between them without needing to list all 17 product variables.
-	subType string
-
-	archVariant bool
-}
-
-func (ca *ConfigurationAxis) less(other ConfigurationAxis) bool {
-	if ca.configurationType == other.configurationType {
-		return ca.subType < other.subType
-	}
-	return ca.configurationType < other.configurationType
-}
diff --git a/bazel/properties.go b/bazel/properties.go
deleted file mode 100644
index 9c63bc0..0000000
--- a/bazel/properties.go
+++ /dev/null
@@ -1,1467 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bazel
-
-import (
-	"fmt"
-	"path/filepath"
-	"reflect"
-	"regexp"
-	"sort"
-	"strings"
-
-	"github.com/google/blueprint"
-)
-
-// BazelTargetModuleProperties contain properties and metadata used for
-// Blueprint to BUILD file conversion.
-type BazelTargetModuleProperties struct {
-	// The Bazel rule class for this target.
-	Rule_class string `blueprint:"mutated"`
-
-	// The target label for the bzl file containing the definition of the rule class.
-	Bzl_load_location string `blueprint:"mutated"`
-}
-
-var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
-
-// Label is used to represent a Bazel compatible Label. Also stores the original
-// bp text to support string replacement.
-type Label struct {
-	// The string representation of a Bazel target label. This can be a relative
-	// or fully qualified label. These labels are used for generating BUILD
-	// files with bp2build.
-	Label string
-
-	// The original Soong/Blueprint module name that the label was derived from.
-	// This is used for replacing references to the original name with the new
-	// label, for example in genrule cmds.
-	//
-	// While there is a reversible 1:1 mapping from the module name to Bazel
-	// label with bp2build that could make computing the original module name
-	// from the label automatic, it is not the case for handcrafted targets,
-	// where modules can have a custom label mapping through the { bazel_module:
-	// { label: <label> } } property.
-	//
-	// With handcrafted labels, those modules don't go through bp2build
-	// conversion, but relies on handcrafted targets in the source tree.
-	OriginalModuleName string
-}
-
-// LabelList is used to represent a list of Bazel labels.
-type LabelList struct {
-	Includes []Label
-	Excludes []Label
-}
-
-// MakeLabelList creates a LabelList from a list Label
-func MakeLabelList(labels []Label) LabelList {
-	return LabelList{
-		Includes: labels,
-		Excludes: nil,
-	}
-}
-
-func SortedConfigurationAxes[T any](m map[ConfigurationAxis]T) []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(m))
-	for k := range m {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
-}
-
-// MakeLabelListFromTargetNames creates a LabelList from unqualified target names
-// This is a utiltity function for bp2build converters of Soong modules that have 1:many generated targets
-func MakeLabelListFromTargetNames(targetNames []string) LabelList {
-	labels := []Label{}
-	for _, name := range targetNames {
-		label := Label{Label: ":" + name}
-		labels = append(labels, label)
-	}
-	return MakeLabelList(labels)
-}
-
-func (ll *LabelList) Equals(other LabelList) bool {
-	if len(ll.Includes) != len(other.Includes) || len(ll.Excludes) != len(other.Excludes) {
-		return false
-	}
-	for i, _ := range ll.Includes {
-		if ll.Includes[i] != other.Includes[i] {
-			return false
-		}
-	}
-	for i, _ := range ll.Excludes {
-		if ll.Excludes[i] != other.Excludes[i] {
-			return false
-		}
-	}
-	return true
-}
-
-func (ll *LabelList) IsNil() bool {
-	return ll.Includes == nil && ll.Excludes == nil
-}
-
-func (ll *LabelList) IsEmpty() bool {
-	return len(ll.Includes) == 0 && len(ll.Excludes) == 0
-}
-
-func (ll *LabelList) deepCopy() LabelList {
-	return LabelList{
-		Includes: ll.Includes[:],
-		Excludes: ll.Excludes[:],
-	}
-}
-
-// uniqueParentDirectories returns a list of the unique parent directories for
-// all files in ll.Includes.
-func (ll *LabelList) uniqueParentDirectories() []string {
-	dirMap := map[string]bool{}
-	for _, label := range ll.Includes {
-		dirMap[filepath.Dir(label.Label)] = true
-	}
-	dirs := []string{}
-	for dir := range dirMap {
-		dirs = append(dirs, dir)
-	}
-	return dirs
-}
-
-// Add inserts the label Label at the end of the LabelList.Includes.
-func (ll *LabelList) Add(label *Label) {
-	if label == nil {
-		return
-	}
-	ll.Includes = append(ll.Includes, *label)
-}
-
-// AddExclude inserts the label Label at the end of the LabelList.Excludes.
-func (ll *LabelList) AddExclude(label *Label) {
-	if label == nil {
-		return
-	}
-	ll.Excludes = append(ll.Excludes, *label)
-}
-
-// Append appends the fields of other labelList to the corresponding fields of ll.
-func (ll *LabelList) Append(other LabelList) {
-	if len(ll.Includes) > 0 || len(other.Includes) > 0 {
-		ll.Includes = append(ll.Includes, other.Includes...)
-	}
-	if len(ll.Excludes) > 0 || len(other.Excludes) > 0 {
-		ll.Excludes = append(ll.Excludes, other.Excludes...)
-	}
-}
-
-// Partition splits a LabelList into two LabelLists depending on the return value
-// of the predicate.
-// This function preserves the Includes and Excludes, but it does not provide
-// that information to the partition function.
-func (ll *LabelList) Partition(predicate func(label Label) bool) (LabelList, LabelList) {
-	predicated := LabelList{}
-	unpredicated := LabelList{}
-	for _, include := range ll.Includes {
-		if predicate(include) {
-			predicated.Add(&include)
-		} else {
-			unpredicated.Add(&include)
-		}
-	}
-	for _, exclude := range ll.Excludes {
-		if predicate(exclude) {
-			predicated.AddExclude(&exclude)
-		} else {
-			unpredicated.AddExclude(&exclude)
-		}
-	}
-	return predicated, unpredicated
-}
-
-// UniqueSortedBazelLabels takes a []Label and deduplicates the labels, and returns
-// the slice in a sorted order.
-func UniqueSortedBazelLabels(originalLabels []Label) []Label {
-	uniqueLabels := FirstUniqueBazelLabels(originalLabels)
-	sort.SliceStable(uniqueLabels, func(i, j int) bool {
-		return uniqueLabels[i].Label < uniqueLabels[j].Label
-	})
-	return uniqueLabels
-}
-
-func FirstUniqueBazelLabels(originalLabels []Label) []Label {
-	var labels []Label
-	found := make(map[string]bool, len(originalLabels))
-	for _, l := range originalLabels {
-		if _, ok := found[l.Label]; ok {
-			continue
-		}
-		labels = append(labels, l)
-		found[l.Label] = true
-	}
-	return labels
-}
-
-func FirstUniqueBazelLabelList(originalLabelList LabelList) LabelList {
-	var uniqueLabelList LabelList
-	uniqueLabelList.Includes = FirstUniqueBazelLabels(originalLabelList.Includes)
-	uniqueLabelList.Excludes = FirstUniqueBazelLabels(originalLabelList.Excludes)
-	return uniqueLabelList
-}
-
-func UniqueSortedBazelLabelList(originalLabelList LabelList) LabelList {
-	var uniqueLabelList LabelList
-	uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
-	uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
-	return uniqueLabelList
-}
-
-// Subtract needle from haystack
-func SubtractStrings(haystack []string, needle []string) []string {
-	// This is really a set
-	needleMap := make(map[string]bool)
-	for _, s := range needle {
-		needleMap[s] = true
-	}
-
-	var strings []string
-	for _, s := range haystack {
-		if exclude := needleMap[s]; !exclude {
-			strings = append(strings, s)
-		}
-	}
-
-	return strings
-}
-
-// Subtract needle from haystack
-func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
-	// This is really a set
-	needleMap := make(map[Label]bool)
-	for _, s := range needle {
-		needleMap[s] = true
-	}
-
-	var labels []Label
-	for _, label := range haystack {
-		if exclude := needleMap[label]; !exclude {
-			labels = append(labels, label)
-		}
-	}
-
-	return labels
-}
-
-// Appends two LabelLists, returning the combined list.
-func AppendBazelLabelLists(a LabelList, b LabelList) LabelList {
-	var result LabelList
-	result.Includes = append(a.Includes, b.Includes...)
-	result.Excludes = append(a.Excludes, b.Excludes...)
-	return result
-}
-
-// Subtract needle from haystack
-func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
-	var result LabelList
-	result.Includes = SubtractBazelLabels(haystack.Includes, needle.Includes)
-	// NOTE: Excludes are intentionally not subtracted
-	result.Excludes = haystack.Excludes
-	return result
-}
-
-// FirstUniqueBazelLabelListAttribute takes a LabelListAttribute and makes the LabelList for
-// each axis/configuration by keeping the first instance of a Label and omitting all subsequent
-// repetitions.
-func FirstUniqueBazelLabelListAttribute(attr LabelListAttribute) LabelListAttribute {
-	var result LabelListAttribute
-	result.Value = FirstUniqueBazelLabelList(attr.Value)
-	if attr.HasConfigurableValues() {
-		result.ConfigurableValues = make(configurableLabelLists)
-	}
-	for axis, configToLabels := range attr.ConfigurableValues {
-		for c, l := range configToLabels {
-			result.SetSelectValue(axis, c, FirstUniqueBazelLabelList(l))
-		}
-	}
-
-	return result
-}
-
-// SubtractBazelLabelListAttribute subtract needle from haystack for LabelList in each
-// axis/configuration.
-func SubtractBazelLabelListAttribute(haystack LabelListAttribute, needle LabelListAttribute) LabelListAttribute {
-	var result LabelListAttribute
-	result.Value = SubtractBazelLabelList(haystack.Value, needle.Value)
-	if haystack.HasConfigurableValues() {
-		result.ConfigurableValues = make(configurableLabelLists)
-	}
-	for axis, configToLabels := range haystack.ConfigurableValues {
-		for haystackConfig, haystackLabels := range configToLabels {
-			result.SetSelectValue(axis, haystackConfig, SubtractBazelLabelList(haystackLabels, needle.SelectValue(axis, haystackConfig)))
-		}
-	}
-
-	return result
-}
-
-type Attribute interface {
-	HasConfigurableValues() bool
-}
-
-type labelSelectValues map[string]*Label
-
-type configurableLabels map[ConfigurationAxis]labelSelectValues
-
-func (cl configurableLabels) setValueForAxis(axis ConfigurationAxis, config string, value *Label) {
-	if cl[axis] == nil {
-		cl[axis] = make(labelSelectValues)
-	}
-	cl[axis][config] = value
-}
-
-// Represents an attribute whose value is a single label
-type LabelAttribute struct {
-	Value *Label
-
-	ConfigurableValues configurableLabels
-}
-
-func (la *LabelAttribute) axisTypes() map[configurationType]bool {
-	types := map[configurationType]bool{}
-	for k := range la.ConfigurableValues {
-		if len(la.ConfigurableValues[k]) > 0 {
-			types[k.configurationType] = true
-		}
-	}
-	return types
-}
-
-// Collapse reduces the configurable axes of the label attribute to a single axis.
-// This is necessary for final writing to bp2build, as a configurable label
-// attribute can only be comprised by a single select.
-func (la *LabelAttribute) Collapse() error {
-	axisTypes := la.axisTypes()
-	_, containsOs := axisTypes[os]
-	_, containsArch := axisTypes[arch]
-	_, containsOsArch := axisTypes[osArch]
-	_, containsProductVariables := axisTypes[productVariables]
-	if containsProductVariables {
-		if containsOs || containsArch || containsOsArch {
-			if containsArch {
-				allProductVariablesAreArchVariant := true
-				for k := range la.ConfigurableValues {
-					if k.configurationType == productVariables && !k.archVariant {
-						allProductVariablesAreArchVariant = false
-					}
-				}
-				if !allProductVariablesAreArchVariant {
-					return fmt.Errorf("label attribute could not be collapsed as it has two or more unrelated axes")
-				}
-			} else {
-				return fmt.Errorf("label attribute could not be collapsed as it has two or more unrelated axes")
-			}
-		}
-	}
-	if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
-		// If a bool attribute has both os and arch configuration axes, the only
-		// way to successfully union their values is to increase the granularity
-		// of the configuration criteria to os_arch.
-		for osType, supportedArchs := range osToArchMap {
-			for _, supportedArch := range supportedArchs {
-				osArch := osArchString(osType, supportedArch)
-				if archOsVal := la.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
-					// Do nothing, as the arch_os is explicitly defined already.
-				} else {
-					archVal := la.SelectValue(ArchConfigurationAxis, supportedArch)
-					osVal := la.SelectValue(OsConfigurationAxis, osType)
-					if osVal != nil && archVal != nil {
-						// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
-						// runs after os mutator.
-						la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
-					} else if osVal != nil && archVal == nil {
-						la.SetSelectValue(OsArchConfigurationAxis, osArch, *osVal)
-					} else if osVal == nil && archVal != nil {
-						la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
-					}
-				}
-			}
-		}
-		// All os_arch values are now set. Clear os and arch axes.
-		delete(la.ConfigurableValues, ArchConfigurationAxis)
-		delete(la.ConfigurableValues, OsConfigurationAxis)
-	}
-	return nil
-}
-
-// HasConfigurableValues returns whether there are configurable values set for this label.
-func (la LabelAttribute) HasConfigurableValues() bool {
-	for _, selectValues := range la.ConfigurableValues {
-		if len(selectValues) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// SetValue sets the base, non-configured value for the Label
-func (la *LabelAttribute) SetValue(value Label) {
-	la.SetSelectValue(NoConfigAxis, "", value)
-}
-
-// SetSelectValue set a value for a bazel select for the given axis, config and value.
-func (la *LabelAttribute) SetSelectValue(axis ConfigurationAxis, config string, value Label) {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		la.Value = &value
-	case arch, os, osArch, productVariables, osAndInApex, sanitizersEnabled:
-		if la.ConfigurableValues == nil {
-			la.ConfigurableValues = make(configurableLabels)
-		}
-		la.ConfigurableValues.setValueForAxis(axis, config, &value)
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SelectValue gets a value for a bazel select for the given axis and config.
-func (la *LabelAttribute) SelectValue(axis ConfigurationAxis, config string) *Label {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		return la.Value
-	case arch, os, osArch, productVariables, osAndInApex, sanitizersEnabled:
-		return la.ConfigurableValues[axis][config]
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
-func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	return SortedConfigurationAxes(la.ConfigurableValues)
-}
-
-// MakeLabelAttribute turns a string into a LabelAttribute
-func MakeLabelAttribute(label string) *LabelAttribute {
-	return &LabelAttribute{
-		Value: &Label{
-			Label: label,
-		},
-	}
-}
-
-type configToBools map[string]bool
-
-func (ctb configToBools) setValue(config string, value *bool) {
-	if value == nil {
-		if _, ok := ctb[config]; ok {
-			delete(ctb, config)
-		}
-		return
-	}
-	ctb[config] = *value
-}
-
-type configurableBools map[ConfigurationAxis]configToBools
-
-func (cb configurableBools) setValueForAxis(axis ConfigurationAxis, config string, value *bool) {
-	if cb[axis] == nil {
-		cb[axis] = make(configToBools)
-	}
-	cb[axis].setValue(config, value)
-}
-
-// BoolAttribute represents an attribute whose value is a single bool but may be configurable..
-type BoolAttribute struct {
-	Value *bool
-
-	ConfigurableValues configurableBools
-}
-
-// HasConfigurableValues returns whether there are configurable values for this attribute.
-func (ba BoolAttribute) HasConfigurableValues() bool {
-	for _, cfgToBools := range ba.ConfigurableValues {
-		if len(cfgToBools) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// SetValue sets value for the no config axis
-func (ba *BoolAttribute) SetValue(value *bool) {
-	ba.SetSelectValue(NoConfigAxis, "", value)
-}
-
-// SetSelectValue sets value for the given axis/config.
-func (ba *BoolAttribute) SetSelectValue(axis ConfigurationAxis, config string, value *bool) {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		ba.Value = value
-	case arch, os, osArch, productVariables, osAndInApex, sanitizersEnabled:
-		if ba.ConfigurableValues == nil {
-			ba.ConfigurableValues = make(configurableBools)
-		}
-		ba.ConfigurableValues.setValueForAxis(axis, config, value)
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// ToLabelListAttribute creates and returns a LabelListAttribute from this
-// bool attribute, where each bool in this attribute corresponds to a
-// label list value in the resultant attribute.
-func (ba *BoolAttribute) ToLabelListAttribute(falseVal LabelList, trueVal LabelList) (LabelListAttribute, error) {
-	getLabelList := func(boolPtr *bool) LabelList {
-		if boolPtr == nil {
-			return LabelList{nil, nil}
-		} else if *boolPtr {
-			return trueVal
-		} else {
-			return falseVal
-		}
-	}
-
-	mainVal := getLabelList(ba.Value)
-	if !ba.HasConfigurableValues() {
-		return MakeLabelListAttribute(mainVal), nil
-	}
-
-	result := LabelListAttribute{}
-	if err := ba.Collapse(); err != nil {
-		return result, err
-	}
-
-	for axis, configToBools := range ba.ConfigurableValues {
-		if len(configToBools) < 1 {
-			continue
-		}
-		for config, boolPtr := range configToBools {
-			val := getLabelList(&boolPtr)
-			if !val.Equals(mainVal) {
-				result.SetSelectValue(axis, config, val)
-			}
-		}
-		result.SetSelectValue(axis, ConditionsDefaultConfigKey, mainVal)
-	}
-
-	return result, nil
-}
-
-// ToStringListAttribute creates a StringListAttribute from this BoolAttribute,
-// where each bool corresponds to a string list value generated by the provided
-// function.
-// TODO(b/271425661): Generalize this
-func (ba *BoolAttribute) ToStringListAttribute(valueFunc func(boolPtr *bool, axis ConfigurationAxis, config string) []string) (StringListAttribute, error) {
-	mainVal := valueFunc(ba.Value, NoConfigAxis, "")
-	if !ba.HasConfigurableValues() {
-		return MakeStringListAttribute(mainVal), nil
-	}
-
-	result := StringListAttribute{}
-	if err := ba.Collapse(); err != nil {
-		return result, err
-	}
-
-	for axis, configToBools := range ba.ConfigurableValues {
-		if len(configToBools) < 1 {
-			continue
-		}
-		for config, boolPtr := range configToBools {
-			val := valueFunc(&boolPtr, axis, config)
-			if !reflect.DeepEqual(val, mainVal) {
-				result.SetSelectValue(axis, config, val)
-			}
-		}
-		result.SetSelectValue(axis, ConditionsDefaultConfigKey, mainVal)
-	}
-
-	return result, nil
-}
-
-// Collapse reduces the configurable axes of the boolean attribute to a single axis.
-// This is necessary for final writing to bp2build, as a configurable boolean
-// attribute can only be comprised by a single select.
-func (ba *BoolAttribute) Collapse() error {
-	axisTypes := ba.axisTypes()
-	_, containsOs := axisTypes[os]
-	_, containsArch := axisTypes[arch]
-	_, containsOsArch := axisTypes[osArch]
-	_, containsProductVariables := axisTypes[productVariables]
-	if containsProductVariables {
-		if containsOs || containsArch || containsOsArch {
-			return fmt.Errorf("boolean attribute could not be collapsed as it has two or more unrelated axes")
-		}
-	}
-	if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
-		// If a bool attribute has both os and arch configuration axes, the only
-		// way to successfully union their values is to increase the granularity
-		// of the configuration criteria to os_arch.
-		for osType, supportedArchs := range osToArchMap {
-			for _, supportedArch := range supportedArchs {
-				osArch := osArchString(osType, supportedArch)
-				if archOsVal := ba.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
-					// Do nothing, as the arch_os is explicitly defined already.
-				} else {
-					archVal := ba.SelectValue(ArchConfigurationAxis, supportedArch)
-					osVal := ba.SelectValue(OsConfigurationAxis, osType)
-					if osVal != nil && archVal != nil {
-						// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
-						// runs after os mutator.
-						ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
-					} else if osVal != nil && archVal == nil {
-						ba.SetSelectValue(OsArchConfigurationAxis, osArch, osVal)
-					} else if osVal == nil && archVal != nil {
-						ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
-					}
-				}
-			}
-		}
-		// All os_arch values are now set. Clear os and arch axes.
-		delete(ba.ConfigurableValues, ArchConfigurationAxis)
-		delete(ba.ConfigurableValues, OsConfigurationAxis)
-		// Verify post-condition; this should never fail, provided no additional
-		// axes are introduced.
-		if len(ba.ConfigurableValues) > 1 {
-			panic(fmt.Errorf("error in collapsing attribute: %#v", ba))
-		}
-	}
-	return nil
-}
-
-func (ba *BoolAttribute) axisTypes() map[configurationType]bool {
-	types := map[configurationType]bool{}
-	for k := range ba.ConfigurableValues {
-		if len(ba.ConfigurableValues[k]) > 0 {
-			types[k.configurationType] = true
-		}
-	}
-	return types
-}
-
-// SelectValue gets the value for the given axis/config.
-func (ba BoolAttribute) SelectValue(axis ConfigurationAxis, config string) *bool {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		return ba.Value
-	case arch, os, osArch, productVariables, osAndInApex, sanitizersEnabled:
-		if v, ok := ba.ConfigurableValues[axis][config]; ok {
-			return &v
-		} else {
-			return nil
-		}
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
-func (ba *BoolAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	return SortedConfigurationAxes(ba.ConfigurableValues)
-}
-
-// labelListSelectValues supports config-specific label_list typed Bazel attribute values.
-type labelListSelectValues map[string]LabelList
-
-func (ll labelListSelectValues) addSelects(label labelSelectValues) {
-	for k, v := range label {
-		if label == nil {
-			continue
-		}
-		l := ll[k]
-		(&l).Add(v)
-		ll[k] = l
-	}
-}
-
-func (ll labelListSelectValues) appendSelects(other labelListSelectValues, forceSpecifyEmptyList bool) {
-	for k, v := range other {
-		l := ll[k]
-		if forceSpecifyEmptyList && l.IsNil() && !v.IsNil() {
-			l.Includes = []Label{}
-		}
-		(&l).Append(v)
-		ll[k] = l
-	}
-}
-
-// HasConfigurableValues returns whether there are configurable values within this set of selects.
-func (ll labelListSelectValues) HasConfigurableValues() bool {
-	for _, v := range ll {
-		if v.Includes != nil {
-			return true
-		}
-	}
-	return false
-}
-
-// LabelListAttribute is used to represent a list of Bazel labels as an
-// attribute.
-type LabelListAttribute struct {
-	// The non-configured attribute label list Value. Required.
-	Value LabelList
-
-	// The configured attribute label list Values. Optional
-	// a map of independent configurability axes
-	ConfigurableValues configurableLabelLists
-
-	// If true, differentiate between "nil" and "empty" list. nil means that
-	// this attribute should not be specified at all, and "empty" means that
-	// the attribute should be explicitly specified as an empty list.
-	// This mode facilitates use of attribute defaults: an empty list should
-	// override the default.
-	ForceSpecifyEmptyList bool
-
-	// If true, signal the intent to the code generator to emit all select keys,
-	// even if the Includes list for that key is empty. This mode facilitates
-	// specific select statements where an empty list for a non-default select
-	// key has a meaning.
-	EmitEmptyList bool
-
-	// If a property has struct tag "variant_prepend", this value should
-	// be set to True, so that when bp2build generates BUILD.bazel, variant
-	// properties(select ...) come before general properties.
-	Prepend bool
-}
-
-type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
-
-func (cll configurableLabelLists) setValueForAxis(axis ConfigurationAxis, config string, list LabelList) {
-	if list.IsNil() {
-		if _, ok := cll[axis][config]; ok {
-			delete(cll[axis], config)
-		}
-		return
-	}
-	if cll[axis] == nil {
-		cll[axis] = make(labelListSelectValues)
-	}
-
-	cll[axis][config] = list
-}
-
-func (cll configurableLabelLists) Append(other configurableLabelLists, forceSpecifyEmptyList bool) {
-	for axis, otherSelects := range other {
-		selects := cll[axis]
-		if selects == nil {
-			selects = make(labelListSelectValues, len(otherSelects))
-		}
-		selects.appendSelects(otherSelects, forceSpecifyEmptyList)
-		cll[axis] = selects
-	}
-}
-
-func (lla *LabelListAttribute) Clone() *LabelListAttribute {
-	result := &LabelListAttribute{ForceSpecifyEmptyList: lla.ForceSpecifyEmptyList}
-	return result.Append(*lla)
-}
-
-// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
-func MakeLabelListAttribute(value LabelList) LabelListAttribute {
-	return LabelListAttribute{
-		Value:              value,
-		ConfigurableValues: make(configurableLabelLists),
-	}
-}
-
-// MakeSingleLabelListAttribute initializes a LabelListAttribute as a non-arch specific list with 1 element, the given Label.
-func MakeSingleLabelListAttribute(value Label) LabelListAttribute {
-	return MakeLabelListAttribute(MakeLabelList([]Label{value}))
-}
-
-func (lla *LabelListAttribute) SetValue(list LabelList) {
-	lla.SetSelectValue(NoConfigAxis, "", list)
-}
-
-// SetSelectValue set a value for a bazel select for the given axis, config and value.
-func (lla *LabelListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list LabelList) {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		lla.Value = list
-	case arch, os, osArch, productVariables, osAndInApex, inApex, errorProneDisabled, sanitizersEnabled:
-		if lla.ConfigurableValues == nil {
-			lla.ConfigurableValues = make(configurableLabelLists)
-		}
-		lla.ConfigurableValues.setValueForAxis(axis, config, list)
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SelectValue gets a value for a bazel select for the given axis and config.
-func (lla *LabelListAttribute) SelectValue(axis ConfigurationAxis, config string) LabelList {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		return lla.Value
-	case arch, os, osArch, productVariables, osAndInApex, inApex, errorProneDisabled, sanitizersEnabled:
-		return lla.ConfigurableValues[axis][config]
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
-func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	return SortedConfigurationAxes(lla.ConfigurableValues)
-}
-
-// Append all values, including os and arch specific ones, from another
-// LabelListAttribute to this LabelListAttribute. Returns this LabelListAttribute.
-func (lla *LabelListAttribute) Append(other LabelListAttribute) *LabelListAttribute {
-	forceSpecifyEmptyList := lla.ForceSpecifyEmptyList || other.ForceSpecifyEmptyList
-	if forceSpecifyEmptyList && lla.Value.IsNil() && !other.Value.IsNil() {
-		lla.Value.Includes = []Label{}
-	}
-	lla.Value.Append(other.Value)
-	if lla.ConfigurableValues == nil {
-		lla.ConfigurableValues = make(configurableLabelLists)
-	}
-	lla.ConfigurableValues.Append(other.ConfigurableValues, forceSpecifyEmptyList)
-	return lla
-}
-
-// Add inserts the labels for each axis of LabelAttribute at the end of corresponding axis's
-// LabelList within the LabelListAttribute
-func (lla *LabelListAttribute) Add(label *LabelAttribute) {
-	if label == nil {
-		return
-	}
-
-	lla.Value.Add(label.Value)
-	if lla.ConfigurableValues == nil && label.ConfigurableValues != nil {
-		lla.ConfigurableValues = make(configurableLabelLists)
-	}
-	for axis, _ := range label.ConfigurableValues {
-		if _, exists := lla.ConfigurableValues[axis]; !exists {
-			lla.ConfigurableValues[axis] = make(labelListSelectValues)
-		}
-		lla.ConfigurableValues[axis].addSelects(label.ConfigurableValues[axis])
-	}
-}
-
-// HasConfigurableValues returns true if the attribute contains axis-specific label list values.
-func (lla LabelListAttribute) HasConfigurableValues() bool {
-	for _, selectValues := range lla.ConfigurableValues {
-		if len(selectValues) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// HasAxisSpecificValues returns true if the attribute contains axis specific label list values from a given axis
-func (lla LabelListAttribute) HasAxisSpecificValues(axis ConfigurationAxis) bool {
-	for _, values := range lla.ConfigurableValues[axis] {
-		if !values.IsNil() {
-			return true
-		}
-	}
-	return false
-}
-
-// IsEmpty returns true if the attribute has no values under any configuration.
-func (lla LabelListAttribute) IsEmpty() bool {
-	if len(lla.Value.Includes) > 0 {
-		return false
-	}
-	for axis, _ := range lla.ConfigurableValues {
-		if lla.ConfigurableValues[axis].HasConfigurableValues() {
-			return false
-		}
-	}
-	return true
-}
-
-// IsNil returns true if the attribute has not been set for any configuration.
-func (lla LabelListAttribute) IsNil() bool {
-	if lla.Value.Includes != nil {
-		return false
-	}
-	return !lla.HasConfigurableValues()
-}
-
-// Exclude for the given axis, config, removes Includes in labelList from Includes and appends them
-// to Excludes. This is to special case any excludes that are not specified in a bp file but need to
-// be removed, e.g. if they could cause duplicate element failures.
-func (lla *LabelListAttribute) Exclude(axis ConfigurationAxis, config string, labelList LabelList) {
-	val := lla.SelectValue(axis, config)
-	newList := SubtractBazelLabelList(val, labelList)
-	newList.Excludes = append(newList.Excludes, labelList.Includes...)
-	lla.SetSelectValue(axis, config, newList)
-}
-
-// ResolveExcludes handles excludes across the various axes, ensuring that items are removed from
-// the base value and included in default values as appropriate.
-func (lla *LabelListAttribute) ResolveExcludes() {
-	// If there are OsAndInApexAxis, we need to use
-	//   * includes from the OS & in APEX Axis for non-Android configs for libraries that need to be
-	//     included in non-Android OSes
-	//   * excludes from the OS Axis for non-Android configs, to exclude libraries that should _not_
-	//     be included in the non-Android OSes
-	if _, ok := lla.ConfigurableValues[OsAndInApexAxis]; ok {
-		inApexLabels := lla.ConfigurableValues[OsAndInApexAxis][ConditionsDefaultConfigKey]
-		for config, labels := range lla.ConfigurableValues[OsConfigurationAxis] {
-			// OsAndroid has already handled its excludes.
-			// We only need to copy the excludes from other arches, so if there are none, skip it.
-			if config == OsAndroid || len(labels.Excludes) == 0 {
-				continue
-			}
-			lla.ConfigurableValues[OsAndInApexAxis][config] = LabelList{
-				Includes: inApexLabels.Includes,
-				Excludes: labels.Excludes,
-			}
-		}
-	}
-
-	for axis, configToLabels := range lla.ConfigurableValues {
-		baseLabels := lla.Value.deepCopy()
-		for config, val := range configToLabels {
-			// Exclude config-specific excludes from base value
-			lla.Value = SubtractBazelLabelList(lla.Value, LabelList{Includes: val.Excludes})
-
-			// add base values to config specific to add labels excluded by others in this axis
-			// then remove all config-specific excludes
-			allLabels := baseLabels.deepCopy()
-			allLabels.Append(val)
-			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: allLabels.Excludes})
-		}
-
-		// After going through all configs, delete the duplicates in the config
-		// values that are already in the base Value.
-		for config, val := range configToLabels {
-			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(val, lla.Value)
-		}
-
-		// Now that the Value list is finalized for this axis, compare it with
-		// the original list, and union the difference with the default
-		// condition for the axis.
-		difference := SubtractBazelLabelList(baseLabels, lla.Value)
-		existingDefaults := lla.ConfigurableValues[axis][ConditionsDefaultConfigKey]
-		existingDefaults.Append(difference)
-		lla.ConfigurableValues[axis][ConditionsDefaultConfigKey] = FirstUniqueBazelLabelList(existingDefaults)
-
-		// if everything ends up without includes, just delete the axis
-		if !lla.ConfigurableValues[axis].HasConfigurableValues() {
-			delete(lla.ConfigurableValues, axis)
-		}
-	}
-}
-
-// Partition splits a LabelListAttribute into two LabelListAttributes depending
-// on the return value of the predicate.
-// This function preserves the Includes and Excludes, but it does not provide
-// that information to the partition function.
-func (lla LabelListAttribute) Partition(predicate func(label Label) bool) (LabelListAttribute, LabelListAttribute) {
-	predicated := LabelListAttribute{}
-	unpredicated := LabelListAttribute{}
-
-	valuePartitionTrue, valuePartitionFalse := lla.Value.Partition(predicate)
-	predicated.SetValue(valuePartitionTrue)
-	unpredicated.SetValue(valuePartitionFalse)
-
-	for axis, selectValueLabelLists := range lla.ConfigurableValues {
-		for config, labelList := range selectValueLabelLists {
-			configPredicated, configUnpredicated := labelList.Partition(predicate)
-			predicated.SetSelectValue(axis, config, configPredicated)
-			unpredicated.SetSelectValue(axis, config, configUnpredicated)
-		}
-	}
-
-	return predicated, unpredicated
-}
-
-// OtherModuleContext is a limited context that has methods with information about other modules.
-type OtherModuleContext interface {
-	ModuleFromName(name string) (blueprint.Module, bool)
-	OtherModuleType(m blueprint.Module) string
-	OtherModuleName(m blueprint.Module) string
-	OtherModuleDir(m blueprint.Module) string
-	ModuleErrorf(fmt string, args ...interface{})
-}
-
-// LabelMapper is a function that takes a OtherModuleContext and returns a (potentially changed)
-// label and whether it was changed.
-type LabelMapper func(OtherModuleContext, Label) (string, bool)
-
-// LabelPartition contains descriptions of a partition for labels
-type LabelPartition struct {
-	// Extensions to include in this partition
-	Extensions []string
-	// LabelMapper is a function that can map a label to a new label, and indicate whether to include
-	// the mapped label in the partition
-	LabelMapper LabelMapper
-	// Whether to store files not included in any other partition in a group of LabelPartitions
-	// Only one partition in a group of LabelPartitions can enabled Keep_remainder
-	Keep_remainder bool
-}
-
-// LabelPartitions is a map of partition name to a LabelPartition describing the elements of the
-// partition
-type LabelPartitions map[string]LabelPartition
-
-// filter returns a pointer to a label if the label should be included in the partition or nil if
-// not.
-func (lf LabelPartition) filter(ctx OtherModuleContext, label Label) *Label {
-	if lf.LabelMapper != nil {
-		if newLabel, changed := lf.LabelMapper(ctx, label); changed {
-			return &Label{newLabel, label.OriginalModuleName}
-		}
-	}
-	for _, ext := range lf.Extensions {
-		if strings.HasSuffix(label.Label, ext) {
-			return &label
-		}
-	}
-
-	return nil
-}
-
-// PartitionToLabelListAttribute is map of partition name to a LabelListAttribute
-type PartitionToLabelListAttribute map[string]LabelListAttribute
-
-type partitionToLabelList map[string]*LabelList
-
-func (p partitionToLabelList) appendIncludes(partition string, label Label) {
-	if _, ok := p[partition]; !ok {
-		p[partition] = &LabelList{}
-	}
-	p[partition].Includes = append(p[partition].Includes, label)
-}
-
-func (p partitionToLabelList) excludes(partition string, excludes []Label) {
-	if _, ok := p[partition]; !ok {
-		p[partition] = &LabelList{}
-	}
-	p[partition].Excludes = excludes
-}
-
-// PartitionLabelListAttribute partitions a LabelListAttribute into the requested partitions
-func PartitionLabelListAttribute(ctx OtherModuleContext, lla *LabelListAttribute, partitions LabelPartitions) PartitionToLabelListAttribute {
-	ret := PartitionToLabelListAttribute{}
-	var partitionNames []string
-	// Stored as a pointer to distinguish nil (no remainder partition) from empty string partition
-	var remainderPartition *string
-	for p, f := range partitions {
-		partitionNames = append(partitionNames, p)
-		if f.Keep_remainder {
-			if remainderPartition != nil {
-				panic("only one partition can store the remainder")
-			}
-			// If we take the address of p in a loop, we'll end up with the last value of p in
-			// remainderPartition, we want the requested partition
-			capturePartition := p
-			remainderPartition = &capturePartition
-		}
-	}
-
-	partitionLabelList := func(axis ConfigurationAxis, config string) {
-		value := lla.SelectValue(axis, config)
-		partitionToLabels := partitionToLabelList{}
-		for _, item := range value.Includes {
-			wasFiltered := false
-			var inPartition *string
-			for partition, f := range partitions {
-				filtered := f.filter(ctx, item)
-				if filtered == nil {
-					// did not match this filter, keep looking
-					continue
-				}
-				wasFiltered = true
-				partitionToLabels.appendIncludes(partition, *filtered)
-				// don't need to check other partitions if this filter used the item,
-				// continue checking if mapped to another name
-				if *filtered == item {
-					if inPartition != nil {
-						ctx.ModuleErrorf("%q was found in multiple partitions: %q, %q", item.Label, *inPartition, partition)
-					}
-					capturePartition := partition
-					inPartition = &capturePartition
-				}
-			}
-
-			// if not specified in a partition, add to remainder partition if one exists
-			if !wasFiltered && remainderPartition != nil {
-				partitionToLabels.appendIncludes(*remainderPartition, item)
-			}
-		}
-
-		// ensure empty lists are maintained
-		if value.Excludes != nil {
-			for _, partition := range partitionNames {
-				partitionToLabels.excludes(partition, value.Excludes)
-			}
-		}
-
-		for partition, list := range partitionToLabels {
-			val := ret[partition]
-			(&val).SetSelectValue(axis, config, *list)
-			ret[partition] = val
-		}
-	}
-
-	partitionLabelList(NoConfigAxis, "")
-	for axis, configToList := range lla.ConfigurableValues {
-		for config, _ := range configToList {
-			partitionLabelList(axis, config)
-		}
-	}
-	return ret
-}
-
-// StringAttribute corresponds to the string Bazel attribute type with
-// support for additional metadata, like configurations.
-type StringAttribute struct {
-	// The base value of the string attribute.
-	Value *string
-
-	// The configured attribute label list Values. Optional
-	// a map of independent configurability axes
-	ConfigurableValues configurableStrings
-}
-
-type configurableStrings map[ConfigurationAxis]stringSelectValues
-
-func (cs configurableStrings) setValueForAxis(axis ConfigurationAxis, config string, str *string) {
-	if cs[axis] == nil {
-		cs[axis] = make(stringSelectValues)
-	}
-	cs[axis][config] = str
-}
-
-type stringSelectValues map[string]*string
-
-// HasConfigurableValues returns true if the attribute contains axis-specific string values.
-func (sa StringAttribute) HasConfigurableValues() bool {
-	for _, selectValues := range sa.ConfigurableValues {
-		if len(selectValues) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// SetValue sets the base, non-configured value for the Label
-func (sa *StringAttribute) SetValue(value string) {
-	sa.SetSelectValue(NoConfigAxis, "", &value)
-}
-
-// SetSelectValue set a value for a bazel select for the given axis, config and value.
-func (sa *StringAttribute) SetSelectValue(axis ConfigurationAxis, config string, str *string) {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		sa.Value = str
-	case arch, os, osArch, productVariables, sanitizersEnabled:
-		if sa.ConfigurableValues == nil {
-			sa.ConfigurableValues = make(configurableStrings)
-		}
-		sa.ConfigurableValues.setValueForAxis(axis, config, str)
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SelectValue gets a value for a bazel select for the given axis and config.
-func (sa *StringAttribute) SelectValue(axis ConfigurationAxis, config string) *string {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		return sa.Value
-	case arch, os, osArch, productVariables, sanitizersEnabled:
-		if v, ok := sa.ConfigurableValues[axis][config]; ok {
-			return v
-		} else {
-			return nil
-		}
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
-func (sa *StringAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	return SortedConfigurationAxes(sa.ConfigurableValues)
-}
-
-// Collapse reduces the configurable axes of the string attribute to a single axis.
-// This is necessary for final writing to bp2build, as a configurable string
-// attribute can only be comprised by a single select.
-func (sa *StringAttribute) Collapse() error {
-	axisTypes := sa.axisTypes()
-	_, containsOs := axisTypes[os]
-	_, containsArch := axisTypes[arch]
-	_, containsOsArch := axisTypes[osArch]
-	_, containsProductVariables := axisTypes[productVariables]
-	if containsProductVariables {
-		if containsOs || containsArch || containsOsArch {
-			return fmt.Errorf("string attribute could not be collapsed as it has two or more unrelated axes")
-		}
-	}
-	if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
-		// If a bool attribute has both os and arch configuration axes, the only
-		// way to successfully union their values is to increase the granularity
-		// of the configuration criteria to os_arch.
-		for osType, supportedArchs := range osToArchMap {
-			for _, supportedArch := range supportedArchs {
-				osArch := osArchString(osType, supportedArch)
-				if archOsVal := sa.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
-					// Do nothing, as the arch_os is explicitly defined already.
-				} else {
-					archVal := sa.SelectValue(ArchConfigurationAxis, supportedArch)
-					osVal := sa.SelectValue(OsConfigurationAxis, osType)
-					if osVal != nil && archVal != nil {
-						// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
-						// runs after os mutator.
-						sa.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
-					} else if osVal != nil && archVal == nil {
-						sa.SetSelectValue(OsArchConfigurationAxis, osArch, osVal)
-					} else if osVal == nil && archVal != nil {
-						sa.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
-					}
-				}
-			}
-		}
-		/// All os_arch values are now set. Clear os and arch axes.
-		delete(sa.ConfigurableValues, ArchConfigurationAxis)
-		delete(sa.ConfigurableValues, OsConfigurationAxis)
-		// Verify post-condition; this should never fail, provided no additional
-		// axes are introduced.
-		if len(sa.ConfigurableValues) > 1 {
-			panic(fmt.Errorf("error in collapsing attribute: %#v", sa))
-		}
-	} else if containsProductVariables {
-		usedBaseValue := false
-		for a, configToProp := range sa.ConfigurableValues {
-			if a.configurationType == productVariables {
-				for c, p := range configToProp {
-					if p == nil {
-						sa.SetSelectValue(a, c, sa.Value)
-						usedBaseValue = true
-					}
-				}
-			}
-		}
-		if usedBaseValue {
-			sa.Value = nil
-		}
-	}
-	return nil
-}
-
-func (sa *StringAttribute) axisTypes() map[configurationType]bool {
-	types := map[configurationType]bool{}
-	for k := range sa.ConfigurableValues {
-		if strs := sa.ConfigurableValues[k]; len(strs) > 0 {
-			types[k.configurationType] = true
-		}
-	}
-	return types
-}
-
-// StringListAttribute corresponds to the string_list Bazel attribute type with
-// support for additional metadata, like configurations.
-type StringListAttribute struct {
-	// The base value of the string list attribute.
-	Value []string
-
-	// The configured attribute label list Values. Optional
-	// a map of independent configurability axes
-	ConfigurableValues configurableStringLists
-
-	// If a property has struct tag "variant_prepend", this value should
-	// be set to True, so that when bp2build generates BUILD.bazel, variant
-	// properties(select ...) come before general properties.
-	Prepend bool
-}
-
-// IsEmpty returns true if the attribute has no values under any configuration.
-func (sla StringListAttribute) IsEmpty() bool {
-	return len(sla.Value) == 0 && !sla.HasConfigurableValues()
-}
-
-type configurableStringLists map[ConfigurationAxis]stringListSelectValues
-
-func (csl configurableStringLists) Append(other configurableStringLists) {
-	for axis, otherSelects := range other {
-		selects := csl[axis]
-		if selects == nil {
-			selects = make(stringListSelectValues, len(otherSelects))
-		}
-		selects.appendSelects(otherSelects)
-		csl[axis] = selects
-	}
-}
-
-func (csl configurableStringLists) setValueForAxis(axis ConfigurationAxis, config string, list []string) {
-	if csl[axis] == nil {
-		csl[axis] = make(stringListSelectValues)
-	}
-	csl[axis][config] = list
-}
-
-type stringListSelectValues map[string][]string
-
-func (sl stringListSelectValues) appendSelects(other stringListSelectValues) {
-	for k, v := range other {
-		sl[k] = append(sl[k], v...)
-	}
-}
-
-func (sl stringListSelectValues) hasConfigurableValues(other stringListSelectValues) bool {
-	for _, val := range sl {
-		if len(val) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
-func MakeStringListAttribute(value []string) StringListAttribute {
-	// NOTE: These strings are not necessarily unique or sorted.
-	return StringListAttribute{
-		Value:              value,
-		ConfigurableValues: make(configurableStringLists),
-	}
-}
-
-// HasConfigurableValues returns true if the attribute contains axis-specific string_list values.
-func (sla StringListAttribute) HasConfigurableValues() bool {
-	for _, selectValues := range sla.ConfigurableValues {
-		if len(selectValues) > 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// Append appends all values, including os and arch specific ones, from another
-// StringListAttribute to this StringListAttribute
-func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
-	sla.Value = append(sla.Value, other.Value...)
-	if sla.ConfigurableValues == nil {
-		sla.ConfigurableValues = make(configurableStringLists)
-	}
-	sla.ConfigurableValues.Append(other.ConfigurableValues)
-	return sla
-}
-
-func (sla *StringListAttribute) Clone() *StringListAttribute {
-	result := &StringListAttribute{}
-	return result.Append(*sla)
-}
-
-// SetSelectValue set a value for a bazel select for the given axis, config and value.
-func (sla *StringListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list []string) {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		sla.Value = list
-	case arch, os, osArch, productVariables, osAndInApex, errorProneDisabled, sanitizersEnabled:
-		if sla.ConfigurableValues == nil {
-			sla.ConfigurableValues = make(configurableStringLists)
-		}
-		sla.ConfigurableValues.setValueForAxis(axis, config, list)
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SelectValue gets a value for a bazel select for the given axis and config.
-func (sla *StringListAttribute) SelectValue(axis ConfigurationAxis, config string) []string {
-	axis.validateConfig(config)
-	switch axis.configurationType {
-	case noConfig:
-		return sla.Value
-	case arch, os, osArch, productVariables, osAndInApex, errorProneDisabled, sanitizersEnabled:
-		return sla.ConfigurableValues[axis][config]
-	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
-	}
-}
-
-// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
-func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	return SortedConfigurationAxes(sla.ConfigurableValues)
-}
-
-// DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
-// configuration-specific values. For example, if we would convert this StringListAttribute as:
-//
-//	["a", "b", "c"] + select({
-//	   "//condition:one": ["a", "d"],
-//	   "//conditions:default": [],
-//	})
-//
-// after this function, we would convert this StringListAttribute as:
-//
-//	["a", "b", "c"] + select({
-//	   "//condition:one": ["d"],
-//	   "//conditions:default": [],
-//	})
-func (sla *StringListAttribute) DeduplicateAxesFromBase() {
-	base := sla.Value
-	for axis, configToList := range sla.ConfigurableValues {
-		for config, list := range configToList {
-			remaining := SubtractStrings(list, base)
-			if len(remaining) == 0 {
-				delete(sla.ConfigurableValues[axis], config)
-			} else {
-				sla.ConfigurableValues[axis][config] = remaining
-			}
-		}
-	}
-}
-
-// TryVariableSubstitution, replace string substitution formatting within each string in slice with
-// Starlark string.format compatible tag for productVariable.
-func TryVariableSubstitutions(slice []string, productVariable string) ([]string, bool) {
-	if len(slice) == 0 {
-		return slice, false
-	}
-	ret := make([]string, 0, len(slice))
-	changesMade := false
-	for _, s := range slice {
-		newS, changed := TryVariableSubstitution(s, productVariable)
-		ret = append(ret, newS)
-		changesMade = changesMade || changed
-	}
-	return ret, changesMade
-}
-
-// TryVariableSubstitution, replace string substitution formatting within s with Starlark
-// string.format compatible tag for productVariable.
-func TryVariableSubstitution(s string, productVariable string) (string, bool) {
-	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
-	return sub, s != sub
-}
-
-// StringMapAttribute is a map of strings.
-// The use case for this is storing the flag_values in a config_setting object.
-// Bazel rules do not support map attributes, and this should NOT be used in Bazel rules.
-type StringMapAttribute map[string]string
-
-// ConfigSettingAttributes stores the keys of a config_setting object.
-type ConfigSettingAttributes struct {
-	// Each key in Flag_values is a label to a custom string_setting
-	Flag_values StringMapAttribute
-	// Each element in Constraint_values is a label to a constraint_value
-	Constraint_values LabelListAttribute
-}
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
deleted file mode 100644
index 751cb8b..0000000
--- a/bazel/properties_test.go
+++ /dev/null
@@ -1,836 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bazel
-
-import (
-	"reflect"
-	"strings"
-	"testing"
-
-	"github.com/google/blueprint/proptools"
-)
-
-func TestUniqueBazelLabels(t *testing.T) {
-	testCases := []struct {
-		originalLabels       []Label
-		expectedUniqueLabels []Label
-	}{
-		{
-			originalLabels: []Label{
-				{Label: "a"},
-				{Label: "b"},
-				{Label: "a"},
-				{Label: "c"},
-				// namespaces
-				{Label: "//foo:bar", OriginalModuleName: "bar"},       // when referenced from foo namespace
-				{Label: "//foo:bar", OriginalModuleName: "//foo:bar"}, // when reference from root namespace
-			},
-			expectedUniqueLabels: []Label{
-				{Label: "//foo:bar", OriginalModuleName: "bar"},
-				{Label: "a"},
-				{Label: "b"},
-				{Label: "c"},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualUniqueLabels := UniqueSortedBazelLabels(tc.originalLabels)
-		if !reflect.DeepEqual(tc.expectedUniqueLabels, actualUniqueLabels) {
-			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabels, actualUniqueLabels)
-		}
-	}
-}
-
-func TestSubtractStrings(t *testing.T) {
-	testCases := []struct {
-		haystack       []string
-		needle         []string
-		expectedResult []string
-	}{
-		{
-			haystack: []string{
-				"a",
-				"b",
-				"c",
-			},
-			needle: []string{
-				"a",
-			},
-			expectedResult: []string{
-				"b", "c",
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualResult := SubtractStrings(tc.haystack, tc.needle)
-		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
-			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
-		}
-	}
-}
-
-func TestSubtractBazelLabelList(t *testing.T) {
-	testCases := []struct {
-		haystack       LabelList
-		needle         LabelList
-		expectedResult LabelList
-	}{
-		{
-			haystack: LabelList{
-				Includes: []Label{
-					{Label: "a"},
-					{Label: "b"},
-					{Label: "c"},
-				},
-				Excludes: []Label{
-					{Label: "x"},
-					{Label: "y"},
-					{Label: "z"},
-				},
-			},
-			needle: LabelList{
-				Includes: []Label{
-					{Label: "a"},
-				},
-				Excludes: []Label{
-					{Label: "z"},
-				},
-			},
-			// NOTE: Excludes are intentionally not subtracted
-			expectedResult: LabelList{
-				Includes: []Label{
-					{Label: "b"},
-					{Label: "c"},
-				},
-				Excludes: []Label{
-					{Label: "x"},
-					{Label: "y"},
-					{Label: "z"},
-				},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualResult := SubtractBazelLabelList(tc.haystack, tc.needle)
-		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
-			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
-		}
-	}
-}
-
-func TestSubtractBazelLabelListAttribute(t *testing.T) {
-	testCases := []struct {
-		haystack LabelListAttribute
-		needle   LabelListAttribute
-		expected LabelListAttribute
-	}{
-		{
-			haystack: LabelListAttribute{
-				Value: makeLabelList(
-					[]string{"a", "b", "a", "c"},
-					[]string{"x", "x", "y", "z"},
-				),
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"arm": makeLabelList([]string{"arm_1", "arm_2"}, []string{}),
-						"x86": makeLabelList([]string{"x86_3", "x86_4", "x86_5"}, []string{"x86_5"}),
-					},
-				},
-			},
-			needle: LabelListAttribute{
-				Value: makeLabelList(
-					[]string{"d", "a"},
-					[]string{"x", "y2", "z2"},
-				),
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"arm": makeLabelList([]string{"arm_1", "arm_3"}, []string{}),
-						"x86": makeLabelList([]string{"x86_3", "x86_4"}, []string{"x86_6"}),
-					},
-				},
-			},
-			expected: LabelListAttribute{
-				Value: makeLabelList(
-					[]string{"b", "c"},
-					[]string{"x", "x", "y", "z"},
-				),
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"arm": makeLabelList([]string{"arm_2"}, []string{}),
-						"x86": makeLabelList([]string{"x86_5"}, []string{"x86_5"}),
-					},
-				},
-				ForceSpecifyEmptyList: false,
-				EmitEmptyList:         false,
-				Prepend:               false,
-			},
-		},
-	}
-	for _, tc := range testCases {
-		got := SubtractBazelLabelListAttribute(tc.haystack, tc.needle)
-		if !reflect.DeepEqual(tc.expected, got) {
-			t.Fatalf("Expected\n%v, but got\n%v", tc.expected, got)
-		}
-	}
-}
-
-func TestFirstUniqueBazelLabelList(t *testing.T) {
-	testCases := []struct {
-		originalLabelList       LabelList
-		expectedUniqueLabelList LabelList
-	}{
-		{
-			originalLabelList: LabelList{
-				Includes: []Label{
-					{Label: "a"},
-					{Label: "b"},
-					{Label: "a"},
-					{Label: "c"},
-					// namespaces
-					{Label: "//foo:bar", OriginalModuleName: "bar"},       // when referenced from foo namespace
-					{Label: "//foo:bar", OriginalModuleName: "//foo:bar"}, // when referenced from root namespace
-				},
-				Excludes: []Label{
-					{Label: "x"},
-					{Label: "x"},
-					{Label: "y"},
-					{Label: "z"},
-				},
-			},
-			expectedUniqueLabelList: LabelList{
-				Includes: []Label{
-					{Label: "a"},
-					{Label: "b"},
-					{Label: "c"},
-					{Label: "//foo:bar", OriginalModuleName: "bar"},
-				},
-				Excludes: []Label{
-					{Label: "x"},
-					{Label: "y"},
-					{Label: "z"},
-				},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualUniqueLabelList := FirstUniqueBazelLabelList(tc.originalLabelList)
-		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
-			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
-		}
-	}
-}
-
-func TestFirstUniqueBazelLabelListAttribute(t *testing.T) {
-	testCases := []struct {
-		originalLabelList       LabelListAttribute
-		expectedUniqueLabelList LabelListAttribute
-	}{
-		{
-			originalLabelList: LabelListAttribute{
-				Value: makeLabelList(
-					[]string{"a", "b", "a", "c"},
-					[]string{"x", "x", "y", "z"},
-				),
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"arm": makeLabelList([]string{"1", "2", "1"}, []string{}),
-						"x86": makeLabelList([]string{"3", "4", "4"}, []string{"5", "5"}),
-					},
-				},
-			},
-			expectedUniqueLabelList: LabelListAttribute{
-				Value: makeLabelList(
-					[]string{"a", "b", "c"},
-					[]string{"x", "y", "z"},
-				),
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"arm": makeLabelList([]string{"1", "2"}, []string{}),
-						"x86": makeLabelList([]string{"3", "4"}, []string{"5"}),
-					},
-				},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualUniqueLabelList := FirstUniqueBazelLabelListAttribute(tc.originalLabelList)
-		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
-			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
-		}
-	}
-}
-
-func TestUniqueSortedBazelLabelList(t *testing.T) {
-	testCases := []struct {
-		originalLabelList       LabelList
-		expectedUniqueLabelList LabelList
-	}{
-		{
-			originalLabelList: LabelList{
-				Includes: []Label{
-					{Label: "c"},
-					{Label: "a"},
-					{Label: "a"},
-					{Label: "b"},
-				},
-				Excludes: []Label{
-					{Label: "y"},
-					{Label: "z"},
-					{Label: "x"},
-					{Label: "x"},
-				},
-			},
-			expectedUniqueLabelList: LabelList{
-				Includes: []Label{
-					{Label: "a"},
-					{Label: "b"},
-					{Label: "c"},
-				},
-				Excludes: []Label{
-					{Label: "x"},
-					{Label: "y"},
-					{Label: "z"},
-				},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		actualUniqueLabelList := UniqueSortedBazelLabelList(tc.originalLabelList)
-		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
-			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
-		}
-	}
-}
-
-func makeLabels(labels ...string) []Label {
-	var ret []Label
-	for _, l := range labels {
-		ret = append(ret, Label{Label: l})
-	}
-	return ret
-}
-
-func makeLabelList(includes, excludes []string) LabelList {
-	return LabelList{
-		Includes: makeLabels(includes...),
-		Excludes: makeLabels(excludes...),
-	}
-}
-
-func TestResolveExcludes(t *testing.T) {
-	attr := LabelListAttribute{
-		Value: makeLabelList(
-			[]string{
-				"all_include",
-				"arm_exclude",
-				"android_exclude",
-				"product_config_exclude",
-			},
-			[]string{"all_exclude"},
-		),
-		ConfigurableValues: configurableLabelLists{
-			ArchConfigurationAxis: labelListSelectValues{
-				"arm":                      makeLabelList([]string{}, []string{"arm_exclude"}),
-				"x86":                      makeLabelList([]string{"x86_include"}, []string{}),
-				ConditionsDefaultConfigKey: makeLabelList([]string{"default_include"}, []string{}),
-			},
-			OsConfigurationAxis: labelListSelectValues{
-				"android": makeLabelList([]string{}, []string{"android_exclude"}),
-				"linux":   makeLabelList([]string{"linux_include"}, []string{}),
-			},
-			OsArchConfigurationAxis: labelListSelectValues{
-				"linux_x86": makeLabelList([]string{"linux_x86_include"}, []string{}),
-			},
-			ProductVariableConfigurationAxis(false, "product_with_defaults"): labelListSelectValues{
-				"a":                        makeLabelList([]string{}, []string{"not_in_value"}),
-				"b":                        makeLabelList([]string{"b_val"}, []string{}),
-				"c":                        makeLabelList([]string{"c_val"}, []string{}),
-				ConditionsDefaultConfigKey: makeLabelList([]string{"c_val", "default", "default2", "all_exclude"}, []string{}),
-			},
-			ProductVariableConfigurationAxis(false, "product_only_with_excludes"): labelListSelectValues{
-				"a": makeLabelList([]string{}, []string{"product_config_exclude"}),
-			},
-		},
-	}
-
-	attr.ResolveExcludes()
-
-	expectedBaseIncludes := []Label{{Label: "all_include"}}
-	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value.Includes) {
-		t.Errorf("Expected Value includes %q, got %q", attr.Value.Includes, expectedBaseIncludes)
-	}
-	var nilLabels []Label
-	expectedConfiguredIncludes := map[ConfigurationAxis]map[string][]Label{
-		ArchConfigurationAxis: {
-			"arm":                      nilLabels,
-			"x86":                      makeLabels("arm_exclude", "x86_include"),
-			ConditionsDefaultConfigKey: makeLabels("arm_exclude", "default_include"),
-		},
-		OsConfigurationAxis: {
-			"android":                  nilLabels,
-			"linux":                    makeLabels("android_exclude", "linux_include"),
-			ConditionsDefaultConfigKey: makeLabels("android_exclude"),
-		},
-		OsArchConfigurationAxis: {
-			"linux_x86":                makeLabels("linux_x86_include"),
-			ConditionsDefaultConfigKey: nilLabels,
-		},
-		ProductVariableConfigurationAxis(false, "product_with_defaults"): {
-			"a":                        nilLabels,
-			"b":                        makeLabels("b_val"),
-			"c":                        makeLabels("c_val"),
-			ConditionsDefaultConfigKey: makeLabels("c_val", "default", "default2"),
-		},
-		ProductVariableConfigurationAxis(false, "product_only_with_excludes"): {
-			"a":                        nilLabels,
-			ConditionsDefaultConfigKey: makeLabels("product_config_exclude"),
-		},
-	}
-	for _, axis := range attr.SortedConfigurationAxes() {
-		if _, ok := expectedConfiguredIncludes[axis]; !ok {
-			t.Errorf("Found unexpected axis %s", axis)
-			continue
-		}
-		expectedForAxis := expectedConfiguredIncludes[axis]
-		gotForAxis := attr.ConfigurableValues[axis]
-		if len(expectedForAxis) != len(gotForAxis) {
-			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
-		}
-		for config, value := range gotForAxis {
-			if expected, ok := expectedForAxis[config]; ok {
-				if !reflect.DeepEqual(expected, value.Includes) {
-					t.Errorf("For %s,\nexpected: %#v\ngot %#v", axis, expected, value.Includes)
-				}
-			} else {
-				t.Errorf("Got unexpected config %q for %s", config, axis)
-			}
-		}
-	}
-}
-
-func TestLabelListAttributePartition(t *testing.T) {
-	testCases := []struct {
-		name         string
-		input        LabelListAttribute
-		predicated   LabelListAttribute
-		unpredicated LabelListAttribute
-		predicate    func(label Label) bool
-	}{
-		{
-			name: "move all to predicated partition",
-			input: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			predicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			unpredicated: LabelListAttribute{},
-			predicate: func(label Label) bool {
-				return true
-			},
-		},
-		{
-			name: "move all to unpredicated partition",
-			input: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			predicated: LabelListAttribute{},
-			unpredicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			predicate: func(label Label) bool {
-				return false
-			},
-		},
-		{
-			name: "partition includes and excludes",
-			input: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			predicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "keep2"},
-				[]string{"keep1", "keep2"},
-			)),
-			unpredicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"throw1", "throw2"},
-				[]string{"throw1", "throw2"},
-			)),
-			predicate: func(label Label) bool {
-				return strings.HasPrefix(label.Label, "keep")
-			},
-		},
-		{
-			name: "partition excludes only",
-			input: MakeLabelListAttribute(makeLabelList(
-				[]string{},
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-			)),
-			predicated: MakeLabelListAttribute(makeLabelList(
-				[]string{},
-				[]string{"keep1", "keep2"},
-			)),
-			unpredicated: MakeLabelListAttribute(makeLabelList(
-				[]string{},
-				[]string{"throw1", "throw2"},
-			)),
-			predicate: func(label Label) bool {
-				return strings.HasPrefix(label.Label, "keep")
-			},
-		},
-		{
-			name: "partition includes only",
-			input: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "throw1", "keep2", "throw2"},
-				[]string{},
-			)),
-			predicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"keep1", "keep2"},
-				[]string{},
-			)),
-			unpredicated: MakeLabelListAttribute(makeLabelList(
-				[]string{"throw1", "throw2"},
-				[]string{},
-			)),
-			predicate: func(label Label) bool {
-				return strings.HasPrefix(label.Label, "keep")
-			},
-		},
-		{
-			name:         "empty partition",
-			input:        MakeLabelListAttribute(makeLabelList([]string{}, []string{})),
-			predicated:   LabelListAttribute{},
-			unpredicated: LabelListAttribute{},
-			predicate: func(label Label) bool {
-				return true
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			predicated, unpredicated := tc.input.Partition(tc.predicate)
-			if !predicated.Value.Equals(tc.predicated.Value) {
-				t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated)
-			}
-			for axis, configs := range predicated.ConfigurableValues {
-				tcConfigs, ok := tc.predicated.ConfigurableValues[axis]
-				if !ok || !reflect.DeepEqual(configs, tcConfigs) {
-					t.Errorf("expected predicated labels to be %v; got %v", tc.predicated, predicated)
-				}
-			}
-			if !unpredicated.Value.Equals(tc.unpredicated.Value) {
-				t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated)
-			}
-			for axis, configs := range unpredicated.ConfigurableValues {
-				tcConfigs, ok := tc.unpredicated.ConfigurableValues[axis]
-				if !ok || !reflect.DeepEqual(configs, tcConfigs) {
-					t.Errorf("expected unpredicated labels to be %v; got %v", tc.unpredicated, unpredicated)
-				}
-			}
-		})
-	}
-}
-
-// labelAddSuffixForTypeMapper returns a LabelMapper that adds suffix to label name for modules of
-// typ
-func labelAddSuffixForTypeMapper(suffix, typ string) LabelMapper {
-	return func(omc OtherModuleContext, label Label) (string, bool) {
-		m, ok := omc.ModuleFromName(label.Label)
-		if !ok {
-			return label.Label, false
-		}
-		mTyp := omc.OtherModuleType(m)
-		if typ == mTyp {
-			return label.Label + suffix, true
-		}
-		return label.Label, false
-	}
-}
-
-func TestPartitionLabelListAttribute(t *testing.T) {
-	testCases := []struct {
-		name           string
-		ctx            *OtherModuleTestContext
-		labelList      LabelListAttribute
-		filters        LabelPartitions
-		expected       PartitionToLabelListAttribute
-		expectedErrMsg *string
-	}{
-		{
-			name: "no configurable values",
-			ctx:  &OtherModuleTestContext{},
-			labelList: LabelListAttribute{
-				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
-			},
-			filters: LabelPartitions{
-				"A": LabelPartition{Extensions: []string{".a"}},
-				"B": LabelPartition{Extensions: []string{".b"}},
-				"C": LabelPartition{Extensions: []string{".c"}},
-			},
-			expected: PartitionToLabelListAttribute{
-				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
-				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
-				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
-			},
-		},
-		{
-			name: "no configurable values, remainder partition",
-			ctx:  &OtherModuleTestContext{},
-			labelList: LabelListAttribute{
-				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
-			},
-			filters: LabelPartitions{
-				"A": LabelPartition{Extensions: []string{".a"}, Keep_remainder: true},
-				"B": LabelPartition{Extensions: []string{".b"}},
-				"C": LabelPartition{Extensions: []string{".c"}},
-			},
-			expected: PartitionToLabelListAttribute{
-				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "d.d", "e.e"}, []string{})},
-				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
-				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
-			},
-		},
-		{
-			name: "no configurable values, empty partition",
-			ctx:  &OtherModuleTestContext{},
-			labelList: LabelListAttribute{
-				Value: makeLabelList([]string{"a.a", "c.c"}, []string{}),
-			},
-			filters: LabelPartitions{
-				"A": LabelPartition{Extensions: []string{".a"}},
-				"B": LabelPartition{Extensions: []string{".b"}},
-				"C": LabelPartition{Extensions: []string{".c"}},
-			},
-			expected: PartitionToLabelListAttribute{
-				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
-				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
-			},
-		},
-		{
-			name: "no configurable values, has map",
-			ctx: &OtherModuleTestContext{
-				Modules: []TestModuleInfo{{ModuleName: "srcs", Typ: "fg", Dir: "dir"}},
-			},
-			labelList: LabelListAttribute{
-				Value: makeLabelList([]string{"a.a", "srcs", "b.b", "c.c"}, []string{}),
-			},
-			filters: LabelPartitions{
-				"A": LabelPartition{Extensions: []string{".a"}, LabelMapper: labelAddSuffixForTypeMapper("_a", "fg")},
-				"B": LabelPartition{Extensions: []string{".b"}},
-				"C": LabelPartition{Extensions: []string{".c"}},
-			},
-			expected: PartitionToLabelListAttribute{
-				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "srcs_a"}, []string{})},
-				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
-				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
-			},
-		},
-		{
-			name: "configurable values, keeps empty if excludes",
-			ctx:  &OtherModuleTestContext{},
-			labelList: LabelListAttribute{
-				ConfigurableValues: configurableLabelLists{
-					ArchConfigurationAxis: labelListSelectValues{
-						"x86":    makeLabelList([]string{"a.a", "c.c"}, []string{}),
-						"arm":    makeLabelList([]string{"b.b"}, []string{}),
-						"x86_64": makeLabelList([]string{"b.b"}, []string{"d.d"}),
-					},
-				},
-			},
-			filters: LabelPartitions{
-				"A": LabelPartition{Extensions: []string{".a"}},
-				"B": LabelPartition{Extensions: []string{".b"}},
-				"C": LabelPartition{Extensions: []string{".c"}},
-			},
-			expected: PartitionToLabelListAttribute{
-				"A": LabelListAttribute{
-					ConfigurableValues: configurableLabelLists{
-						ArchConfigurationAxis: labelListSelectValues{
-							"x86":    makeLabelList([]string{"a.a"}, []string{}),
-							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
-						},
-					},
-				},
-				"B": LabelListAttribute{
-					ConfigurableValues: configurableLabelLists{
-						ArchConfigurationAxis: labelListSelectValues{
-							"arm":    makeLabelList([]string{"b.b"}, []string{}),
-							"x86_64": makeLabelList([]string{"b.b"}, []string{"c.c"}),
-						},
-					},
-				},
-				"C": LabelListAttribute{
-					ConfigurableValues: configurableLabelLists{
-						ArchConfigurationAxis: labelListSelectValues{
-							"x86":    makeLabelList([]string{"c.c"}, []string{}),
-							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
-						},
-					},
-				},
-			},
-		},
-		{
-			name: "error for multiple partitions same value",
-			ctx:  &OtherModuleTestContext{},
-			labelList: LabelListAttribute{
-				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
-			},
-			filters: LabelPartitions{
-				"A":       LabelPartition{Extensions: []string{".a"}},
-				"other A": LabelPartition{Extensions: []string{".a"}},
-			},
-			expected:       PartitionToLabelListAttribute{},
-			expectedErrMsg: proptools.StringPtr(`"a.a" was found in multiple partitions:`),
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			got := PartitionLabelListAttribute(tc.ctx, &tc.labelList, tc.filters)
-
-			if hasErrors, expectsErr := len(tc.ctx.errors) > 0, tc.expectedErrMsg != nil; hasErrors != expectsErr {
-				t.Errorf("Unexpected error(s): %q, expected: %q", tc.ctx.errors, *tc.expectedErrMsg)
-			} else if tc.expectedErrMsg != nil {
-				found := false
-				for _, err := range tc.ctx.errors {
-					if strings.Contains(err, *tc.expectedErrMsg) {
-						found = true
-						break
-					}
-				}
-
-				if !found {
-					t.Errorf("Expected error message: %q, got %q", *tc.expectedErrMsg, tc.ctx.errors)
-				}
-				return
-			}
-
-			if len(tc.expected) != len(got) {
-				t.Errorf("Expected %d partitions, got %d partitions", len(tc.expected), len(got))
-			}
-			for partition, expectedLla := range tc.expected {
-				gotLla, ok := got[partition]
-				if !ok {
-					t.Errorf("Expected partition %q, but it was not found %v", partition, got)
-					continue
-				}
-				expectedLabelList := expectedLla.Value
-				gotLabelList := gotLla.Value
-				if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
-					t.Errorf("Expected no config includes %v, got %v", expectedLabelList.Includes, gotLabelList.Includes)
-				}
-				expectedAxes := expectedLla.SortedConfigurationAxes()
-				gotAxes := gotLla.SortedConfigurationAxes()
-				if !reflect.DeepEqual(expectedAxes, gotAxes) {
-					t.Errorf("Expected axes %v, got %v (%#v)", expectedAxes, gotAxes, gotLla)
-				}
-				for _, axis := range expectedLla.SortedConfigurationAxes() {
-					if _, exists := gotLla.ConfigurableValues[axis]; !exists {
-						t.Errorf("Expected %s to be a supported axis, but it was not found", axis)
-					}
-					if expected, got := expectedLla.ConfigurableValues[axis], gotLla.ConfigurableValues[axis]; len(expected) != len(got) {
-						t.Errorf("For axis %q: expected configs %v, got %v", axis, expected, got)
-					}
-					for config, expectedLabelList := range expectedLla.ConfigurableValues[axis] {
-						gotLabelList, exists := gotLla.ConfigurableValues[axis][config]
-						if !exists {
-							t.Errorf("Expected %s to be a supported config, but config was not found", config)
-							continue
-						}
-						if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
-							t.Errorf("Expected %s %s includes %v, got %v", axis, config, expectedLabelList.Includes, gotLabelList.Includes)
-						}
-					}
-				}
-			}
-		})
-	}
-}
-
-func TestDeduplicateAxesFromBase(t *testing.T) {
-	attr := StringListAttribute{
-		Value: []string{
-			"all_include",
-			"arm_include",
-			"android_include",
-			"linux_x86_include",
-		},
-		ConfigurableValues: configurableStringLists{
-			ArchConfigurationAxis: stringListSelectValues{
-				"arm": []string{"arm_include"},
-				"x86": []string{"x86_include"},
-			},
-			OsConfigurationAxis: stringListSelectValues{
-				"android": []string{"android_include"},
-				"linux":   []string{"linux_include"},
-			},
-			OsArchConfigurationAxis: stringListSelectValues{
-				"linux_x86": {"linux_x86_include"},
-			},
-			ProductVariableConfigurationAxis(false, "a"): stringListSelectValues{
-				"a": []string{"not_in_value"},
-			},
-		},
-	}
-
-	attr.DeduplicateAxesFromBase()
-
-	expectedBaseIncludes := []string{
-		"all_include",
-		"arm_include",
-		"android_include",
-		"linux_x86_include",
-	}
-	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value) {
-		t.Errorf("Expected Value includes %q, got %q", attr.Value, expectedBaseIncludes)
-	}
-	expectedConfiguredIncludes := configurableStringLists{
-		ArchConfigurationAxis: stringListSelectValues{
-			"x86": []string{"x86_include"},
-		},
-		OsConfigurationAxis: stringListSelectValues{
-			"linux": []string{"linux_include"},
-		},
-		OsArchConfigurationAxis: stringListSelectValues{},
-		ProductVariableConfigurationAxis(false, "a"): stringListSelectValues{
-			"a": []string{"not_in_value"},
-		},
-	}
-	for _, axis := range attr.SortedConfigurationAxes() {
-		if _, ok := expectedConfiguredIncludes[axis]; !ok {
-			t.Errorf("Found unexpected axis %s", axis)
-			continue
-		}
-		expectedForAxis := expectedConfiguredIncludes[axis]
-		gotForAxis := attr.ConfigurableValues[axis]
-		if len(expectedForAxis) != len(gotForAxis) {
-			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
-		}
-		for config, value := range gotForAxis {
-			if expected, ok := expectedForAxis[config]; ok {
-				if !reflect.DeepEqual(expected, value) {
-					t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value)
-				}
-			} else {
-				t.Errorf("Got unexpected config %q for %s", config, axis)
-			}
-		}
-	}
-}
diff --git a/bazel/testing.go b/bazel/testing.go
deleted file mode 100644
index 9a43b61..0000000
--- a/bazel/testing.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package bazel
-
-import (
-	"fmt"
-
-	"github.com/google/blueprint"
-)
-
-// TestModuleInfo implements blueprint.Module interface with sufficient information to mock a subset of
-// a blueprint ModuleContext
-type TestModuleInfo struct {
-	ModuleName string
-	Typ        string
-	Dir        string
-}
-
-// Name returns name for testModuleInfo -- required to implement blueprint.Module
-func (mi TestModuleInfo) Name() string {
-	return mi.ModuleName
-}
-
-// GenerateBuildActions unused, but required to implmeent blueprint.Module
-func (mi TestModuleInfo) GenerateBuildActions(blueprint.ModuleContext) {}
-
-func (mi TestModuleInfo) equals(other TestModuleInfo) bool {
-	return mi.ModuleName == other.ModuleName && mi.Typ == other.Typ && mi.Dir == other.Dir
-}
-
-// ensure testModuleInfo implements blueprint.Module
-var _ blueprint.Module = TestModuleInfo{}
-
-// OtherModuleTestContext is a mock context that implements OtherModuleContext
-type OtherModuleTestContext struct {
-	Modules []TestModuleInfo
-	errors  []string
-}
-
-// ModuleFromName retrieves the testModuleInfo corresponding to name, if it exists
-func (omc *OtherModuleTestContext) ModuleFromName(name string) (blueprint.Module, bool) {
-	for _, m := range omc.Modules {
-		if m.ModuleName == name {
-			return m, true
-		}
-	}
-	return TestModuleInfo{}, false
-}
-
-// testModuleInfo returns the testModuleInfo corresponding to a blueprint.Module if it exists in omc
-func (omc *OtherModuleTestContext) testModuleInfo(m blueprint.Module) (TestModuleInfo, bool) {
-	mi, ok := m.(TestModuleInfo)
-	if !ok {
-		return TestModuleInfo{}, false
-	}
-	for _, other := range omc.Modules {
-		if other.equals(mi) {
-			return mi, true
-		}
-	}
-	return TestModuleInfo{}, false
-}
-
-// OtherModuleType returns type of m if it exists in omc
-func (omc *OtherModuleTestContext) OtherModuleType(m blueprint.Module) string {
-	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.Typ
-	}
-	return ""
-}
-
-// OtherModuleName returns name of m if it exists in omc
-func (omc *OtherModuleTestContext) OtherModuleName(m blueprint.Module) string {
-	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.ModuleName
-	}
-	return ""
-}
-
-// OtherModuleDir returns dir of m if it exists in omc
-func (omc *OtherModuleTestContext) OtherModuleDir(m blueprint.Module) string {
-	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.Dir
-	}
-	return ""
-}
-
-func (omc *OtherModuleTestContext) ModuleErrorf(format string, args ...interface{}) {
-	omc.errors = append(omc.errors, fmt.Sprintf(format, args...))
-}
-
-// Ensure otherModuleTestContext implements OtherModuleContext
-var _ OtherModuleContext = &OtherModuleTestContext{}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index ba12682..28c0268 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -9,7 +9,6 @@
         "androidbp_to_build_templates.go",
         "build_conversion.go",
         "bzl_conversion.go",
-        "configurability.go",
         "constants.go",
         "conversion.go",
     ],
@@ -21,7 +20,6 @@
         "soong-android-allowlists",
         "soong-android-soongconfig",
         "soong-apex",
-        "soong-bazel",
         "soong-cc",
         "soong-cc-config",
         "soong-etc",
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index bd56768..18213a8 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -26,7 +26,6 @@
 	"strings"
 
 	"android/soong/android"
-	"android/soong/bazel"
 	"android/soong/starlark_fmt"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -182,12 +181,11 @@
 }
 
 type CodegenContext struct {
-	config             android.Config
-	context            *android.Context
-	mode               CodegenMode
-	additionalDeps     []string
-	unconvertedDepMode unconvertedDepsMode
-	topDir             string
+	config         android.Config
+	context        *android.Context
+	mode           CodegenMode
+	additionalDeps []string
+	topDir         string
 }
 
 func (ctx *CodegenContext) Mode() CodegenMode {
@@ -207,16 +205,6 @@
 	QueryView CodegenMode = iota
 )
 
-type unconvertedDepsMode int
-
-const (
-	// Include a warning in conversion metrics about converted modules with unconverted direct deps
-	warnUnconvertedDeps unconvertedDepsMode = iota
-	// Error and fail conversion if encountering a module with unconverted direct deps
-	// Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
-	errorModulesUnconvertedDeps
-)
-
 func (mode CodegenMode) String() string {
 	switch mode {
 	case QueryView:
@@ -245,13 +233,11 @@
 // NewCodegenContext creates a wrapper context that conforms to PathContext for
 // writing BUILD files in the output directory.
 func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext {
-	var unconvertedDeps unconvertedDepsMode
 	return &CodegenContext{
-		context:            context,
-		config:             config,
-		mode:               mode,
-		unconvertedDepMode: unconvertedDeps,
-		topDir:             topDir,
+		context: context,
+		config:  config,
+		mode:    mode,
+		topDir:  topDir,
 	}
 }
 
@@ -482,14 +468,6 @@
 		}), nil
 
 	case reflect.Struct:
-		// Special cases where the bp2build sends additional information to the codegenerator
-		// by wrapping the attributes in a custom struct type.
-		if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
-			return prettyPrintAttribute(attr, indent)
-		} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
-			return fmt.Sprintf("%q", label.Label), nil
-		}
-
 		// Sort and print the struct props by the key.
 		structProps, err := extractStructProperties(propertyValue, indent)
 
@@ -506,7 +484,7 @@
 		// Interfaces are used for for arch, multilib and target properties.
 		return "", nil
 	case reflect.Map:
-		if v, ok := propertyValue.Interface().(bazel.StringMapAttribute); ok {
+		if v, ok := propertyValue.Interface().(map[string]string); ok {
 			return starlark_fmt.PrintStringStringDict(v, indent), nil
 		}
 		return "", fmt.Errorf("bp2build expects map of type map[string]string for field: %s", propertyValue)
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
deleted file mode 100644
index 3d9f0a2..0000000
--- a/bp2build/configurability.go
+++ /dev/null
@@ -1,328 +0,0 @@
-package bp2build
-
-import (
-	"fmt"
-	"reflect"
-
-	"android/soong/android"
-	"android/soong/bazel"
-	"android/soong/starlark_fmt"
-)
-
-// Configurability support for bp2build.
-
-type selects map[string]reflect.Value
-
-func getStringValue(str bazel.StringAttribute) (reflect.Value, []selects) {
-	value := reflect.ValueOf(str.Value)
-
-	if !str.HasConfigurableValues() {
-		return value, []selects{}
-	}
-
-	ret := selects{}
-	for _, axis := range str.SortedConfigurationAxes() {
-		configToStrs := str.ConfigurableValues[axis]
-		for config, strs := range configToStrs {
-			selectKey := axis.SelectKey(config)
-			ret[selectKey] = reflect.ValueOf(strs)
-		}
-	}
-
-	// if there is a select, use the base value as the conditions default value
-	if len(ret) > 0 {
-		if _, ok := ret[bazel.ConditionsDefaultSelectKey]; !ok {
-			ret[bazel.ConditionsDefaultSelectKey] = value
-			value = reflect.Zero(value.Type())
-		}
-	}
-
-	return value, []selects{ret}
-}
-
-func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects, bool) {
-	value := reflect.ValueOf(list.Value)
-	prepend := list.Prepend
-	if !list.HasConfigurableValues() {
-		return value, []selects{}, prepend
-	}
-
-	var ret []selects
-	for _, axis := range list.SortedConfigurationAxes() {
-		configToLists := list.ConfigurableValues[axis]
-		archSelects := map[string]reflect.Value{}
-		for config, labels := range configToLists {
-			selectKey := axis.SelectKey(config)
-			archSelects[selectKey] = reflect.ValueOf(labels)
-		}
-		if len(archSelects) > 0 {
-			ret = append(ret, archSelects)
-		}
-	}
-
-	return value, ret, prepend
-}
-
-func getLabelValue(label bazel.LabelAttribute) (reflect.Value, []selects) {
-	value := reflect.ValueOf(label.Value)
-	if !label.HasConfigurableValues() {
-		return value, []selects{}
-	}
-
-	ret := selects{}
-	for _, axis := range label.SortedConfigurationAxes() {
-		configToLabels := label.ConfigurableValues[axis]
-		for config, labels := range configToLabels {
-			selectKey := axis.SelectKey(config)
-			ret[selectKey] = reflect.ValueOf(labels)
-		}
-	}
-
-	// if there is a select, use the base value as the conditions default value
-	if len(ret) > 0 {
-		ret[bazel.ConditionsDefaultSelectKey] = value
-		value = reflect.Zero(value.Type())
-	}
-
-	return value, []selects{ret}
-}
-
-func getBoolValue(boolAttr bazel.BoolAttribute) (reflect.Value, []selects) {
-	value := reflect.ValueOf(boolAttr.Value)
-	if !boolAttr.HasConfigurableValues() {
-		return value, []selects{}
-	}
-
-	ret := selects{}
-	for _, axis := range boolAttr.SortedConfigurationAxes() {
-		configToBools := boolAttr.ConfigurableValues[axis]
-		for config, bools := range configToBools {
-			selectKey := axis.SelectKey(config)
-			ret[selectKey] = reflect.ValueOf(bools)
-		}
-	}
-	// if there is a select, use the base value as the conditions default value
-	if len(ret) > 0 {
-		ret[bazel.ConditionsDefaultSelectKey] = value
-		value = reflect.Zero(value.Type())
-	}
-
-	return value, []selects{ret}
-}
-func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, []selects, bool) {
-	value := reflect.ValueOf(list.Value.Includes)
-	prepend := list.Prepend
-	var ret []selects
-	for _, axis := range list.SortedConfigurationAxes() {
-		configToLabels := list.ConfigurableValues[axis]
-		if !configToLabels.HasConfigurableValues() {
-			continue
-		}
-		archSelects := map[string]reflect.Value{}
-		defaultVal := configToLabels[bazel.ConditionsDefaultConfigKey]
-		// Skip empty list values unless ether EmitEmptyList is true, or these values differ from the default.
-		emitEmptyList := list.EmitEmptyList || len(defaultVal.Includes) > 0
-		for config, labels := range configToLabels {
-			// Omit any entries in the map which match the default value, for brevity.
-			if config != bazel.ConditionsDefaultConfigKey && labels.Equals(defaultVal) {
-				continue
-			}
-			selectKey := axis.SelectKey(config)
-			if use, value := labelListSelectValue(selectKey, labels, emitEmptyList); use {
-				archSelects[selectKey] = value
-			}
-		}
-		if len(archSelects) > 0 {
-			ret = append(ret, archSelects)
-		}
-	}
-
-	return value, ret, prepend
-}
-
-func labelListSelectValue(selectKey string, list bazel.LabelList, emitEmptyList bool) (bool, reflect.Value) {
-	if selectKey == bazel.ConditionsDefaultSelectKey || emitEmptyList || len(list.Includes) > 0 {
-		return true, reflect.ValueOf(list.Includes)
-	} else if len(list.Excludes) > 0 {
-		// if there is still an excludes -- we need to have an empty list for this select & use the
-		// value in conditions default Includes
-		return true, reflect.ValueOf([]string{})
-	}
-	return false, reflect.Zero(reflect.TypeOf([]string{}))
-}
-
-var (
-	emptyBazelList = "[]"
-	bazelNone      = "None"
-)
-
-// prettyPrintAttribute converts an Attribute to its Bazel syntax. May contain
-// select statements.
-func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
-	var value reflect.Value
-	// configurableAttrs is the list of individual select statements to be
-	// concatenated together. These select statements should be along different
-	// axes. For example, one element may be
-	// `select({"//color:red": "one", "//color:green": "two"})`, and the second
-	// element may be `select({"//animal:cat": "three", "//animal:dog": "four"}).
-	// These selects should be sorted by axis identifier.
-	var configurableAttrs []selects
-	var prepend bool
-	var defaultSelectValue *string
-	var emitZeroValues bool
-	// If true, print the default attribute value, even if the attribute is zero.
-	shouldPrintDefault := false
-	switch list := v.(type) {
-	case bazel.StringAttribute:
-		if err := list.Collapse(); err != nil {
-			return "", err
-		}
-		value, configurableAttrs = getStringValue(list)
-		defaultSelectValue = &bazelNone
-	case bazel.StringListAttribute:
-		value, configurableAttrs, prepend = getStringListValues(list)
-		defaultSelectValue = &emptyBazelList
-	case bazel.LabelListAttribute:
-		value, configurableAttrs, prepend = getLabelListValues(list)
-		emitZeroValues = list.EmitEmptyList
-		defaultSelectValue = &emptyBazelList
-		if list.ForceSpecifyEmptyList && (!value.IsNil() || list.HasConfigurableValues()) {
-			shouldPrintDefault = true
-		}
-	case bazel.LabelAttribute:
-		if err := list.Collapse(); err != nil {
-			return "", err
-		}
-		value, configurableAttrs = getLabelValue(list)
-		defaultSelectValue = &bazelNone
-	case bazel.BoolAttribute:
-		if err := list.Collapse(); err != nil {
-			return "", err
-		}
-		value, configurableAttrs = getBoolValue(list)
-		defaultSelectValue = &bazelNone
-	default:
-		return "", fmt.Errorf("Not a supported Bazel attribute type: %s", v)
-	}
-
-	var err error
-	ret := ""
-	if value.Kind() != reflect.Invalid {
-		s, err := prettyPrint(value, indent, false) // never emit zero values for the base value
-		if err != nil {
-			return ret, err
-		}
-
-		ret += s
-	}
-	// Convenience function to prepend/append selects components to an attribute value.
-	concatenateSelects := func(selectsData selects, defaultValue *string, s string, prepend bool) (string, error) {
-		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent, emitZeroValues)
-		if err != nil {
-			return "", err
-		}
-		var left, right string
-		if prepend {
-			left, right = selectMap, s
-		} else {
-			left, right = s, selectMap
-		}
-		if left != "" && right != "" {
-			left += " + "
-		}
-		left += right
-
-		return left, nil
-	}
-
-	for _, configurableAttr := range configurableAttrs {
-		ret, err = concatenateSelects(configurableAttr, defaultSelectValue, ret, prepend)
-		if err != nil {
-			return "", err
-		}
-	}
-
-	if ret == "" && shouldPrintDefault {
-		return *defaultSelectValue, nil
-	}
-	return ret, nil
-}
-
-// prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
-// to construct a select map for any kind of attribute type.
-func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *string, indent int, emitZeroValues bool) (string, error) {
-	if selectMap == nil {
-		return "", nil
-	}
-
-	var selects string
-	for _, selectKey := range android.SortedKeys(selectMap) {
-		if selectKey == bazel.ConditionsDefaultSelectKey {
-			// Handle default condition later.
-			continue
-		}
-		value := selectMap[selectKey]
-		if isZero(value) && !emitZeroValues && isZero(selectMap[bazel.ConditionsDefaultSelectKey]) {
-			// Ignore zero values to not generate empty lists. However, always note zero values if
-			// the default value is non-zero.
-			continue
-		}
-		s, err := prettyPrintSelectEntry(value, selectKey, indent, true)
-		if err != nil {
-			return "", err
-		}
-		// s could still be an empty string, e.g. unset slices of structs with
-		// length of 0.
-		if s != "" {
-			selects += s + ",\n"
-		}
-	}
-
-	if len(selects) == 0 {
-		// If there is a default value, and there are no selects for this axis, print that without any selects.
-		if val, exists := selectMap[bazel.ConditionsDefaultSelectKey]; exists {
-			return prettyPrint(val, indent, emitZeroValues)
-		}
-		// No conditions (or all values are empty lists), so no need for a map.
-		return "", nil
-	}
-
-	// Create the map.
-	ret := "select({\n"
-	ret += selects
-
-	// Handle the default condition
-	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent, emitZeroValues)
-	if err != nil {
-		return "", err
-	}
-	if s != "" {
-		// Print the custom default value.
-		ret += s
-		ret += ",\n"
-	} else if defaultValue != nil {
-		// Print an explicit empty list (the default value) even if the value is
-		// empty, to avoid errors about not finding a configuration that matches.
-		ret += fmt.Sprintf("%s\"%s\": %s,\n", starlark_fmt.Indention(indent+1), bazel.ConditionsDefaultSelectKey, *defaultValue)
-	}
-
-	ret += starlark_fmt.Indention(indent)
-	ret += "})"
-
-	return ret, nil
-}
-
-// prettyPrintSelectEntry converts a reflect.Value into an entry in a select map
-// with a provided key.
-func prettyPrintSelectEntry(value reflect.Value, key string, indent int, emitZeroValues bool) (string, error) {
-	s := starlark_fmt.Indention(indent + 1)
-	v, err := prettyPrint(value, indent+1, emitZeroValues)
-	if err != nil {
-		return "", err
-	}
-	if v == "" {
-		return "", nil
-	}
-	s += fmt.Sprintf("\"%s\": %s", key, v)
-	return s, nil
-}
diff --git a/bp2build/constants.go b/bp2build/constants.go
index 4870dff..76ba106 100644
--- a/bp2build/constants.go
+++ b/bp2build/constants.go
@@ -20,9 +20,4 @@
 
 	// The file name used for automatically generated files.
 	GeneratedBuildFileName = "BUILD.bazel"
-
-	// The file name used for hand-crafted build targets.
-	// NOTE: It is okay that this matches GeneratedBuildFileName, since we generate BUILD files in a different directory to source files
-	// FIXME: Because there are hundreds of existing BUILD.bazel files in the AOSP tree, we should pick another name here, like BUILD.android
-	HandcraftedBuildFileName = "BUILD.bazel"
 )
diff --git a/cc/Android.bp b/cc/Android.bp
index 3688c8a..88a793c 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -102,6 +102,7 @@
         "orderfile_test.go",
         "prebuilt_test.go",
         "proto_test.go",
+        "sabi_test.go",
         "sanitize_test.go",
         "sdk_test.go",
         "test_data_test.go",
diff --git a/cc/cc.go b/cc/cc.go
index a8ff474..1b7624d 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -76,9 +76,9 @@
 		ctx.BottomUp("double_loadable", checkDoubleLoadableLibraries).Parallel()
 	})
 
-	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
+	ctx.PostApexMutators(func(ctx android.RegisterMutatorsContext) {
 		// sabi mutator needs to be run after apex mutator finishes.
-		ctx.TopDown("sabi_deps", sabiDepsMutator)
+		ctx.Transition("sabi", &sabiTransitionMutator{})
 	})
 
 	ctx.RegisterParallelSingletonType("kythe_extract_all", kytheExtractAllFactory)
diff --git a/cc/compiler.go b/cc/compiler.go
index a6f623f..022b712 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -228,9 +228,6 @@
 		Static *bool `android:"arch_variant"`
 	} `android:"arch_variant"`
 
-	// Stores the original list of source files before being cleared by library reuse
-	OriginalSrcs proptools.Configurable[[]string] `blueprint:"mutated"`
-
 	// Build and link with OpenMP
 	Openmp *bool `android:"arch_variant"`
 }
@@ -363,10 +360,20 @@
 	tc := ctx.toolchain()
 	modulePath := ctx.ModuleDir()
 
-	srcs := compiler.Properties.Srcs.GetOrDefault(ctx, nil)
-	exclude_srcs := compiler.Properties.Exclude_srcs.GetOrDefault(ctx, nil)
-	compiler.srcsBeforeGen = android.PathsForModuleSrcExcludes(ctx, srcs, exclude_srcs)
-	compiler.srcsBeforeGen = append(compiler.srcsBeforeGen, deps.GeneratedSources...)
+	reuseObjs := false
+	if len(ctx.GetDirectDepsWithTag(reuseObjTag)) > 0 {
+		reuseObjs = true
+	}
+
+	// If a reuseObjTag dependency exists then this module is reusing the objects (generally the shared variant
+	// reusing objects from the static variant), and doesn't need to compile any sources of its own.
+	var srcs []string
+	if !reuseObjs {
+		srcs = compiler.Properties.Srcs.GetOrDefault(ctx, nil)
+		exclude_srcs := compiler.Properties.Exclude_srcs.GetOrDefault(ctx, nil)
+		compiler.srcsBeforeGen = android.PathsForModuleSrcExcludes(ctx, srcs, exclude_srcs)
+		compiler.srcsBeforeGen = append(compiler.srcsBeforeGen, deps.GeneratedSources...)
+	}
 
 	cflags := compiler.Properties.Cflags.GetOrDefault(ctx, nil)
 	cppflags := compiler.Properties.Cppflags.GetOrDefault(ctx, nil)
@@ -721,11 +728,6 @@
 			return true
 		}
 	}
-	for _, src := range compiler.Properties.OriginalSrcs.GetOrDefault(ctx, nil) {
-		if filepath.Ext(src) == ext {
-			return true
-		}
-	}
 
 	return false
 }
diff --git a/cc/library.go b/cc/library.go
index 3833b98..988a7fa 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -548,8 +548,7 @@
 	return flags
 }
 
-func (library *libraryDecorator) getHeaderAbiCheckerProperties(ctx android.BaseModuleContext) headerAbiCheckerProperties {
-	m := ctx.Module().(*Module)
+func (library *libraryDecorator) getHeaderAbiCheckerProperties(m *Module) headerAbiCheckerProperties {
 	variantProps := &library.Properties.Target.Platform.Header_abi_checker
 	if m.InVendor() {
 		variantProps = &library.Properties.Target.Vendor.Header_abi_checker
@@ -559,7 +558,7 @@
 	props := library.Properties.Header_abi_checker
 	err := proptools.AppendProperties(&props, variantProps, nil)
 	if err != nil {
-		ctx.ModuleErrorf("Cannot merge headerAbiCheckerProperties: %s", err.Error())
+		panic(fmt.Errorf("Cannot merge headerAbiCheckerProperties: %s", err.Error()))
 	}
 	return props
 }
@@ -718,7 +717,7 @@
 	setShared()
 
 	// Gets the ABI properties for vendor, product, or platform variant
-	getHeaderAbiCheckerProperties(ctx android.BaseModuleContext) headerAbiCheckerProperties
+	getHeaderAbiCheckerProperties(m *Module) headerAbiCheckerProperties
 
 	// Write LOCAL_ADDITIONAL_DEPENDENCIES for ABI diff
 	androidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer)
@@ -1365,7 +1364,7 @@
 	sourceVersion, errorMessage string) {
 
 	extraFlags := []string{"-target-version", sourceVersion}
-	headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx)
+	headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx.Module().(*Module))
 	if Bool(headerAbiChecker.Check_all_apis) {
 		extraFlags = append(extraFlags, "-check-all-apis")
 	} else {
@@ -1437,7 +1436,7 @@
 func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, deps PathDeps, objs Objects, fileName string, soFile android.Path) {
 	if library.sabi.shouldCreateSourceAbiDump() {
 		exportedIncludeDirs := library.exportedIncludeDirsForAbiCheck(ctx)
-		headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx)
+		headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx.Module().(*Module))
 		currSdkVersion := currRefAbiDumpSdkVersion(ctx)
 		currVendorVersion := ctx.Config().VendorApiLevel()
 
@@ -1451,7 +1450,7 @@
 			[]string{} /* includeSymbolTags */, currSdkVersion, false /* isLlndk */)
 
 		var llndkDump, apexVariantDump android.Path
-		tags := classifySourceAbiDump(ctx)
+		tags := classifySourceAbiDump(ctx.Module().(*Module))
 		optInTags := []lsdumpTag{}
 		for _, tag := range tags {
 			if tag == llndkLsdumpTag && currVendorVersion != "" {
@@ -1868,7 +1867,7 @@
 }
 
 func (library *libraryDecorator) symbolFileForAbiCheck(ctx ModuleContext) *string {
-	if props := library.getHeaderAbiCheckerProperties(ctx); props.Symbol_file != nil {
+	if props := library.getHeaderAbiCheckerProperties(ctx.Module().(*Module)); props.Symbol_file != nil {
 		return props.Symbol_file
 	}
 	if library.hasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
@@ -2071,12 +2070,7 @@
 			sharedCompiler.StaticProperties.Static.System_shared_libs == nil &&
 			sharedCompiler.SharedProperties.Shared.System_shared_libs == nil {
 
-			// TODO: namespaces?
 			ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, reuseObjTag, ctx.ModuleName())
-			sharedCompiler.baseCompiler.Properties.OriginalSrcs =
-				sharedCompiler.baseCompiler.Properties.Srcs
-			sharedCompiler.baseCompiler.Properties.Srcs = proptools.NewConfigurable[[]string](nil, nil)
-			sharedCompiler.baseCompiler.Properties.Generated_sources = nil
 		}
 
 		// This dep is just to reference static variant from shared variant
diff --git a/cc/sabi.go b/cc/sabi.go
index 64eab41..2caf0d4 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -84,8 +84,8 @@
 
 type SAbiProperties struct {
 	// Whether ABI dump should be created for this module.
-	// Set by `sabiDepsMutator` if this module is a shared library that needs ABI check, or a static
-	// library that is depended on by an ABI checked library.
+	// Set by `sabiTransitionMutator` if this module is a shared library that needs ABI check,
+	// or a static library that is depended on by an ABI checked library.
 	ShouldCreateSourceAbiDump bool `blueprint:"mutated"`
 
 	// Include directories that may contain ABI information exported by a library.
@@ -121,10 +121,9 @@
 }
 
 // Returns a slice of strings that represent the ABI dumps generated for this module.
-func classifySourceAbiDump(ctx android.BaseModuleContext) []lsdumpTag {
+func classifySourceAbiDump(m *Module) []lsdumpTag {
 	result := []lsdumpTag{}
-	m := ctx.Module().(*Module)
-	headerAbiChecker := m.library.getHeaderAbiCheckerProperties(ctx)
+	headerAbiChecker := m.library.getHeaderAbiCheckerProperties(m)
 	if headerAbiChecker.explicitlyDisabled() {
 		return result
 	}
@@ -149,24 +148,37 @@
 	return result
 }
 
-// Called from sabiDepsMutator to check whether ABI dumps should be created for this module.
+type shouldCreateAbiDumpContext interface {
+	android.ModuleProviderContext
+	Module() android.Module
+	Config() android.Config
+}
+
+var _ shouldCreateAbiDumpContext = android.ModuleContext(nil)
+var _ shouldCreateAbiDumpContext = android.OutgoingTransitionContext(nil)
+
+// Called from sabiTransitionMutator to check whether ABI dumps should be created for this module.
 // ctx should be wrapping a native library type module.
-func shouldCreateSourceAbiDumpForLibrary(ctx android.BaseModuleContext) bool {
-	// Only generate ABI dump for device modules.
-	if !ctx.Device() {
+func shouldCreateSourceAbiDumpForLibrary(ctx shouldCreateAbiDumpContext) bool {
+	m, ok := ctx.Module().(*Module)
+	if !ok {
 		return false
 	}
 
-	m := ctx.Module().(*Module)
+	// Only generate ABI dump for device modules.
+	if !m.Device() {
+		return false
+	}
 
 	// Only create ABI dump for native library module types.
 	if m.library == nil {
 		return false
 	}
 
-	// Create ABI dump for static libraries only if they are dependencies of ABI checked libraries.
+	// Don't create ABI dump for static libraries
+	// The sabi variant will be propagated to dependencies of ABI checked libraries.
 	if m.library.static() {
-		return m.sabi.shouldCreateSourceAbiDump()
+		return false
 	}
 
 	// Module is shared library type.
@@ -215,31 +227,64 @@
 			return false
 		}
 	}
-	return len(classifySourceAbiDump(ctx)) > 0
+	return len(classifySourceAbiDump(m)) > 0
 }
 
 // Mark the direct and transitive dependencies of libraries that need ABI check, so that ABI dumps
 // of their dependencies would be generated.
-func sabiDepsMutator(mctx android.TopDownMutatorContext) {
+type sabiTransitionMutator struct{}
+
+func (s *sabiTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	return []string{""}
+}
+
+func (s *sabiTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
 	// Escape hatch to not check any ABI dump.
-	if mctx.Config().IsEnvTrue("SKIP_ABI_CHECKS") {
-		return
+	if ctx.Config().IsEnvTrue("SKIP_ABI_CHECKS") {
+		return ""
 	}
+
 	// Only create ABI dump for native shared libraries and their static library dependencies.
-	if m, ok := mctx.Module().(*Module); ok && m.sabi != nil {
-		if shouldCreateSourceAbiDumpForLibrary(mctx) {
-			// Mark this module so that .sdump / .lsdump for this library can be generated.
+	if m, ok := ctx.Module().(*Module); ok && m.sabi != nil {
+		if shouldCreateSourceAbiDumpForLibrary(ctx) {
+			if IsStaticDepTag(ctx.DepTag()) || ctx.DepTag() == reuseObjTag {
+				return "sabi"
+			}
+		} else if sourceVariation == "sabi" {
+			if IsWholeStaticLib(ctx.DepTag()) || ctx.DepTag() == reuseObjTag {
+				return "sabi"
+			}
+		}
+	}
+
+	return ""
+}
+
+func (s *sabiTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if incomingVariation == "" {
+		return ""
+	}
+
+	if incomingVariation == "sabi" {
+		if m, ok := ctx.Module().(*Module); ok && m.sabi != nil {
+			return "sabi"
+		}
+	}
+
+	return ""
+}
+
+func (s *sabiTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if m, ok := ctx.Module().(*Module); ok && m.sabi != nil {
+		if variation == "sabi" {
 			m.sabi.Properties.ShouldCreateSourceAbiDump = true
-			// Mark all of its static library dependencies.
-			mctx.VisitDirectDeps(func(child android.Module) {
-				depTag := mctx.OtherModuleDependencyTag(child)
-				if IsStaticDepTag(depTag) || depTag == reuseObjTag {
-					if c, ok := child.(*Module); ok && c.sabi != nil {
-						// Mark this module so that .sdump for this static library can be generated.
-						c.sabi.Properties.ShouldCreateSourceAbiDump = true
-					}
-				}
-			})
+			m.HideFromMake()
+			m.Properties.PreventInstall = true
+		} else if shouldCreateSourceAbiDumpForLibrary(ctx) {
+			// Escape hatch to not check any ABI dump.
+			if !ctx.Config().IsEnvTrue("SKIP_ABI_CHECKS") {
+				m.sabi.Properties.ShouldCreateSourceAbiDump = true
+			}
 		}
 	}
 }
diff --git a/cc/sabi_test.go b/cc/sabi_test.go
new file mode 100644
index 0000000..6b8cc17
--- /dev/null
+++ b/cc/sabi_test.go
@@ -0,0 +1,66 @@
+// 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 cc
+
+import (
+	"android/soong/android"
+	"testing"
+)
+
+func TestSabi(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "libsabi",
+			srcs: ["sabi.cpp"],
+			static_libs: ["libdirect"],
+			header_abi_checker: {
+				enabled: true,
+				symbol_file: "libsabi.map.txt",
+                ref_dump_dirs: ["abi-dumps"],
+			},
+		}
+
+		cc_library {
+			name: "libdirect",
+			srcs: ["direct.cpp"],
+			whole_static_libs: ["libtransitive"],
+		}
+
+		cc_library {
+			name: "libtransitive",
+			srcs: ["transitive.cpp"],
+		}
+	`
+
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithCcDefaultModules,
+	).RunTestWithBp(t, bp)
+
+	libsabiStatic := result.ModuleForTests("libsabi", "android_arm64_armv8-a_static_sabi")
+	sabiObjSDump := libsabiStatic.Output("obj/sabi.sdump")
+
+	libDirect := result.ModuleForTests("libdirect", "android_arm64_armv8-a_static_sabi")
+	directObjSDump := libDirect.Output("obj/direct.sdump")
+
+	libTransitive := result.ModuleForTests("libtransitive", "android_arm64_armv8-a_static_sabi")
+	transitiveObjSDump := libTransitive.Output("obj/transitive.sdump")
+
+	libsabiShared := result.ModuleForTests("libsabi", "android_arm64_armv8-a_shared")
+	sabiLink := libsabiShared.Rule("sAbiLink")
+
+	android.AssertStringListContains(t, "sabi link inputs", sabiLink.Inputs.Strings(), sabiObjSDump.Output.String())
+	android.AssertStringListContains(t, "sabi link inputs", sabiLink.Inputs.Strings(), directObjSDump.Output.String())
+	android.AssertStringListContains(t, "sabi link inputs", sabiLink.Inputs.Strings(), transitiveObjSDump.Output.String())
+}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index a8722a0..85fdb02 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -79,7 +79,7 @@
 
 	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
 		"-fno-sanitize-recover=integer,undefined"}
-	memtagStackCommonFlags = []string{"-Xclang -target-feature -Xclang +mte"}
+	memtagStackCommonFlags = []string{"-march=armv8-a+memtag"}
 	memtagStackLlvmFlags   = []string{"-dom-tree-reachability-max-bbs-to-explore=128"}
 
 	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
@@ -176,7 +176,7 @@
 	switch t {
 	case cfi, Hwasan, Asan, tsan, Fuzzer, scs, Memtag_stack:
 		sanitizer := &sanitizerSplitMutator{t}
-		ctx.BottomUp(t.variationName()+"_markapexes", sanitizer.markSanitizableApexesMutator)
+		ctx.BottomUp(t.variationName()+"_markapexes", sanitizer.markSanitizableApexesMutator).Parallel()
 		ctx.Transition(t.variationName(), sanitizer)
 	case Memtag_heap, Memtag_globals, intOverflow:
 		// do nothing
diff --git a/cmd/release_config/release_config_contributions/Android.bp b/cmd/release_config/release_config_contributions/Android.bp
new file mode 100644
index 0000000..6882ea2
--- /dev/null
+++ b/cmd/release_config/release_config_contributions/Android.bp
@@ -0,0 +1,32 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "release-config-contributions",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-proto",
+        "soong-cmd-release_config-lib",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-release_config-release_config_contributions",
+    pkgPath: "android/soong/cmd/release_config/release_config_contributions",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-proto",
+        "soong-cmd-release_config-lib",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
diff --git a/cmd/release_config/release_config_contributions/main.go b/cmd/release_config/release_config_contributions/main.go
new file mode 100644
index 0000000..a954cf1
--- /dev/null
+++ b/cmd/release_config/release_config_contributions/main.go
@@ -0,0 +1,130 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"slices"
+	"strings"
+
+	rc_lib "android/soong/cmd/release_config/release_config_lib"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
+)
+
+type Flags struct {
+	// The path to the top of the workspace.  Default: ".".
+	top string
+
+	// Output file.
+	output string
+
+	// Format for output file
+	format string
+
+	// List of release config directories to process.
+	dirs rc_lib.StringList
+
+	// Disable warning messages
+	quiet bool
+
+	// Panic on errors.
+	debug bool
+}
+
+func sortDirectories(dirList []string) {
+	order := func(dir string) int {
+		switch {
+		// These three are always in this order.
+		case dir == "build/release":
+			return 1
+		case dir == "vendor/google_shared/build/release":
+			return 2
+		case dir == "vendor/google/release":
+			return 3
+		// Keep their subdirs in the same order.
+		case strings.HasPrefix(dir, "build/release/"):
+			return 21
+		case strings.HasPrefix(dir, "vendor/google_shared/build/release/"):
+			return 22
+		case strings.HasPrefix(dir, "vendor/google/release/"):
+			return 23
+		// Everything else sorts by directory path.
+		default:
+			return 99
+		}
+	}
+
+	slices.SortFunc(dirList, func(a, b string) int {
+		aOrder, bOrder := order(a), order(b)
+		if aOrder != bOrder {
+			return aOrder - bOrder
+		}
+		return strings.Compare(a, b)
+	})
+}
+
+func main() {
+	var flags Flags
+	topDir, err := rc_lib.GetTopDir()
+
+	// Handle the common arguments
+	flag.StringVar(&flags.top, "top", topDir, "path to top of workspace")
+	flag.Var(&flags.dirs, "dir", "path to a release config contribution directory. May be repeated")
+	flag.StringVar(&flags.format, "format", "pb", "output file format")
+	flag.StringVar(&flags.output, "output", "release_config_contributions.pb", "output file")
+	flag.BoolVar(&flags.debug, "debug", false, "turn on debugging output for errors")
+	flag.BoolVar(&flags.quiet, "quiet", false, "disable warning messages")
+	flag.Parse()
+
+	errorExit := func(err error) {
+		if flags.debug {
+			panic(err)
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err)
+		os.Exit(1)
+	}
+
+	if flags.quiet {
+		rc_lib.DisableWarnings()
+	}
+
+	if err = os.Chdir(flags.top); err != nil {
+		errorExit(err)
+	}
+
+	contributingDirsMap := make(map[string][]string)
+	for _, dir := range flags.dirs {
+		contributions, err := rc_lib.EnumerateReleaseConfigs(dir)
+		if err != nil {
+			errorExit(err)
+		}
+		for _, name := range contributions {
+			contributingDirsMap[name] = append(contributingDirsMap[name], dir)
+		}
+	}
+
+	releaseConfigNames := []string{}
+	for name := range contributingDirsMap {
+		releaseConfigNames = append(releaseConfigNames, name)
+	}
+	slices.Sort(releaseConfigNames)
+
+	message := &rc_proto.ReleaseConfigContributionsArtifacts{
+		ReleaseConfigContributionsArtifactList: []*rc_proto.ReleaseConfigContributionsArtifact{},
+	}
+	for _, name := range releaseConfigNames {
+		dirs := contributingDirsMap[name]
+		sortDirectories(dirs)
+		message.ReleaseConfigContributionsArtifactList = append(
+			message.ReleaseConfigContributionsArtifactList,
+			&rc_proto.ReleaseConfigContributionsArtifact{
+				Name:                    &name,
+				ContributingDirectories: dirs,
+			})
+	}
+
+	err = rc_lib.WriteFormattedMessage(flags.output, flags.format, message)
+	if err != nil {
+		errorExit(err)
+	}
+}
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index 97eb8f1..831ec02 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -248,6 +248,18 @@
 	return configs.configDirs[index], nil
 }
 
+// Return the (unsorted) release configs contributed to by `dir`.
+func EnumerateReleaseConfigs(dir string) ([]string, error) {
+	var ret []string
+	err := WalkTextprotoFiles(dir, "release_configs", func(path string, d fs.DirEntry, err error) error {
+		// Strip off the trailing `.textproto` from the name.
+		name := filepath.Base(path)
+		ret = append(ret, name[:len(name)-10])
+		return err
+	})
+	return ret, err
+}
+
 func (configs *ReleaseConfigs) LoadReleaseConfigMap(path string, ConfigDirIndex int) error {
 	if _, err := os.Stat(path); err != nil {
 		return fmt.Errorf("%s does not exist\n", path)
diff --git a/cmd/release_config/release_config_proto/Android.bp b/cmd/release_config/release_config_proto/Android.bp
index c34d203..c6869b1 100644
--- a/cmd/release_config/release_config_proto/Android.bp
+++ b/cmd/release_config/release_config_proto/Android.bp
@@ -28,5 +28,6 @@
         "build_flags_declarations.pb.go",
         "build_flags_src.pb.go",
         "build_flags_out.pb.go",
+        "release_configs_contributions.pb.go",
     ],
 }
diff --git a/cmd/release_config/release_config_proto/regen.sh b/cmd/release_config/release_config_proto/regen.sh
index 23e3115..a92bfc0 100644
--- a/cmd/release_config/release_config_proto/regen.sh
+++ b/cmd/release_config/release_config_proto/regen.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-aprotoc --go_out=paths=source_relative:. build_flags_src.proto build_flags_out.proto build_flags_common.proto build_flags_declarations.proto
+aprotoc --go_out=paths=source_relative:. build_flags_src.proto build_flags_out.proto build_flags_common.proto build_flags_declarations.proto release_configs_contributions.proto
diff --git a/cmd/release_config/release_config_proto/release_configs_contributions.pb.go b/cmd/release_config/release_config_proto/release_configs_contributions.pb.go
new file mode 100644
index 0000000..54854f1
--- /dev/null
+++ b/cmd/release_config/release_config_proto/release_configs_contributions.pb.go
@@ -0,0 +1,252 @@
+//
+// Copyright (C) 2025 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        v3.21.12
+// source: release_configs_contributions.proto
+
+package release_config_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ReleaseConfigContributionsArtifact struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The name of the release config.
+	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// The release config contribution directories that may contribute to this
+	// release config.
+	ContributingDirectories []string `protobuf:"bytes,2,rep,name=contributing_directories,json=contributingDirectories" json:"contributing_directories,omitempty"`
+}
+
+func (x *ReleaseConfigContributionsArtifact) Reset() {
+	*x = ReleaseConfigContributionsArtifact{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_release_configs_contributions_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReleaseConfigContributionsArtifact) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReleaseConfigContributionsArtifact) ProtoMessage() {}
+
+func (x *ReleaseConfigContributionsArtifact) ProtoReflect() protoreflect.Message {
+	mi := &file_release_configs_contributions_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReleaseConfigContributionsArtifact.ProtoReflect.Descriptor instead.
+func (*ReleaseConfigContributionsArtifact) Descriptor() ([]byte, []int) {
+	return file_release_configs_contributions_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ReleaseConfigContributionsArtifact) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *ReleaseConfigContributionsArtifact) GetContributingDirectories() []string {
+	if x != nil {
+		return x.ContributingDirectories
+	}
+	return nil
+}
+
+type ReleaseConfigContributionsArtifacts struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The artifacts
+	ReleaseConfigContributionsArtifactList []*ReleaseConfigContributionsArtifact `protobuf:"bytes,1,rep,name=release_config_contributions_artifact_list,json=releaseConfigContributionsArtifactList" json:"release_config_contributions_artifact_list,omitempty"`
+}
+
+func (x *ReleaseConfigContributionsArtifacts) Reset() {
+	*x = ReleaseConfigContributionsArtifacts{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_release_configs_contributions_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReleaseConfigContributionsArtifacts) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReleaseConfigContributionsArtifacts) ProtoMessage() {}
+
+func (x *ReleaseConfigContributionsArtifacts) ProtoReflect() protoreflect.Message {
+	mi := &file_release_configs_contributions_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReleaseConfigContributionsArtifacts.ProtoReflect.Descriptor instead.
+func (*ReleaseConfigContributionsArtifacts) Descriptor() ([]byte, []int) {
+	return file_release_configs_contributions_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ReleaseConfigContributionsArtifacts) GetReleaseConfigContributionsArtifactList() []*ReleaseConfigContributionsArtifact {
+	if x != nil {
+		return x.ReleaseConfigContributionsArtifactList
+	}
+	return nil
+}
+
+var File_release_configs_contributions_proto protoreflect.FileDescriptor
+
+var file_release_configs_contributions_proto_rawDesc = []byte{
+	0x0a, 0x23, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x22, 0x73, 0x0a, 0x22, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a,
+	0x18, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x69,
+	0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x17, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x72,
+	0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x23, 0x52, 0x65, 0x6c,
+	0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69,
+	0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73,
+	0x12, 0x9c, 0x01, 0x0a, 0x2a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x41,
+	0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x26, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69,
+	0x6f, 0x6e, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x42,
+	0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f,
+	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	file_release_configs_contributions_proto_rawDescOnce sync.Once
+	file_release_configs_contributions_proto_rawDescData = file_release_configs_contributions_proto_rawDesc
+)
+
+func file_release_configs_contributions_proto_rawDescGZIP() []byte {
+	file_release_configs_contributions_proto_rawDescOnce.Do(func() {
+		file_release_configs_contributions_proto_rawDescData = protoimpl.X.CompressGZIP(file_release_configs_contributions_proto_rawDescData)
+	})
+	return file_release_configs_contributions_proto_rawDescData
+}
+
+var file_release_configs_contributions_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_release_configs_contributions_proto_goTypes = []interface{}{
+	(*ReleaseConfigContributionsArtifact)(nil),  // 0: android.release_config_proto.ReleaseConfigContributionsArtifact
+	(*ReleaseConfigContributionsArtifacts)(nil), // 1: android.release_config_proto.ReleaseConfigContributionsArtifacts
+}
+var file_release_configs_contributions_proto_depIdxs = []int32{
+	0, // 0: android.release_config_proto.ReleaseConfigContributionsArtifacts.release_config_contributions_artifact_list:type_name -> android.release_config_proto.ReleaseConfigContributionsArtifact
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_release_configs_contributions_proto_init() }
+func file_release_configs_contributions_proto_init() {
+	if File_release_configs_contributions_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_release_configs_contributions_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ReleaseConfigContributionsArtifact); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_release_configs_contributions_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ReleaseConfigContributionsArtifacts); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_release_configs_contributions_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_release_configs_contributions_proto_goTypes,
+		DependencyIndexes: file_release_configs_contributions_proto_depIdxs,
+		MessageInfos:      file_release_configs_contributions_proto_msgTypes,
+	}.Build()
+	File_release_configs_contributions_proto = out.File
+	file_release_configs_contributions_proto_rawDesc = nil
+	file_release_configs_contributions_proto_goTypes = nil
+	file_release_configs_contributions_proto_depIdxs = nil
+}
diff --git a/cmd/release_config/release_config_proto/release_configs_contributions.proto b/cmd/release_config/release_config_proto/release_configs_contributions.proto
new file mode 100644
index 0000000..bc7aeda
--- /dev/null
+++ b/cmd/release_config/release_config_proto/release_configs_contributions.proto
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2025 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.
+
+syntax = "proto2";
+package android.release_config_proto;
+option go_package = "android/soong/release_config/release_config_proto";
+
+message ReleaseConfigContributionsArtifact {
+  // The name of the release config.
+  optional string name = 1;
+
+  // The release config contribution directories that may contribute to this
+  // release config.
+  repeated string contributing_directories = 2;
+}
+
+message ReleaseConfigContributionsArtifacts {
+  // The artifacts
+  repeated ReleaseConfigContributionsArtifact release_config_contributions_artifact_list = 1;
+}
diff --git a/compliance/Android.bp b/compliance/Android.bp
index 80f5685..72c2f27 100644
--- a/compliance/Android.bp
+++ b/compliance/Android.bp
@@ -35,6 +35,5 @@
     partition_name: "system",
     visibility: [
         "//build/make/target/product/generic",
-        "//device/google/cuttlefish/system_image",
     ],
 }
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index fe6317c..84d4f10 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -191,6 +191,10 @@
 	ForceCreateAppImage bool
 
 	PresignedPrebuilt bool
+
+	// ApexPartition is the partition in which the dexpreopt files of apex system server jars (if any) are installed.
+	// This is a noop unless the module is apex system server jar.
+	ApexPartition string
 }
 
 type globalSoongConfigSingleton struct{}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 5616483..7a39fa1 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -219,9 +219,9 @@
 }
 
 // Returns the location to the odex file for the dex file at `path`.
-func ToOdexPath(path string, arch android.ArchType) string {
+func ToOdexPath(path string, arch android.ArchType, partition string) string {
 	if strings.HasPrefix(path, "/apex/") {
-		return filepath.Join("/system/framework/oat", arch.String(),
+		return filepath.Join(partition, "framework/oat", arch.String(),
 			strings.ReplaceAll(path[1:], "/", "@")+"@classes.odex")
 	}
 
@@ -245,7 +245,7 @@
 
 	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
 	odexSymbolsPath := odexPath.ReplaceExtension(ctx, "symbols.odex")
-	odexInstallPath := ToOdexPath(module.DexLocation, arch)
+	odexInstallPath := ToOdexPath(module.DexLocation, arch, module.ApexPartition)
 	if odexOnSystemOther(module, global) {
 		odexInstallPath = filepath.Join(SystemOtherPartition, odexInstallPath)
 	}
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 6f7d3bb..7b0f51f 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -42,12 +42,14 @@
 }
 
 func testApexModuleConfig(ctx android.PathContext, name, apexName string) *ModuleConfig {
-	return createTestModuleConfig(
+	ret := createTestModuleConfig(
 		name,
 		fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, name),
 		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
 		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
 		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+	ret.ApexPartition = "/system"
+	return ret
 }
 
 func testPlatformSystemServerModuleConfig(ctx android.PathContext, name string) *ModuleConfig {
@@ -221,6 +223,49 @@
 	DexpreoptRunningInSoong = oldDexpreoptRunningInSoong
 }
 
+// Same as `TestDexPreoptApexSystemServerJars`, but the apex jar is in /system_ext
+func TestDexPreoptApexSystemServerJarsSystemExt(t *testing.T) {
+	// modify the global variable for test
+	var oldDexpreoptRunningInSoong = DexpreoptRunningInSoong
+	DexpreoptRunningInSoong = true
+
+	// test begin
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests(ctx)
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+	module.ApexPartition = "/system_ext"
+	productPackages := android.PathForTesting("product_packages.txt")
+
+	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system_ext/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system_ext/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+
+	android.AssertStringListContains(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
+
+	// rule with apex sscp cp as false
+	rule, err = GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	android.AssertStringListDoesNotContain(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
+
+	// cleanup the global variable for test
+	DexpreoptRunningInSoong = oldDexpreoptRunningInSoong
+}
+
 func TestDexPreoptStandaloneSystemServerJars(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
diff --git a/etc/Android.bp b/etc/Android.bp
index 580c54f..8e043b8 100644
--- a/etc/Android.bp
+++ b/etc/Android.bp
@@ -11,6 +11,7 @@
         "soong-android",
     ],
     srcs: [
+        "adb_keys.go",
         "install_symlink.go",
         "otacerts_zip.go",
         "prebuilt_etc.go",
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
new file mode 100644
index 0000000..1bce2f1
--- /dev/null
+++ b/etc/adb_keys.go
@@ -0,0 +1,66 @@
+// 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 etc
+
+import (
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterModuleType("adb_keys", AdbKeysModuleFactory)
+}
+
+type AdbKeysModule struct {
+	android.ModuleBase
+	outputPath  android.OutputPath
+	installPath android.InstallPath
+}
+
+func AdbKeysModuleFactory() android.Module {
+	module := &AdbKeysModule{}
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+func (m *AdbKeysModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	productVariables := ctx.Config().ProductVariables()
+	if !(android.Bool(productVariables.Debuggable) && len(android.String(productVariables.AdbKeys)) > 0) {
+		m.Disable()
+		m.SkipInstall()
+		return
+	}
+
+	m.outputPath = android.PathForModuleOut(ctx, "adb_keys").OutputPath
+	input := android.ExistentPathForSource(ctx, android.String(productVariables.AdbKeys))
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   android.Cp,
+		Output: m.outputPath,
+		Input:  input.Path(),
+	})
+	m.installPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().ProductPath(), "etc/security")
+	ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
+}
+
+func (m *AdbKeysModule) AndroidMkEntries() []android.AndroidMkEntries {
+	if m.IsSkipInstall() {
+		return []android.AndroidMkEntries{}
+	}
+
+	return []android.AndroidMkEntries{
+		{
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(m.outputPath),
+		}}
+}
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
index 8af2ffa..608fccd 100644
--- a/filesystem/aconfig_files.go
+++ b/filesystem/aconfig_files.go
@@ -76,10 +76,13 @@
 		cmd.ImplicitOutput(outputPath)
 		f.appendToEntry(ctx, outputPath)
 	}
-	generatePartitionAconfigStorageFile("package_map", "package.map")
-	generatePartitionAconfigStorageFile("flag_map", "flag.map")
-	generatePartitionAconfigStorageFile("flag_val", "flag.val")
-	generatePartitionAconfigStorageFile("flag_info", "flag.info")
+
+	if ctx.Config().ReleaseCreateAconfigStorageFile() {
+		generatePartitionAconfigStorageFile("package_map", "package.map")
+		generatePartitionAconfigStorageFile("flag_map", "flag.map")
+		generatePartitionAconfigStorageFile("flag_val", "flag.val")
+		generatePartitionAconfigStorageFile("flag_info", "flag.info")
+	}
 
 	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 0353992..09d8fba 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -35,9 +35,9 @@
 }
 
 func registerBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("android_filesystem", filesystemFactory)
+	ctx.RegisterModuleType("android_filesystem", FilesystemFactory)
 	ctx.RegisterModuleType("android_filesystem_defaults", filesystemDefaultsFactory)
-	ctx.RegisterModuleType("android_system_image", systemImageFactory)
+	ctx.RegisterModuleType("android_system_image", SystemImageFactory)
 	ctx.RegisterModuleType("avb_add_hash_footer", avbAddHashFooterFactory)
 	ctx.RegisterModuleType("avb_add_hash_footer_defaults", avbAddHashFooterDefaultsFactory)
 	ctx.RegisterModuleType("avb_gen_vbmeta_image", avbGenVbmetaImageFactory)
@@ -49,7 +49,7 @@
 	android.PackagingBase
 	android.DefaultableModuleBase
 
-	properties filesystemProperties
+	properties FilesystemProperties
 
 	// Function that builds extra files under the root directory and returns the files
 	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
@@ -71,7 +71,7 @@
 	Name   *string
 }
 
-type filesystemProperties struct {
+type FilesystemProperties struct {
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
 
@@ -137,6 +137,12 @@
 	Gen_aconfig_flags_pb *bool
 
 	Fsverity fsverityProperties
+
+	// If this property is set to true, the filesystem will call ctx.UncheckedModule(), causing
+	// it to not be built on checkbuilds. Used for the automatic migration from make to soong
+	// build modules, where we want to emit some not-yet-working filesystems and we don't want them
+	// to be built.
+	Unchecked_module *bool `blueprint:"mutated"`
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -144,7 +150,7 @@
 // modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
 // The modules are placed in the filesystem image just like they are installed to the ordinary
 // partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
-func filesystemFactory() android.Module {
+func FilesystemFactory() android.Module {
 	module := &filesystem{}
 	module.filterPackagingSpec = module.filterInstallablePackagingSpec
 	initFilesystemModule(module, module)
@@ -177,6 +183,13 @@
 	unknown
 )
 
+type FilesystemInfo struct {
+	// A text file containing the list of paths installed on the partition.
+	FileListFile android.Path
+}
+
+var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
+
 func (f *filesystem) fsType(ctx android.ModuleContext) fsType {
 	typeStr := proptools.StringDefault(f.properties.Type, "ext4")
 	switch typeStr {
@@ -227,6 +240,14 @@
 
 	f.fileListFile = android.PathForModuleOut(ctx, "fileList").OutputPath
 	android.WriteFileRule(ctx, f.fileListFile, f.installedFilesList())
+
+	android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
+		FileListFile: f.fileListFile,
+	})
+
+	if proptools.Bool(f.properties.Unchecked_module) {
+		ctx.UncheckedModule()
+	}
 }
 
 func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.OutputPath) {
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 63cb627..57239ae 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -33,7 +33,7 @@
 // android_system_image is a specialization of android_filesystem for the 'system' partition.
 // Currently, the only difference is the inclusion of linker.config.pb file which specifies
 // the provided and the required libraries to and from APEXes.
-func systemImageFactory() android.Module {
+func SystemImageFactory() android.Module {
 	module := &systemImage{}
 	module.AddProperties(&module.properties)
 	module.filesystem.buildExtraFiles = module.buildExtraFiles
@@ -42,6 +42,10 @@
 	return module
 }
 
+func (s systemImage) FsProps() FilesystemProperties {
+	return s.filesystem.properties
+}
+
 func (s *systemImage) buildExtraFiles(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths {
 	if s.filesystem.properties.Partition_type != nil {
 		ctx.PropertyErrorf("partition_type", "partition_type must be unset on an android_system_image module. It is assumed to be 'system'.")
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
new file mode 100644
index 0000000..9fa9557
--- /dev/null
+++ b/fsgen/Android.bp
@@ -0,0 +1,25 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-fsgen",
+    pkgPath: "android/soong/fsgen",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+        "soong-filesystem",
+    ],
+    srcs: [
+        "filesystem_creator.go",
+    ],
+    testSrcs: [
+        "filesystem_creator_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
+
+soong_filesystem_creator {
+    name: "soong_filesystem_creator",
+}
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
new file mode 100644
index 0000000..a9f4256
--- /dev/null
+++ b/fsgen/filesystem_creator.go
@@ -0,0 +1,217 @@
+// 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.
+
+package fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/filesystem"
+	"crypto/sha256"
+	"fmt"
+	"strconv"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var pctx = android.NewPackageContext("android/soong/fsgen")
+
+func init() {
+	registerBuildComponents(android.InitRegistrationContext)
+}
+
+func registerBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("soong_filesystem_creator", filesystemCreatorFactory)
+}
+
+type filesystemCreatorProps struct {
+	Generated_partition_types   []string `blueprint:"mutated"`
+	Unsupported_partition_types []string `blueprint:"mutated"`
+}
+
+type filesystemCreator struct {
+	android.ModuleBase
+
+	properties filesystemCreatorProps
+}
+
+func filesystemCreatorFactory() android.Module {
+	module := &filesystemCreator{}
+
+	android.InitAndroidModule(module)
+	module.AddProperties(&module.properties)
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		module.createInternalModules(ctx)
+	})
+
+	return module
+}
+
+func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
+	for _, partitionType := range []string{"system"} {
+		if f.createPartition(ctx, partitionType) {
+			f.properties.Generated_partition_types = append(f.properties.Generated_partition_types, partitionType)
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, partitionType)
+		}
+	}
+}
+
+func (f *filesystemCreator) generatedModuleNameForPartition(cfg android.Config, partitionType string) string {
+	prefix := "soong"
+	if cfg.HasDeviceProduct() {
+		prefix = cfg.DeviceProduct()
+	}
+	return fmt.Sprintf("%s_generated_%s_image", prefix, partitionType)
+}
+
+// Creates a soong module to build the given partition. Returns false if we can't support building
+// it.
+func (f *filesystemCreator) createPartition(ctx android.LoadHookContext, partitionType string) bool {
+	baseProps := &struct {
+		Name *string
+	}{
+		Name: proptools.StringPtr(f.generatedModuleNameForPartition(ctx.Config(), partitionType)),
+	}
+
+	fsProps := &filesystem.FilesystemProperties{}
+
+	// Don't build this module on checkbuilds, the soong-built partitions are still in-progress
+	// and sometimes don't build.
+	fsProps.Unchecked_module = proptools.BoolPtr(true)
+
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
+
+	// BOARD_AVB_ENABLE
+	fsProps.Use_avb = proptools.BoolPtr(partitionVars.BoardAvbEnable)
+	// BOARD_AVB_KEY_PATH
+	fsProps.Avb_private_key = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
+	// BOARD_AVB_ALGORITHM
+	fsProps.Avb_algorithm = proptools.StringPtr(specificPartitionVars.BoardAvbAlgorithm)
+	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX
+	if rollbackIndex, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndex, 10, 64); err == nil {
+		fsProps.Rollback_index = proptools.Int64Ptr(rollbackIndex)
+	}
+
+	fsProps.Partition_name = proptools.StringPtr(partitionType)
+	// BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE
+	fsProps.Type = proptools.StringPtr(specificPartitionVars.BoardFileSystemType)
+	if *fsProps.Type != "ext4" {
+		// Currently the android_filesystem module type only supports ext4:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/soong/filesystem/filesystem.go;l=416;drc=98047cfd07944b297a12d173453bc984806760d2
+		return false
+	}
+
+	fsProps.Base_dir = proptools.StringPtr(partitionType)
+
+	fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
+
+	// Identical to that of the generic_system_image
+	fsProps.Fsverity.Inputs = []string{
+		"etc/boot-image.prof",
+		"etc/dirty-image-objects",
+		"etc/preloaded-classes",
+		"etc/classpaths/*.pb",
+		"framework/*",
+		"framework/*/*",     // framework/{arch}
+		"framework/oat/*/*", // framework/oat/{arch}
+	}
+
+	// system_image properties that are not set:
+	// - filesystemProperties.Avb_hash_algorithm
+	// - filesystemProperties.File_contexts
+	// - filesystemProperties.Dirs
+	// - filesystemProperties.Symlinks
+	// - filesystemProperties.Fake_timestamp
+	// - filesystemProperties.Uuid
+	// - filesystemProperties.Mount_point
+	// - filesystemProperties.Include_make_built_files
+	// - filesystemProperties.Build_logtags
+	// - filesystemProperties.Fsverity.Libs
+	// - systemImageProperties.Linker_config_src
+	var module android.Module
+	if partitionType == "system" {
+		module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
+	} else {
+		module = ctx.CreateModule(filesystem.FilesystemFactory, baseProps, fsProps)
+	}
+	module.HideFromMake()
+	return true
+}
+
+func (f *filesystemCreator) createDiffTest(ctx android.ModuleContext, partitionType string) android.Path {
+	partitionModuleName := f.generatedModuleNameForPartition(ctx.Config(), partitionType)
+	systemImage := ctx.GetDirectDepWithTag(partitionModuleName, generatedFilesystemDepTag)
+	filesystemInfo, ok := android.OtherModuleProvider(ctx, systemImage, filesystem.FilesystemProvider)
+	if !ok {
+		ctx.ModuleErrorf("Expected module %s to provide FileysystemInfo", partitionModuleName)
+	}
+	makeFileList := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partitionType))
+	// For now, don't allowlist anything. The test will fail, but that's fine in the current
+	// early stages where we're just figuring out what we need
+	emptyAllowlistFile := android.PathForModuleOut(ctx, fmt.Sprintf("allowlist_%s.txt", partitionModuleName))
+	android.WriteFileRule(ctx, emptyAllowlistFile, "")
+	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", partitionModuleName))
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().BuiltTool("file_list_diff").
+		Input(makeFileList).
+		Input(filesystemInfo.FileListFile).
+		Text(partitionModuleName).
+		FlagWithInput("--allowlists ", emptyAllowlistFile)
+	builder.Command().Text("touch").Output(diffTestResultFile)
+	builder.Build(partitionModuleName+" diff test", partitionModuleName+" diff test")
+	return diffTestResultFile
+}
+
+func createFailingCommand(ctx android.ModuleContext, message string) android.Path {
+	hasher := sha256.New()
+	hasher.Write([]byte(message))
+	filename := fmt.Sprintf("failing_command_%x.txt", hasher.Sum(nil))
+	file := android.PathForModuleOut(ctx, filename)
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Textf("echo %s", proptools.NinjaAndShellEscape(message))
+	builder.Command().Text("exit 1 #").Output(file)
+	builder.Build("failing command "+filename, "failing command "+filename)
+	return file
+}
+
+type systemImageDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var generatedFilesystemDepTag systemImageDepTagType
+
+func (f *filesystemCreator) DepsMutator(ctx android.BottomUpMutatorContext) {
+	for _, partitionType := range f.properties.Generated_partition_types {
+		ctx.AddDependency(ctx.Module(), generatedFilesystemDepTag, f.generatedModuleNameForPartition(ctx.Config(), partitionType))
+	}
+}
+
+func (f *filesystemCreator) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if ctx.ModuleDir() != "build/soong/fsgen" {
+		ctx.ModuleErrorf("There can only be one soong_filesystem_creator in build/soong/fsgen")
+	}
+	f.HideFromMake()
+
+	var diffTestFiles []android.Path
+	for _, partitionType := range f.properties.Generated_partition_types {
+		diffTestFiles = append(diffTestFiles, f.createDiffTest(ctx, partitionType))
+	}
+	for _, partitionType := range f.properties.Unsupported_partition_types {
+		diffTestFiles = append(diffTestFiles, createFailingCommand(ctx, fmt.Sprintf("Couldn't build %s partition", partitionType)))
+	}
+	ctx.Phony("soong_generated_filesystem_tests", diffTestFiles...)
+}
diff --git a/fsgen/filesystem_creator_test.go b/fsgen/filesystem_creator_test.go
new file mode 100644
index 0000000..554b66b
--- /dev/null
+++ b/fsgen/filesystem_creator_test.go
@@ -0,0 +1,88 @@
+// 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 fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/filesystem"
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+)
+
+var prepareForTestWithFsgenBuildComponents = android.FixtureRegisterWithContext(registerBuildComponents)
+
+func TestFileSystemCreatorSystemImageProps(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.PrepareForTestWithAndroidBuildComponents,
+		filesystem.PrepareForTestWithFilesystemBuildComponents,
+		prepareForTestWithFsgenBuildComponents,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbEnable = true
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
+				map[string]android.PartitionQualifiedVariablesType{
+					"system": {
+						BoardAvbKeyPath:       "external/avb/test/data/testkey_rsa4096.pem",
+						BoardAvbAlgorithm:     "SHA256_RSA4096",
+						BoardAvbRollbackIndex: "0",
+						BoardFileSystemType:   "ext4",
+					},
+				}
+		}),
+		android.FixtureMergeMockFs(android.MockFS{
+			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"build/soong/fsgen/Android.bp": []byte(`
+			soong_filesystem_creator {
+				name: "foo",
+			}
+			`),
+		}),
+	).RunTest(t)
+
+	fooSystem := result.ModuleForTests("test_product_generated_system_image", "android_common").Module().(interface {
+		FsProps() filesystem.FilesystemProperties
+	})
+	android.AssertBoolEquals(
+		t,
+		"Property expected to match the product variable 'BOARD_AVB_ENABLE'",
+		true,
+		proptools.Bool(fooSystem.FsProps().Use_avb),
+	)
+	android.AssertStringEquals(
+		t,
+		"Property expected to match the product variable 'BOARD_AVB_KEY_PATH'",
+		"external/avb/test/data/testkey_rsa4096.pem",
+		proptools.String(fooSystem.FsProps().Avb_private_key),
+	)
+	android.AssertStringEquals(
+		t,
+		"Property expected to match the product variable 'BOARD_AVB_ALGORITHM'",
+		"SHA256_RSA4096",
+		proptools.String(fooSystem.FsProps().Avb_algorithm),
+	)
+	android.AssertIntEquals(
+		t,
+		"Property expected to match the product variable 'BOARD_AVB_SYSTEM_ROLLBACK_INDEX'",
+		0,
+		proptools.Int(fooSystem.FsProps().Rollback_index),
+	)
+	android.AssertStringEquals(
+		t,
+		"Property expected to match the product variable 'BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE'",
+		"ext4",
+		proptools.String(fooSystem.FsProps().Type),
+	)
+}
diff --git a/golang/golang.go b/golang/golang.go
index 618a085..6ee924f 100644
--- a/golang/golang.go
+++ b/golang/golang.go
@@ -22,6 +22,7 @@
 
 import (
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 )
@@ -46,7 +47,7 @@
 func goPackageModuleFactory() android.Module {
 	module := &GoPackage{}
 	module.AddProperties(module.Properties()...)
-	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	android.InitAndroidArchModule(module, android.HostSupportedNoCross, android.MultilibFirst)
 	return module
 }
 
diff --git a/java/aar.go b/java/aar.go
index 7d73b03..41cc24a 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -483,9 +483,9 @@
 	}
 
 	linkFlags = append(linkFlags, "--no-static-lib-packages")
-	if a.isLibrary && a.useResourceProcessorBusyBox(ctx) {
-		// When building an android_library using ResourceProcessorBusyBox pass --merge-only to skip resource
-		// references validation until the final app link step when all static libraries are present.
+	if a.isLibrary {
+		// Pass --merge-only to skip resource references validation until the final
+		// app link step when when all static libraries are present.
 		linkFlags = append(linkFlags, "--merge-only")
 	}
 
diff --git a/java/androidmk.go b/java/androidmk.go
index 0539d25..2dff6cd 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -313,6 +313,7 @@
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetBool("LOCAL_STRIP_MODULE", false)
+					entries.AddStrings("LOCAL_REQUIRED_MODULES", binary.androidMkNamesOfJniLibs...)
 				},
 			},
 			ExtraFooters: []android.AndroidMkExtraFootersFunc{
diff --git a/java/app.go b/java/app.go
index 381808a..dd99675 100644
--- a/java/app.go
+++ b/java/app.go
@@ -63,6 +63,16 @@
 	ctx.RegisterModuleType("override_android_test", OverrideAndroidTestModuleFactory)
 }
 
+type AppInfo struct {
+	// Updatable is set to the value of the updatable property
+	Updatable bool
+
+	// TestHelperApp is true if the module is a android_test_helper_app
+	TestHelperApp bool
+}
+
+var AppInfoProvider = blueprint.NewProvider[*AppInfo]()
+
 // AndroidManifest.xml merging
 // package splits
 
@@ -162,7 +172,7 @@
 	RotationMinSdkVersion *string
 
 	// the package name of this app. The package name in the manifest file is used if one was not given.
-	Package_name *string
+	Package_name proptools.Configurable[string]
 
 	// the logging parent of this app.
 	Logging_parent *string
@@ -376,7 +386,8 @@
 	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	applicationId := a.appTestHelperAppProperties.Manifest_values.ApplicationId
 	if applicationId != nil {
-		if a.overridableAppProperties.Package_name != nil {
+		packageName := a.overridableAppProperties.Package_name.Get(ctx)
+		if packageName.IsPresent() {
 			ctx.PropertyErrorf("manifest_values.applicationId", "property is not supported when property package_name is set.")
 		}
 		a.aapt.manifestValues.applicationId = *applicationId
@@ -385,7 +396,10 @@
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
 		TestOnly: true,
 	})
-
+	android.SetProvider(ctx, AppInfoProvider, &AppInfo{
+		Updatable:     Bool(a.appProperties.Updatable),
+		TestHelperApp: true,
+	})
 }
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -393,6 +407,10 @@
 	a.checkEmbedJnis(ctx)
 	a.generateAndroidBuildActions(ctx)
 	a.generateJavaUsedByApex(ctx)
+	android.SetProvider(ctx, AppInfoProvider, &AppInfo{
+		Updatable:     Bool(a.appProperties.Updatable),
+		TestHelperApp: false,
+	})
 }
 
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
@@ -569,10 +587,11 @@
 	}
 
 	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
-	if overridden || a.overridableAppProperties.Package_name != nil {
+	packageNameProp := a.overridableAppProperties.Package_name.Get(ctx)
+	if overridden || packageNameProp.IsPresent() {
 		// The product override variable has a priority over the package_name property.
 		if !overridden {
-			manifestPackageName = *a.overridableAppProperties.Package_name
+			manifestPackageName = packageNameProp.Get()
 		}
 		aaptLinkFlags = append(aaptLinkFlags, generateAaptRenamePackageFlags(manifestPackageName, a.renameResourcesPackage())...)
 		a.overriddenManifestPackageName = manifestPackageName
@@ -812,11 +831,12 @@
 		return android.PathForModuleSrc(ctx, *a.appProperties.Privapp_allowlist)
 	}
 
-	if a.overridableAppProperties.Package_name == nil {
+	packageNameProp := a.overridableAppProperties.Package_name.Get(ctx)
+	if packageNameProp.IsEmpty() {
 		ctx.PropertyErrorf("privapp_allowlist", "package_name must be set to use privapp_allowlist")
 	}
 
-	packageName := *a.overridableAppProperties.Package_name
+	packageName := packageNameProp.Get()
 	fileName := "privapp_allowlist_" + packageName + ".xml"
 	outPath := android.PathForModuleOut(ctx, fileName).OutputPath
 	ctx.Build(pctx, android.BuildParams{
@@ -1206,10 +1226,6 @@
 	return Bool(a.appProperties.Updatable)
 }
 
-func (a *AndroidApp) SetUpdatable(val bool) {
-	a.appProperties.Updatable = &val
-}
-
 func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string {
 	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName())
 	if overridden {
@@ -1405,7 +1421,8 @@
 	}
 	applicationId := a.appTestProperties.Manifest_values.ApplicationId
 	if applicationId != nil {
-		if a.overridableAppProperties.Package_name != nil {
+		packageNameProp := a.overridableAppProperties.Package_name.Get(ctx)
+		if packageNameProp.IsPresent() {
 			ctx.PropertyErrorf("manifest_values.applicationId", "property is not supported when property package_name is set.")
 		}
 		a.aapt.manifestValues.applicationId = *applicationId
@@ -1456,10 +1473,11 @@
 		command.FlagWithArg("--test-file-name ", a.installApkName+".apk")
 	}
 
-	if a.overridableAppProperties.Package_name != nil {
+	packageNameProp := a.overridableAppProperties.Package_name.Get(ctx)
+	if packageNameProp.IsPresent() {
 		fixNeeded = true
 		command.FlagWithInput("--manifest ", a.manifestPath).
-			FlagWithArg("--package-name ", *a.overridableAppProperties.Package_name)
+			FlagWithArg("--package-name ", packageNameProp.Get())
 	}
 
 	if a.appTestProperties.Mainline_package_name != nil {
diff --git a/java/base.go b/java/base.go
index 7c26cc3..7a95735 100644
--- a/java/base.go
+++ b/java/base.go
@@ -83,9 +83,6 @@
 	// list of java libraries that will be compiled into the resulting jar
 	Static_libs proptools.Configurable[[]string] `android:"arch_variant"`
 
-	// list of java libraries that should not be used to build this module
-	Exclude_static_libs []string `android:"arch_variant"`
-
 	// manifest file to be included in resulting jar
 	Manifest *string `android:"path"`
 
@@ -221,11 +218,15 @@
 	// the stubs via static libs.
 	Is_stubs_module *bool
 
-	// If true, enable the "Ravenizer" tool on the output jar.
-	// "Ravenizer" is a tool for Ravenwood tests, but it can also be enabled on other kinds
-	// of java targets.
 	Ravenizer struct {
+		// If true, enable the "Ravenizer" tool on the output jar.
+		// "Ravenizer" is a tool for Ravenwood tests, but it can also be enabled on other kinds
+		// of java targets.
 		Enabled *bool
+
+		// If true, the "Ravenizer" tool will remove all Mockito and DexMaker
+		// classes from the output jar.
+		Strip_mockito *bool
 	}
 
 	// Contributing api surface of the stub module. Is not visible to bp modules, and should
@@ -827,7 +828,7 @@
 }
 
 func (j *Module) staticLibs(ctx android.BaseModuleContext) []string {
-	return android.RemoveListFromList(j.properties.Static_libs.GetOrDefault(ctx, nil), j.properties.Exclude_static_libs)
+	return j.properties.Static_libs.GetOrDefault(ctx, nil)
 }
 
 func (j *Module) deps(ctx android.BottomUpMutatorContext) {
@@ -1137,8 +1138,9 @@
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.deviceProperties.Aidl.Export_include_dirs)
 
-	if re := proptools.Bool(j.properties.Ravenizer.Enabled); re {
-		j.ravenizer.enabled = re
+	// Only override the original value if explicitly set
+	if j.properties.Ravenizer.Enabled != nil {
+		j.ravenizer.enabled = *j.properties.Ravenizer.Enabled
 	}
 
 	deps := j.collectDeps(ctx)
@@ -1626,16 +1628,23 @@
 
 	if j.ravenizer.enabled {
 		ravenizerInput := outputFile
-		ravenizerOutput := android.PathForModuleOut(ctx, "ravenizer", jarName)
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        ravenizer,
-			Description: "ravenizer",
-			Input:       ravenizerInput,
-			Output:      ravenizerOutput,
-		})
+		ravenizerOutput := android.PathForModuleOut(ctx, "ravenizer", "", jarName)
+		ravenizerArgs := ""
+		if proptools.Bool(j.properties.Ravenizer.Strip_mockito) {
+			ravenizerArgs = "--strip-mockito"
+		}
+		TransformRavenizer(ctx, ravenizerOutput, ravenizerInput, ravenizerArgs)
 		outputFile = ravenizerOutput
 		localImplementationJars = android.Paths{ravenizerOutput}
 		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+		if combinedResourceJar != nil {
+			ravenizerInput = combinedResourceJar
+			ravenizerOutput = android.PathForModuleOut(ctx, "ravenizer", "resources", jarName)
+			TransformRavenizer(ctx, ravenizerOutput, ravenizerInput, ravenizerArgs)
+			combinedResourceJar = ravenizerOutput
+			localResourceJars = android.Paths{ravenizerOutput}
+			completeStaticLibsResourceJars = android.NewDepSet(android.PREORDER, localResourceJars, nil)
+		}
 	}
 
 	if j.shouldApiMapper() {
diff --git a/java/builder.go b/java/builder.go
index 81b0feb..e5d5109 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -260,10 +260,10 @@
 
 	ravenizer = pctx.AndroidStaticRule("ravenizer",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && ${ravenizer} --in-jar $in --out-jar $out",
+			Command:     "rm -f $out && ${ravenizer} --in-jar $in --out-jar $out $ravenizerArgs",
 			CommandDeps: []string{"${ravenizer}"},
 		},
-	)
+		"ravenizerArgs")
 
 	apimapper = pctx.AndroidStaticRule("apimapper",
 		blueprint.RuleParams{
@@ -703,6 +703,7 @@
 	// Remove any module-info.class files that may have come from prebuilt jars, they cause problems
 	// for downstream tools like desugar.
 	jarArgs = append(jarArgs, "-stripFile module-info.class")
+	jarArgs = append(jarArgs, "-stripFile META-INF/versions/*/module-info.class")
 
 	if stripDirEntries {
 		jarArgs = append(jarArgs, "-D")
@@ -782,12 +783,15 @@
 }
 
 func TransformRavenizer(ctx android.ModuleContext, outputFile android.WritablePath,
-	inputFile android.Path) {
+	inputFile android.Path, ravenizerArgs string) {
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        ravenizer,
 		Description: "ravenizer",
 		Output:      outputFile,
 		Input:       inputFile,
+		Args: map[string]string{
+			"ravenizerArgs": ravenizerArgs,
+		},
 	})
 }
 
diff --git a/java/dex.go b/java/dex.go
index e16b052..a3f699b 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -133,7 +133,7 @@
 			`$d8Template${config.D8Cmd} ${config.D8Flags} $d8Flags --output $outDir --no-dex-input-jar $in && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
 			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in && ` +
-			`rm -f "$outDir/classes*.dex" "$outDir/classes.dex.jar"`,
+			`rm -f "$outDir"/classes*.dex "$outDir/classes.dex.jar"`,
 		CommandDeps: []string{
 			"${config.D8Cmd}",
 			"${config.SoongZipCmd}",
@@ -172,7 +172,7 @@
 			`rm -rf ${outUsageDir} && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
 			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in && ` +
-			`rm -f "$outDir/classes*.dex" "$outDir/classes.dex.jar"`,
+			`rm -f "$outDir"/classes*.dex "$outDir/classes.dex.jar"`,
 		Depfile: "${out}.d",
 		Deps:    blueprint.DepsGCC,
 		CommandDeps: []string{
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 63a8634..637da36 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -494,6 +494,12 @@
 		PresignedPrebuilt: d.isPresignedPrebuilt,
 	}
 
+	if ctx.Config().InstallApexSystemServerDexpreoptSamePartition() {
+		dexpreoptConfig.ApexPartition = android.PathForModuleInstall(ctx).Partition()
+	} else {
+		dexpreoptConfig.ApexPartition = "system"
+	}
+
 	d.configPath = android.PathForModuleOut(ctx, "dexpreopt", dexJarStem, "dexpreopt.config")
 	dexpreopt.WriteModuleConfig(ctx, dexpreoptConfig, d.configPath)
 	ctx.CheckbuildFile(d.configPath)
diff --git a/java/dexpreopt_check.go b/java/dexpreopt_check.go
index 33be603..c971565 100644
--- a/java/dexpreopt_check.go
+++ b/java/dexpreopt_check.go
@@ -17,6 +17,8 @@
 import (
 	"strings"
 
+	"github.com/google/blueprint"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 
@@ -43,16 +45,12 @@
 type dexpreoptSystemserverCheck struct {
 	android.SingletonModuleBase
 
-	// Mapping from the module name to the install paths to the compilation artifacts.
-	artifactsByModuleName map[string][]string
-
 	// The install paths to the compilation artifacts.
 	artifacts []string
 }
 
 func dexpreoptSystemserverCheckFactory() android.SingletonModule {
 	m := &dexpreoptSystemserverCheck{}
-	m.artifactsByModuleName = make(map[string][]string)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
@@ -62,7 +60,25 @@
 		ctx, "", strings.TrimPrefix(location, "/"))
 }
 
-func (m *dexpreoptSystemserverCheck) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+type systemServerDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+// systemServerJarDepTag willl be used for validation. Skip visiblility.
+func (b systemServerDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+var (
+	// dep tag for platform and apex system server jars
+	systemServerJarDepTag = systemServerDependencyTag{}
+)
+
+var _ android.ExcludeFromVisibilityEnforcementTag = systemServerJarDepTag
+
+// Add a depenendency on the system server jars. The dexpreopt files of those will be emitted to make.
+// The kati packaging system will verify that those files appear in installed files.
+// Adding the dependency allows the singleton module to determine whether an apex system server jar is system_ext specific.
+func (m *dexpreoptSystemserverCheck) DepsMutator(ctx android.BottomUpMutatorContext) {
 	global := dexpreopt.GetGlobalConfig(ctx)
 	targets := ctx.Config().Targets[android.Android]
 
@@ -72,23 +88,27 @@
 		return
 	}
 
-	systemServerJars := global.AllSystemServerJars(ctx)
-	for _, jar := range systemServerJars.CopyOfJars() {
-		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, jar)
-		odexLocation := dexpreopt.ToOdexPath(dexLocation, targets[0].Arch.ArchType)
+	ctx.AddDependency(ctx.Module(), systemServerJarDepTag, global.AllSystemServerJars(ctx).CopyOfJars()...)
+}
+
+func (m *dexpreoptSystemserverCheck) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	targets := ctx.Config().Targets[android.Android]
+
+	ctx.VisitDirectDepsWithTag(systemServerJarDepTag, func(systemServerJar android.Module) {
+		partition := "system"
+		if systemServerJar.InstallInSystemExt() && ctx.Config().InstallApexSystemServerDexpreoptSamePartition() {
+			partition = ctx.DeviceConfig().SystemExtPath() // system_ext
+		}
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, systemServerJar.Name())
+		odexLocation := dexpreopt.ToOdexPath(dexLocation, targets[0].Arch.ArchType, partition)
 		odexPath := getInstallPath(ctx, odexLocation)
 		vdexPath := getInstallPath(ctx, pathtools.ReplaceExtension(odexLocation, "vdex"))
-		m.artifactsByModuleName[jar] = []string{odexPath.String(), vdexPath.String()}
-	}
+		m.artifacts = append(m.artifacts, odexPath.String(), vdexPath.String())
+	})
 }
 
 func (m *dexpreoptSystemserverCheck) GenerateSingletonBuildActions(ctx android.SingletonContext) {
-	// Only keep modules defined in Soong.
-	ctx.VisitAllModules(func(module android.Module) {
-		if artifacts, ok := m.artifactsByModuleName[module.Name()]; ok {
-			m.artifacts = append(m.artifacts, artifacts...)
-		}
-	})
 }
 
 func (m *dexpreoptSystemserverCheck) MakeVars(ctx android.MakeVarsContext) {
diff --git a/java/java.go b/java/java.go
index 661422b..018850f 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1795,8 +1795,7 @@
 	// Name of the class containing main to be inserted into the manifest as Main-Class.
 	Main_class *string
 
-	// Names of modules containing JNI libraries that should be installed alongside the host
-	// variant of the binary.
+	// Names of modules containing JNI libraries that should be installed alongside the binary.
 	Jni_libs []string `android:"arch_variant"`
 }
 
@@ -1809,6 +1808,8 @@
 
 	wrapperFile android.Path
 	binaryFile  android.InstallPath
+
+	androidMkNamesOfJniLibs []string
 }
 
 func (j *Binary) HostToolPath() android.OptionalPath {
@@ -1880,6 +1881,21 @@
 			ctx.ModuleName()+ext, j.wrapperFile)
 
 		setOutputFiles(ctx, j.Library.Module)
+
+		// Set the jniLibs of this binary.
+		// These will be added to `LOCAL_REQUIRED_MODULES`, and the kati packaging system will
+		// install these alongside the java binary.
+		ctx.VisitDirectDepsWithTag(jniInstallTag, func(jni android.Module) {
+			// Use the BaseModuleName of the dependency (without any prebuilt_ prefix)
+			bmn, _ := jni.(interface{ BaseModuleName() string })
+			j.androidMkNamesOfJniLibs = append(j.androidMkNamesOfJniLibs, bmn.BaseModuleName()+":"+jni.Target().Arch.ArchType.Bitness())
+		})
+		// Check that native libraries are not listed in `required`. Prompt users to use `jni_libs` instead.
+		ctx.VisitDirectDepsWithTag(android.RequiredDepTag, func(dep android.Module) {
+			if _, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider); hasSharedLibraryInfo {
+				ctx.ModuleErrorf("cc_library %s is no longer supported in `required` of java_binary modules. Please use jni_libs instead.", dep.Name())
+			}
+		})
 	}
 }
 
@@ -1888,11 +1904,9 @@
 		j.deps(ctx)
 	}
 	// These dependencies ensure the installation rules will install the jar file when the
-	// wrapper is installed, and the jni libraries on host when the wrapper is installed.
-	if ctx.Arch().ArchType != android.Common && ctx.Os().Class == android.Host {
-		ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
-	}
+	// wrapper is installed, and the jni libraries when the wrapper is installed.
 	if ctx.Arch().ArchType != android.Common {
+		ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
 		ctx.AddVariationDependencies(
 			[]blueprint.Variation{{Mutator: "arch", Variation: android.CommonArch.String()}},
 			binaryInstallTag, ctx.ModuleName())
diff --git a/java/java_test.go b/java/java_test.go
index e976b08..24dabdb1 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2454,37 +2454,6 @@
 	}
 }
 
-func TestJavaExcludeStaticLib(t *testing.T) {
-	ctx, _ := testJava(t, `
-	java_library {
-		name: "bar",
-	}
-	java_library {
-		name: "foo",
-	}
-	java_library {
-		name: "baz",
-		static_libs: [
-			"foo",
-			"bar",
-		],
-		exclude_static_libs: [
-			"bar",
-		],
-	}
-	`)
-
-	// "bar" not included as dependency of "baz"
-	CheckModuleDependencies(t, ctx, "baz", "android_common", []string{
-		`core-lambda-stubs`,
-		`ext`,
-		`foo`,
-		`framework`,
-		`stable-core-platform-api-stubs-system-modules`,
-		`stable.core.platform.api.stubs`,
-	})
-}
-
 func TestJavaLibraryWithResourcesStem(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
     java_library {
@@ -3133,7 +3102,7 @@
 	}
 }
 
-// Test that a dependency edge is created to the "first" variant of a native library listed in `required` of java_binary
+// Test that a dependency edge is created to the matching variant of a native library listed in `jni_libs` of java_binary
 func TestNativeRequiredDepOfJavaBinary(t *testing.T) {
 	findDepsOfModule := func(ctx *android.TestContext, module android.Module, depName string) []blueprint.Module {
 		var ret []blueprint.Module
@@ -3149,7 +3118,7 @@
 java_binary {
 	name: "myjavabin",
 	main_class: "com.android.MyJava",
-	required: ["mynativelib"],
+	jni_libs: ["mynativelib"],
 }
 cc_library_shared {
 	name: "mynativelib",
diff --git a/java/sdk_library.go b/java/sdk_library.go
index f308772..dfbde0e 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -2481,19 +2481,3 @@
 		propertySet.AddProperty("doctag_files", dests)
 	}
 }
-
-// TODO(b/358613520): This can be removed when modules are no longer allowed to depend on the top-level library.
-func (s *SdkLibrary) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	s.Library.IDEInfo(ctx, dpInfo)
-	if s.implLibraryModule != nil {
-		dpInfo.Deps = append(dpInfo.Deps, s.implLibraryModule.Name())
-	} else {
-		// This java_sdk_library does not have an implementation (it sets `api_only` to true).
-		// Examples of this are `art.module.intra.core.api` (IntraCore api surface).
-		// Return the "public" stubs for these.
-		stubPaths := s.findClosestScopePath(apiScopePublic)
-		if len(stubPaths.stubsHeaderPath) > 0 {
-			dpInfo.Jars = append(dpInfo.Jars, stubPaths.stubsHeaderPath[0].String())
-		}
-	}
-}
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 924abd4..aad1060 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -127,6 +127,26 @@
 	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
 	classpathJars = append(classpathJars, standaloneClasspathJars...)
 	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
+	s.setPartitionInfoOfLibraries(ctx)
+}
+
+// Map of java library name to their install partition.
+type LibraryNameToPartitionInfo struct {
+	LibraryNameToPartition map[string]string
+}
+
+// LibraryNameToPartitionInfoProvider will be used by the top-level apex to enforce that dexpreopt files
+// of apex system server jars are installed in the same partition as the top-level apex.
+var LibraryNameToPartitionInfoProvider = blueprint.NewProvider[LibraryNameToPartitionInfo]()
+
+func (s *SystemServerClasspathModule) setPartitionInfoOfLibraries(ctx android.ModuleContext) {
+	libraryNameToPartition := map[string]string{}
+	ctx.VisitDirectDepsWithTag(systemServerClasspathFragmentContentDepTag, func(m android.Module) {
+		libraryNameToPartition[m.Name()] = m.PartitionTag(ctx.DeviceConfig())
+	})
+	android.SetProvider(ctx, LibraryNameToPartitionInfoProvider, LibraryNameToPartitionInfo{
+		LibraryNameToPartition: libraryNameToPartition,
+	})
 }
 
 func (s *SystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
diff --git a/rust/compiler.go b/rust/compiler.go
index a2546a1..fd86917 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -39,13 +39,13 @@
 	initialize(ctx ModuleContext)
 	compilerFlags(ctx ModuleContext, flags Flags) Flags
 	cfgFlags(ctx ModuleContext, flags Flags) Flags
-	featureFlags(ctx ModuleContext, flags Flags) Flags
+	featureFlags(ctx ModuleContext, module *Module, flags Flags) Flags
 	compilerProps() []interface{}
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput
 	compilerDeps(ctx DepsContext, deps Deps) Deps
 	crateName() string
 	edition() string
-	features() []string
+	features(ctx android.ConfigurableEvaluatorContext, module *Module) []string
 	rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath
 	Thinlto() bool
 
@@ -154,7 +154,7 @@
 
 	// list of rust automatic crate dependencies.
 	// Rustlibs linkage is rlib for host targets and dylib for device targets.
-	Rustlibs []string `android:"arch_variant"`
+	Rustlibs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of rust proc_macro crate dependencies
 	Proc_macros []string `android:"arch_variant"`
@@ -194,7 +194,7 @@
 	Crate_name string `android:"arch_variant"`
 
 	// list of features to enable for this crate
-	Features []string `android:"arch_variant"`
+	Features proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of configuration options to enable for this crate. To enable features, use the "features" property.
 	Cfgs proptools.Configurable[[]string] `android:"arch_variant"`
@@ -346,22 +346,23 @@
 	return flags
 }
 
-func (compiler *baseCompiler) features() []string {
-	return compiler.Properties.Features
+func (compiler *baseCompiler) features(ctx android.ConfigurableEvaluatorContext, module *Module) []string {
+	eval := module.ConfigurableEvaluator(ctx)
+	return compiler.Properties.Features.GetOrDefault(eval, nil)
 }
 
-func (compiler *baseCompiler) featuresToFlags() []string {
+func (compiler *baseCompiler) featuresToFlags(ctx android.ConfigurableEvaluatorContext, module *Module) []string {
 	flags := []string{}
-	for _, feature := range compiler.features() {
+	for _, feature := range compiler.features(ctx, module) {
 		flags = append(flags, "--cfg 'feature=\""+feature+"\"'")
 	}
 
 	return flags
 }
 
-func (compiler *baseCompiler) featureFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
+func (compiler *baseCompiler) featureFlags(ctx ModuleContext, module *Module, flags Flags) Flags {
+	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(ctx, module)...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags(ctx, module)...)
 
 	return flags
 }
@@ -496,7 +497,7 @@
 
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
-	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs...)
+	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs.GetOrDefault(ctx, nil)...)
 	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...)
 	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
 	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs...)
diff --git a/rust/project_json.go b/rust/project_json.go
index 24dcc89..6c1e320 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -151,7 +151,7 @@
 		crate.Env["OUT_DIR"] = rModule.compiler.cargoOutDir().String()
 	}
 
-	for _, feature := range rModule.compiler.features() {
+	for _, feature := range rModule.compiler.features(ctx, rModule) {
 		crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"")
 	}
 
diff --git a/rust/rust.go b/rust/rust.go
index 50f822b..a044a99 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -920,7 +920,7 @@
 	if mod.compiler != nil {
 		flags = mod.compiler.compilerFlags(ctx, flags)
 		flags = mod.compiler.cfgFlags(ctx, flags)
-		flags = mod.compiler.featureFlags(ctx, flags)
+		flags = mod.compiler.featureFlags(ctx, mod, flags)
 	}
 	if mod.coverage != nil {
 		flags, deps = mod.coverage.flags(ctx, flags, deps)
@@ -1756,6 +1756,16 @@
 
 var _ android.ApexModule = (*Module)(nil)
 
+// If a module is marked for exclusion from apexes, don't provide apex variants.
+// TODO(b/362509506): remove this once stubs are properly supported by rust_ffi targets.
+func (m *Module) CanHaveApexVariants() bool {
+	if m.ApexExclude() {
+		return false
+	} else {
+		return m.ApexModuleBase.CanHaveApexVariants()
+	}
+}
+
 func (mod *Module) MinSdkVersion() string {
 	return String(mod.Properties.Min_sdk_version)
 }
diff --git a/soong_ui.bash b/soong_ui.bash
index 2f688ef..be78b68 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -14,18 +14,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../make/shell_utils.sh
+require_top
+
 # To track how long we took to startup.
 case $(uname -s) in
   Darwin)
-    export TRACE_BEGIN_SOONG=`$T/prebuilts/build-tools/path/darwin-x86/date +%s%3N`
+    export TRACE_BEGIN_SOONG=`$TOP/prebuilts/build-tools/path/darwin-x86/date +%s%3N`
     ;;
   *)
     export TRACE_BEGIN_SOONG=$(date +%s%N)
     ;;
 esac
 
-source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../make/shell_utils.sh
-require_top
 setup_cog_env_if_needed
 
 # Save the current PWD for use in soong_ui
diff --git a/ui/build/config.go b/ui/build/config.go
index bd20442..75edfcd 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -1754,12 +1754,10 @@
 }
 
 func (c *configImpl) PrebuiltBuildTool(name string) string {
-	if v, ok := c.environ.Get("SANITIZE_HOST"); ok {
-		if sanitize := strings.Fields(v); inList("address", sanitize) {
-			asan := filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "asan/bin", name)
-			if _, err := os.Stat(asan); err == nil {
-				return asan
-			}
+	if c.environ.IsEnvTrue("SANITIZE_BUILD_TOOL_PREBUILTS") {
+		asan := filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "asan/bin", name)
+		if _, err := os.Stat(asan); err == nil {
+			return asan
 		}
 	}
 	return filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "bin", name)