Merge changes Ifd3117a4,Ic4f8b2b1

* changes:
  Revert "apex: skip symbol files when replaced with prebuilt"
  override_apex can be replaced with apex_set
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index c4e49c8..67ca8ec 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -1521,6 +1521,11 @@
 		"adb_tls_connection_test",
 		// M9: mixed builds for mainline trains launch
 		"api_fingerprint",
+		// M11: neuralnetworks launch
+		"com.android.neuralnetworks",
+		"test_com.android.neuralnetworks",
+		"libneuralnetworks",
+		"libneuralnetworks_static",
 	}
 
 	// Staging-mode allowlist. Modules in this list are only built
@@ -1528,11 +1533,7 @@
 	// which will soon be added to the prod allowlist.
 	// It is implicit that all modules in ProdMixedBuildsEnabledList will
 	// also be built - do not add them to this list.
-	StagingMixedBuildsEnabledList = []string{
-		"com.android.neuralnetworks",
-		"libneuralnetworks",
-		"libneuralnetworks_static",
-	}
+	StagingMixedBuildsEnabledList = []string{}
 
 	// These should be the libs that are included by the apexes in the ProdMixedBuildsEnabledList
 	ProdDclaMixedBuildsEnabledList = []string{
diff --git a/android/apex.go b/android/apex.go
index 823afbb..5dcf73b 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -513,8 +513,9 @@
 // exactly the same set of APEXes (and platform), i.e. if their apex_available
 // properties have the same elements.
 func AvailableToSameApexes(mod1, mod2 ApexModule) bool {
-	mod1ApexAvail := SortedUniqueStrings(mod1.apexModuleBase().ApexProperties.Apex_available)
-	mod2ApexAvail := SortedUniqueStrings(mod2.apexModuleBase().ApexProperties.Apex_available)
+	// Use CopyOf to prevent non-determinism (b/275313114#comment1)
+	mod1ApexAvail := SortedUniqueStrings(CopyOf(mod1.apexModuleBase().ApexProperties.Apex_available))
+	mod2ApexAvail := SortedUniqueStrings(CopyOf(mod2.apexModuleBase().ApexProperties.Apex_available))
 	if len(mod1ApexAvail) != len(mod2ApexAvail) {
 		return false
 	}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 9c273d9..85d00cc 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -18,7 +18,6 @@
 	"bytes"
 	"fmt"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"runtime"
@@ -30,7 +29,6 @@
 	"android/soong/bazel/cquery"
 	"android/soong/shared"
 	"android/soong/starlark_fmt"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/metrics"
 
@@ -220,8 +218,7 @@
 }
 
 type bazelRunner interface {
-	createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
-	issueBazelCommand(bazelCmd *exec.Cmd, eventHandler *metrics.EventHandler) (output string, errorMessage string, error error)
+	issueBazelCommand(cmdRequest bazel.CmdRequest, paths *bazelPaths, eventHandler *metrics.EventHandler) (output string, errorMessage string, error error)
 }
 
 type bazelPaths struct {
@@ -660,36 +657,6 @@
 	expression string
 }
 
-type mockBazelRunner struct {
-	bazelCommandResults map[bazelCommand]string
-	// use *exec.Cmd as a key to get the bazelCommand, the map will be used in issueBazelCommand()
-	// Register createBazelCommand() invocations. Later, an
-	// issueBazelCommand() invocation can be mapped to the *exec.Cmd instance
-	// and then to the expected result via bazelCommandResults
-	tokens     map[*exec.Cmd]bazelCommand
-	commands   []bazelCommand
-	extraFlags []string
-}
-
-func (r *mockBazelRunner) createBazelCommand(_ Config, _ *bazelPaths, _ bazel.RunName,
-	command bazelCommand, extraFlags ...string) *exec.Cmd {
-	r.commands = append(r.commands, command)
-	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
-	cmd := &exec.Cmd{}
-	if r.tokens == nil {
-		r.tokens = make(map[*exec.Cmd]bazelCommand)
-	}
-	r.tokens[cmd] = command
-	return cmd
-}
-
-func (r *mockBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd, _ *metrics.EventHandler) (string, string, error) {
-	if command, ok := r.tokens[bazelCmd]; ok {
-		return r.bazelCommandResults[command], "", nil
-	}
-	return "", "", nil
-}
-
 type builtinBazelRunner struct {
 	useBazelProxy bool
 	outDir        string
@@ -699,17 +666,12 @@
 // Returns (stdout, stderr, error). The first and second return values are strings
 // containing the stdout and stderr of the run command, and an error is returned if
 // the invocation returned an error code.
-func (r *builtinBazelRunner) issueBazelCommand(bazelCmd *exec.Cmd, eventHandler *metrics.EventHandler) (string, string, error) {
+func (r *builtinBazelRunner) issueBazelCommand(cmdRequest bazel.CmdRequest, paths *bazelPaths, eventHandler *metrics.EventHandler) (string, string, error) {
 	if r.useBazelProxy {
 		eventHandler.Begin("client_proxy")
 		defer eventHandler.End("client_proxy")
 		proxyClient := bazel.NewProxyClient(r.outDir)
-		// Omit the arg containing the Bazel binary, as that is handled by the proxy
-		// server.
-		bazelFlags := bazelCmd.Args[1:]
-		// TODO(b/270989498): Refactor these functions to not take exec.Cmd, as its
-		// not actually executed for client proxying.
-		resp, err := proxyClient.IssueCommand(bazel.CmdRequest{bazelFlags, bazelCmd.Env})
+		resp, err := proxyClient.IssueCommand(cmdRequest)
 
 		if err != nil {
 			return "", "", err
@@ -721,26 +683,19 @@
 	} else {
 		eventHandler.Begin("bazel command")
 		defer eventHandler.End("bazel command")
-		stderr := &bytes.Buffer{}
-		bazelCmd.Stderr = stderr
-		if output, err := bazelCmd.Output(); err != nil {
-			return "", string(stderr.Bytes()),
-				fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
-					err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderr)
-		} else {
-			return string(output), string(stderr.Bytes()), nil
-		}
+
+		stdout, stderr, err := bazel.ExecBazel(paths.bazelPath, absolutePath(paths.syntheticWorkspaceDir()), cmdRequest)
+		return string(stdout), string(stderr), err
 	}
 }
 
-func (r *builtinBazelRunner) createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand,
-	extraFlags ...string) *exec.Cmd {
+func (context *mixedBuildBazelContext) createBazelCommand(config Config, runName bazel.RunName, command bazelCommand,
+	extraFlags ...string) bazel.CmdRequest {
 	cmdFlags := []string{
-		"--output_base=" + absolutePath(paths.outputBase),
+		"--output_base=" + absolutePath(context.paths.outputBase),
 		command.command,
 		command.expression,
-		// TODO(asmundak): is it needed in every build?
-		"--profile=" + shared.BazelMetricsFilename(paths, runName),
+		"--profile=" + shared.BazelMetricsFilename(context.paths, runName),
 
 		// We don't need to set --host_platforms because it's set in bazelrc files
 		// that the bazel shell script wrapper passes
@@ -755,15 +710,13 @@
 	}
 	cmdFlags = append(cmdFlags, extraFlags...)
 
-	bazelCmd := exec.Command(paths.bazelPath, cmdFlags...)
-	bazelCmd.Dir = absolutePath(paths.syntheticWorkspaceDir())
 	extraEnv := []string{
-		"HOME=" + paths.homeDir,
+		"HOME=" + context.paths.homeDir,
 		pwdPrefix(),
-		"BUILD_DIR=" + absolutePath(paths.soongOutDir),
+		"BUILD_DIR=" + absolutePath(context.paths.soongOutDir),
 		// Make OUT_DIR absolute here so build/bazel/bin/bazel uses the correct
 		// OUT_DIR at <root>/out, instead of <root>/out/soong/workspace/out.
-		"OUT_DIR=" + absolutePath(paths.outDir()),
+		"OUT_DIR=" + absolutePath(context.paths.outDir()),
 		// Disables local host detection of gcc; toolchain information is defined
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1",
@@ -775,15 +728,15 @@
 		}
 		extraEnv = append(extraEnv, fmt.Sprintf("%s=%s", envvar, val))
 	}
-	bazelCmd.Env = append(os.Environ(), extraEnv...)
+	envVars := append(os.Environ(), extraEnv...)
 
-	return bazelCmd
+	return bazel.CmdRequest{cmdFlags, envVars}
 }
 
-func printableCqueryCommand(bazelCmd *exec.Cmd) string {
-	outputString := strings.Join(bazelCmd.Env, " ") + " \"" + strings.Join(bazelCmd.Args, "\" \"") + "\""
+func (context *mixedBuildBazelContext) printableCqueryCommand(bazelCmd bazel.CmdRequest) string {
+	args := append([]string{context.paths.bazelPath}, bazelCmd.Argv...)
+	outputString := strings.Join(bazelCmd.Env, " ") + " \"" + strings.Join(args, "\" \"") + "\""
 	return outputString
-
 }
 
 func (context *mixedBuildBazelContext) mainBzlFileContents() []byte {
@@ -1106,9 +1059,10 @@
 const buildrootLabel = "@soong_injection//mixed_builds:buildroot"
 
 var (
-	cqueryCmd = bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)}
-	aqueryCmd = bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)}
-	buildCmd  = bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"}
+	cqueryCmd        = bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)}
+	aqueryCmd        = bazelCommand{"aquery", fmt.Sprintf("deps(%s)", buildrootLabel)}
+	buildCmd         = bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"}
+	allBazelCommands = []bazelCommand{aqueryCmd, cqueryCmd, buildCmd}
 )
 
 // Issues commands to Bazel to receive results for all cquery requests
