Merge "Set the test suite for Robolectric tests"
diff --git a/README.md b/README.md
index 18cf7b2..7f18463 100644
--- a/README.md
+++ b/README.md
@@ -609,15 +609,15 @@
   Content Root, then add the `build/blueprint` directory.
 * Optional: also add the `external/golang-protobuf` directory. In practice,
   IntelliJ seems to work well enough without this, too.
+
 ### Running Soong in a debugger
 
-To make `soong_build` wait for a debugger connection, install `dlv` and then
-start the build with `SOONG_DELVE=<listen addr>` in the environment.
-For example:
-```bash
-SOONG_DELVE=5006 m nothing
-```
+Both the Android build driver (`soong_ui`) and Soong proper (`soong_build`) are
+Go applications and can be debugged with the help of the standard Go debugger
+called Delve. A client (e.g., IntelliJ IDEA) communicates with Delve via IP port
+that Delve listens to (the port number is passed to it on invocation).
 
+#### Debugging Android Build Driver ####
 To make `soong_ui` wait for a debugger connection, use the `SOONG_UI_DELVE`
 variable:
 
@@ -625,11 +625,28 @@
 SOONG_UI_DELVE=5006 m nothing
 ```
 
+#### Debugging Soong Proper ####
 
-setting or unsetting `SOONG_DELVE` causes a recompilation of `soong_build`. This
+To make `soong_build` wait for a debugger connection, install `dlv` and then
+start the build with `SOONG_DELVE=<listen addr>` in the environment.
+For example:
+```bash
+SOONG_DELVE=5006 m nothing
+```
+Android build driver invokes `soong_build` multiple times, and by default each
+invocation is run in the debugger. Setting `SOONG_DELVE_STEPS` controls which
+invocations are run in the debugger, e.g., running
+```bash
+SOONG_DELVE=2345 SOONG_DELVE_STEPS='build,modulegraph' m
+```
+results in only `build` (main build step) and `modulegraph` being run in the debugger.
+The allowed step names are `api_bp2build`, `bp2build_files`, `bp2build_workspace`,
+`build`, `modulegraph`, `queryview`, `soong_docs`.
+
+Note setting or unsetting `SOONG_DELVE` causes a recompilation of `soong_build`. This
 is because in order to debug the binary, it needs to be built with debug
 symbols.
-
+#### Delve Troubleshooting ####
 To test the debugger connection, run this command:
 
 ```
