Merge "Clean up some bp2build technical debt"
diff --git a/android/Android.bp b/android/Android.bp
index d583703..8eb55d2 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -49,6 +49,7 @@
         "expand.go",
         "filegroup.go",
         "fixture.go",
+        "gen_notice.go",
         "hooks.go",
         "image.go",
         "license.go",
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 9aae6fd..622c3c4 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -227,7 +227,7 @@
 
 		"prebuilts/bundletool":/* recursive = */ true,
 		"prebuilts/gcc":/* recursive = */ true,
-		"prebuilts/build-tools":/* recursive = */ false,
+		"prebuilts/build-tools":/* recursive = */ true,
 		"prebuilts/jdk/jdk11":/* recursive = */ false,
 		"prebuilts/sdk":/* recursive = */ false,
 		"prebuilts/sdk/current/extras/app-toolkit":/* recursive = */ false,
diff --git a/android/apex.go b/android/apex.go
index 5665da2..019efdd 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -864,7 +864,6 @@
 	"libtflite_kernel_utils":                                   30,
 	"libzstd":                                                  30,
 	"net-utils-framework-common":                               29,
-	"permissioncontroller-statsd":                              28,
 	"philox_random_headers":                                    30,
 	"philox_random":                                            30,
 	"tensorflow_headers":                                       30,
diff --git a/android/bazel.go b/android/bazel.go
index 67002ec..40f2917 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -115,6 +115,27 @@
 	SetBaseModuleType(baseModuleType string)
 }
 
+// MixedBuildBuildable is an interface that module types should implement in order
+// to be "handled by Bazel" in a mixed build.
+type MixedBuildBuildable interface {
+	// IsMixedBuildSupported returns true if and only if this module should be
+	// "handled by Bazel" in a mixed build.
+	// This "escape hatch" allows modules with corner-case scenarios to opt out
+	// of being built with Bazel.
+	IsMixedBuildSupported(ctx BaseModuleContext) bool
+
+	// QueueBazelCall invokes request-queueing functions on the BazelContext
+	// so that these requests are handled when Bazel's cquery is invoked.
+	QueueBazelCall(ctx BaseModuleContext)
+
+	// ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext)
+	// to set module fields and providers to propagate this module's metadata upstream.
+	// This effectively "bridges the gap" between Bazel and Soong in a mixed build.
+	// Soong modules depending on this module should be oblivious to the fact that
+	// this module was handled by Bazel.
+	ProcessBazelQueryResponse(ctx ModuleContext)
+}
+
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
 type BazelModule interface {
 	Module
@@ -300,25 +321,31 @@
 	return a
 }
 
-var bp2buildAllowlist = NewBp2BuildAllowlist().
-	SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
-	SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
-	SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
-	SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
-	SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList).
-	SetCcLibraryStaticOnlyList(allowlists.Bp2buildCcLibraryStaticOnlyList).
-	SetMixedBuildsDisabledList(allowlists.MixedBuildsDisabledList)
+var bp2BuildAllowListKey = NewOnceKey("Bp2BuildAllowlist")
+var bp2buildAllowlist OncePer
+
+func getBp2BuildAllowList() bp2BuildConversionAllowlist {
+	return bp2buildAllowlist.Once(bp2BuildAllowListKey, func() interface{} {
+		return NewBp2BuildAllowlist().SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
+			SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
+			SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
+			SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
+			SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList).
+			SetCcLibraryStaticOnlyList(allowlists.Bp2buildCcLibraryStaticOnlyList).
+			SetMixedBuildsDisabledList(allowlists.MixedBuildsDisabledList)
+	}).(bp2BuildConversionAllowlist)
+}
 
 // GenerateCcLibraryStaticOnly returns whether a cc_library module should only
 // generate a static version of itself based on the current global configuration.
 func GenerateCcLibraryStaticOnly(moduleName string) bool {
-	return bp2buildAllowlist.ccLibraryStaticOnly[moduleName]
+	return getBp2BuildAllowList().ccLibraryStaticOnly[moduleName]
 }
 
 // ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
 // added to the build symlink forest based on the current global configuration.
 func ShouldKeepExistingBuildFileForDir(dir string) bool {
-	return shouldKeepExistingBuildFileForDir(bp2buildAllowlist, dir)
+	return shouldKeepExistingBuildFileForDir(getBp2BuildAllowList(), dir)
 }
 
 func shouldKeepExistingBuildFileForDir(allowlist bp2BuildConversionAllowlist, dir string) bool {
@@ -342,7 +369,7 @@
 // converted or handcrafted Bazel target. As a side effect, calling this
 // method will also log whether this module is mixed build enabled for
 // metrics reporting.
-func MixedBuildsEnabled(ctx ModuleContext) bool {
+func MixedBuildsEnabled(ctx BaseModuleContext) bool {
 	mixedBuildEnabled := mixedBuildPossible(ctx)
 	ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
 	return mixedBuildEnabled
@@ -350,7 +377,7 @@
 
 // mixedBuildPossible returns true if a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
-func mixedBuildPossible(ctx ModuleContext) bool {
+func mixedBuildPossible(ctx BaseModuleContext) bool {
 	if ctx.Os() == Windows {
 		// Windows toolchains are not currently supported.
 		return false
@@ -371,7 +398,7 @@
 		// variants of a cc_library.
 		return false
 	}
-	return !bp2buildAllowlist.mixedBuildsDisabled[ctx.Module().Name()]
+	return !getBp2BuildAllowList().mixedBuildsDisabled[ctx.Module().Name()]
 }
 
 // ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index fa26fc8..4d9423a 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -33,6 +33,26 @@
 	"android/soong/bazel"
 )
 
+func init() {
+	RegisterMixedBuildsMutator(InitRegistrationContext)
+}
+
+func RegisterMixedBuildsMutator(ctx RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+		ctx.BottomUp("mixed_builds_prep", mixedBuildsPrepareMutator).Parallel()
+	})
+}
+
+func mixedBuildsPrepareMutator(ctx BottomUpMutatorContext) {
+	if m := ctx.Module(); m.Enabled() {
+		if mixedBuildMod, ok := m.(MixedBuildBuildable); ok {
+			if mixedBuildMod.IsMixedBuildSupported(ctx) && MixedBuildsEnabled(ctx) {
+				mixedBuildMod.QueueBazelCall(ctx)
+			}
+		}
+	}
+}
+
 type cqueryRequest interface {
 	// Name returns a string name for this request type. Such request type names must be unique,
 	// and must only consist of alphanumeric characters.
@@ -62,33 +82,32 @@
 	configKey   configKey
 }
 
-// bazelHandler is the interface for a helper object related to deferring to Bazel for
-// processing a module (during Bazel mixed builds). Individual module types should define
-// their own bazel handler if they support deferring to Bazel.
-type BazelHandler interface {
-	// Issue query to Bazel to retrieve information about Bazel's view of the current module.
-	// If Bazel returns this information, set module properties on the current module to reflect
-	// the returned information.
-	// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
-	GenerateBazelBuildActions(ctx ModuleContext, label string) bool
-}
-
+// BazelContext is a context object useful for interacting with Bazel during
+// the course of a build. Use of Bazel to evaluate part of the build graph
+// is referred to as a "mixed build". (Some modules are managed by Soong,
+// some are managed by Bazel). To facilitate interop between these build
+// subgraphs, Soong may make requests to Bazel and evaluate their responses
+// so that Soong modules may accurately depend on Bazel targets.
 type BazelContext interface {
-	// The methods below involve queuing cquery requests to be later invoked
-	// by bazel. If any of these methods return (_, false), then the request
-	// has been queued to be run later.
+	// Add a cquery request to the bazel request queue. All queued requests
+	// will be sent to Bazel on a subsequent invocation of InvokeBazel.
+	QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey)
+
+	// ** Cquery Results Retrieval Functions
+	// The below functions pertain to retrieving cquery results from a prior
+	// InvokeBazel function call and parsing the results.
 
 	// Returns result files built by building the given bazel target label.
-	GetOutputFiles(label string, cfgKey configKey) ([]string, bool)
+	GetOutputFiles(label string, cfgKey configKey) ([]string, error)
 
-	// TODO(cparsons): Other cquery-related methods should be added here.
 	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
-	GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error)
+	GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error)
 
 	// Returns the executable binary resultant from building together the python sources
-	GetPythonBinary(label string, cfgKey configKey) (string, bool)
+	// TODO(b/232976601): Remove.
+	GetPythonBinary(label string, cfgKey configKey) (string, error)
 
-	// ** End cquery methods
+	// ** end Cquery Results Retrieval Functions
 
 	// Issues commands to Bazel to receive results for all cquery requests
 	// queued in the BazelContext.
@@ -153,19 +172,23 @@
 	LabelToPythonBinary map[string]string
 }
 
-func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
-	result, ok := m.LabelToOutputFiles[label]
-	return result, ok
+func (m MockBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
+	panic("unimplemented")
 }
 
-func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
-	result, ok := m.LabelToCcInfo[label]
-	return result, ok, nil
+func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
+	result, _ := m.LabelToOutputFiles[label]
+	return result, nil
 }
 
-func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
-	result, ok := m.LabelToPythonBinary[label]
-	return result, ok
+func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
+	result, _ := m.LabelToCcInfo[label]
+	return result, nil
+}
+
+func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
+	result, _ := m.LabelToPythonBinary[label]
+	return result, nil
 }
 
 func (m MockBazelContext) InvokeBazel() error {
@@ -188,46 +211,53 @@
 
 var _ BazelContext = MockBazelContext{}
 
-func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, cfgKey)
-	var ret []string
-	if ok {
+func (bazelCtx *bazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
+	key := cqueryKey{label, requestType, cfgKey}
+	bazelCtx.requestMutex.Lock()
+	defer bazelCtx.requestMutex.Unlock()
+	bazelCtx.requests[key] = true
+}
+
+func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
+	key := cqueryKey{label, cquery.GetOutputFiles, cfgKey}
+	if rawString, ok := bazelCtx.results[key]; ok {
 		bazelOutput := strings.TrimSpace(rawString)
-		ret = cquery.GetOutputFiles.ParseResult(bazelOutput)
+		return cquery.GetOutputFiles.ParseResult(bazelOutput), nil
 	}
-	return ret, ok
+	return nil, fmt.Errorf("no bazel response found for %v", key)
 }
 
-func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
-	result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, cfgKey)
-	if !ok {
-		return cquery.CcInfo{}, ok, nil
-	}
-
-	bazelOutput := strings.TrimSpace(result)
-	ret, err := cquery.GetCcInfo.ParseResult(bazelOutput)
-	return ret, ok, err
-}
-
-func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetPythonBinary, cfgKey)
-	var ret string
-	if ok {
+func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
+	key := cqueryKey{label, cquery.GetCcInfo, cfgKey}
+	if rawString, ok := bazelCtx.results[key]; ok {
 		bazelOutput := strings.TrimSpace(rawString)
-		ret = cquery.GetPythonBinary.ParseResult(bazelOutput)
+		return cquery.GetCcInfo.ParseResult(bazelOutput)
 	}
-	return ret, ok
+	return cquery.CcInfo{}, fmt.Errorf("no bazel response found for %v", key)
 }
 
-func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
+func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
+	key := cqueryKey{label, cquery.GetPythonBinary, cfgKey}
+	if rawString, ok := bazelCtx.results[key]; ok {
+		bazelOutput := strings.TrimSpace(rawString)
+		return cquery.GetPythonBinary.ParseResult(bazelOutput), nil
+	}
+	return "", fmt.Errorf("no bazel response found for %v", key)
+}
+
+func (n noopBazelContext) QueueBazelRequest(label string, requestType cqueryRequest, cfgKey configKey) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
+func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
+func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, error) {
+	panic("unimplemented")
+}
+
+func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, error) {
 	panic("unimplemented")
 }
 
@@ -314,24 +344,6 @@
 	return true
 }
 
-// Adds a cquery request to the Bazel request queue, to be later invoked, or
-// returns the result of the given request if the request was already made.
-// If the given request was already made (and the results are available), then
-// returns (result, true). If the request is queued but no results are available,
-// then returns ("", false).
-func (context *bazelContext) cquery(label string, requestType cqueryRequest,
-	cfgKey configKey) (string, bool) {
-	key := cqueryKey{label, requestType, cfgKey}
-	if result, ok := context.results[key]; ok {
-		return result, true
-	} else {
-		context.requestMutex.Lock()
-		defer context.requestMutex.Unlock()
-		context.requests[key] = true
-		return "", false
-	}
-}
-
 func pwdPrefix() string {
 	// Darwin doesn't have /proc
 	if runtime.GOOS != "darwin" {
@@ -825,14 +837,14 @@
 
 	for _, depset := range ctx.Config().BazelContext.AqueryDepsets() {
 		var outputs []Path
-		for _, depsetDepId := range depset.TransitiveDepSetIds {
-			otherDepsetName := bazelDepsetName(depsetDepId)
+		for _, depsetDepHash := range depset.TransitiveDepSetHashes {
+			otherDepsetName := bazelDepsetName(depsetDepHash)
 			outputs = append(outputs, PathForPhony(ctx, otherDepsetName))
 		}
 		for _, artifactPath := range depset.DirectArtifacts {
 			outputs = append(outputs, PathForBazelOut(ctx, artifactPath))
 		}
-		thisDepsetName := bazelDepsetName(depset.Id)
+		thisDepsetName := bazelDepsetName(depset.ContentHash)
 		ctx.Build(pctx, BuildParams{
 			Rule:      blueprint.Phony,
 			Outputs:   []WritablePath{PathForPhony(ctx, thisDepsetName)},
@@ -874,8 +886,8 @@
 		for _, inputPath := range buildStatement.InputPaths {
 			cmd.Implicit(PathForBazelOut(ctx, inputPath))
 		}
-		for _, inputDepsetId := range buildStatement.InputDepsetIds {
-			otherDepsetName := bazelDepsetName(inputDepsetId)
+		for _, inputDepsetHash := range buildStatement.InputDepsetHashes {
+			otherDepsetName := bazelDepsetName(inputDepsetHash)
 			cmd.Implicit(PathForPhony(ctx, otherDepsetName))
 		}
 
@@ -916,7 +928,7 @@
 	return arch + "|" + os
 }
 
-func GetConfigKey(ctx ModuleContext) configKey {
+func GetConfigKey(ctx BaseModuleContext) configKey {
 	return configKey{
 		// use string because Arch is not a valid key in go
 		arch:   ctx.Arch().String(),
@@ -924,6 +936,6 @@
 	}
 }
 
-func bazelDepsetName(depsetId int) string {
-	return fmt.Sprintf("bazel_depset_%d", depsetId)
+func bazelDepsetName(contentHash string) string {
+	return fmt.Sprintf("bazel_depset_%s", contentHash)
 }
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index e5cff90..cfdccd7 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -5,6 +5,8 @@
 	"path/filepath"
 	"reflect"
 	"testing"
+
+	"android/soong/bazel/cquery"
 )
 
 func TestRequestResultsAfterInvokeBazel(t *testing.T) {
@@ -13,17 +15,14 @@
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
 		bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
 	})
-	g, ok := bazelContext.GetOutputFiles(label, cfg)
-	if ok {
-		t.Errorf("Did not expect cquery results prior to running InvokeBazel(), but got %s", g)
-	}
+	bazelContext.QueueBazelRequest(label, cquery.GetOutputFiles, cfg)
 	err := bazelContext.InvokeBazel()
 	if err != nil {
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
-	g, ok = bazelContext.GetOutputFiles(label, cfg)
-	if !ok {
-		t.Errorf("Expected cquery results after running InvokeBazel(), but got none")
+	g, err := bazelContext.GetOutputFiles(label, cfg)
+	if err != nil {
+		t.Errorf("Expected cquery results after running InvokeBazel(), but got err %v", err)
 	} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("Expected output %s, got %s", w, g)
 	}
diff --git a/android/bazel_test.go b/android/bazel_test.go
index 482df2a..e14649e 100644
--- a/android/bazel_test.go
+++ b/android/bazel_test.go
@@ -14,11 +14,12 @@
 package android
 
 import (
-	"android/soong/android/allowlists"
-	"android/soong/bazel"
 	"fmt"
 	"testing"
 
+	"android/soong/android/allowlists"
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -386,3 +387,37 @@
 		})
 	}
 }
+
+func TestBp2buildAllowList(t *testing.T) {
+	allowlist := getBp2BuildAllowList()
+	for k, v := range allowlists.Bp2buildDefaultConfig {
+		if allowlist.defaultConfig[k] != v {
+			t.Errorf("bp2build default config of %s: expected: %v, got: %v", k, v, allowlist.defaultConfig[k])
+		}
+	}
+	for k, v := range allowlists.Bp2buildKeepExistingBuildFile {
+		if allowlist.keepExistingBuildFile[k] != v {
+			t.Errorf("bp2build keep existing build file of %s: expected: %v, got: %v", k, v, allowlist.keepExistingBuildFile[k])
+		}
+	}
+	for _, k := range allowlists.Bp2buildModuleTypeAlwaysConvertList {
+		if !allowlist.moduleTypeAlwaysConvert[k] {
+			t.Errorf("bp2build module type always convert of %s: expected: true, got: %v", k, allowlist.moduleTypeAlwaysConvert[k])
+		}
+	}
+	for _, k := range allowlists.Bp2buildModuleDoNotConvertList {
+		if !allowlist.moduleDoNotConvert[k] {
+			t.Errorf("bp2build module do not convert of %s: expected: true, got: %v", k, allowlist.moduleDoNotConvert[k])
+		}
+	}
+	for _, k := range allowlists.Bp2buildCcLibraryStaticOnlyList {
+		if !allowlist.ccLibraryStaticOnly[k] {
+			t.Errorf("bp2build cc library static only of %s: expected: true, got: %v", k, allowlist.ccLibraryStaticOnly[k])
+		}
+	}
+	for _, k := range allowlists.MixedBuildsDisabledList {
+		if !allowlist.mixedBuildsDisabled[k] {
+			t.Errorf("bp2build mix build disabled of %s: expected: true, got: %v", k, allowlist.mixedBuildsDisabled[k])
+		}
+	}
+}
diff --git a/android/config.go b/android/config.go
index d695217..403999d 100644
--- a/android/config.go
+++ b/android/config.go
@@ -558,7 +558,7 @@
 	}
 
 	config.BazelContext, err = NewBazelContext(config)
-	config.bp2buildPackageConfig = bp2buildAllowlist
+	config.bp2buildPackageConfig = getBp2BuildAllowList()
 
 	return Config{config}, err
 }
@@ -2047,7 +2047,7 @@
 	return Bool(c.productVariables.HostMusl)
 }
 
-func (c *config) LogMixedBuild(ctx ModuleContext, useBazel bool) {
+func (c *config) LogMixedBuild(ctx BaseModuleContext, useBazel bool) {
 	moduleName := ctx.Module().Name()
 	c.mixedBuildsLock.Lock()
 	defer c.mixedBuildsLock.Unlock()
diff --git a/android/filegroup.go b/android/filegroup.go
index 1bf5e07..485e0b9 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -18,6 +18,7 @@
 	"strings"
 
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 
 	"github.com/google/blueprint"
 )
@@ -101,6 +102,7 @@
 	srcs       Paths
 }
 
+var _ MixedBuildBuildable = (*fileGroup)(nil)
 var _ SourceFileProducer = (*fileGroup)(nil)
 
 // filegroup contains a list of files that are referenced by other modules
@@ -114,42 +116,11 @@
 	return module
 }
 