@@ -1170,12 +1124,12 @@
 		extraFlags = append(extraFlags, "--collect_code_coverage")
 	}
 
-	cqueryCommandWithFlag := context.createBazelCommand(config, context.paths, bazel.CqueryBuildRootRunName, cqueryCmd, extraFlags...)
-	cqueryOutput, cqueryErrorMessage, cqueryErr := context.issueBazelCommand(cqueryCommandWithFlag, eventHandler)
+	cqueryCmdRequest := context.createBazelCommand(config, bazel.CqueryBuildRootRunName, cqueryCmd, extraFlags...)
+	cqueryOutput, cqueryErrorMessage, cqueryErr := context.issueBazelCommand(cqueryCmdRequest, context.paths, eventHandler)
 	if cqueryErr != nil {
 		return cqueryErr
 	}
-	cqueryCommandPrint := fmt.Sprintf("cquery command line:\n  %s \n\n\n", printableCqueryCommand(cqueryCommandWithFlag))
+	cqueryCommandPrint := fmt.Sprintf("cquery command line:\n  %s \n\n\n", context.printableCqueryCommand(cqueryCmdRequest))
 	if err := os.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryCommandPrint+cqueryOutput), 0666); err != nil {
 		return err
 	}
@@ -1233,8 +1187,8 @@
 			extraFlags = append(extraFlags, "--instrumentation_filter="+strings.Join(paths, ","))
 		}
 	}
-	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
-		extraFlags...), eventHandler)
+	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(config, bazel.AqueryBuildRootRunName, aqueryCmd,
+		extraFlags...), context.paths, eventHandler)
 	if err != nil {
 		return err
 	}
@@ -1249,7 +1203,7 @@
 	// Issue a build command of the phony root to generate symlink forests for dependencies of the
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
-	_, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd), eventHandler)
+	_, _, err := context.issueBazelCommand(context.createBazelCommand(config, bazel.BazelBuildPhonyRootRunName, buildCmd), context.paths, eventHandler)
 	return err
 }
 
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index c67d7fb..b17b765 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -8,6 +8,7 @@
 	"strings"
 	"testing"
 
+	"android/soong/bazel"
 	"android/soong/bazel/cquery"
 	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
 
@@ -19,6 +20,34 @@
 
 type testInvokeBazelContext struct{}
 
+type mockBazelRunner struct {
+	testHelper *testing.T
+	// Stores mock behavior. If an issueBazelCommand request is made for command
+	// k, and {k:v} is present in this map, then the mock will return v.
+	bazelCommandResults map[bazelCommand]string
+	// Requests actually made of the mockBazelRunner with issueBazelCommand,
+	// keyed by the command they represent.
+	bazelCommandRequests map[bazelCommand]bazel.CmdRequest
+}
+
+func (r *mockBazelRunner) bazelCommandForRequest(cmdRequest bazel.CmdRequest) bazelCommand {
+	for _, arg := range cmdRequest.Argv {
+		for _, cmdType := range allBazelCommands {
+			if arg == cmdType.command {
+				return cmdType
+			}
+		}
+	}
+	r.testHelper.Fatalf("Unrecognized bazel request: %s", cmdRequest)
+	return cqueryCmd
+}
+
+func (r *mockBazelRunner) issueBazelCommand(cmdRequest bazel.CmdRequest, paths *bazelPaths, eventHandler *metrics.EventHandler) (string, string, error) {
+	command := r.bazelCommandForRequest(cmdRequest)
+	r.bazelCommandRequests[command] = cmdRequest
+	return r.bazelCommandResults[command], "", nil
+}
+
 func (t *testInvokeBazelContext) GetEventHandler() *metrics.EventHandler {
 	return &metrics.EventHandler{}
 }