@@ -648,15 +665,23 @@
 sudo sysctl -w kernel.yama.ptrace_scope=0
 ```
 
+#### IntelliJ Setup ####
 To connect to the process using IntelliJ:
 
 * Run -> Edit Configurations...
 * Choose "Go Remote" on the left
 * Click on the "+" buttion on the top-left
-* Give it a nice name and set "Host" to localhost and "Port" to the port in the
-  environment variable
+* Give it a nice _name_ and set "Host" to `localhost` and "Port" to the port in the
+  environment variable (`SOONG_UI_DELVE` for `soong_ui`, `SOONG_DELVE` for
+  `soong_build`)
+* Set the breakpoints where you want application to stop
+* Run the build from the command line
+* In IntelliJ, click Run -> Debug _name_
+* Observe _Connecting..._ message in the debugger pane. It changes to
+  _Connected_ once the communication with the debugger has been established; the
+  terminal window where the build started will display
+  `API server listening at ...` message
 
-Debugging works far worse than debugging Java, but is sometimes useful.
 
 Sometimes the `dlv` process hangs on connection. A symptom of this is `dlv`
 spinning a core or two. In that case, `kill -9` `dlv` and try again.
diff --git a/android/androidmk.go b/android/androidmk.go
index 18e3e7a..846d506 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -504,6 +504,7 @@
 	Config() Config
 	ModuleProvider(module blueprint.Module, provider blueprint.ProviderKey) interface{}
 	ModuleHasProvider(module blueprint.Module, provider blueprint.ProviderKey) bool
+	ModuleType(module blueprint.Module) string
 }
 
 func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
@@ -527,7 +528,7 @@
 		fmt.Fprintf(&a.header, distString)
 	}
 
-	fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)")
+	fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)  # "+ctx.ModuleType(mod))
 
 	// Collect make variable assignment entries.
 	a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index cf74b9c..122495f 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -387,6 +387,10 @@
 		for _, enabledProdModule := range allowlists.ProdMixedBuildsEnabledList {
 			enabledModules[enabledProdModule] = true
 		}
+
+		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
+			enabledModules[enabledAdHocModule] = true
+		}
 	case BazelStagingMode:
 		modulesDefaultToBazel = false
 		// Staging mode includes all prod modules plus all staging modules.
@@ -396,6 +400,10 @@
 		for _, enabledStagingMode := range allowlists.StagingMixedBuildsEnabledList {
 			enabledModules[enabledStagingMode] = true
 		}
+
+		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
+			enabledModules[enabledAdHocModule] = true
+		}
 	case BazelDevMode:
 		modulesDefaultToBazel = true
 
diff --git a/android/config.go b/android/config.go
index f430b72..9d9ab30 100644
--- a/android/config.go
+++ b/android/config.go
@@ -227,6 +227,11 @@
 	mixedBuildsLock           sync.Mutex
 	mixedBuildEnabledModules  map[string]struct{}
 	mixedBuildDisabledModules map[string]struct{}
+
+	// These are modules to be built with Bazel beyond the allowlisted/build-mode
+	// specified modules. They are passed via the command-line flag
+	// "--bazel-force-enabled-modules"
+	bazelForceEnabledModules map[string]struct{}
 }
 
 type deviceConfig struct {
@@ -399,7 +404,8 @@
 
 // NewConfig creates a new Config object. The srcDir argument specifies the path
 // to the root source directory. It also loads the config file, if found.
-func NewConfig(moduleListFile string, buildMode SoongBuildMode, runGoTests bool, outDir, soongOutDir string, availableEnv map[string]string) (Config, error) {
+func NewConfig(moduleListFile string, buildMode SoongBuildMode, runGoTests bool, outDir, soongOutDir string, availableEnv map[string]string,
+	bazelForceEnabledModules []string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
 		ProductVariablesFileName: filepath.Join(soongOutDir, productVariablesFileName),
@@ -415,6 +421,7 @@
 		fs:                        pathtools.NewOsFs(absSrcDir),
 		mixedBuildDisabledModules: make(map[string]struct{}),
 		mixedBuildEnabledModules:  make(map[string]struct{}),
+		bazelForceEnabledModules:  make(map[string]struct{}),
 	}
 
 	config.deviceConfig = &deviceConfig{
@@ -500,6 +507,10 @@
 	config.BazelContext, err = NewBazelContext(config)
 	config.Bp2buildPackageConfig = GetBp2BuildAllowList()
 
+	for _, module := range bazelForceEnabledModules {
+		config.bazelForceEnabledModules[module] = struct{}{}
+	}
+
 	return Config{config}, err
 }
 
@@ -1100,6 +1111,10 @@
 	return append([]string(nil), c.productVariables.NamespacesToExport...)
 }
 
+func (c *config) IncludeTags() []string {
+	return c.productVariables.IncludeTags
+}
+
 func (c *config) HostStaticBinaries() bool {
 	return Bool(c.productVariables.HostStaticBinaries)
 }
@@ -1158,6 +1173,10 @@
 	return String(c.productVariables.PrebuiltHiddenApiDir)
 }
 
+func (c *config) BazelModulesForceEnabledByFlag() map[string]struct{} {
+	return c.bazelForceEnabledModules
+}
+
 func (c *deviceConfig) Arches() []Arch {
 	var arches []Arch
 	for _, target := range c.config.Targets[Android] {
diff --git a/android/fixture.go b/android/fixture.go
index 3f01f5a..c2b16f6 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -213,6 +213,46 @@
 	})
 }
 
+// FixtureTestRunner determines the type of test to run.
+//
+// If no custom FixtureTestRunner is provided (using the FixtureSetTestRunner) then the default test
+// runner will run a standard Soong test that corresponds to what happens when Soong is run on the
+// command line.
+type FixtureTestRunner interface {
+	// FinalPreparer is a function that is run immediately before parsing the blueprint files. It is
+	// intended to perform the initialization needed by PostParseProcessor.
+	//
+	// It returns a CustomTestResult that is passed into PostParseProcessor and returned from
+	// FixturePreparer.RunTestWithCustomResult. If it needs to return some custom data then it must
+	// provide its own implementation of CustomTestResult and return an instance of that. Otherwise,
+	// it can just return the supplied *TestResult.
+	FinalPreparer(result *TestResult) CustomTestResult
+
+	// PostParseProcessor is called after successfully parsing the blueprint files and can do further
+	// work on the result of parsing the files.
+	//
+	// Successfully parsing simply means that no errors were encountered when parsing the blueprint
+	// files.
+	//
+	// This must collate any information useful for testing, e.g. errs, ninja deps and custom data in
+	// the supplied result.
+	PostParseProcessor(result CustomTestResult)
+}
+
+// FixtureSetTestRunner sets the FixtureTestRunner in the fixture.
+//
+// It is an error if more than one of these is applied to a single fixture. If none of these are
+// applied then the fixture will use the defaultTestRunner which will run the test as if it was
+// being run in `m <target>`.
+func FixtureSetTestRunner(testRunner FixtureTestRunner) FixturePreparer {
+	return newSimpleFixturePreparer(func(fixture *fixture) {
+		if fixture.testRunner != nil {
+			panic("fixture test runner has already been set")
+		}
+		fixture.testRunner = testRunner
+	})
+}
+
 // Modify the config
 func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
 	return newSimpleFixturePreparer(func(f *fixture) {
@@ -391,6 +431,21 @@
 	// Shorthand for Fixture(t).RunTest()
 	RunTest(t *testing.T) *TestResult
 
+	// RunTestWithCustomResult runs the test just as RunTest(t) does but instead of returning a
+	// *TestResult it returns the CustomTestResult that was returned by the custom
+	// FixtureTestRunner.PostParseProcessor method that ran the test, or the *TestResult if that
+	// method returned nil.
+	//
+	// This method must be used when needing to access custom data collected by the
+	// FixtureTestRunner.PostParseProcessor method.
+	//
+	// e.g. something like this
+	//
+	//   preparers := ...FixtureSetTestRunner(&myTestRunner)...
+	//   customResult := preparers.RunTestWithCustomResult(t).(*myCustomTestResult)
+	//   doSomething(customResult.data)
+	RunTestWithCustomResult(t *testing.T) CustomTestResult
+
 	// Run the test with the supplied Android.bp file.
 	//
 	// preparer.RunTestWithBp(t, bp) is shorthand for
@@ -619,7 +674,7 @@
 	MockFS() MockFS
 
 	// Run the test, checking any errors reported and returning a TestResult instance.
-	RunTest() *TestResult
+	RunTest() CustomTestResult
 }
 
 // Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
@@ -642,6 +697,39 @@
 	NinjaDeps []string
 }
 
+func (r *TestResult) testResult() *TestResult { return r }
+
+// CustomTestResult is the interface that FixtureTestRunner implementations who wish to return
+// custom data must implement. It must embed *TestResult and initialize that to the value passed
+// into the method. It is returned from the FixtureTestRunner.FinalPreparer, passed into the
+// FixtureTestRunner.PostParseProcessor and returned from FixturePreparer.RunTestWithCustomResult.
+//
+// e.g. something like this:
+//
+//		type myCustomTestResult struct {
+//		    *android.TestResult
+//		    data []string
+//		}
+//
+//		func (r *myTestRunner) FinalPreparer(result *TestResult) CustomTestResult {
+//	     ... do some final test preparation ...
+//	     return &myCustomTestResult{TestResult: result)
+//	 }
+//
+//		func (r *myTestRunner) PostParseProcessor(result CustomTestResult) {
+//		    ...
+//		    myData := []string {....}
+//		    ...
+//		    customResult := result.(*myCustomTestResult)
+//	     customResult.data = myData
+//		}
+type CustomTestResult interface {
+	// testResult returns the embedded *TestResult.
+	testResult() *TestResult
+}
+
+var _ CustomTestResult = (*TestResult)(nil)
+
 type TestPathContext struct {
 	*TestResult
 }
@@ -696,6 +784,11 @@
 
 func (b *baseFixturePreparer) RunTest(t *testing.T) *TestResult {
 	t.Helper()
+	return b.RunTestWithCustomResult(t).testResult()
+}
+
+func (b *baseFixturePreparer) RunTestWithCustomResult(t *testing.T) CustomTestResult {
+	t.Helper()
 	fixture := b.self.Fixture(t)
 	return fixture.RunTest()
 }
@@ -724,13 +817,16 @@
 		ctx.SetModuleListFile(ctx.config.mockBpList)
 	}
 
-	return fixture.RunTest()
+	return fixture.RunTest().testResult()
 }
 
 type fixture struct {
 	// The preparers used to create this fixture.
 	preparers []*simpleFixturePreparer
 
+	// The test runner used in this fixture, defaults to defaultTestRunner if not set.
+	testRunner FixtureTestRunner
+
 	// The gotest state of the go test within which this was created.
 	t *testing.T
 
@@ -762,7 +858,7 @@
 	return f.mockFS
 }
 
-func (f *fixture) RunTest() *TestResult {
+func (f *fixture) RunTest() CustomTestResult {
 	f.t.Helper()
 
 	// If in debug mode output the state of the fixture before running the test.
@@ -800,30 +896,59 @@
 	// Set the NameResolver in the TestContext.
 	ctx.NameResolver = resolver
 
-	ctx.Register()
-	var ninjaDeps []string
-	extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
-	if len(errs) == 0 {
-		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-		extraNinjaDeps, errs = ctx.PrepareBuildActions(f.config)
-		if len(errs) == 0 {
-			ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-		}
+	// If test runner has not been set then use the default runner.
+	if f.testRunner == nil {
+		f.testRunner = defaultTestRunner
 	}
 
+	// Create the result to collate result information.
 	result := &TestResult{
 		testContext: testContext{ctx},
 		fixture:     f,
 		Config:      f.config,
-		Errs:        errs,
-		NinjaDeps:   ninjaDeps,
+	}
+
+	// Do any last minute preparation before parsing the blueprint files.
+	customResult := f.testRunner.FinalPreparer(result)
+
+	// Parse the blueprint files adding the information to the result.
+	extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
+	result.NinjaDeps = append(result.NinjaDeps, extraNinjaDeps...)
+	result.Errs = append(result.Errs, errs...)
+
+	if len(result.Errs) == 0 {
+		// If parsing the blueprint files was successful then perform any additional processing.
+		f.testRunner.PostParseProcessor(customResult)
 	}
 
 	f.errorHandler.CheckErrors(f.t, result)
 
+	return customResult
+}
+
+// standardTestRunner is the implementation of the default test runner
+type standardTestRunner struct{}
+
+func (s *standardTestRunner) FinalPreparer(result *TestResult) CustomTestResult {
+	// Register the hard coded mutators and singletons used by the standard Soong build as well as
+	// any additional instances that have been registered with this fixture.
+	result.TestContext.Register()
 	return result
 }
 
+func (s *standardTestRunner) PostParseProcessor(customResult CustomTestResult) {
+	result := customResult.(*TestResult)
+	ctx := result.TestContext
+	cfg := result.Config
+	// Prepare the build actions, i.e. run all the mutators, singletons and then invoke the
+	// GenerateAndroidBuildActions methods on all the modules.
+	extraNinjaDeps, errs := ctx.PrepareBuildActions(cfg)
+	result.NinjaDeps = append(result.NinjaDeps, extraNinjaDeps...)
+	result.CollateErrs(errs)
+}
+
+var defaultTestRunner FixtureTestRunner = &standardTestRunner{}
+
 func (f *fixture) outputDebugState() {
 	fmt.Printf("Begin Fixture State for %s\n", f.t.Name())
 	if len(f.config.env) == 0 {
@@ -909,3 +1034,10 @@
 func (r *TestResult) Module(name string, variant string) Module {
 	return r.ModuleForTests(name, variant).Module()
 }
+
+// CollateErrs adds additional errors to the result and returns true if there is more than one
+// error in the result.
+func (r *TestResult) CollateErrs(errs []error) bool {
+	r.Errs = append(r.Errs, errs...)
+	return len(r.Errs) > 0
+}
diff --git a/android/mutator.go b/android/mutator.go
index 83d4e66..d92b87c 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -705,6 +705,28 @@
 	t.createBazelTargetModule(bazelProps, commonAttrs, attrs, enabledProperty)
 }
 
+// ApexAvailableTags converts the apex_available property value of an ApexModule
+// module and returns it as a list of keyed tags.
+func ApexAvailableTags(mod Module) bazel.StringListAttribute {
+	attr := bazel.StringListAttribute{}
+	tags := []string{}
+	// Transform specific attributes into tags.
+	if am, ok := mod.(ApexModule); ok {
+		// TODO(b/218841706): hidl_interface has the apex_available prop, but it's
+		// defined directly as a prop and not via ApexModule, so this doesn't
+		// pick those props up.
+		// TODO(b/260694842): This does not pick up aidl_interface.backend.ndk.apex_available.
+		for _, a := range am.apexModuleBase().ApexAvailable() {
+			tags = append(tags, "apex_available="+a)
+		}
+	}
+	if len(tags) > 0 {
+		// This avoids creating a tags attr with an empty list if there are no tags.
+		attr.Value = tags
+	}
+	return attr
+}
+
 func (t *topDownMutatorContext) createBazelTargetModule(
 	bazelProps bazel.BazelTargetModuleProperties,
 	commonAttrs CommonAttributes,
diff --git a/android/register.go b/android/register.go
index 6c69cc5..33e9ea3 100644
--- a/android/register.go
+++ b/android/register.go
@@ -161,32 +161,34 @@
 func NewContext(config Config) *Context {
 	ctx := &Context{blueprint.NewContext(), config}
 	ctx.SetSrcDir(absSrcDir)
+	ctx.AddIncludeTags(config.IncludeTags()...)
 	return ctx
 }
 
+// Helper function to register the module types used in bp2build and
+// api_bp2build.
+func registerModuleTypes(ctx *Context) {
+	for _, t := range moduleTypes {
+		t.register(ctx)
+	}
+	// Required for SingletonModule types, even though we are not using them.
+	for _, t := range singletons {
+		t.register(ctx)
+	}
+}
+
 // RegisterForBazelConversion registers an alternate shadow pipeline of
 // singletons, module types and mutators to register for converting Blueprint
 // files to semantically equivalent BUILD files.
 func (ctx *Context) RegisterForBazelConversion() {
-	for _, t := range moduleTypes {
-		t.register(ctx)
-	}
-
-	// Required for SingletonModule types, even though we are not using them.
-	for _, t := range singletons {
-		t.register(ctx)
-	}
-
+	registerModuleTypes(ctx)
 	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
 // RegisterForApiBazelConversion is similar to RegisterForBazelConversion except that
 // it only generates API targets in the generated  workspace
 func (ctx *Context) RegisterForApiBazelConversion() {
-	for _, t := range moduleTypes {
-		t.register(ctx)
-	}
-
+	registerModuleTypes(ctx)
 	RegisterMutatorsForApiBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
diff --git a/android/variable.go b/android/variable.go
index 28f22c9..9725895 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -452,6 +452,8 @@
 	GenerateAidlNdkPlatformBackend bool `json:",omitempty"`
 
 	IgnorePrefer32OnDevice bool `json:",omitempty"`
+
+	IncludeTags []string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 3373211..0fc971b 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -134,7 +134,7 @@
 			continue
 		}
 
-		fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+		fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)  # apex.apexBundle.files")
 		if fi.moduleDir != "" {
 			fmt.Fprintln(w, "LOCAL_PATH :=", fi.moduleDir)
 		} else {
@@ -348,7 +348,7 @@
 
 			if apexType == flattenedApex {
 				// Only image APEXes can be flattened.
-				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)  # apex.apexBundle.flat")
 				fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 				fmt.Fprintln(w, "LOCAL_MODULE :=", name+a.suffix)
 				data.Entries.WriteLicenseVariables(w)
@@ -356,7 +356,7 @@
 				fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
 
 			} else {
-				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)  # apex.apexBundle")
 				fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 				fmt.Fprintln(w, "LOCAL_MODULE :=", name+a.suffix)
 				data.Entries.WriteLicenseVariables(w)
diff --git a/apex/apex.go b/apex/apex.go
index b1b4e47..8e1783e 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2676,6 +2676,10 @@
 	}
 	attrs, props := convertWithBp2build(a, ctx)
 
+	// We just want the name, not module reference.
+	baseApexName := strings.TrimPrefix(baseApexModuleName, ":")
+	attrs.Base_apex_name = &baseApexName
+
 	for _, p := range o.GetProperties() {
 		overridableProperties, ok := p.(*overridableProperties)
 		if !ok {
@@ -3397,6 +3401,7 @@
 	Package_name          *string
 	Logging_parent        *string
 	Tests                 bazel.LabelListAttribute
+	Base_apex_name        *string
 }
 
 type convertedNativeSharedLibs struct {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 883c3c8..876a052 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -4130,6 +4130,41 @@
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := mylib.com.android.myapex\n")
 }
 
+func TestOverrideApexManifestDefaultVersion(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apex_name: "com.android.myapex",
+			native_shared_libs: ["mylib"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+	`, android.FixtureMergeEnv(map[string]string{
+		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
+	}))
+
+	module := ctx.ModuleForTests("myapex", "android_common_com.android.myapex_image")
+	apexManifestRule := module.Rule("apexManifestRule")
+	ensureContains(t, apexManifestRule.Args["default_version"], "1234")
+}
+
 func TestCompileMultilibProp(t *testing.T) {
 	testCases := []struct {
 		compileMultiLibProp string
diff --git a/apex/builder.go b/apex/builder.go
index 9e368b6..4be34d2 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -236,6 +236,10 @@
 	}
 
 	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
+	defaultVersion := android.DefaultUpdatableModuleVersion
+	if override := ctx.Config().Getenv("OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION"); override != "" {
+		defaultVersion = override
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexManifestRule,
 		Input:  src,
@@ -243,7 +247,7 @@
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
 			"requireNativeLibs": strings.Join(requireNativeLibs, " "),
-			"default_version":   android.DefaultUpdatableModuleVersion,
+			"default_version":   defaultVersion,
 			"opt":               strings.Join(optCommands, " "),
 		},
 	})
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index e4830d3..118a3a9 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -33,6 +33,7 @@
 	TidyFiles            []string
 	TocFile              string
 	UnstrippedOutput     string