-func (fg *fileGroup) maybeGenerateBazelBuildActions(ctx ModuleContext) {
-	if !MixedBuildsEnabled(ctx) {
-		return
-	}
-
-	archVariant := ctx.Arch().String()
-	osVariant := ctx.Os()
-	if len(fg.Srcs()) == 1 && fg.Srcs()[0].Base() == fg.Name() {
-		// This will be a regular file target, not filegroup, in Bazel.
-		// See FilegroupBp2Build for more information.
-		archVariant = Common.String()
-		osVariant = CommonOS
-	}
-
-	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{archVariant, osVariant})
-	if !ok {
-		return
-	}
-
-	bazelOuts := make(Paths, 0, len(filePaths))
-	for _, p := range filePaths {
-		src := PathForBazelOut(ctx, p)
-		bazelOuts = append(bazelOuts, src)
-	}
-
-	fg.srcs = bazelOuts
-}
-
 func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
 	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
 	if fg.properties.Path != nil {
 		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
 	}
-
-	fg.maybeGenerateBazelBuildActions(ctx)
 }
 
 func (fg *fileGroup) Srcs() Paths {
@@ -161,3 +132,38 @@
 		ctx.StrictRaw(makeVar, strings.Join(fg.srcs.Strings(), " "))
 	}
 }
+
+func (fg *fileGroup) QueueBazelCall(ctx BaseModuleContext) {
+	bazelCtx := ctx.Config().BazelContext
+
+	bazelCtx.QueueBazelRequest(
+		fg.GetBazelLabel(ctx, fg),
+		cquery.GetOutputFiles,
+		configKey{Common.String(), CommonOS})
+}
+
+func (fg *fileGroup) IsMixedBuildSupported(ctx BaseModuleContext) bool {
+	return true
+}
+
+func (fg *fileGroup) ProcessBazelQueryResponse(ctx ModuleContext) {
+	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
+	if fg.properties.Path != nil {
+		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
+	}
+
+	bazelCtx := ctx.Config().BazelContext
+	filePaths, err := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{Common.String(), CommonOS})
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+		return
+	}
+
+	bazelOuts := make(Paths, 0, len(filePaths))
+	for _, p := range filePaths {
+		src := PathForBazelOut(ctx, p)
+		bazelOuts = append(bazelOuts, src)
+	}
+
+	fg.srcs = bazelOuts
+}
diff --git a/android/gen_notice.go b/android/gen_notice.go
new file mode 100644
index 0000000..fda91ac
--- /dev/null
+++ b/android/gen_notice.go
@@ -0,0 +1,207 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	RegisterGenNoticeBuildComponents(InitRegistrationContext)
+}
+
+// Register the gen_notice module type.
+func RegisterGenNoticeBuildComponents(ctx RegistrationContext) {
+	ctx.RegisterSingletonType("gen_notice_build_rules", GenNoticeBuildRulesFactory)
+	ctx.RegisterModuleType("gen_notice", GenNoticeFactory)
+}
+
+type genNoticeBuildRules struct{}
+
+func (s *genNoticeBuildRules) GenerateBuildActions(ctx SingletonContext) {
+	ctx.VisitAllModules(func(m Module) {
+		gm, ok := m.(*genNoticeModule)
+		if !ok {
+			return
+		}
+		if len(gm.missing) > 0 {
+			missingReferencesRule(ctx, gm)
+			return
+		}
+		out := BuildNoticeTextOutputFromLicenseMetadata
+		if proptools.Bool(gm.properties.Xml) {
+			out = BuildNoticeXmlOutputFromLicenseMetadata
+		} else if proptools.Bool(gm.properties.Html) {
+			out = BuildNoticeHtmlOutputFromLicenseMetadata
+		}
+		defaultName := ""
+		if len(gm.properties.For) > 0 {
+			defaultName = gm.properties.For[0]
+		}
+
+		modules := make([]Module, 0)
+		for _, name := range gm.properties.For {
+			mods := ctx.ModuleVariantsFromName(gm, name)
+			for _, mod := range mods {
+				if mod == nil {
+					continue
+				}
+				modules = append(modules, mod)
+			}
+		}
+		if ctx.Failed() {
+			return
+		}
+		out(ctx, gm.output, ctx.ModuleName(gm), proptools.StringDefault(gm.properties.ArtifactName, defaultName), "", modules...)
+	})
+}
+
+func GenNoticeBuildRulesFactory() Singleton {
+	return &genNoticeBuildRules{}
+}
+
+type genNoticeProperties struct {
+	// For specifies the modules for which to generate a notice file.
+	For []string
+	// ArtifactName specifies the internal name to use for the notice file.
+	// It appears in the "used by:" list for targets whose entire name is stripped by --strip_prefix.
+	ArtifactName *string
+	// Stem specifies the base name of the output file.
+	Stem *string `android:"arch_variant"`
+	// Html indicates an html-format file is needed. The default is text. Can be Html or Xml but not both.
+	Html *bool
+	// Xml indicates an xml-format file is needed. The default is text. Can be Html or Xml but not both.
+	Xml *bool
+	// Gzipped indicates the output file must be compressed with gzip. Will append .gz to suffix if not there.
+	Gzipped *bool
+	// Suffix specifies the file extension to use. Defaults to .html for html, .xml for xml, or no extension for text.
+	Suffix *string
+	// Visibility specifies where this license can be used
+	Visibility []string
+}
+
+type genNoticeModule struct {
+	ModuleBase
+	DefaultableModuleBase
+
+	properties genNoticeProperties
+
+	output  OutputPath
+	missing []string
+}
+
+func (m *genNoticeModule) DepsMutator(ctx BottomUpMutatorContext) {
+	if proptools.Bool(m.properties.Html) && proptools.Bool(m.properties.Xml) {
+		ctx.ModuleErrorf("can be html or xml but not both")
+	}
+	if !ctx.Config().AllowMissingDependencies() {
+		var missing []string
+		// Verify the modules for which to generate notices exist.
+		for _, otherMod := range m.properties.For {
+			if !ctx.OtherModuleExists(otherMod) {
+				missing = append(missing, otherMod)
+			}
+		}
+		if len(missing) == 1 {
+			ctx.PropertyErrorf("for", "no %q module exists", missing[0])
+		} else if len(missing) > 1 {
+			ctx.PropertyErrorf("for", "modules \"%s\" do not exist", strings.Join(missing, "\", \""))
+		}
+	}
+}
+
+func (m *genNoticeModule) getStem() string {
+	stem := m.base().BaseModuleName()
+	if m.properties.Stem != nil {
+		stem = proptools.String(m.properties.Stem)
+	}
+	return stem
+}
+
+func (m *genNoticeModule) getSuffix() string {
+	suffix := ""
+	if m.properties.Suffix == nil {
+		if proptools.Bool(m.properties.Html) {
+			suffix = ".html"
+		} else if proptools.Bool(m.properties.Xml) {
+			suffix = ".xml"
+		}
+	} else {
+		suffix = proptools.String(m.properties.Suffix)
+	}
+	if proptools.Bool(m.properties.Gzipped) && !strings.HasSuffix(suffix, ".gz") {
+		suffix += ".gz"
+	}
+	return suffix
+}
+
+func (m *genNoticeModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if ctx.Config().AllowMissingDependencies() {
+		// Verify the modules for which to generate notices exist.
+		for _, otherMod := range m.properties.For {
+			if !ctx.OtherModuleExists(otherMod) {
+				m.missing = append(m.missing, otherMod)
+			}
+		}
+		m.missing = append(m.missing, ctx.GetMissingDependencies()...)
+		m.missing = FirstUniqueStrings(m.missing)
+	}
+	out := m.getStem() + m.getSuffix()
+	m.output = PathForModuleOut(ctx, out).OutputPath
+}
+
+func GenNoticeFactory() Module {
+	module := &genNoticeModule{}
+
+	base := module.base()
+	module.AddProperties(&base.nameProperties, &module.properties)
+
+	// The visibility property needs to be checked and parsed by the visibility module.
+	setPrimaryVisibilityProperty(module, "visibility", &module.properties.Visibility)
+
+	initAndroidModuleBase(module)
+	InitDefaultableModule(module)
+
+	return module
+}
+
+var _ OutputFileProducer = (*genNoticeModule)(nil)
+
+// Implements OutputFileProducer
+func (m *genNoticeModule) OutputFiles(tag string) (Paths, error) {
+	if tag == "" {
+		return Paths{m.output}, nil
+	}
+	return nil, fmt.Errorf("unrecognized tag %q", tag)
+}
+
+// missingReferencesRule emits an ErrorRule for missing module references.
+func missingReferencesRule(ctx BuilderContext, m *genNoticeModule) {
+	if len(m.missing) < 1 {
+		panic(fmt.Errorf("missing references rule requested with no missing references"))
+	}
+
+	ctx.Build(pctx, BuildParams{
+		Rule:        ErrorRule,
+		Output:      m.output,
+		Description: "notice for " + proptools.StringDefault(m.properties.ArtifactName, "container"),
+		Args: map[string]string{
+			"error": m.Name() + " references missing module(s): " + strings.Join(m.missing, ", "),
+		},
+	})
+}
diff --git a/android/licenses.go b/android/licenses.go
index bd14b26..81c557e 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -303,6 +303,7 @@
 	switch reflect.TypeOf(module).String() {
 	case "*android.licenseModule": // is a license, doesn't need one
 	case "*android.licenseKindModule": // is a license, doesn't need one
+	case "*android.genNoticeModule": // contains license texts as data
 	case "*android.NamespaceModule": // just partitions things, doesn't add anything
 	case "*android.soongConfigModuleTypeModule": // creates aliases for modules with licenses
 	case "*android.soongConfigModuleTypeImport": // creates aliases for modules with licenses
diff --git a/android/module.go b/android/module.go
index 57cfcc7..21d5a3d 100644
--- a/android/module.go
+++ b/android/module.go
@@ -2275,7 +2275,11 @@
 			return
 		}
 
-		m.module.GenerateAndroidBuildActions(ctx)
+		if mixedBuildMod, handled := m.isHandledByBazel(ctx); handled {
+			mixedBuildMod.ProcessBazelQueryResponse(ctx)
+		} else {
+			m.module.GenerateAndroidBuildActions(ctx)
+		}
 		if ctx.Failed() {
 			return
 		}
@@ -2331,6 +2335,18 @@
 	m.variables = ctx.variables
 }
 
+func (m *ModuleBase) isHandledByBazel(ctx ModuleContext) (MixedBuildBuildable, bool) {
+	if !ctx.Config().BazelContext.BazelEnabled() {
+		return nil, false
+	}
+	if mixedBuildMod, ok := m.module.(MixedBuildBuildable); ok {
+		if mixedBuildMod.IsMixedBuildSupported(ctx) && MixedBuildsEnabled(ctx) {
+			return mixedBuildMod, true
+		}
+	}
+	return nil, false
+}
+
 // Check the supplied dist structure to make sure that it is valid.
 //
 // property - the base property, e.g. dist or dists[1], which is combined with the
diff --git a/android/notices.go b/android/notices.go
index 2a4c17c..562a156 100644
--- a/android/notices.go
+++ b/android/notices.go
@@ -15,31 +15,86 @@
 package android
 
 import (
+	"fmt"
+	"path/filepath"
 	"strings"
 )
 
-// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based on the module's
-// generated license metadata file.
-func BuildNoticeTextOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
-	depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", "."))
-	rule := NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("textnotice").
-		FlagWithOutput("-o ", outputFile).
-		FlagWithDepFile("-d ", depsFile).
-		Input(ctx.Module().base().licenseMetadataFile)
-	rule.Build("text_notice", "container notice file")
+func modulesOutputDirs(ctx BuilderContext, modules ...Module) []string {
+	dirs := make([]string, 0, len(modules))
+	for _, module := range modules {
+		paths, err := outputFilesForModule(ctx, module, "")
+		if err != nil {
+			continue
+		}
+		for _, path := range paths {
+			if path != nil {
+				dirs = append(dirs, filepath.Dir(path.String()))
+			}
+		}
+	}
+	return SortedUniqueStrings(dirs)
 }
 
-// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based on the module's
-// generated license metadata file.
-func BuildNoticeHtmlOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
+func modulesLicenseMetadata(ctx BuilderContext, modules ...Module) Paths {
+	result := make(Paths, 0, len(modules))
+	for _, module := range modules {
+		if mf := module.base().licenseMetadataFile; mf != nil {
+			result = append(result, mf)
+		}
+	}
+	return result
+}
+
+// buildNoticeOutputFromLicenseMetadata writes out a notice file.
+func buildNoticeOutputFromLicenseMetadata(ctx BuilderContext, tool, ruleName string, outputFile WritablePath, libraryName, stripPrefix string, modules ...Module) {
 	depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", "."))
 	rule := NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("htmlnotice").
+	if len(modules) == 0 {
+		if mctx, ok := ctx.(ModuleContext); ok {
+			modules = []Module{mctx.Module()}
+		} else {
+			panic(fmt.Errorf("%s %q needs a module to generate the notice for", ruleName, libraryName))
+		}
+	}
+	if libraryName == "" {
+		libraryName = modules[0].Name()
+	}
+	cmd := rule.Command().
+		BuiltTool(tool).
 		FlagWithOutput("-o ", outputFile).
-		FlagWithDepFile("-d ", depsFile).
-		Input(ctx.Module().base().licenseMetadataFile)
-	rule.Build("html_notice", "container notice file")
+		FlagWithDepFile("-d ", depsFile)
+	if stripPrefix != "" {
+		cmd = cmd.FlagWithArg("--strip_prefix ", stripPrefix)
+	}
+	outputs := modulesOutputDirs(ctx, modules...)
+	if len(outputs) > 0 {
+		cmd = cmd.FlagForEachArg("--strip_prefix ", outputs)
+	}
+	if libraryName != "" {
+		cmd = cmd.FlagWithArg("--product ", libraryName)
+	}
+	cmd = cmd.Inputs(modulesLicenseMetadata(ctx, modules...))
+	rule.Build(ruleName, "container notice file")
+}
+
+// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeTextOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, ruleName, libraryName, stripPrefix string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "textnotice", "text_notice_"+ruleName, outputFile, libraryName, stripPrefix, modules...)
+}
+
+// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeHtmlOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, ruleName, libraryName, stripPrefix string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "htmlnotice", "html_notice_"+ruleName, outputFile, libraryName, stripPrefix, modules...)
+}
+
+// BuildNoticeXmlOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeXmlOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, ruleName, libraryName, stripPrefix string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "xmlnotice", "xml_notice_"+ruleName, outputFile, libraryName, stripPrefix, modules...)
 }
diff --git a/android/sdk.go b/android/sdk.go
index 3a56240..9317910 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -961,3 +961,10 @@
 }
 
 var ExportedComponentsInfoProvider = blueprint.NewProvider(ExportedComponentsInfo{})
+
+// AdditionalSdkInfo contains additional properties to add to the generated SDK info file.
+type AdditionalSdkInfo struct {
+	Properties map[string]interface{}
+}
+
+var AdditionalSdkInfoProvider = blueprint.NewProvider(AdditionalSdkInfo{})
diff --git a/android/singleton.go b/android/singleton.go
index 7ff96c9..7c6cf4f 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -29,6 +29,10 @@
 	ModuleType(module blueprint.Module) string
 	BlueprintFile(module blueprint.Module) string
 
+	// ModuleVariantsFromName returns the list of module variants named `name` in the same namespace as `referer` enforcing visibility rules.
+	// Allows generating build actions for `referer` based on the metadata for `name` deferred until the singleton context.
+	ModuleVariantsFromName(referer Module, name string) []Module
+
 	// ModuleProvider returns the value, if any, for the provider for a module.  If the value for the
 	// provider was not set it returns the zero value of the type of the provider, which means the
 	// return value can always be type-asserted to the type of the provider.  The return value should
@@ -251,3 +255,30 @@
 func (s *singletonContextAdaptor) FinalModule(module Module) Module {
 	return s.SingletonContext.FinalModule(module).(Module)
 }
+
+func (s *singletonContextAdaptor) ModuleVariantsFromName(referer Module, name string) []Module {
+	// get qualified module name for visibility enforcement
+	qualified := createQualifiedModuleName(s.ModuleName(referer), s.ModuleDir(referer))
+
+	modules := s.SingletonContext.ModuleVariantsFromName(referer, name)
+	result := make([]Module, 0, len(modules))
+	for _, m := range modules {
+		if module, ok := m.(Module); ok {
+			// enforce visibility
+			depName := s.ModuleName(module)
+			depDir := s.ModuleDir(module)
+			depQualified := qualifiedModuleName{depDir, depName}
+			// Targets are always visible to other targets in their own package.
+			if depQualified.pkg != qualified.pkg {
+				rule := effectiveVisibilityRules(s.Config(), depQualified)
+				if !rule.matches(qualified) {
+					s.ModuleErrorf(referer, "module %q references %q which is not visible to this module\nYou may need to add %q to its visibility",
+						referer.Name(), depQualified, "//"+s.ModuleDir(referer))
+					continue
+				}
+			}
+			result = append(result, module)
+		}
+	}
+	return result
+}
diff --git a/android/testing.go b/android/testing.go
index ac02db9..85bdca4 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -83,6 +83,8 @@
 	FixtureRegisterWithContext(registerLicenseMutators),
 )
 