@@ -36,9 +65,7 @@
 		`@//foo:foo|arm64_armv8-a|android|within_apex|29>>out/foo/foo.txt`,
 		`@//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
 	}
-	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: strings.Join(cmd_results, "\n"),
-	})
+	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{cqueryCmd: strings.Join(cmd_results, "\n")})
 
 	bazelContext.QueueBazelRequest(label_foo, cquery.GetOutputFiles, cfg_foo)
 	bazelContext.QueueBazelRequest(label_bar, cquery.GetOutputFiles, cfg_bar)
@@ -139,8 +166,7 @@
 		if err != nil {
 			t.Error(err)
 		}
-		bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-			bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"}: string(data)})
+		bazelContext, _ := testBazelContext(t, map[bazelCommand]string{aqueryCmd: string(data)})
 
 		err = bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
 		if err != nil {
@@ -166,30 +192,26 @@
 
 	testConfig.productVariables.NativeCoveragePaths = []string{"foo1", "foo2"}
 	testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1", "bar2"}
-	verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,+foo2,-bar1,-bar2`)
+	verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,+foo2,-bar1,-bar2")
 
 	testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
 	testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
-	verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1,-bar1`)
+	verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,-bar1")
 
 	testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
 	testConfig.productVariables.NativeCoverageExcludePaths = nil
-	verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+foo1`)
+	verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1")
 
 	testConfig.productVariables.NativeCoveragePaths = nil
 	testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