+	AbiDiffFiles         []string
 }
 
 type getOutputFilesRequestType struct{}
@@ -174,6 +175,11 @@
 if clang_tidy_info:
   tidy_files = [v.path for v in clang_tidy_info.tidy_files.to_list()]
 
+abi_diff_files = []
+abi_diff_info = p.get("//build/bazel/rules/abi:abi_dump.bzl%AbiDiffInfo")
+if abi_diff_info:
+  abi_diff_files = [f.path for f in abi_diff_info.diff_files.to_list()]
+
 return json_encode({
 	"OutputFiles": outputFiles,
 	"CcObjectFiles": ccObjectFiles,
@@ -187,6 +193,7 @@
 	"TidyFiles": tidy_files,
 	"TocFile": toc_file,
 	"UnstrippedOutput": unstripped,
+	"AbiDiffFiles": abi_diff_files,
 })`
 
 }
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index b6061e4..714b848 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -663,6 +663,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
 				"android_manifest": `"ApogeeAndroidManifest.xml"`,
+				"base_apex_name":   `"com.android.apogee"`,
 				"binaries": `[
         ":cc_binary_1",
         ":sh_binary_2",
@@ -729,8 +730,9 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"//a/b:apex_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"//a/b:apex_manifest.json"`,
 			}),
 		}})
 }
@@ -763,8 +765,9 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"//a/b:apogee_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"//a/b:apogee_manifest.json"`,
 			}),
 		}})
 }
@@ -795,8 +798,9 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apex_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apex_manifest.json"`,
 			}),
 		}})
 }
@@ -828,8 +832,9 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apogee_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apogee_manifest.json"`,
 			}),
 		}})
 }
@@ -861,9 +866,10 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apex_manifest.json"`,
-				"package_name":  `"com.google.android.apogee"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apex_manifest.json"`,
+				"package_name":   `"com.google.android.apogee"`,
 			}),
 		}})
 }
@@ -900,9 +906,10 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apex_manifest.json"`,
-				"prebuilts":     `[":prebuilt_file"]`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apex_manifest.json"`,
+				"prebuilts":      `[":prebuilt_file"]`,
 			}),
 		}})
 }
@@ -945,9 +952,10 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apex_manifest.json"`,
-				"prebuilts":     `[":prebuilt_file2"]`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apex_manifest.json"`,
+				"prebuilts":      `[":prebuilt_file2"]`,
 			}),
 		}})
 }
@@ -985,9 +993,10 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
-				"manifest":      `"apex_manifest.json"`,
-				"prebuilts":     `[]`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+				"manifest":       `"apex_manifest.json"`,
+				"prebuilts":      `[]`,
 			}),
 		}})
 }
@@ -1019,6 +1028,7 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"base_apex_name": `"com.android.apogee"`,
 				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 				"manifest":       `"apex_manifest.json"`,
 				"logging_parent": `"foo.bar.baz"`,
@@ -1054,6 +1064,7 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"base_apex_name": `"com.android.apogee"`,
 				"file_contexts":  `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 				"manifest":       `"apex_manifest.json"`,
 				"logging_parent": `"foo.bar.baz.override"`,
@@ -1099,8 +1110,9 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `":com.android.apogee-file_contexts"`,
-				"manifest":      `"apogee_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `":com.android.apogee-file_contexts"`,
+				"manifest":       `"apogee_manifest.json"`,
 			}),
 		}})
 }
@@ -1200,9 +1212,10 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
-				"file_contexts": `":com.android.apogee-file_contexts"`,
-				"certificate":   `":com.google.android.apogee.certificate"`,
-				"manifest":      `"apogee_manifest.json"`,
+				"base_apex_name": `"com.android.apogee"`,
+				"file_contexts":  `":com.android.apogee-file_contexts"`,
+				"certificate":    `":com.google.android.apogee.certificate"`,
+				"manifest":       `"apogee_manifest.json"`,
 			}),
 		}})
 }
@@ -1244,6 +1257,7 @@
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("apex", "com.google.android.apogee", AttrNameToString{
+				"base_apex_name":   `"com.android.apogee"`,
 				"file_contexts":    `":com.android.apogee-file_contexts"`,
 				"certificate_name": `"com.google.android.apogee.certificate"`,
 				"manifest":         `"apogee_manifest.json"`,
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index edb0c43..a1e83d8 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -3645,3 +3645,49 @@
 		},
 	})
 }
+
+func TestCcLibraryApexAvailable(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library apex_available converted to tags",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "a",
+    srcs: ["a.cpp"],
+    apex_available: ["com.android.foo"],
+}
+`,
+		ExpectedBazelTargets: makeCcLibraryTargets("a", AttrNameToString{
+			"tags":           `["apex_available=com.android.foo"]`,
+			"srcs":           `["a.cpp"]`,
+			"local_includes": `["."]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryApexAvailableMultiple(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library apex_available converted to multiple tags",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "a",
+    srcs: ["a.cpp"],
+    apex_available: ["com.android.foo", "//apex_available:platform", "com.android.bar"],
+}
+`,
+		ExpectedBazelTargets: makeCcLibraryTargets("a", AttrNameToString{
+			"tags": `[
+        "apex_available=com.android.foo",
+        "apex_available=//apex_available:platform",
+        "apex_available=com.android.bar",
+    ]`,
+			"srcs":           `["a.cpp"]`,
+			"local_includes": `["."]`,
+		}),
+	},
+	)
+}
diff --git a/bp2build/cc_prebuilt_library_conversion_test.go b/bp2build/cc_prebuilt_library_conversion_test.go
index 47006ac..2fe158e 100644
--- a/bp2build/cc_prebuilt_library_conversion_test.go
+++ b/bp2build/cc_prebuilt_library_conversion_test.go
@@ -91,9 +91,9 @@
 			ModuleTypeUnderTest:        "cc_prebuilt_library",
 			ModuleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
 			Filesystem: map[string]string{
-				"libf.so":    "",
-				"testdir/1/": "",
-				"testdir/2/": "",
+				"libf.so":             "",
+				"testdir/1/include.h": "",
+				"testdir/2/other.h":   "",
 			},
 			Blueprint: `
 cc_prebuilt_library {
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 4e63d19..c059add 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -91,65 +91,66 @@
 
 func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
 	t.Helper()
-	bp2buildSetup := func(ctx *android.TestContext) {
-		registerModuleTypes(ctx)
-		ctx.RegisterForBazelConversion()
-	}
+	bp2buildSetup := android.GroupFixturePreparers(
+		android.FixtureRegisterWithContext(registerModuleTypes),
+		SetBp2BuildTestRunner,
+	)
 	runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
 }
 
 func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
 	t.Helper()
-	apiBp2BuildSetup := func(ctx *android.TestContext) {
-		registerModuleTypes(ctx)
-		ctx.RegisterForApiBazelConversion()
-	}
+	apiBp2BuildSetup := android.GroupFixturePreparers(
+		android.FixtureRegisterWithContext(registerModuleTypes),
+		SetApiBp2BuildTestRunner,
+	)
 	runBp2BuildTestCaseWithSetup(t, apiBp2BuildSetup, tc)
 }
 
-func runBp2BuildTestCaseWithSetup(t *testing.T, setup func(ctx *android.TestContext), tc Bp2buildTestCase) {
+func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) {
 	t.Helper()
 	dir := "."
 	filesystem := make(map[string][]byte)
-	toParse := []string{
-		"Android.bp",
-	}
 	for f, content := range tc.Filesystem {
-		if strings.HasSuffix(f, "Android.bp") {
-			toParse = append(toParse, f)
-		}
 		filesystem[f] = []byte(content)
 	}
-	config := android.TestConfig(buildDir, nil, tc.Blueprint, filesystem)
-	ctx := android.NewTestContext(config)
 
-	setup(ctx)
-	ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
-
-	// A default configuration for tests to not have to specify bp2build_available on top level targets.
-	bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
-		allowlists.Bp2BuildConfig{
-			android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
-		},
-	)
-	for _, f := range tc.KeepBuildFileForDirs {
-		bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
-			f: /*recursive=*/ false,
-		})
-	}
-	ctx.RegisterBp2BuildConfig(bp2buildConfig)
-
-	_, parseErrs := ctx.ParseFileList(dir, toParse)
-	if errored(t, tc, parseErrs) {
-		return
-	}
-	_, resolveDepsErrs := ctx.ResolveDependencies(config)
-	if errored(t, tc, resolveDepsErrs) {
-		return
+	preparers := []android.FixturePreparer{
+		extraPreparer,
+		android.FixtureMergeMockFs(filesystem),
+		android.FixtureWithRootAndroidBp(tc.Blueprint),
+		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+			ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
+		}),
+		android.FixtureModifyContext(func(ctx *android.TestContext) {
+			// A default configuration for tests to not have to specify bp2build_available on top level
+			// targets.
+			bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
+				allowlists.Bp2BuildConfig{
+					android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
+				},
+			)
+			for _, f := range tc.KeepBuildFileForDirs {
+				bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
+					f: /*recursive=*/ false,
+				})
+			}
+			ctx.RegisterBp2BuildConfig(bp2buildConfig)
+		}),
+		android.FixtureModifyEnv(func(env map[string]string) {
+			if tc.UnconvertedDepsMode == errorModulesUnconvertedDeps {
+				env["BP2BUILD_ERROR_UNCONVERTED"] = "true"
+			}
+		}),
 	}
 