+var PrepareForTestWithGenNotice = FixtureRegisterWithContext(RegisterGenNoticeBuildComponents)
+
 func registerLicenseMutators(ctx RegistrationContext) {
 	ctx.PreArchMutators(RegisterLicensesPackageMapper)
 	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
diff --git a/android/visibility.go b/android/visibility.go
index 5d1be6b..b209599 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -234,7 +234,7 @@
 
 // Checks the per-module visibility rule lists before defaults expansion.
 func visibilityRuleChecker(ctx BottomUpMutatorContext) {
-	qualified := createQualifiedModuleName(ctx)
+	qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir())
 	if m, ok := ctx.Module().(Module); ok {
 		visibilityProperties := m.visibilityProperties()
 		for _, p := range visibilityProperties {
@@ -435,7 +435,7 @@
 		return
 	}
 
-	qualified := createQualifiedModuleName(ctx)
+	qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir())
 
 	// Visit all the dependencies making sure that this module has access to them all.
 	ctx.VisitDirectDeps(func(dep Module) {
@@ -486,9 +486,7 @@
 	return rule
 }
 
-func createQualifiedModuleName(ctx BaseModuleContext) qualifiedModuleName {
-	moduleName := ctx.ModuleName()
-	dir := ctx.ModuleDir()
+func createQualifiedModuleName(moduleName, dir string) qualifiedModuleName {
 	qualified := qualifiedModuleName{dir, moduleName}
 	return qualified
 }
diff --git a/android/visibility_test.go b/android/visibility_test.go
index 714c92a..a66f0b6 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -135,7 +135,49 @@
 					name: "libexample",
 					visibility: ["//visibility:public"],
 				}
-	
+
+				mock_library {
+					name: "libsamepackage",
+					deps: ["libexample"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				mock_library {
+					name: "libnested",
+					deps: ["libexample"],
+				}
+
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"other/Android.bp": []byte(`
+				mock_library {
+					name: "libother",
+					deps: ["libexample"],
+				}
+
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
+		// Verify that //visibility:private allows the module to be referenced from the current
+		// directory only.
+		name: "//visibility:private",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:private"],
+				}
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -151,18 +193,61 @@
 					deps: ["libexample"],
 				}`),
 		},
+		expectedErrors: []string{
+			`module "libnested" variant "android_common": depends on //top:libexample which is not` +
+				` visible to this module`,
+			`module "libother" variant "android_common": depends on //top:libexample which is not` +
+				` visible to this module`,
+		},
 	},
 	{
 		// Verify that //visibility:private allows the module to be referenced from the current
 		// directory only.
-		name: "//visibility:private",
+		name: "//visibility:private (notices)",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:private"],
 				}
-	
+
+				mock_library {
+					name: "libsamepackage",
+					deps: ["libexample"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "nested-notice" references "//top:libexample" which is not visible to this` +
+				` module\nYou may need to add "//top/nested" to its visibility`,
+			`module "other-notice" references "//top:libexample" which is not visible to this module\n` +
+				`You may need to add "//other" to its visibility`,
+		},
+	},
+	{
+		// Verify that :__pkg__ allows the module to be referenced from the current directory only.
+		name: ":__pkg__",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: [":__pkg__"],
+				}
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -187,34 +272,32 @@
 	},
 	{
 		// Verify that :__pkg__ allows the module to be referenced from the current directory only.
-		name: ":__pkg__",
+		name: ":__pkg__ (notices)",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [":__pkg__"],
 				}
-	
-				mock_library {
-					name: "libsamepackage",
-					deps: ["libexample"],
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
 				}`),
 			"top/nested/Android.bp": []byte(`
-				mock_library {
-					name: "libnested",
-					deps: ["libexample"],
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
 				}`),
 			"other/Android.bp": []byte(`
-				mock_library {
-					name: "libother",
-					deps: ["libexample"],
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
 				}`),
 		},
 		expectedErrors: []string{
-			`module "libnested" variant "android_common": depends on //top:libexample which is not` +
-				` visible to this module`,
-			`module "libother" variant "android_common": depends on //top:libexample which is not` +
-				` visible to this module`,
+			`module "nested-notice" references "//top:libexample" which is not visible to this module`,
+			`module "other-notice" references "//top:libexample" which is not visible to this module`,
 		},
 	},
 	{
@@ -227,7 +310,7 @@
 					name: "libexample",
 					visibility: ["//top/nested"],
 				}
-	
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -256,6 +339,42 @@
 		},
 	},
 	{
+		// Verify that //top/nested allows the module to be referenced from the current directory and
+		// the top/nested directory only, not a subdirectory of top/nested and not peak directory.
+		name: "//top/nested (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: ["//top/nested"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/again/Android.bp": []byte(`
+				gen_notice {
+					name: "nestedagain-notice",
+					for: ["libexample"],
+				}`),
+			"peak/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "other-notice" references "//top:libexample" which is not visible to this module`,
+			`module "nestedagain-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		// Verify that :__subpackages__ allows the module to be referenced from the current directory
 		// and sub directories but nowhere else.
 		name: ":__subpackages__",
@@ -265,7 +384,7 @@
 					name: "libexample",
 					visibility: [":__subpackages__"],
 				}
-	
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -287,6 +406,36 @@
 		},
 	},
 	{
+		// Verify that :__subpackages__ allows the module to be referenced from the current directory
+		// and sub directories but nowhere else.
+		name: ":__subpackages__ (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: [":__subpackages__"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"peak/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "other-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		// Verify that //top/nested:__subpackages__ allows the module to be referenced from the current
 		// directory and sub directories but nowhere else.
 		name: "//top/nested:__subpackages__",
@@ -296,7 +445,7 @@
 					name: "libexample",
 					visibility: ["//top/nested:__subpackages__", "//other"],
 				}
-	
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -318,6 +467,36 @@
 		},
 	},
 	{
+		// Verify that //top/nested:__subpackages__ allows the module to be referenced from the current
+		// directory and sub directories but nowhere else.
+		name: "//top/nested:__subpackages__ (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: ["//top/nested:__subpackages__", "//other"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"top/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "other-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		// Verify that ["//top/nested", "//peak:__subpackages"] allows the module to be referenced from
 		// the current directory, top/nested and peak and all its subpackages.
 		name: `["//top/nested", "//peak:__subpackages__"]`,
@@ -327,7 +506,7 @@
 					name: "libexample",
 					visibility: ["//top/nested", "//peak:__subpackages__"],
 				}
-	
+
 				mock_library {
 					name: "libsamepackage",
 					deps: ["libexample"],
@@ -345,6 +524,33 @@
 		},
 	},
 	{
+		// Verify that ["//top/nested", "//peak:__subpackages"] allows the module to be referenced from
+		// the current directory, top/nested and peak and all its subpackages.
+		name: `["//top/nested", "//peak:__subpackages__ (notices)"]`,
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "libexample",
+					visibility: ["//top/nested", "//peak:__subpackages__"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"peak/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
 		name: `//vendor`,
 		fs: MockFS{
@@ -353,7 +559,7 @@
 					name: "libexample",
 					visibility: ["//vendor:__subpackages__"],
 				}
-	
+
 				mock_library {
 					name: "libsamepackage",
 					visibility: ["//vendor/apps/AcmeSettings"],
@@ -418,6 +624,45 @@
 		},
 	},
 	{
+		// Check that visibility is the union of the defaults modules.
+		name: "defaults union, basic (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//other"],
+				}
+				mock_library {
+					name: "libexample",
+					visibility: ["//top/nested"],
+					defaults: ["libexample_defaults"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "defaults union, multiple defaults",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -459,6 +704,47 @@
 		},
 	},
 	{
+		name: "defaults union, multiple defaults (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults_1",
+					visibility: ["//other"],
+				}
+				mock_defaults {
+					name: "libexample_defaults_2",
+					visibility: ["//top/nested"],
+				}
+				mock_library {
+					name: "libexample",
+					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "//visibility:public mixed with other in defaults",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -500,6 +786,29 @@
 		},
 	},
 	{
+		name: "//visibility:public overriding defaults (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//namespace"],
+				}
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:public"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		effectiveVisibility: map[qualifiedModuleName][]string{
+			qualifiedModuleName{pkg: "top", name: "libexample"}: {"//visibility:public"},
+		},
+	},
+	{
 		name: "//visibility:public mixed with other from different defaults 1",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -523,6 +832,34 @@
 		},
 	},
 	{
+		name: "//visibility:public mixed with other from different defaults 1",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults_1",
+					visibility: ["//namespace"],
+				}
+				mock_defaults {
+					name: "libexample_defaults_2",
+					visibility: ["//visibility:public"],
+				}
+				mock_library {
+					name: "libexample",
+					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		name: "//visibility:public mixed with other from different defaults 2",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -546,6 +883,29 @@
 		},
 	},
 	{
+		name: "//visibility:public mixed with other from different defaults 2 (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults_1",
+					visibility: ["//visibility:public"],
+				}
+				mock_defaults {
+					name: "libexample_defaults_2",
+					visibility: ["//namespace"],
+				}
+				mock_library {
+					name: "libexample",
+					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		name: "//visibility:private in defaults",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -580,6 +940,39 @@
 		},
 	},
 	{
+		name: "//visibility:private in defaults (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//visibility:private"],
+				}
+				mock_library {
+					name: "libexample",
+					defaults: ["libexample_defaults"],
+				}
+
+				gen_notice {
+					name: "libexample-notice",
+					for: ["libexample"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "nested-notice" references "//top:libexample" which is not visible to this module`,
+			`module "other-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "//visibility:private mixed with other in defaults",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -706,6 +1099,27 @@
 		},
 	},
 	{
+		name: "//visibility:override discards //visibility:private (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//visibility:private"],
+				}
+				mock_library {
+					name: "libexample",
+					// Make this visibility to //other but not //visibility:private
+					visibility: ["//visibility:override", "//other"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		name: "//visibility:override discards //visibility:public",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -735,6 +1149,35 @@
 		},
 	},
 	{
+		name: "//visibility:override discards //visibility:public (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//visibility:public"],
+				}
+				mock_library {
+					name: "libexample",
+					// Make this visibility to //other but not //visibility:public
+					visibility: ["//visibility:override", "//other"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+			"namespace/Android.bp": []byte(`
+				gen_notice {
+					name: "namespace-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "namespace-notice" references "//top:libexample" which is not visible to this module\nYou may need to add "//namespace" to its visibility`,
+		},
+	},
+	{
 		name: "//visibility:override discards defaults supplied rules",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -764,6 +1207,35 @@
 		},
 	},
 	{
+		name: "//visibility:override discards defaults supplied rules (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//namespace"],
+				}
+				mock_library {
+					name: "libexample",
+					// Make this visibility to //other but not //namespace
+					visibility: ["//visibility:override", "//other"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libexample"],
+				}`),
+			"namespace/Android.bp": []byte(`
+				gen_notice {
+					name: "namespace-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "namespace-notice" references "//top:libexample" which is not visible to this module\nYou may need to add "//namespace" to its visibility`,
+		},
+	},
+	{
 		name: "//visibility:override can override //visibility:public with //visibility:private",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -787,6 +1259,29 @@
 		},
 	},
 	{
+		name: "//visibility:override can override //visibility:public with //visibility:private (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//visibility:public"],
+				}
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:override", "//visibility:private"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"namespace/Android.bp": []byte(`
+				gen_notice {
+					name: "namespace-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "namespace-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "//visibility:override can override //visibility:private with //visibility:public",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -807,6 +1302,26 @@
 		},
 	},
 	{
+		name: "//visibility:override can override //visibility:private with //visibility:public (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults",
+					visibility: ["//visibility:private"],
+				}
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:override", "//visibility:public"],
+					defaults: ["libexample_defaults"],
+				}`),
+			"namespace/Android.bp": []byte(`
+				gen_notice {
+					name: "namespace-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		name: "//visibility:private mixed with itself",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -834,6 +1349,33 @@
 				` visible to this module`,
 		},
 	},
+	{
+		name: "//visibility:private mixed with itself (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_defaults {
+					name: "libexample_defaults_1",
+					visibility: ["//visibility:private"],
+				}
+				mock_defaults {
+					name: "libexample_defaults_2",
+					visibility: ["//visibility:private"],
+				}
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:private"],
+					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
 
 	// Defaults module's defaults_visibility tests
 	{
@@ -903,6 +1445,28 @@
 		},
 	},
 	{
+		// This test relies on the default visibility being legacy_public.
+		name: "package default_visibility property used when no visibility specified (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: ["//visibility:private"],
+				}
+
+				mock_library {
+					name: "libexample",
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "package default_visibility public does not override visibility private",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -926,6 +1490,28 @@
 		},
 	},
 	{
+		name: "package default_visibility public does not override visibility private (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: ["//visibility:public"],
+				}
+
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:private"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "package default_visibility private does not override visibility public",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -945,6 +1531,25 @@
 		},
 	},
 	{
+		name: "package default_visibility private does not override visibility public (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: ["//visibility:private"],
+				}
+
+				mock_library {
+					name: "libexample",
+					visibility: ["//visibility:public"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+	},
+	{
 		name: "package default_visibility :__subpackages__",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -972,6 +1577,32 @@
 		},
 	},
 	{
+		name: "package default_visibility :__subpackages__ (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: [":__subpackages__"],
+				}
+
+				mock_library {
+					name: "libexample",
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "package default_visibility inherited to subpackages",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -981,7 +1612,7 @@
 
 				mock_library {
 					name: "libexample",
-          visibility: [":__subpackages__"],
+					visibility: [":__subpackages__"],
 				}`),
 			"top/nested/Android.bp": []byte(`
 				mock_library {
@@ -1000,6 +1631,38 @@
 		},
 	},
 	{
+		name: "package default_visibility inherited to subpackages (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: ["//outsider"],
+				}
+
+				mock_library {
+					name: "libexample",
+					visibility: [":__subpackages__"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				mock_library {
+					name: "libnested",
+					deps: ["libexample"],
+				}
+
+				gen_notice {
+					name: "nested-notice",
+					for: ["libexample"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libexample", "libnested"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top:libexample" which is not visible to this module`,
+		},
+	},
+	{
 		name: "package default_visibility inherited to subpackages",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -1030,6 +1693,41 @@
 		},
 	},
 	{
+		name: "package default_visibility inherited to subpackages (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				package {
+					default_visibility: ["//visibility:private"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				package {
+					default_visibility: ["//outsider"],
+				}
+
+				mock_library {
+					name: "libnested",
+				}`),
+			"top/other/Android.bp": []byte(`
+				mock_library {
+					name: "libother",
+				}
+
+				gen_notice {
+					name: "other-notice",
+					for: ["libother"],
+				}`),
+			"outsider/Android.bp": []byte(`
+				gen_notice {
+					name: "outsider-notice",
+					for: ["libother", "libnested"],
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outsider-notice" references "//top/other:libother" which is not visible to this` +
+				` module\nYou may need to add "//outsider" to its visibility`,
+		},
+	},
+	{
 		name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred)",
 		fs: MockFS{
 			"prebuilts/Android.bp": []byte(`
@@ -1052,6 +1750,28 @@
 		},
 	},
 	{
+		name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred) (notices)",
+		fs: MockFS{
+			"prebuilts/Android.bp": []byte(`
+				prebuilt {
+					name: "module",
+					visibility: ["//top/other"],
+				}`),
+			"top/sources/source_file": nil,
+			"top/sources/Android.bp": []byte(`
+				source {
+					name: "module",
+					visibility: ["//top/other"],
+				}`),
+			"top/other/source_file": nil,
+			"top/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["module"],
+				}`),
+		},
+	},
+	{
 		name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred)",
 		fs: MockFS{
 			"prebuilts/Android.bp": []byte(`
@@ -1075,6 +1795,29 @@
 		},
 	},
 	{
+		name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred) (notices)",
+		fs: MockFS{
+			"prebuilts/Android.bp": []byte(`
+				prebuilt {
+					name: "module",
+					visibility: ["//top/other"],
+					prefer: true,
+				}`),
+			"top/sources/source_file": nil,
+			"top/sources/Android.bp": []byte(`
+				source {
+					name: "module",
+					visibility: ["//top/other"],
+				}`),
+			"top/other/source_file": nil,
+			"top/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["module"],
+				}`),
+		},
+	},
+	{
 		name: "ensure visibility properties are checked for correctness",
 		fs: MockFS{
 			"top/Android.bp": []byte(`
@@ -1137,6 +1880,30 @@
 				}`),
 		},
 	},