-	verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=-bar1`)
+	verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=-bar1")
 
 	testConfig.productVariables.NativeCoveragePaths = []string{"*"}
 	testConfig.productVariables.NativeCoverageExcludePaths = nil
-	verifyExtraFlags(t, testConfig, `--collect_code_coverage --instrumentation_filter=+.*`)
+	verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+.*")
 
 	testConfig.productVariables.ClangCoverage = boolPtr(false)
-	actual := verifyExtraFlags(t, testConfig, ``)
-	if strings.Contains(actual, "--collect_code_coverage") ||
-		strings.Contains(actual, "--instrumentation_filter=") {
-		t.Errorf("Expected code coverage disabled, but got %#v", actual)
-	}
+	verifyAqueryDoesNotContainSubstrings(t, testConfig, "collect_code_coverage", "instrumentation_filter")
 }
 
 func TestBazelRequestsSorted(t *testing.T) {
@@ -268,7 +290,8 @@
 	}
 }
 
-func verifyExtraFlags(t *testing.T, config Config, expected string) string {
+func verifyAqueryContainsFlags(t *testing.T, config Config, expected ...string) {
+	t.Helper()
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
 
 	err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
@@ -276,17 +299,49 @@
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
 
-	flags := bazelContext.bazelRunner.(*mockBazelRunner).extraFlags
-	if expected := 3; len(flags) != expected {
-		t.Errorf("Expected %d extra flags got %#v", expected, flags)
+	sliceContains := func(slice []string, x string) bool {
+		for _, s := range slice {
+			if s == x {
+				return true
+			}
+		}
+		return false
 	}
 
-	actual := flags[1]
-	if !strings.Contains(actual, expected) {
-		t.Errorf("Expected %#v got %#v", expected, actual)
+	aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
+
+	for _, expectedFlag := range expected {
+		if !sliceContains(aqueryArgv, expectedFlag) {
+			t.Errorf("aquery does not contain expected flag %#v. Argv was: %#v", expectedFlag, aqueryArgv)
+		}
+	}
+}
+
+func verifyAqueryDoesNotContainSubstrings(t *testing.T, config Config, substrings ...string) {
+	t.Helper()
+	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
+
+	err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
+	if err != nil {
+		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
 
-	return actual
+	sliceContainsSubstring := func(slice []string, substring string) bool {
+		for _, s := range slice {
+			if strings.Contains(s, substring) {
+				return true
+			}
+		}
+		return false
+	}
+
+	aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
+
+	for _, substring := range substrings {
+		if sliceContainsSubstring(aqueryArgv, substring) {
+			t.Errorf("aquery contains unexpected substring %#v. Argv was: %#v", substring, aqueryArgv)
+		}
+	}
 }
 
 func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*mixedBuildBazelContext, string) {
@@ -296,11 +351,14 @@
 		outputBase:   "outputbase",
 		workspaceDir: "workspace_dir",
 	}
-	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"}
-	if _, exists := bazelCommandResults[aqueryCommand]; !exists {
-		bazelCommandResults[aqueryCommand] = ""
+	if _, exists := bazelCommandResults[aqueryCmd]; !exists {
+		bazelCommandResults[aqueryCmd] = ""
 	}
-	runner := &mockBazelRunner{bazelCommandResults: bazelCommandResults}
+	runner := &mockBazelRunner{
+		testHelper:           t,
+		bazelCommandResults:  bazelCommandResults,
+		bazelCommandRequests: map[bazelCommand]bazel.CmdRequest{},
+	}
 	return &mixedBuildBazelContext{
 		bazelRunner: runner,
 		paths:       &p,
diff --git a/android/sdk_version.go b/android/sdk_version.go
index 0ae8073..08762ef 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -100,6 +100,15 @@
 	return name
 }
 
+// JavaApiLibraryNames applies JavaApiLibraryName to the list of java_library names.
+func JavaApiLibraryNames(c Config, names []string) []string {
+	apiLibs := make([]string, len(names))
+	for i, name := range names {
+		apiLibs[i] = JavaApiLibraryName(c, name)
+	}
+	return apiLibs
+}
+
 func (k SdkKind) DefaultJavaLibraryName() string {
 	switch k {
 	case SdkPublic:
diff --git a/bazel/bazel_proxy.go b/bazel/bazel_proxy.go
index d7f5e64..2940b99 100644
--- a/bazel/bazel_proxy.go
+++ b/bazel/bazel_proxy.go
@@ -128,6 +128,24 @@
 	}
 }
 
+func ExecBazel(bazelPath string, workspaceDir string, request CmdRequest) (stdout []byte, stderr []byte, cmdErr error) {
+	bazelCmd := exec.Command(bazelPath, request.Argv...)
+	bazelCmd.Dir = workspaceDir
+	bazelCmd.Env = request.Env
+
+	stderrBuffer := &bytes.Buffer{}
+	bazelCmd.Stderr = stderrBuffer
+
+	if output, err := bazelCmd.Output(); err != nil {
+		cmdErr = fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
+			err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderrBuffer)
+	} else {
+		stdout = output
+	}
+	stderr = stderrBuffer.Bytes()
+	return
+}
+
 func (b *ProxyServer) handleRequest(conn net.Conn) error {
 	defer conn.Close()
 
@@ -137,23 +155,13 @@
 		return fmt.Errorf("Error decoding request: %s", err)
 	}
 
-	bazelCmd := exec.Command("./build/bazel/bin/bazel", req.Argv...)
-	bazelCmd.Dir = b.workspaceDir
-	bazelCmd.Env = req.Env
-
-	stderr := &bytes.Buffer{}
-	bazelCmd.Stderr = stderr
-	var stdout string
-	var bazelErrString string
-
-	if output, err := bazelCmd.Output(); err != nil {
-		bazelErrString = fmt.Sprintf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
-			err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderr)
-	} else {
-		stdout = string(output)
+	stdout, stderr, cmdErr := ExecBazel("./build/bazel/bin/bazel", b.workspaceDir, req)
+	errorString := ""
+	if cmdErr != nil {
+		errorString = cmdErr.Error()
 	}
 
-	resp := CmdResponse{stdout, string(stderr.Bytes()), bazelErrString}
+	resp := CmdResponse{string(stdout), string(stderr), errorString}
 	enc := gob.NewEncoder(conn)
 	if err := enc.Encode(&resp); err != nil {
 		return fmt.Errorf("Error encoding response: %s", err)
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index bf3a6b5..6a3b3c8 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -149,6 +149,7 @@
 rootSharedLibraries = []
 
 shared_info_tag = "//build/bazel/rules/cc:cc_library_shared.bzl%CcSharedLibraryOutputInfo"
+stubs_tag = "//build/bazel/rules/cc:cc_stub_library.bzl%CcStubInfo"
 unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
 unstripped = ""
 
@@ -160,6 +161,8 @@
   unstripped = path
   if unstripped_tag in p:
     unstripped = p[unstripped_tag].unstripped.path
+elif stubs_tag in p:
+  rootSharedLibraries.extend([f.path for f in target.files.to_list()])
 else:
   for linker_input in linker_inputs:
     for library in linker_input.libraries:
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
index 29316ab..f5d62c6 100644
--- a/bp2build/cc_library_shared_conversion_test.go
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -1291,3 +1291,66 @@
 		},
 	})
 }
+
+func TestCcLibrarySharedStubsDessertVersionConversion(t *testing.T) {
+	runCcLibrarySharedTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_shared converts dessert codename versions to numerical versions",
+		Blueprint: `
+cc_library_shared {
+	name: "a",
+	include_build_directory: false,
+	stubs: {
+		symbol_file: "a.map.txt",
+		versions: [
+			"Q",
+			"R",
+			"31",
+		],
+	},
+}
+cc_library_shared {
+	name: "b",
+	include_build_directory: false,
+	stubs: {
+		symbol_file: "b.map.txt",
+		versions: [
+			"Q",
+			"R",
+			"31",
+			"current",
+		],
+	},
+}
+`,
+		ExpectedBazelTargets: []string{
+			makeCcStubSuiteTargets("a", AttrNameToString{
+				"soname":               `"a.so"`,
+				"source_library_label": `"//:a"`,
+				"stubs_symbol_file":    `"a.map.txt"`,
+				"stubs_versions": `[
+        "29",
+        "30",
+        "31",
+        "current",
+    ]`,
+			}),
+			MakeBazelTarget("cc_library_shared", "a", AttrNameToString{
+				"stubs_symbol_file": `"a.map.txt"`,
+			}),
+			makeCcStubSuiteTargets("b", AttrNameToString{
+				"soname":               `"b.so"`,
+				"source_library_label": `"//:b"`,
+				"stubs_symbol_file":    `"b.map.txt"`,
+				"stubs_versions": `[
+        "29",
+        "30",
+        "31",
+        "current",
+    ]`,
+			}),
+			MakeBazelTarget("cc_library_shared", "b", AttrNameToString{
+				"stubs_symbol_file": `"b.map.txt"`,
+			}),
+		},
+	})
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 96c4789..ad9d702 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -772,8 +772,13 @@
 
 			if libraryProps, ok := archVariantLibraryProperties[axis][cfg].(*LibraryProperties); ok {
 				if axis == bazel.NoConfigAxis {
-					compilerAttrs.stubsSymbolFile = libraryProps.Stubs.Symbol_file
-					compilerAttrs.stubsVersions.SetSelectValue(axis, cfg, libraryProps.Stubs.Versions)
+					if libraryProps.Stubs.Symbol_file != nil {
+						compilerAttrs.stubsSymbolFile = libraryProps.Stubs.Symbol_file
+						versions := android.CopyOf(libraryProps.Stubs.Versions)
+						normalizeVersions(ctx, versions)
+						versions = addCurrentVersionIfNotPresent(versions)
+						compilerAttrs.stubsVersions.SetSelectValue(axis, cfg, versions)
+					}
 				}
 				if suffix := libraryProps.Suffix; suffix != nil {
 					compilerAttrs.suffix.SetSelectValue(axis, cfg, suffix)
@@ -1118,8 +1123,11 @@
 		// having stubs or not, so Bazel select() statement can be used to choose
 		// source/stub variants of them.
 		apexAvailable := module.ApexAvailable()
-		setStubsForDynamicDeps(ctx, axis, config, apexAvailable, sharedDeps.export, &la.dynamicDeps, 0)
-		setStubsForDynamicDeps(ctx, axis, config, apexAvailable, sharedDeps.implementation, &la.implementationDynamicDeps, 1)
+		setStubsForDynamicDeps(ctx, axis, config, apexAvailable, sharedDeps.export, &la.dynamicDeps, 0, false)
+		setStubsForDynamicDeps(ctx, axis, config, apexAvailable, sharedDeps.implementation, &la.implementationDynamicDeps, 1, false)
+		if len(systemSharedLibs) > 0 {
+			setStubsForDynamicDeps(ctx, axis, config, apexAvailable, bazelLabelForSharedDeps(ctx, systemSharedLibs), &la.systemDynamicDeps, 2, true)
+		}
 	}
 
 	if !BoolDefault(props.Pack_relocations, packRelocationsDefault) {
@@ -1189,7 +1197,7 @@
 }
 
 func setStubsForDynamicDeps(ctx android.BazelConversionPathContext, axis bazel.ConfigurationAxis,
-	config string, apexAvailable []string, dynamicLibs bazel.LabelList, dynamicDeps *bazel.LabelListAttribute, ind int) {
+	config string, apexAvailable []string, dynamicLibs bazel.LabelList, dynamicDeps *bazel.LabelListAttribute, ind int, buildNonApexWithStubs bool) {
 
 	depsWithStubs := []bazel.Label{}
 	for _, l := range dynamicLibs.Includes {
@@ -1215,16 +1223,20 @@
 		inApexSelectValue := dynamicDeps.SelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndInApex)
 		nonApexSelectValue := dynamicDeps.SelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndNonApex)
 		defaultSelectValue := dynamicDeps.SelectValue(bazel.OsAndInApexAxis, bazel.ConditionsDefaultConfigKey)
+		nonApexDeps := depsWithStubs
+		if buildNonApexWithStubs {
+			nonApexDeps = stubLibLabels
+		}
 		if axis == bazel.NoConfigAxis {
 			(&inApexSelectValue).Append(bazel.MakeLabelList(stubLibLabels))
-			(&nonApexSelectValue).Append(bazel.MakeLabelList(depsWithStubs))
+			(&nonApexSelectValue).Append(bazel.MakeLabelList(nonApexDeps))
 			(&defaultSelectValue).Append(bazel.MakeLabelList(depsWithStubs))
 			dynamicDeps.SetSelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndInApex, bazel.FirstUniqueBazelLabelList(inApexSelectValue))
 			dynamicDeps.SetSelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndNonApex, bazel.FirstUniqueBazelLabelList(nonApexSelectValue))
 			dynamicDeps.SetSelectValue(bazel.OsAndInApexAxis, bazel.ConditionsDefaultConfigKey, bazel.FirstUniqueBazelLabelList(defaultSelectValue))
 		} else if config == bazel.OsAndroid {
 			(&inApexSelectValue).Append(bazel.MakeLabelList(stubLibLabels))
-			(&nonApexSelectValue).Append(bazel.MakeLabelList(depsWithStubs))
+			(&nonApexSelectValue).Append(bazel.MakeLabelList(nonApexDeps))
 			dynamicDeps.SetSelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndInApex, bazel.FirstUniqueBazelLabelList(inApexSelectValue))
 			dynamicDeps.SetSelectValue(bazel.OsAndInApexAxis, bazel.AndroidAndNonApex, bazel.FirstUniqueBazelLabelList(nonApexSelectValue))
 		}
diff --git a/cc/cc.go b/cc/cc.go
index 82cfe90..c1a1020 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1976,6 +1976,17 @@
 	c.maybeInstall(mctx, apexInfo)
 }
 
+func moduleContextFromAndroidModuleContext(actx android.ModuleContext, c *Module) ModuleContext {
+	ctx := &moduleContext{
+		ModuleContext: actx,
+		moduleContextImpl: moduleContextImpl{
+			mod: c,
+		},
+	}
+	ctx.ctx = ctx
+	return ctx
+}
+
 func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
 	// Handle the case of a test module split by `test_per_src` mutator.
 	//
@@ -1995,13 +2006,7 @@
 
 	c.makeLinkType = GetMakeLinkType(actx, c)
 
-	ctx := &moduleContext{
-		ModuleContext: actx,
-		moduleContextImpl: moduleContextImpl{
-			mod: c,
-		},
-	}
-	ctx.ctx = ctx
+	ctx := moduleContextFromAndroidModuleContext(actx, c)
 
 	deps := c.depsToPaths(ctx)
 	if ctx.Failed() {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 830de40..787669c 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -3578,9 +3578,6 @@
 }
 
 func TestStubsForLibraryInMultipleApexes(t *testing.T) {
-	// TODO(b/275313114): Test exposes non-determinism which should be corrected and the test
-	// reenabled.
-	t.Skip()
 	t.Parallel()
 	ctx := testCc(t, `
 		cc_library_shared {
@@ -3685,6 +3682,133 @@
 	}
 }
 