-	parseAndResolveErrs := append(parseErrs, resolveDepsErrs...)
-	if tc.ExpectedErr != nil && checkError(t, parseAndResolveErrs, tc.ExpectedErr) {
+	preparer := android.GroupFixturePreparers(preparers...)
+	if tc.ExpectedErr != nil {
+		pattern := "\\Q" + tc.ExpectedErr.Error() + "\\E"
+		preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(pattern))
+	}
+	result := preparer.RunTestWithCustomResult(t).(*BazelTestResult)
+	if len(result.Errs) > 0 {
 		return
 	}
 
@@ -157,27 +158,115 @@
 	if tc.Dir != "" {
 		checkDir = tc.Dir
 	}
-	codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build)
-	codegenCtx.unconvertedDepMode = tc.UnconvertedDepsMode
-	bazelTargets, errs := generateBazelTargetsForDir(codegenCtx, checkDir)
-	if tc.ExpectedErr != nil {
-		if checkError(t, errs, tc.ExpectedErr) {
-			return
-		} else {
-			t.Errorf("Expected error: %q, got: %q and %q", tc.ExpectedErr, errs, parseAndResolveErrs)
-		}
-	} else {
-		android.FailIfErrored(t, errs)
+	expectedTargets := map[string][]string{
+		checkDir: tc.ExpectedBazelTargets,
 	}
-	if actualCount, expectedCount := len(bazelTargets), len(tc.ExpectedBazelTargets); actualCount != expectedCount {
+
+	result.CompareAllBazelTargets(t, tc.Description, expectedTargets, true)
+}
+
+// SetBp2BuildTestRunner customizes the test fixture mechanism to run tests in Bp2Build mode.
+var SetBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{Bp2Build})
+
+// SetApiBp2BuildTestRunner customizes the test fixture mechanism to run tests in ApiBp2build mode.
+var SetApiBp2BuildTestRunner = android.FixtureSetTestRunner(&bazelTestRunner{ApiBp2build})
+
+// bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build and
+// apiBp2build build modes.
+type bazelTestRunner struct {
+	mode CodegenMode
+}
+
+func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult {
+	ctx := result.TestContext
+	switch b.mode {
+	case Bp2Build:
+		ctx.RegisterForBazelConversion()
+	case ApiBp2build:
+		ctx.RegisterForApiBazelConversion()
+	default:
+		panic(fmt.Errorf("unknown build mode: %d", b.mode))
+	}
+
+	return &BazelTestResult{TestResult: result}
+}
+
+func (b *bazelTestRunner) PostParseProcessor(result android.CustomTestResult) {
+	bazelResult := result.(*BazelTestResult)
+	ctx := bazelResult.TestContext
+	config := bazelResult.Config
+	_, errs := ctx.ResolveDependencies(config)
+	if bazelResult.CollateErrs(errs) {
+		return
+	}
+
+	codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build)
+	res, errs := GenerateBazelTargets(codegenCtx, false)
+	if bazelResult.CollateErrs(errs) {
+		return
+	}
+
+	// Store additional data for access by tests.
+	bazelResult.conversionResults = res
+}
+
+// BazelTestResult is a wrapper around android.TestResult to provide type safe access to the bazel
+// specific data stored by the bazelTestRunner.
+type BazelTestResult struct {
+	*android.TestResult
+
+	// The result returned by the GenerateBazelTargets function.
+	conversionResults
+}
+
+// CompareAllBazelTargets compares the BazelTargets produced by the test for all the directories
+// with the supplied set of expected targets.
+//
+// If ignoreUnexpected=false then this enforces an exact match where every BazelTarget produced must
+// have a corresponding expected BazelTarget.
+//
+// If ignoreUnexpected=true then it will ignore directories for which there are no expected targets.
+func (b BazelTestResult) CompareAllBazelTargets(t *testing.T, description string, expectedTargets map[string][]string, ignoreUnexpected bool) {
+	actualTargets := b.buildFileToTargets
+
+	// Generate the sorted set of directories to check.
+	dirsToCheck := android.SortedStringKeys(expectedTargets)
+	if !ignoreUnexpected {
+		// This needs to perform an exact match so add the directories in which targets were
+		// produced to the list of directories to check.
+		dirsToCheck = append(dirsToCheck, android.SortedStringKeys(actualTargets)...)
+		dirsToCheck = android.SortedUniqueStrings(dirsToCheck)
+	}
+
+	for _, dir := range dirsToCheck {
+		expected := expectedTargets[dir]
+		actual := actualTargets[dir]
+
+		if expected == nil {
+			if actual != nil {
+				t.Errorf("did not expect any bazel modules in %q but found %d", dir, len(actual))
+			}
+		} else if actual == nil {
+			expectedCount := len(expected)
+			if expectedCount > 0 {
+				t.Errorf("expected %d bazel modules in %q but did not find any", expectedCount, dir)
+			}
+		} else {
+			b.CompareBazelTargets(t, description, expected, actual)
+		}
+	}
+}
+
+func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) {
+	if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount {
 		t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)",
-			tc.Description, expectedCount, tc.ExpectedBazelTargets, actualCount, bazelTargets)
+			description, expectedCount, expectedContents, actualCount, actualTargets)
 	} else {
-		for i, target := range bazelTargets {
-			if w, g := tc.ExpectedBazelTargets[i], target.content; w != g {
+		for i, actualTarget := range actualTargets {
+			if w, g := expectedContents[i], actualTarget.content; w != g {
 				t.Errorf(
-					"%s: Expected generated Bazel target to be `%s`, got `%s`",
-					tc.Description, w, g)
+					"%s[%d]: Expected generated Bazel target to be `%s`, got `%s`",
+					description, i, w, g)
 			}
 		}
 	}
diff --git a/bpf/bpf.go b/bpf/bpf.go
index a840fa3..d91180b 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -227,7 +227,7 @@
 			for _, obj := range bpf.objs {
 				objName := name + "_" + obj.Base()
 				names = append(names, objName)
-				fmt.Fprintln(w, "include $(CLEAR_VARS)")
+				fmt.Fprintln(w, "include $(CLEAR_VARS)", " # bpf.bpf.obj")
 				fmt.Fprintln(w, "LOCAL_MODULE := ", objName)
 				data.Entries.WriteLicenseVariables(w)
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
@@ -237,7 +237,7 @@
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 				fmt.Fprintln(w)
 			}
-			fmt.Fprintln(w, "include $(CLEAR_VARS)")
+			fmt.Fprintln(w, "include $(CLEAR_VARS)", " # bpf.bpf")
 			fmt.Fprintln(w, "LOCAL_MODULE := ", name)
 			data.Entries.WriteLicenseVariables(w)
 			fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(names, " "))
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
index 5515464..fe3392a 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -15,28 +15,46 @@
 package cc
 
 import (
+	"strings"
 	"testing"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
+type visitDirectDepsInterface interface {
+	VisitDirectDeps(blueprint.Module, func(dep blueprint.Module))
+}
+
+func hasDirectDep(ctx visitDirectDepsInterface, m android.Module, wantDep android.Module) bool {
+	var found bool
+	ctx.VisitDirectDeps(m, func(dep blueprint.Module) {
+		if dep == wantDep {
+			found = true
+		}
+	})
+	return found
+}
+
 func TestAfdoDeps(t *testing.T) {
 	bp := `
-	cc_library {
+	cc_library_shared {
 		name: "libTest",
-		srcs: ["foo.c"],
+		srcs: ["test.c"],
 		static_libs: ["libFoo"],
 		afdo: true,
 	}
 
-	cc_library {
+	cc_library_static {
 		name: "libFoo",
+		srcs: ["foo.c"],
 		static_libs: ["libBar"],
 	}
 
-	cc_library {
+	cc_library_static {
 		name: "libBar",
+		srcs: ["bar.c"],
 	}
 	`
 	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
@@ -46,25 +64,89 @@
 		prepareForAfdoTest,
 	).RunTestWithBp(t, bp)
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest").Module()
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest").Module()
+	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
 
-	hasDep := func(m android.Module, wantDep android.Module) bool {
-		var found bool
-		result.VisitDirectDeps(m, func(dep blueprint.Module) {
-			if dep == wantDep {
-				found = true
-			}
-		})
-		return found
-	}
-
-	if !hasDep(libTest, libFoo) {
+	if !hasDirectDep(result, libTest.Module(), libFoo.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libFoo")
 	}
 
-	if !hasDep(libFoo, libBar) {
+	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libBar")
 	}
+
+	cFlags := libTest.Rule("cc").Args["cFlags"]
+	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	}
+
+	cFlags = libFoo.Rule("cc").Args["cFlags"]
+	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
+		t.Errorf("Expected 'libFoo' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	}
+
+	cFlags = libBar.Rule("cc").Args["cFlags"]
+	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
+		t.Errorf("Expected 'libBar' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	}
+}
+
+func TestAfdoEnabledOnStaticDepNoAfdo(t *testing.T) {
+	bp := `
+	cc_library_shared {
+		name: "libTest",
+		srcs: ["foo.c"],
+		static_libs: ["libFoo"],
+	}
+
+	cc_library_static {
+		name: "libFoo",
+		srcs: ["foo.c"],
+		static_libs: ["libBar"],
+		afdo: true, // TODO(b/256670524): remove support for enabling afdo from static only libraries, this can only propagate from shared libraries/binaries
+	}
+
+	cc_library_static {
+		name: "libBar",
+	}
+	`
+	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libFoo.afdo", "TEST")
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForAfdoTest,
+	).RunTestWithBp(t, bp)
+
+	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
+	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static").Module()
+
+	if !hasDirectDep(result, libTest, libFoo.Module()) {
+		t.Errorf("libTest missing dependency on afdo variant of libFoo")
+	}
+
+	if !hasDirectDep(result, libFoo.Module(), libBar) {
+		t.Errorf("libFoo missing dependency on afdo variant of libBar")
+	}
+
+	fooVariants := result.ModuleVariantsForTests("foo")
+	for _, v := range fooVariants {
+		if strings.Contains(v, "afdo-") {
+			t.Errorf("Expected no afdo variant of 'foo', got %q", v)
+		}
+	}
+
+	cFlags := libFoo.Rule("cc").Args["cFlags"]
+	if w := "-fprofile-sample-accurate"; strings.Contains(cFlags, w) {
+		t.Errorf("Expected 'foo' to not enable afdo, but found %q in cflags %q", w, cFlags)
+	}
+
+	barVariants := result.ModuleVariantsForTests("bar")
+	for _, v := range barVariants {
+		if strings.Contains(v, "afdo-") {
+			t.Errorf("Expected no afdo variant of 'bar', got %q", v)
+		}
+	}
+
 }
diff --git a/cc/binary.go b/cc/binary.go
index c2868e7..998934e 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -655,11 +655,12 @@
 	// shared with cc_test
 	binaryAttrs := binaryBp2buildAttrs(ctx, m)
 
+	tags := android.ApexAvailableTags(m)
 	ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
 		Rule_class:        "cc_binary",
 		Bzl_load_location: "//build/bazel/rules/cc:cc_binary.bzl",
 	},