+	{
+		name: "automatic visibility inheritance enabled (notices)",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_parent {
+					name: "parent",
+					visibility: ["//top/nested"],
+					child: {
+						name: "libchild",
+						visibility: ["//top/other"],
+					},
+				}`),
+			"top/nested/Android.bp": []byte(`
+				gen_notice {
+					name: "nested-notice",
+					for: ["libchild"],
+				}`),
+			"top/other/Android.bp": []byte(`
+				gen_notice {
+					name: "other-notice",
+					for: ["libchild"],
+				}`),
+		},
+	},
 }
 
 func TestVisibility(t *testing.T) {
@@ -1147,6 +1914,7 @@
 				// registration order.
 				PrepareForTestWithArchMutator,
 				PrepareForTestWithDefaults,
+				PrepareForTestWithGenNotice,
 				PrepareForTestWithOverrides,
 				PrepareForTestWithPackageModule,
 				PrepareForTestWithPrebuilts,
diff --git a/android_sdk/sdk_repo_host.go b/android_sdk/sdk_repo_host.go
index f050a2e..9519be0 100644
--- a/android_sdk/sdk_repo_host.go
+++ b/android_sdk/sdk_repo_host.go
@@ -107,6 +107,7 @@
 
 func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	dir := android.PathForModuleOut(ctx, "zip")
+	outputZipFile := dir.Join(ctx, "output.zip")
 	builder := android.NewRuleBuilder(pctx, ctx).
 		Sbox(dir, android.PathForModuleOut(ctx, "out.sbox.textproto")).
 		SandboxInputs()
@@ -123,7 +124,7 @@
 	s.CopySpecsToDir(ctx, builder, packageSpecs, dir)
 
 	noticeFile := android.PathForModuleOut(ctx, "NOTICES.txt")
-	android.BuildNoticeTextOutputFromLicenseMetadata(ctx, noticeFile)
+	android.BuildNoticeTextOutputFromLicenseMetadata(ctx, noticeFile, "", "", outputZipFile.String())
 	builder.Command().Text("cp").
 		Input(noticeFile).
 		Text(filepath.Join(dir.String(), "NOTICE.txt"))
@@ -209,7 +210,6 @@
 	}
 
 	// Zip up our temporary directory as the sdk-repo
-	outputZipFile := dir.Join(ctx, "output.zip")
 	builder.Command().
 		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputZipFile).
diff --git a/apex/apex.go b/apex/apex.go
index eac7cf2..83dd8b0 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -608,30 +608,34 @@
 	sourceOnly bool
 }
 
-func (d dependencyTag) ReplaceSourceWithPrebuilt() bool {
+func (d *dependencyTag) String() string {
+	return fmt.Sprintf("apex.dependencyTag{%q}", d.name)
+}
+
+func (d *dependencyTag) ReplaceSourceWithPrebuilt() bool {
 	return !d.sourceOnly
 }
 
 var _ android.ReplaceSourceWithPrebuilt = &dependencyTag{}
 
 var (
-	androidAppTag   = dependencyTag{name: "androidApp", payload: true}
-	bpfTag          = dependencyTag{name: "bpf", payload: true}
-	certificateTag  = dependencyTag{name: "certificate"}
-	executableTag   = dependencyTag{name: "executable", payload: true}
-	fsTag           = dependencyTag{name: "filesystem", payload: true}
-	bcpfTag         = dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true}
-	sscpfTag        = dependencyTag{name: "systemserverclasspathFragment", payload: true, sourceOnly: true}
-	compatConfigTag = dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
-	javaLibTag      = dependencyTag{name: "javaLib", payload: true}
-	jniLibTag       = dependencyTag{name: "jniLib", payload: true}
-	keyTag          = dependencyTag{name: "key"}
-	prebuiltTag     = dependencyTag{name: "prebuilt", payload: true}
-	rroTag          = dependencyTag{name: "rro", payload: true}
-	sharedLibTag    = dependencyTag{name: "sharedLib", payload: true}
-	testForTag      = dependencyTag{name: "test for"}
-	testTag         = dependencyTag{name: "test", payload: true}
-	shBinaryTag     = dependencyTag{name: "shBinary", payload: true}
+	androidAppTag   = &dependencyTag{name: "androidApp", payload: true}
+	bpfTag          = &dependencyTag{name: "bpf", payload: true}
+	certificateTag  = &dependencyTag{name: "certificate"}
+	executableTag   = &dependencyTag{name: "executable", payload: true}
+	fsTag           = &dependencyTag{name: "filesystem", payload: true}
+	bcpfTag         = &dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true}
+	sscpfTag        = &dependencyTag{name: "systemserverclasspathFragment", payload: true, sourceOnly: true}
+	compatConfigTag = &dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
+	javaLibTag      = &dependencyTag{name: "javaLib", payload: true}
+	jniLibTag       = &dependencyTag{name: "jniLib", payload: true}
+	keyTag          = &dependencyTag{name: "key"}
+	prebuiltTag     = &dependencyTag{name: "prebuilt", payload: true}
+	rroTag          = &dependencyTag{name: "rro", payload: true}
+	sharedLibTag    = &dependencyTag{name: "sharedLib", payload: true}
+	testForTag      = &dependencyTag{name: "test for"}
+	testTag         = &dependencyTag{name: "test", payload: true}
+	shBinaryTag     = &dependencyTag{name: "shBinary", payload: true}
 )
 
 // TODO(jiyong): shorten this function signature
@@ -1752,7 +1756,7 @@
 		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
 			return false
 		}
-		if dt, ok := depTag.(dependencyTag); ok && !dt.payload {
+		if dt, ok := depTag.(*dependencyTag); ok && !dt.payload {
 			return false
 		}
 
@@ -3221,33 +3225,6 @@
 	//
 	// Module separator
 	//
-	m["com.android.permission"] = []string{
-		"car-ui-lib",
-		"iconloader",
-		"kotlin-annotations",
-		"kotlin-stdlib",
-		"kotlin-stdlib-jdk7",
-		"kotlin-stdlib-jdk8",
-		"kotlinx-coroutines-android",
-		"kotlinx-coroutines-android-nodeps",
-		"kotlinx-coroutines-core",
-		"kotlinx-coroutines-core-nodeps",
-		"permissioncontroller-statsd",
-		"GooglePermissionController",
-		"PermissionController",
-		"SettingsLibActionBarShadow",
-		"SettingsLibAppPreference",
-		"SettingsLibBarChartPreference",
-		"SettingsLibLayoutPreference",
-		"SettingsLibProgressBar",
-		"SettingsLibSearchWidget",
-		"SettingsLibSettingsTheme",
-		"SettingsLibRestrictedLockUtils",
-		"SettingsLibHelpUtils",
-	}
-	//
-	// Module separator
-	//
 	m["com.android.runtime"] = []string{
 		"bionic_libc_platform_headers",
 		"libarm-optimized-routines-math",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index bcb55af..07372a3 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -5931,7 +5931,7 @@
 func TestApexAvailable_IndirectDep(t *testing.T) {
 	// libbbaz is an indirect dep
 	testApexError(t, `requires "libbaz" that doesn't list the APEX under 'apex_available'.\n\nDependency path:
-.*via tag apex\.dependencyTag.*name:sharedLib.*
+.*via tag apex\.dependencyTag\{"sharedLib"\}
 .*-> libfoo.*link:shared.*
 .*via tag cc\.libraryDependencyTag.*Kind:sharedLibraryDependency.*
 .*-> libbar.*link:shared.*
diff --git a/apex/builder.go b/apex/builder.go
index abbf8ad..9119363 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -618,7 +618,7 @@
 
 		// Create a NOTICE file, and embed it as an asset file in the APEX.
 		a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE.html.gz")
-		android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice)
+		android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice, "", "", unsignedOutputFile.String())
 		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
 		builder := android.NewRuleBuilder(pctx, ctx)
 		builder.Command().Text("cp").
diff --git a/bazel/aquery.go b/bazel/aquery.go
index e05cbd6..ee09d0b 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -15,10 +15,13 @@
 package bazel
 
 import (
+	"crypto/sha256"
 	"encoding/json"
 	"fmt"
 	"path/filepath"
+	"reflect"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -44,15 +47,16 @@
 }
 
 // AqueryDepset is a depset definition from Bazel's aquery response. This is
-// akin to the `depSetOfFiles` in the response proto, except that direct
-// artifacts are enumerated by full path instead of by ID.
+// akin to the `depSetOfFiles` in the response proto, except:
+//   * direct artifacts are enumerated by full path instead of by ID
+//   * it has a hash of the depset contents, instead of an int ID (for determinism)
 // A depset is a data structure for efficient transitive handling of artifact
 // paths. A single depset consists of one or more artifact paths and one or
 // more "child" depsets.
 type AqueryDepset struct {
-	Id                  int
-	DirectArtifacts     []string
-	TransitiveDepSetIds []int
+	ContentHash            string
+	DirectArtifacts        []string
+	TransitiveDepSetHashes []string
 }
 
 // depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
@@ -99,19 +103,24 @@
 	// input paths. There should be no overlap between these fields; an input
 	// path should either be included as part of an unexpanded depset or a raw
 	// input path string, but not both.
-	InputDepsetIds []int
-	InputPaths     []string
+	InputDepsetHashes []string
+	InputPaths        []string
 }
 
 // A helper type for aquery processing which facilitates retrieval of path IDs from their
 // less readable Bazel structures (depset and path fragment).
 type aqueryArtifactHandler struct {
-	// Maps depset Id to depset struct.
-	depsetIdToDepset map[int]depSetOfFiles
+	// Maps depset id to AqueryDepset, a representation of depset which is
+	// post-processed for middleman artifact handling, unhandled artifact
+	// dropping, content hashing, etc.
+	depsetIdToAqueryDepset map[int]AqueryDepset
+	// Maps content hash to AqueryDepset.
+	depsetHashToAqueryDepset map[string]AqueryDepset
+
 	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
 	// may be an expensive operation.
-	depsetIdToArtifactIdsCache map[int][]int
-	// Maps artifact Id to fully expanded path.
+	depsetHashToArtifactPathsCache map[string][]string
+	// Maps artifact ContentHash to fully expanded path.
 	artifactIdToPath map[int]string
 }
 
@@ -127,7 +136,7 @@
 var manifestFilePattern = regexp.MustCompile(".*/.+\\.runfiles/MANIFEST$")
 
 // The file name of py3wrapper.sh, which is used by py_binary targets.
-var py3wrapperFileName = "/py3wrapper.sh"
+const py3wrapperFileName = "/py3wrapper.sh"
 
 func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
 	pathFragments := map[int]pathFragment{}
@@ -144,7 +153,7 @@
 		artifactIdToPath[artifact.Id] = artifactPath
 	}
 
-	// Map middleman artifact Id to input artifact depset ID.
+	// Map middleman artifact ContentHash to input artifact depset ID.
 	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
 	// if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
 	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
@@ -165,50 +174,84 @@
 	}
 
 	depsetIdToDepset := map[int]depSetOfFiles{}
-	// Validate and adjust aqueryResult.DepSetOfFiles values.
 	for _, depset := range aqueryResult.DepSetOfFiles {
-		filteredArtifactIds := []int{}
-		for _, artifactId := range depset.DirectArtifactIds {
-			path, pathExists := artifactIdToPath[artifactId]
-			if !pathExists {
-				return nil, fmt.Errorf("undefined input artifactId %d", artifactId)
-			}
-			// Filter out any inputs which are universally dropped, and swap middleman
-			// artifacts with their corresponding depsets.
-			if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
-				// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
-				depset.TransitiveDepSetIds = append(depset.TransitiveDepSetIds, depsetsToUse...)
-			} else if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
-				// Drop these artifacts.
-				// See go/python-binary-host-mixed-build for more details.
-				// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
-				// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
-				// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
-				// but it doesn't contain sufficient information so no Ninja build statements are generated
-				// for creating it.
-				// So in mixed build mode, when these two are used as input of some Ninja build statement,
-				// since there is no build statement to create them, they should be removed from input paths.
-				// TODO(b/197135294): Clean up this custom runfiles handling logic when
-				// SourceSymlinkManifest and SymlinkTree actions are supported.
-			} else {
-				// TODO(b/216194240): Filter out bazel tools.
-				filteredArtifactIds = append(filteredArtifactIds, artifactId)
-			}
-		}
-		depset.DirectArtifactIds = filteredArtifactIds
-		for _, childDepsetId := range depset.TransitiveDepSetIds {
-			if _, exists := depsetIds[childDepsetId]; !exists {
-				return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
-			}
-		}
 		depsetIdToDepset[depset.Id] = depset
 	}
 
-	return &aqueryArtifactHandler{
-		depsetIdToDepset:           depsetIdToDepset,
-		depsetIdToArtifactIdsCache: map[int][]int{},
-		artifactIdToPath:           artifactIdToPath,
-	}, nil
+	aqueryHandler := aqueryArtifactHandler{
+		depsetIdToAqueryDepset:         map[int]AqueryDepset{},
+		depsetHashToAqueryDepset:       map[string]AqueryDepset{},
+		depsetHashToArtifactPathsCache: map[string][]string{},
+		artifactIdToPath:               artifactIdToPath,
+	}
+
+	// Validate and adjust aqueryResult.DepSetOfFiles values.
+	for _, depset := range aqueryResult.DepSetOfFiles {
+		_, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &aqueryHandler, nil
+}
+
+// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
+// depset.
+func (a *aqueryArtifactHandler) populateDepsetMaps(depset depSetOfFiles, middlemanIdToDepsetIds map[int][]int, depsetIdToDepset map[int]depSetOfFiles) (AqueryDepset, error) {
+	if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depset.Id]; containsDepset {
+		return aqueryDepset, nil
+	}
+	transitiveDepsetIds := depset.TransitiveDepSetIds
+	directArtifactPaths := []string{}
+	for _, artifactId := range depset.DirectArtifactIds {
+		path, pathExists := a.artifactIdToPath[artifactId]
+		if !pathExists {
+			return AqueryDepset{}, fmt.Errorf("undefined input artifactId %d", artifactId)
+		}
+		// Filter out any inputs which are universally dropped, and swap middleman
+		// artifacts with their corresponding depsets.
+		if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
+			// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
+			transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
+		} else if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
+			// Drop these artifacts.
+			// See go/python-binary-host-mixed-build for more details.
+			// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
+			// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
+			// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+			// but it doesn't contain sufficient information so no Ninja build statements are generated
+			// for creating it.
+			// So in mixed build mode, when these two are used as input of some Ninja build statement,
+			// since there is no build statement to create them, they should be removed from input paths.
+			// TODO(b/197135294): Clean up this custom runfiles handling logic when
+			// SourceSymlinkManifest and SymlinkTree actions are supported.
+		} else {
+			// TODO(b/216194240): Filter out bazel tools.
+			directArtifactPaths = append(directArtifactPaths, path)
+		}
+	}
+
+	childDepsetHashes := []string{}
+	for _, childDepsetId := range transitiveDepsetIds {
+		childDepset, exists := depsetIdToDepset[childDepsetId]
+		if !exists {
+			return AqueryDepset{}, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
+		}
+		childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset)
+		if err != nil {
+			return AqueryDepset{}, err
+		}
+		childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
+	}
+	aqueryDepset := AqueryDepset{
+		ContentHash:            depsetContentHash(directArtifactPaths, childDepsetHashes),
+		DirectArtifacts:        directArtifactPaths,
+		TransitiveDepSetHashes: childDepsetHashes,
+	}
+	a.depsetIdToAqueryDepset[depset.Id] = aqueryDepset
+	a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
+	return aqueryDepset, nil
 }
 
 // getInputPaths flattens the depsets of the given IDs and returns all transitive
@@ -219,15 +262,12 @@
 	inputPaths := []string{}
 
 	for _, inputDepSetId := range depsetIds {
-		inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
+		depset := a.depsetIdToAqueryDepset[inputDepSetId]
+		inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
 		if err != nil {
 			return nil, err
 		}
-		for _, inputId := range inputArtifacts {
-			inputPath, exists := a.artifactIdToPath[inputId]
-			if !exists {
-				return nil, fmt.Errorf("undefined input artifactId %d", inputId)
-			}
+		for _, inputPath := range inputArtifacts {
 			inputPaths = append(inputPaths, inputPath)
 		}
 	}
@@ -235,23 +275,23 @@
 	return inputPaths, nil
 }
 
-func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
-	if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
+func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
+	if result, exists := a.depsetHashToArtifactPathsCache[depsetHash]; exists {
 		return result, nil
 	}
-	if depset, exists := a.depsetIdToDepset[depsetId]; exists {
-		result := depset.DirectArtifactIds
-		for _, childId := range depset.TransitiveDepSetIds {
-			childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
+	if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
+		result := depset.DirectArtifacts
+		for _, childHash := range depset.TransitiveDepSetHashes {
+			childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
 			if err != nil {
 				return nil, err
 			}
 			result = append(result, childArtifactIds...)
 		}
-		a.depsetIdToArtifactIdsCache[depsetId] = result
+		a.depsetHashToArtifactPathsCache[depsetHash] = result
 		return result, nil
 	} else {
-		return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+		return nil, fmt.Errorf("undefined input depset hash %d", depsetHash)
 	}
 }
 