+func TestMixedBuildUsesStubs(t *testing.T) {
+	// TODO(b/275313114): Test exposes non-determinism which should be corrected and the test
+	// reenabled.
+	t.Skip()
+	t.Parallel()
+	bp := `
+		cc_library_shared {
+			name: "libFoo",
+			bazel_module: { label: "//:libFoo" },
+			srcs: ["foo.c"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["current"],
+			},
+			apex_available: ["bar", "a1"],
+		}
+
+		cc_library_shared {
+			name: "libBar",
+			srcs: ["bar.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["a1"],
+		}
+
+		cc_library_shared {
+			name: "libA1",
+			srcs: ["a1.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["a1"],
+		}
+
+		cc_library_shared {
+			name: "libBarA1",
+			srcs: ["bara1.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["bar", "a1"],
+		}
+
+		cc_library_shared {
+			name: "libAnyApex",
+			srcs: ["anyApex.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["//apex_available:anyapex"],
+		}
+
+		cc_library_shared {
+			name: "libBaz",
+			srcs: ["baz.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["baz"],
+		}
+
+		cc_library_shared {
+			name: "libQux",
+			srcs: ["qux.c"],
+			shared_libs: ["libFoo"],
+			apex_available: ["qux", "bar"],
+		}`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.BazelContext = android.MockBazelContext{
+				OutputBaseDir: "out/bazel",
+				LabelToCcInfo: map[string]cquery.CcInfo{
+					"//:libFoo": {
+						RootDynamicLibraries: []string{"libFoo.so"},
+					},
+					"//:libFoo_stub_libs-current": {
+						RootDynamicLibraries: []string{"libFoo_stub_libs-current.so"},
+					},
+				},
+			}
+		}),
+	).RunTestWithBp(t, bp)
+	ctx := result.TestContext
+
+	variants := ctx.ModuleVariantsForTests("libFoo")
+	expectedVariants := []string{
+		"android_arm64_armv8-a_shared",
+		"android_arm64_armv8-a_shared_current",
+		"android_arm_armv7-a-neon_shared",
+		"android_arm_armv7-a-neon_shared_current",
+	}
+	variantsMismatch := false
+	if len(variants) != len(expectedVariants) {
+		variantsMismatch = true
+	} else {
+		for _, v := range expectedVariants {
+			if !inList(v, variants) {
+				variantsMismatch = false
+			}
+		}
+	}
+	if variantsMismatch {
+		t.Errorf("variants of libFoo expected:\n")
+		for _, v := range expectedVariants {
+			t.Errorf("%q\n", v)
+		}
+		t.Errorf(", but got:\n")
+		for _, v := range variants {
+			t.Errorf("%q\n", v)
+		}
+	}
+
+	linkAgainstFoo := []string{"libBarA1"}
+	linkAgainstFooStubs := []string{"libBar", "libA1", "libBaz", "libQux", "libAnyApex"}
+
+	libFooPath := "out/bazel/execroot/__main__/libFoo.so"
+	for _, lib := range linkAgainstFoo {
+		libLinkRule := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared").Rule("ld")
+		libFlags := libLinkRule.Args["libFlags"]
+		if !strings.Contains(libFlags, libFooPath) {
+			t.Errorf("%q: %q is not found in %q", lib, libFooPath, libFlags)
+		}
+	}
+
+	libFooStubPath := "out/bazel/execroot/__main__/libFoo_stub_libs-current.so"
+	for _, lib := range linkAgainstFooStubs {
+		libLinkRule := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared").Rule("ld")
+		libFlags := libLinkRule.Args["libFlags"]
+		if !strings.Contains(libFlags, libFooStubPath) {
+			t.Errorf("%q: %q is not found in %q", lib, libFooStubPath, libFlags)
+		}
+	}
+}
+
 func TestVersioningMacro(t *testing.T) {
 	t.Parallel()
 	for _, tc := range []struct{ moduleName, expected string }{
diff --git a/cc/library.go b/cc/library.go
index 1daeeff..ee09389 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -936,9 +936,17 @@
 func (handler *ccLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
 	bazelCtx := ctx.Config().BazelContext
 	bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKeyApexVariant(ctx, GetApexConfigKey(ctx)))
+	if v := handler.module.library.stubsVersion(); v != "" {
+		stubsLabel := label + "_stub_libs-" + v
+		bazelCtx.QueueBazelRequest(stubsLabel, cquery.GetCcInfo, android.GetConfigKeyApexVariant(ctx, GetApexConfigKey(ctx)))
+	}
 }
 
 func (handler *ccLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	if v := handler.module.library.stubsVersion(); v != "" {
+		// if we are a stubs variant, just use the Bazel stubs target
+		label = label + "_stub_libs-" + v
+	}
 	bazelCtx := ctx.Config().BazelContext
 	ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKeyApexVariant(ctx, GetApexConfigKey(ctx)))
 	if err != nil {
@@ -967,6 +975,9 @@
 	}
 
 	handler.module.setAndroidMkVariablesFromCquery(ccInfo.CcAndroidMkInfo)
+
+	cctx := moduleContextFromAndroidModuleContext(ctx, handler.module)
+	addStubDependencyProviders(cctx)
 }
 
 func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