-		android.CommonAttributes{Name: m.Name()},
+		android.CommonAttributes{Name: m.Name(), Tags: tags},
 		&binaryAttrs)
 }
 
diff --git a/cc/library.go b/cc/library.go
index 1cad6b9..7059023 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -120,6 +120,9 @@
 
 		// Extra flags passed to header-abi-diff
 		Diff_flags []string
+
+		// Opt-in reference dump directories
+		Ref_dump_dirs []string
 	}
 
 	// Inject boringssl hash into the shared library.  This is only intended for use by external/boringssl.
@@ -448,11 +451,18 @@
 		Bzl_load_location: "//build/bazel/rules/cc:cc_library_shared.bzl",
 	}
 
+	tags := android.ApexAvailableTags(m)
 	ctx.CreateBazelTargetModuleWithRestrictions(staticProps,
-		android.CommonAttributes{Name: m.Name() + "_bp2build_cc_library_static"},
+		android.CommonAttributes{
+			Name: m.Name() + "_bp2build_cc_library_static",
+			Tags: tags,
+		},
 		staticTargetAttrs, staticAttrs.Enabled)
 	ctx.CreateBazelTargetModuleWithRestrictions(sharedProps,
-		android.CommonAttributes{Name: m.Name()},
+		android.CommonAttributes{
+			Name: m.Name(),
+			Tags: tags,
+		},
 		sharedTargetAttrs, sharedAttrs.Enabled)
 
 	createStubsBazelTargetIfNeeded(ctx, m, compilerAttrs, exportedIncludes, baseAttributes)
@@ -898,6 +908,10 @@
 	}
 	handler.module.linker.(*libraryDecorator).tocFile = tocFile
 
+	if len(ccInfo.AbiDiffFiles) > 0 {
+		handler.module.linker.(*libraryDecorator).sAbiDiff = android.PathsForBazelOut(ctx, ccInfo.AbiDiffFiles)
+	}
+
 	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
 		TableOfContents: tocFile,
 		SharedLibrary:   outputFilePath,
@@ -1900,6 +1914,16 @@
 		isLlndkOrNdk, allowExtensions, "current", errorMessage)
 }
 
+func (library *libraryDecorator) optInAbiDiff(ctx android.ModuleContext, referenceDump android.Path,
+	baseName, nameExt string, isLlndkOrNdk bool, refDumpDir string) {
+
+	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
+	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName + " -ref-dump-dir $$ANDROID_BUILD_TOP/" + refDumpDir
+
+	library.sourceAbiDiff(ctx, referenceDump, baseName, nameExt,
+		isLlndkOrNdk, /* allowExtensions */ false, "current", errorMessage)
+}
+
 func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
 	if library.sabi.shouldCreateSourceAbiDump() {
 		exportIncludeDirs := library.flagExporter.exportedIncludes(ctx)
@@ -1944,6 +1968,19 @@
 			library.sameVersionAbiDiff(ctx, currDumpFile.Path(),
 				fileName, isLlndk || isNdk, ctx.IsVndkExt())
 		}
+		// Check against the opt-in reference dumps.
+		for i, optInDumpDir := range library.Properties.Header_abi_checker.Ref_dump_dirs {
+			optInDumpDirPath := android.PathForModuleSrc(ctx, optInDumpDir)
+			// Ref_dump_dirs are not versioned.
+			// They do not contain subdir for binder bitness because 64-bit binder has been mandatory.
+			optInDumpFile := getRefAbiDumpFile(ctx, optInDumpDirPath.String(), fileName)
+			if !optInDumpFile.Valid() {
+				continue
+			}
+			library.optInAbiDiff(ctx, optInDumpFile.Path(),
+				fileName, "opt"+strconv.Itoa(i), isLlndk || isNdk,
+				optInDumpDirPath.String())
+		}
 	}
 }
 
@@ -2914,7 +2951,8 @@
 		Bzl_load_location: fmt.Sprintf("//build/bazel/rules/cc:%s.bzl", modType),
 	}
 
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
+	tags := android.ApexAvailableTags(module)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name(), Tags: tags}, attrs)
 }
 
 // TODO(b/199902614): Can this be factored to share with the other Attributes?
diff --git a/cc/library_stub.go b/cc/library_stub.go
index c61e2d1..d21df51 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -148,8 +148,9 @@
 
 	var in android.Path
 
+	// src might not exist during the beginning of soong analysis in Multi-tree
 	if src := String(d.properties.Src); src != "" {
-		in = android.PathForModuleSrc(ctx, src)
+		in = android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), src)
 	}
 
 	// LLNDK variant
diff --git a/cc/lto_test.go b/cc/lto_test.go
index b52f2b6..afd2c77 100644
--- a/cc/lto_test.go
+++ b/cc/lto_test.go
@@ -24,29 +24,35 @@
 
 func TestThinLtoDeps(t *testing.T) {
 	bp := `
-	cc_library {
+	cc_library_shared {
 		name: "lto_enabled",
 		srcs: ["src.c"],
-		static_libs: ["foo"],
+		static_libs: ["foo", "lib_never_lto"],
 		shared_libs: ["bar"],
 		lto: {
 			thin: true,
 		}
 	}
-	cc_library {
+	cc_library_static {
 		name: "foo",
 		static_libs: ["baz"],
 	}
-	cc_library {
+	cc_library_shared {
 		name: "bar",
 		static_libs: ["qux"],
 	}
-	cc_library {
+	cc_library_static {
 		name: "baz",
 	}
-	cc_library {
+	cc_library_static {
 		name: "qux",
 	}
+	cc_library_static {
+		name: "lib_never_lto",
+		lto: {
+			never: true,
+		},
+	}
 `
 
 	result := android.GroupFixturePreparers(
@@ -54,8 +60,6 @@
 	).RunTestWithBp(t, bp)
 
 	libLto := result.ModuleForTests("lto_enabled", "android_arm64_armv8-a_shared").Module()
-	libFoo := result.ModuleForTests("foo", "android_arm64_armv8-a_static_lto-thin").Module()
-	libBaz := result.ModuleForTests("baz", "android_arm64_armv8-a_static_lto-thin").Module()
 
 	hasDep := func(m android.Module, wantDep android.Module) bool {
 		var found bool
@@ -67,12 +71,24 @@
 		return found
 	}
 
+	libFoo := result.ModuleForTests("foo", "android_arm64_armv8-a_static_lto-thin").Module()
 	if !hasDep(libLto, libFoo) {
 		t.Errorf("'lto_enabled' missing dependency on thin lto variant of 'foo'")
 	}
 
+	libBaz := result.ModuleForTests("baz", "android_arm64_armv8-a_static_lto-thin").Module()
 	if !hasDep(libFoo, libBaz) {
-		t.Errorf("'lto_enabled' missing dependency on thin lto variant of transitive dep 'baz'")
+		t.Errorf("'foo' missing dependency on thin lto variant of transitive dep 'baz'")
+	}
+
+	libNeverLto := result.ModuleForTests("lib_never_lto", "android_arm64_armv8-a_static_lto-thin").Module()
+	if !hasDep(libLto, libNeverLto) {
+		t.Errorf("'lto_enabled' missing dependency on NO-thin lto variant of 'lib_never_lto'")
+	}
+
+	libBar := result.ModuleForTests("bar", "android_arm64_armv8-a_shared").Module()
+	if !hasDep(libLto, libBar) {
+		t.Errorf("'lto_enabled' missing dependency on non-thin lto variant of 'bar'")
 	}
 
 	barVariants := result.ModuleVariantsForTests("bar")
@@ -88,3 +104,74 @@
 		}
 	}
 }
+
+func TestThinLtoOnlyOnStaticDep(t *testing.T) {
+	bp := `
+	cc_library_shared {
+		name: "root",
+		srcs: ["src.c"],
+		static_libs: ["foo"],
+	}
+	cc_library_shared {
+		name: "root_no_lto",
+		srcs: ["src.c"],
+		static_libs: ["foo"],
+		lto: {
+			never: true,
+		}
+	}
+	cc_library_static {
+		name: "foo",
+		srcs: ["foo.c"],
+		static_libs: ["baz"],
+		lto: {
+			thin: true,
+		}
+	}
+	cc_library_static {
+		name: "baz",
+		srcs: ["baz.c"],
+	}
+`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+	).RunTestWithBp(t, bp)
+
+	libRoot := result.ModuleForTests("root", "android_arm64_armv8-a_shared").Module()
+	libRootLtoNever := result.ModuleForTests("root_no_lto", "android_arm64_armv8-a_shared").Module()
+
+	hasDep := func(m android.Module, wantDep android.Module) bool {
+		var found bool
+		result.VisitDirectDeps(m, func(dep blueprint.Module) {
+			if dep == wantDep {
+				found = true
+			}
+		})
+		return found
+	}
+
+	libFoo := result.ModuleForTests("foo", "android_arm64_armv8-a_static")
+	if !hasDep(libRoot, libFoo.Module()) {
+		t.Errorf("'root' missing dependency on thin lto variant of 'foo'")
+	}
+
+	if !hasDep(libRootLtoNever, libFoo.Module()) {
+		t.Errorf("'root_no_lto' missing dependency on thin lto variant of 'foo'")
+	}
+
+	libFooCFlags := libFoo.Rule("cc").Args["cFlags"]
+	if w := "-flto=thin -fsplit-lto-unit"; !strings.Contains(libFooCFlags, w) {
+		t.Errorf("'foo' expected to have flags %q, but got %q", w, libFooCFlags)
+	}
+
+	libBaz := result.ModuleForTests("baz", "android_arm64_armv8-a_static_lto-thin")
+	if !hasDep(libFoo.Module(), libBaz.Module()) {
+		t.Errorf("'foo' missing dependency on thin lto variant of transitive dep 'baz'")
+	}
+
+	libBazCFlags := libFoo.Rule("cc").Args["cFlags"]
+	if w := "-flto=thin -fsplit-lto-unit"; !strings.Contains(libBazCFlags, w) {
+		t.Errorf("'baz' expected to have flags %q, but got %q", w, libFooCFlags)
+	}
+}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index ba21004..9fbf879 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -388,7 +388,9 @@
 	if fullBuild {
 		name += "_bp2build_cc_library_static"
 	}