@@ -261,9 +301,6 @@
 // BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
 // are one-to-one with Bazel's depSetOfFiles objects.
 func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, []AqueryDepset, error) {
-	buildStatements := []BuildStatement{}
-	depsets := []AqueryDepset{}
-
 	var aqueryResult actionGraphContainer
 	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
 	if err != nil {
@@ -274,6 +311,8 @@
 		return nil, nil, err
 	}
 
+	var buildStatements []BuildStatement
+
 	for _, actionEntry := range aqueryResult.Actions {
 		if shouldSkipAction(actionEntry) {
 			continue
@@ -298,38 +337,75 @@
 		buildStatements = append(buildStatements, buildStatement)
 	}
 
-	// Iterate over depset IDs in the initial aquery order to preserve determinism.
-	for _, depset := range aqueryResult.DepSetOfFiles {
-		// Use the depset in the aqueryHandler, as this contains the augmented depsets.
-		depset = aqueryHandler.depsetIdToDepset[depset.Id]
-		directPaths := []string{}
-		for _, artifactId := range depset.DirectArtifactIds {
-			pathString := aqueryHandler.artifactIdToPath[artifactId]
-			directPaths = append(directPaths, pathString)
+	depsetsByHash := map[string]AqueryDepset{}
+	depsets := []AqueryDepset{}
+	for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
+		if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
+			// Two depsets collide on hash. Ensure that their contents are identical.
+			if !reflect.DeepEqual(aqueryDepset, prevEntry) {
+				return nil, nil, fmt.Errorf("Two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
+			}
+		} else {
+			depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
+			depsets = append(depsets, aqueryDepset)
 		}
-		aqueryDepset := AqueryDepset{
-			Id:                  depset.Id,
-			DirectArtifacts:     directPaths,
-			TransitiveDepSetIds: depset.TransitiveDepSetIds,
-		}
-		depsets = append(depsets, aqueryDepset)
 	}
+
+	// Build Statements and depsets must be sorted by their content hash to
+	// preserve determinism between builds (this will result in consistent ninja file
+	// output). Note they are not sorted by their original IDs nor their Bazel ordering,
+	// as Bazel gives nondeterministic ordering / identifiers in aquery responses.
+	sort.Slice(buildStatements, func(i, j int) bool {
+		// For build statements, compare output lists. In Bazel, each output file
+		// may only have one action which generates it, so this will provide
+		// a deterministic ordering.
+		outputs_i := buildStatements[i].OutputPaths
+		outputs_j := buildStatements[j].OutputPaths
+		if len(outputs_i) != len(outputs_j) {
+			return len(outputs_i) < len(outputs_j)
+		}
+		if len(outputs_i) == 0 {
+			// No outputs for these actions, so compare commands.
+			return buildStatements[i].Command < buildStatements[j].Command
+		}
+		// There may be multiple outputs, but the output ordering is deterministic.
+		return outputs_i[0] < outputs_j[0]
+	})
+	sort.Slice(depsets, func(i, j int) bool {
+		return depsets[i].ContentHash < depsets[j].ContentHash
+	})
 	return buildStatements, depsets, nil
 }
 
-func (aqueryHandler *aqueryArtifactHandler) validateInputDepsets(inputDepsetIds []int) ([]int, error) {
-	// Validate input depsets correspond to real depsets.
+// depsetContentHash computes and returns a SHA256 checksum of the contents of
+// the given depset. This content hash may serve as the depset's identifier.
+// Using a content hash for an identifier is superior for determinism. (For example,
+// using an integer identifier which depends on the order in which the depsets are
+// created would result in nondeterministic depset IDs.)
+func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
+	h := sha256.New()
+	// Use newline as delimiter, as paths cannot contain newline.
+	h.Write([]byte(strings.Join(directPaths, "\n")))
+	h.Write([]byte(strings.Join(transitiveDepsetHashes, "\n")))
+	fullHash := fmt.Sprintf("%016x", h.Sum(nil))
+	return fullHash
+}
+
+func (aqueryHandler *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []int) ([]string, error) {
+	hashes := []string{}
 	for _, depsetId := range inputDepsetIds {
-		if _, exists := aqueryHandler.depsetIdToDepset[depsetId]; !exists {
+		if aqueryDepset, exists := aqueryHandler.depsetIdToAqueryDepset[depsetId]; !exists {
 			return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+		} else {
+			hashes = append(hashes, aqueryDepset.ContentHash)
 		}
 	}
-	return inputDepsetIds, nil
+	return hashes, nil
 }
 
 func (aqueryHandler *aqueryArtifactHandler) normalActionBuildStatement(actionEntry action) (BuildStatement, error) {
 	command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
-	inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+	inputDepsetHashes, err := aqueryHandler.depsetContentHashes(actionEntry.InputDepSetIds)
 	if err != nil {
 		return BuildStatement{}, err
 	}
@@ -339,12 +415,12 @@
 	}
 
 	buildStatement := BuildStatement{
-		Command:        command,
-		Depfile:        depfile,
-		OutputPaths:    outputPaths,
-		InputDepsetIds: inputDepsetIds,
-		Env:            actionEntry.EnvironmentVariables,
-		Mnemonic:       actionEntry.Mnemonic,
+		Command:           command,
+		Depfile:           depfile,
+		OutputPaths:       outputPaths,
+		InputDepsetHashes: inputDepsetHashes,
+		Env:               actionEntry.EnvironmentVariables,
+		Mnemonic:          actionEntry.Mnemonic,
 	}
 	return buildStatement, nil
 }
@@ -413,18 +489,18 @@
 	// See go/python-binary-host-mixed-build for more details.
 	command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
 		escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
-	inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+	inputDepsetHashes, err := aqueryHandler.depsetContentHashes(actionEntry.InputDepSetIds)
 	if err != nil {
 		return BuildStatement{}, err
 	}
 
 	buildStatement := BuildStatement{
-		Command:        command,
-		Depfile:        depfile,
-		OutputPaths:    outputPaths,
-		InputDepsetIds: inputDepsetIds,
-		Env:            actionEntry.EnvironmentVariables,
-		Mnemonic:       actionEntry.Mnemonic,
+		Command:           command,
+		Depfile:           depfile,
+		OutputPaths:       outputPaths,
+		InputDepsetHashes: inputDepsetHashes,
+		Env:               actionEntry.EnvironmentVariables,
+		Mnemonic:          actionEntry.Mnemonic,
 	}
 	return buildStatement, nil
 }
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 2328411..740a1f1 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -234,7 +234,6 @@
 				OutputPaths: []string{
 					fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
 				},
-				InputDepsetIds: []int{1},
 				Env: []KeyValuePair{
 					KeyValuePair{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
 				},
@@ -248,9 +247,12 @@
 		"../sourceroot/bionic/libc/tools/gensyscalls.py",
 		"../bazel_tools/tools/genrule/genrule-setup.sh",
 	}
-	actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
-	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+	// In this example, each depset should have the same expected inputs.
+	for _, actualDepset := range actualDepsets {
+		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
+		if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+			t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+		}
 	}
 }
 
@@ -746,10 +748,9 @@
 
 	expectedBuildStatements := []BuildStatement{
 		BuildStatement{
-			Command:        "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
-			OutputPaths:    []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
-			InputDepsetIds: []int{1},
-			Mnemonic:       "Action",
+			Command:     "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
+			OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
+			Mnemonic:    "Action",
 		},
 	}
 	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
@@ -763,7 +764,8 @@
 	}
 	expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
 
-	actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
+	actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes
+	actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets)
 	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
 		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
 	}
@@ -844,38 +846,24 @@
 		t.Fatalf("Expected %d build statements, got %d", expected, len(actualBuildStatements))
 	}
 
+	expectedDepsetFiles := [][]string{
+		[]string{"middleinput_one", "middleinput_two"},
+		[]string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"},
+	}
+	assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles)
+
 	bs := actualBuildStatements[0]
 	if len(bs.InputPaths) > 0 {
 		t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
 	}
 
-	expectedInputDepsets := []int{2}
-	if !reflect.DeepEqual(bs.InputDepsetIds, expectedInputDepsets) {
-		t.Errorf("Expected main action depset IDs %v, but got %v", expectedInputDepsets, bs.InputDepsetIds)
-	}
-
 	expectedOutputs := []string{"output"}
 	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
 		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
 	}
 
-	expectedAllDepsets := []AqueryDepset{
-		{
-			Id:              1,
-			DirectArtifacts: []string{"middleinput_one", "middleinput_two"},
-		},
-		{
-			Id:                  2,
-			DirectArtifacts:     []string{"maininput_one", "maininput_two"},
-			TransitiveDepSetIds: []int{1},
-		},
-	}
-	if !reflect.DeepEqual(actualDepsets, expectedAllDepsets) {
-		t.Errorf("Expected depsets %v, but got %v", expectedAllDepsets, actualDepsets)
-	}
-
 	expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
-	actualFlattenedInputs := flattenDepsets(bs.InputDepsetIds, actualDepsets)
+	actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets)
 
 	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
 		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
@@ -883,29 +871,42 @@
 }
 
 // Returns the contents of given depsets in concatenated post order.