@@ -1792,6 +1803,12 @@
 		Target:                               ctx.Target(),
 	})
 
+	addStubDependencyProviders(ctx)
+
+	return unstrippedOutputFile
+}
+
+func addStubDependencyProviders(ctx ModuleContext) {
 	stubs := ctx.GetDirectDepsWithTag(stubImplDepTag)
 	if len(stubs) > 0 {
 		var stubsInfo []SharedStubLibrary
@@ -1806,12 +1823,9 @@
 		}
 		ctx.SetProvider(SharedLibraryStubsProvider, SharedLibraryStubsInfo{
 			SharedStubLibraries: stubsInfo,
-
-			IsLLNDK: ctx.IsLlndk(),
+			IsLLNDK:             ctx.IsLlndk(),
 		})
 	}
-
-	return unstrippedOutputFile
 }
 
 func (library *libraryDecorator) unstrippedOutputFilePath() android.Path {
@@ -2397,7 +2411,10 @@
 	}
 
 	// Future API level is implicitly added if there isn't
-	vers := library.Properties.Stubs.Versions
+	return addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+}
+
+func addCurrentVersionIfNotPresent(vers []string) []string {
 	if inList(android.FutureApiLevel.String(), vers) {
 		return vers
 	}
@@ -2662,7 +2679,7 @@
 // normalizeVersions modifies `versions` in place, so that each raw version
 // string becomes its normalized canonical form.
 // Validates that the versions in `versions` are specified in least to greatest order.
-func normalizeVersions(ctx android.BaseModuleContext, versions []string) {
+func normalizeVersions(ctx android.BazelConversionPathContext, versions []string) {
 	var previous android.ApiLevel
 	for i, v := range versions {
 		ver, err := android.ApiLevelFromUser(ctx, v)
diff --git a/cc/util.go b/cc/util.go
index aa0f6b5..6d8ac43 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -100,17 +100,6 @@
 		"ln -sf " + target + " " + filepath.Join(dir, linkName)
 }
 
-func combineNoticesRule(ctx android.SingletonContext, paths android.Paths, out string) android.OutputPath {
-	outPath := android.PathForOutput(ctx, out)
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.Cat,
-		Inputs:      paths,
-		Output:      outPath,
-		Description: "combine notices for " + out,
-	})
-	return outPath
-}
-
 // Dump a map to a list file as:
 //
 // {key1} {value1}
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index aed3775..e6e5660 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -160,7 +160,7 @@
 	MinSdkVersion  string   `json:",omitempty"`
 }
 
-var ccSnapshotAction snapshot.GenerateSnapshotAction = func(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths {
+var ccSnapshotAction snapshot.GenerateSnapshotAction = func(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) snapshot.SnapshotPaths {
 	/*
 		Vendor snapshot zipped artifacts directory structure for cc modules:
 		{SNAPSHOT_ARCH}/
@@ -195,10 +195,10 @@
 	*/
 
 	var snapshotOutputs android.Paths
+	var snapshotNotices android.Paths
 
 	includeDir := filepath.Join(snapshotArchDir, "include")
 	configsDir := filepath.Join(snapshotArchDir, "configs")
-	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
 
 	installedNotices := make(map[string]bool)
 	installedConfigs := make(map[string]bool)
@@ -228,7 +228,7 @@
 		prop := snapshotJsonFlags{}
 
 		// Common properties among snapshots.
-		prop.ModuleName = ctx.ModuleName(m)
+		prop.InitBaseSnapshotPropsWithName(m, ctx.ModuleName(m))
 		if supportsVndkExt(s.Image) && m.IsVndkExt() {
 			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
 			if m.IsVndkSp() {
@@ -406,13 +406,10 @@
 			headers = append(headers, m.SnapshotHeaders()...)
 		}
 
-		if len(m.EffectiveLicenseFiles()) > 0 {
-			noticeName := ctx.ModuleName(m) + ".txt"
-			noticeOut := filepath.Join(noticeDir, noticeName)
-			// skip already copied notice file
-			if !installedNotices[noticeOut] {
-				installedNotices[noticeOut] = true
-				snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.EffectiveLicenseFiles(), noticeOut))
+		for _, notice := range m.EffectiveLicenseFiles() {
+			if _, ok := installedNotices[notice.String()]; !ok {
+				installedNotices[notice.String()] = true
+				snapshotNotices = append(snapshotNotices, notice)
 			}
 		}
 	})
@@ -422,7 +419,7 @@
 		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, header, filepath.Join(includeDir, header.String()), s.Fake))
 	}
 
-	return snapshotOutputs
+	return snapshot.SnapshotPaths{OutputFiles: snapshotOutputs, NoticeFiles: snapshotNotices}
 }
 
 func init() {
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index dcd7fdc..6817dce 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -617,7 +617,7 @@
 	return true
 }
 