-	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
+
+	tags := android.ApexAvailableTags(module)
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name, Tags: tags}, attrs, prebuiltAttrs.Enabled)
 }
 
 type bazelPrebuiltLibrarySharedAttributes struct {
@@ -408,7 +410,8 @@
 	}
 
 	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
-	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
+	tags := android.ApexAvailableTags(module)
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name, Tags: tags}, attrs, prebuiltAttrs.Enabled)
 }
 
 type prebuiltObjectProperties struct {
@@ -740,7 +743,8 @@
 	}
 
 	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+	tags := android.ApexAvailableTags(module)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name, Tags: tags}, attrs)
 }
 
 type Sanitized struct {
diff --git a/cc/proto.go b/cc/proto.go
index cf5ed04..27f37cb 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -205,13 +205,13 @@
 	protoAttrs.Deps.SetValue(protoInfo.Proto_libs)
 
 	name := m.Name() + suffix
-
+	tags := android.ApexAvailableTags(m)
 	ctx.CreateBazelTargetModule(
 		bazel.BazelTargetModuleProperties{
 			Rule_class:        rule_class,
 			Bzl_load_location: "//build/bazel/rules/cc:cc_proto.bzl",
 		},
-		android.CommonAttributes{Name: name},
+		android.CommonAttributes{Name: name, Tags: tags},
 		&protoAttrs)
 
 	var privateHdrs bool
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index 580adfa..143602a 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -314,6 +314,10 @@
 }
 
 func TestMiscUndefined(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+
 	bp := `
 	cc_binary {
 		name: "bin_with_ubsan",
@@ -568,7 +572,7 @@
 		}
 
 		cc_binary {
-			name: "bin_depends_ubsan",
+			name: "bin_depends_ubsan_static",
 			host_supported: true,
 			shared_libs: [
 				"libshared",
@@ -581,6 +585,14 @@
 		}
 
 		cc_binary {
+			name: "bin_depends_ubsan_shared",
+			host_supported: true,
+			shared_libs: [
+				"libsharedubsan",
+			],
+		}
+
+		cc_binary {
 			name: "bin_no_ubsan",
 			host_supported: true,
 			shared_libs: [
@@ -603,6 +615,14 @@
 			host_supported: true,
 		}
 
+		cc_library_shared {
+			name: "libsharedubsan",
+			host_supported: true,
+			sanitize: {
+				undefined: true,
+			}
+		}
+
 		cc_library_static {
 			name: "libubsan",
 			host_supported: true,
@@ -628,22 +648,33 @@
 
 	check := func(t *testing.T, result *android.TestResult, variant string) {
 		staticVariant := variant + "_static"
+		sharedVariant := variant + "_shared"
 
 		minimalRuntime := result.ModuleForTests("libclang_rt.ubsan_minimal", staticVariant)
 
 		// The binaries, one with ubsan and one without
 		binWithUbsan := result.ModuleForTests("bin_with_ubsan", variant)
-		binDependsUbsan := result.ModuleForTests("bin_depends_ubsan", variant)
+		binDependsUbsan := result.ModuleForTests("bin_depends_ubsan_static", variant)
+		libSharedUbsan := result.ModuleForTests("libsharedubsan", sharedVariant)
+		binDependsUbsanShared := result.ModuleForTests("bin_depends_ubsan_shared", variant)
 		binNoUbsan := result.ModuleForTests("bin_no_ubsan", variant)
 
 		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_with_ubsan static libs",
 			strings.Split(binWithUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
 
-		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_depends_ubsan static libs",
+		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_depends_ubsan_static static libs",
 			strings.Split(binDependsUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
 
+		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in libsharedubsan static libs",
+			strings.Split(libSharedUbsan.Rule("ld").Args["libFlags"], " "),
+			minimalRuntime.OutputFiles(t, "")[0].String())
+
+		android.AssertStringListDoesNotContain(t, "unexpected libclang_rt.ubsan_minimal in bin_depends_ubsan_shared static libs",
+			strings.Split(binDependsUbsanShared.Rule("ld").Args["libFlags"], " "),
+			minimalRuntime.OutputFiles(t, "")[0].String())
+
 		android.AssertStringListDoesNotContain(t, "unexpected libclang_rt.ubsan_minimal in bin_no_ubsan static libs",
 			strings.Split(binNoUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
@@ -652,10 +683,18 @@
 			strings.Split(binWithUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
 
-		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan static libs",
+		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan_static static libs",
 			strings.Split(binDependsUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
 
+		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in libsharedubsan static libs",
+			strings.Split(libSharedUbsan.Rule("ld").Args["ldFlags"], " "),
+			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
+
+		android.AssertStringListDoesNotContain(t, "unexpected -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan_shared static libs",
+			strings.Split(binDependsUbsanShared.Rule("ld").Args["ldFlags"], " "),
+			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
+
 		android.AssertStringListDoesNotContain(t, "unexpected -Wl,--exclude-libs for minimal runtime in bin_no_ubsan static libs",
 			strings.Split(binNoUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 029bbb4..f4a63a4 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -89,6 +89,7 @@
 	flag.StringVar(&bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
 	flag.StringVar(&symlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
+	flag.StringVar(&cmdlineArgs.BazelForceEnabledModules, "bazel-force-enabled-modules", "", "additional modules to build with Bazel. Comma-delimited")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
 	flag.BoolVar(&cmdlineArgs.BazelMode, "bazel-mode", false, "use bazel for analysis of certain modules")
 	flag.BoolVar(&cmdlineArgs.BazelModeStaging, "bazel-mode-staging", false, "use bazel for analysis of certain near-ready modules")
@@ -113,11 +114,16 @@
 	ctx := android.NewContext(configuration)
 	ctx.SetNameInterface(newNameResolver(configuration))
 	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
+	ctx.AddIncludeTags(configuration.IncludeTags()...)
 	return ctx
 }
 
 func newConfig(availableEnv map[string]string) android.Config {
 	var buildMode android.SoongBuildMode
+	var bazelForceEnabledModules []string
+	if len(cmdlineArgs.BazelForceEnabledModules) > 0 {
+		bazelForceEnabledModules = strings.Split(cmdlineArgs.BazelForceEnabledModules, ",")
+	}
 
 	if symlinkForestMarker != "" {
 		buildMode = android.SymlinkForest
@@ -141,7 +147,7 @@
 		buildMode = android.AnalysisNoBazel
 	}
 
-	configuration, err := android.NewConfig(cmdlineArgs.ModuleListFile, buildMode, runGoTests, outDir, soongOutDir, availableEnv)
+	configuration, err := android.NewConfig(cmdlineArgs.ModuleListFile, buildMode, runGoTests, outDir, soongOutDir, availableEnv, bazelForceEnabledModules)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
diff --git a/filesystem/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index 1ee0edc..2ee420c 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -149,7 +149,7 @@
 		cmd.FlagWithArg("--prop ", proptools.ShellEscape(fmt.Sprintf("%s:%s", name, value)))
 	} else {
 		p := android.PathForModuleSrc(ctx, file)
-		cmd.Input(p)
+		cmd.Implicit(p)
 		cmd.FlagWithArg("--prop_from_file ", proptools.ShellEscape(fmt.Sprintf("%s:%s", name, cmd.PathForInput(p))))
 	}
 }
diff --git a/java/base.go b/java/base.go
index 5d24981..55d77dc 100644
--- a/java/base.go
+++ b/java/base.go
@@ -868,7 +868,7 @@
 	flags = append(flags, genAidlIncludeFlags(ctx, aidlSrcs, includeDirs))
 
 	sdkVersion := (j.SdkVersion(ctx)).Kind
-	defaultTrace := ((sdkVersion == android.SdkSystemServer) || (sdkVersion == android.SdkCore) || (sdkVersion == android.SdkCorePlatform) || (sdkVersion == android.SdkModule))
+	defaultTrace := ((sdkVersion == android.SdkSystemServer) || (sdkVersion == android.SdkCore) || (sdkVersion == android.SdkCorePlatform) || (sdkVersion == android.SdkModule) || (sdkVersion == android.SdkSystem))
 	if proptools.BoolDefault(j.deviceProperties.Aidl.Generate_traces, defaultTrace) {
 		flags = append(flags, "-t")
 	}
diff --git a/java/java.go b/java/java.go
index e37a77e..9dd5850 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1567,8 +1567,14 @@
 	Api_surface *string
 
 	// list of Java API contribution modules that consists this API surface
+	// This is a list of Soong modules
 	Api_contributions []string
 
+	// list of api.txt files relative to this directory that contribute to the
+	// API surface.
+	// This is a list of relative paths
+	Api_files []string
+
 	// List of flags to be passed to the javac compiler to generate jar file
 	Javacflags []string
 }
@@ -1665,6 +1671,13 @@
 		srcFiles = append(srcFiles, android.PathForSource(ctx, provider.ApiFile.String()))
 	})
 
+	// Add the api_files inputs
+	for _, api := range al.properties.Api_files {
+		// Use MaybeExistentPathForSource since the api file might not exist during analysis.
+		// This will be provided by the orchestrator in the combined execution.
+		srcFiles = append(srcFiles, android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), api))
+	}
+
 	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
diff --git a/java/java_test.go b/java/java_test.go
index ff15783..dff1fd0 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1863,6 +1863,7 @@
 			name: "bar2",
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
+			api_files: ["api1/current.txt", "api2/current.txt"]
 		}
 		`,
 		map[string][]byte{
@@ -1880,7 +1881,7 @@
 		},
 		{
 			moduleName:         "bar2",
-			sourceTextFileDirs: []string{"a/foo1.txt", "b/foo2.txt"},
+			sourceTextFileDirs: []string{"a/foo1.txt", "b/foo2.txt", "api1/current.txt", "api2/current.txt"},
 		},
 	}
 	for _, c := range testcases {
diff --git a/java/robolectric.go b/java/robolectric.go
index 3a6b4ac..938abe1 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -328,7 +328,7 @@
 
 func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) {
 	fmt.Fprintln(w, "")
-	fmt.Fprintln(w, "include $(CLEAR_VARS)")
+	fmt.Fprintln(w, "include $(CLEAR_VARS)", " # java.robolectricTest")
 	fmt.Fprintln(w, "LOCAL_MODULE :=", name)
 	fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module)
 	fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