-func flattenDepsets(depsetIdsToFlatten []int, allDepsets []AqueryDepset) []string {
-	depsetsById := map[int]AqueryDepset{}
+func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string {
+	depsetsByHash := map[string]AqueryDepset{}
 	for _, depset := range allDepsets {
-		depsetsById[depset.Id] = depset
+		depsetsByHash[depset.ContentHash] = depset
 	}
 	result := []string{}
-	for _, depsetId := range depsetIdsToFlatten {
-		result = append(result, flattenDepset(depsetId, depsetsById)...)
+	for _, depsetId := range depsetHashesToFlatten {
+		result = append(result, flattenDepset(depsetId, depsetsByHash)...)
 	}
 	return result
 }
 
 // Returns the contents of a given depset in post order.
-func flattenDepset(depsetIdToFlatten int, allDepsets map[int]AqueryDepset) []string {
-	depset := allDepsets[depsetIdToFlatten]
+func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string {
+	depset := allDepsets[depsetHashToFlatten]
 	result := []string{}
-	for _, depsetId := range depset.TransitiveDepSetIds {
+	for _, depsetId := range depset.TransitiveDepSetHashes {
 		result = append(result, flattenDepset(depsetId, allDepsets)...)
 	}
 	result = append(result, depset.DirectArtifacts...)
 	return result
 }
 
+func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) {
+	t.Helper()
+	if len(actualDepsets) != len(expectedDepsetFiles) {
+		t.Errorf("Expected %d depsets, but got %d depsets", expectedDepsetFiles, actualDepsets)
+	}
+	for i, actualDepset := range actualDepsets {
+		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
+		if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) {
+			t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs)
+		}
+	}
+}
+
 func TestSimpleSymlink(t *testing.T) {
 	const inputString = `
 {
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 2775a10..ab92981 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -1914,9 +1914,9 @@
 		{cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17"},
 
 		// some c_std test cases
-		{c_std: "experimental", gnu_extensions: "", bazel_c_std: "gnu11"},
-		{c_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
-		{c_std: "experimental", gnu_extensions: "true", bazel_c_std: "gnu11"},
+		{c_std: "experimental", gnu_extensions: "", bazel_c_std: "gnu17"},
+		{c_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c17"},
+		{c_std: "experimental", gnu_extensions: "true", bazel_c_std: "gnu17"},
 		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
 		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
 		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 1790dd7..4b013d9 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -152,6 +152,7 @@
 		"target":     true, // interface prop type is not supported yet.
 		"visibility": true, // Bazel has native visibility semantics. Handle later.
 		"features":   true, // There is already a built-in attribute 'features' which cannot be overridden.
+		"for":        true, // reserved keyword, b/233579439
 	}
 )
 
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 7d48191..818d7ae 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -95,13 +95,19 @@
 		return fi.IsDir()
 	}
 
-	fi2, err := os.Stat(path)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Cannot stat '%s': %s\n", path, err)
+	fi2, statErr := os.Stat(path)
+	if statErr == nil {
+		return fi2.IsDir()
+	}
+
+	// Check if this is a dangling symlink. If so, treat it like a file, not a dir.
+	_, lstatErr := os.Lstat(path)
+	if lstatErr != nil {
+		fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
 		os.Exit(1)
 	}
 
-	return fi2.IsDir()
+	return false
 }
 
 // Recursively plants a symlink forest at forestDir. The symlink tree will
diff --git a/cc/binary.go b/cc/binary.go
index 3e52fbc..1e9f48c 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -17,6 +17,7 @@
 import (
 	"path/filepath"
 
+	"android/soong/bazel/cquery"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -562,25 +563,32 @@
 }
 
 type ccBinaryBazelHandler struct {
-	android.BazelHandler
+	BazelHandler
 
 	module *Module
 }
 
-func (handler *ccBinaryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *ccBinaryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
-	if ok {
-		if len(filePaths) != 1 {
-			ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
-			return false
-		}
-		outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
-		handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
-		// TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
-		handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
+	bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
+
+func (handler *ccBinaryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
-	return ok
+
+	if len(filePaths) != 1 {
+		ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
+		return
+	}
+	outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+	// TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
+	handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
 }
 
 func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 19855fa..a2041f4 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -684,6 +684,13 @@
 		la.additionalLinkerInputs.SetSelectValue(axis, config, bazel.LabelList{Includes: []bazel.Label{label}})
 		linkerFlags = append(linkerFlags, fmt.Sprintf("-Wl,--version-script,$(location %s)", label.Label))
 	}
+
+	if props.Dynamic_list != nil {
+		label := android.BazelLabelForModuleSrcSingle(ctx, *props.Dynamic_list)
+		la.additionalLinkerInputs.SetSelectValue(axis, config, bazel.LabelList{Includes: []bazel.Label{label}})
+		linkerFlags = append(linkerFlags, fmt.Sprintf("-Wl,--dynamic-list,$(location %s)", label.Label))
+	}
+
 	la.linkopts.SetSelectValue(axis, config, linkerFlags)
 	la.useLibcrt.SetSelectValue(axis, config, props.libCrt())
 
diff --git a/cc/cc.go b/cc/cc.go
index f1a1983..da8a807 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -772,6 +772,19 @@
 	return ok && ccDepTag == testPerSrcDepTag
 }
 
+// bazelHandler is the interface for a helper object related to deferring to Bazel for
+// processing a cc module (during Bazel mixed builds). Individual module types should define
+// their own bazel handler if they support being handled by Bazel.
+type BazelHandler interface {
+	// QueueBazelCall invokes request-queueing functions on the BazelContext
+	//so that these requests are handled when Bazel's cquery is invoked.
+	QueueBazelCall(ctx android.BaseModuleContext, label string)
+
+	// ProcessBazelQueryResponse uses information retrieved from Bazel to set properties
+	// on the current module with given label.
+	ProcessBazelQueryResponse(ctx android.ModuleContext, label string)
+}
+
 // Module contains the properties and members used by all C/C++ module types, and implements
 // the blueprint.Module interface.  It delegates to compiler, linker, and installer interfaces
 // to construct the output file.  Behavior can be customized with a Customizer, or "decorator",
@@ -811,7 +824,7 @@
 	compiler     compiler
 	linker       linker
 	installer    installer
-	bazelHandler android.BazelHandler
+	bazelHandler BazelHandler
 
 	features []feature
 	stl      *stl
@@ -1773,31 +1786,51 @@
 	return subName
 }
 
-// Returns true if Bazel was successfully used for the analysis of this module.
-func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
-	var bazelModuleLabel string
+var _ android.MixedBuildBuildable = (*Module)(nil)
+
+func (c *Module) getBazelModuleLabel(ctx android.BaseModuleContext) string {
 	if c.typ() == fullLibrary && c.static() {
 		// cc_library is a special case in bp2build; two targets are generated -- one for each
 		// of the shared and static variants. The shared variant keeps the module name, but the
 		// static variant uses a different suffixed name.
-		bazelModuleLabel = bazelLabelForStaticModule(actx, c)
-	} else {
-		bazelModuleLabel = c.GetBazelLabel(actx, c)
+		return bazelLabelForStaticModule(ctx, c)
+	}
+	return c.GetBazelLabel(ctx, c)
+}
+
+func (c *Module) QueueBazelCall(ctx android.BaseModuleContext) {
+	c.bazelHandler.QueueBazelCall(ctx, c.getBazelModuleLabel(ctx))
+}
+
+func (c *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
+	return c.bazelHandler != nil
+}
+
+func (c *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
+	bazelModuleLabel := c.getBazelModuleLabel(ctx)
+
+	c.bazelHandler.ProcessBazelQueryResponse(ctx, bazelModuleLabel)
+
+	c.Properties.SubName = GetSubnameProperty(ctx, c)
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		c.hideApexVariantFromMake = true
 	}
 
-	bazelActionsUsed := false
-	// Mixed builds mode is disabled for modules outside of device OS.
-	// TODO(b/200841190): Support non-device OS in mixed builds.
-	if android.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
-		bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
+	c.makeLinkType = GetMakeLinkType(ctx, c)
+
+	mctx := &moduleContext{
+		ModuleContext: ctx,
+		moduleContextImpl: moduleContextImpl{
+			mod: c,
+		},
 	}
-	return bazelActionsUsed
+	mctx.ctx = mctx
+
+	c.maybeInstall(mctx, apexInfo)
 }
 
 func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
-	// TODO(cparsons): Any logic in this method occurring prior to querying Bazel should be
-	// requested from Bazel instead.
-
 	// Handle the case of a test module split by `test_per_src` mutator.
 	//
 	// The `test_per_src` mutator adds an extra variation named "", depending on all the other
@@ -1824,11 +1857,6 @@
 	}
 	ctx.ctx = ctx
 
-	if c.maybeGenerateBazelActions(actx) {
-		c.maybeInstall(ctx, apexInfo)
-		return
-	}
-
 	deps := c.depsToPaths(ctx)
 	if ctx.Failed() {
 		return
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 09cc352..fb24624 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4041,8 +4041,8 @@
 	conly := []string{"-fPIC", "${config.CommonGlobalConlyflags}"}
 	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
 
-	cflags := []string{"-Wall", "-Werror", "-std=candcpp"}
-	cstd := []string{"-std=gnu99", "-std=conly"}
+	cflags := []string{"-Werror", "-std=candcpp"}
+	cstd := []string{"-std=gnu11", "-std=conly"}
 	cppstd := []string{"-std=gnu++17", "-std=cpp", "-fno-rtti"}
 
 	lastIncludes := []string{
diff --git a/cc/compiler.go b/cc/compiler.go
index eb5458f..773a642 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -589,10 +589,9 @@
 			addToModuleList(ctx, modulesUsingWnoErrorKey, module)
 		} else if !inList("-Werror", flags.Local.CFlags) && !inList("-Werror", flags.Local.CppFlags) {
 			if warningsAreAllowed(ctx.ModuleDir()) {
-				addToModuleList(ctx, modulesAddedWallKey, module)
-				flags.Local.CFlags = append([]string{"-Wall"}, flags.Local.CFlags...)
+				addToModuleList(ctx, modulesWarningsAllowedKey, module)
 			} else {
-				flags.Local.CFlags = append([]string{"-Wall", "-Werror"}, flags.Local.CFlags...)
+				flags.Local.CFlags = append([]string{"-Werror"}, flags.Local.CFlags...)
 			}
 		}
 	}
diff --git a/cc/config/global.go b/cc/config/global.go
index dc6310c..1c4ad7f 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -279,9 +279,9 @@
 		"-w",
 	}
 
-	CStdVersion               = "gnu99"
+	CStdVersion               = "gnu11"
 	CppStdVersion             = "gnu++17"
-	ExperimentalCStdVersion   = "gnu11"
+	ExperimentalCStdVersion   = "gnu17"
 	ExperimentalCppStdVersion = "gnu++2a"
 
 	// prebuilts/clang default settings.
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index 1f90843..826197a 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -35,14 +35,22 @@
 			"bugprone-*",
 			"cert-*",
 			"clang-diagnostic-unused-command-line-argument",
-			"google-*",
+			// Select only google-* checks that do not have thousands of warnings.
+			// Add more such checks when we clean up source code.
+			// "google-build-using-namespace",
+			// "google-default-arguments",
+			// "google-explicit-constructor",
+			// "google-global-names-in-headers",
+			// "google-runtime-int",
+			"google-build-explicit-make-pair",
+			"google-build-namespaces",
+			"google-runtime-operator",
+			"google-upgrade-*",
 			"misc-*",
 			"performance-*",
 			"portability-*",
 			"-bugprone-easily-swappable-parameters",
 			"-bugprone-narrowing-conversions",
-			"-google-readability*",
-			"-google-runtime-references",
 			"-misc-no-recursion",
 			"-misc-non-private-member-variables-in-classes",
 			"-misc-unused-parameters",
@@ -79,13 +87,10 @@
 		return strings.Join([]string{
 			"-*",
 			"clang-diagnostic-unused-command-line-argument",
-			"google*",
-			"-google-build-using-namespace",
-			"-google-default-arguments",
-			"-google-explicit-constructor",
-			"-google-readability*",
-			"-google-runtime-int",
-			"-google-runtime-references",
+			"google-build-explicit-make-pair",
+			"google-build-namespaces",
+			"google-runtime-operator",
+			"google-upgrade-*",
 		}, ",")
 	})
 
diff --git a/cc/library.go b/cc/library.go
index fdbbccb..5746529 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -642,18 +642,18 @@
 }
 
 type ccLibraryBazelHandler struct {
-	android.BazelHandler
+	BazelHandler
 
 	module *Module
 }
 
 // generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
 // provider from a Bazel shared library's CcInfo provider.
-func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
 	rootStaticArchives := ccInfo.RootStaticArchives
 	if len(rootStaticArchives) != 1 {
 		ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
-		return false
+		return
 	}
 	outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
@@ -679,17 +679,17 @@
 			Build(),
 	})
 
-	return true
+	return
 }
 
 // generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
 // provider from a Bazel shared library's CcInfo provider.
-func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
 	rootDynamicLibraries := ccInfo.RootDynamicLibraries
 
 	if len(rootDynamicLibraries) != 1 {
 		ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
-		return false
+		return
 	}
 	outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
@@ -709,30 +709,27 @@
 		// TODO(b/190524881): Include transitive static libraries in this provider to support
 		// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
 	})
-	return true
 }
 
-func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *ccLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (handler *ccLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
-		return false
-	}
-	if !ok {
-		return ok
+		return
 	}
 
 	if handler.module.static() {
-		if ok := handler.generateStaticBazelBuildActions(ctx, label, ccInfo); !ok {
-			return false
-		}
+		handler.generateStaticBazelBuildActions(ctx, label, ccInfo)
 	} else if handler.module.Shared() {
-		if ok := handler.generateSharedBazelBuildActions(ctx, label, ccInfo); !ok {
-			return false
-		}
+		handler.generateSharedBazelBuildActions(ctx, label, ccInfo)
 	} else {
-		return false
+		ctx.ModuleErrorf("Unhandled bazel case for %s (neither shared nor static!)", ctx.ModuleName())
 	}
 
 	handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
@@ -746,7 +743,6 @@
 		// implementation.
 		i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
 	}
-	return ok
 }
 
 func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 41ebcc7..6fd9568 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 )
 
 func init() {
@@ -47,28 +48,30 @@
 	ctx.RegisterModuleType("cc_prebuilt_library_headers", prebuiltLibraryHeaderFactory)
 }
 
-type libraryHeaderBazelHander struct {
-	android.BazelHandler
+type libraryHeaderBazelHandler struct {
+	BazelHandler
 
 	module  *Module
 	library *libraryDecorator
 }
 
-func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *libraryHeaderBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *libraryHeaderBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
-		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
-		return false
-	}
-	if !ok {
-		return false
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
 
 	outputPaths := ccInfo.OutputFiles
 	if len(outputPaths) != 1 {
 		ctx.ModuleErrorf("expected exactly one output file for %q, but got %q", label, outputPaths)
-		return false
+		return
 	}
 
 	outputPath := android.PathForBazelOut(ctx, outputPaths[0])
@@ -83,8 +86,6 @@
 	// validation will fail. For now, set this to an empty list.
 	// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
 	h.library.collectedSnapshotHeaders = android.Paths{}
-
-	return true
 }
 
 // cc_library_headers contains a set of c/c++ headers which are imported by
@@ -96,7 +97,7 @@
 	library.HeaderOnly()
 	module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
 	module.bazelable = true
-	module.bazelHandler = &libraryHeaderBazelHander{module: module, library: library}
+	module.bazelHandler = &libraryHeaderBazelHandler{module: module, library: library}
 	return module.Init()
 }
 
diff --git a/cc/linkable.go b/cc/linkable.go
index 6bec30c..04eab39 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -22,16 +22,16 @@
 	// than left undefined.
 	IsSanitizerExplicitlyDisabled(t SanitizerType) bool
 
-	// SanitizeDep returns the value of the SanitizeDep flag, which is set if a module is a dependency of a
-	// sanitized module.
-	SanitizeDep() bool
+	// SanitizeDep returns true if the module is statically linked into another that is sanitized
+	// with the given sanitizer.
+	SanitizeDep(t SanitizerType) bool
+
+	// SetSanitizeDep marks a module as a static dependency of another module to be sanitized.
+	SetSanitizeDep(t SanitizerType)
 
 	// SetSanitizer enables or disables the specified sanitizer type if it's supported, otherwise this should panic.
 	SetSanitizer(t SanitizerType, b bool)
 
-	// SetSanitizerDep returns true if the module is statically linked.
-	SetSanitizeDep(b bool)
-
 	// StaticallyLinked returns true if the module is statically linked.
 	StaticallyLinked() bool
 
diff --git a/cc/makevars.go b/cc/makevars.go
index 6752f8c..8154436 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -25,7 +25,7 @@
 )
 
 var (
-	modulesAddedWallKey          = android.NewOnceKey("ModulesAddedWall")
+	modulesWarningsAllowedKey    = android.NewOnceKey("ModulesWarningsAllowed")
 	modulesUsingWnoErrorKey      = android.NewOnceKey("ModulesUsingWnoError")
 	modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile")
 )
@@ -119,7 +119,7 @@
 	ctx.Strict("LSDUMP_PATHS", strings.Join(lsdumpPaths, " "))
 
 	ctx.Strict("ANDROID_WARNING_ALLOWED_PROJECTS", makeStringOfWarningAllowedProjects())
-	ctx.Strict("SOONG_MODULES_ADDED_WALL", makeStringOfKeys(ctx, modulesAddedWallKey))
+	ctx.Strict("SOONG_MODULES_WARNINGS_ALLOWED", makeStringOfKeys(ctx, modulesWarningsAllowedKey))
 	ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoErrorKey))
 	ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFileKey))
 
diff --git a/cc/object.go b/cc/object.go
index bd5bd45..a3be6b1 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -19,6 +19,7 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 )
 
 //
@@ -46,23 +47,30 @@
 }
 
 type objectBazelHandler struct {
-	android.BazelHandler
+	BazelHandler
 
 	module *Module
 }
 
-func (handler *objectBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *objectBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	objPaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
-	if ok {
-		if len(objPaths) != 1 {
-			ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
-			return false
-		}
+	bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
 
-		handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+func (handler *objectBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	objPaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
-	return ok
+
+	if len(objPaths) != 1 {
+		ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
+		return
+	}
+
+	handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
 }
 
 type ObjectLinkerProperties struct {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index f54c6f8..e8c60ba 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 )
 
 func init() {
@@ -406,25 +407,28 @@
 }
 
 type prebuiltStaticLibraryBazelHandler struct {
-	android.BazelHandler
+	BazelHandler
 
 	module  *Module
 	library *libraryDecorator
 }
 
-func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *prebuiltStaticLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *prebuiltStaticLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
-		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
-	}
-	if !ok {
-		return false
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
 	staticLibs := ccInfo.CcStaticLibraryFiles
 	if len(staticLibs) > 1 {
 		ctx.ModuleErrorf("expected 1 static library from bazel target %q, got %s", label, staticLibs)
-		return false
+		return
 	}
 
 	// TODO(b/184543518): cc_prebuilt_library_static may have properties for re-exporting flags
@@ -439,7 +443,7 @@
 
 	if len(staticLibs) == 0 {
 		h.module.outputFile = android.OptionalPath{}
-		return true
+		return
 	}
 
 	out := android.PathForBazelOut(ctx, staticLibs[0])
@@ -451,30 +455,31 @@
 
 		TransitiveStaticLibrariesForOrdering: depSet,
 	})
-
-	return true
 }
 
 type prebuiltSharedLibraryBazelHandler struct {
-	android.BazelHandler
+	BazelHandler
 
 	module  *Module
 	library *libraryDecorator
 }
 
-func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *prebuiltSharedLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *prebuiltSharedLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
-		ctx.ModuleErrorf("Error getting Bazel CcInfo for %s: %s", label, err)
-	}
-	if !ok {
-		return false
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
 	sharedLibs := ccInfo.CcSharedLibraryFiles
 	if len(sharedLibs) != 1 {
 		ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
-		return false
+		return
 	}
 
 	// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
@@ -489,7 +494,7 @@
 
 	if len(sharedLibs) == 0 {
 		h.module.outputFile = android.OptionalPath{}
-		return true
+		return
 	}
 
 	out := android.PathForBazelOut(ctx, sharedLibs[0])
@@ -514,8 +519,6 @@
 
 	h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
 	h.module.maybeUnhideFromMake()
-
-	return true
 }
 
 func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 53169de..87cee9f 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -276,7 +276,7 @@
 type SanitizeProperties struct {
 	Sanitize          SanitizeUserProps `android:"arch_variant"`
 	SanitizerEnabled  bool              `blueprint:"mutated"`
-	SanitizeDep       bool              `blueprint:"mutated"`
+	SanitizeDepTypes  []SanitizerType   `blueprint:"mutated"`
 	MinimalRuntimeDep bool              `blueprint:"mutated"`
 	BuiltinsDep       bool              `blueprint:"mutated"`
 	UbsanRuntimeDep   bool              `blueprint:"mutated"`
@@ -944,7 +944,7 @@
 				// determine defaultVariation in sanitizerMutator below.
 				// Instead, just mark SanitizeDep to forcefully create cfi variant.
 				enabled = true
-				c.SetSanitizeDep(true)
+				c.SetSanitizeDep(t)
 			}
 			if enabled {
 				isSanitizableDependencyTag := c.SanitizableDepTagChecker()
@@ -959,32 +959,30 @@
 							if d.StaticallyLinked() && d.SanitizerSupported(t) {
 								// Rust does not support some of these sanitizers, so we need to check if it's
 								// supported before setting this true.
-								d.SetSanitizeDep(true)
+								d.SetSanitizeDep(t)
 							}
 						} else {
-							d.SetSanitizeDep(true)
+							d.SetSanitizeDep(t)
 						}
 					}
 					return true
 				})
 			}
-		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
+		} else if jniSanitizeable, ok := mctx.Module().(JniSanitizeable); ok {
 			// If it's a Java module with native dependencies through jni,
 			// set the sanitizer for them
-			if jniSanitizeable, ok := mctx.Module().(JniSanitizeable); ok {
-				if jniSanitizeable.IsSanitizerEnabledForJni(mctx, t.name()) {
-					mctx.VisitDirectDeps(func(child android.Module) {
-						if c, ok := child.(PlatformSanitizeable); ok &&
-							mctx.OtherModuleDependencyTag(child) == JniFuzzLibTag &&
-							c.SanitizePropDefined() &&
-							!c.SanitizeNever() &&
-							!c.IsSanitizerExplicitlyDisabled(t) {
-							c.SetSanitizeDep(true)
-						}
-					})
-				}
+			if jniSanitizeable.IsSanitizerEnabledForJni(mctx, t.name()) {
+				mctx.VisitDirectDeps(func(child android.Module) {
+					if c, ok := child.(PlatformSanitizeable); ok &&
+						mctx.OtherModuleDependencyTag(child) == JniFuzzLibTag &&
+						c.SanitizePropDefined() &&
+						!c.SanitizeNever() &&
+						!c.IsSanitizerExplicitlyDisabled(t) {
+						c.SetSanitizeDep(t)
+					}
+				})
 			}
-
+		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
 			// If an APEX module includes a lib which is enabled for a sanitizer T, then
 			// the APEX module is also enabled for the same sanitizer type.
 			mctx.VisitDirectDeps(func(child android.Module) {
@@ -1317,8 +1315,14 @@
 	return c.sanitize.isSanitizerEnabled(t)
 }
 
-func (c *Module) SanitizeDep() bool {
-	return c.sanitize.Properties.SanitizeDep
+func (c *Module) SanitizeDep(t SanitizerType) bool {
+	for _, e := range c.sanitize.Properties.SanitizeDepTypes {
+		if t == e {
+			return true
+		}
+	}
+
+	return false
 }
 
 func (c *Module) StaticallyLinked() bool {
@@ -1337,9 +1341,9 @@
 	}
 }
 
-func (c *Module) SetSanitizeDep(b bool) {
-	if c.sanitize != nil {
-		c.sanitize.Properties.SanitizeDep = b
+func (c *Module) SetSanitizeDep(t SanitizerType) {
+	if !c.SanitizeDep(t) {
+		c.sanitize.Properties.SanitizeDepTypes = append(c.sanitize.Properties.SanitizeDepTypes, t)
 	}
 }
 
@@ -1356,7 +1360,7 @@
 			if c.Binary() && c.IsSanitizerEnabled(t) {
 				modules := mctx.CreateVariations(t.variationName())
 				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
-			} else if c.IsSanitizerEnabled(t) || c.SanitizeDep() {
+			} else if c.IsSanitizerEnabled(t) || c.SanitizeDep(t) {
 				isSanitizerEnabled := c.IsSanitizerEnabled(t)
 				if c.StaticallyLinked() || c.Header() || t == Fuzzer {
 					// Static and header libs are split into non-sanitized and sanitized variants.
@@ -1378,8 +1382,6 @@
 					modules := mctx.CreateVariations("", t.variationName())
 					modules[0].(PlatformSanitizeable).SetSanitizer(t, false)
 					modules[1].(PlatformSanitizeable).SetSanitizer(t, true)
-					modules[0].(PlatformSanitizeable).SetSanitizeDep(false)
-					modules[1].(PlatformSanitizeable).SetSanitizeDep(false)
 
 					if mctx.Device() && t.incompatibleWithCfi() && cfiSupported {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
@@ -1412,7 +1414,6 @@
 					// Shared libs are not split. Only the sanitized variant is created.
 					modules := mctx.CreateVariations(t.variationName())
 					modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
-					modules[0].(PlatformSanitizeable).SetSanitizeDep(false)
 
 					// locate the asan libraries under /data/asan
 					if mctx.Device() && t == Asan && isSanitizerEnabled {
@@ -1426,7 +1427,6 @@
 					}
 				}
 			}
-			c.SetSanitizeDep(false)
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok && sanitizeable.IsSanitizerEnabled(mctx, t.name()) {
 			// APEX and Java fuzz modules fall here
 			sanitizeable.AddSanitizerDependencies(mctx, t.name())
@@ -1529,12 +1529,10 @@
 	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) ||
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 4b3161b..ad379d5 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -129,44 +129,26 @@
 	return configuration
 }
 
-// Bazel-enabled mode. Soong runs in two passes.
-// First pass: Analyze the build tree, but only store all bazel commands
-// needed to correctly evaluate the tree in the second pass.
-// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
-// the incorrect results from the first pass, and file I/O is expensive.
-func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
-	firstCtx.EventHandler.Begin("mixed_build")
-	defer firstCtx.EventHandler.End("mixed_build")
+// Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
+// BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
+// for modules that should be handled by Bazel.
+func runMixedModeBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) {
+	ctx.EventHandler.Begin("mixed_build")
+	defer ctx.EventHandler.End("mixed_build")
 
-	firstCtx.EventHandler.Begin("prepare")
-	bootstrap.RunBlueprint(cmdlineArgs, bootstrap.StopBeforeWriteNinja, firstCtx.Context, configuration)
-	firstCtx.EventHandler.End("prepare")
-
-	firstCtx.EventHandler.Begin("bazel")
-	// Invoke bazel commands and save results for second pass.
-	if err := configuration.BazelContext.InvokeBazel(); err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
+	bazelHook := func() error {
+		ctx.EventHandler.Begin("bazel")
+		defer ctx.EventHandler.End("bazel")
+		return configuration.BazelContext.InvokeBazel()
 	}
-	// Second pass: Full analysis, using the bazel command results. Output ninja file.
-	secondConfig, err := android.ConfigForAdditionalRun(configuration)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
-	}
-	firstCtx.EventHandler.End("bazel")
+	ctx.SetBeforePrepareBuildActionsHook(bazelHook)
 
-	secondCtx := newContext(secondConfig)
-	secondCtx.EventHandler = firstCtx.EventHandler
-	secondCtx.EventHandler.Begin("analyze")
-	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, secondCtx.Context, secondConfig)
-	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-	secondCtx.EventHandler.End("analyze")
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, ctx.Context, configuration)
 
-	globListFiles := writeBuildGlobsNinjaFile(secondCtx, configuration.SoongOutDir(), configuration)
+	globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
 	ninjaDeps = append(ninjaDeps, globListFiles...)
 
-	writeDepFile(cmdlineArgs.OutFile, *secondCtx.EventHandler, ninjaDeps)
+	writeDepFile(cmdlineArgs.OutFile, *ctx.EventHandler, ninjaDeps)
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 3531ee6..8649b15 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -25,6 +25,7 @@
 	"strconv"
 	"strings"
 
+	"android/soong/bazel/cquery"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/proptools"
@@ -189,6 +190,8 @@
 	modulePaths []string
 }
 
+var _ android.MixedBuildBuildable = (*Module)(nil)
+
 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
 
 type generateTask struct {
@@ -249,27 +252,36 @@
 	}
 }
 
-// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
-func (c *Module) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (g *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
+	g.generateCommonBuildActions(ctx)
+
+	label := g.GetBazelLabel(ctx, g)
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
-	if ok {
-		var bazelOutputFiles android.Paths
-		exportIncludeDirs := map[string]bool{}
-		for _, bazelOutputFile := range filePaths {
-			bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOut(ctx, bazelOutputFile))
-			exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
-		}
-		c.outputFiles = bazelOutputFiles
-		c.outputDeps = bazelOutputFiles
-		for includePath, _ := range exportIncludeDirs {
-			c.exportedIncludeDirs = append(c.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
-		}
+	filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+		return
 	}
-	return ok
+
+	var bazelOutputFiles android.Paths
+	exportIncludeDirs := map[string]bool{}
+	for _, bazelOutputFile := range filePaths {
+		bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOut(ctx, bazelOutputFile))
+		exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
+	}
+	g.outputFiles = bazelOutputFiles
+	g.outputDeps = bazelOutputFiles
+	for includePath, _ := range exportIncludeDirs {
+		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
+	}
 }
 
-func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+// generateCommonBuildActions contains build action generation logic
+// common to both the mixed build case and the legacy case of genrule processing.
+// To fully support genrule in mixed builds, the contents of this function should
+// approach zero; there should be no genrule action registration done directly
+// by Soong logic in the mixed-build case.
+func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
 	g.subName = ctx.ModuleSubDir()
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -575,29 +587,35 @@
 	}
 
 	g.outputFiles = outputFiles.Paths()
+}
 
-	bazelModuleLabel := g.GetBazelLabel(ctx, g)
-	bazelActionsUsed := false
-	if android.MixedBuildsEnabled(ctx) {
-		bazelActionsUsed = g.GenerateBazelBuildActions(ctx, bazelModuleLabel)
+func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	g.generateCommonBuildActions(ctx)
+
+	// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
+	// the genrules on AOSP. That will make things simpler to look at the graph in the common
+	// case. For larger sets of outputs, inject a phony target in between to limit ninja file
+	// growth.
+	if len(g.outputFiles) <= 6 {
+		g.outputDeps = g.outputFiles
+	} else {
+		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   blueprint.Phony,
+			Output: phonyFile,
+			Inputs: g.outputFiles,
+		})
+		g.outputDeps = android.Paths{phonyFile}
 	}
-	if !bazelActionsUsed {
-		// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
-		// the genrules on AOSP. That will make things simpler to look at the graph in the common
-		// case. For larger sets of outputs, inject a phony target in between to limit ninja file
-		// growth.
-		if len(g.outputFiles) <= 6 {
-			g.outputDeps = g.outputFiles
-		} else {
-			phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
-			ctx.Build(pctx, android.BuildParams{
-				Rule:   blueprint.Phony,
-				Output: phonyFile,
-				Inputs: g.outputFiles,
-			})
-			g.outputDeps = android.Paths{phonyFile}
-		}
-	}
+}
+
+func (g *Module) QueueBazelCall(ctx android.BaseModuleContext) {
+	bazelCtx := ctx.Config().BazelContext
+	bazelCtx.QueueBazelRequest(g.GetBazelLabel(ctx, g), cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
+
+func (g *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
+	return true
 }
 
 // Collect information for opening IDE project files in java/jdeps.go.
diff --git a/java/androidmk.go b/java/androidmk.go
index 439b1d1..330e594 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -324,7 +324,7 @@
 }
 
 func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries {
-	if app.hideApexVariantFromMake || app.appProperties.HideFromMake {
+	if app.hideApexVariantFromMake || app.IsHideFromMake() {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Disabled: true,
 		}}
@@ -424,8 +424,8 @@
 
 func (a *AndroidApp) getOverriddenPackages() []string {
 	var overridden []string
-	if len(a.appProperties.Overrides) > 0 {
-		overridden = append(overridden, a.appProperties.Overrides...)
+	if len(a.overridableAppProperties.Overrides) > 0 {
+		overridden = append(overridden, a.overridableAppProperties.Overrides...)
 	}
 	// When APK name is overridden via PRODUCT_PACKAGE_NAME_OVERRIDES
 	// ensure that the original name is overridden.
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 246c0eb..197da4f 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -206,3 +206,49 @@
 		t.Errorf("Unexpected flag value - expected: %q, actual: %q", expected, actual)
 	}
 }
+
+func TestGetOverriddenPackages(t *testing.T) {
+	ctx, _ := testJava(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			overrides: ["qux"]
+		}
+
+		override_android_app {
+			name: "foo_override",
+			base: "foo",
+			overrides: ["bar"]
+		}
+		`)
+
+	expectedVariants := []struct {
+		name        string
+		moduleName  string
+		variantName string
+		overrides   []string
+	}{
+		{
+			name:        "foo",
+			moduleName:  "foo",
+			variantName: "android_common",
+			overrides:   []string{"qux"},
+		},
+		{
+			name:        "foo",
+			moduleName:  "foo_override",
+			variantName: "android_common_foo_override",
+			overrides:   []string{"bar", "foo"},
+		},
+	}
+
+	for _, expected := range expectedVariants {
+		mod := ctx.ModuleForTests(expected.name, expected.variantName).Module()
+		entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+		actual := entries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
+
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, actual)
+	}
+}
diff --git a/java/app.go b/java/app.go
index 5f14cef..86238d5 100755
--- a/java/app.go
+++ b/java/app.go
@@ -63,13 +63,6 @@
 	// list of resource labels to generate individual resource packages
 	Package_splits []string
 
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-
 	// list of native libraries that will be provided in or alongside the resulting jar
 	Jni_libs []string `android:"arch_variant"`
 