-func generatePrebuiltSnapshot(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths {
+func generatePrebuiltSnapshot(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) snapshot.SnapshotPaths {
 	/*
 		Snapshot zipped artifacts directory structure for etc modules:
 		{SNAPSHOT_ARCH}/
@@ -631,7 +631,7 @@
 				(notice files)
 	*/
 	var snapshotOutputs android.Paths
-	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
+	var snapshotNotices android.Paths
 	installedNotices := make(map[string]bool)
 
 	ctx.VisitAllModules(func(module android.Module) {
@@ -651,7 +651,7 @@
 
 		prop := snapshot.SnapshotJsonFlags{}
 		propOut := snapshotLibOut + ".json"
-		prop.ModuleName = m.BaseModuleName()
+		prop.InitBaseSnapshotProps(m)
 		if m.subdirProperties.Relative_install_path != nil {
 			prop.RelativeInstallPath = *m.subdirProperties.Relative_install_path
 		}
@@ -667,27 +667,16 @@
 		}
 		snapshotOutputs = append(snapshotOutputs, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
 
-		if len(m.EffectiveLicenseFiles()) > 0 {
-			noticeName := ctx.ModuleName(m) + ".txt"
-			noticeOut := filepath.Join(noticeDir, noticeName)
-			// skip already copied notice file
-			if !installedNotices[noticeOut] {
-				installedNotices[noticeOut] = true
-
-				noticeOutPath := android.PathForOutput(ctx, noticeOut)
-				ctx.Build(pctx, android.BuildParams{
-					Rule:        android.Cat,
-					Inputs:      m.EffectiveLicenseFiles(),
-					Output:      noticeOutPath,
-					Description: "combine notices for " + noticeOut,
-				})
-				snapshotOutputs = append(snapshotOutputs, noticeOutPath)
+		for _, notice := range m.EffectiveLicenseFiles() {
+			if _, ok := installedNotices[notice.String()]; !ok {
+				installedNotices[notice.String()] = true
+				snapshotNotices = append(snapshotNotices, notice)
 			}
 		}
 
 	})
 
-	return snapshotOutputs
+	return snapshot.SnapshotPaths{OutputFiles: snapshotOutputs, NoticeFiles: snapshotNotices}
 }
 
 // For Bazel / bp2build
diff --git a/java/base.go b/java/base.go
index 9911323..374138c 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1923,9 +1923,12 @@
 
 func (m *Module) getSdkLinkType(ctx android.BaseModuleContext, name string) (ret sdkLinkType, stubs bool) {
 	switch name {
-	case android.SdkCore.JavaLibraryName(ctx.Config()), "legacy.core.platform.api.stubs", "stable.core.platform.api.stubs",
+	case android.SdkCore.JavaLibraryName(ctx.Config()),
+		android.JavaApiLibraryName(ctx.Config(), "legacy.core.platform.api.stubs"),
+		android.JavaApiLibraryName(ctx.Config(), "stable.core.platform.api.stubs"),
 		"stub-annotations", "private-stub-annotations-jar",
-		"core-lambda-stubs", "core-generated-annotation-stubs":
+		android.JavaApiLibraryName(ctx.Config(), "core-lambda-stubs"),
+		"core-generated-annotation-stubs":
 		return javaCore, true
 	case android.SdkPublic.JavaLibraryName(ctx.Config()):
 		return javaSdk, true
diff --git a/java/config/makevars.go b/java/config/makevars.go
index 273aca0..d383d98 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -28,8 +28,11 @@
 	ctx.Strict("FRAMEWORK_LIBRARIES", strings.Join(FrameworkLibraries, " "))
 
 	// These are used by make when LOCAL_PRIVATE_PLATFORM_APIS is set (equivalent to platform_apis in blueprint):
-	ctx.Strict("LEGACY_CORE_PLATFORM_BOOTCLASSPATH_LIBRARIES", strings.Join(LegacyCorePlatformBootclasspathLibraries, " "))
-	ctx.Strict("LEGACY_CORE_PLATFORM_SYSTEM_MODULES", LegacyCorePlatformSystemModules)
+	ctx.Strict("LEGACY_CORE_PLATFORM_BOOTCLASSPATH_LIBRARIES",
+		strings.Join(android.JavaApiLibraryNames(ctx.Config(), LegacyCorePlatformBootclasspathLibraries), " "))
+	ctx.Strict("LEGACY_CORE_PLATFORM_SYSTEM_MODULES",
+		android.JavaApiLibraryName(ctx.Config(), LegacyCorePlatformSystemModules),
+	)
 
 	ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}")
 	ctx.Strict("ANDROID_JAVA8_HOME", "prebuilts/jdk/jdk8/${hostPrebuiltTag}")
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index c6176e6..96e084a 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -241,7 +241,7 @@
 		testStubModules = append(testStubModules, android.SdkTest.JavaLibraryName(config))
 	}
 	// We do not have prebuilts of the core platform api yet
-	corePlatformStubModules = append(corePlatformStubModules, "legacy.core.platform.api.stubs")
+	corePlatformStubModules = append(corePlatformStubModules, android.JavaApiLibraryName(config, "legacy.core.platform.api.stubs"))
 
 	// Allow products to define their own stubs for custom product jars that apps can use.
 	publicStubModules = append(publicStubModules, config.ProductHiddenAPIStubs()...)
diff --git a/java/java.go b/java/java.go
index 2de4ea9..fe4df75 100644
--- a/java/java.go
+++ b/java/java.go
@@ -456,7 +456,9 @@
 		ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
 		ctx.AddVariationDependencies(nil, sdkLibTag, sdkDep.classpath...)
 		if d.effectiveOptimizeEnabled() && sdkDep.hasStandardLibs() {
-			ctx.AddVariationDependencies(nil, proguardRaiseTag, config.LegacyCorePlatformBootclasspathLibraries...)
+			ctx.AddVariationDependencies(nil, proguardRaiseTag,
+				android.JavaApiLibraryNames(ctx.Config(), config.LegacyCorePlatformBootclasspathLibraries)...,
+			)
 		}
 		if d.effectiveOptimizeEnabled() && sdkDep.hasFrameworkLibs() {
 			ctx.AddVariationDependencies(nil, proguardRaiseTag, config.FrameworkLibraries...)
diff --git a/java/legacy_core_platform_api_usage.go b/java/legacy_core_platform_api_usage.go
index 6cb549e..04c6d05 100644
--- a/java/legacy_core_platform_api_usage.go
+++ b/java/legacy_core_platform_api_usage.go
@@ -93,16 +93,16 @@
 
 func corePlatformSystemModules(ctx android.EarlyModuleContext) string {
 	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
-		return config.LegacyCorePlatformSystemModules
+		return android.JavaApiLibraryName(ctx.Config(), config.LegacyCorePlatformSystemModules)
 	} else {
-		return config.StableCorePlatformSystemModules
+		return android.JavaApiLibraryName(ctx.Config(), config.StableCorePlatformSystemModules)
 	}
 }
 
 func corePlatformBootclasspathLibraries(ctx android.EarlyModuleContext) []string {
 	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
-		return config.LegacyCorePlatformBootclasspathLibraries
+		return android.JavaApiLibraryNames(ctx.Config(), config.LegacyCorePlatformBootclasspathLibraries)
 	} else {
-		return config.StableCorePlatformBootclasspathLibraries
+		return android.JavaApiLibraryNames(ctx.Config(), config.StableCorePlatformBootclasspathLibraries)
 	}
 }
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index 9793218..edcc163 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -96,6 +96,7 @@
 	var jsonData []SnapshotJsonFlags
 	var metaPaths android.Paths
 
+	installedNotices := make(map[string]bool)
 	metaZipFile := android.PathForModuleOut(ctx, fileName).OutputPath
 
 	// Create JSON file based on the direct dependencies
@@ -104,12 +105,14 @@
 		if desc != nil {
 			jsonData = append(jsonData, *desc)
 		}
-		if len(dep.EffectiveLicenseFiles()) > 0 {
-			noticeFile := android.PathForModuleOut(ctx, "NOTICE_FILES", dep.Name()+".txt").OutputPath
-			android.CatFileRule(ctx, dep.EffectiveLicenseFiles(), noticeFile)
-			metaPaths = append(metaPaths, noticeFile)
+		for _, notice := range dep.EffectiveLicenseFiles() {
+			if _, ok := installedNotices[notice.String()]; !ok {
+				installedNotices[notice.String()] = true
+				noticeOut := android.PathForModuleOut(ctx, "NOTICE_FILES", notice.String()).OutputPath
+				CopyFileToOutputPathRule(pctx, ctx, notice, noticeOut)
+				metaPaths = append(metaPaths, noticeOut)
+			}
 		}
-
 	})
 	// Sort notice paths and json data for repeatble build
 	sort.Slice(jsonData, func(i, j int) bool {
@@ -220,8 +223,7 @@
 	}
 
 	if path.Valid() && path.String() != "" {
-		return &SnapshotJsonFlags{
-			ModuleName:          m.Name(),
+		props := &SnapshotJsonFlags{
 			ModuleStemName:      moduleStem,
 			Filename:            path.String(),
 			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
@@ -229,6 +231,8 @@
 			RustProcMacro:       procMacro,
 			CrateName:           crateName,
 		}
+		props.InitBaseSnapshotProps(m)
+		return props
 	}
 	return nil
 }
diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go
index 206ecc9..c95a537 100644
--- a/snapshot/snapshot.go
+++ b/snapshot/snapshot.go
@@ -26,6 +26,10 @@
 
 var pctx = android.NewPackageContext("android/soong/snapshot")
 
+func init() {
+	pctx.Import("android/soong/android")
+}
+
 type SnapshotSingleton struct {
 	// Name, e.g., "vendor", "recovery", "ramdisk".
 	name string
@@ -48,8 +52,18 @@
 	Fake bool
 }
 
+// The output files to be included in the snapshot.
+type SnapshotPaths struct {
+	// All files to be included in the snapshot
+	OutputFiles android.Paths
+
+	// Notice files of the snapshot output files
+	NoticeFiles android.Paths
+}
+
 // Interface of function to capture snapshot from each module
-type GenerateSnapshotAction func(snapshot SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths
+// Returns snapshot ouputs and notice files.
+type GenerateSnapshotAction func(snapshot SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) SnapshotPaths
 
 var snapshotActionList []GenerateSnapshotAction
 
@@ -74,9 +88,19 @@
 		snapshotDir = filepath.Join("fake", snapshotDir)
 	}
 	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
+	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
+	installedNotices := make(map[string]bool)
 
 	for _, f := range snapshotActionList {
-		snapshotOutputs = append(snapshotOutputs, f(*c, ctx, snapshotArchDir)...)
+		snapshotPaths := f(*c, ctx, snapshotArchDir)
+		snapshotOutputs = append(snapshotOutputs, snapshotPaths.OutputFiles...)
+		for _, notice := range snapshotPaths.NoticeFiles {
+			if _, ok := installedNotices[notice.String()]; !ok {
+				installedNotices[notice.String()] = true
+				snapshotOutputs = append(snapshotOutputs, CopyFileRule(
+					pctx, ctx, notice, filepath.Join(noticeDir, notice.String())))
+			}
+		}
 	}
 
 	// All artifacts are ready. Sort them to normalize ninja and then zip.
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index 809ca3d..fb4ee0c 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -120,4 +120,19 @@
 	// dependencies
 	Required  []string `json:",omitempty"`
 	Overrides []string `json:",omitempty"`
+
+	// license information
+	LicenseKinds []string `json:",omitempty"`
+	LicenseTexts []string `json:",omitempty"`
+}
+
+func (prop *SnapshotJsonFlags) InitBaseSnapshotPropsWithName(m android.Module, name string) {
+	prop.ModuleName = name
+
+	prop.LicenseKinds = m.EffectiveLicenseKinds()
+	prop.LicenseTexts = m.EffectiveLicenseFiles().Strings()
+}
+
+func (prop *SnapshotJsonFlags) InitBaseSnapshotProps(m android.Module) {
+	prop.InitBaseSnapshotPropsWithName(m, m.Name())
 }
diff --git a/snapshot/util.go b/snapshot/util.go
index 806ac90..c87c508 100644
--- a/snapshot/util.go
+++ b/snapshot/util.go
@@ -21,17 +21,25 @@
 	return outPath
 }
 
-func CopyFileRule(pctx android.PackageContext, ctx android.SingletonContext, path android.Path, out string) android.OutputPath {
-	outPath := android.PathForOutput(ctx, out)
+type buildContext interface {
+	Build(pctx android.PackageContext, params android.BuildParams)
+}
+
+func CopyFileToOutputPathRule(pctx android.PackageContext, ctx buildContext, path android.Path, outPath android.OutputPath) {
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        android.Cp,
 		Input:       path,
 		Output:      outPath,
-		Description: "copy " + path.String() + " -> " + out,
+		Description: "copy " + path.String() + " -> " + outPath.String(),
 		Args: map[string]string{
-			"cpFlags": "-f -L",
+			"cpFlags": "-L",
 		},
 	})
+}
+
+func CopyFileRule(pctx android.PackageContext, ctx android.SingletonContext, path android.Path, out string) android.OutputPath {
+	outPath := android.PathForOutput(ctx, out)
+	CopyFileToOutputPathRule(pctx, ctx, path, outPath)
 	return outPath
 }