diff --git a/phony/phony.go b/phony/phony.go
index a31d402..760b79b 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -49,7 +49,7 @@
 func (p *phony) AndroidMk() android.AndroidMkData {
 	return android.AndroidMkData{
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)", " # phony.phony")
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w, "LOCAL_MODULE :=", name)
 			data.Entries.WriteLicenseVariables(w)
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 1f0d28d..0edbb7c 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -336,8 +336,8 @@
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
 			// sysprop_library module itself is defined as a FAKE module to perform API check.
 			// Actual implementation libraries are created on LoadHookMutator
-			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
-			fmt.Fprintf(w, "LOCAL_MODULE := %s\n", m.Name())
+			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)", " # sysprop.syspropLibrary")
+			fmt.Fprintln(w, "LOCAL_MODULE :=", m.Name())
 			data.Entries.WriteLicenseVariables(w)
 			fmt.Fprintf(w, "LOCAL_MODULE_CLASS := FAKE\n")
 			fmt.Fprintf(w, "LOCAL_MODULE_TAGS := optional\n")
diff --git a/tests/apex_comparison_tests.sh b/tests/apex_comparison_tests.sh
index 5fbbd0f..bfcf42d 100755
--- a/tests/apex_comparison_tests.sh
+++ b/tests/apex_comparison_tests.sh
@@ -29,10 +29,13 @@
 # Test Setup
 ############
 
-OUTPUT_DIR="$(mktemp -d)"
+OUTPUT_DIR="$(mktemp -d tmp.XXXXXX)"
 SOONG_OUTPUT_DIR="$OUTPUT_DIR/soong"
 BAZEL_OUTPUT_DIR="$OUTPUT_DIR/bazel"
 
+export TARGET_PRODUCT="module_arm"
+[ "$#" -eq 1 ] && export TARGET_PRODUCT="$1"
+
 function call_bazel() {
   build/bazel/bin/bazel --output_base="$BAZEL_OUTPUT_DIR" $@
 }
@@ -50,7 +53,7 @@
 export UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true # don't rely on prebuilts
 export TARGET_BUILD_APPS="com.android.adbd com.android.tzdata build.bazel.examples.apex.minimal"
 packages/modules/common/build/build_unbundled_mainline_module.sh \
-  --product module_arm \
+  --product "$TARGET_PRODUCT" \
   --dist_dir "$SOONG_OUTPUT_DIR"
 
 ######################
@@ -60,22 +63,15 @@
 
 BAZEL_OUT="$(call_bazel info --config=bp2build output_path)"
 
-export TARGET_PRODUCT="module_arm"
 call_bazel build --config=bp2build --config=ci --config=android \
   //packages/modules/adb/apex:com.android.adbd \
   //system/timezone/apex:com.android.tzdata \
   //build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal.apex
 
 # Build debugfs separately, as it's not a dep of apexer, but needs to be an explicit arg.
-call_bazel build --config=bp2build --config=linux_x86_64 //external/e2fsprogs/debugfs
+call_bazel build --config=bp2build --config=linux_x86_64 //external/e2fsprogs/debugfs //system/apex/tools:deapexer
 DEBUGFS_PATH="$BAZEL_OUT/linux_x86_64-fastbuild/bin/external/e2fsprogs/debugfs/debugfs"
-
-function run_deapexer() {
-  call_bazel run --config=bp2build --config=linux_x86_64 //system/apex/tools:deapexer \
-    -- \
-    --debugfs_path="$DEBUGFS_PATH" \
-    $@
-}
+DEAPEXER="$BAZEL_OUT/linux_x86_64-fastbuild/bin/system/apex/tools/deapexer --debugfs_path=$DEBUGFS_PATH"
 
 #######
 # Tests
@@ -92,8 +88,8 @@
   local SOONG_LIST="$OUTPUT_DIR/soong.list"
   local BAZEL_LIST="$OUTPUT_DIR/bazel.list"
 
-  run_deapexer list "$SOONG_APEX" > "$SOONG_LIST"
-  run_deapexer list "$BAZEL_APEX" > "$BAZEL_LIST"
+  $DEAPEXER list "$SOONG_APEX" > "$SOONG_LIST"
+  $DEAPEXER list "$BAZEL_APEX" > "$BAZEL_LIST"
 
   if cmp -s "$SOONG_LIST" "$BAZEL_LIST"
   then
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 1e07727..7a71b27 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -13,3 +13,4 @@
 # The following tests build against the full source tree and don't rely on the
 # mock client.
 "$TOP/build/soong/tests/apex_comparison_tests.sh"
+"$TOP/build/soong/tests/apex_comparison_tests.sh" "module_arm64only"
diff --git a/ui/build/config.go b/ui/build/config.go
index c98601e..ef2e87e 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -109,6 +109,10 @@
 	emptyNinjaFile bool
 
 	metricsUploader string