@@ -106,7 +99,6 @@
 
 	// cc.Coverage related properties
 	PreventInstall    bool `blueprint:"mutated"`
-	HideFromMake      bool `blueprint:"mutated"`
 	IsCoverageVariant bool `blueprint:"mutated"`
 
 	// Whether this app is considered mainline updatable or not. When set to true, this will enforce
@@ -133,6 +125,13 @@
 
 	// Whether to rename the package in resources to the override name rather than the base name. Defaults to true.
 	Rename_resources_package *bool
+
+	// Names of modules to be overridden. Listed modules can only be other binaries
+	// (in Make or Soong).
+	// This does not completely prevent installation of the overridden binaries, but if both
+	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
+	// from PRODUCT_PACKAGES.
+	Overrides []string
 }
 
 type AndroidApp struct {
@@ -582,18 +581,6 @@
 	}
 	a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
 
-	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
-		noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz")
-		android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile)
-		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
-		builder := android.NewRuleBuilder(pctx, ctx)
-		builder.Command().Text("cp").
-			Input(noticeFile).
-			Output(noticeAssetPath)
-		builder.Build("notice_dir", "Building notice dir")
-		a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath)
-	}
-
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	// Process all building blocks, from AAPT to certificates.
@@ -667,6 +654,18 @@
 		a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile)
 	}
 
+	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
+		noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz")
+		android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile, "", "", a.outputFile.String())
+		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+		builder := android.NewRuleBuilder(pctx, ctx)
+		builder.Command().Text("cp").
+			Input(noticeFile).
+			Output(noticeAssetPath)
+		builder.Build("notice_dir", "Building notice dir")
+		a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath)
+	}
+
 	for _, split := range a.aapt.splits {
 		// Sign the split APKs
 		packageFile := android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk")
@@ -880,10 +879,6 @@
 	a.appProperties.PreventInstall = true
 }
 
-func (a *AndroidApp) HideFromMake() {
-	a.appProperties.HideFromMake = true
-}
-
 func (a *AndroidApp) MarkAsCoverageVariant(coverage bool) {
 	a.appProperties.IsCoverageVariant = coverage
 }
@@ -913,7 +908,7 @@
 
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitOverridableModule(module, &module.appProperties.Overrides)
+	android.InitOverridableModule(module, &module.overridableAppProperties.Overrides)
 	android.InitApexModule(module)
 	android.InitBazelModule(module)
 
@@ -1037,7 +1032,7 @@
 
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitOverridableModule(module, &module.appProperties.Overrides)
+	android.InitOverridableModule(module, &module.overridableAppProperties.Overrides)
 	return module
 }
 
diff --git a/java/app_test.go b/java/app_test.go
index b83a333..c4ac4df 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -1966,7 +1966,7 @@
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidApp)
-		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.appProperties.Overrides)
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)
 
 		// Test Overridable property: Logging_parent
 		logging_parent := mod.aapt.LoggingParent
@@ -1984,6 +1984,99 @@
 	}
 }
 
+func TestOverrideAndroidAppOverrides(t *testing.T) {
+	ctx, _ := testJava(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			overrides: ["qux"]
+		}
+
+		android_app {
+			name: "bar",
+			srcs: ["b.java"],
+			sdk_version: "current",
+			overrides: ["foo"]
+		}
+
+		override_android_app {
+			name: "foo_override",
+			base: "foo",
+			overrides: ["bar"]
+		}
+		`)
+
+	expectedVariants := []struct {
+		name        string
+		moduleName  string
+		variantName string
+		overrides   []string
+	}{
+		{
+			name:        "foo",
+			moduleName:  "foo",
+			variantName: "android_common",
+			overrides:   []string{"qux"},
+		},
+		{
+			name:        "bar",
+			moduleName:  "bar",
+			variantName: "android_common",
+			overrides:   []string{"foo"},
+		},
+		{
+			name:        "foo",
+			moduleName:  "foo_override",
+			variantName: "android_common_foo_override",
+			overrides:   []string{"bar", "foo"},
+		},
+	}
+	for _, expected := range expectedVariants {
+		variant := ctx.ModuleForTests(expected.name, expected.variantName)
+
+		// Check if the overrides field values are correctly aggregated.
+		mod := variant.Module().(*AndroidApp)
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)
+	}
+}
+
+func TestOverrideAndroidAppWithPrebuilt(t *testing.T) {
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+
+		override_android_app {
+			name: "bar",
+			base: "foo",
+		}
+
+		android_app_import {
+			name: "bar",
+			prefer: true,
+			apk: "bar.apk",
+			presigned: true,
+		}
+		`)
+
+	// An app that has an override that also has a prebuilt should not be hidden.
+	foo := result.ModuleForTests("foo", "android_common")
+	if foo.Module().IsHideFromMake() {
+		t.Errorf("expected foo to have HideFromMake false")
+	}
+
+	// An override that also has a prebuilt should be hidden.
+	barOverride := result.ModuleForTests("foo", "android_common_bar")
+	if !barOverride.Module().IsHideFromMake() {
+		t.Errorf("expected bar override variant of foo to have HideFromMake true")
+	}
+}
+
 func TestOverrideAndroidAppStem(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_app {
@@ -2164,9 +2257,9 @@
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidTest)
-		if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) {
+		if !reflect.DeepEqual(expected.overrides, mod.overridableAppProperties.Overrides) {
 			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
-				expected.overrides, mod.appProperties.Overrides)
+				expected.overrides, mod.overridableAppProperties.Overrides)
 		}
 
 		// Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides.
diff --git a/java/base.go b/java/base.go
index c6f859a..0900daa 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1913,6 +1913,9 @@
 			case bootClasspathTag:
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
 			case libTag, instrumentationForTag:
+				if _, ok := module.(*Plugin); ok {
+					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a libs dependency", otherName)
+				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
 				deps.dexClasspath = append(deps.dexClasspath, dep.HeaderJars...)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
@@ -1921,6 +1924,9 @@
 			case java9LibTag:
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
 			case staticLibTag:
+				if _, ok := module.(*Plugin); ok {
+					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a static_libs dependency", otherName)
+				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
 				deps.staticJars = append(deps.staticJars, dep.ImplementationJars...)
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars...)
diff --git a/java/fuzz.go b/java/fuzz.go
index 584c80b..cf2c981 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -50,9 +50,10 @@
 	jniFilePaths       android.Paths
 }
 
-// IsSanitizerEnabled implemented to make JavaFuzzLibrary implement
-// cc.Sanitizeable
-func (j *JavaFuzzLibrary) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
+// IsSanitizerEnabledForJni implemented to make JavaFuzzLibrary implement
+// cc.JniSanitizeable. It returns a bool for whether a cc dependency should be
+// sanitized for the given sanitizer or not.
+func (j *JavaFuzzLibrary) IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool {
 	for _, s := range j.jniProperties.Sanitizers {
 		if sanitizerName == s {
 			return true
@@ -61,26 +62,6 @@
 	return false
 }
 
-// IsSanitizerEnabledForJni implemented to make JavaFuzzLibrary implement
-// cc.JniSanitizeable. It returns a bool for whether a cc dependency should be
-// sanitized for the given sanitizer or not.
-func (j *JavaFuzzLibrary) IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool {
-	return j.IsSanitizerEnabled(ctx, sanitizerName)
-}
-
-// EnableSanitizer implemented to make JavaFuzzLibrary implement
-// cc.Sanitizeable
-func (j *JavaFuzzLibrary) EnableSanitizer(sanitizerName string) {
-}
-
-// AddSanitizerDependencies implemented to make JavaFuzzLibrary implement
-// cc.Sanitizeable
-func (j *JavaFuzzLibrary) AddSanitizerDependencies(mctx android.BottomUpMutatorContext, sanitizerName string) {
-}
-
-// To verify that JavaFuzzLibrary implements cc.Sanitizeable
-var _ cc.Sanitizeable = (*JavaFuzzLibrary)(nil)
-
 func (j *JavaFuzzLibrary) DepsMutator(mctx android.BottomUpMutatorContext) {
 	if len(j.jniProperties.Jni_libs) > 0 {
 		if j.fuzzPackagedModule.FuzzProperties.Fuzz_config == nil {
diff --git a/java/java.go b/java/java.go
index 9ecd362..511e885 100644
--- a/java/java.go
+++ b/java/java.go
@@ -592,12 +592,14 @@
 	}
 
 	j.checkSdkVersions(ctx)
-	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
-		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
-	j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
-	setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
-	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	if ctx.Device() {
+		j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+			ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
+		j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
+		setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
+		j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
+		j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	}
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index 44650a6..9449707 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -212,6 +212,10 @@
 	mctx.CreateModule(systemModulesImportFactory, &props)
 }
 
+func PrebuiltApiModuleName(module, scope, version string) string {
+	return module + ".api." + scope + "." + version
+}
+
 func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) {
 	// <apiver>/<scope>/api/<module>.txt
 	apiLevelFiles := globApiDirs(mctx, p, "api/*.txt")
@@ -220,12 +224,9 @@
 	}
 
 	// Create modules for all (<module>, <scope, <version>) triplets,
-	apiModuleName := func(module, scope, version string) string {
-		return module + ".api." + scope + "." + version
-	}
 	for _, f := range apiLevelFiles {
 		module, version, scope := parseFinalizedPrebuiltPath(mctx, f)
-		createApiModule(mctx, apiModuleName(module, scope, strconv.Itoa(version)), f)
+		createApiModule(mctx, PrebuiltApiModuleName(module, scope, strconv.Itoa(version)), f)
 	}
 
 	// Figure out the latest version of each module/scope
@@ -266,7 +267,7 @@
 	// Sort the keys in order to make build.ninja stable
 	for _, k := range android.SortedStringKeys(latest) {
 		info := latest[k]
-		name := apiModuleName(info.module, info.scope, "latest")
+		name := PrebuiltApiModuleName(info.module, info.scope, "latest")
 		createApiModule(mctx, name, info.path)
 	}
 
@@ -278,7 +279,7 @@
 			filename, _, scope := parsePrebuiltPath(mctx, f)
 			referencedModule := strings.TrimSuffix(filename, "-incompatibilities")
 
-			createApiModule(mctx, apiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f)
+			createApiModule(mctx, PrebuiltApiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f)
 
 			incompatibilities[referencedModule+"."+scope] = true
 		}
@@ -286,7 +287,7 @@
 	// Create empty incompatibilities files for remaining modules
 	for _, k := range android.SortedStringKeys(latest) {
 		if _, ok := incompatibilities[k]; !ok {
-			createEmptyFile(mctx, apiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest"))
+			createEmptyFile(mctx, PrebuiltApiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest"))
 		}
 	}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index cd8e875..8778937 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -97,6 +97,13 @@
 	// The tag to use to depend on the stubs source and API module.
 	stubsSourceAndApiTag scopeDependencyTag
 
+	// The tag to use to depend on the module that provides the latest version of the API .txt file.
+	latestApiModuleTag scopeDependencyTag
+
+	// The tag to use to depend on the module that provides the latest version of the API removed.txt
+	// file.
+	latestRemovedApiModuleTag scopeDependencyTag
+
 	// The scope specific prefix to add to the api file base of "current.txt" or "removed.txt".
 	apiFilePrefix string
 
@@ -158,6 +165,16 @@
 		apiScope:         scope,
 		depInfoExtractor: (*scopePaths).extractStubsSourceAndApiInfoFromApiStubsProvider,
 	}
+	scope.latestApiModuleTag = scopeDependencyTag{
+		name:             name + "-latest-api",
+		apiScope:         scope,
+		depInfoExtractor: (*scopePaths).extractLatestApiPath,
+	}
+	scope.latestRemovedApiModuleTag = scopeDependencyTag{
+		name:             name + "-latest-removed-api",
+		apiScope:         scope,
+		depInfoExtractor: (*scopePaths).extractLatestRemovedApiPath,
+	}
 
 	// To get the args needed to generate the stubs source append all the args from
 	// this scope and all the scopes it extends as each set of args adds additional
@@ -203,6 +220,24 @@
 	return scope.name
 }
 
+// snapshotRelativeDir returns the snapshot directory into which the files related to scopes will
+// be stored.
+func (scope *apiScope) snapshotRelativeDir() string {
+	return filepath.Join("sdk_library", scope.name)
+}
+
+// snapshotRelativeCurrentApiTxtPath returns the snapshot path to the API .txt file for the named
+// library.
+func (scope *apiScope) snapshotRelativeCurrentApiTxtPath(name string) string {
+	return filepath.Join(scope.snapshotRelativeDir(), name+".txt")
+}
+
+// snapshotRelativeRemovedApiTxtPath returns the snapshot path to the removed API .txt file for the
+// named library.
+func (scope *apiScope) snapshotRelativeRemovedApiTxtPath(name string) string {
+	return filepath.Join(scope.snapshotRelativeDir(), name+"-removed.txt")
+}
+
 type apiScopes []*apiScope
 
 func (scopes apiScopes) Strings(accessor func(*apiScope) string) []string {
@@ -539,6 +574,12 @@
 
 	// Extracted annotations.
 	annotationsZip android.OptionalPath
+
+	// The path to the latest API file.
+	latestApiPath android.OptionalPath
+
+	// The path to the latest removed API file.
+	latestRemovedApiPath android.OptionalPath
 }
 
 func (paths *scopePaths) extractStubsLibraryInfoFromDependency(ctx android.ModuleContext, dep android.Module) error {
@@ -602,6 +643,31 @@
 	})
 }
 
