Merge "Support abi check in mixed build."
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 acb81a4..cf74b9c 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -18,7 +18,6 @@
 	"bytes"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
@@ -260,11 +259,11 @@
 	return result, nil
 }
 
-func (m MockBazelContext) InvokeBazel(_ Config, ctx *Context) error {
+func (m MockBazelContext) InvokeBazel(_ Config, _ *Context) error {
 	panic("unimplemented")
 }
 
-func (m MockBazelContext) BazelAllowlisted(moduleName string) bool {
+func (m MockBazelContext) BazelAllowlisted(_ string) bool {
 	return true
 }
 
@@ -356,7 +355,7 @@
 	panic("implement me")
 }
 
-func (n noopBazelContext) InvokeBazel(_ Config, ctx *Context) error {
+func (n noopBazelContext) InvokeBazel(_ Config, _ *Context) error {
 	panic("unimplemented")
 }
 
@@ -364,7 +363,7 @@
 	return ""
 }
 
-func (n noopBazelContext) BazelAllowlisted(moduleName string) bool {
+func (n noopBazelContext) BazelAllowlisted(_ string) bool {
 	return false
 }
 
@@ -403,7 +402,7 @@
 		// Don't use partially-converted cc_library targets in mixed builds,
 		// since mixed builds would generally rely on both static and shared
 		// variants of a cc_library.
-		for staticOnlyModule, _ := range GetBp2BuildAllowList().ccLibraryStaticOnly {
+		for staticOnlyModule := range GetBp2BuildAllowList().ccLibraryStaticOnly {
 			disabledModules[staticOnlyModule] = true
 		}
 		for _, disabledDevModule := range allowlists.MixedBuildsDisabledList {
@@ -509,7 +508,7 @@
 	extraFlags []string
 }
 
-func (r *mockBazelRunner) createBazelCommand(paths *bazelPaths, runName bazel.RunName,
+func (r *mockBazelRunner) createBazelCommand(_ *bazelPaths, _ bazel.RunName,
 	command bazelCommand, extraFlags ...string) *exec.Cmd {
 	r.commands = append(r.commands, command)
 	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
@@ -534,13 +533,13 @@
 // Returns (stdout, stderr, error). The first and second return values are strings
 // containing the stdout and stderr of the run command, and an error is returned if
 // the invocation returned an error code.
-
 func (r *builtinBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd) (string, string, error) {
 	stderr := &bytes.Buffer{}
 	bazelCmd.Stderr = stderr
 	if output, err := bazelCmd.Output(); err != nil {
 		return "", string(stderr.Bytes()),
-			fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
+			fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
+				err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderr)
 	} else {
 		return string(output), string(stderr.Bytes()), nil
 	}
@@ -916,17 +915,17 @@
 			return err
 		}
 	}
-	if err := ioutil.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil {
 		return err
 	}
 	cqueryFileRelpath := filepath.Join(context.paths.injectedFilesDir(), "buildroot.cquery")
-	if err := ioutil.WriteFile(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil {
+	if err := os.WriteFile(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil {
 		return err
 	}
 
@@ -937,7 +936,7 @@
 		return cqueryErr
 	}
 	cqueryCommandPrint := fmt.Sprintf("cquery command line:\n  %s \n\n\n", printableCqueryCommand(cqueryCommandWithFlag))
-	if err := ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryCommandPrint+cqueryOutput), 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryCommandPrint+cqueryOutput), 0666); err != nil {
 		return err
 	}
 	cqueryResults := map[string]string{}
@@ -972,7 +971,7 @@
 		extraFlags = append(extraFlags, "--collect_code_coverage")
 		paths := make([]string, 0, 2)
 		if p := config.productVariables.NativeCoveragePaths; len(p) > 0 {
-			for i, _ := range p {
+			for i := range p {
 				// TODO(b/259404593) convert path wildcard to regex values
 				if p[i] == "*" {
 					p[i] = ".*"
@@ -1039,7 +1038,7 @@
 		filepath.Dir(ctx.Config().moduleListFile), "bazel.list"))
 	ctx.AddNinjaFileDeps(bazelBuildList)
 
-	data, err := ioutil.ReadFile(bazelBuildList)
+	data, err := os.ReadFile(bazelBuildList)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
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/testing.go b/android/testing.go
index 8fcf440..29af71f 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -203,6 +203,10 @@
 	ctx.PreArchMutators(f)
 }
 
+func (ctx *TestContext) ModuleProvider(m blueprint.Module, p blueprint.ProviderKey) interface{} {
+	return ctx.Context.ModuleProvider(m, p)
+}
+
 func (ctx *TestContext) PreDepsMutators(f RegisterMutatorFunc) {
 	ctx.preDeps = append(ctx.preDeps, f)
 }
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/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/androidmk.go b/cc/androidmk.go
index aaf21e9..ce35b5c 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -530,8 +530,10 @@
 
 	entries.SubName = ""
 
-	if c.sanitizerProperties.CfiEnabled {
+	if c.isSanitizerEnabled(cfi) {
 		entries.SubName += ".cfi"
+	} else if c.isSanitizerEnabled(Hwasan) {
+		entries.SubName += ".hwasan"
 	}
 
 	entries.SubName += c.baseProperties.Androidmk_suffix
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/fuzz.go b/cc/fuzz.go
index 64bb7dd..3da7651 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -301,7 +301,6 @@
 	baseInstallerPath := "fuzz"
 
 	binary.baseInstaller = NewBaseInstaller(baseInstallerPath, baseInstallerPath, InstallInData)
-	module.sanitize.SetSanitizer(Fuzzer, true)
 
 	fuzzBin := &fuzzBinary{
 		binaryDecorator: binary,
@@ -315,7 +314,11 @@
 
 	// The fuzzer runtime is not present for darwin host modules, disable cc_fuzz modules when targeting darwin.
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		disableDarwinAndLinuxBionic := struct {
+
+		extraProps := struct {
+			Sanitize struct {
+				Fuzzer *bool
+			}
 			Target struct {
 				Darwin struct {
 					Enabled *bool
@@ -325,9 +328,10 @@
 				}
 			}
 		}{}
-		disableDarwinAndLinuxBionic.Target.Darwin.Enabled = BoolPtr(false)
-		disableDarwinAndLinuxBionic.Target.Linux_bionic.Enabled = BoolPtr(false)
-		ctx.AppendProperties(&disableDarwinAndLinuxBionic)
+		extraProps.Sanitize.Fuzzer = BoolPtr(true)
+		extraProps.Target.Darwin.Enabled = BoolPtr(false)
+		extraProps.Target.Linux_bionic.Enabled = BoolPtr(false)
+		ctx.AppendProperties(&extraProps)
 
 		targetFramework := fuzz.GetFramework(ctx, fuzz.Cc)
 		if !fuzz.IsValidFrameworkForModule(targetFramework, fuzz.Cc, fuzzBin.fuzzPackagedModule.FuzzProperties.Fuzzing_frameworks) {
diff --git a/cc/genrule.go b/cc/genrule.go
index 4ef990c..d1c4c2a 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -48,6 +48,8 @@
 //
 //	CC_NATIVE_BRIDGE  the name of the subdirectory that native bridge libraries are stored in if
 //	                  the architecture has native bridge enabled, empty if it is disabled.
+//
+//	CC_OS             the name of the OS the command is being executed for.
 func GenRuleFactory() android.Module {
 	module := genrule.NewGenRule()
 
@@ -68,8 +70,9 @@
 func genruleCmdModifier(ctx android.ModuleContext, cmd string) string {
 	target := ctx.Target()
 	arch := target.Arch.ArchType
-	return fmt.Sprintf("CC_ARCH=%s CC_NATIVE_BRIDGE=%s CC_MULTILIB=%s && %s",
-		arch.Name, target.NativeBridgeRelativePath, arch.Multilib, cmd)
+	osName := target.Os.Name
+	return fmt.Sprintf("CC_ARCH=%s CC_NATIVE_BRIDGE=%s CC_MULTILIB=%s CC_OS=%s && %s",
+		arch.Name, target.NativeBridgeRelativePath, arch.Multilib, osName, cmd)
 }
 
 var _ android.ImageInterface = (*GenruleExtraProperties)(nil)
diff --git a/cc/library.go b/cc/library.go
index 365f392..d1d1945 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -448,11 +448,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)
@@ -2918,7 +2925,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 22e61a7..c61e2d1 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -172,7 +172,7 @@
 				// Copy LLDNK properties to cc_api_library module
 				d.libraryDecorator.flagExporter.Properties.Export_include_dirs = append(
 					d.libraryDecorator.flagExporter.Properties.Export_include_dirs,
-					variantMod.exportProperties.Export_headers...)
+					variantMod.exportProperties.Export_include_dirs...)
 
 				// Export headers as system include dirs if specified. Mostly for libc
 				if Bool(variantMod.exportProperties.Export_headers_as_system) {
@@ -203,7 +203,7 @@
 				// Copy NDK properties to cc_api_library module
 				d.libraryDecorator.flagExporter.Properties.Export_include_dirs = append(
 					d.libraryDecorator.flagExporter.Properties.Export_include_dirs,
-					variantMod.exportProperties.Export_headers...)
+					variantMod.exportProperties.Export_include_dirs...)
 			}
 		}
 	}
@@ -362,7 +362,7 @@
 
 type variantExporterProperties struct {
 	// Header directory to export
-	Export_headers []string `android:"arch_variant"`
+	Export_include_dirs []string `android:"arch_variant"`
 
 	// Export all headers as system include
 	Export_headers_as_system *bool
diff --git a/cc/library_stub_test.go b/cc/library_stub_test.go
index e372860..868447a 100644
--- a/cc/library_stub_test.go
+++ b/cc/library_stub_test.go
@@ -308,7 +308,7 @@
 			name: "libbar",
 			variant: "llndk",
 			src: "libbar_llndk.so",
-			export_headers: ["libbar_llndk_include"]
+			export_include_dirs: ["libbar_llndk_include"]
 		}
 
 		api_imports {
@@ -370,7 +370,7 @@
 			variant: "ndk",
 			version: "29",
 			src: "libbar_ndk_29.so",
-			export_headers: ["libbar_ndk_29_include"]
+			export_include_dirs: ["libbar_ndk_29_include"]
 		}
 
 		cc_api_variant {
@@ -378,7 +378,7 @@
 			variant: "ndk",
 			version: "30",
 			src: "libbar_ndk_30.so",
-			export_headers: ["libbar_ndk_30_include"]
+			export_include_dirs: ["libbar_ndk_30_include"]
 		}
 
 		cc_api_variant {
@@ -386,7 +386,7 @@
 			variant: "ndk",
 			version: "current",
 			src: "libbar_ndk_current.so",
-			export_headers: ["libbar_ndk_current_include"]
+			export_include_dirs: ["libbar_ndk_current_include"]
 		}
 
 		api_imports {
@@ -458,7 +458,7 @@
 			variant: "ndk",
 			version: "29",
 			src: "libbar_ndk_29.so",
-			export_headers: ["libbar_ndk_29_include"]
+			export_include_dirs: ["libbar_ndk_29_include"]
 		}
 
 		cc_api_variant {
@@ -466,7 +466,7 @@
 			variant: "ndk",
 			version: "30",
 			src: "libbar_ndk_30.so",
-			export_headers: ["libbar_ndk_30_include"]
+			export_include_dirs: ["libbar_ndk_30_include"]
 		}
 
 		cc_api_variant {
@@ -474,14 +474,14 @@
 			variant: "ndk",
 			version: "current",
 			src: "libbar_ndk_current.so",
-			export_headers: ["libbar_ndk_current_include"]
+			export_include_dirs: ["libbar_ndk_current_include"]
 		}
 
 		cc_api_variant {
 			name: "libbar",
 			variant: "llndk",
 			src: "libbar_llndk.so",
-			export_headers: ["libbar_llndk_include"]
+			export_include_dirs: ["libbar_llndk_include"]
 		}
 
 		api_imports {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 1842e5a..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 {
@@ -759,10 +763,10 @@
 	if sanitize == nil {
 		return nil
 	}
-	if Bool(sanitize.Properties.Sanitize.Address) && sanitized.Address.Srcs != nil {
+	if sanitize.isSanitizerEnabled(Asan) && sanitized.Address.Srcs != nil {
 		return sanitized.Address.Srcs
 	}
-	if Bool(sanitize.Properties.Sanitize.Hwaddress) && sanitized.Hwaddress.Srcs != nil {
+	if sanitize.isSanitizerEnabled(Hwasan) && sanitized.Hwaddress.Srcs != nil {
 		return sanitized.Hwaddress.Srcs
 	}
 	return sanitized.None.Srcs
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.go b/cc/sanitize.go
index d3fc221..eba709b 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -171,6 +171,20 @@
 	}
 }
 
+// shouldPropagateToSharedLibraryDeps returns whether a sanitizer type should propagate to share
+// dependencies. In most cases, sanitizers only propagate to static dependencies; however, some
+// sanitizers also must be enabled for shared libraries for linking.
+func (t SanitizerType) shouldPropagateToSharedLibraryDeps() bool {
+	switch t {
+	case Fuzzer:
+		// Typically, shared libs are not split. However, for fuzzer, we split even for shared libs
+		// because a library sanitized for fuzzer can't be linked from a library that isn't sanitized
+		// for fuzzer.
+		return true
+	default:
+		return false
+	}
+}
 func (*Module) SanitizerSupported(t SanitizerType) bool {
 	switch t {
 	case Asan:
@@ -286,15 +300,72 @@
 	Blocklist *string
 }
 
+type sanitizeMutatedProperties struct {
+	// Whether sanitizers can be enabled on this module
+	Never *bool `blueprint:"mutated"`
+
+	// Whether ASan (Address sanitizer) is enabled for this module.
+	// Hwaddress sanitizer takes precedence over this sanitizer.
+	Address *bool `blueprint:"mutated"`
+	// Whether TSan (Thread sanitizer) is enabled for this module
+	Thread *bool `blueprint:"mutated"`
+	// Whether HWASan (Hardware Address sanitizer) is enabled for this module
+	Hwaddress *bool `blueprint:"mutated"`
+
+	// Whether Undefined behavior sanitizer is enabled for this module
+	All_undefined *bool `blueprint:"mutated"`
+	// Whether undefined behavior sanitizer subset is enabled for this module
+	Undefined *bool `blueprint:"mutated"`
+	// List of specific undefined behavior sanitizers enabled for this module
+	Misc_undefined []string `blueprint:"mutated"`
+	// Whether Fuzzeris enabled for this module
+	Fuzzer *bool `blueprint:"mutated"`
+	// whether safe-stack sanitizer is enabled for this module
+	Safestack *bool `blueprint:"mutated"`
+	// Whether cfi sanitizer is enabled for this module
+	Cfi *bool `blueprint:"mutated"`
+	// Whether signed/unsigned integer overflow sanitizer is enabled for this module
+	Integer_overflow *bool `blueprint:"mutated"`
+	// Whether scudo sanitizer is enabled for this module
+	Scudo *bool `blueprint:"mutated"`
+	// Whether shadow-call-stack sanitizer is enabled for this module.
+	Scs *bool `blueprint:"mutated"`
+	// Whether Memory-tagging is enabled for this module
+	Memtag_heap *bool `blueprint:"mutated"`
+	// Whether Memory-tagging stack instrumentation is enabled for this module
+	Memtag_stack *bool `blueprint:"mutated"`
+
+	// Whether a modifier for ASAN and HWASAN for write only instrumentation is enabled for this
+	// module
+	Writeonly *bool `blueprint:"mutated"`
+
+	// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
+	Diag struct {
+		// Whether Undefined behavior sanitizer, diagnostic mode is enabled for this module
+		Undefined *bool `blueprint:"mutated"`
+		// Whether cfi sanitizer, diagnostic mode is enabled for this module
+		Cfi *bool `blueprint:"mutated"`
+		// Whether signed/unsigned integer overflow sanitizer, diagnostic mode is enabled for this
+		// module
+		Integer_overflow *bool `blueprint:"mutated"`
+		// Whether Memory-tagging, diagnostic mode is enabled for this module
+		Memtag_heap *bool `blueprint:"mutated"`
+		// List of specific undefined behavior sanitizers enabled in diagnostic mode
+		Misc_undefined []string `blueprint:"mutated"`
+	} `blueprint:"mutated"`
+}
+
 type SanitizeProperties struct {
-	Sanitize          SanitizeUserProps `android:"arch_variant"`
-	SanitizerEnabled  bool              `blueprint:"mutated"`
-	MinimalRuntimeDep bool              `blueprint:"mutated"`
-	BuiltinsDep       bool              `blueprint:"mutated"`
-	UbsanRuntimeDep   bool              `blueprint:"mutated"`
-	InSanitizerDir    bool              `blueprint:"mutated"`
-	Sanitizers        []string          `blueprint:"mutated"`
-	DiagSanitizers    []string          `blueprint:"mutated"`
+	Sanitize        SanitizeUserProps         `android:"arch_variant"`
+	SanitizeMutated sanitizeMutatedProperties `blueprint:"mutated"`
+
+	SanitizerEnabled  bool     `blueprint:"mutated"`
+	MinimalRuntimeDep bool     `blueprint:"mutated"`
+	BuiltinsDep       bool     `blueprint:"mutated"`
+	UbsanRuntimeDep   bool     `blueprint:"mutated"`
+	InSanitizerDir    bool     `blueprint:"mutated"`
+	Sanitizers        []string `blueprint:"mutated"`
+	DiagSanitizers    []string `blueprint:"mutated"`
 }
 
 type sanitize struct {
@@ -317,8 +388,42 @@
 	return []interface{}{&sanitize.Properties}
 }
 
+func (p *sanitizeMutatedProperties) copyUserPropertiesToMutated(userProps *SanitizeUserProps) {
+	p.Never = userProps.Never
+	p.Address = userProps.Address
+	p.All_undefined = userProps.All_undefined
+	p.Cfi = userProps.Cfi
+	p.Fuzzer = userProps.Fuzzer
+	p.Hwaddress = userProps.Hwaddress
+	p.Integer_overflow = userProps.Integer_overflow
+	p.Memtag_heap = userProps.Memtag_heap
+	p.Memtag_stack = userProps.Memtag_stack
+	p.Safestack = userProps.Safestack
+	p.Scs = userProps.Scs
+	p.Scudo = userProps.Scudo
+	p.Thread = userProps.Thread
+	p.Undefined = userProps.Undefined
+	p.Writeonly = userProps.Writeonly
+
+	p.Misc_undefined = make([]string, 0, len(userProps.Misc_undefined))
+	for _, v := range userProps.Misc_undefined {
+		p.Misc_undefined = append(p.Misc_undefined, v)
+	}
+
+	p.Diag.Cfi = userProps.Diag.Cfi
+	p.Diag.Integer_overflow = userProps.Diag.Integer_overflow
+	p.Diag.Memtag_heap = userProps.Diag.Memtag_heap
+	p.Diag.Undefined = userProps.Diag.Undefined
+
+	p.Diag.Misc_undefined = make([]string, 0, len(userProps.Diag.Misc_undefined))
+	for _, v := range userProps.Diag.Misc_undefined {
+		p.Diag.Misc_undefined = append(p.Diag.Misc_undefined, v)
+	}
+}
+
 func (sanitize *sanitize) begin(ctx BaseModuleContext) {
-	s := &sanitize.Properties.Sanitize
+	s := &sanitize.Properties.SanitizeMutated
+	s.copyUserPropertiesToMutated(&sanitize.Properties.Sanitize)
 
 	// Don't apply sanitizers to NDK code.
 	if ctx.useSdk() {
@@ -614,20 +719,13 @@
 	return false
 }
 
-func (sanitize *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
-	if !sanitize.Properties.SanitizerEnabled && !sanitize.Properties.UbsanRuntimeDep {
+func (s *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
+	if !s.Properties.SanitizerEnabled && !s.Properties.UbsanRuntimeDep {
 		return flags
 	}
+	sanProps := &s.Properties.SanitizeMutated
 
-	// Currently unwinding through tagged frames for exceptions is broken, so disable memtag stack
-	// in that case, so we don't end up tagging those.
-	// TODO(b/174878242): Remove once https://r.android.com/2251926 is included in toolchain.
-	if android.InList("-fexceptions", flags.Local.CFlags) || android.InList("-fexceptions", flags.Global.CFlags) {
-		sanitize.Properties.Sanitize.Memtag_stack = nil
-		_, sanitize.Properties.Sanitizers = android.RemoveFromList("memtag-stack", sanitize.Properties.Sanitizers)
-	}
-
-	if Bool(sanitize.Properties.Sanitize.Address) {
+	if Bool(sanProps.Address) {
 		if ctx.Arch().ArchType == android.Arm {
 			// Frame pointer based unwinder in ASan requires ARM frame setup.
 			// TODO: put in flags?
@@ -636,7 +734,7 @@
 		flags.Local.CFlags = append(flags.Local.CFlags, asanCflags...)
 		flags.Local.LdFlags = append(flags.Local.LdFlags, asanLdflags...)
 
-		if Bool(sanitize.Properties.Sanitize.Writeonly) {
+		if Bool(sanProps.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-instrument-reads=0")
 		}
 
@@ -657,7 +755,7 @@
 		}
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
+	if Bool(sanProps.Hwaddress) {
 		flags.Local.CFlags = append(flags.Local.CFlags, hwasanCflags...)
 
 		for _, flag := range hwasanCommonflags {
@@ -667,12 +765,12 @@
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
 		}
 
-		if Bool(sanitize.Properties.Sanitize.Writeonly) {
+		if Bool(sanProps.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-hwasan-instrument-reads=0")
 		}
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Fuzzer) {
+	if Bool(sanProps.Fuzzer) {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize=fuzzer-no-link")
 
 		// TODO(b/131771163): LTO and Fuzzer support is mutually incompatible.
@@ -701,7 +799,7 @@
 		flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN`)
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Cfi) {
+	if Bool(sanProps.Cfi) {
 		if ctx.Arch().ArchType == android.Arm {
 			// __cfi_check needs to be built as Thumb (see the code in linker_cfi.cpp). LLVM is not set up
 			// to do this on a function basis, so force Thumb on the entire module.
@@ -710,7 +808,7 @@
 
 		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
-		if Bool(sanitize.Properties.Sanitize.Config.Cfi_assembly_support) {
+		if Bool(s.Properties.Sanitize.Config.Cfi_assembly_support) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-cfi-canonical-jump-tables")
 		}
 		// Only append the default visibility flag if -fvisibility has not already been set
@@ -726,7 +824,7 @@
 		}
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Memtag_stack) {
+	if Bool(sanProps.Memtag_stack) {
 		flags.Local.CFlags = append(flags.Local.CFlags, memtagStackCommonFlags...)
 		// TODO(fmayer): remove -Wno-error once https://reviews.llvm.org/D127917 is in Android toolchain.
 		flags.Local.CFlags = append(flags.Local.CFlags, "-Wno-error=frame-larger-than")
@@ -737,20 +835,20 @@
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-fatal-warnings")
 	}
 
-	if (Bool(sanitize.Properties.Sanitize.Memtag_heap) || Bool(sanitize.Properties.Sanitize.Memtag_stack)) && ctx.binary() {
-		if Bool(sanitize.Properties.Sanitize.Diag.Memtag_heap) {
+	if (Bool(sanProps.Memtag_heap) || Bool(sanProps.Memtag_stack)) && ctx.binary() {
+		if Bool(sanProps.Diag.Memtag_heap) {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fsanitize-memtag-mode=sync")
 		} else {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fsanitize-memtag-mode=async")
 		}
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Integer_overflow) {
+	if Bool(sanProps.Integer_overflow) {
 		flags.Local.CFlags = append(flags.Local.CFlags, intOverflowCflags...)
 	}
 
-	if len(sanitize.Properties.Sanitizers) > 0 {
-		sanitizeArg := "-fsanitize=" + strings.Join(sanitize.Properties.Sanitizers, ",")
+	if len(s.Properties.Sanitizers) > 0 {
+		sanitizeArg := "-fsanitize=" + strings.Join(s.Properties.Sanitizers, ",")
 		flags.Local.CFlags = append(flags.Local.CFlags, sanitizeArg)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, sanitizeArg)
 		flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)
@@ -774,7 +872,7 @@
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=vptr,function")
 		}
 
-		if Bool(sanitize.Properties.Sanitize.Fuzzer) {
+		if Bool(sanProps.Fuzzer) {
 			// When fuzzing, we wish to crash with diagnostics on any bug.
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap=all", "-fno-sanitize-recover=all")
 		} else if ctx.Host() {
@@ -783,7 +881,7 @@
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-trap=all", "-ftrap-function=abort")
 		}
 
-		if enableMinimalRuntime(sanitize) {
+		if enableMinimalRuntime(s) {
 			flags.Local.CFlags = append(flags.Local.CFlags, strings.Join(minimalRuntimeFlags, " "))
 		}
 
@@ -797,22 +895,22 @@
 		}
 	}
 
-	if len(sanitize.Properties.DiagSanitizers) > 0 {
-		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap="+strings.Join(sanitize.Properties.DiagSanitizers, ","))
+	if len(s.Properties.DiagSanitizers) > 0 {
+		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap="+strings.Join(s.Properties.DiagSanitizers, ","))
 	}
 	// FIXME: enable RTTI if diag + (cfi or vptr)
 
-	if sanitize.Properties.Sanitize.Recover != nil {
+	if s.Properties.Sanitize.Recover != nil {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-recover="+
-			strings.Join(sanitize.Properties.Sanitize.Recover, ","))
+			strings.Join(s.Properties.Sanitize.Recover, ","))
 	}
 
-	if sanitize.Properties.Sanitize.Diag.No_recover != nil {
+	if s.Properties.Sanitize.Diag.No_recover != nil {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-recover="+
-			strings.Join(sanitize.Properties.Sanitize.Diag.No_recover, ","))
+			strings.Join(s.Properties.Sanitize.Diag.No_recover, ","))
 	}
 
-	blocklist := android.OptionalPathForModuleSrc(ctx, sanitize.Properties.Sanitize.Blocklist)
+	blocklist := android.OptionalPathForModuleSrc(ctx, s.Properties.Sanitize.Blocklist)
 	if blocklist.Valid() {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-ignorelist="+blocklist.String())
 		flags.CFlagsDeps = append(flags.CFlagsDeps, blocklist.Path())
@@ -821,47 +919,47 @@
 	return flags
 }
 
-func (sanitize *sanitize) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (s *sanitize) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	// Add a suffix for cfi/hwasan/scs-enabled static/header libraries to allow surfacing
 	// both the sanitized and non-sanitized variants to make without a name conflict.
 	if entries.Class == "STATIC_LIBRARIES" || entries.Class == "HEADER_LIBRARIES" {
-		if Bool(sanitize.Properties.Sanitize.Cfi) {
+		if Bool(s.Properties.SanitizeMutated.Cfi) {
 			entries.SubName += ".cfi"
 		}
-		if Bool(sanitize.Properties.Sanitize.Hwaddress) {
+		if Bool(s.Properties.SanitizeMutated.Hwaddress) {
 			entries.SubName += ".hwasan"
 		}
-		if Bool(sanitize.Properties.Sanitize.Scs) {
+		if Bool(s.Properties.SanitizeMutated.Scs) {
 			entries.SubName += ".scs"
 		}
 	}
 }
 
-func (sanitize *sanitize) inSanitizerDir() bool {
-	return sanitize.Properties.InSanitizerDir
+func (s *sanitize) inSanitizerDir() bool {
+	return s.Properties.InSanitizerDir
 }
 
 // getSanitizerBoolPtr returns the SanitizerTypes associated bool pointer from SanitizeProperties.
-func (sanitize *sanitize) getSanitizerBoolPtr(t SanitizerType) *bool {
+func (s *sanitize) getSanitizerBoolPtr(t SanitizerType) *bool {
 	switch t {
 	case Asan:
-		return sanitize.Properties.Sanitize.Address
+		return s.Properties.SanitizeMutated.Address
 	case Hwasan:
-		return sanitize.Properties.Sanitize.Hwaddress
+		return s.Properties.SanitizeMutated.Hwaddress
 	case tsan:
-		return sanitize.Properties.Sanitize.Thread
+		return s.Properties.SanitizeMutated.Thread
 	case intOverflow:
-		return sanitize.Properties.Sanitize.Integer_overflow
+		return s.Properties.SanitizeMutated.Integer_overflow
 	case cfi:
-		return sanitize.Properties.Sanitize.Cfi
+		return s.Properties.SanitizeMutated.Cfi
 	case scs:
-		return sanitize.Properties.Sanitize.Scs
+		return s.Properties.SanitizeMutated.Scs
 	case Memtag_heap:
-		return sanitize.Properties.Sanitize.Memtag_heap
+		return s.Properties.SanitizeMutated.Memtag_heap
 	case Memtag_stack:
-		return sanitize.Properties.Sanitize.Memtag_stack
+		return s.Properties.SanitizeMutated.Memtag_stack
 	case Fuzzer:
-		return sanitize.Properties.Sanitize.Fuzzer
+		return s.Properties.SanitizeMutated.Fuzzer
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
@@ -894,28 +992,28 @@
 	}
 	switch t {
 	case Asan:
-		sanitize.Properties.Sanitize.Address = bPtr
+		sanitize.Properties.SanitizeMutated.Address = bPtr
 		// For ASAN variant, we need to disable Memtag_stack
-		sanitize.Properties.Sanitize.Memtag_stack = nil
+		sanitize.Properties.SanitizeMutated.Memtag_stack = nil
 	case Hwasan:
-		sanitize.Properties.Sanitize.Hwaddress = bPtr
+		sanitize.Properties.SanitizeMutated.Hwaddress = bPtr
 		// For HWAsan variant, we need to disable Memtag_stack
-		sanitize.Properties.Sanitize.Memtag_stack = nil
+		sanitize.Properties.SanitizeMutated.Memtag_stack = nil
 	case tsan:
-		sanitize.Properties.Sanitize.Thread = bPtr
+		sanitize.Properties.SanitizeMutated.Thread = bPtr
 	case intOverflow:
-		sanitize.Properties.Sanitize.Integer_overflow = bPtr
+		sanitize.Properties.SanitizeMutated.Integer_overflow = bPtr
 	case cfi:
-		sanitize.Properties.Sanitize.Cfi = bPtr
+		sanitize.Properties.SanitizeMutated.Cfi = bPtr
 	case scs:
-		sanitize.Properties.Sanitize.Scs = bPtr
+		sanitize.Properties.SanitizeMutated.Scs = bPtr
 	case Memtag_heap:
-		sanitize.Properties.Sanitize.Memtag_heap = bPtr
+		sanitize.Properties.SanitizeMutated.Memtag_heap = bPtr
 	case Memtag_stack:
-		sanitize.Properties.Sanitize.Memtag_stack = bPtr
+		sanitize.Properties.SanitizeMutated.Memtag_stack = bPtr
 		// We do not need to disable ASAN or HWASan here, as there is no Memtag_stack variant.
 	case Fuzzer:
-		sanitize.Properties.Sanitize.Fuzzer = bPtr
+		sanitize.Properties.SanitizeMutated.Fuzzer = bPtr
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
@@ -1065,7 +1163,7 @@
 		//TODO: When Rust modules have vendor support, enable this path for PlatformSanitizeable
 
 		// Check if it's a snapshot module supporting sanitizer
-		if ss, ok := c.linker.(snapshotSanitizer); ok && ss.isSanitizerEnabled(s.sanitizer) {
+		if ss, ok := c.linker.(snapshotSanitizer); ok && ss.isSanitizerAvailable(s.sanitizer) {
 			return []string{"", s.sanitizer.variationName()}
 		} else {
 			return []string{""}
@@ -1097,7 +1195,7 @@
 func (s *sanitizerSplitMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
 	if d, ok := ctx.Module().(PlatformSanitizeable); ok {
 		if dm, ok := ctx.Module().(*Module); ok {
-			if ss, ok := dm.linker.(snapshotSanitizer); ok && ss.isSanitizerEnabled(s.sanitizer) {
+			if ss, ok := dm.linker.(snapshotSanitizer); ok && ss.isSanitizerAvailable(s.sanitizer) {
 				return incomingVariation
 			}
 		}
@@ -1127,7 +1225,8 @@
 				return s.sanitizer.variationName()
 			}
 
-			if s.sanitizer == cfi || s.sanitizer == Hwasan || s.sanitizer == scs || s.sanitizer == Asan {
+			// Some sanitizers do not propagate to shared dependencies
+			if !s.sanitizer.shouldPropagateToSharedLibraryDeps() {
 				return ""
 			}
 		}
@@ -1212,14 +1311,23 @@
 			sanitizeable.AddSanitizerDependencies(mctx, s.sanitizer.name())
 		}
 	} else if c, ok := mctx.Module().(*Module); ok {
-		if ss, ok := c.linker.(snapshotSanitizer); ok && ss.isSanitizerEnabled(s.sanitizer) {
+		if ss, ok := c.linker.(snapshotSanitizer); ok && ss.isSanitizerAvailable(s.sanitizer) {
+			if !ss.isUnsanitizedVariant() {
+				// Snapshot sanitizer may have only one variantion.
+				// Skip exporting the module if it already has a sanitizer variation.
+				c.SetPreventInstall()
+				c.SetHideFromMake()
+				return
+			}
 			c.linker.(snapshotSanitizer).setSanitizerVariation(s.sanitizer, sanitizerVariation)
 
 			// Export the static lib name to make
 			if c.static() && c.ExportedToMake() {
+				// use BaseModuleName which is the name for Make.
 				if s.sanitizer == cfi {
-					// use BaseModuleName which is the name for Make.
 					cfiStaticLibs(mctx.Config()).add(c, c.BaseModuleName())
+				} else if s.sanitizer == Hwasan {
+					hwasanStaticLibs(mctx.Config()).add(c, c.BaseModuleName())
 				}
 			}
 		}
@@ -1227,7 +1335,7 @@
 }
 
 func (c *Module) SanitizeNever() bool {
-	return Bool(c.sanitize.Properties.Sanitize.Never)
+	return Bool(c.sanitize.Properties.SanitizeMutated.Never)
 }
 
 func (c *Module) IsSanitizerExplicitlyDisabled(t SanitizerType) bool {
@@ -1295,10 +1403,12 @@
 		var sanitizers []string
 		var diagSanitizers []string
 
-		if Bool(c.sanitize.Properties.Sanitize.All_undefined) {
+		sanProps := &c.sanitize.Properties.SanitizeMutated
+
+		if Bool(sanProps.All_undefined) {
 			sanitizers = append(sanitizers, "undefined")
 		} else {
-			if Bool(c.sanitize.Properties.Sanitize.Undefined) {
+			if Bool(sanProps.Undefined) {
 				sanitizers = append(sanitizers,
 					"bool",
 					"integer-divide-by-zero",
@@ -1323,66 +1433,66 @@
 					// "object-size",
 				)
 			}
-			sanitizers = append(sanitizers, c.sanitize.Properties.Sanitize.Misc_undefined...)
+			sanitizers = append(sanitizers, sanProps.Misc_undefined...)
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Diag.Undefined) {
+		if Bool(sanProps.Diag.Undefined) {
 			diagSanitizers = append(diagSanitizers, "undefined")
 		}
 
-		diagSanitizers = append(diagSanitizers, c.sanitize.Properties.Sanitize.Diag.Misc_undefined...)
+		diagSanitizers = append(diagSanitizers, sanProps.Diag.Misc_undefined...)
 
-		if Bool(c.sanitize.Properties.Sanitize.Address) {
+		if Bool(sanProps.Address) {
 			sanitizers = append(sanitizers, "address")
 			diagSanitizers = append(diagSanitizers, "address")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Hwaddress) {
+		if Bool(sanProps.Hwaddress) {
 			sanitizers = append(sanitizers, "hwaddress")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Thread) {
+		if Bool(sanProps.Thread) {
 			sanitizers = append(sanitizers, "thread")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Safestack) {
+		if Bool(sanProps.Safestack) {
 			sanitizers = append(sanitizers, "safe-stack")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Cfi) {
+		if Bool(sanProps.Cfi) {
 			sanitizers = append(sanitizers, "cfi")
 
-			if Bool(c.sanitize.Properties.Sanitize.Diag.Cfi) {
+			if Bool(sanProps.Diag.Cfi) {
 				diagSanitizers = append(diagSanitizers, "cfi")
 			}
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Integer_overflow) {
+		if Bool(sanProps.Integer_overflow) {
 			sanitizers = append(sanitizers, "unsigned-integer-overflow")
 			sanitizers = append(sanitizers, "signed-integer-overflow")
-			if Bool(c.sanitize.Properties.Sanitize.Diag.Integer_overflow) {
+			if Bool(sanProps.Diag.Integer_overflow) {
 				diagSanitizers = append(diagSanitizers, "unsigned-integer-overflow")
 				diagSanitizers = append(diagSanitizers, "signed-integer-overflow")
 			}
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Scudo) {
+		if Bool(sanProps.Scudo) {
 			sanitizers = append(sanitizers, "scudo")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Scs) {
+		if Bool(sanProps.Scs) {
 			sanitizers = append(sanitizers, "shadow-call-stack")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Memtag_heap) && c.Binary() {
+		if Bool(sanProps.Memtag_heap) && c.Binary() {
 			sanitizers = append(sanitizers, "memtag-heap")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Memtag_stack) {
+		if Bool(sanProps.Memtag_stack) {
 			sanitizers = append(sanitizers, "memtag-stack")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Fuzzer) {
+		if Bool(sanProps.Fuzzer) {
 			sanitizers = append(sanitizers, "fuzzer-no-link")
 		}
 
@@ -1401,27 +1511,27 @@
 		alwaysStaticRuntime := false
 		var extraStaticDeps []string
 		toolchain := c.toolchain(mctx)
-		if Bool(c.sanitize.Properties.Sanitize.Address) {
+		if Bool(sanProps.Address) {
 			runtimeLibrary = config.AddressSanitizerRuntimeLibrary(toolchain)
-		} else if Bool(c.sanitize.Properties.Sanitize.Hwaddress) {
+		} else if Bool(sanProps.Hwaddress) {
 			if c.staticBinary() {
 				runtimeLibrary = config.HWAddressSanitizerStaticLibrary(toolchain)
 				extraStaticDeps = []string{"libdl"}
 			} else {
 				runtimeLibrary = config.HWAddressSanitizerRuntimeLibrary(toolchain)
 			}
-		} else if Bool(c.sanitize.Properties.Sanitize.Thread) {
+		} else if Bool(sanProps.Thread) {
 			runtimeLibrary = config.ThreadSanitizerRuntimeLibrary(toolchain)
-		} else if Bool(c.sanitize.Properties.Sanitize.Scudo) {
+		} else if Bool(sanProps.Scudo) {
 			if len(diagSanitizers) == 0 && !c.sanitize.Properties.UbsanRuntimeDep {
 				runtimeLibrary = config.ScudoMinimalRuntimeLibrary(toolchain)
 			} else {
 				runtimeLibrary = config.ScudoRuntimeLibrary(toolchain)
 			}
 		} else if len(diagSanitizers) > 0 || c.sanitize.Properties.UbsanRuntimeDep ||
-			Bool(c.sanitize.Properties.Sanitize.Fuzzer) ||
-			Bool(c.sanitize.Properties.Sanitize.Undefined) ||
-			Bool(c.sanitize.Properties.Sanitize.All_undefined) {
+			Bool(sanProps.Fuzzer) ||
+			Bool(sanProps.Undefined) ||
+			Bool(sanProps.All_undefined) {
 			runtimeLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain)
 			if c.staticBinary() || toolchain.Musl() {
 				// Use a static runtime for static binaries.
@@ -1632,21 +1742,27 @@
 }
 
 func enableMinimalRuntime(sanitize *sanitize) bool {
-	if !Bool(sanitize.Properties.Sanitize.Address) &&
-		!Bool(sanitize.Properties.Sanitize.Hwaddress) &&
-		!Bool(sanitize.Properties.Sanitize.Fuzzer) &&
-		(Bool(sanitize.Properties.Sanitize.Integer_overflow) ||
-			len(sanitize.Properties.Sanitize.Misc_undefined) > 0 ||
-			Bool(sanitize.Properties.Sanitize.Undefined) ||
-			Bool(sanitize.Properties.Sanitize.All_undefined)) &&
-		!(Bool(sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
-			Bool(sanitize.Properties.Sanitize.Diag.Cfi) ||
-			Bool(sanitize.Properties.Sanitize.Diag.Undefined) ||
-			len(sanitize.Properties.Sanitize.Diag.Misc_undefined) > 0) {
-
-		return true
+	if sanitize.isSanitizerEnabled(Asan) {
+		return false
+	} else if sanitize.isSanitizerEnabled(Hwasan) {
+		return false
+	} else if sanitize.isSanitizerEnabled(Fuzzer) {
+		return false
 	}
-	return false
+
+	if enableUbsanRuntime(sanitize) {
+		return false
+	}
+
+	sanitizeProps := &sanitize.Properties.SanitizeMutated
+	if Bool(sanitizeProps.Diag.Cfi) {
+		return false
+	}
+
+	return Bool(sanitizeProps.Integer_overflow) ||
+		len(sanitizeProps.Misc_undefined) > 0 ||
+		Bool(sanitizeProps.Undefined) ||
+		Bool(sanitizeProps.All_undefined)
 }
 
 func (m *Module) UbsanRuntimeNeeded() bool {
@@ -1658,9 +1774,10 @@
 }
 
 func enableUbsanRuntime(sanitize *sanitize) bool {
-	return Bool(sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
-		Bool(sanitize.Properties.Sanitize.Diag.Undefined) ||
-		len(sanitize.Properties.Sanitize.Diag.Misc_undefined) > 0
+	sanitizeProps := &sanitize.Properties.SanitizeMutated
+	return Bool(sanitizeProps.Diag.Integer_overflow) ||
+		Bool(sanitizeProps.Diag.Undefined) ||
+		len(sanitizeProps.Diag.Misc_undefined) > 0
 }
 
 func cfiMakeVarsProvider(ctx android.MakeVarsContext) {
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index 2393f3e..580adfa 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -21,6 +21,8 @@
 	"testing"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint"
 )
 
 var prepareForAsanTest = android.FixtureAddFile("asan/Android.bp", []byte(`
@@ -29,6 +31,60 @@
 	}
 `))
 
+var prepareForTsanTest = android.FixtureAddFile("tsan/Android.bp", []byte(`
+	cc_library_shared {
+		name: "libclang_rt.tsan",
+	}
+`))
+
+type providerInterface interface {
+	ModuleProvider(blueprint.Module, blueprint.ProviderKey) interface{}
+}
+
+// expectSharedLinkDep verifies that the from module links against the to module as a
+// shared library.
+func expectSharedLinkDep(t *testing.T, ctx providerInterface, from, to android.TestingModule) {
+	t.Helper()
+	fromLink := from.Description("link")
+	toInfo := ctx.ModuleProvider(to.Module(), SharedLibraryInfoProvider).(SharedLibraryInfo)
+
+	if g, w := fromLink.OrderOnly.Strings(), toInfo.SharedLibrary.RelativeToTop().String(); !android.InList(w, g) {
+		t.Errorf("%s should link against %s, expected %q, got %q",
+			from.Module(), to.Module(), w, g)
+	}
+}
+
+// expectStaticLinkDep verifies that the from module links against the to module as a
+// static library.
+func expectStaticLinkDep(t *testing.T, ctx providerInterface, from, to android.TestingModule) {
+	t.Helper()
+	fromLink := from.Description("link")
+	toInfo := ctx.ModuleProvider(to.Module(), StaticLibraryInfoProvider).(StaticLibraryInfo)
+
+	if g, w := fromLink.Implicits.Strings(), toInfo.StaticLibrary.RelativeToTop().String(); !android.InList(w, g) {
+		t.Errorf("%s should link against %s, expected %q, got %q",
+			from.Module(), to.Module(), w, g)
+	}
+
+}
+
+// expectInstallDep verifies that the install rule of the from module depends on the
+// install rule of the to module.
+func expectInstallDep(t *testing.T, from, to android.TestingModule) {
+	t.Helper()
+	fromInstalled := from.Description("install")
+	toInstalled := to.Description("install")
+
+	// combine implicits and order-only dependencies, host uses implicit but device uses
+	// order-only.
+	got := append(fromInstalled.Implicits.Strings(), fromInstalled.OrderOnly.Strings()...)
+	want := toInstalled.Output.String()
+	if !android.InList(want, got) {
+		t.Errorf("%s installation should depend on %s, expected %q, got %q",
+			from.Module(), to.Module(), want, got)
+	}
+}
+
 func TestAsan(t *testing.T) {
 	bp := `
 		cc_binary {
@@ -111,6 +167,7 @@
 	).RunTestWithBp(t, bp)
 
 	check := func(t *testing.T, result *android.TestResult, variant string) {
+		ctx := result.TestContext
 		asanVariant := variant + "_asan"
 		sharedVariant := variant + "_shared"
 		sharedAsanVariant := sharedVariant + "_asan"
@@ -140,85 +197,355 @@
 		libStaticAsan := result.ModuleForTests("libstatic_asan", staticAsanVariant)
 		libStaticAsanNoAsanVariant := result.ModuleForTests("libstatic_asan", staticVariant)
 
-		// expectSharedLinkDep verifies that the from module links against the to module as a
-		// shared library.
-		expectSharedLinkDep := func(from, to android.TestingModule) {
-			t.Helper()
-			fromLink := from.Description("link")
-			toLink := to.Description("strip")
+		expectSharedLinkDep(t, ctx, binWithAsan, libShared)
+		expectSharedLinkDep(t, ctx, binWithAsan, libAsan)
+		expectSharedLinkDep(t, ctx, libShared, libTransitive)
+		expectSharedLinkDep(t, ctx, libAsan, libTransitive)
 
-			if g, w := fromLink.OrderOnly.Strings(), toLink.Output.String(); !android.InList(w, g) {
-				t.Errorf("%s should link against %s, expected %q, got %q",
-					from.Module(), to.Module(), w, g)
-			}
-		}
+		expectStaticLinkDep(t, ctx, binWithAsan, libStaticAsanVariant)
+		expectStaticLinkDep(t, ctx, binWithAsan, libNoAsan)
+		expectStaticLinkDep(t, ctx, binWithAsan, libStaticAsan)
 
-		// expectStaticLinkDep verifies that the from module links against the to module as a
-		// static library.
-		expectStaticLinkDep := func(from, to android.TestingModule) {
-			t.Helper()
-			fromLink := from.Description("link")
-			toLink := to.Description("static link")
+		expectInstallDep(t, binWithAsan, libShared)
+		expectInstallDep(t, binWithAsan, libAsan)
+		expectInstallDep(t, binWithAsan, libTransitive)
+		expectInstallDep(t, libShared, libTransitive)
+		expectInstallDep(t, libAsan, libTransitive)
 
-			if g, w := fromLink.Implicits.Strings(), toLink.Output.String(); !android.InList(w, g) {
-				t.Errorf("%s should link against %s, expected %q, got %q",
-					from.Module(), to.Module(), w, g)
-			}
+		expectSharedLinkDep(t, ctx, binNoAsan, libShared)
+		expectSharedLinkDep(t, ctx, binNoAsan, libAsan)
+		expectSharedLinkDep(t, ctx, libShared, libTransitive)
+		expectSharedLinkDep(t, ctx, libAsan, libTransitive)
 
-		}
+		expectStaticLinkDep(t, ctx, binNoAsan, libStaticNoAsanVariant)
+		expectStaticLinkDep(t, ctx, binNoAsan, libNoAsan)
+		expectStaticLinkDep(t, ctx, binNoAsan, libStaticAsanNoAsanVariant)
 
-		// expectInstallDep verifies that the install rule of the from module depends on the
-		// install rule of the to module.
-		expectInstallDep := func(from, to android.TestingModule) {
-			t.Helper()
-			fromInstalled := from.Description("install")
-			toInstalled := to.Description("install")
-
-			// combine implicits and order-only dependencies, host uses implicit but device uses
-			// order-only.
-			got := append(fromInstalled.Implicits.Strings(), fromInstalled.OrderOnly.Strings()...)
-			want := toInstalled.Output.String()
-			if !android.InList(want, got) {
-				t.Errorf("%s installation should depend on %s, expected %q, got %q",
-					from.Module(), to.Module(), want, got)
-			}
-		}
-
-		expectSharedLinkDep(binWithAsan, libShared)
-		expectSharedLinkDep(binWithAsan, libAsan)
-		expectSharedLinkDep(libShared, libTransitive)
-		expectSharedLinkDep(libAsan, libTransitive)
-
-		expectStaticLinkDep(binWithAsan, libStaticAsanVariant)
-		expectStaticLinkDep(binWithAsan, libNoAsan)
-		expectStaticLinkDep(binWithAsan, libStaticAsan)
-
-		expectInstallDep(binWithAsan, libShared)
-		expectInstallDep(binWithAsan, libAsan)
-		expectInstallDep(binWithAsan, libTransitive)
-		expectInstallDep(libShared, libTransitive)
-		expectInstallDep(libAsan, libTransitive)
-
-		expectSharedLinkDep(binNoAsan, libShared)
-		expectSharedLinkDep(binNoAsan, libAsan)
-		expectSharedLinkDep(libShared, libTransitive)
-		expectSharedLinkDep(libAsan, libTransitive)
-
-		expectStaticLinkDep(binNoAsan, libStaticNoAsanVariant)
-		expectStaticLinkDep(binNoAsan, libNoAsan)
-		expectStaticLinkDep(binNoAsan, libStaticAsanNoAsanVariant)
-
-		expectInstallDep(binNoAsan, libShared)
-		expectInstallDep(binNoAsan, libAsan)
-		expectInstallDep(binNoAsan, libTransitive)
-		expectInstallDep(libShared, libTransitive)
-		expectInstallDep(libAsan, libTransitive)
+		expectInstallDep(t, binNoAsan, libShared)
+		expectInstallDep(t, binNoAsan, libAsan)
+		expectInstallDep(t, binNoAsan, libTransitive)
+		expectInstallDep(t, libShared, libTransitive)
+		expectInstallDep(t, libAsan, libTransitive)
 	}
 
 	t.Run("host", func(t *testing.T) { check(t, result, result.Config.BuildOSTarget.String()) })
 	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
 }
 
+func TestTsan(t *testing.T) {
+	bp := `
+	cc_binary {
+		name: "bin_with_tsan",
+		host_supported: true,
+		shared_libs: [
+			"libshared",
+			"libtsan",
+		],
+		sanitize: {
+			thread: true,
+		}
+	}
+
+	cc_binary {
+		name: "bin_no_tsan",
+		host_supported: true,
+		shared_libs: [
+			"libshared",
+			"libtsan",
+		],
+	}
+
+	cc_library_shared {
+		name: "libshared",
+		host_supported: true,
+		shared_libs: ["libtransitive"],
+	}
+
+	cc_library_shared {
+		name: "libtsan",
+		host_supported: true,
+		shared_libs: ["libtransitive"],
+		sanitize: {
+			thread: true,
+		}
+	}
+
+	cc_library_shared {
+		name: "libtransitive",
+		host_supported: true,
+	}
+`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForTsanTest,
+	).RunTestWithBp(t, bp)
+
+	check := func(t *testing.T, result *android.TestResult, variant string) {
+		ctx := result.TestContext
+		tsanVariant := variant + "_tsan"
+		sharedVariant := variant + "_shared"
+		sharedTsanVariant := sharedVariant + "_tsan"
+
+		// The binaries, one with tsan and one without
+		binWithTsan := result.ModuleForTests("bin_with_tsan", tsanVariant)
+		binNoTsan := result.ModuleForTests("bin_no_tsan", variant)
+
+		// Shared libraries that don't request tsan
+		libShared := result.ModuleForTests("libshared", sharedVariant)
+		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+
+		// Shared library that requests tsan
+		libTsan := result.ModuleForTests("libtsan", sharedTsanVariant)
+
+		expectSharedLinkDep(t, ctx, binWithTsan, libShared)
+		expectSharedLinkDep(t, ctx, binWithTsan, libTsan)
+		expectSharedLinkDep(t, ctx, libShared, libTransitive)
+		expectSharedLinkDep(t, ctx, libTsan, libTransitive)
+
+		expectSharedLinkDep(t, ctx, binNoTsan, libShared)
+		expectSharedLinkDep(t, ctx, binNoTsan, libTsan)
+		expectSharedLinkDep(t, ctx, libShared, libTransitive)
+		expectSharedLinkDep(t, ctx, libTsan, libTransitive)
+	}
+
+	t.Run("host", func(t *testing.T) { check(t, result, result.Config.BuildOSTarget.String()) })
+	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
+}
+
+func TestMiscUndefined(t *testing.T) {
+	bp := `
+	cc_binary {
+		name: "bin_with_ubsan",
+		srcs: ["src.cc"],
+		host_supported: true,
+		static_libs: [
+			"libstatic",
+			"libubsan",
+		],
+		sanitize: {
+			misc_undefined: ["integer"],
+		}
+	}
+
+	cc_binary {
+		name: "bin_no_ubsan",
+		host_supported: true,
+		srcs: ["src.cc"],
+		static_libs: [
+			"libstatic",
+			"libubsan",
+		],
+	}
+
+	cc_library_static {
+		name: "libstatic",
+		host_supported: true,
+		srcs: ["src.cc"],
+		static_libs: ["libtransitive"],
+	}
+
+	cc_library_static {
+		name: "libubsan",
+		host_supported: true,
+		srcs: ["src.cc"],
+		whole_static_libs: ["libtransitive"],
+		sanitize: {
+			misc_undefined: ["integer"],
+		}
+	}
+
+	cc_library_static {
+		name: "libtransitive",
+		host_supported: true,
+		srcs: ["src.cc"],
+	}
+`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+	).RunTestWithBp(t, bp)
+
+	check := func(t *testing.T, result *android.TestResult, variant string) {
+		ctx := result.TestContext
+		staticVariant := variant + "_static"
+
+		// The binaries, one with ubsan and one without
+		binWithUbsan := result.ModuleForTests("bin_with_ubsan", variant)
+		binNoUbsan := result.ModuleForTests("bin_no_ubsan", variant)
+
+		// Static libraries that don't request ubsan
+		libStatic := result.ModuleForTests("libstatic", staticVariant)
+		libTransitive := result.ModuleForTests("libtransitive", staticVariant)
+
+		libUbsan := result.ModuleForTests("libubsan", staticVariant)
+
+		libUbsanMinimal := result.ModuleForTests("libclang_rt.ubsan_minimal", staticVariant)
+
+		expectStaticLinkDep(t, ctx, binWithUbsan, libStatic)
+		expectStaticLinkDep(t, ctx, binWithUbsan, libUbsan)
+		expectStaticLinkDep(t, ctx, binWithUbsan, libUbsanMinimal)
+
+		miscUndefinedSanFlag := "-fsanitize=integer"
+		binWithUbsanCflags := binWithUbsan.Rule("cc").Args["cFlags"]
+		if !strings.Contains(binWithUbsanCflags, miscUndefinedSanFlag) {
+			t.Errorf("'bin_with_ubsan' Expected %q to be in flags %q, was not", miscUndefinedSanFlag, binWithUbsanCflags)
+		}
+		libStaticCflags := libStatic.Rule("cc").Args["cFlags"]
+		if strings.Contains(libStaticCflags, miscUndefinedSanFlag) {
+			t.Errorf("'libstatic' Expected %q to NOT be in flags %q, was", miscUndefinedSanFlag, binWithUbsanCflags)
+		}
+		libUbsanCflags := libUbsan.Rule("cc").Args["cFlags"]
+		if !strings.Contains(libUbsanCflags, miscUndefinedSanFlag) {
+			t.Errorf("'libubsan' Expected %q to be in flags %q, was not", miscUndefinedSanFlag, binWithUbsanCflags)
+		}
+		libTransitiveCflags := libTransitive.Rule("cc").Args["cFlags"]
+		if strings.Contains(libTransitiveCflags, miscUndefinedSanFlag) {
+			t.Errorf("'libtransitive': Expected %q to NOT be in flags %q, was", miscUndefinedSanFlag, binWithUbsanCflags)
+		}
+
+		expectStaticLinkDep(t, ctx, binNoUbsan, libStatic)
+		expectStaticLinkDep(t, ctx, binNoUbsan, libUbsan)
+	}
+
+	t.Run("host", func(t *testing.T) { check(t, result, result.Config.BuildOSTarget.String()) })
+	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
+}
+
+func TestFuzz(t *testing.T) {
+	bp := `
+		cc_binary {
+			name: "bin_with_fuzzer",
+			host_supported: true,
+			shared_libs: [
+				"libshared",
+				"libfuzzer",
+			],
+			static_libs: [
+				"libstatic",
+				"libnofuzzer",
+				"libstatic_fuzzer",
+			],
+			sanitize: {
+				fuzzer: true,
+			}
+		}
+
+		cc_binary {
+			name: "bin_no_fuzzer",
+			host_supported: true,
+			shared_libs: [
+				"libshared",
+				"libfuzzer",
+			],
+			static_libs: [
+				"libstatic",
+				"libnofuzzer",
+				"libstatic_fuzzer",
+			],
+		}
+
+		cc_library_shared {
+			name: "libshared",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+		}
+
+		cc_library_shared {
+			name: "libfuzzer",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+			sanitize: {
+				fuzzer: true,
+			}
+		}
+
+		cc_library_shared {
+			name: "libtransitive",
+			host_supported: true,
+		}
+
+		cc_library_static {
+			name: "libstatic",
+			host_supported: true,
+		}
+
+		cc_library_static {
+			name: "libnofuzzer",
+			host_supported: true,
+			sanitize: {
+				fuzzer: false,
+			}
+		}
+
+		cc_library_static {
+			name: "libstatic_fuzzer",
+			host_supported: true,
+		}
+
+	`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+	).RunTestWithBp(t, bp)
+
+	check := func(t *testing.T, result *android.TestResult, variant string) {
+		ctx := result.TestContext
+		fuzzerVariant := variant + "_fuzzer"
+		sharedVariant := variant + "_shared"
+		sharedFuzzerVariant := sharedVariant + "_fuzzer"
+		staticVariant := variant + "_static"
+		staticFuzzerVariant := staticVariant + "_fuzzer"
+
+		// The binaries, one with fuzzer and one without
+		binWithFuzzer := result.ModuleForTests("bin_with_fuzzer", fuzzerVariant)
+		binNoFuzzer := result.ModuleForTests("bin_no_fuzzer", variant)
+
+		// Shared libraries that don't request fuzzer
+		libShared := result.ModuleForTests("libshared", sharedVariant)
+		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+
+		// Shared libraries that don't request fuzzer
+		libSharedFuzzer := result.ModuleForTests("libshared", sharedFuzzerVariant)
+		libTransitiveFuzzer := result.ModuleForTests("libtransitive", sharedFuzzerVariant)
+
+		// Shared library that requests fuzzer
+		libFuzzer := result.ModuleForTests("libfuzzer", sharedFuzzerVariant)
+
+		// Static library that uses an fuzzer variant for bin_with_fuzzer and a non-fuzzer variant
+		// for bin_no_fuzzer.
+		libStaticFuzzerVariant := result.ModuleForTests("libstatic", staticFuzzerVariant)
+		libStaticNoFuzzerVariant := result.ModuleForTests("libstatic", staticVariant)
+
+		// Static library that never uses fuzzer.
+		libNoFuzzer := result.ModuleForTests("libnofuzzer", staticVariant)
+
+		// Static library that specifies fuzzer
+		libStaticFuzzer := result.ModuleForTests("libstatic_fuzzer", staticFuzzerVariant)
+		libStaticFuzzerNoFuzzerVariant := result.ModuleForTests("libstatic_fuzzer", staticVariant)
+
+		expectSharedLinkDep(t, ctx, binWithFuzzer, libSharedFuzzer)
+		expectSharedLinkDep(t, ctx, binWithFuzzer, libFuzzer)
+		expectSharedLinkDep(t, ctx, libSharedFuzzer, libTransitiveFuzzer)
+		expectSharedLinkDep(t, ctx, libFuzzer, libTransitiveFuzzer)
+
+		expectStaticLinkDep(t, ctx, binWithFuzzer, libStaticFuzzerVariant)
+		expectStaticLinkDep(t, ctx, binWithFuzzer, libNoFuzzer)
+		expectStaticLinkDep(t, ctx, binWithFuzzer, libStaticFuzzer)
+
+		expectSharedLinkDep(t, ctx, binNoFuzzer, libShared)
+		expectSharedLinkDep(t, ctx, binNoFuzzer, libFuzzer)
+		expectSharedLinkDep(t, ctx, libShared, libTransitive)
+		expectSharedLinkDep(t, ctx, libFuzzer, libTransitiveFuzzer)
+
+		expectStaticLinkDep(t, ctx, binNoFuzzer, libStaticNoFuzzerVariant)
+		expectStaticLinkDep(t, ctx, binNoFuzzer, libNoFuzzer)
+		expectStaticLinkDep(t, ctx, binNoFuzzer, libStaticFuzzerNoFuzzerVariant)
+	}
+
+	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
+}
+
 func TestUbsan(t *testing.T) {
 	if runtime.GOOS != "linux" {
 		t.Skip("requires linux")
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index 792ffe3..570300b 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -18,6 +18,7 @@
 // snapshot mutators and snapshot information maps which are also defined in this file.
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
@@ -399,8 +400,10 @@
 }
 
 type snapshotSanitizer interface {
-	isSanitizerEnabled(t SanitizerType) bool
+	isSanitizerAvailable(t SanitizerType) bool
 	setSanitizerVariation(t SanitizerType, enabled bool)
+	isSanitizerEnabled(t SanitizerType) bool
+	isUnsanitizedVariant() bool
 }
 
 type snapshotLibraryDecorator struct {
@@ -408,10 +411,13 @@
 	*libraryDecorator
 	properties          SnapshotLibraryProperties
 	sanitizerProperties struct {
-		CfiEnabled bool `blueprint:"mutated"`
+		SanitizerVariation SanitizerType `blueprint:"mutated"`
 
 		// Library flags for cfi variant.
 		Cfi SnapshotLibraryProperties `android:"arch_variant"`
+
+		// Library flags for hwasan variant.
+		Hwasan SnapshotLibraryProperties `android:"arch_variant"`
 	}
 }
 
@@ -450,8 +456,10 @@
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
 	}
 
-	if p.sanitizerProperties.CfiEnabled {
+	if p.isSanitizerEnabled(cfi) {
 		p.properties = p.sanitizerProperties.Cfi
+	} else if p.isSanitizerEnabled(Hwasan) {
+		p.properties = p.sanitizerProperties.Hwasan
 	}
 
 	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
@@ -514,25 +522,34 @@
 	return false
 }
 
-func (p *snapshotLibraryDecorator) isSanitizerEnabled(t SanitizerType) bool {
+func (p *snapshotLibraryDecorator) isSanitizerAvailable(t SanitizerType) bool {
 	switch t {
 	case cfi:
 		return p.sanitizerProperties.Cfi.Src != nil
+	case Hwasan:
+		return p.sanitizerProperties.Hwasan.Src != nil
 	default:
 		return false
 	}
 }
 
 func (p *snapshotLibraryDecorator) setSanitizerVariation(t SanitizerType, enabled bool) {
-	if !enabled {
+	if !enabled || p.isSanitizerEnabled(t) {
 		return
 	}
-	switch t {
-	case cfi:
-		p.sanitizerProperties.CfiEnabled = true
-	default:
-		return
+	if !p.isUnsanitizedVariant() {
+		panic(fmt.Errorf("snapshot Sanitizer must be one of Cfi or Hwasan but not both"))
 	}
+	p.sanitizerProperties.SanitizerVariation = t
+}
+
+func (p *snapshotLibraryDecorator) isSanitizerEnabled(t SanitizerType) bool {
+	return p.sanitizerProperties.SanitizerVariation == t
+}
+
+func (p *snapshotLibraryDecorator) isUnsanitizedVariant() bool {
+	return !p.isSanitizerEnabled(Asan) &&
+		!p.isSanitizerEnabled(Hwasan)
 }
 
 func snapshotLibraryFactory(image SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index 6a98778..79405e9 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -1053,6 +1053,7 @@
 			},
 		},
 	}
+
 	vendor_snapshot_static {
 		name: "libsnapshot",
 		vendor: true,
@@ -1063,7 +1064,10 @@
 				src: "libsnapshot.a",
 				cfi: {
 					src: "libsnapshot.cfi.a",
-				}
+				},
+				hwasan: {
+					src: "libsnapshot.hwasan.a",
+				},
 			},
 		},
 	}
@@ -1098,6 +1102,7 @@
 		"vendor/libc++demangle.a":        nil,
 		"vendor/libsnapshot.a":           nil,
 		"vendor/libsnapshot.cfi.a":       nil,
+		"vendor/libsnapshot.hwasan.a":    nil,
 		"vendor/note_memtag_heap_sync.a": nil,
 	}
 
@@ -1106,15 +1111,25 @@
 	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
-	// Check non-cfi and cfi variant.
+	// Check non-cfi, cfi and hwasan variant.
 	staticVariant := "android_vendor.28_arm64_armv8-a_static"
 	staticCfiVariant := "android_vendor.28_arm64_armv8-a_static_cfi"
+	staticHwasanVariant := "android_vendor.28_arm64_armv8-a_static_hwasan"
+	staticHwasanCfiVariant := "android_vendor.28_arm64_armv8-a_static_hwasan_cfi"
 
 	staticModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticVariant).Module().(*Module)
 	assertString(t, staticModule.outputFile.Path().Base(), "libsnapshot.a")
 
 	staticCfiModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticCfiVariant).Module().(*Module)
 	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
+
+	staticHwasanModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticHwasanVariant).Module().(*Module)
+	assertString(t, staticHwasanModule.outputFile.Path().Base(), "libsnapshot.hwasan.a")
+
+	staticHwasanCfiModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticHwasanCfiVariant).Module().(*Module)
+	if !staticHwasanCfiModule.HiddenFromMake() || !staticHwasanCfiModule.PreventInstall() {
+		t.Errorf("Hwasan and Cfi cannot enabled at the same time.")
+	}
 }
 
 func TestVendorSnapshotExclude(t *testing.T) {
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 3bc311b..029bbb4 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -629,40 +629,41 @@
 // symlink tree creation binary. Then the latter would not need to depend on
 // the very heavy-weight machinery of soong_build .
 func runSymlinkForestCreation(configuration android.Config, ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
-	var ninjaDeps []string
-	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-
-	generatedRoot := shared.JoinPath(configuration.SoongOutDir(), "bp2build")
-	workspaceRoot := shared.JoinPath(configuration.SoongOutDir(), "workspace")
-
-	excludes := bazelArtifacts()
-
-	if outDir[0] != '/' {
-		excludes = append(excludes, outDir)
-	}
-
-	existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
-		os.Exit(1)
-	}
-
-	pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(configuration.Bp2buildPackageConfig, topDir, existingBazelRelatedFiles, configuration.IsEnvTrue("BP2BUILD_VERBOSE"))
-	excludes = append(excludes, pathsToIgnoredBuildFiles...)
-	excludes = append(excludes, getTemporaryExcludes()...)
-
-	// PlantSymlinkForest() returns all the directories that were readdir()'ed.
-	// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
-	// or file created/deleted under it would trigger an update of the symlink
-	// forest.
 	ctx.EventHandler.Do("symlink_forest", func() {
-		symlinkForestDeps := bp2build.PlantSymlinkForest(
-			configuration.IsEnvTrue("BP2BUILD_VERBOSE"), topDir, workspaceRoot, generatedRoot, excludes)
-		ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
-	})
+		var ninjaDeps []string
+		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
-	writeDepFile(symlinkForestMarker, ctx.EventHandler, ninjaDeps)
-	touch(shared.JoinPath(topDir, symlinkForestMarker))
+		generatedRoot := shared.JoinPath(configuration.SoongOutDir(), "bp2build")
+		workspaceRoot := shared.JoinPath(configuration.SoongOutDir(), "workspace")
+
+		excludes := bazelArtifacts()
+
+		if outDir[0] != '/' {
+			excludes = append(excludes, outDir)
+		}
+
+		existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
+			os.Exit(1)
+		}
+
+		pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(configuration.Bp2buildPackageConfig, topDir, existingBazelRelatedFiles, configuration.IsEnvTrue("BP2BUILD_VERBOSE"))
+		excludes = append(excludes, pathsToIgnoredBuildFiles...)
+		excludes = append(excludes, getTemporaryExcludes()...)
+
+		// PlantSymlinkForest() returns all the directories that were readdir()'ed.
+		// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
+		// or file created/deleted under it would trigger an update of the symlink forest.
+		ctx.EventHandler.Do("plant", func() {
+			symlinkForestDeps := bp2build.PlantSymlinkForest(
+				configuration.IsEnvTrue("BP2BUILD_VERBOSE"), topDir, workspaceRoot, generatedRoot, excludes)
+			ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
+		})
+
+		writeDepFile(symlinkForestMarker, ctx.EventHandler, ninjaDeps)
+		touch(shared.JoinPath(topDir, symlinkForestMarker))
+	})
 	codegenMetrics := bp2build.ReadCodegenMetrics(metricsDir)
 	if codegenMetrics == nil {
 		m := bp2build.CreateCodegenMetrics()
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/robolectric.go b/java/robolectric.go
index 2cb0798..1d56708 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -327,7 +327,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/ui/build/soong.go b/ui/build/soong.go
index abaf5ae..837f0a4 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,117 @@
 	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())
-	}
-
-	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,11 +392,11 @@
 		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.SetIgnoreUnknownModuleTypes(true)
@@ -376,16 +406,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