+
+	bazelForceEnabledModules string
+
+	includeTags []string
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -238,7 +242,7 @@
 }
 
 func defaultBazelProdMode(cfg *configImpl) bool {
-	// Envirnoment flag to disable Bazel for users which experience
+	// Environment flag to disable Bazel for users which experience
 	// broken bazel-handled builds, or significant performance regressions.
 	if cfg.IsBazelMixedBuildForceDisabled() {
 		return false
@@ -741,6 +745,14 @@
 			c.bazelStagingMode = true
 		} else if arg == "--search-api-dir" {
 			c.searchApiDir = true
+		} else if strings.HasPrefix(arg, "--build-command=") {
+			buildCmd := strings.TrimPrefix(arg, "--build-command=")
+			// remove quotations
+			buildCmd = strings.TrimPrefix(buildCmd, "\"")
+			buildCmd = strings.TrimSuffix(buildCmd, "\"")
+			ctx.Metrics.SetBuildCommand([]string{buildCmd})
+		} else if strings.HasPrefix(arg, "--bazel-force-enabled-modules=") {
+			c.bazelForceEnabledModules = strings.TrimPrefix(arg, "--bazel-force-enabled-modules=")
 		} else if len(arg) > 0 && arg[0] == '-' {
 			parseArgNum := func(def int) int {
 				if len(arg) > 2 {
@@ -1069,6 +1081,14 @@
 	return c.parallel
 }
 
+func (c *configImpl) GetIncludeTags() []string {
+	return c.includeTags
+}
+
+func (c *configImpl) SetIncludeTags(i []string) {
+	c.includeTags = i
+}
+
 func (c *configImpl) HighmemParallel() int {
 	if i, ok := c.environ.GetInt("NINJA_HIGHMEM_NUM_JOBS"); ok {
 		return i
@@ -1488,6 +1508,10 @@
 	return c.Environment().IsEnvTrue("BUILD_BROKEN_DISABLE_BAZEL")
 }
 
+func (c *configImpl) BazelModulesForceEnabledByFlag() string {
+	return c.bazelForceEnabledModules
+}
+
 func GetMetricsUploader(topDir string, env *Environment) string {
 	if p, ok := env.Get("METRICS_UPLOADER"); ok {
 		metricsUploader := filepath.Join(topDir, p)
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index b3b3866..1e3e547 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -139,6 +139,7 @@
 var BannerVars = []string{
 	"PLATFORM_VERSION_CODENAME",
 	"PLATFORM_VERSION",
+	"PRODUCT_INCLUDE_TAGS",
 	"TARGET_PRODUCT",
 	"TARGET_BUILD_VARIANT",
 	"TARGET_BUILD_APPS",
@@ -289,4 +290,5 @@
 	config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true")
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
 	config.SetBuildBrokenNinjaUsesEnvVars(strings.Fields(makeVars["BUILD_BROKEN_NINJA_USES_ENV_VARS"]))
+	config.SetIncludeTags(strings.Fields(makeVars["PRODUCT_INCLUDE_TAGS"]))
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index abaf5ae..b89ca20 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -18,7 +18,6 @@
 	"errors"
 	"fmt"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -56,13 +55,13 @@
 	bootstrapEpoch = 1
 )
 
-func writeEnvironmentFile(ctx Context, envFile string, envDeps map[string]string) error {
+func writeEnvironmentFile(_ Context, envFile string, envDeps map[string]string) error {
 	data, err := shared.EnvFileContents(envDeps)
 	if err != nil {
 		return err
 	}
 
-	return ioutil.WriteFile(envFile, data, 0644)
+	return os.WriteFile(envFile, data, 0644)
 }
 
 // This uses Android.bp files and various tools to generate <builddir>/build.ninja.
@@ -141,7 +140,7 @@
 	if exists, err := fileExists(path); err != nil {
 		ctx.Fatalf("Failed to check if file '%s' exists: %s", path, err)
 	} else if !exists {
-		err = ioutil.WriteFile(path, nil, 0666)
+		err = os.WriteFile(path, nil, 0666)
 		if err != nil {
 			ctx.Fatalf("Failed to create empty file '%s': %s", path, err)
 		}
@@ -157,24 +156,28 @@
 	return true, nil
 }
 
-func primaryBuilderInvocation(
-	config Config,
-	name string,
-	output string,
-	specificArgs []string,
-	description string) bootstrap.PrimaryBuilderInvocation {
+type PrimaryBuilderFactory struct {
+	name         string
+	description  string
+	config       Config
+	output       string
+	specificArgs []string
+	debugPort    string
+}
+
+func (pb PrimaryBuilderFactory) primaryBuilderInvocation() bootstrap.PrimaryBuilderInvocation {
 	commonArgs := make([]string, 0, 0)
 
-	if !config.skipSoongTests {
+	if !pb.config.skipSoongTests {
 		commonArgs = append(commonArgs, "-t")
 	}
 
-	commonArgs = append(commonArgs, "-l", filepath.Join(config.FileListDir(), "Android.bp.list"))
+	commonArgs = append(commonArgs, "-l", filepath.Join(pb.config.FileListDir(), "Android.bp.list"))
 	invocationEnv := make(map[string]string)
-	if os.Getenv("SOONG_DELVE") != "" {
+	if pb.debugPort != "" {
 		//debug mode
-		commonArgs = append(commonArgs, "--delve_listen", os.Getenv("SOONG_DELVE"))
-		commonArgs = append(commonArgs, "--delve_path", shared.ResolveDelveBinary())
+		commonArgs = append(commonArgs, "--delve_listen", pb.debugPort,
+			"--delve_path", shared.ResolveDelveBinary())
 		// GODEBUG=asyncpreemptoff=1 disables the preemption of goroutines. This
 		// is useful because the preemption happens by sending SIGURG to the OS
 		// thread hosting the goroutine in question and each signal results in
@@ -188,26 +191,26 @@
 	}
 
 	var allArgs []string
-	allArgs = append(allArgs, specificArgs...)
+	allArgs = append(allArgs, pb.specificArgs...)
 	allArgs = append(allArgs,
-		"--globListDir", name,
-		"--globFile", config.NamedGlobFile(name))
+		"--globListDir", pb.name,
+		"--globFile", pb.config.NamedGlobFile(pb.name))
 
 	allArgs = append(allArgs, commonArgs...)
-	allArgs = append(allArgs, environmentArgs(config, name)...)
+	allArgs = append(allArgs, environmentArgs(pb.config, pb.name)...)
 	if profileCpu := os.Getenv("SOONG_PROFILE_CPU"); profileCpu != "" {
-		allArgs = append(allArgs, "--cpuprofile", profileCpu+"."+name)
+		allArgs = append(allArgs, "--cpuprofile", profileCpu+"."+pb.name)
 	}
 	if profileMem := os.Getenv("SOONG_PROFILE_MEM"); profileMem != "" {
-		allArgs = append(allArgs, "--memprofile", profileMem+"."+name)
+		allArgs = append(allArgs, "--memprofile", profileMem+"."+pb.name)
 	}
 	allArgs = append(allArgs, "Android.bp")
 
 	return bootstrap.PrimaryBuilderInvocation{
 		Inputs:      []string{"Android.bp"},
-		Outputs:     []string{output},
+		Outputs:     []string{pb.output},
 		Args:        allArgs,
-		Description: description,
+		Description: pb.description,
 		// NB: Changing the value of this environment variable will not result in a
 		// rebuild. The bootstrap Ninja file will change, but apparently Ninja does
 		// not consider changing the pool specified in a statement a change that's
@@ -270,90 +273,121 @@
 	if config.bazelStagingMode {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--bazel-mode-staging")
 	}
-
-	mainSoongBuildInvocation := primaryBuilderInvocation(
-		config,
-		soongBuildTag,
-		config.SoongNinjaFile(),
-		mainSoongBuildExtraArgs,
-		fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
-	)
-
-	if config.BazelBuildEnabled() {
-		// Mixed builds call Bazel from soong_build and they therefore need the
-		// Bazel workspace to be available. Make that so by adding a dependency on
-		// the bp2build marker file to the action that invokes soong_build .
-		mainSoongBuildInvocation.OrderOnlyInputs = append(mainSoongBuildInvocation.OrderOnlyInputs,
-			config.Bp2BuildWorkspaceMarkerFile())
+	if len(config.bazelForceEnabledModules) > 0 {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--bazel-force-enabled-modules="+config.bazelForceEnabledModules)
 	}
 
-	bp2buildInvocation := primaryBuilderInvocation(
-		config,
-		bp2buildFilesTag,
-		config.Bp2BuildFilesMarkerFile(),
-		[]string{
-			"--bp2build_marker", config.Bp2BuildFilesMarkerFile(),
-		},
-		fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
-	)
-
-	bp2buildWorkspaceInvocation := primaryBuilderInvocation(
-		config,
-		bp2buildWorkspaceTag,
-		config.Bp2BuildWorkspaceMarkerFile(),
-		[]string{
-			"--symlink_forest_marker", config.Bp2BuildWorkspaceMarkerFile(),
-		},
-		fmt.Sprintf("Creating Bazel symlink forest"),
-	)
-
-	bp2buildWorkspaceInvocation.Inputs = append(bp2buildWorkspaceInvocation.Inputs,
-		config.Bp2BuildFilesMarkerFile(), filepath.Join(config.FileListDir(), "bazel.list"))
-
-	jsonModuleGraphInvocation := primaryBuilderInvocation(
-		config,
-		jsonModuleGraphTag,
-		config.ModuleGraphFile(),
-		[]string{
-			"--module_graph_file", config.ModuleGraphFile(),
-			"--module_actions_file", config.ModuleActionsFile(),
-		},
-		fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
-	)
-
 	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
-	queryviewInvocation := primaryBuilderInvocation(
-		config,
-		queryviewTag,
-		config.QueryviewMarkerFile(),
-		[]string{
-			"--bazel_queryview_dir", queryviewDir,
-		},
-		fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
-	)
-
 	// The BUILD files will be generated in out/soong/.api_bp2build (no symlinks to src files)
 	// The final workspace will be generated in out/soong/api_bp2build
 	apiBp2buildDir := filepath.Join(config.SoongOutDir(), ".api_bp2build")
-	apiBp2buildInvocation := primaryBuilderInvocation(
-		config,
-		apiBp2buildTag,
-		config.ApiBp2buildMarkerFile(),
-		[]string{
-			"--bazel_api_bp2build_dir", apiBp2buildDir,
-		},
-		fmt.Sprintf("generating BUILD files for API contributions at %s", apiBp2buildDir),
-	)
 
-	soongDocsInvocation := primaryBuilderInvocation(
-		config,
-		soongDocsTag,
-		config.SoongDocsHtml(),
-		[]string{
-			"--soong_docs", config.SoongDocsHtml(),
+	pbfs := []PrimaryBuilderFactory{
+		{
+			name:         soongBuildTag,
+			description:  fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
+			config:       config,
+			output:       config.SoongNinjaFile(),
+			specificArgs: mainSoongBuildExtraArgs,
 		},
-		fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
-	)
+		{
+			name:         bp2buildFilesTag,
+			description:  fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
+			config:       config,
+			output:       config.Bp2BuildFilesMarkerFile(),
+			specificArgs: []string{"--bp2build_marker", config.Bp2BuildFilesMarkerFile()},
+		},
+		{
+			name:         bp2buildWorkspaceTag,
+			description:  "Creating Bazel symlink forest",
+			config:       config,
+			output:       config.Bp2BuildWorkspaceMarkerFile(),
+			specificArgs: []string{"--symlink_forest_marker", config.Bp2BuildWorkspaceMarkerFile()},
+		},
+		{
+			name:        jsonModuleGraphTag,
+			description: fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
+			config:      config,
+			output:      config.ModuleGraphFile(),
+			specificArgs: []string{
+				"--module_graph_file", config.ModuleGraphFile(),
+				"--module_actions_file", config.ModuleActionsFile(),
+			},
+		},
+		{
+			name:         queryviewTag,
+			description:  fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
+			config:       config,
+			output:       config.QueryviewMarkerFile(),
+			specificArgs: []string{"--bazel_queryview_dir", queryviewDir},
+		},
+		{
+			name:         apiBp2buildTag,
+			description:  fmt.Sprintf("generating BUILD files for API contributions at %s", apiBp2buildDir),
+			config:       config,
+			output:       config.ApiBp2buildMarkerFile(),
+			specificArgs: []string{"--bazel_api_bp2build_dir", apiBp2buildDir},
+		},
+		{
+			name:         soongDocsTag,
+			description:  fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
+			config:       config,
+			output:       config.SoongDocsHtml(),
+			specificArgs: []string{"--soong_docs", config.SoongDocsHtml()},
+		},
+	}
+
+	// Figure out which invocations will be run under the debugger:
+	//   * SOONG_DELVE if set specifies listening port
+	//   * SOONG_DELVE_STEPS if set specifies specific invocations to be debugged, otherwise all are
+	debuggedInvocations := make(map[string]bool)
+	delvePort := os.Getenv("SOONG_DELVE")
+	if delvePort != "" {
+		if steps := os.Getenv("SOONG_DELVE_STEPS"); steps != "" {
+			var validSteps []string
+			for _, pbf := range pbfs {
+				debuggedInvocations[pbf.name] = false
+				validSteps = append(validSteps, pbf.name)
+
+			}
+			for _, step := range strings.Split(steps, ",") {
+				if _, ok := debuggedInvocations[step]; ok {
+					debuggedInvocations[step] = true
+				} else {
+					ctx.Fatalf("SOONG_DELVE_STEPS contains unknown soong_build step %s\n"+
+						"Valid steps are %v", step, validSteps)
+				}
+			}
+		} else {
+			//  SOONG_DELVE_STEPS is not set, run all steps in the debugger
+			for _, pbf := range pbfs {
+				debuggedInvocations[pbf.name] = true
+			}
+		}
+	}
+
+	var invocations []bootstrap.PrimaryBuilderInvocation
+	for _, pbf := range pbfs {
+		if debuggedInvocations[pbf.name] {
+			pbf.debugPort = delvePort
+		}
+		pbi := pbf.primaryBuilderInvocation()
+		// Some invocations require adjustment:
+		switch pbf.name {
+		case soongBuildTag:
+			if config.BazelBuildEnabled() {
+				// Mixed builds call Bazel from soong_build and they therefore need the
+				// Bazel workspace to be available. Make that so by adding a dependency on
+				// the bp2build marker file to the action that invokes soong_build .
+				pbi.OrderOnlyInputs = append(pbi.OrderOnlyInputs, config.Bp2BuildWorkspaceMarkerFile())
+			}
+		case bp2buildWorkspaceTag:
+			pbi.Inputs = append(pbi.Inputs,
+				config.Bp2BuildFilesMarkerFile(),
+				filepath.Join(config.FileListDir(), "bazel.list"))
+		}
+		invocations = append(invocations, pbi)
+	}
 
 	// The glob .ninja files are subninja'd. However, they are generated during
 	// the build itself so we write an empty file if the file does not exist yet
@@ -362,13 +396,14 @@
 		writeEmptyFile(ctx, globFile)
 	}
 
-	var blueprintArgs bootstrap.Args
-
-	blueprintArgs.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
-	blueprintArgs.OutFile = shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja")
-	blueprintArgs.EmptyNinjaFile = false
+	blueprintArgs := bootstrap.Args{
+		ModuleListFile: filepath.Join(config.FileListDir(), "Android.bp.list"),
+		OutFile:        shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja"),
+		EmptyNinjaFile: false,
+	}
 
 	blueprintCtx := blueprint.NewContext()
+	blueprintCtx.AddIncludeTags(config.GetIncludeTags()...)
 	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 	blueprintConfig := BlueprintConfig{
 		soongOutDir: config.SoongOutDir(),
@@ -376,16 +411,9 @@
 		outDir:      config.OutDir(),
 		runGoTests:  !config.skipSoongTests,
 		// If we want to debug soong_build, we need to compile it for debugging
-		debugCompilation: os.Getenv("SOONG_DELVE") != "",
-		subninjas:        bootstrapGlobFileList(config),
-		primaryBuilderInvocations: []bootstrap.PrimaryBuilderInvocation{
-			mainSoongBuildInvocation,
-			bp2buildInvocation,
-			bp2buildWorkspaceInvocation,
-			jsonModuleGraphInvocation,
-			queryviewInvocation,
-			apiBp2buildInvocation,
-			soongDocsInvocation},
+		debugCompilation:          delvePort != "",
+		subninjas:                 bootstrapGlobFileList(config),
+		primaryBuilderInvocations: invocations,
 	}
 
 	// since `bootstrap.ninja` is regenerated unconditionally, we ignore the deps, i.e. little