+func extractSingleOptionalOutputPath(dep android.Module) (android.OptionalPath, error) {
+	var paths android.Paths
+	if sourceFileProducer, ok := dep.(android.SourceFileProducer); ok {
+		paths = sourceFileProducer.Srcs()
+	} else {
+		return android.OptionalPath{}, fmt.Errorf("module %q does not produce source files", dep)
+	}
+	if len(paths) != 1 {
+		return android.OptionalPath{}, fmt.Errorf("expected one path from %q, got %q", dep, paths)
+	}
+	return android.OptionalPathForPath(paths[0]), nil
+}
+
+func (paths *scopePaths) extractLatestApiPath(ctx android.ModuleContext, dep android.Module) error {
+	outputPath, err := extractSingleOptionalOutputPath(dep)
+	paths.latestApiPath = outputPath
+	return err
+}
+
+func (paths *scopePaths) extractLatestRemovedApiPath(ctx android.ModuleContext, dep android.Module) error {
+	outputPath, err := extractSingleOptionalOutputPath(dep)
+	paths.latestRemovedApiPath = outputPath
+	return err
+}
+
 type commonToSdkLibraryAndImportProperties struct {
 	// The naming scheme to use for the components that this module creates.
 	//
@@ -1174,6 +1240,16 @@
 
 		// Add a dependency on the stubs source in order to access both stubs source and api information.
 		ctx.AddVariationDependencies(nil, apiScope.stubsSourceAndApiTag, module.stubsSourceModuleName(apiScope))
+
+		if module.compareAgainstLatestApi(apiScope) {
+			// Add dependencies on the latest finalized version of the API .txt file.
+			latestApiModuleName := module.latestApiModuleName(apiScope)
+			ctx.AddDependency(module, apiScope.latestApiModuleTag, latestApiModuleName)
+
+			// Add dependencies on the latest finalized version of the remove API .txt file.
+			latestRemovedApiModuleName := module.latestRemovedApiModuleName(apiScope)
+			ctx.AddDependency(module, apiScope.latestRemovedApiModuleTag, latestRemovedApiModuleName)
+		}
 	}
 
 	if module.requiresRuntimeImplementationLibrary() {
@@ -1194,13 +1270,13 @@
 		if apiScope.unstable {
 			continue
 		}
-		if m := android.SrcIsModule(module.latestApiFilegroupName(apiScope)); !ctx.OtherModuleExists(m) {
+		if m := module.latestApiModuleName(apiScope); !ctx.OtherModuleExists(m) {
 			missingApiModules = append(missingApiModules, m)
 		}
-		if m := android.SrcIsModule(module.latestRemovedApiFilegroupName(apiScope)); !ctx.OtherModuleExists(m) {
+		if m := module.latestRemovedApiModuleName(apiScope); !ctx.OtherModuleExists(m) {
 			missingApiModules = append(missingApiModules, m)
 		}
-		if m := android.SrcIsModule(module.latestIncompatibilitiesFilegroupName(apiScope)); !ctx.OtherModuleExists(m) {
+		if m := module.latestIncompatibilitiesModuleName(apiScope); !ctx.OtherModuleExists(m) {
 			missingApiModules = append(missingApiModules, m)
 		}
 	}
@@ -1274,6 +1350,26 @@
 	// Make the set of components exported by this module available for use elsewhere.
 	exportedComponentInfo := android.ExportedComponentsInfo{Components: android.SortedStringKeys(exportedComponents)}
 	ctx.SetProvider(android.ExportedComponentsInfoProvider, exportedComponentInfo)
+
+	// Provide additional information for inclusion in an sdk's generated .info file.
+	additionalSdkInfo := map[string]interface{}{}
+	additionalSdkInfo["dist_stem"] = module.distStem()
+	baseModuleName := module.BaseModuleName()
+	scopes := map[string]interface{}{}
+	additionalSdkInfo["scopes"] = scopes
+	for scope, scopePaths := range module.scopePaths {
+		scopeInfo := map[string]interface{}{}
+		scopes[scope.name] = scopeInfo
+		scopeInfo["current_api"] = scope.snapshotRelativeCurrentApiTxtPath(baseModuleName)
+		scopeInfo["removed_api"] = scope.snapshotRelativeRemovedApiTxtPath(baseModuleName)
+		if p := scopePaths.latestApiPath; p.Valid() {
+			scopeInfo["latest_api"] = p.Path().String()
+		}
+		if p := scopePaths.latestRemovedApiPath; p.Valid() {
+			scopeInfo["latest_removed_api"] = p.Path().String()
+		}
+	}
+	ctx.SetProvider(android.AdditionalSdkInfoProvider, android.AdditionalSdkInfo{additionalSdkInfo})
 }
 
 func (module *SdkLibrary) AndroidMkEntries() []android.AndroidMkEntries {
@@ -1319,16 +1415,32 @@
 	return proptools.StringDefault(module.sdkLibraryProperties.Dist_group, "unknown")
 }
 
+func latestPrebuiltApiModuleName(name string, apiScope *apiScope) string {
+	return PrebuiltApiModuleName(name, apiScope.name, "latest")
+}
+
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.distStem() + ".api." + apiScope.name + ".latest"
+	return ":" + module.latestApiModuleName(apiScope)
+}
+
+func (module *SdkLibrary) latestApiModuleName(apiScope *apiScope) string {
+	return latestPrebuiltApiModuleName(module.distStem(), apiScope)
 }
 
 func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.distStem() + "-removed.api." + apiScope.name + ".latest"
+	return ":" + module.latestRemovedApiModuleName(apiScope)
+}
+
+func (module *SdkLibrary) latestRemovedApiModuleName(apiScope *apiScope) string {
+	return latestPrebuiltApiModuleName(module.distStem()+"-removed", apiScope)
 }
 
 func (module *SdkLibrary) latestIncompatibilitiesFilegroupName(apiScope *apiScope) string {
-	return ":" + module.distStem() + "-incompatibilities.api." + apiScope.name + ".latest"
+	return ":" + module.latestIncompatibilitiesModuleName(apiScope)
+}
+
+func (module *SdkLibrary) latestIncompatibilitiesModuleName(apiScope *apiScope) string {
+	return latestPrebuiltApiModuleName(module.distStem()+"-incompatibilities", apiScope)
 }
 
 func childModuleVisibility(childVisibility []string) []string {
@@ -1557,7 +1669,7 @@
 	props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName)
 	props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName)
 
-	if !(apiScope.unstable || module.sdkLibraryProperties.Unsafe_ignore_missing_latest_api) {
+	if module.compareAgainstLatestApi(apiScope) {
 		// check against the latest released API
 		latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope))
 		props.Previous_api = latestApiFilegroupName
@@ -1609,6 +1721,10 @@
 	mctx.CreateModule(DroidstubsFactory, &props)
 }
 
+func (module *SdkLibrary) compareAgainstLatestApi(apiScope *apiScope) bool {
+	return !(apiScope.unstable || module.sdkLibraryProperties.Unsafe_ignore_missing_latest_api)
+}
+
 // Implements android.ApexModule
 func (module *SdkLibrary) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool {
 	depTag := mctx.OtherModuleDependencyTag(dep)
@@ -2903,7 +3019,7 @@
 		if properties, ok := s.Scopes[apiScope]; ok {
 			scopeSet := propertySet.AddPropertySet(apiScope.propertyName)
 
-			scopeDir := filepath.Join("sdk_library", s.OsPrefix(), apiScope.name)
+			scopeDir := apiScope.snapshotRelativeDir()
 
 			var jars []string
 			for _, p := range properties.Jars {
@@ -2927,13 +3043,13 @@
 			}
 
 			if properties.CurrentApiFile != nil {
-				currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt")
+				currentApiSnapshotPath := apiScope.snapshotRelativeCurrentApiTxtPath(ctx.Name())
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.CurrentApiFile, currentApiSnapshotPath)
 				scopeSet.AddProperty("current_api", currentApiSnapshotPath)
 			}
 
 			if properties.RemovedApiFile != nil {
-				removedApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"-removed.txt")
+				removedApiSnapshotPath := apiScope.snapshotRelativeRemovedApiTxtPath(ctx.Name())
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.RemovedApiFile, removedApiSnapshotPath)
 				scopeSet.AddProperty("removed_api", removedApiSnapshotPath)
 			}
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 3500c84..805bc22 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -126,6 +126,10 @@
 
 	exportedComponentsInfo := result.ModuleProvider(foo.Module(), android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
 	expectedFooExportedComponents := []string{
+		"foo-removed.api.public.latest",
+		"foo-removed.api.system.latest",
+		"foo.api.public.latest",
+		"foo.api.system.latest",
 		"foo.stubs",
 		"foo.stubs.source",
 		"foo.stubs.source.system",
@@ -529,6 +533,8 @@
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`dex2oatd`,
+		`sdklib-removed.api.public.latest`,
+		`sdklib.api.public.latest`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
 		`sdklib.stubs.source`,
@@ -851,6 +857,8 @@
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_sdklib`,
+		`sdklib-removed.api.public.latest`,
+		`sdklib.api.public.latest`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
 		`sdklib.stubs.source`,
@@ -894,6 +902,8 @@
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`prebuilt_sdklib`,
+		`sdklib-removed.api.public.latest`,
+		`sdklib.api.public.latest`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
 		`sdklib.stubs.source`,
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 39aaf33..aadc00f 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -49,8 +49,8 @@
 			Memtag_heap *bool `android:"arch_variant"`
 		}
 	}
-	SanitizerEnabled bool `blueprint:"mutated"`
-	SanitizeDep      bool `blueprint:"mutated"`
+	SanitizerEnabled bool               `blueprint:"mutated"`
+	SanitizeDepTypes []cc.SanitizerType `blueprint:"mutated"`
 
 	// Used when we need to place libraries in their own directory, such as ASAN.
 	InSanitizerDir bool `blueprint:"mutated"`
@@ -444,8 +444,14 @@
 	return mod.sanitize.isSanitizerExplicitlyDisabled(t)
 }
 
-func (mod *Module) SanitizeDep() bool {
-	return mod.sanitize.Properties.SanitizeDep
+func (mod *Module) SanitizeDep(t cc.SanitizerType) bool {
+	for _, e := range mod.sanitize.Properties.SanitizeDepTypes {
+		if t == e {
+			return true
+		}
+	}
+
+	return false
 }
 
 func (mod *Module) SetSanitizer(t cc.SanitizerType, b bool) {
@@ -454,8 +460,10 @@
 	}
 }
 
-func (mod *Module) SetSanitizeDep(b bool) {
-	mod.sanitize.Properties.SanitizeDep = b
+func (c *Module) SetSanitizeDep(t cc.SanitizerType) {
+	if !c.SanitizeDep(t) {
+		c.sanitize.Properties.SanitizeDepTypes = append(c.sanitize.Properties.SanitizeDepTypes, t)
+	}
 }
 
 func (mod *Module) StaticallyLinked() bool {
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 9d0c3de..f33aa26 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -651,7 +651,19 @@
 }
 
 func TestSnapshotWithJavaSystemModules(t *testing.T) {
-	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithPrebuiltApisAndExtensions(map[string][]string{
+			"31":      {"myjavalib"},
+			"32":      {"myjavalib"},
+			"current": {"myjavalib"},
+		}, map[string][]string{
+			"1": {"myjavalib"},
+			"2": {"myjavalib"},
+		}),
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_header_libs: ["exported-system-module"],
@@ -796,7 +808,7 @@
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
-		checkInfoContents(`
+		checkInfoContents(result.Config, `
 [
   {
     "@type": "sdk",
@@ -826,7 +838,16 @@
   },
   {
     "@type": "java_sdk_library",
-    "@name": "myjavalib"
+    "@name": "myjavalib",
+    "dist_stem": "myjavalib",
+    "scopes": {
+      "public": {
+        "current_api": "sdk_library/public/myjavalib.txt",
+        "latest_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib.api.public.latest/gen/myjavalib.api.public.latest",
+        "latest_removed_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib-removed.api.public.latest/gen/myjavalib-removed.api.public.latest",
+        "removed_api": "sdk_library/public/myjavalib-removed.txt"
+      }
+    }
   },
   {
     "@type": "java_library",
diff --git a/sdk/sdk.go b/sdk/sdk.go
index b37eaad..468ed99 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -281,7 +281,6 @@
 func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
 	ctx.BottomUp("SdkMember", memberMutator).Parallel()
 	ctx.TopDown("SdkMember_deps", memberDepsMutator).Parallel()
-	ctx.BottomUp("SdkMemberInterVersion", memberInterVersionMutator).Parallel()
 }
 
 type dependencyTag struct {
@@ -383,22 +382,6 @@
 	}
 }
 
-// Step 3: create dependencies from the unversioned SDK member to snapshot versions
-// of the same member. By having these dependencies, they are mutated for multiple Mainline modules
-// (apex and apk), each of which might want different sdks to be built with. For example, if both
-// apex A and B are referencing libfoo which is a member of sdk 'mysdk', the two APEXes can be
-// built with libfoo.mysdk.11 and libfoo.mysdk.12, respectively depending on which sdk they are
-// using.
-func memberInterVersionMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(android.SdkAware); ok && m.IsInAnySdk() && m.IsVersioned() {
-		if !m.ContainingSdk().Unversioned() {
-			memberName := m.MemberName()
-			tag := sdkMemberVersionedDepTag{member: memberName, version: m.ContainingSdk().Version}
-			mctx.AddReverseDependency(mctx.Module(), tag, memberName)
-		}
-	}
-}
-
 // An interface that encapsulates all the functionality needed to manage the sdk dependencies.
 //
 // It is a mixture of apex and sdk module functionality.
diff --git a/sdk/testing.go b/sdk/testing.go
index b0f5fdc..72344de 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -406,11 +406,11 @@
 // Check that the snapshot's info contents are ciorrect.
 //
 // Both the expected and actual string are both trimmed before comparing.
-func checkInfoContents(expected string) snapshotBuildInfoChecker {
+func checkInfoContents(config android.Config, expected string) snapshotBuildInfoChecker {
 	return func(info *snapshotBuildInfo) {
 		info.t.Helper()
 		android.AssertTrimmedStringEquals(info.t, "info contents do not match",
-			expected, info.infoContents)
+			expected, android.StringRelativeToTop(config, info.infoContents))
 	}
 }
 
diff --git a/sdk/update.go b/sdk/update.go
index 71bd042..e61ae0d 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -566,8 +566,9 @@
 	name string
 	// A list of additional dependencies of the module.
 	deps []string
-	// Additional dynamic properties.
-	dynamic map[string]interface{}
+	// Additional member specific properties.
+	// These will be added into the generated JSON alongside the above properties.
+	memberSpecific map[string]interface{}
 }
 
 func (m *moduleInfo) MarshalJSON() ([]byte, error) {
@@ -590,8 +591,8 @@
 	if m.deps != nil {
 		writeObjectPair("@deps", m.deps)
 	}
-	for _, k := range android.SortedStringKeys(m.dynamic) {
-		v := m.dynamic[k]
+	for _, k := range android.SortedStringKeys(m.memberSpecific) {
+		v := m.memberSpecific[k]
 		writeObjectPair(k, v)
 	}
 	buffer.WriteString("}")
@@ -604,9 +605,9 @@
 func (s *sdk) generateInfoData(ctx android.ModuleContext, memberVariantDeps []sdkMemberVariantDep) interface{} {
 	modules := []*moduleInfo{}
 	sdkInfo := moduleInfo{
-		moduleType: "sdk",
-		name:       ctx.ModuleName(),
-		dynamic:    map[string]interface{}{},
+		moduleType:     "sdk",
+		name:           ctx.ModuleName(),
+		memberSpecific: map[string]interface{}{},
 	}
 	modules = append(modules, &sdkInfo)
 
@@ -622,6 +623,10 @@
 				moduleType: moduleType,
 				name:       name,
 			}
+
+			additionalSdkInfo := ctx.OtherModuleProvider(module, android.AdditionalSdkInfoProvider).(android.AdditionalSdkInfo)
+			info.memberSpecific = additionalSdkInfo.Properties
+
 			name2Info[name] = info
 		}
 		return info
@@ -630,13 +635,13 @@
 	for _, memberVariantDep := range memberVariantDeps {
 		propertyName := memberVariantDep.memberType.SdkPropertyName()
 		var list []string
-		if v, ok := sdkInfo.dynamic[propertyName]; ok {
+		if v, ok := sdkInfo.memberSpecific[propertyName]; ok {
 			list = v.([]string)
 		}
 
 		memberName := memberVariantDep.variant.Name()
 		list = append(list, memberName)
-		sdkInfo.dynamic[propertyName] = android.SortedUniqueStrings(list)
+		sdkInfo.memberSpecific[propertyName] = android.SortedUniqueStrings(list)
 
 		if memberVariantDep.container != nil {
 			containerInfo := getModuleInfo(memberVariantDep.container)
@@ -1217,6 +1222,7 @@
 	// The target build release for which the snapshot is to be generated.
 	targetBuildRelease *buildRelease
 
+	// The contents of the .info file that describes the sdk contents.
 	infoContents string
 }
 
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 74e49aa..78ddced 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -169,3 +169,29 @@
 }
 
 test_cc_correctness
+
+# Regression test for the following failure during symlink forest creation:
+#
+#   Cannot stat '/tmp/st.rr054/foo/bar/unresolved_symlink': stat /tmp/st.rr054/foo/bar/unresolved_symlink: no such file or directory
+#
+function test_bp2build_null_build_with_unresolved_symlink_in_source() {
+  setup
+
+  mkdir -p foo/bar
+  ln -s /tmp/non-existent foo/bar/unresolved_symlink
+  cat > foo/bar/Android.bp <<'EOF'
+filegroup {
+    name: "fg",
+    srcs: ["unresolved_symlink/non-existent-file.txt"],
+  }
+EOF
+
+  run_soong bp2build
+
+  dest=$(readlink -f out/soong/workspace/foo/bar/unresolved_symlink)
+  if [[ "$dest" != "/tmp/non-existent" ]]; then
+    fail "expected to plant an unresolved symlink out/soong/workspace/foo/bar/unresolved_symlink that resolves to /tmp/non-existent"
+  fi
+}
+
+test_bp2build_null_build_with_unresolved_symlink_in_source
diff --git a/tests/lib.sh b/tests/lib.sh
index 7fd970a..abe84d3 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -127,6 +127,10 @@
 }
 
 run_bazel() {
+  # Remove the ninja_build output marker file to communicate to buildbot that this is not a regular Ninja build, and its
+  # output should not be parsed as such.
+  rm -rf out/ninja_build
+
   tools/bazel "$@"
 }