Merge "prebuilt_etc: Improve comments and slight refactoring"
diff --git a/android/Android.bp b/android/Android.bp
index 7bd1450..503462e 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -32,11 +32,13 @@
         "mutator.go",
         "namespace.go",
         "neverallow.go",
+        "ninja_deps.go",
         "notices.go",
         "onceper.go",
         "override_module.go",
         "package.go",
         "package_ctx.go",
+        "packaging.go",
         "path_properties.go",
         "paths.go",
         "phony.go",
@@ -73,6 +75,7 @@
         "mutator_test.go",
         "namespace_test.go",
         "neverallow_test.go",
+        "ninja_deps_test.go",
         "onceper_test.go",
         "package_test.go",
         "path_properties_test.go",
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index a558f45..10527b9 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -80,10 +80,10 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.inMake = true // Enable androidmk Singleton
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
 	ctx.RegisterModuleType("custom", customModuleFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -252,10 +252,10 @@
 		config := TestConfig(buildDir, nil, testCase.bp, nil)
 		config.inMake = true // Enable androidmk Singleton
 
-		ctx := NewTestContext()
+		ctx := NewTestContext(config)
 		ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
 		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register(config)
+		ctx.Register()
 
 		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 		FailIfErrored(t, errs)
diff --git a/android/apex.go b/android/apex.go
index e70ec4f..276f7a4 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -598,36 +598,22 @@
 	var fullContent strings.Builder
 	var flatContent strings.Builder
 
-	fmt.Fprintf(&fullContent, "%s(minSdkVersion:%s):\\n", ctx.ModuleName(), minSdkVersion)
+	fmt.Fprintf(&fullContent, "%s(minSdkVersion:%s):\n", ctx.ModuleName(), minSdkVersion)
 	for _, key := range FirstUniqueStrings(SortedStringKeys(depInfos)) {
 		info := depInfos[key]
 		toName := fmt.Sprintf("%s(minSdkVersion:%s)", info.To, info.MinSdkVersion)
 		if info.IsExternal {
 			toName = toName + " (external)"
 		}
-		fmt.Fprintf(&fullContent, "  %s <- %s\\n", toName, strings.Join(SortedUniqueStrings(info.From), ", "))
-		fmt.Fprintf(&flatContent, "%s\\n", toName)
+		fmt.Fprintf(&fullContent, "  %s <- %s\n", toName, strings.Join(SortedUniqueStrings(info.From), ", "))
+		fmt.Fprintf(&flatContent, "%s\n", toName)
 	}
 
 	d.fullListPath = PathForModuleOut(ctx, "depsinfo", "fulllist.txt").OutputPath
-	ctx.Build(pctx, BuildParams{
-		Rule:        WriteFile,
-		Description: "Full Dependency Info",
-		Output:      d.fullListPath,
-		Args: map[string]string{
-			"content": fullContent.String(),
-		},
-	})
+	WriteFileRule(ctx, d.fullListPath, fullContent.String())
 
 	d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
-	ctx.Build(pctx, BuildParams{
-		Rule:        WriteFile,
-		Description: "Flat Dependency Info",
-		Output:      d.flatListPath,
-		Args: map[string]string{
-			"content": flatContent.String(),
-		},
-	})
+	WriteFileRule(ctx, d.flatListPath, flatContent.String())
 }
 
 // TODO(b/158059172): remove minSdkVersion allowlist
diff --git a/android/api_levels.go b/android/api_levels.go
index bace3d4..08328e1 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -225,14 +225,7 @@
 		ctx.Errorf(err.Error())
 	}
 
-	ctx.Build(pctx, BuildParams{
-		Rule:        WriteFile,
-		Description: "generate " + file.Base(),
-		Output:      file,
-		Args: map[string]string{
-			"content": string(jsonStr[:]),
-		},
-	})
+	WriteFileRule(ctx, file, string(jsonStr))
 }
 
 func GetApiLevelsJson(ctx PathContext) WritablePath {
diff --git a/android/arch.go b/android/arch.go
index 98ff07a..afb9c7f 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -757,7 +757,7 @@
 
 	for _, os := range OsTypeList {
 		for _, t := range mctx.Config().Targets[os] {
-			if base.supportsTarget(t, mctx.Config()) {
+			if base.supportsTarget(t) {
 				moduleOSList = append(moduleOSList, os)
 				break
 			}
diff --git a/android/arch_test.go b/android/arch_test.go
index 8525b03..4cef4c8 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -353,9 +353,9 @@
 		t.Run(tt.name, func(t *testing.T) {
 			config := TestArchConfig(buildDir, nil, bp, nil)
 
-			ctx := NewTestArchContext()
+			ctx := NewTestArchContext(config)
 			ctx.RegisterModuleType("module", archTestModuleFactory)
-			ctx.Register(config)
+			ctx.Register()
 			if tt.config != nil {
 				tt.config(config)
 			}
@@ -442,9 +442,9 @@
 		t.Run(tt.name, func(t *testing.T) {
 			config := TestArchConfigNativeBridge(buildDir, nil, bp, nil)
 
-			ctx := NewTestArchContext()
+			ctx := NewTestArchContext(config)
 			ctx.RegisterModuleType("module", archTestModuleFactory)
-			ctx.Register(config)
+			ctx.Register()
 			if tt.config != nil {
 				tt.config(config)
 			}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index c87a945..7d8d12f 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -192,9 +192,9 @@
 func (context *bazelContext) issueBazelCommand(command string, labels []string,
 	extraFlags ...string) (string, error) {
 
-	cmdFlags := []string{"--bazelrc=build/bazel/common.bazelrc",
-		"--output_base=" + context.outputBase, command}
+	cmdFlags := []string{"--output_base=" + context.outputBase, command}
 	cmdFlags = append(cmdFlags, labels...)
+	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+context.intermediatesDir())
 	cmdFlags = append(cmdFlags, extraFlags...)
 
 	bazelCmd := exec.Command(context.bazelPath, cmdFlags...)
@@ -211,6 +211,21 @@
 	}
 }
 
+// Returns the string contents of a workspace file that should be output
+// adjacent to the main bzl file and build file.
+// This workspace file allows, via local_repository rule, sourcetree-level
+// BUILD targets to be referenced via @sourceroot.
+func (context *bazelContext) workspaceFileContents() []byte {
+	formatString := `
+# This file is generated by soong_build. Do not edit.
+local_repository(
+    name = "sourceroot",
+    path = "%s",
+)
+`
+	return []byte(fmt.Sprintf(formatString, context.workspaceDir))
+}
+
 func (context *bazelContext) mainBzlFileContents() []byte {
 	contents := `
 # This file is generated by soong_build. Do not edit.
@@ -225,6 +240,18 @@
 	return []byte(contents)
 }
 
+// Returns a "canonicalized" corresponding to the given sourcetree-level label.
+// This abstraction is required because a sourcetree label such as //foo/bar:baz
+// must be referenced via the local repository prefix, such as
+// @sourceroot//foo/bar:baz.
+func canonicalizeLabel(label string) string {
+	if strings.HasPrefix(label, "//") {
+		return "@sourceroot" + label
+	} else {
+		return "@sourceroot//" + label
+	}
+}
+
 func (context *bazelContext) mainBuildFileContents() []byte {
 	formatString := `
 # This file is generated by soong_build. Do not edit.
@@ -236,7 +263,7 @@
 `
 	var buildRootDeps []string = nil
 	for val, _ := range context.requests {
-		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\"", val.label))
+		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label)))
 	}
 	buildRootDepsString := strings.Join(buildRootDeps, ",\n            ")
 
@@ -262,13 +289,19 @@
 	// TODO(cparsons): Sort by request type instead of assuming all requests
 	// are of GetAllFiles type.
 	for val, _ := range context.requests {
-		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\" : True", val.label))
+		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\" : True", canonicalizeLabel(val.label)))
 	}
 	buildRootDepsString := strings.Join(buildRootDeps, ",\n  ")
 
 	return []byte(fmt.Sprintf(formatString, buildRootDepsString))
 }
 
+// Returns a workspace-relative path containing build-related metadata required
+// for interfacing with Bazel. Example: out/soong/bazel.
+func (context *bazelContext) intermediatesDir() string {
+	return filepath.Join(context.buildDir, "bazel")
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *bazelContext) InvokeBazel() error {
@@ -276,26 +309,38 @@
 
 	var cqueryOutput string
 	var err error
+
+	err = os.Mkdir(absolutePath(context.intermediatesDir()), 0777)
+	if err != nil {
+		return err
+	}
 	err = ioutil.WriteFile(
-		absolutePath(filepath.Join(context.buildDir, "main.bzl")),
+		absolutePath(filepath.Join(context.intermediatesDir(), "main.bzl")),
 		context.mainBzlFileContents(), 0666)
 	if err != nil {
 		return err
 	}
 	err = ioutil.WriteFile(
-		absolutePath(filepath.Join(context.buildDir, "BUILD.bazel")),
+		absolutePath(filepath.Join(context.intermediatesDir(), "BUILD.bazel")),
 		context.mainBuildFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	cquery_file_relpath := filepath.Join(context.buildDir, "buildroot.cquery")
+	cquery_file_relpath := filepath.Join(context.intermediatesDir(), "buildroot.cquery")
 	err = ioutil.WriteFile(
 		absolutePath(cquery_file_relpath),
 		context.cqueryStarlarkFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	buildroot_label := fmt.Sprintf("//%s:buildroot", context.buildDir)
+	workspace_file_relpath := filepath.Join(context.intermediatesDir(), "WORKSPACE.bazel")
+	err = ioutil.WriteFile(
+		absolutePath(workspace_file_relpath),
+		context.workspaceFileContents(), 0666)
+	if err != nil {
+		return err
+	}
+	buildroot_label := "//:buildroot"
 	cqueryOutput, err = context.issueBazelCommand("cquery",
 		[]string{fmt.Sprintf("deps(%s)", buildroot_label)},
 		"--output=starlark",
@@ -314,7 +359,7 @@
 	}
 
 	for val, _ := range context.requests {
-		if cqueryResult, ok := cqueryResults[val.label]; ok {
+		if cqueryResult, ok := cqueryResults[canonicalizeLabel(val.label)]; ok {
 			context.results[val] = string(cqueryResult)
 		} else {
 			return fmt.Errorf("missing result for bazel target %s", val.label)
diff --git a/android/config.go b/android/config.go
index e87a4ac..d833c5c 100644
--- a/android/config.go
+++ b/android/config.go
@@ -126,6 +126,8 @@
 	// in tests when a path doesn't exist.
 	testAllowNonExistentPaths bool
 
+	ninjaFileDepsSet sync.Map
+
 	OncePer
 }
 
diff --git a/android/csuite_config_test.go b/android/csuite_config_test.go
index bf1a19a..9ac959e 100644
--- a/android/csuite_config_test.go
+++ b/android/csuite_config_test.go
@@ -21,9 +21,9 @@
 func testCSuiteConfig(test *testing.T, bpFileContents string) *TestContext {
 	config := TestArchConfig(buildDir, nil, bpFileContents, nil)
 
-	ctx := NewTestArchContext()
+	ctx := NewTestArchContext(config)
 	ctx.RegisterModuleType("csuite_config", CSuiteConfigFactory)
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(test, errs)
 	_, errs = ctx.PrepareBuildActions(config)
diff --git a/android/defaults_test.go b/android/defaults_test.go
index d096b2f..2689d86 100644
--- a/android/defaults_test.go
+++ b/android/defaults_test.go
@@ -80,14 +80,14 @@
 
 	config := TestConfig(buildDir, nil, bp, nil)
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 
 	ctx.RegisterModuleType("test", defaultsTestModuleFactory)
 	ctx.RegisterModuleType("defaults", defaultsTestDefaultsFactory)
 
 	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -125,7 +125,7 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.SetAllowMissingDependencies(true)
 
 	ctx.RegisterModuleType("test", defaultsTestModuleFactory)
@@ -133,7 +133,7 @@
 
 	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
diff --git a/android/defs.go b/android/defs.go
index 2b1bd85..631dfe8 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -15,8 +15,12 @@
 package android
 
 import (
+	"strings"
+	"testing"
+
 	"github.com/google/blueprint"
 	_ "github.com/google/blueprint/bootstrap"
+	"github.com/google/blueprint/proptools"
 )
 
 var (
@@ -91,9 +95,9 @@
 	// ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command
 	// doesn't support -e option. Therefore we force to use /bin/bash when writing out
 	// content to file.
-	WriteFile = pctx.AndroidStaticRule("WriteFile",
+	writeFile = pctx.AndroidStaticRule("writeFile",
 		blueprint.RuleParams{
-			Command:     "/bin/bash -c 'echo -e $$0 > $out' '$content'",
+			Command:     `/bin/bash -c 'echo -e "$$0" > $out' $content`,
 			Description: "writing file $out",
 		},
 		"content")
@@ -111,3 +115,64 @@
 func init() {
 	pctx.Import("github.com/google/blueprint/bootstrap")
 }
+
+var (
+	// echoEscaper escapes a string such that passing it to "echo -e" will produce the input value.
+	echoEscaper = strings.NewReplacer(
+		`\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`.
+		"\n", `\n`, // Then replace newlines with \n
+	)
+
+	// echoEscaper reverses echoEscaper.
+	echoUnescaper = strings.NewReplacer(
+		`\n`, "\n",
+		`\\`, `\`,
+	)
+
+	// shellUnescaper reverses the replacer in proptools.ShellEscape
+	shellUnescaper = strings.NewReplacer(`'\''`, `'`)
+)
+
+// WriteFileRule creates a ninja rule to write contents to a file.  The contents will be escaped
+// so that the file contains exactly the contents passed to the function, plus a trailing newline.
+func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
+	content = echoEscaper.Replace(content)
+	content = proptools.ShellEscape(content)
+	if content == "" {
+		content = "''"
+	}
+	ctx.Build(pctx, BuildParams{
+		Rule:        writeFile,
+		Output:      outputFile,
+		Description: "write " + outputFile.Base(),
+		Args: map[string]string{
+			"content": content,
+		},
+	})
+}
+
+// shellUnescape reverses proptools.ShellEscape
+func shellUnescape(s string) string {
+	// Remove leading and trailing quotes if present
+	if len(s) >= 2 && s[0] == '\'' {
+		s = s[1 : len(s)-1]
+	}
+	s = shellUnescaper.Replace(s)
+	return s
+}
+
+// ContentFromFileRuleForTests returns the content that was passed to a WriteFileRule for use
+// in tests.
+func ContentFromFileRuleForTests(t *testing.T, params TestingBuildParams) string {
+	t.Helper()
+	if g, w := params.Rule, writeFile; g != w {
+		t.Errorf("expected params.Rule to be %q, was %q", w, g)
+		return ""
+	}
+
+	content := params.Args["content"]
+	content = shellUnescape(content)
+	content = echoUnescaper.Replace(content)
+
+	return content
+}
diff --git a/android/makevars.go b/android/makevars.go
index 3ca7792..f784395 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -139,15 +139,24 @@
 	MakeVars(ctx MakeVarsContext)
 }
 
-// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to the list of
-// MakeVarsProviders to run.
-func registerSingletonMakeVarsProvider(singleton SingletonMakeVarsProvider) {
-	singletonMakeVarsProviders = append(singletonMakeVarsProviders,
-		makeVarsProvider{pctx, SingletonmakeVarsProviderAdapter(singleton)})
+var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
+
+// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
+// the list of MakeVarsProviders to run.
+func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
+	// Singletons are registered on the Context and may be different between different Contexts,
+	// for example when running multiple tests.  Store the SingletonMakeVarsProviders in the
+	// Config so they are attached to the Context.
+	singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} {
+		return &[]makeVarsProvider{}
+	}).(*[]makeVarsProvider)
+
+	*singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
+		makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
 }
 
-// SingletonmakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
-func SingletonmakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
+// singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
+func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
 	return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
 }
 
@@ -175,9 +184,6 @@
 // Collection of makevars providers that are registered in init() methods.
 var makeVarsInitProviders []makeVarsProvider
 
-// Collection of singleton makevars providers that are not registered as part of init() methods.
-var singletonMakeVarsProviders []makeVarsProvider
-
 type makeVarsContext struct {
 	SingletonContext
 	config  Config
@@ -224,7 +230,11 @@
 	var vars []makeVarsVariable
 	var dists []dist
 	var phonies []phony
-	for _, provider := range append(makeVarsInitProviders) {
+
+	providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
+	providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
+
+	for _, provider := range providers {
 		mctx := &makeVarsContext{
 			SingletonContext: ctx,
 			pctx:             provider.pctx,
@@ -237,25 +247,6 @@
 		dists = append(dists, mctx.dists...)
 	}
 
-	for _, provider := range append(singletonMakeVarsProviders) {
-		mctx := &makeVarsContext{
-			SingletonContext: ctx,
-			pctx:             provider.pctx,
-		}
-
-		provider.call(mctx)
-
-		vars = append(vars, mctx.vars...)
-		phonies = append(phonies, mctx.phonies...)
-		dists = append(dists, mctx.dists...)
-	}
-
-	// Clear singleton makevars providers after use. Since these are in-memory
-	// singletons, this ensures state is reset if the build tree is processed
-	// multiple times.
-	// TODO(cparsons): Clean up makeVarsProviders to be part of the context.
-	singletonMakeVarsProviders = nil
-
 	ctx.VisitAllModules(func(m Module) {
 		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
 			mctx := &makeVarsContext{
diff --git a/android/module.go b/android/module.go
index d677406..eeb4fae 100644
--- a/android/module.go
+++ b/android/module.go
@@ -440,6 +440,7 @@
 	TargetRequiredModuleNames() []string
 
 	FilesToInstall() InstallPaths
+	PackagingSpecs() []PackagingSpec
 }
 
 // Qualified id for a module
@@ -765,27 +766,32 @@
 type HostOrDeviceSupported int
 
 const (
-	_ HostOrDeviceSupported = iota
+	hostSupported = 1 << iota
+	hostCrossSupported
+	deviceSupported
+	hostDefault
+	deviceDefault
 
 	// Host and HostCross are built by default. Device is not supported.
-	HostSupported
+	HostSupported = hostSupported | hostCrossSupported | hostDefault
 
 	// Host is built by default. HostCross and Device are not supported.
-	HostSupportedNoCross
+	HostSupportedNoCross = hostSupported | hostDefault
 
 	// Device is built by default. Host and HostCross are not supported.
-	DeviceSupported
+	DeviceSupported = deviceSupported | deviceDefault
 
 	// Device is built by default. Host and HostCross are supported.
-	HostAndDeviceSupported
+	HostAndDeviceSupported = hostSupported | hostCrossSupported | deviceSupported | deviceDefault
 
 	// Host, HostCross, and Device are built by default.
-	HostAndDeviceDefault
+	HostAndDeviceDefault = hostSupported | hostCrossSupported | hostDefault |
+		deviceSupported | deviceDefault
 
 	// Nothing is supported. This is not exposed to the user, but used to mark a
 	// host only module as unsupported when the module type is not supported on
 	// the host OS. E.g. benchmarks are supported on Linux but not Darwin.
-	NeitherHostNorDeviceSupported
+	NeitherHostNorDeviceSupported = 0
 )
 
 type moduleKind int
@@ -847,8 +853,7 @@
 	base.commonProperties.ArchSpecific = true
 	base.commonProperties.UseTargetVariants = true
 
-	switch hod {
-	case HostAndDeviceSupported, HostAndDeviceDefault:
+	if hod&hostSupported != 0 && hod&deviceSupported != 0 {
 		m.AddProperties(&base.hostAndDeviceProperties)
 	}
 
@@ -934,6 +939,7 @@
 	noAddressSanitizer bool
 	installFiles       InstallPaths
 	checkbuildFiles    Paths
+	packagingSpecs     []PackagingSpec
 	noticeFiles        Paths
 	phonies            map[string]Paths
 
@@ -1098,43 +1104,54 @@
 	return m.commonProperties.CommonOSVariant
 }
 
-func (m *ModuleBase) supportsTarget(target Target, config Config) bool {
-	switch m.commonProperties.HostOrDeviceSupported {
-	case HostSupported:
-		return target.Os.Class == Host
-	case HostSupportedNoCross:
-		return target.Os.Class == Host && !target.HostCross
-	case DeviceSupported:
-		return target.Os.Class == Device
-	case HostAndDeviceSupported, HostAndDeviceDefault:
-		supported := false
-		if Bool(m.hostAndDeviceProperties.Host_supported) ||
-			(m.commonProperties.HostOrDeviceSupported == HostAndDeviceDefault &&
-				m.hostAndDeviceProperties.Host_supported == nil) {
-			supported = supported || target.Os.Class == Host
+// supportsTarget returns true if the given Target is supported by the current module.
+func (m *ModuleBase) supportsTarget(target Target) bool {
+	switch target.Os.Class {
+	case Host:
+		if target.HostCross {
+			return m.HostCrossSupported()
+		} else {
+			return m.HostSupported()
 		}
-		if m.hostAndDeviceProperties.Device_supported == nil ||
-			*m.hostAndDeviceProperties.Device_supported {
-			supported = supported || target.Os.Class == Device
-		}
-		return supported
+	case Device:
+		return m.DeviceSupported()
 	default:
 		return false
 	}
 }
 
+// DeviceSupported returns true if the current module is supported and enabled for device targets,
+// i.e. the factory method set the HostOrDeviceSupported value to include device support and
+// the device support is enabled by default or enabled by the device_supported property.
 func (m *ModuleBase) DeviceSupported() bool {
-	return m.commonProperties.HostOrDeviceSupported == DeviceSupported ||
-		m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported &&
-			(m.hostAndDeviceProperties.Device_supported == nil ||
-				*m.hostAndDeviceProperties.Device_supported)
+	hod := m.commonProperties.HostOrDeviceSupported
+	// deviceEnabled is true if the device_supported property is true or the HostOrDeviceSupported
+	// value has the deviceDefault bit set.
+	deviceEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Device_supported, hod&deviceDefault != 0)
+	return hod&deviceSupported != 0 && deviceEnabled
 }
 
+// HostSupported returns true if the current module is supported and enabled for host targets,
+// i.e. the factory method set the HostOrDeviceSupported value to include host support and
+// the host support is enabled by default or enabled by the host_supported property.
 func (m *ModuleBase) HostSupported() bool {
-	return m.commonProperties.HostOrDeviceSupported == HostSupported ||
-		m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported &&
-			(m.hostAndDeviceProperties.Host_supported != nil &&
-				*m.hostAndDeviceProperties.Host_supported)
+	hod := m.commonProperties.HostOrDeviceSupported
+	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
+	// value has the hostDefault bit set.
+	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
+	return hod&hostSupported != 0 && hostEnabled
+}
+
+// HostCrossSupported returns true if the current module is supported and enabled for host cross
+// targets, i.e. the factory method set the HostOrDeviceSupported value to include host cross
+// support and the host cross support is enabled by default or enabled by the
+// host_supported property.
+func (m *ModuleBase) HostCrossSupported() bool {
+	hod := m.commonProperties.HostOrDeviceSupported
+	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
+	// value has the hostDefault bit set.
+	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
+	return hod&hostCrossSupported != 0 && hostEnabled
 }
 
 func (m *ModuleBase) Platform() bool {
@@ -1259,6 +1276,10 @@
 	return m.installFiles
 }
 
+func (m *ModuleBase) PackagingSpecs() []PackagingSpec {
+	return m.packagingSpecs
+}
+
 func (m *ModuleBase) NoAddressSanitizer() bool {
 	return m.noAddressSanitizer
 }
@@ -1581,6 +1602,7 @@
 
 		m.installFiles = append(m.installFiles, ctx.installFiles...)
 		m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
+		m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
 		m.initRcPaths = PathsForModuleSrc(ctx, m.commonProperties.Init_rc)
 		m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments)
 		for k, v := range ctx.phonies {
@@ -1748,6 +1770,7 @@
 type moduleContext struct {
 	bp blueprint.ModuleContext
 	baseModuleContext
+	packagingSpecs  []PackagingSpec
 	installDeps     InstallPaths
 	installFiles    InstallPaths
 	checkbuildFiles Paths
@@ -2284,16 +2307,15 @@
 
 func (m *moduleContext) InstallFile(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, Cp, deps)
+	return m.installFile(installPath, name, srcPath, deps, false)
 }
 
 func (m *moduleContext) InstallExecutable(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, CpExecutable, deps)
+	return m.installFile(installPath, name, srcPath, deps, true)
 }
 
-func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path,
-	rule blueprint.Rule, deps []Path) InstallPath {
+func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path, executable bool) InstallPath {
 
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
@@ -2312,6 +2334,11 @@
 			orderOnlyDeps = deps
 		}
 
+		rule := Cp
+		if executable {
+			rule = CpExecutable
+		}
+
 		m.Build(pctx, BuildParams{
 			Rule:        rule,
 			Description: "install " + fullInstallPath.Base(),
@@ -2324,6 +2351,14 @@
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          srcPath,
+		symlinkTarget:    "",
+		executable:       executable,
+	})
+
 	m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 	return fullInstallPath
 }
@@ -2332,12 +2367,12 @@
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, true)
 
+	relPath, err := filepath.Rel(path.Dir(fullInstallPath.String()), srcPath.String())
+	if err != nil {
+		panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err))
+	}
 	if !m.skipInstall(fullInstallPath) {
 
-		relPath, err := filepath.Rel(path.Dir(fullInstallPath.String()), srcPath.String())
-		if err != nil {
-			panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err))
-		}
 		m.Build(pctx, BuildParams{
 			Rule:        Symlink,
 			Description: "install symlink " + fullInstallPath.Base(),
@@ -2352,6 +2387,14 @@
 		m.installFiles = append(m.installFiles, fullInstallPath)
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          nil,
+		symlinkTarget:    relPath,
+		executable:       false,
+	})
+
 	return fullInstallPath
 }
 
@@ -2374,6 +2417,14 @@
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          nil,
+		symlinkTarget:    absPath,
+		executable:       false,
+	})
+
 	return fullInstallPath
 }
 
diff --git a/android/module_test.go b/android/module_test.go
index 3a039e2..6cc1813 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -164,9 +164,6 @@
 }
 
 func TestErrorDependsOnDisabledModule(t *testing.T) {
-	ctx := NewTestContext()
-	ctx.RegisterModuleType("deps", depsModuleFactory)
-
 	bp := `
 		deps {
 			name: "foo",
@@ -180,7 +177,9 @@
 
 	config := TestConfig(buildDir, nil, bp, nil)
 
-	ctx.Register(config)
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("deps", depsModuleFactory)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
diff --git a/android/mutator_test.go b/android/mutator_test.go
index 191b535..1c395c7 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -70,7 +70,7 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.SetAllowMissingDependencies(true)
 
 	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
@@ -78,7 +78,7 @@
 		ctx.TopDown("add_missing_dependencies", addMissingDependenciesMutator)
 	})
 
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
@@ -92,7 +92,15 @@
 }
 
 func TestModuleString(t *testing.T) {
-	ctx := NewTestContext()
+	bp := `
+		test {
+			name: "foo",
+		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestContext(config)
 
 	var moduleStrings []string
 
@@ -130,15 +138,7 @@
 
 	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
 
-	bp := `
-		test {
-			name: "foo",
-		}
-	`
-
-	config := TestConfig(buildDir, nil, bp, nil)
-
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -190,7 +190,21 @@
 }
 
 func TestFinalDepsPhase(t *testing.T) {
-	ctx := NewTestContext()
+	bp := `
+		test {
+			name: "common_dep_1",
+		}
+		test {
+			name: "common_dep_2",
+		}
+		test {
+			name: "foo",
+		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestContext(config)
 
 	finalGot := map[string]int{}
 
@@ -228,20 +242,7 @@
 
 	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
 
-	bp := `
-		test {
-			name: "common_dep_1",
-		}
-		test {
-			name: "common_dep_2",
-		}
-		test {
-			name: "foo",
-		}
-	`
-
-	config := TestConfig(buildDir, nil, bp, nil)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -267,7 +268,8 @@
 }
 
 func TestNoCreateVariationsInFinalDeps(t *testing.T) {
-	ctx := NewTestContext()
+	config := TestConfig(buildDir, nil, `test {name: "foo"}`, nil)
+	ctx := NewTestContext(config)
 
 	checkErr := func() {
 		if err := recover(); err == nil || !strings.Contains(fmt.Sprintf("%s", err), "not allowed in FinalDepsMutators") {
@@ -287,8 +289,7 @@
 	})
 
 	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
-	config := TestConfig(buildDir, nil, `test {name: "foo"}`, nil)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 66c0d89..45e2cdb 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -635,7 +635,7 @@
 func setupTestFromFiles(bps map[string][]byte) (ctx *TestContext, errs []error) {
 	config := TestConfig(buildDir, nil, "", bps)
 
-	ctx = NewTestContext()
+	ctx = NewTestContext(config)
 	ctx.RegisterModuleType("test_module", newTestModule)
 	ctx.RegisterModuleType("soong_namespace", NamespaceFactory)
 	ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
@@ -643,7 +643,7 @@
 	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
 		ctx.BottomUp("rename", renameMutator)
 	})
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs = ctx.ParseBlueprintsFiles("Android.bp")
 	if len(errs) > 0 {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 56a07dc..1d454e5 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -359,14 +359,14 @@
 }
 
 func testNeverallow(config Config) (*TestContext, []error) {
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.RegisterModuleType("cc_library", newMockCcLibraryModule)
 	ctx.RegisterModuleType("java_library", newMockJavaLibraryModule)
 	ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
 	ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
 	ctx.RegisterModuleType("makefile_goal", newMockMakefileGoalModule)
 	ctx.PostDepsMutators(RegisterNeverallowMutator)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	if len(errs) > 0 {
diff --git a/android/ninja_deps.go b/android/ninja_deps.go
new file mode 100644
index 0000000..2f442d5
--- /dev/null
+++ b/android/ninja_deps.go
@@ -0,0 +1,43 @@
+// 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 "sort"
+
+func (c *config) addNinjaFileDeps(deps ...string) {
+	for _, dep := range deps {
+		c.ninjaFileDepsSet.Store(dep, true)
+	}
+}
+
+func (c *config) ninjaFileDeps() []string {
+	var deps []string
+	c.ninjaFileDepsSet.Range(func(key, value interface{}) bool {
+		deps = append(deps, key.(string))
+		return true
+	})
+	sort.Strings(deps)
+	return deps
+}
+
+func ninjaDepsSingletonFactory() Singleton {
+	return &ninjaDepsSingleton{}
+}
+
+type ninjaDepsSingleton struct{}
+
+func (ninjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	ctx.AddNinjaFileDeps(ctx.Config().ninjaFileDeps()...)
+}
diff --git a/android/ninja_deps_test.go b/android/ninja_deps_test.go
new file mode 100644
index 0000000..d3775ed
--- /dev/null
+++ b/android/ninja_deps_test.go
@@ -0,0 +1,75 @@
+// 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 (
+	"testing"
+)
+
+func init() {
+	// This variable uses ExistentPathForSource on a PackageVarContext, which is a PathContext
+	// that is not a PathGlobContext.  That requires the deps to be stored in the Config.
+	pctx.VariableFunc("test_ninja_deps_variable", func(ctx PackageVarContext) string {
+		// Using ExistentPathForSource to look for a file that does not exist in a directory that
+		// does exist (test_ninja_deps) from a PackageVarContext adds a dependency from build.ninja
+		// to the directory.
+		if ExistentPathForSource(ctx, "test_ninja_deps/does_not_exist").Valid() {
+			return "true"
+		} else {
+			return "false"
+		}
+	})
+}
+
+func testNinjaDepsSingletonFactory() Singleton {
+	return testNinjaDepsSingleton{}
+}
+
+type testNinjaDepsSingleton struct{}
+
+func (testNinjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	// Reference the test_ninja_deps_variable in a build statement so Blueprint is forced to
+	// evaluate it.
+	ctx.Build(pctx, BuildParams{
+		Rule:   Cp,
+		Input:  PathForTesting("foo"),
+		Output: PathForOutput(ctx, "test_ninja_deps_out"),
+		Args: map[string]string{
+			"cpFlags": "${test_ninja_deps_variable}",
+		},
+	})
+}
+
+func TestNinjaDeps(t *testing.T) {
+	fs := map[string][]byte{
+		"test_ninja_deps/exists": nil,
+	}
+	config := TestConfig(buildDir, nil, "", fs)
+
+	ctx := NewTestContext(config)
+	ctx.RegisterSingletonType("test_ninja_deps_singleton", testNinjaDepsSingletonFactory)
+	ctx.RegisterSingletonType("ninja_deps_singleton", ninjaDepsSingletonFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	ninjaDeps, errs := ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	// Verify that the ninja file has a dependency on the test_ninja_deps directory.
+	if g, w := ninjaDeps, "test_ninja_deps"; !InList(w, g) {
+		t.Errorf("expected %q in %q", w, g)
+	}
+}
diff --git a/android/package_ctx.go b/android/package_ctx.go
index 0de356e..6d0fcb3 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -56,7 +56,7 @@
 	e.errors = append(e.errors, fmt.Errorf(format, args...))
 }
 func (e *configErrorWrapper) AddNinjaFileDeps(deps ...string) {
-	e.pctx.AddNinjaFileDeps(deps...)
+	e.config.addNinjaFileDeps(deps...)
 }
 
 type PackageVarContext interface {
diff --git a/android/package_test.go b/android/package_test.go
index 04dfc08..ade95d4 100644
--- a/android/package_test.go
+++ b/android/package_test.go
@@ -86,9 +86,9 @@
 	// Create a new config per test as visibility information is stored in the config.
 	config := TestArchConfig(buildDir, nil, "", fs)
 
-	ctx := NewTestArchContext()
+	ctx := NewTestArchContext(config)
 	RegisterPackageBuildComponents(ctx)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseBlueprintsFiles(".")
 	if len(errs) > 0 {
diff --git a/android/packaging.go b/android/packaging.go
new file mode 100644
index 0000000..8d0de9e
--- /dev/null
+++ b/android/packaging.go
@@ -0,0 +1,34 @@
+// 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
+
+// PackagingSpec abstracts a request to place a built artifact at a certain path in a package.
+// A package can be the traditional <partition>.img, but isn't limited to those. Other examples could
+// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running
+// on a VM), or a zip archive for some of the host tools.
+type PackagingSpec struct {
+	// Path relative to the root of the package
+	relPathInPackage string
+
+	// The path to the built artifact
+	srcPath Path
+
+	// If this is not empty, then relPathInPackage should be a symlink to this target. (Then
+	// srcPath is of course ignored.)
+	symlinkTarget string
+
+	// Whether relPathInPackage should be marked as executable or not
+	executable bool
+}
diff --git a/android/path_properties_test.go b/android/path_properties_test.go
index f367b82..f964d9f 100644
--- a/android/path_properties_test.go
+++ b/android/path_properties_test.go
@@ -116,12 +116,12 @@
 			`
 
 			config := TestArchConfig(buildDir, nil, bp, nil)
-			ctx := NewTestArchContext()
+			ctx := NewTestArchContext(config)
 
 			ctx.RegisterModuleType("test", pathDepsMutatorTestModuleFactory)
 			ctx.RegisterModuleType("filegroup", FileGroupFactory)
 
-			ctx.Register(config)
+			ctx.Register()
 			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 			FailIfErrored(t, errs)
 			_, errs = ctx.PrepareBuildActions(config)
diff --git a/android/paths_test.go b/android/paths_test.go
index e7fd763..14a4773 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -980,12 +980,6 @@
 func testPathForModuleSrc(t *testing.T, buildDir string, tests []pathForModuleSrcTestCase) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx := NewTestContext()
-
-			ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
-			ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
-			ctx.RegisterModuleType("filegroup", FileGroupFactory)
-
 			fgBp := `
 				filegroup {
 					name: "a",
@@ -1015,7 +1009,13 @@
 
 			config := TestConfig(buildDir, nil, "", mockFS)
 
-			ctx.Register(config)
+			ctx := NewTestContext(config)
+
+			ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
+			ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
+			ctx.RegisterModuleType("filegroup", FileGroupFactory)
+
+			ctx.Register()
 			_, errs := ctx.ParseFileList(".", []string{"fg/Android.bp", "foo/Android.bp", "ofp/Android.bp"})
 			FailIfErrored(t, errs)
 			_, errs = ctx.PrepareBuildActions(config)
@@ -1224,12 +1224,12 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.SetAllowMissingDependencies(true)
 
 	ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 854395e..9ac3875 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -288,10 +288,10 @@
 				{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
 			}
 
-			ctx := NewTestArchContext()
+			ctx := NewTestArchContext(config)
 			registerTestPrebuiltBuildComponents(ctx)
 			ctx.RegisterModuleType("filegroup", FileGroupFactory)
-			ctx.Register(config)
+			ctx.Register()
 
 			_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 			FailIfErrored(t, errs)
diff --git a/android/queryview.go b/android/queryview.go
index 7e615b1..970ae01 100644
--- a/android/queryview.go
+++ b/android/queryview.go
@@ -57,7 +57,7 @@
 			),
 			CommandDeps: []string{primaryBuilder.String()},
 			Description: fmt.Sprintf(
-				"[EXPERIMENTAL] Creating the Bazel QueryView workspace with %s at $outDir",
+				"[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir",
 				primaryBuilder.Base()),
 			Deps:    blueprint.DepsGCC,
 			Depfile: "${outDir}/.queryview-depfile.d",
diff --git a/android/register.go b/android/register.go
index ad3df7e..08e47b3 100644
--- a/android/register.go
+++ b/android/register.go
@@ -29,7 +29,7 @@
 
 type singleton struct {
 	name    string
-	factory blueprint.SingletonFactory
+	factory SingletonFactory
 }
 
 var singletons []singleton
@@ -57,11 +57,11 @@
 
 // SingletonFactoryAdaptor wraps a SingletonFactory into a blueprint.SingletonFactory by converting
 // a Singleton into a blueprint.Singleton
-func SingletonFactoryAdaptor(factory SingletonFactory) blueprint.SingletonFactory {
+func SingletonFactoryAdaptor(ctx *Context, factory SingletonFactory) blueprint.SingletonFactory {
 	return func() blueprint.Singleton {
 		singleton := factory()
 		if makevars, ok := singleton.(SingletonMakeVarsProvider); ok {
-			registerSingletonMakeVarsProvider(makevars)
+			registerSingletonMakeVarsProvider(ctx.config, makevars)
 		}
 		return &singletonAdaptor{Singleton: singleton}
 	}
@@ -72,26 +72,27 @@
 }
 
 func RegisterSingletonType(name string, factory SingletonFactory) {
-	singletons = append(singletons, singleton{name, SingletonFactoryAdaptor(factory)})
+	singletons = append(singletons, singleton{name, factory})
 }
 
 func RegisterPreSingletonType(name string, factory SingletonFactory) {
-	preSingletons = append(preSingletons, singleton{name, SingletonFactoryAdaptor(factory)})
+	preSingletons = append(preSingletons, singleton{name, factory})
 }
 
 type Context struct {
 	*blueprint.Context
+	config Config
 }
 
-func NewContext() *Context {
-	ctx := &Context{blueprint.NewContext()}
+func NewContext(config Config) *Context {
+	ctx := &Context{blueprint.NewContext(), config}
 	ctx.SetSrcDir(absSrcDir)
 	return ctx
 }
 
 func (ctx *Context) Register() {
 	for _, t := range preSingletons {
-		ctx.RegisterPreSingletonType(t.name, t.factory)
+		ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
 	}
 
 	for _, t := range moduleTypes {
@@ -99,21 +100,23 @@
 	}
 
 	for _, t := range singletons {
-		ctx.RegisterSingletonType(t.name, t.factory)
+		ctx.RegisterSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
 	}
 
 	registerMutators(ctx.Context, preArch, preDeps, postDeps, finalDeps)
 
-	ctx.RegisterSingletonType("bazeldeps", SingletonFactoryAdaptor(BazelSingleton))
+	ctx.RegisterSingletonType("bazeldeps", SingletonFactoryAdaptor(ctx, BazelSingleton))
 
 	// Register phony just before makevars so it can write out its phony rules as Make rules
-	ctx.RegisterSingletonType("phony", SingletonFactoryAdaptor(phonySingletonFactory))
+	ctx.RegisterSingletonType("phony", SingletonFactoryAdaptor(ctx, phonySingletonFactory))
 
 	// Register makevars after other singletons so they can export values through makevars
-	ctx.RegisterSingletonType("makevars", SingletonFactoryAdaptor(makeVarsSingletonFunc))
+	ctx.RegisterSingletonType("makevars", SingletonFactoryAdaptor(ctx, makeVarsSingletonFunc))
 
-	// Register env last so that it can track all used environment variables
-	ctx.RegisterSingletonType("env", SingletonFactoryAdaptor(EnvSingleton))
+	// Register env and ninjadeps last so that they can track all used environment variables and
+	// Ninja file dependencies stored in the config.
+	ctx.RegisterSingletonType("env", SingletonFactoryAdaptor(ctx, EnvSingleton))
+	ctx.RegisterSingletonType("ninjadeps", SingletonFactoryAdaptor(ctx, ninjaDepsSingletonFactory))
 }
 
 func ModuleTypeFactories() map[string]ModuleFactory {
@@ -157,7 +160,7 @@
 // Extracting the actual registration into a separate RegisterBuildComponents(ctx) function
 // allows it to be used to initialize test context, e.g.
 //
-//   ctx := android.NewTestContext()
+//   ctx := android.NewTestContext(config)
 //   RegisterBuildComponents(ctx)
 var InitRegistrationContext RegistrationContext = &initRegistrationContext{
 	moduleTypes:    make(map[string]ModuleFactory),
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 8dc9d6a..86418b2 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -15,7 +15,9 @@
 package android
 
 import (
+	"crypto/sha256"
 	"fmt"
+	"path/filepath"
 	"sort"
 	"strings"
 
@@ -25,6 +27,8 @@
 	"android/soong/shared"
 )
 
+const sboxOutDir = "__SBOX_OUT_DIR__"
+
 // RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
 // graph.
 type RuleBuilder struct {
@@ -133,8 +137,8 @@
 // race with any call to Build.
 func (r *RuleBuilder) Command() *RuleBuilderCommand {
 	command := &RuleBuilderCommand{
-		sbox:       r.sbox,
-		sboxOutDir: r.sboxOutDir,
+		sbox:   r.sbox,
+		outDir: r.sboxOutDir,
 	}
 	r.commands = append(r.commands, command)
 	return command
@@ -163,7 +167,7 @@
 }
 
 // Inputs returns the list of paths that were passed to the RuleBuilderCommand methods that take
-// input paths, such as RuleBuilderCommand.Input, RuleBuilderComand.Implicit, or
+// input paths, such as RuleBuilderCommand.Input, RuleBuilderCommand.Implicit, or
 // RuleBuilderCommand.FlagWithInput.  Inputs to a command that are also outputs of another command
 // in the same RuleBuilder are filtered out.  The list is sorted and duplicates removed.
 func (r *RuleBuilder) Inputs() Paths {
@@ -362,7 +366,7 @@
 	return commands
 }
 
-// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to
+// NinjaEscapedCommands returns a slice containing the built command line after ninja escaping for each call to
 // RuleBuilder.Command.
 func (r *RuleBuilder) NinjaEscapedCommands() []string {
 	var commands []string
@@ -427,6 +431,7 @@
 	tools := r.Tools()
 	commands := r.NinjaEscapedCommands()
 	outputs := r.Outputs()
+	inputs := r.Inputs()
 
 	if len(commands) == 0 {
 		return
@@ -440,7 +445,7 @@
 	if r.sbox {
 		sboxOutputs := make([]string, len(outputs))
 		for i, output := range outputs {
-			sboxOutputs[i] = "__SBOX_OUT_DIR__/" + Rel(ctx, r.sboxOutDir.String(), output.String())
+			sboxOutputs[i] = filepath.Join(sboxOutDir, Rel(ctx, r.sboxOutDir.String(), output.String()))
 		}
 
 		commandString = proptools.ShellEscape(commandString)
@@ -458,10 +463,19 @@
 			sboxCmd.Flag("--depfile-out").Text(depFile.String())
 		}
 
+		// Add a hash of the list of input files to the xbox command line so that ninja reruns
+		// it when the list of input files changes.
+		sboxCmd.FlagWithArg("--input-hash ", hashSrcFiles(inputs))
+
 		sboxCmd.Flags(sboxOutputs)
 
 		commandString = sboxCmd.buf.String()
 		tools = append(tools, sboxCmd.tools...)
+	} else {
+		// If not using sbox the rule will run the command directly, put the hash of the
+		// list of input files in a comment at the end of the command line to ensure ninja
+		// reruns the rule when the list of input files changes.
+		commandString += " # hash of input list: " + hashSrcFiles(inputs)
 	}
 
 	// Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
@@ -499,7 +513,7 @@
 			Pool:           pool,
 		}),
 		Inputs:          rspFileInputs,
-		Implicits:       r.Inputs(),
+		Implicits:       inputs,
 		Output:          output,
 		ImplicitOutputs: implicitOutputs,
 		SymlinkOutputs:  r.SymlinkOutputs(),
@@ -527,14 +541,16 @@
 	// spans [start,end) of the command that should not be ninja escaped
 	unescapedSpans [][2]int
 
-	sbox       bool
-	sboxOutDir WritablePath
+	sbox bool
+	// outDir is the directory that will contain the output files of the rules.  sbox will copy
+	// the output files from the sandbox directory to this directory when it finishes.
+	outDir WritablePath
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
 	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
-			return "__SBOX_OUT_DIR__/" + rel
+		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.inputs = append(c.inputs, path)
@@ -543,8 +559,8 @@
 
 func (c *RuleBuilderCommand) addImplicit(path Path) string {
 	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
-			return "__SBOX_OUT_DIR__/" + rel
+		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.implicits = append(c.implicits, path)
@@ -555,15 +571,22 @@
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
-func (c *RuleBuilderCommand) outputStr(path Path) string {
+func (c *RuleBuilderCommand) outputStr(path WritablePath) string {
 	if c.sbox {
-		// Errors will be handled in RuleBuilder.Build where we have a context to report them
-		rel, _, _ := maybeRelErr(c.sboxOutDir.String(), path.String())
-		return "__SBOX_OUT_DIR__/" + rel
+		return SboxPathForOutput(path, c.outDir)
 	}
 	return path.String()
 }
 
+// SboxPathForOutput takes an output path and the out directory passed to RuleBuilder.Sbox(),
+// and returns the corresponding path for the output in the sbox sandbox.  This can be used
+// on the RuleBuilder command line to reference the output.
+func SboxPathForOutput(path WritablePath, outDir WritablePath) string {
+	// Errors will be handled in RuleBuilder.Build where we have a context to report them
+	rel, _, _ := maybeRelErr(outDir.String(), path.String())
+	return filepath.Join(sboxOutDir, rel)
+}
+
 // Text adds the specified raw text to the command line.  The text should not contain input or output paths or the
 // rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
@@ -727,7 +750,7 @@
 	if !c.sbox {
 		panic("OutputDir only valid with Sbox")
 	}
-	return c.Text("__SBOX_OUT_DIR__")
+	return c.Text(sboxOutDir)
 }
 
 // DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
@@ -906,3 +929,12 @@
 	}
 	return s
 }
+
+// hashSrcFiles returns a hash of the list of source files.  It is used to ensure the command line
+// or the sbox textproto manifest change even if the input files are not listed on the command line.
+func hashSrcFiles(srcFiles Paths) string {
+	h := sha256.New()
+	srcFileList := strings.Join(srcFiles.Strings(), "\n")
+	h.Write([]byte(srcFileList))
+	return fmt.Sprintf("%x", h.Sum(nil))
+}
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 0d1070d..c1d5521 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -441,7 +442,7 @@
 type testRuleBuilderModule struct {
 	ModuleBase
 	properties struct {
-		Src string
+		Srcs []string
 
 		Restat bool
 		Sbox   bool
@@ -449,7 +450,7 @@
 }
 
 func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	in := PathForSource(ctx, t.properties.Src)
+	in := PathsForSource(ctx, t.properties.Srcs)
 	out := PathForModuleOut(ctx, ctx.ModuleName())
 	outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
 	outDir := PathForModuleOut(ctx)
@@ -468,17 +469,17 @@
 	out := PathForOutput(ctx, "baz")
 	outDep := PathForOutput(ctx, "baz.d")
 	outDir := PathForOutput(ctx)
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, true, false)
+	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, true, false)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir WritablePath, restat, sbox bool) {
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir WritablePath, restat, sbox bool) {
 	rule := NewRuleBuilder()
 
 	if sbox {
 		rule.Sbox(outDir)
 	}
 
-	rule.Command().Tool(PathForSource(ctx, "cp")).Input(in).Output(out).ImplicitDepFile(outDep)
+	rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
 
 	if restat {
 		rule.Restat()
@@ -496,21 +497,21 @@
 	bp := `
 		rule_builder_test {
 			name: "foo",
-			src: "bar",
+			srcs: ["bar"],
 			restat: true,
 		}
 		rule_builder_test {
 			name: "foo_sbox",
-			src: "bar",
+			srcs: ["bar"],
 			sbox: true,
 		}
 	`
 
 	config := TestConfig(buildDir, nil, bp, fs)
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
 	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -519,7 +520,10 @@
 
 	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
 		t.Helper()
-		if params.RuleParams.Command != wantCommand {
+		command := params.RuleParams.Command
+		re := regexp.MustCompile(" (# hash of input list:|--input-hash) [a-z0-9]*")
+		command = re.ReplaceAllLiteralString(command, "")
+		if command != wantCommand {
 			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
 		}
 
@@ -651,3 +655,78 @@
 		})
 	}
 }
+
+func TestRuleBuilderHashInputs(t *testing.T) {
+	// The basic idea here is to verify that the command (in the case of a
+	// non-sbox rule) or the sbox textproto manifest contain a hash of the
+	// inputs.
+
+	// By including a hash of the inputs, we cause the rule to re-run if
+	// the list of inputs changes because the command line or a dependency
+	// changes.
+
+	bp := `
+			rule_builder_test {
+				name: "hash0",
+				srcs: ["in1.txt", "in2.txt"],
+			}
+			rule_builder_test {
+				name: "hash0_sbox",
+				srcs: ["in1.txt", "in2.txt"],
+				sbox: true,
+			}
+			rule_builder_test {
+				name: "hash1",
+				srcs: ["in1.txt", "in2.txt", "in3.txt"],
+			}
+			rule_builder_test {
+				name: "hash1_sbox",
+				srcs: ["in1.txt", "in2.txt", "in3.txt"],
+				sbox: true,
+			}
+		`
+	testcases := []struct {
+		name         string
+		expectedHash string
+	}{
+		{
+			name: "hash0",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
+			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
+		},
+		{
+			name: "hash1",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
+		},
+	}
+
+	config := TestConfig(buildDir, nil, bp, nil)
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	for _, test := range testcases {
+		t.Run(test.name, func(t *testing.T) {
+			t.Run("sbox", func(t *testing.T) {
+				gen := ctx.ModuleForTests(test.name+"_sbox", "")
+				command := gen.Output(test.name + "_sbox").RuleParams.Command
+				if g, w := command, " --input-hash "+test.expectedHash; !strings.Contains(g, w) {
+					t.Errorf("Expected command line to end with %q, got %q", w, g)
+				}
+			})
+			t.Run("", func(t *testing.T) {
+				gen := ctx.ModuleForTests(test.name+"", "")
+				command := gen.Output(test.name).RuleParams.Command
+				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
+					t.Errorf("Expected command line to end with %q, got %q", w, g)
+				}
+			})
+		})
+	}
+}
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 9677f34..b1810b3 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -175,7 +175,7 @@
 			},
 		}
 
-		ctx := NewTestContext()
+		ctx := NewTestContext(config)
 		ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
 		ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
 		ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
@@ -183,7 +183,7 @@
 		ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
 		ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
 		ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-		ctx.Register(config)
+		ctx.Register()
 
 		_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 		FailIfErrored(t, errs)
diff --git a/android/testing.go b/android/testing.go
index 8ea4168..1e2ae13 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -25,14 +25,14 @@
 	"github.com/google/blueprint"
 )
 
-func NewTestContext() *TestContext {
+func NewTestContext(config Config) *TestContext {
 	namespaceExportFilter := func(namespace *Namespace) bool {
 		return true
 	}
 
 	nameResolver := NewNameResolver(namespaceExportFilter)
 	ctx := &TestContext{
-		Context:      &Context{blueprint.NewContext()},
+		Context:      &Context{blueprint.NewContext(), config},
 		NameResolver: nameResolver,
 	}
 
@@ -40,11 +40,16 @@
 
 	ctx.postDeps = append(ctx.postDeps, registerPathDepsMutator)
 
+	ctx.SetFs(ctx.config.fs)
+	if ctx.config.mockBpList != "" {
+		ctx.SetModuleListFile(ctx.config.mockBpList)
+	}
+
 	return ctx
 }
 
-func NewTestArchContext() *TestContext {
-	ctx := NewTestContext()
+func NewTestArchContext(config Config) *TestContext {
+	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
 	return ctx
 }
@@ -53,7 +58,6 @@
 	*Context
 	preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
 	NameResolver                          *NameResolver
-	config                                Config
 }
 
 func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {
@@ -77,16 +81,10 @@
 	ctx.finalDeps = append(ctx.finalDeps, f)
 }
 
-func (ctx *TestContext) Register(config Config) {
-	ctx.SetFs(config.fs)
-	if config.mockBpList != "" {
-		ctx.SetModuleListFile(config.mockBpList)
-	}
+func (ctx *TestContext) Register() {
 	registerMutators(ctx.Context.Context, ctx.preArch, ctx.preDeps, ctx.postDeps, ctx.finalDeps)
 
 	ctx.RegisterSingletonType("env", EnvSingleton)
-
-	ctx.config = config
 }
 
 func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
@@ -106,7 +104,7 @@
 }
 
 func (ctx *TestContext) RegisterSingletonType(name string, factory SingletonFactory) {
-	ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(factory))
+	ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(ctx.Context, factory))
 }
 
 func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
diff --git a/android/variable_test.go b/android/variable_test.go
index 9cafedd..393fe01 100644
--- a/android/variable_test.go
+++ b/android/variable_test.go
@@ -157,23 +157,6 @@
 }
 
 func TestProductVariables(t *testing.T) {
-	ctx := NewTestContext()
-	// A module type that has a srcs property but not a cflags property.
-	ctx.RegisterModuleType("module1", testProductVariableModuleFactoryFactory(&struct {
-		Srcs []string
-	}{}))
-	// A module type that has a cflags property but not a srcs property.
-	ctx.RegisterModuleType("module2", testProductVariableModuleFactoryFactory(&struct {
-		Cflags []string
-	}{}))
-	// A module type that does not have any properties that match product_variables.
-	ctx.RegisterModuleType("module3", testProductVariableModuleFactoryFactory(&struct {
-		Foo []string
-	}{}))
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("variable", VariableMutator).Parallel()
-	})
-
 	// Test that a module can use one product variable even if it doesn't have all the properties
 	// supported by that product variable.
 	bp := `
@@ -201,7 +184,24 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.TestProductVariables.Eng = proptools.BoolPtr(true)
 
-	ctx.Register(config)
+	ctx := NewTestContext(config)
+	// A module type that has a srcs property but not a cflags property.
+	ctx.RegisterModuleType("module1", testProductVariableModuleFactoryFactory(&struct {
+		Srcs []string
+	}{}))
+	// A module type that has a cflags property but not a srcs property.
+	ctx.RegisterModuleType("module2", testProductVariableModuleFactoryFactory(&struct {
+		Cflags []string
+	}{}))
+	// A module type that does not have any properties that match product_variables.
+	ctx.RegisterModuleType("module3", testProductVariableModuleFactoryFactory(&struct {
+		Foo []string
+	}{}))
+	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+		ctx.BottomUp("variable", VariableMutator).Parallel()
+	})
+
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
@@ -293,7 +293,7 @@
 	config := TestConfig(buildDir, nil, bp, nil)
 	config.TestProductVariables.Eng = boolPtr(true)
 
-	ctx := NewTestContext()
+	ctx := NewTestContext(config)
 
 	ctx.RegisterModuleType("test", productVariablesDefaultsTestModuleFactory)
 	ctx.RegisterModuleType("defaults", productVariablesDefaultsTestDefaultsFactory)
@@ -303,7 +303,7 @@
 		ctx.BottomUp("variable", VariableMutator).Parallel()
 	})
 
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	FailIfErrored(t, errs)
diff --git a/android/visibility_test.go b/android/visibility_test.go
index cb5ef35..87a295e 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -1168,7 +1168,7 @@
 	// Create a new config per test as visibility information is stored in the config.
 	config := TestArchConfig(buildDir, nil, "", fs)
 
-	ctx := NewTestArchContext()
+	ctx := NewTestArchContext(config)
 	ctx.RegisterModuleType("mock_library", newMockLibraryModule)
 	ctx.RegisterModuleType("mock_parent", newMockParentFactory)
 	ctx.RegisterModuleType("mock_defaults", defaultsFactory)
@@ -1180,7 +1180,7 @@
 	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
 	ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
 	ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseBlueprintsFiles(".")
 	if len(errs) > 0 {
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 739a965..4540a1f 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -214,6 +214,8 @@
 
 			"LOCAL_PRIVATE_PLATFORM_APIS": "platform_apis",
 			"LOCAL_JETIFIER_ENABLED":      "jetifier",
+
+			"LOCAL_IS_UNIT_TEST": "unit_test",
 		})
 }
 
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
index 86dabf9..e61241b 100644
--- a/androidmk/parser/parser.go
+++ b/androidmk/parser/parser.go
@@ -212,8 +212,21 @@
 	expression := SimpleMakeString("", pos)
 
 	switch d {
-	case "endif", "endef", "else":
+	case "endif", "endef":
 		// Nothing
+	case "else":
+		p.ignoreSpaces()
+		if p.tok != '\n' {
+			d = p.scanner.TokenText()
+			p.accept(scanner.Ident)
+			if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
+				d = "el" + d
+				p.ignoreSpaces()
+				expression = p.parseExpression()
+			} else {
+				p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
+			}
+		}
 	case "define":
 		expression, endPos = p.parseDefine()
 	default:
diff --git a/apex/androidmk.go b/apex/androidmk.go
index ee8b2b3..993260c 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -307,6 +307,7 @@
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
 	return android.AndroidMkData{
+		DistFiles: a.distFiles,
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
 			moduleNames := []string{}
 			apexType := a.properties.ApexType
@@ -391,6 +392,9 @@
 					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
 						goal, a.installedFilesFile.String(), distFile)
 				}
+				for _, dist := range data.Entries.GetDistForGoals(a) {
+					fmt.Fprintf(w, dist)
+				}
 			}
 		}}
 }
diff --git a/apex/apex.go b/apex/apex.go
index fdc105e..91770f4 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -63,7 +63,6 @@
 	testTag        = dependencyTag{name: "test", payload: true}
 	keyTag         = dependencyTag{name: "key"}
 	certificateTag = dependencyTag{name: "certificate"}
-	usesTag        = dependencyTag{name: "uses"}
 	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
 	rroTag         = dependencyTag{name: "rro", payload: true}
 	bpfTag         = dependencyTag{name: "bpf", payload: true}
@@ -764,7 +763,6 @@
 	ctx.BottomUp("apex", apexMutator).Parallel()
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
-	ctx.BottomUp("apex_uses", apexUsesMutator).Parallel()
 	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
 }
 
@@ -1007,12 +1005,6 @@
 	}
 }
 
-func apexUsesMutator(mctx android.BottomUpMutatorContext) {
-	if ab, ok := mctx.Module().(*apexBundle); ok {
-		mctx.AddFarVariationDependencies(nil, usesTag, ab.properties.Uses...)
-	}
-}
-
 var (
 	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
 )
@@ -1132,12 +1124,6 @@
 
 	HideFromMake bool `blueprint:"mutated"`
 
-	// Indicates this APEX provides C++ shared libaries to other APEXes. Default: false.
-	Provide_cpp_shared_libs *bool
-
-	// List of providing APEXes' names so that this APEX can depend on provided shared libraries.
-	Uses []string
-
 	// package format of this apex variant; could be non-flattened, flattened, or zip.
 	// imageApex, zipApex or flattened
 	ApexType apexPackaging `blueprint:"mutated"`
@@ -1477,6 +1463,8 @@
 	lintReports android.Paths
 
 	payloadFsType fsType
+
+	distFiles android.TaggedDistFiles
 }
 
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
@@ -2179,30 +2167,6 @@
 	var provideNativeLibs []string
 	var requireNativeLibs []string
 
-	// Check if "uses" requirements are met with dependent apexBundles
-	var providedNativeSharedLibs []string
-	useVendor := proptools.Bool(a.properties.Use_vendor)
-	ctx.VisitDirectDepsBlueprint(func(m blueprint.Module) {
-		if ctx.OtherModuleDependencyTag(m) != usesTag {
-			return
-		}
-		otherName := ctx.OtherModuleName(m)
-		other, ok := m.(*apexBundle)
-		if !ok {
-			ctx.PropertyErrorf("uses", "%q is not a provider", otherName)
-			return
-		}
-		if proptools.Bool(other.properties.Use_vendor) != useVendor {
-			ctx.PropertyErrorf("use_vendor", "%q has different value of use_vendor", otherName)
-			return
-		}
-		if !proptools.Bool(other.properties.Provide_cpp_shared_libs) {
-			ctx.PropertyErrorf("uses", "%q does not provide native_shared_libs", otherName)
-			return
-		}
-		providedNativeSharedLibs = append(providedNativeSharedLibs, other.properties.Native_shared_libs...)
-	})
-
 	var filesInfo []apexFile
 	// TODO(jiyong) do this using WalkPayloadDeps
 	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
@@ -2350,11 +2314,6 @@
 				// tags used below are private (e.g. `cc.sharedDepTag`).
 				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
 					if cc, ok := child.(*cc.Module); ok {
-						if android.InList(cc.Name(), providedNativeSharedLibs) {
-							// If we're using a shared library which is provided from other APEX,
-							// don't include it in this APEX
-							return false
-						}
 						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
 							requireNativeLibs = append(requireNativeLibs, ":vndk")
 							return false
@@ -2522,6 +2481,8 @@
 	a.buildApexDependencyInfo(ctx)
 
 	a.buildLintReports(ctx)
+
+	a.distFiles = a.GenerateTaggedDistFiles(ctx)
 }
 
 // Enforce that Java deps of the apex are using stable SDKs to compile
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 09d9c44..33e5077 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -149,10 +149,8 @@
 		"system/sepolicy/apex/myapex.updatable-file_contexts": nil,
 		"system/sepolicy/apex/myapex2-file_contexts":          nil,
 		"system/sepolicy/apex/otherapex-file_contexts":        nil,
-		"system/sepolicy/apex/commonapex-file_contexts":       nil,
 		"system/sepolicy/apex/com.android.vndk-file_contexts": nil,
 		"mylib.cpp":                                  nil,
-		"mylib_common.cpp":                           nil,
 		"mytest.cpp":                                 nil,
 		"mytest1.cpp":                                nil,
 		"mytest2.cpp":                                nil,
@@ -216,7 +214,7 @@
 		handler(tempFS, config)
 	}
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 
 	// from android package
 	android.RegisterPackageBuildComponents(ctx)
@@ -261,7 +259,7 @@
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx, config
 }
@@ -4202,131 +4200,6 @@
 	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += myapex.flattened")
 }
 
-func TestApexUsesOtherApex(t *testing.T) {
-	ctx, _ := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib"],
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-			native_shared_libs: ["libcommon"],
-			provide_cpp_shared_libs: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			shared_libs: ["libcommon"],
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "myapex" ],
-		}
-
-		cc_library {
-			name: "libcommon",
-			srcs: ["mylib_common.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			// TODO: remove //apex_available:platform
-			apex_available: [
-				"//apex_available:platform",
-				"commonapex",
-				"myapex",
-			],
-		}
-	`)
-
-	module1 := ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	apexRule1 := module1.Rule("apexRule")
-	copyCmds1 := apexRule1.Args["copy_commands"]
-
-	module2 := ctx.ModuleForTests("commonapex", "android_common_commonapex_image")
-	apexRule2 := module2.Rule("apexRule")
-	copyCmds2 := apexRule2.Args["copy_commands"]
-
-	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
-	ensureListContains(t, ctx.ModuleVariantsForTests("libcommon"), "android_arm64_armv8-a_shared_apex10000")
-	ensureContains(t, copyCmds1, "image.apex/lib64/mylib.so")
-	ensureContains(t, copyCmds2, "image.apex/lib64/libcommon.so")
-	ensureNotContains(t, copyCmds1, "image.apex/lib64/libcommon.so")
-}
-
-func TestApexUsesFailsIfNotProvided(t *testing.T) {
-	testApexError(t, `uses: "commonapex" does not provide native_shared_libs`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-	testApexError(t, `uses: "commonapex" is not a provider`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			uses: ["commonapex"],
-		}
-
-		cc_library {
-			name: "commonapex",
-			system_shared_libs: [],
-			stl: "none",
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-}
-
-func TestApexUsesFailsIfUseVenderMismatch(t *testing.T) {
-	testApexError(t, `use_vendor: "commonapex" has different value of use_vendor`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			use_vendor: true,
-			uses: ["commonapex"],
-		}
-
-		apex {
-			name: "commonapex",
-			key: "myapex.key",
-			provide_cpp_shared_libs: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`, func(fs map[string][]byte, config android.Config) {
-		setUseVendorAllowListForTest(config, []string{"myapex"})
-	})
-}
-
 func TestErrorsIfDepsAreNotEnabled(t *testing.T) {
 	testApexError(t, `module "myapex" .* depends on disabled module "libfoo"`, `
 		apex {
@@ -5574,7 +5447,7 @@
 		}
 		`, withManifestPackageNameOverrides([]string{"AppFoo:com.android.foo"}))
 
-	bundleConfigRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Description("Bundle Config")
+	bundleConfigRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("bundle_config.json")
 	content := bundleConfigRule.Args["content"]
 
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
@@ -5600,7 +5473,7 @@
 			set: "AppSet.apks",
 		}`)
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	bundleConfigRule := mod.Description("Bundle Config")
+	bundleConfigRule := mod.Output("bundle_config.json")
 	content := bundleConfigRule.Args["content"]
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
 	s := mod.Rule("apexRule").Args["copy_commands"]
@@ -5746,7 +5619,9 @@
 	}
 	cc.GatherRequiredFilesForTest(fs)
 
-	ctx := android.NewTestArchContext()
+	config := android.TestArchConfig(buildDir, nil, bp, fs)
+
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("apex", BundleFactory)
 	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
@@ -5761,8 +5636,7 @@
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
 
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-	ctx.Register(config)
+	ctx.Register()
 
 	_ = dexpreopt.GlobalSoongConfigForTests(config)
 	dexpreopt.RegisterToolModulesForTest(ctx)
@@ -5897,7 +5771,15 @@
 		"system/sepolicy/apex/myapex-file_contexts": nil,
 	}
 
-	ctx := android.NewTestArchContext()
+	config := android.TestArchConfig(buildDir, nil, bp, fs)
+	android.SetTestNeverallowRules(config, rules)
+	updatableBootJars := make([]string, 0, len(apexBootJars))
+	for _, apexBootJar := range apexBootJars {
+		updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
+	}
+	config.TestProductVariables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
+
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("apex", BundleFactory)
 	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
@@ -5910,15 +5792,7 @@
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
 	ctx.PostDepsMutators(android.RegisterNeverallowMutator)
 
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-	android.SetTestNeverallowRules(config, rules)
-	updatableBootJars := make([]string, 0, len(apexBootJars))
-	for _, apexBootJar := range apexBootJars {
-		updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
-	}
-	config.TestProductVariables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
-
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	android.FailIfErrored(t, errs)
diff --git a/apex/builder.go b/apex/builder.go
index ad673d6..acfb8c5 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -373,14 +373,7 @@
 		panic(fmt.Errorf("error while marshalling to %q: %#v", output, err))
 	}
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Output:      output,
-		Description: "Bundle Config " + output.String(),
-		Args: map[string]string{
-			"content": string(j),
-		},
-	})
+	android.WriteFileRule(ctx, output, string(j))
 
 	return output.OutputPath
 }
diff --git a/apex/key.go b/apex/key.go
index d2d5786..43764da 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -116,7 +116,7 @@
 		partition             string
 	}
 	toString := func(e apexKeyEntry) string {
-		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\\n"
+		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\n"
 		if e.presigned {
 			return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition)
 		} else {
@@ -173,17 +173,9 @@
 
 	var filecontent strings.Builder
 	for _, name := range moduleNames {
-		fmt.Fprintf(&filecontent, "%s", toString(apexKeyMap[name]))
+		filecontent.WriteString(toString(apexKeyMap[name]))
 	}
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Description: "apexkeys.txt",
-		Output:      s.output,
-		Args: map[string]string{
-			"content": filecontent.String(),
-		},
-	})
+	android.WriteFileRule(ctx, s.output, filecontent.String())
 }
 
 func apexKeysTextFactory() android.Singleton {
diff --git a/bazel/bazelenv.sh b/bazel/bazelenv.sh
deleted file mode 100755
index fcf71f1..0000000
--- a/bazel/bazelenv.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-# Helper script for setting environment variables required for Bazel/Soong
-# mixed builds prototype. For development use only.
-#
-# Usage:
-#   export BAZEL_PATH=[some_bazel_path] && source bazelenv.sh
-#
-# If BAZEL_PATH is not set, `which bazel` will be used
-# to locate the appropriate bazel to use.
-
-
-# Function to find top of the source tree (if $TOP isn't set) by walking up the
-# tree.
-function gettop
-{
-    local TOPFILE=build/soong/root.bp
-    if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
-        # The following circumlocution ensures we remove symlinks from TOP.
-        (cd $TOP; PWD= /bin/pwd)
-    else
-        if [ -f $TOPFILE ] ; then
-            # The following circumlocution (repeated below as well) ensures
-            # that we record the true directory name and not one that is
-            # faked up with symlink names.
-            PWD= /bin/pwd
-        else
-            local HERE=$PWD
-            T=
-            while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
-                \cd ..
-                T=`PWD= /bin/pwd -P`
-            done
-            \cd $HERE
-            if [ -f "$T/$TOPFILE" ]; then
-                echo $T
-            fi
-        fi
-    fi
-}
-
-BASE_DIR="$(mktemp -d)"
-
-if [ -z "$BAZEL_PATH" ] ; then
-    export BAZEL_PATH="$(which bazel)"
-fi
-
-# TODO(cparsons): Use USE_BAZEL=1 instead once "mixed Soong/Bazel builds" are
-# production ready.
-export USE_BAZEL_ANALYSIS=1
-# TODO(cparsons): Retrieve this information in either envsetup.sh or 
-# bazel.sh.
-export BAZEL_HOME="$BASE_DIR/bazelhome"
-export BAZEL_OUTPUT_BASE="$BASE_DIR/output"
-export BAZEL_WORKSPACE="$(gettop)"
-
-echo "USE_BAZEL_ANALYSIS=${USE_BAZEL_ANALYSIS}"
-echo "BAZEL_PATH=${BAZEL_PATH}"
-echo "BAZEL_HOME=${BAZEL_HOME}"
-echo "BAZEL_OUTPUT_BASE=${BAZEL_OUTPUT_BASE}"
-echo "BAZEL_WORKSPACE=${BAZEL_WORKSPACE}"
-
-mkdir -p $BAZEL_HOME
-mkdir -p $BAZEL_OUTPUT_BASE
diff --git a/bazel/master.WORKSPACE.bazel b/bazel/master.WORKSPACE.bazel
deleted file mode 100644
index e69de29..0000000
--- a/bazel/master.WORKSPACE.bazel
+++ /dev/null
diff --git a/bpf/bpf_test.go b/bpf/bpf_test.go
index d06d7d1..be9e36e 100644
--- a/bpf/bpf_test.go
+++ b/bpf/bpf_test.go
@@ -58,9 +58,9 @@
 }
 
 func testContext(config android.Config) *android.TestContext {
-	ctx := cc.CreateTestContext()
+	ctx := cc.CreateTestContext(config)
 	ctx.RegisterModuleType("bpf", BpfFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx
 }
diff --git a/build_test.bash b/build_test.bash
index a53a585..accca0f 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -43,14 +43,9 @@
     ;;
 esac
 
-function bazel_cleanup {
-  "${TOP}/tools/bazel" shutdown
-}
-trap bazel_cleanup EXIT
-
 echo
 echo "Running Bazel smoke test..."
-"${TOP}/tools/bazel" info
+"${TOP}/tools/bazel" --batch --max_idle_secs=1 info
 
 echo
 echo "Running Soong test..."
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 38269cb..d32e4de 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -370,6 +370,9 @@
 			entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
 		}
 		entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", test.Properties.Test_mainline_modules...)
+		if Bool(test.Properties.Test_options.Unit_test) {
+			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+		}
 	})
 
 	androidMkWriteTestData(test.data, ctx, entries)
@@ -505,7 +508,7 @@
 	})
 }
 
-func (c *vendorSnapshotLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	// Each vendor snapshot is exported to androidMk only when BOARD_VNDK_VERSION != current
 	// and the version of the prebuilt is same as BOARD_VNDK_VERSION.
 	if c.shared() {
@@ -549,7 +552,7 @@
 	})
 }
 
-func (c *vendorSnapshotBinaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotBinaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.Class = "EXECUTABLES"
 
 	if c.androidMkVendorSuffix {
@@ -563,7 +566,7 @@
 	})
 }
 
-func (c *vendorSnapshotObjectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotObjectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.Class = "STATIC_LIBRARIES"
 
 	if c.androidMkVendorSuffix {
diff --git a/cc/binary.go b/cc/binary.go
index 70cf5ff..da29412 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -95,6 +95,10 @@
 	// Names of symlinks to be installed for use in LOCAL_MODULE_SYMLINKS
 	symlinks []string
 
+	// If the module has symlink_preferred_arch set, the name of the symlink to the
+	// binary for the preferred arch.
+	preferredArchSymlink string
+
 	// Output archive of gcno coverage information
 	coverageOutputFile android.OptionalPath
 
@@ -402,7 +406,9 @@
 			ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix")
 		}
 		if ctx.TargetPrimary() {
-			binary.symlinks = append(binary.symlinks, binary.getStemWithoutSuffix(ctx))
+			symlinkName := binary.getStemWithoutSuffix(ctx)
+			binary.symlinks = append(binary.symlinks, symlinkName)
+			binary.preferredArchSymlink = symlinkName
 		}
 	}
 
@@ -455,12 +461,26 @@
 		binary.baseInstaller.subDir = "bootstrap"
 	}
 	binary.baseInstaller.install(ctx, file)
+
+	var preferredArchSymlinkPath android.OptionalPath
 	for _, symlink := range binary.symlinks {
-		ctx.InstallSymlink(binary.baseInstaller.installDir(ctx), symlink, binary.baseInstaller.path)
+		installedSymlink := ctx.InstallSymlink(binary.baseInstaller.installDir(ctx), symlink,
+			binary.baseInstaller.path)
+		if symlink == binary.preferredArchSymlink {
+			// If this is the preferred arch symlink, save the installed path for use as the
+			// tool path.
+			preferredArchSymlinkPath = android.OptionalPathForPath(installedSymlink)
+		}
 	}
 
 	if ctx.Os().Class == android.Host {
-		binary.toolPath = android.OptionalPathForPath(binary.baseInstaller.path)
+		// If the binary is multilib with a symlink to the preferred architecture, use the
+		// symlink instead of the binary because that's the more "canonical" name.
+		if preferredArchSymlinkPath.Valid() {
+			binary.toolPath = preferredArchSymlinkPath
+		} else {
+			binary.toolPath = android.OptionalPathForPath(binary.baseInstaller.path)
+		}
 	}
 }
 
diff --git a/cc/cc.go b/cc/cc.go
index 1f31872..bd6e5d5 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -88,7 +88,7 @@
 		ctx.TopDown("double_loadable", checkDoubleLoadableLibraries).Parallel()
 	})
 
-	android.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
+	ctx.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
 }
 
 type Deps struct {
@@ -305,10 +305,11 @@
 
 	// Normally Soong uses the directory structure to decide which modules
 	// should be included (framework) or excluded (non-framework) from the
-	// vendor snapshot, but this property allows a partner to exclude a
-	// module normally thought of as a framework module from the vendor
-	// snapshot.
-	Exclude_from_vendor_snapshot *bool
+	// different snapshots (vendor, recovery, etc.), but these properties
+	// allow a partner to exclude a module normally thought of as a
+	// framework module from a snapshot.
+	Exclude_from_vendor_snapshot   *bool
+	Exclude_from_recovery_snapshot *bool
 }
 
 type VendorProperties struct {
@@ -368,7 +369,7 @@
 	useSdk() bool
 	sdkVersion() string
 	useVndk() bool
-	isNdk() bool
+	isNdk(config android.Config) bool
 	isLlndk(config android.Config) bool
 	isLlndkPublic(config android.Config) bool
 	isVndkPrivate(config android.Config) bool
@@ -592,6 +593,11 @@
 	return ok && ccLibDepTag.static()
 }
 
+func IsHeaderDepTag(depTag blueprint.DependencyTag) bool {
+	ccLibDepTag, ok := depTag.(libraryDependencyTag)
+	return ok && ccLibDepTag.header()
+}
+
 func IsRuntimeDepTag(depTag blueprint.DependencyTag) bool {
 	ccDepTag, ok := depTag.(dependencyTag)
 	return ok && ccDepTag == runtimeDepTag
@@ -933,8 +939,8 @@
 	return c.coverage.Properties.IsCoverageVariant
 }
 
-func (c *Module) IsNdk() bool {
-	return inList(c.BaseModuleName(), ndkKnownLibs)
+func (c *Module) IsNdk(config android.Config) bool {
+	return inList(c.BaseModuleName(), *getNDKKnownLibs(config))
 }
 
 func (c *Module) isLlndk(config android.Config) bool {
@@ -1046,6 +1052,10 @@
 	return Bool(c.Properties.Exclude_from_vendor_snapshot)
 }
 
+func (c *Module) ExcludeFromRecoverySnapshot() bool {
+	return Bool(c.Properties.Exclude_from_recovery_snapshot)
+}
+
 func isBionic(name string) bool {
 	switch name {
 	case "libc", "libm", "libdl", "libdl_android", "linker":
@@ -1135,8 +1145,8 @@
 	return ctx.mod.UseVndk()
 }
 
-func (ctx *moduleContextImpl) isNdk() bool {
-	return ctx.mod.IsNdk()
+func (ctx *moduleContextImpl) isNdk(config android.Config) bool {
+	return ctx.mod.IsNdk(config)
 }
 
 func (ctx *moduleContextImpl) isLlndk(config android.Config) bool {
@@ -1756,7 +1766,7 @@
 			for _, entry := range list {
 				// strip #version suffix out
 				name, _ := StubsLibNameAndVersion(entry)
-				if ctx.useSdk() && inList(name, ndkKnownLibs) {
+				if ctx.useSdk() && inList(name, *getNDKKnownLibs(ctx.Config())) {
 					variantLibs = append(variantLibs, name+ndkLibrarySuffix)
 				} else if ctx.useVndk() {
 					nonvariantLibs = append(nonvariantLibs, rewriteVendorLibs(entry))
diff --git a/cc/cc_test.go b/cc/cc_test.go
index b3bb282..f5ce867 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -53,8 +53,8 @@
 
 func testCcWithConfig(t *testing.T, config android.Config) *android.TestContext {
 	t.Helper()
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -84,8 +84,8 @@
 func testCcErrorWithConfig(t *testing.T, pattern string, config android.Config) {
 	t.Helper()
 
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	if len(errs) > 0 {
@@ -296,8 +296,8 @@
 
 func checkWriteFileOutput(t *testing.T, params android.TestingBuildParams, expected []string) {
 	t.Helper()
-	assertString(t, params.Rule.String(), android.WriteFile.String())
-	actual := strings.FieldsFunc(strings.ReplaceAll(params.Args["content"], "\\n", "\n"), func(r rune) bool { return r == '\n' })
+	content := android.ContentFromFileRuleForTests(t, params)
+	actual := strings.FieldsFunc(content, func(r rune) bool { return r == '\n' })
 	assertArrayString(t, actual, expected)
 }
 
@@ -1293,8 +1293,8 @@
 	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("BOARD")
 	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"deps/Android.bp", "framework/Android.bp", "vendor/Android.bp", "vndk/Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -1451,8 +1451,8 @@
 	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"deps/Android.bp", "framework/Android.bp", "device/Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -1541,8 +1541,8 @@
 	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"deps/Android.bp", "device/Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -1551,6 +1551,8 @@
 	android.CheckErrorsAgainstExpectations(t, errs, []string{
 		`module "libvendor\{.+,image:vendor.+,arch:arm64_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
 		`module "libvendor\{.+,image:vendor.+,arch:arm_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
+		`module "libvendor\{.+,image:vendor.+,arch:arm64_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
+		`module "libvendor\{.+,image:vendor.+,arch:arm_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
 	})
 }
 
@@ -1582,8 +1584,8 @@
 	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
-	ctx := CreateTestContext()
-	ctx.Register(config)
+	ctx := CreateTestContext(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"deps/Android.bp", "framework/Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -1597,6 +1599,132 @@
 	})
 }
 
+func TestRecoverySnapshotCapture(t *testing.T) {
+	bp := `
+	cc_library {
+		name: "libvndk",
+		vendor_available: true,
+		recovery_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "librecovery",
+		recovery: true,
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "librecovery_available",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	cc_library_headers {
+		name: "librecovery_headers",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	cc_binary {
+		name: "recovery_bin",
+		recovery: true,
+		nocrt: true,
+	}
+
+	cc_binary {
+		name: "recovery_available_bin",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	toolchain_library {
+		name: "libb",
+		recovery_available: true,
+		src: "libb.a",
+	}
+
+	cc_object {
+		name: "obj",
+		recovery_available: true,
+	}
+`
+	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	ctx := testCcWithConfig(t, config)
+
+	// Check Recovery snapshot output.
+
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var jsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, only recovery_available modules are captured.
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "libvndk.so.json"),
+			filepath.Join(sharedDir, "librecovery.so.json"),
+			filepath.Join(sharedDir, "librecovery_available.so.json"))
+
+		// For static libraries, all recovery:true and recovery_available modules are captured.
+		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libb.a.json"),
+			filepath.Join(staticDir, "librecovery.a.json"),
+			filepath.Join(staticDir, "librecovery_available.a.json"))
+
+		// For binary executables, all recovery:true and recovery_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			checkSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			checkSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "recovery_bin.json"),
+				filepath.Join(binaryDir, "recovery_available_bin.json"))
+		}
+
+		// For header libraries, all vendor:true and vendor_available modules are captured.
+		headerDir := filepath.Join(snapshotVariantPath, archDir, "header")
+		jsonFiles = append(jsonFiles, filepath.Join(headerDir, "librecovery_headers.json"))
+
+		// For object modules, all vendor:true and vendor_available modules are captured.
+		objectVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
+		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found", jsonFile)
+		}
+	}
+}
+
 func TestDoubleLoadableDepError(t *testing.T) {
 	// Check whether an error is emitted when a LLNDK depends on a non-double_loadable VNDK lib.
 	testCcError(t, "module \".*\" variant \".*\": link.* \".*\" which is not LL-NDK, VNDK-SP, .*double_loadable", `
@@ -3882,11 +4010,11 @@
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
 	config.TestProductVariables.Debuggable = BoolPtr(true)
 
-	ctx := CreateTestContext()
+	ctx := CreateTestContext(config)
 	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("variable", android.VariableMutator).Parallel()
 	})
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
@@ -3917,9 +4045,9 @@
 	config := TestConfig(buildDir, android.Android, nil, bp, nil)
 	config.TestProductVariables.Allow_missing_dependencies = BoolPtr(true)
 
-	ctx := CreateTestContext()
+	ctx := CreateTestContext(config)
 	ctx.SetAllowMissingDependencies(true)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
diff --git a/cc/compiler.go b/cc/compiler.go
index 3c86d20..04ed80d 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -687,6 +687,9 @@
 	// list of shared libraries that provide headers for this binding.
 	Shared_libs []string `android:"arch_variant"`
 
+	// List of libraries which export include paths required for this module
+	Header_libs []string `android:"arch_variant,variant_prepend"`
+
 	// list of clang flags required to correctly interpret the headers.
 	Cflags []string `android:"arch_variant"`
 
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index d18ae25..563ce76 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -21,6 +21,8 @@
 	"android.hardware.automotive.occupant_awareness-ndk_platform",
 	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-ndk_platform",
+	"android.hardware.keymint-ndk_platform",
+	"android.hardware.keymint-unstable-ndk_platform",
 	"android.hardware.nfc@1.2",
 	"android.hardware.power-ndk_platform",
 	"android.hardware.rebootescrow-ndk_platform",
diff --git a/cc/fuzz.go b/cc/fuzz.go
index fe3c12b..dddfe94 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -278,14 +278,7 @@
 
 	if fuzz.Properties.Fuzz_config != nil {
 		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        android.WriteFile,
-			Description: "fuzzer infrastructure configuration",
-			Output:      configPath,
-			Args: map[string]string{
-				"content": fuzz.Properties.Fuzz_config.String(),
-			},
-		})
+		android.WriteFileRule(ctx, configPath, fuzz.Properties.Fuzz_config.String())
 		fuzz.config = configPath
 	}
 
diff --git a/cc/gen.go b/cc/gen.go
index ccc3d0e..134d6d9 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -75,7 +75,11 @@
 	cmd := rule.Command()
 
 	// Fix up #line markers to not use the sbox temporary directory
-	sedCmd := "sed -i.bak 's#__SBOX_OUT_DIR__#" + outDir.String() + "#'"
+	// android.SboxPathForOutput(outDir, outDir) returns the sbox placeholder for the out
+	// directory itself, without any filename appended.
+	// TODO(ccross): make this cmd.PathForOutput(outDir) instead.
+	sboxOutDir := android.SboxPathForOutput(outDir, outDir)
+	sedCmd := "sed -i.bak 's#" + sboxOutDir + "#" + outDir.String() + "#'"
 	rule.Command().Text(sedCmd).Input(outFile)
 	rule.Command().Text(sedCmd).Input(headerFile)
 
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index a366f76..fa0c6f2 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -22,9 +22,9 @@
 )
 
 func testGenruleContext(config android.Config) *android.TestContext {
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx
 }
@@ -66,14 +66,14 @@
 
 	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out_arm")
 	expected := []string{"foo"}
-	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
-		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Inputs.Strings())
+	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
+		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
 	}
 
 	gen = ctx.ModuleForTests("gen", "android_arm64_armv8-a").Output("out_arm64")
 	expected = []string{"bar"}
-	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
-		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Inputs.Strings())
+	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
+		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Implicits.Strings())
 	}
 }
 
@@ -108,10 +108,10 @@
 	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out")
 	expected := []string{"libboth.so", "libshared.so", "libstatic.a"}
 	var got []string
-	for _, input := range gen.Inputs {
+	for _, input := range gen.Implicits {
 		got = append(got, input.Base())
 	}
-	if !reflect.DeepEqual(expected, got) {
+	if !reflect.DeepEqual(expected, got[:len(expected)]) {
 		t.Errorf(`want inputs %v, got %v`, expected, got)
 	}
 }
diff --git a/cc/library.go b/cc/library.go
index eeddd90..2127c08 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -567,7 +567,7 @@
 		return ""
 	}
 	// Return NDK if the library is both NDK and LLNDK.
-	if ctx.isNdk() {
+	if ctx.isNdk(ctx.Config()) {
 		return "NDK"
 	}
 	if ctx.isLlndkPublic(ctx.Config()) {
@@ -1099,7 +1099,7 @@
 
 func getRefAbiDumpFile(ctx ModuleContext, vndkVersion, fileName string) android.Path {
 	// The logic must be consistent with classifySourceAbiDump.
-	isNdk := ctx.isNdk()
+	isNdk := ctx.isNdk(ctx.Config())
 	isLlndkOrVndk := ctx.isLlndkPublic(ctx.Config()) || (ctx.useVndk() && ctx.isVndk())
 
 	refAbiDumpTextFile := android.PathForVndkRefAbiDump(ctx, vndkVersion, fileName, isNdk, isLlndkOrVndk, false)
@@ -1153,7 +1153,7 @@
 			library.sAbiDiff = SourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
 				refAbiDumpFile, fileName, exportedHeaderFlags,
 				Bool(library.Properties.Header_abi_checker.Check_all_apis),
-				ctx.isLlndk(ctx.Config()), ctx.isNdk(), ctx.isVndkExt())
+				ctx.isLlndk(ctx.Config()), ctx.isNdk(ctx.Config()), ctx.isVndkExt())
 		}
 	}
 }
diff --git a/cc/linkable.go b/cc/linkable.go
index 60ab6df..0609b28 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -64,6 +64,10 @@
 	return libraryDependencyTag{Kind: staticLibraryDependency}
 }
 
+func HeaderDepTag() blueprint.DependencyTag {
+	return libraryDependencyTag{Kind: headerLibraryDependency}
+}
+
 type SharedLibraryInfo struct {
 	SharedLibrary           android.Path
 	UnstrippedSharedLibrary android.Path
diff --git a/cc/makevars.go b/cc/makevars.go
index dcfd6d8..bd8aab5 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -171,6 +171,7 @@
 	ctx.StrictRaw("SRC_HEADERS", strings.Join(includes, " "))
 	ctx.StrictRaw("SRC_SYSTEM_HEADERS", strings.Join(systemIncludes, " "))
 
+	ndkKnownLibs := *getNDKKnownLibs(ctx.Config())
 	sort.Strings(ndkKnownLibs)
 	ctx.Strict("NDK_KNOWN_LIBS", strings.Join(ndkKnownLibs, " "))
 
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 9097e7b..a5c43fe 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -45,8 +45,7 @@
 
 	ndkLibrarySuffix = ".ndk"
 
-	// Added as a variation dependency via depsMutator.
-	ndkKnownLibs = []string{}
+	ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
 	// protects ndkKnownLibs writes during parallel BeginMutator.
 	ndkKnownLibsLock sync.Mutex
 )
@@ -158,6 +157,12 @@
 	return true
 }
 
+func getNDKKnownLibs(config android.Config) *[]string {
+	return config.Once(ndkKnownLibsKey, func() interface{} {
+		return &[]string{}
+	}).(*[]string)
+}
+
 func (c *stubDecorator) compilerInit(ctx BaseModuleContext) {
 	c.baseCompiler.compilerInit(ctx)
 
@@ -168,12 +173,13 @@
 
 	ndkKnownLibsLock.Lock()
 	defer ndkKnownLibsLock.Unlock()
-	for _, lib := range ndkKnownLibs {
+	ndkKnownLibs := getNDKKnownLibs(ctx.Config())
+	for _, lib := range *ndkKnownLibs {
 		if lib == name {
 			return
 		}
 	}
-	ndkKnownLibs = append(ndkKnownLibs, name)
+	*ndkKnownLibs = append(*ndkKnownLibs, name)
 }
 
 func addStubLibraryCompilerFlags(flags Flags) Flags {
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 1f070a5..5bf334e 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -25,7 +25,7 @@
 
 func testPrebuilt(t *testing.T, bp string, fs map[string][]byte, handlers ...configCustomizer) *android.TestContext {
 	config := TestConfig(buildDir, android.Android, nil, bp, fs)
-	ctx := CreateTestContext()
+	ctx := CreateTestContext(config)
 
 	// Enable androidmk support.
 	// * Register the singleton
@@ -38,7 +38,7 @@
 		handler(config)
 	}
 
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
diff --git a/cc/sanitize.go b/cc/sanitize.go
index b1326d9..eb61525 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -847,7 +847,7 @@
 				return true
 			}
 
-			if p, ok := d.linker.(*vendorSnapshotLibraryDecorator); ok {
+			if p, ok := d.linker.(*snapshotLibraryDecorator); ok {
 				if Bool(p.properties.Sanitize_minimal_dep) {
 					c.sanitize.Properties.MinimalRuntimeDep = true
 				}
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index 238508d..a3d52e6 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -60,7 +60,8 @@
 func isSnapshotAware(ctx android.ModuleContext, m *Module, apexInfo android.ApexInfo) bool {
 	if _, _, ok := isVndkSnapshotLibrary(ctx.DeviceConfig(), m, apexInfo); ok {
 		return ctx.Config().VndkSnapshotBuildArtifacts()
-	} else if isVendorSnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) {
+	} else if isVendorSnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) ||
+		isRecoverySnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) {
 		return true
 	}
 	return false
@@ -93,13 +94,6 @@
 
 func writeStringToFile(ctx android.SingletonContext, content, out string) android.OutputPath {
 	outPath := android.PathForOutput(ctx, out)
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Output:      outPath,
-		Description: "WriteFile " + out,
-		Args: map[string]string{
-			"content": content,
-		},
-	})
+	android.WriteFileRule(ctx, outPath, content)
 	return outPath
 }
diff --git a/cc/test.go b/cc/test.go
index 619dc4d..3772691 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -46,6 +46,9 @@
 
 	// a list of extra test configuration files that should be installed with the module.
 	Extra_test_configs []string `android:"path,arch_variant"`
+
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
 }
 
 type TestBinaryProperties struct {
diff --git a/cc/test_data_test.go b/cc/test_data_test.go
index ae59e2f..426dfc5 100644
--- a/cc/test_data_test.go
+++ b/cc/test_data_test.go
@@ -122,10 +122,10 @@
 				"dir/baz":        nil,
 				"dir/bar/baz":    nil,
 			})
-			ctx := android.NewTestContext()
+			ctx := android.NewTestContext(config)
 			ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 			ctx.RegisterModuleType("test", newTest)
-			ctx.Register(config)
+			ctx.Register()
 
 			_, errs := ctx.ParseBlueprintsFiles("Blueprints")
 			android.FailIfErrored(t, errs)
diff --git a/cc/testing.go b/cc/testing.go
index 388a9ce..7161313 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -545,8 +545,8 @@
 	return config
 }
 
-func CreateTestContext() *android.TestContext {
-	ctx := android.NewTestArchContext()
+func CreateTestContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("cc_fuzz", FuzzFactory)
 	ctx.RegisterModuleType("cc_test", TestFactory)
 	ctx.RegisterModuleType("cc_test_library", TestLibraryFactory)
@@ -563,6 +563,7 @@
 	RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
+	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
 
 	return ctx
 }
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 78bde38..6563f6e 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -25,6 +25,115 @@
 	"android/soong/android"
 )
 
+// Defines the specifics of different images to which the snapshot process is
+// applicable, e.g., vendor, recovery, ramdisk.
+type image interface {
+	// Used to register callbacks with the build system.
+	init()
+
+	// Function that returns true if the module is included in this image.
+	// Using a function return instead of a value to prevent early
+	// evalution of a function that may be not be defined.
+	inImage(m *Module) func() bool
+
+	// Returns the value of the "available" property for a given module for
+	// and snapshot, e.g., "vendor_available", "recovery_available", etc.
+	// or nil if the property is not defined.
+	available(m *Module) *bool
+
+	// Returns true if a dir under source tree is an SoC-owned proprietary
+	// directory, such as device/, vendor/, etc.
+	//
+	// For a given snapshot (e.g., vendor, recovery, etc.) if
+	// isProprietaryPath(dir) returns true, then the module in dir will be
+	// built from sources.
+	isProprietaryPath(dir string) bool
+
+	// Whether to include VNDK in the snapshot for this image.
+	includeVndk() bool
+
+	// Whether a given module has been explicitly excluded from the
+	// snapshot, e.g., using the exclude_from_vendor_snapshot or
+	// exclude_from_recovery_snapshot properties.
+	excludeFromSnapshot(m *Module) bool
+}
+
+type vendorImage struct{}
+type recoveryImage struct{}
+
+func (vendorImage) init() {
+	android.RegisterSingletonType(
+		"vendor-snapshot", VendorSnapshotSingleton)
+	android.RegisterModuleType(
+		"vendor_snapshot_shared", VendorSnapshotSharedFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_static", VendorSnapshotStaticFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_header", VendorSnapshotHeaderFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_binary", VendorSnapshotBinaryFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_object", VendorSnapshotObjectFactory)
+}
+
+func (vendorImage) inImage(m *Module) func() bool {
+	return m.inVendor
+}
+
+func (vendorImage) available(m *Module) *bool {
+	return m.VendorProperties.Vendor_available
+}
+
+func (vendorImage) isProprietaryPath(dir string) bool {
+	return isVendorProprietaryPath(dir)
+}
+
+func (vendorImage) includeVndk() bool {
+	return true
+}
+
+func (vendorImage) excludeFromSnapshot(m *Module) bool {
+	return m.ExcludeFromVendorSnapshot()
+}
+
+func (recoveryImage) init() {
+	android.RegisterSingletonType(
+		"recovery-snapshot", RecoverySnapshotSingleton)
+	android.RegisterModuleType(
+		"recovery_snapshot_shared", RecoverySnapshotSharedFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_static", RecoverySnapshotStaticFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_header", RecoverySnapshotHeaderFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_binary", RecoverySnapshotBinaryFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_object", RecoverySnapshotObjectFactory)
+}
+
+func (recoveryImage) inImage(m *Module) func() bool {
+	return m.InRecovery
+}
+
+func (recoveryImage) available(m *Module) *bool {
+	return m.Properties.Recovery_available
+}
+
+func (recoveryImage) isProprietaryPath(dir string) bool {
+	return isRecoveryProprietaryPath(dir)
+}
+
+func (recoveryImage) includeVndk() bool {
+	return false
+}
+
+func (recoveryImage) excludeFromSnapshot(m *Module) bool {
+	return m.ExcludeFromRecoverySnapshot()
+}
+
+var vendorImageSingleton vendorImage
+var recoveryImageSingleton recoveryImage
+
 const (
 	vendorSnapshotHeaderSuffix = ".vendor_header."
 	vendorSnapshotSharedSuffix = ".vendor_shared."
@@ -33,6 +142,14 @@
 	vendorSnapshotObjectSuffix = ".vendor_object."
 )
 
+const (
+	recoverySnapshotHeaderSuffix = ".recovery_header."
+	recoverySnapshotSharedSuffix = ".recovery_shared."
+	recoverySnapshotStaticSuffix = ".recovery_static."
+	recoverySnapshotBinarySuffix = ".recovery_binary."
+	recoverySnapshotObjectSuffix = ".recovery_object."
+)
+
 var (
 	vendorSnapshotsLock         sync.Mutex
 	vendorSuffixModulesKey      = android.NewOnceKey("vendorSuffixModules")
@@ -136,7 +253,7 @@
 	}
 }
 
-type vendorSnapshotLibraryProperties struct {
+type snapshotLibraryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 
@@ -161,25 +278,25 @@
 	setSanitizerVariation(t sanitizerType, enabled bool)
 }
 
-type vendorSnapshotLibraryDecorator struct {
+type snapshotLibraryDecorator struct {
 	vendorSnapshotModuleBase
 	*libraryDecorator
-	properties          vendorSnapshotLibraryProperties
+	properties          snapshotLibraryProperties
 	sanitizerProperties struct {
 		CfiEnabled bool `blueprint:"mutated"`
 
 		// Library flags for cfi variant.
-		Cfi vendorSnapshotLibraryProperties `android:"arch_variant"`
+		Cfi snapshotLibraryProperties `android:"arch_variant"`
 	}
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotLibraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+func (p *snapshotLibraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	p.libraryDecorator.libName = strings.TrimSuffix(ctx.ModuleName(), p.NameSuffix())
 	return p.libraryDecorator.linkerFlags(ctx, flags)
 }
 
-func (p *vendorSnapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
 	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
 		return false
@@ -190,7 +307,7 @@
 	return true
 }
 
-func (p *vendorSnapshotLibraryDecorator) link(ctx ModuleContext,
+func (p *snapshotLibraryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	m := ctx.Module().(*Module)
 	p.androidMkVendorSuffix = vendorSuffixModules(ctx.Config())[m.BaseModuleName()]
@@ -246,17 +363,17 @@
 	return in
 }
 
-func (p *vendorSnapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
+func (p *snapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
 	if p.matchesWithDevice(ctx.DeviceConfig()) && (p.shared() || p.static()) {
 		p.baseInstaller.install(ctx, file)
 	}
 }
 
-func (p *vendorSnapshotLibraryDecorator) nativeCoverage() bool {
+func (p *snapshotLibraryDecorator) nativeCoverage() bool {
 	return false
 }
 
-func (p *vendorSnapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
+func (p *snapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
 	switch t {
 	case cfi:
 		return p.sanitizerProperties.Cfi.Src != nil
@@ -265,7 +382,7 @@
 	}
 }
 
-func (p *vendorSnapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
+func (p *snapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
 	if !enabled {
 		return
 	}
@@ -277,14 +394,14 @@
 	}
 }
 
-func vendorSnapshotLibrary(suffix string) (*Module, *vendorSnapshotLibraryDecorator) {
+func snapshotLibrary(suffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
 	module.sanitize = nil
 	library.disableStripping()
 
-	prebuilt := &vendorSnapshotLibraryDecorator{
+	prebuilt := &snapshotLibraryDecorator{
 		libraryDecorator: library,
 	}
 
@@ -310,38 +427,56 @@
 }
 
 func VendorSnapshotSharedFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotSharedSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotSharedSuffix)
+	prebuilt.libraryDecorator.BuildOnlyShared()
+	return module.Init()
+}
+
+func RecoverySnapshotSharedFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
 
 func VendorSnapshotStaticFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotStaticSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotStaticSuffix)
+	prebuilt.libraryDecorator.BuildOnlyStatic()
+	return module.Init()
+}
+
+func RecoverySnapshotStaticFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
 
 func VendorSnapshotHeaderFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotHeaderSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
 
-var _ snapshotSanitizer = (*vendorSnapshotLibraryDecorator)(nil)
+func RecoverySnapshotHeaderFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotHeaderSuffix)
+	prebuilt.libraryDecorator.HeaderOnly()
+	return module.Init()
+}
 
-type vendorSnapshotBinaryProperties struct {
+var _ snapshotSanitizer = (*snapshotLibraryDecorator)(nil)
+
+type snapshotBinaryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 }
 
-type vendorSnapshotBinaryDecorator struct {
+type snapshotBinaryDecorator struct {
 	vendorSnapshotModuleBase
 	*binaryDecorator
-	properties            vendorSnapshotBinaryProperties
+	properties            snapshotBinaryProperties
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
 	if config.DeviceArch() != p.arch() {
 		return false
 	}
@@ -351,7 +486,7 @@
 	return true
 }
 
-func (p *vendorSnapshotBinaryDecorator) link(ctx ModuleContext,
+func (p *snapshotBinaryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
@@ -382,11 +517,19 @@
 	return outputFile
 }
 
-func (p *vendorSnapshotBinaryDecorator) nativeCoverage() bool {
+func (p *snapshotBinaryDecorator) nativeCoverage() bool {
 	return false
 }
 
 func VendorSnapshotBinaryFactory() android.Module {
+	return snapshotBinaryFactory(vendorSnapshotBinarySuffix)
+}
+
+func RecoverySnapshotBinaryFactory() android.Module {
+	return snapshotBinaryFactory(recoverySnapshotBinarySuffix)
+}
+
+func snapshotBinaryFactory(suffix string) android.Module {
 	module, binary := NewBinary(android.DeviceSupported)
 	binary.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	binary.baseLinker.Properties.Nocrt = BoolPtr(true)
@@ -396,7 +539,7 @@
 		binary.baseLinker.Properties.System_shared_libs = []string{}
 	}
 
-	prebuilt := &vendorSnapshotBinaryDecorator{
+	prebuilt := &snapshotBinaryDecorator{
 		binaryDecorator: binary,
 	}
 
@@ -405,7 +548,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotBinarySuffix)
+	prebuilt.init(module, suffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -415,14 +558,14 @@
 	Src *string `android:"arch_variant"`
 }
 
-type vendorSnapshotObjectLinker struct {
+type snapshotObjectLinker struct {
 	vendorSnapshotModuleBase
 	objectLinker
 	properties            vendorSnapshotObjectProperties
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
 	if config.DeviceArch() != p.arch() {
 		return false
 	}
@@ -432,7 +575,7 @@
 	return true
 }
 
-func (p *vendorSnapshotObjectLinker) link(ctx ModuleContext,
+func (p *snapshotObjectLinker) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
@@ -444,14 +587,14 @@
 	return android.PathForModuleSrc(ctx, *p.properties.Src)
 }
 
-func (p *vendorSnapshotObjectLinker) nativeCoverage() bool {
+func (p *snapshotObjectLinker) nativeCoverage() bool {
 	return false
 }
 
 func VendorSnapshotObjectFactory() android.Module {
 	module := newObject()
 
-	prebuilt := &vendorSnapshotObjectLinker{
+	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
 			baseLinker: NewBaseLinker(nil),
 		},
@@ -463,21 +606,68 @@
 	return module.Init()
 }
 
+func RecoverySnapshotObjectFactory() android.Module {
+	module := newObject()
+
+	prebuilt := &snapshotObjectLinker{
+		objectLinker: objectLinker{
+			baseLinker: NewBaseLinker(nil),
+		},
+	}
+	module.linker = prebuilt
+
+	prebuilt.init(module, recoverySnapshotObjectSuffix)
+	module.AddProperties(&prebuilt.properties)
+	return module.Init()
+}
+
 func init() {
-	android.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
-	android.RegisterModuleType("vendor_snapshot_shared", VendorSnapshotSharedFactory)
-	android.RegisterModuleType("vendor_snapshot_static", VendorSnapshotStaticFactory)
-	android.RegisterModuleType("vendor_snapshot_header", VendorSnapshotHeaderFactory)
-	android.RegisterModuleType("vendor_snapshot_binary", VendorSnapshotBinaryFactory)
-	android.RegisterModuleType("vendor_snapshot_object", VendorSnapshotObjectFactory)
+	vendorImageSingleton.init()
+	recoveryImageSingleton.init()
+}
+
+var vendorSnapshotSingleton = snapshotSingleton{
+	"vendor",
+	"SOONG_VENDOR_SNAPSHOT_ZIP",
+	android.OptionalPath{},
+	true,
+	vendorImageSingleton,
+}
+
+var recoverySnapshotSingleton = snapshotSingleton{
+	"recovery",
+	"SOONG_RECOVERY_SNAPSHOT_ZIP",
+	android.OptionalPath{},
+	false,
+	recoveryImageSingleton,
 }
 
 func VendorSnapshotSingleton() android.Singleton {
-	return &vendorSnapshotSingleton{}
+	return &vendorSnapshotSingleton
 }
 
-type vendorSnapshotSingleton struct {
-	vendorSnapshotZipFile android.OptionalPath
+func RecoverySnapshotSingleton() android.Singleton {
+	return &recoverySnapshotSingleton
+}
+
+type snapshotSingleton struct {
+	// Name, e.g., "vendor", "recovery", "ramdisk".
+	name string
+
+	// Make variable that points to the snapshot file, e.g.,
+	// "SOONG_RECOVERY_SNAPSHOT_ZIP".
+	makeVar string
+
+	// Path to the snapshot zip file.
+	snapshotZipFile android.OptionalPath
+
+	// Whether the image supports VNDK extension modules.
+	supportsVndkExt bool
+
+	// Implementation of the image interface specific to the image
+	// associated with this snapshot (e.g., specific to the vendor image,
+	// recovery image, etc.).
+	image image
 }
 
 var (
@@ -491,6 +681,17 @@
 		"hardware",
 	}
 
+	// Modules under following directories are ignored. They are OEM's and vendor's
+	// proprietary modules(device/, kernel/, vendor/, and hardware/).
+	// TODO(b/65377115): Clean up these with more maintainable way
+	recoveryProprietaryDirs = []string{
+		"bootable/recovery",
+		"device",
+		"hardware",
+		"kernel",
+		"vendor",
+	}
+
 	// Modules under following directories are included as they are in AOSP,
 	// although hardware/ and kernel/ are normally for vendor's own.
 	// TODO(b/65377115): Clean up these with more maintainable way
@@ -508,7 +709,17 @@
 // Determine if a dir under source tree is an SoC-owned proprietary directory, such as
 // device/, vendor/, etc.
 func isVendorProprietaryPath(dir string) bool {
-	for _, p := range vendorProprietaryDirs {
+	return isProprietaryPath(dir, vendorProprietaryDirs)
+}
+
+func isRecoveryProprietaryPath(dir string) bool {
+	return isProprietaryPath(dir, recoveryProprietaryDirs)
+}
+
+// Determine if a dir under source tree is an SoC-owned proprietary directory, such as
+// device/, vendor/, etc.
+func isProprietaryPath(dir string, proprietaryDirs []string) bool {
+	for _, p := range proprietaryDirs {
 		if strings.HasPrefix(dir, p) {
 			// filter out AOSP defined directories, e.g. hardware/interfaces/
 			aosp := false
@@ -556,6 +767,14 @@
 // depend on newer VNDK) So they are captured as vendor snapshot To build older vendor
 // image and newer system image altogether.
 func isVendorSnapshotModule(m *Module, inVendorProprietaryPath bool, apexInfo android.ApexInfo) bool {
+	return isSnapshotModule(m, inVendorProprietaryPath, apexInfo, vendorImageSingleton)
+}
+
+func isRecoverySnapshotModule(m *Module, inRecoveryProprietaryPath bool, apexInfo android.ApexInfo) bool {
+	return isSnapshotModule(m, inRecoveryProprietaryPath, apexInfo, recoveryImageSingleton)
+}
+
+func isSnapshotModule(m *Module, inProprietaryPath bool, apexInfo android.ApexInfo, image image) bool {
 	if !m.Enabled() || m.Properties.HideFromMake {
 		return false
 	}
@@ -564,8 +783,9 @@
 	if m.IsSkipInstall() {
 		return false
 	}
-	// skip proprietary modules, but include all VNDK (static)
-	if inVendorProprietaryPath && !m.IsVndk() {
+	// skip proprietary modules, but (for the vendor snapshot only)
+	// include all VNDK (static)
+	if inProprietaryPath && (!image.includeVndk() || !m.IsVndk()) {
 		return false
 	}
 	// If the module would be included based on its path, check to see if
@@ -580,7 +800,7 @@
 		return false
 	}
 	// the module must be installed in /vendor
-	if !apexInfo.IsForPlatform() || m.isSnapshotPrebuilt() || !m.inVendor() {
+	if !apexInfo.IsForPlatform() || m.isSnapshotPrebuilt() || !image.inImage(m)() {
 		return false
 	}
 	// skip kernel_headers which always depend on vendor
@@ -612,29 +832,31 @@
 			}
 		}
 		if l.static() {
-			return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
+			return m.outputFile.Valid() && proptools.BoolDefault(image.available(m), true)
 		}
 		if l.shared() {
 			if !m.outputFile.Valid() {
 				return false
 			}
-			if !m.IsVndk() {
-				return true
+			if image.includeVndk() {
+				if !m.IsVndk() {
+					return true
+				}
+				return m.isVndkExt()
 			}
-			return m.isVndkExt()
 		}
 		return true
 	}
 
 	// Binaries and Objects
 	if m.binary() || m.object() {
-		return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
+		return m.outputFile.Valid() && proptools.BoolDefault(image.available(m), true)
 	}
 
 	return false
 }
 
-func (c *vendorSnapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+func (c *snapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	// BOARD_VNDK_VERSION must be set to 'current' in order to generate a vendor snapshot.
 	if ctx.DeviceConfig().VndkVersion() != "current" {
 		return
@@ -675,7 +897,7 @@
 				(header files of same directory structure with source tree)
 	*/
 
-	snapshotDir := "vendor-snapshot"
+	snapshotDir := c.name + "-snapshot"
 	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
 
 	includeDir := filepath.Join(snapshotArchDir, "include")
@@ -722,7 +944,7 @@
 
 		// Common properties among snapshots.
 		prop.ModuleName = ctx.ModuleName(m)
-		if m.isVndkExt() {
+		if c.supportsVndkExt && m.isVndkExt() {
 			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
 			if m.isVndkSp() {
 				prop.RelativeInstallPath = "vndk-sp"
@@ -843,26 +1065,30 @@
 		}
 
 		moduleDir := ctx.ModuleDir(module)
-		inVendorProprietaryPath := isVendorProprietaryPath(moduleDir)
+		inProprietaryPath := c.image.isProprietaryPath(moduleDir)
 		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
 		if m.ExcludeFromVendorSnapshot() {
-			if inVendorProprietaryPath {
+			if inProprietaryPath {
 				// Error: exclude_from_vendor_snapshot applies
 				// to framework-path modules only.
 				ctx.Errorf("module %q in vendor proprietary path %q may not use \"exclude_from_vendor_snapshot: true\"", m.String(), moduleDir)
 				return
 			}
-			if Bool(m.VendorProperties.Vendor_available) {
+			if Bool(c.image.available(m)) {
 				// Error: may not combine "vendor_available:
 				// true" with "exclude_from_vendor_snapshot:
 				// true".
-				ctx.Errorf("module %q may not use both \"vendor_available: true\" and \"exclude_from_vendor_snapshot: true\"", m.String())
+				ctx.Errorf(
+					"module %q may not use both \""+
+						c.name+
+						"_available: true\" and \"exclude_from_vendor_snapshot: true\"",
+					m.String())
 				return
 			}
 		}
 
-		if !isVendorSnapshotModule(m, inVendorProprietaryPath, apexInfo) {
+		if !isSnapshotModule(m, inProprietaryPath, apexInfo, c.image) {
 			return
 		}
 
@@ -894,11 +1120,17 @@
 		return snapshotOutputs[i].String() < snapshotOutputs[j].String()
 	})
 
-	zipPath := android.PathForOutput(ctx, snapshotDir, "vendor-"+ctx.Config().DeviceName()+".zip")
+	zipPath := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+".zip")
 	zipRule := android.NewRuleBuilder()
 
 	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
-	snapshotOutputList := android.PathForOutput(ctx, snapshotDir, "vendor-"+ctx.Config().DeviceName()+"_list")
+	snapshotOutputList := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+"_list")
 	zipRule.Command().
 		Text("tr").
 		FlagWithArg("-d ", "\\'").
@@ -913,13 +1145,15 @@
 		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
 		FlagWithInput("-l ", snapshotOutputList)
 
-	zipRule.Build(pctx, ctx, zipPath.String(), "vendor snapshot "+zipPath.String())
+	zipRule.Build(pctx, ctx, zipPath.String(), c.name+" snapshot "+zipPath.String())
 	zipRule.DeleteTemporaryFiles()
-	c.vendorSnapshotZipFile = android.OptionalPathForPath(zipPath)
+	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
 }
 
-func (c *vendorSnapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.Strict("SOONG_VENDOR_SNAPSHOT_ZIP", c.vendorSnapshotZipFile.String())
+func (c *snapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict(
+		c.makeVar,
+		c.snapshotZipFile.String())
 }
 
 type snapshotInterface interface {
@@ -927,9 +1161,9 @@
 }
 
 var _ snapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotLibraryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotBinaryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotObjectLinker)(nil)
+var _ snapshotInterface = (*snapshotLibraryDecorator)(nil)
+var _ snapshotInterface = (*snapshotBinaryDecorator)(nil)
+var _ snapshotInterface = (*snapshotObjectLinker)(nil)
 
 // gathers all snapshot modules for vendor, and disable unnecessary snapshots
 // TODO(b/145966707): remove mutator and utilize android.Prebuilt to override source modules
@@ -970,9 +1204,9 @@
 			// header
 			snapshotMap = vendorSnapshotHeaderLibs(ctx.Config())
 		}
-	} else if _, ok := module.linker.(*vendorSnapshotBinaryDecorator); ok {
+	} else if _, ok := module.linker.(*snapshotBinaryDecorator); ok {
 		snapshotMap = vendorSnapshotBinaries(ctx.Config())
-	} else if _, ok := module.linker.(*vendorSnapshotObjectLinker); ok {
+	} else if _, ok := module.linker.(*snapshotObjectLinker); ok {
 		snapshotMap = vendorSnapshotObjects(ctx.Config())
 	} else {
 		return
diff --git a/cc/vndk.go b/cc/vndk.go
index 2cac03c..4a005f3 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -487,14 +487,7 @@
 	}
 
 	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Output:      txt.outputFile,
-		Description: "Writing " + txt.outputFile.String(),
-		Args: map[string]string{
-			"content": strings.Join(list, "\\n"),
-		},
-	})
+	android.WriteFileRule(ctx, txt.outputFile, strings.Join(list, "\n"))
 
 	installPath := android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(installPath, filename, txt.outputFile)
@@ -825,14 +818,7 @@
 	merged = append(merged, addPrefix(filterOutLibClangRt(vndkcore), "VNDK-core: ")...)
 	merged = append(merged, addPrefix(vndkprivate, "VNDK-private: ")...)
 	c.vndkLibrariesFile = android.PathForOutput(ctx, "vndk", "vndk.libraries.txt")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Output:      c.vndkLibrariesFile,
-		Description: "Writing " + c.vndkLibrariesFile.String(),
-		Args: map[string]string{
-			"content": strings.Join(merged, "\\n"),
-		},
-	})
+	android.WriteFileRule(ctx, c.vndkLibrariesFile, strings.Join(merged, "\n"))
 }
 
 func (c *vndkSnapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 9d7dddd..b88803a 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -52,7 +52,7 @@
 }
 
 func newContext(srcDir string, configuration android.Config) *android.Context {
-	ctx := android.NewContext()
+	ctx := android.NewContext(configuration)
 	ctx.Register()
 	if !shouldPrepareBuildActions() {
 		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
@@ -95,6 +95,7 @@
 		// 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.
 		firstCtx := newContext(srcDir, configuration)
+		configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
 		bootstrap.Main(firstCtx.Context, configuration, extraNinjaDeps...)
 		// Invoke bazel commands and save results for second pass.
 		if err := configuration.BazelContext.InvokeBazel(); err != nil {
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
index 675b532..525802a 100644
--- a/cmd/soong_build/queryview_test.go
+++ b/cmd/soong_build/queryview_test.go
@@ -234,9 +234,9 @@
 
 	for _, testCase := range testCases {
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext()
+		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register(config)
+		ctx.Register()
 
 		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 		android.FailIfErrored(t, errs)
diff --git a/cmd/soong_env/soong_env.go b/cmd/soong_env/soong_env.go
index d305d83..8020b17 100644
--- a/cmd/soong_env/soong_env.go
+++ b/cmd/soong_env/soong_env.go
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// soong_glob is the command line tool that checks if the list of files matching a glob has
-// changed, and only updates the output file list if it has changed.  It is used to optimize
-// out build.ninja regenerations when non-matching files are added.  See
-// android/soong/android/glob.go for a longer description.
+// soong_env determines if the given soong environment file (usually ".soong.environment") is stale
+// by comparing its contents to the current corresponding environment variable values.
+// It fails if the file cannot be opened or corrupted, or its contents differ from the current
+// values.
+
 package main
 
 import (
@@ -34,6 +35,7 @@
 	os.Exit(2)
 }
 
+// This is a simple executable packaging, and the real work happens in env.StaleEnvFile.
 func main() {
 	flag.Parse()
 
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index e778bd6..29030d6 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -125,27 +125,35 @@
 		os.Exit(1)
 	}
 
+	// Create a terminal output that mimics Ninja's.
 	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput,
 		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
 
+	// Attach a new logger instance to the terminal output.
 	log := logger.New(output)
 	defer log.Cleanup()
 
+	// Create a context to simplify the program termination process.
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
+	// Create a new trace file writer, making it log events to the log instance.
 	trace := tracer.New(log)
 	defer trace.Close()
 
+	// Create and start a new metric record.
 	met := metrics.New()
 	met.SetBuildDateTime(buildStarted)
 	met.SetBuildCommand(os.Args)
 
+	// Create a new Status instance, which manages action counts and event output channels.
 	stat := &status.Status{}
 	defer stat.Finish()
+	// Hook up the terminal output and tracer to Status.
 	stat.AddOutput(output)
 	stat.AddOutput(trace.StatusTracer())
 
+	// Set up a cleanup procedure in case the normal termination process doesn't work.
 	build.SetupSignals(log, cancel, func() {
 		trace.Close()
 		log.Cleanup()
@@ -165,6 +173,7 @@
 
 	build.SetupOutDir(buildCtx, config)
 
+	// Set up files to be outputted in the log directory.
 	logsDir := config.OutDir()
 	if config.Dist() {
 		logsDir = filepath.Join(config.DistDir(), "logs")
@@ -192,7 +201,10 @@
 	defer met.Dump(soongMetricsFile)
 	defer build.DumpRBEMetrics(buildCtx, config, rbeMetricsFile)
 
+	// Read the time at the starting point.
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
+		// soong_ui.bash uses the date command's %N (nanosec) flag when getting the start time,
+		// which Darwin doesn't support. Check if it was executed properly before parsing the value.
 		if !strings.HasSuffix(start, "N") {
 			if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
 				log.Verbosef("Took %dms to start up.",
@@ -211,6 +223,7 @@
 	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
 	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
 
+	// Create a source finder.
 	f := build.NewSourceFinder(buildCtx, config)
 	defer f.Shutdown()
 	build.FindSources(buildCtx, config, f)
@@ -354,6 +367,8 @@
 	return terminal.StdioImpl{}
 }
 
+// dumpvar and dumpvars use stdout to output variable values, so use stderr instead of stdout when
+// reporting events to keep stdout clean from noise.
 func customStdio() terminal.StdioInterface {
 	return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
 }
@@ -471,11 +486,9 @@
 		ctx.Fatal("done")
 	}
 
-	var toBuild int
-	if _, ok := config.Environment().Get("USE_BAZEL"); ok {
+	toBuild := build.BuildAll
+	if config.UseBazel() {
 		toBuild = build.BuildAllWithBazel
-	} else {
-		toBuild = build.BuildAll
 	}
 
 	if config.Checkbuild() {
@@ -495,16 +508,6 @@
 		if c.flag == args[1] {
 			return &c, args[2:], nil
 		}
-
-		// special case for --make-mode: if soong_ui was called from
-		// build/make/core/main.mk, the makeparallel with --ninja
-		// option specified puts the -j<num> before --make-mode.
-		// TODO: Remove this hack once it has been fixed.
-		if c.flag == makeModeFlagName {
-			if inList(makeModeFlagName, args) {
-				return &c, args[1:], nil
-			}
-		}
 	}
 
 	// command not found
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 6d77812..b910a70 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -16,7 +16,7 @@
 
 import (
 	"fmt"
-	"path/filepath"
+	"strconv"
 	"strings"
 
 	"android/soong/android"
@@ -49,39 +49,43 @@
 
 const UnknownInstallLibraryPath = "error"
 
-const AnySdkVersion int = 9999 // should go last in class loader context
+// AnySdkVersion means that the class loader context is needed regardless of the targetSdkVersion
+// of the app. The numeric value affects the key order in the map and, as a result, the order of
+// arguments passed to construct_context.py (high value means that the unconditional context goes
+// last). We use the converntional "current" SDK level (10000), but any big number would do as well.
+const AnySdkVersion int = android.FutureApiLevelInt
 
-// LibraryPath contains paths to the library DEX jar on host and on device.
-type LibraryPath struct {
-	Host   android.Path
+// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
+// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
+// that it can be checked agains the run-time class loader context on device. If there is a mismatch
+// at runtime, AOT-compiled code is rejected.
+type ClassLoaderContext struct {
+	// The name of the library (same as the name of the module that contains it).
+	Name string
+
+	// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
+	Host android.Path
+
+	// On-device install path (used in dex2oat argument --stored-class-loader-context).
 	Device string
+
+	// Nested class loader subcontexts for dependencies.
+	Subcontexts []*ClassLoaderContext
+
+	// If the library is a shared library. This affects which elements of class loader context are
+	// added as <uses-library> tags by the manifest_fixer (dependencies of shared libraries aren't).
+	IsSharedLibrary bool
 }
 
-// LibraryPaths is a map from library name to on-host and on-device paths to its DEX jar.
-type LibraryPaths map[string]*LibraryPath
+// ClassLoaderContextMap is a map from SDK version to a class loader context.
+// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
+// Other entries store conditional contexts that should be added for some apps that have
+// targetSdkVersion in the manifest lower than the key SDK version.
+type ClassLoaderContextMap map[int][]*ClassLoaderContext
 
-type classLoaderContext struct {
-	// Library names
-	Names []string
-
-	// The class loader context using paths in the build.
-	Host android.Paths
-
-	// The class loader context using paths as they will be on the device.
-	Target []string
-}
-
-// A map of class loader contexts for each SDK version.
-// A map entry for "any" version contains libraries that are unconditionally added to class loader
-// context. Map entries for existing versions contains libraries that were in the default classpath
-// until that API version, and should be added to class loader context if and only if the
-// targetSdkVersion in the manifest or APK is less than that API version.
-type classLoaderContextMap map[int]*classLoaderContext
-
-// Add a new library path to the map, unless a path for this library already exists.
-// If necessary, check that the build and install paths exist.
-func (libPaths LibraryPaths) addLibraryPath(ctx android.ModuleInstallPathContext, lib string,
-	hostPath, installPath android.Path, strict bool) error {
+// Add class loader context for the given library to the map entry for the given SDK version.
+func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
+	shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
 
 	// If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
 	// not found. However, this is likely to result is disabling dexpreopt, as it won't be
@@ -89,263 +93,284 @@
 	strict = strict && !ctx.Config().AllowMissingDependencies()
 
 	if hostPath == nil && strict {
-		return fmt.Errorf("unknown build path to <uses-library> '%s'", lib)
+		return fmt.Errorf("unknown build path to <uses-library> \"%s\"", lib)
 	}
 
+	devicePath := UnknownInstallLibraryPath
 	if installPath == nil {
 		if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) {
 			// Assume that compatibility libraries are installed in /system/framework.
 			installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar")
 		} else if strict {
-			return fmt.Errorf("unknown install path to <uses-library> '%s'", lib)
-		}
-	}
-
-	// Add a library only if the build and install path to it is known.
-	if _, present := libPaths[lib]; !present {
-		var devicePath string
-		if installPath != nil {
-			devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
+			return fmt.Errorf("unknown install path to <uses-library> \"%s\"", lib)
 		} else {
 			// For some stub libraries the only known thing is the name of their implementation
 			// library, but the library itself is unavailable (missing or part of a prebuilt). In
 			// such cases we still need to add the library to <uses-library> tags in the manifest,
-			// but we cannot use if for dexpreopt.
-			devicePath = UnknownInstallLibraryPath
+			// but we cannot use it for dexpreopt.
 		}
-		libPaths[lib] = &LibraryPath{hostPath, devicePath}
 	}
+	if installPath != nil {
+		devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
+	}
+
+	// Nested class loader context shouldn't have conditional part (it is allowed only at the top level).
+	for ver, _ := range nestedClcMap {
+		if ver != AnySdkVersion {
+			clcStr, _ := ComputeClassLoaderContext(nestedClcMap)
+			return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr)
+		}
+	}
+	subcontexts := nestedClcMap[AnySdkVersion]
+
+	// If the library with this name is already present as one of the unconditional top-level
+	// components, do not re-add it.
+	for _, clc := range clcMap[sdkVer] {
+		if clc.Name == lib {
+			return nil
+		}
+	}
+
+	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
+		Name:            lib,
+		Host:            hostPath,
+		Device:          devicePath,
+		Subcontexts:     subcontexts,
+		IsSharedLibrary: shared,
+	})
 	return nil
 }
 
-// Wrapper around addLibraryPath that does error reporting.
-func (libPaths LibraryPaths) addLibraryPathOrReportError(ctx android.ModuleInstallPathContext, lib string,
-	hostPath, installPath android.Path, strict bool) {
+// Wrapper around addContext that reports errors.
+func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
+	shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
 
-	err := libPaths.addLibraryPath(ctx, lib, hostPath, installPath, strict)
+	err := clcMap.addContext(ctx, sdkVer, lib, shared, hostPath, installPath, strict, nestedClcMap)
 	if err != nil {
-		android.ReportPathErrorf(ctx, err.Error())
+		ctx.ModuleErrorf(err.Error())
 	}
 }
 
-// Add a new library path to the map. Enforce checks that the library paths exist.
-func (libPaths LibraryPaths) AddLibraryPath(ctx android.ModuleInstallPathContext, lib string, hostPath, installPath android.Path) {
-	libPaths.addLibraryPathOrReportError(ctx, lib, hostPath, installPath, true)
+// Add class loader context. Fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
+	shared bool, hostPath, installPath android.Path) {
+
+	clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, shared, hostPath, installPath, true, nil)
 }
 
-// Add a new library path to the map, if the library exists (name is not nil).
-// Don't enforce checks that the library paths exist. Some libraries may be missing from the build,
-// but their names still need to be added to <uses-library> tags in the manifest.
-func (libPaths LibraryPaths) MaybeAddLibraryPath(ctx android.ModuleInstallPathContext, lib *string, hostPath, installPath android.Path) {
+// Add class loader context if the library exists. Don't fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
+	shared bool, hostPath, installPath android.Path) {
+
 	if lib != nil {
-		libPaths.addLibraryPathOrReportError(ctx, *lib, hostPath, installPath, false)
+		clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, shared, hostPath, installPath, false, nil)
 	}
 }
 
-// Add library paths from the second map to the first map (do not override existing entries).
-func (libPaths LibraryPaths) AddLibraryPaths(otherPaths LibraryPaths) {
-	for lib, path := range otherPaths {
-		if _, present := libPaths[lib]; !present {
-			libPaths[lib] = path
+// Add class loader context for the given SDK version. Fail on unknown build/install paths.
+func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
+	lib string, shared bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
+
+	clcMap.addContextOrReportError(ctx, sdkVer, lib, shared, hostPath, installPath, true, nestedClcMap)
+}
+
+// Merge the other class loader context map into this one, do not override existing entries.
+// The implicitRootLib parameter is the name of the library for which the other class loader
+// context map was constructed. If the implicitRootLib is itself a <uses-library>, it should be
+// already present in the class loader context (with the other context as its subcontext) -- in
+// that case do not re-add the other context. Otherwise add the other context at the top-level.
+func (clcMap ClassLoaderContextMap) AddContextMap(otherClcMap ClassLoaderContextMap, implicitRootLib string) {
+	if otherClcMap == nil {
+		return
+	}
+
+	// If the implicit root of the merged map is already present as one of top-level subtrees, do
+	// not merge it second time.
+	for _, clc := range clcMap[AnySdkVersion] {
+		if clc.Name == implicitRootLib {
+			return
 		}
 	}
-}
 
-func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
-	if _, ok := m[sdkVer]; !ok {
-		m[sdkVer] = &classLoaderContext{}
-	}
-	return m[sdkVer]
-}
-
-func (clc *classLoaderContext) addLib(lib string, hostPath android.Path, targetPath string) {
-	clc.Names = append(clc.Names, lib)
-	clc.Host = append(clc.Host, hostPath)
-	clc.Target = append(clc.Target, targetPath)
-}
-
-func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, module *ModuleConfig,
-	libs ...string) (bool, error) {
-
-	clc := m.getValue(sdkVer)
-	for _, lib := range libs {
-		if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath {
-			clc.addLib(lib, p.Host, p.Device)
-		} else {
-			if sdkVer == AnySdkVersion {
-				// Fail the build if dexpreopt doesn't know paths to one of the <uses-library>
-				// dependencies. In the future we may need to relax this and just disable dexpreopt.
-				return false, fmt.Errorf("dexpreopt cannot find path for <uses-library> '%s'", lib)
-			} else {
-				// No error for compatibility libraries, as Soong doesn't know if they are needed
-				// (this depends on the targetSdkVersion in the manifest).
-				return false, nil
+	for sdkVer, otherClcs := range otherClcMap {
+		for _, otherClc := range otherClcs {
+			alreadyHave := false
+			for _, clc := range clcMap[sdkVer] {
+				if clc.Name == otherClc.Name {
+					alreadyHave = true
+					break
+				}
+			}
+			if !alreadyHave {
+				clcMap[sdkVer] = append(clcMap[sdkVer], otherClc)
 			}
 		}
 	}
-	return true, nil
 }
 
-func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
-	clc := m.getValue(sdkVer)
-	for _, lib := range libs {
-		clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar"))
+// List of libraries in the unconditional class loader context, excluding dependencies of shared
+// libraries. These libraries should be in the <uses-library> tags in the manifest. Some of them may
+// be present in the original manifest, others are added by the manifest_fixer.
+func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
+	if clcMap != nil {
+		// compatibility libraries (those in conditional context) are not added to <uses-library> tags
+		ulibs = usesLibsRec(clcMap[AnySdkVersion])
+		ulibs = android.FirstUniqueStrings(ulibs)
 	}
+	return ulibs
 }
 
-func (m classLoaderContextMap) usesLibs() []string {
-	if clc, ok := m[AnySdkVersion]; ok {
-		return clc.Names
+func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
+	for _, clc := range clcs {
+		ulibs = append(ulibs, clc.Name)
+		// <uses-library> tags in the manifest should not include dependencies of shared libraries,
+		// because PackageManager already tracks all such dependencies and automatically adds their
+		// class loader contexts as subcontext of the shared library.
+		if !clc.IsSharedLibrary {
+			ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
+		}
 	}
-	return nil
-}
-
-// genClassLoaderContext generates host and target class loader context to be passed to the dex2oat
-// command for the dexpreopted module. There are three possible cases:
-//
-// 1. System server jars. They have a special class loader context that includes other system
-//    server jars.
-//
-// 2. Library jars or APKs which have precise list of their <uses-library> libs. Their class loader
-//    context includes build and on-device paths to these libs. In some cases it may happen that
-//    the path to a <uses-library> is unknown (e.g. the dexpreopted module may depend on stubs
-//    library, whose implementation library is missing from the build altogether). In such case
-//    dexpreopting with the <uses-library> is impossible, and dexpreopting without it is pointless,
-//    as the runtime classpath won't match and the dexpreopted code will be discarded. Therefore in
-//    such cases the function returns nil, which disables dexpreopt.
-//
-// 3. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
-//    the unsafe &-classpath workaround that means empty class loader context and absence of runtime
-//    check that the class loader context provided by the PackageManager agrees with the stored
-//    class loader context recorded in the .odex file.
-//
-func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) (*classLoaderContextMap, error) {
-	classLoaderContexts := make(classLoaderContextMap)
-	systemServerJars := NonUpdatableSystemServerJars(ctx, global)
-
-	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
-		// System server jars should be dexpreopted together: class loader context of each jar
-		// should include all preceding jars on the system server classpath.
-		classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
-
-	} else if module.EnforceUsesLibraries {
-		// Unconditional class loader context.
-		usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
-		if ok, err := classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 28.
-		const httpLegacy = "org.apache.http.legacy"
-		if ok, err := classLoaderContexts.addLibs(ctx, 28, module, httpLegacy); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 29.
-		usesLibs29 := []string{
-			"android.hidl.base-V1.0-java",
-			"android.hidl.manager-V1.0-java",
-		}
-		if ok, err := classLoaderContexts.addLibs(ctx, 29, module, usesLibs29...); !ok {
-			return nil, err
-		}
-
-		// Conditional class loader context for API version < 30.
-		if ok, err := classLoaderContexts.addLibs(ctx, 30, module, OptionalCompatUsesLibs30...); !ok {
-			return nil, err
-		}
-
-	} else {
-		// Pass special class loader context to skip the classpath and collision check.
-		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
-		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
-		// to the &.
-	}
-
-	fixConditionalClassLoaderContext(classLoaderContexts)
-
-	return &classLoaderContexts, nil
+	return ulibs
 }
 
 // Now that the full unconditional context is known, reconstruct conditional context.
 // Apply filters for individual libraries, mirroring what the PackageManager does when it
 // constructs class loader context on device.
 //
-// TODO(b/132357300):
-//   - remove android.hidl.manager and android.hidl.base unless the app is a system app.
+// TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps.
 //
-func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
-	usesLibs := clcMap.usesLibs()
+func fixClassLoaderContext(clcMap ClassLoaderContextMap) {
+	usesLibs := clcMap.UsesLibs()
 
-	for sdkVer, clc := range clcMap {
+	for sdkVer, clcs := range clcMap {
 		if sdkVer == AnySdkVersion {
 			continue
 		}
-		clcMap[sdkVer] = &classLoaderContext{}
-		for i, lib := range clc.Names {
-			if android.InList(lib, usesLibs) {
+		fixedClcs := []*ClassLoaderContext{}
+		for _, clc := range clcs {
+			if android.InList(clc.Name, usesLibs) {
 				// skip compatibility libraries that are already included in unconditional context
-			} else if lib == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
+			} else if clc.Name == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
 				// android.test.mock is only needed as a compatibility library (in conditional class
 				// loader context) if android.test.runner is used, otherwise skip it
 			} else {
-				clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i])
+				fixedClcs = append(fixedClcs, clc)
 			}
+			clcMap[sdkVer] = fixedClcs
 		}
 	}
 }
 
-// Return the class loader context as a string and a slice of build paths for all dependencies.
-func computeClassLoaderContext(ctx android.PathContext, clcMap classLoaderContextMap) (clcStr string, paths android.Paths) {
-	for _, ver := range android.SortedIntKeys(clcMap) {
-		clc := clcMap.getValue(ver)
-
-		clcLen := len(clc.Names)
-		if clcLen != len(clc.Host) || clcLen != len(clc.Target) {
-			android.ReportPathErrorf(ctx, "ill-formed class loader context")
+// Return true if all build/install library paths are valid (including recursive subcontexts),
+// otherwise return false. A build path is valid if it's not nil. An install path is valid if it's
+// not equal to a special "error" value.
+func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) {
+	for sdkVer, clcs := range clcMap {
+		if valid, err := validateClassLoaderContextRec(sdkVer, clcs); !valid || err != nil {
+			return valid, err
 		}
+	}
+	return true, nil
+}
 
-		var hostClc, targetClc []string
-		var hostPaths android.Paths
-
-		for i := 0; i < clcLen; i++ {
-			hostStr := "PCL[" + clc.Host[i].String() + "]"
-			targetStr := "PCL[" + clc.Target[i] + "]"
-
-			hostClc = append(hostClc, hostStr)
-			targetClc = append(targetClc, targetStr)
-			hostPaths = append(hostPaths, clc.Host[i])
+func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
+	for _, clc := range clcs {
+		if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
+			if sdkVer == AnySdkVersion {
+				// Return error if dexpreopt doesn't know paths to one of the <uses-library>
+				// dependencies. In the future we may need to relax this and just disable dexpreopt.
+				return false, fmt.Errorf("invalid path for <uses-library> \"%s\"", clc.Name)
+			} else {
+				// No error for compatibility libraries, as Soong doesn't know if they are needed
+				// (this depends on the targetSdkVersion in the manifest), but the CLC is invalid.
+				return false, nil
+			}
 		}
+		if valid, err := validateClassLoaderContextRec(sdkVer, clc.Subcontexts); !valid || err != nil {
+			return valid, err
+		}
+	}
+	return true, nil
+}
 
+// Return the class loader context as a string, and a slice of build paths for all dependencies.
+// Perform a depth-first preorder traversal of the class loader context tree for each SDK version.
+// Return the resulting string and a slice of on-host build paths to all library dependencies.
+func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) {
+	for _, sdkVer := range android.SortedIntKeys(clcMap) { // determinisitc traversal order
+		sdkVerStr := fmt.Sprintf("%d", sdkVer)
+		if sdkVer == AnySdkVersion {
+			sdkVerStr = "any" // a special keyword that means any SDK version
+		}
+		hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer])
 		if hostPaths != nil {
-			sdkVerStr := fmt.Sprintf("%d", ver)
-			if ver == AnySdkVersion {
-				sdkVerStr = "any" // a special keyword that means any SDK version
-			}
-			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, strings.Join(hostClc, "#"))
-			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, strings.Join(targetClc, "#"))
-			paths = append(paths, hostPaths...)
+			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc)
+			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc)
 		}
+		paths = append(paths, hostPaths...)
 	}
-
-	return clcStr, paths
+	return clcStr, android.FirstUniquePaths(paths)
 }
 
+func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
+	var paths android.Paths
+	var clcsHost, clcsTarget []string
+
+	for _, clc := range clcs {
+		subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts)
+		if subPaths != nil {
+			subClcHost = "{" + subClcHost + "}"
+			subClcTarget = "{" + subClcTarget + "}"
+		}
+
+		clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost)
+		clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget)
+
+		paths = append(paths, clc.Host)
+		paths = append(paths, subPaths...)
+	}
+
+	clcHost := strings.Join(clcsHost, "#")
+	clcTarget := strings.Join(clcsTarget, "#")
+
+	return clcHost, clcTarget, paths
+}
+
+// Paths to a <uses-library> on host and on device.
 type jsonLibraryPath struct {
 	Host   string
 	Device string
 }
 
-type jsonLibraryPaths map[string]jsonLibraryPath
+// Class loader contexts that come from Make (via JSON dexpreopt.config) files have simpler
+// structure than Soong class loader contexts: they are flat maps from a <uses-library> name to its
+// on-host and on-device paths. There are no nested subcontexts. It is a limitation of the current
+// Make implementation.
+type jsonClassLoaderContext map[string]jsonLibraryPath
 
-// convert JSON map of library paths to LibraryPaths
-func constructLibraryPaths(ctx android.PathContext, paths jsonLibraryPaths) LibraryPaths {
-	m := LibraryPaths{}
-	for lib, path := range paths {
-		m[lib] = &LibraryPath{
-			constructPath(ctx, path.Host),
-			path.Device,
+// A map from SDK version (represented with a JSON string) to JSON class loader context.
+type jsonClassLoaderContextMap map[string]jsonClassLoaderContext
+
+// Convert JSON class loader context map to ClassLoaderContextMap.
+func fromJsonClassLoaderContext(ctx android.PathContext, jClcMap jsonClassLoaderContextMap) ClassLoaderContextMap {
+	clcMap := make(ClassLoaderContextMap)
+	for sdkVerStr, clc := range jClcMap {
+		sdkVer, ok := strconv.Atoi(sdkVerStr)
+		if ok != nil {
+			if sdkVerStr == "any" {
+				sdkVer = AnySdkVersion
+			} else {
+				android.ReportPathErrorf(ctx, "failed to parse SDK version in dexpreopt.config: '%s'", sdkVerStr)
+			}
+		}
+		for lib, path := range clc {
+			clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
+				Name:        lib,
+				Host:        constructPath(ctx, path.Host),
+				Device:      path.Device,
+				Subcontexts: nil,
+			})
 		}
 	}
-	return m
+	return clcMap
 }
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 51c1a0a..abfca27 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -25,6 +25,11 @@
 	"android/soong/android"
 )
 
+const (
+	shared    = true  // dependencies are not added to uses libs
+	nonshared = false // dependencies are added to uses libs
+)
+
 func TestCLC(t *testing.T) {
 	// Construct class loader context with the following structure:
 	// .
@@ -37,91 +42,79 @@
 	//     ├── b
 	//     ├── c
 	//     ├── d
-	//     ├── a2
-	//     ├── b2
-	//     ├── c2
-	//     ├── a1
-	//     ├── b1
+	//     │   ├── a2
+	//     │   ├── b2
+	//     │   └── c2
+	//     │       ├── a1
+	//     │       └── b1
 	//     ├── f
 	//     ├── a3
 	//     └── b3
 	//
 	ctx := testContext()
 
-	lp := make(LibraryPaths)
+	m := make(ClassLoaderContextMap)
 
-	lp.AddLibraryPath(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
-	lp.AddLibraryPath(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
+	m.AddContext(ctx, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"))
+	m.AddContext(ctx, "b", shared, buildPath(ctx, "b"), installPath(ctx, "b"))
 
 	// "Maybe" variant in the good case: add as usual.
 	c := "c"
-	lp.MaybeAddLibraryPath(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
+	m.MaybeAddContext(ctx, &c, nonshared, buildPath(ctx, "c"), installPath(ctx, "c"))
 
 	// "Maybe" variant in the bad case: don't add library with unknown name, keep going.
-	lp.MaybeAddLibraryPath(ctx, nil, nil, nil)
+	m.MaybeAddContext(ctx, nil, nonshared, nil, nil)
 
 	// Add some libraries with nested subcontexts.
 
-	lp1 := make(LibraryPaths)
-	lp1.AddLibraryPath(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
-	lp1.AddLibraryPath(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
+	m1 := make(ClassLoaderContextMap)
+	m1.AddContext(ctx, "a1", nonshared, buildPath(ctx, "a1"), installPath(ctx, "a1"))
+	m1.AddContext(ctx, "b1", shared, buildPath(ctx, "b1"), installPath(ctx, "b1"))
 
-	lp2 := make(LibraryPaths)
-	lp2.AddLibraryPath(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
-	lp2.AddLibraryPath(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
-	lp2.AddLibraryPath(ctx, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"))
-	lp2.AddLibraryPaths(lp1)
+	m2 := make(ClassLoaderContextMap)
+	m2.AddContext(ctx, "a2", nonshared, buildPath(ctx, "a2"), installPath(ctx, "a2"))
+	m2.AddContext(ctx, "b2", shared, buildPath(ctx, "b2"), installPath(ctx, "b2"))
+	m2.AddContextForSdk(ctx, AnySdkVersion, "c2", shared, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
 
-	lp.AddLibraryPath(ctx, "d", buildPath(ctx, "d"), installPath(ctx, "d"))
-	lp.AddLibraryPaths(lp2)
+	m3 := make(ClassLoaderContextMap)
+	m3.AddContext(ctx, "a3", nonshared, buildPath(ctx, "a3"), installPath(ctx, "a3"))
+	m3.AddContext(ctx, "b3", shared, buildPath(ctx, "b3"), installPath(ctx, "b3"))
 
-	lp3 := make(LibraryPaths)
-	lp3.AddLibraryPath(ctx, "f", buildPath(ctx, "f"), installPath(ctx, "f"))
-	lp3.AddLibraryPath(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
-	lp3.AddLibraryPath(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
-	lp.AddLibraryPaths(lp3)
+	m.AddContextForSdk(ctx, AnySdkVersion, "d", nonshared, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	// When the same library is both in conditional and unconditional context, it should be removed
+	// from conditional context.
+	m.AddContextForSdk(ctx, 42, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContextForSdk(ctx, AnySdkVersion, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+
+	// Merge map with implicit root library that is among toplevel contexts => does nothing.
+	m.AddContextMap(m1, "c")
+	// Merge map with implicit root library that is not among toplevel contexts => all subcontexts
+	// of the other map are added as toplevel contexts.
+	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	lp.AddLibraryPath(ctx, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil)
-	lp.AddLibraryPath(ctx, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil)
-	lp.AddLibraryPath(ctx, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil)
-
-	module := testSystemModuleConfig(ctx, "test")
-	module.LibraryPaths = lp
-
-	m := make(classLoaderContextMap)
-	valid := true
-
-	ok, err := m.addLibs(ctx, AnySdkVersion, module, "a", "b", "c", "d", "a2", "b2", "c2", "a1", "b1", "f", "a3", "b3")
-	valid = valid && ok && err == nil
-
-	// Add compatibility libraries to conditional CLC for SDK level 29.
-	ok, err = m.addLibs(ctx, 29, module, AndroidHidlManager, AndroidHidlBase)
-	valid = valid && ok && err == nil
+	m.AddContextForSdk(ctx, 29, AndroidHidlManager, nonshared, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContextForSdk(ctx, 29, AndroidHidlBase, nonshared, buildPath(ctx, AndroidHidlBase), nil, nil)
 
 	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
 	// needed as a compatibility library if "android.test.runner" is in CLC as well.
-	ok, err = m.addLibs(ctx, 30, module, AndroidTestMock)
-	valid = valid && ok && err == nil
+	m.AddContextForSdk(ctx, 30, AndroidTestMock, nonshared, buildPath(ctx, AndroidTestMock), nil, nil)
 
-	// When the same library is both in conditional and unconditional context, it should be removed
-	// from conditional context.
-	ok, err = m.addLibs(ctx, 42, module, "f")
-	valid = valid && ok && err == nil
+	valid, validationError := validateClassLoaderContext(m)
 
-	fixConditionalClassLoaderContext(m)
+	fixClassLoaderContext(m)
 
 	var haveStr string
 	var havePaths android.Paths
 	var haveUsesLibs []string
-	if valid {
-		haveStr, havePaths = computeClassLoaderContext(ctx, m)
-		haveUsesLibs = m.usesLibs()
+	if valid && validationError == nil {
+		haveStr, havePaths = ComputeClassLoaderContext(m)
+		haveUsesLibs = m.UsesLibs()
 	}
 
 	// Test that validation is successful (all paths are known).
 	t.Run("validate", func(t *testing.T) {
-		if !valid {
+		if !(valid && validationError == nil) {
 			t.Errorf("invalid class loader context")
 		}
 	})
@@ -135,14 +128,14 @@
 			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
 			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
 			" --host-context-for-sdk any " +
-			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]#" +
-			"PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]#" +
-			"PCL[out/a1.jar]#PCL[out/b1.jar]#" +
+			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]" +
+			"{PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]" +
+			"{PCL[out/a1.jar]#PCL[out/b1.jar]}}#" +
 			"PCL[out/f.jar]#PCL[out/a3.jar]#PCL[out/b3.jar]" +
 			" --target-context-for-sdk any " +
-			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]#" +
-			"PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]#" +
-			"PCL[/system/a1.jar]#PCL[/system/b1.jar]#" +
+			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
+			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
+			"{PCL[/system/a1.jar]#PCL[/system/b1.jar]}}#" +
 			"PCL[/system/f.jar]#PCL[/system/a3.jar]#PCL[/system/b3.jar]"
 		if wantStr != haveStr {
 			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
@@ -165,7 +158,7 @@
 
 	// Test for libraries that are added by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		wantUsesLibs := []string{"a", "b", "c", "d", "a2", "b2", "c2", "a1", "b1", "f", "a3", "b3"}
+		wantUsesLibs := []string{"a", "b", "c", "d", "a2", "b2", "c2", "f", "a3", "b3"}
 		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
 			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
 		}
@@ -175,32 +168,50 @@
 // Test that an unexpected unknown build path causes immediate error.
 func TestCLCUnknownBuildPath(t *testing.T) {
 	ctx := testContext()
-	lp := make(LibraryPaths)
-	err := lp.addLibraryPath(ctx, "a", nil, nil, true)
-	checkError(t, err, "unknown build path to <uses-library> 'a'")
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "a", nonshared, nil, nil, true, nil)
+	checkError(t, err, "unknown build path to <uses-library> \"a\"")
 }
 
 // Test that an unexpected unknown install path causes immediate error.
 func TestCLCUnknownInstallPath(t *testing.T) {
 	ctx := testContext()
-	lp := make(LibraryPaths)
-	err := lp.addLibraryPath(ctx, "a", buildPath(ctx, "a"), nil, true)
-	checkError(t, err, "unknown install path to <uses-library> 'a'")
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "a", nonshared, buildPath(ctx, "a"), nil, true, nil)
+	checkError(t, err, "unknown install path to <uses-library> \"a\"")
 }
 
 func TestCLCMaybeAdd(t *testing.T) {
 	ctx := testContext()
 
-	lp := make(LibraryPaths)
+	m := make(ClassLoaderContextMap)
 	a := "a"
-	lp.MaybeAddLibraryPath(ctx, &a, nil, nil)
+	m.MaybeAddContext(ctx, &a, nonshared, nil, nil)
 
-	module := testSystemModuleConfig(ctx, "test")
-	module.LibraryPaths = lp
+	// The library should be added to <uses-library> tags by the manifest_fixer.
+	t.Run("maybe add", func(t *testing.T) {
+		haveUsesLibs := m.UsesLibs()
+		wantUsesLibs := []string{"a"}
+		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
+			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
+		}
+	})
 
-	m := make(classLoaderContextMap)
-	_, err := m.addLibs(ctx, AnySdkVersion, module, "a")
-	checkError(t, err, "dexpreopt cannot find path for <uses-library> 'a'")
+	// But class loader context in such cases should raise an error on validation.
+	t.Run("validate", func(t *testing.T) {
+		_, err := validateClassLoaderContext(m)
+		checkError(t, err, "invalid path for <uses-library> \"a\"")
+	})
+}
+
+// An attempt to add conditional nested subcontext should fail.
+func TestCLCNestedConditional(t *testing.T) {
+	ctx := testContext()
+	m1 := make(ClassLoaderContextMap)
+	m1.AddContextForSdk(ctx, 42, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m := make(ClassLoaderContextMap)
+	err := m.addContext(ctx, AnySdkVersion, "b", nonshared, buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
+	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
 func checkError(t *testing.T, have error, want string) {
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 03accc8..f52ecb4 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -114,10 +114,8 @@
 	ProfileIsTextListing bool
 	ProfileBootListing   android.OptionalPath
 
-	EnforceUsesLibraries  bool
-	OptionalUsesLibraries []string
-	UsesLibraries         []string
-	LibraryPaths          LibraryPaths
+	EnforceUsesLibraries bool
+	ClassLoaderContexts  ClassLoaderContextMap
 
 	Archs                   []android.ArchType
 	DexPreoptImages         []android.Path
@@ -265,7 +263,7 @@
 		DexPath                     string
 		ManifestPath                string
 		ProfileClassListing         string
-		LibraryPaths                jsonLibraryPaths
+		ClassLoaderContexts         jsonClassLoaderContextMap
 		DexPreoptImages             []string
 		DexPreoptImageLocations     []string
 		PreoptBootClassPathDexFiles []string
@@ -283,7 +281,7 @@
 	config.ModuleConfig.DexPath = constructPath(ctx, config.DexPath)
 	config.ModuleConfig.ManifestPath = constructPath(ctx, config.ManifestPath)
 	config.ModuleConfig.ProfileClassListing = android.OptionalPathForPath(constructPath(ctx, config.ProfileClassListing))
-	config.ModuleConfig.LibraryPaths = constructLibraryPaths(ctx, config.LibraryPaths)
+	config.ModuleConfig.ClassLoaderContexts = fromJsonClassLoaderContext(ctx, config.ClassLoaderContexts)
 	config.ModuleConfig.DexPreoptImages = constructPaths(ctx, config.DexPreoptImages)
 	config.ModuleConfig.DexPreoptImageLocations = config.DexPreoptImageLocations
 	config.ModuleConfig.PreoptBootClassPathDexFiles = constructPaths(ctx, config.PreoptBootClassPathDexFiles)
@@ -488,13 +486,7 @@
 		return
 	}
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:   android.WriteFile,
-		Output: android.PathForOutput(ctx, "dexpreopt_soong.config"),
-		Args: map[string]string{
-			"content": string(data),
-		},
-	})
+	android.WriteFileRule(ctx, android.PathForOutput(ctx, "dexpreopt_soong.config"), string(data))
 }
 
 func (s *globalSoongConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index a07f1fa..65380fe 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -81,16 +81,18 @@
 	}
 
 	if !dexpreoptDisabled(ctx, global, module) {
-		if clc, err := genClassLoaderContext(ctx, global, module); err != nil {
+		if valid, err := validateClassLoaderContext(module.ClassLoaderContexts); err != nil {
 			android.ReportPathErrorf(ctx, err.Error())
-		} else if clc != nil {
+		} else if valid {
+			fixClassLoaderContext(module.ClassLoaderContexts)
+
 			appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) &&
 				!module.NoCreateAppImage
 
 			generateDM := shouldGenerateDM(module, global)
 
 			for archIdx, _ := range module.Archs {
-				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, *clc, profile, appImage, generateDM)
+				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
 			}
 		}
 	}
@@ -197,8 +199,8 @@
 }
 
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
-	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, classLoaderContexts classLoaderContextMap,
-	profile android.WritablePath, appImage bool, generateDM bool) {
+	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
+	appImage bool, generateDM bool) {
 
 	arch := module.Archs[archIdx]
 
@@ -235,6 +237,16 @@
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 
 	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
+		// System server jars should be dexpreopted together: class loader context of each jar
+		// should include all preceding jars on the system server classpath.
+
+		var clcHost android.Paths
+		var clcTarget []string
+		for _, lib := range systemServerJars[:jarIndex] {
+			clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib))
+			clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar"))
+		}
+
 		// Copy the system server jar to a predefined location where dex2oat will find it.
 		dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
 		rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
@@ -242,11 +254,11 @@
 
 		checkSystemServerOrder(ctx, jarIndex)
 
-		clc := classLoaderContexts[AnySdkVersion]
 		rule.Command().
-			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clc.Host.Strings(), ":") + "]").
-			Implicits(clc.Host).
-			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clc.Target, ":") + "]")
+			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clcHost.Strings(), ":") + "]").
+			Implicits(clcHost).
+			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]")
+
 	} else if module.EnforceUsesLibraries {
 		// Generate command that saves target SDK version in a shell variable.
 		if module.ManifestPath != nil {
@@ -266,13 +278,15 @@
 		}
 
 		// Generate command that saves host and target class loader context in shell variables.
-		clc, paths := computeClassLoaderContext(ctx, classLoaderContexts)
+		clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts)
 		cmd := rule.Command().
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`).
 			Text(clc).Implicits(paths)
 		cmd.Text(`)"`)
+
 	} else {
+		// Other libraries or APKs for which the exact <uses-library> list is unknown.
 		// Pass special class loader context to skip the classpath and collision check.
 		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
 		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index ec31549..feabd70 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -44,9 +44,7 @@
 		ProfileClassListing:             android.OptionalPath{},
 		ProfileIsTextListing:            false,
 		EnforceUsesLibraries:            false,
-		OptionalUsesLibraries:           nil,
-		UsesLibraries:                   nil,
-		LibraryPaths:                    nil,
+		ClassLoaderContexts:             nil,
 		Archs:                           []android.ArchType{android.Arm},
 		DexPreoptImages:                 android.Paths{android.PathForTesting("system/framework/arm/boot.art")},
 		DexPreoptImagesDeps:             []android.OutputPaths{android.OutputPaths{}},
diff --git a/env/env.go b/env/env.go
index a98e1f6..735a38a 100644
--- a/env/env.go
+++ b/env/env.go
@@ -27,6 +27,15 @@
 type envFileEntry struct{ Key, Value string }
 type envFileData []envFileEntry
 
+// Serializes the given environment variable name/value map into JSON formatted bytes by converting
+// to envFileEntry values and marshaling them.
+//
+// e.g. OUT_DIR = "out"
+// is converted to:
+// {
+//     "Key": "OUT_DIR",
+//     "Value": "out",
+// },
 func EnvFileContents(envDeps map[string]string) ([]byte, error) {
 	contents := make(envFileData, 0, len(envDeps))
 	for key, value := range envDeps {
@@ -45,8 +54,11 @@
 	return data, nil
 }
 
-func StaleEnvFile(filename string) (bool, error) {
-	data, err := ioutil.ReadFile(filename)
+// Reads and deserializes a Soong environment file located at the given file path to determine its
+// staleness. If any environment variable values have changed, it prints them out and returns true.
+// Failing to read or parse the file also causes it to return true.
+func StaleEnvFile(filepath string) (bool, error) {
+	data, err := ioutil.ReadFile(filepath)
 	if err != nil {
 		return true, err
 	}
@@ -79,6 +91,7 @@
 	return false, nil
 }
 
+// Implements sort.Interface so that we can use sort.Sort on envFileData arrays.
 func (e envFileData) Len() int {
 	return len(e)
 }
@@ -90,3 +103,5 @@
 func (e envFileData) Swap(i, j int) {
 	e[i], e[j] = e[j], e[i]
 }
+
+var _ sort.Interface = envFileData{}
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 8fc36c2..6c4c0b6 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -58,7 +58,7 @@
 
 	config := android.TestArchConfig(buildDir, nil, bp, fs)
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("prebuilt_etc", PrebuiltEtcFactory)
 	ctx.RegisterModuleType("prebuilt_etc_host", PrebuiltEtcHostFactory)
 	ctx.RegisterModuleType("prebuilt_usr_share", PrebuiltUserShareFactory)
@@ -66,7 +66,7 @@
 	ctx.RegisterModuleType("prebuilt_font", PrebuiltFontFactory)
 	ctx.RegisterModuleType("prebuilt_firmware", PrebuiltFirmwareFactory)
 	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx, config
 }
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 53b9dbe..f85146c 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
@@ -25,9 +26,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/shared"
-	"crypto/sha256"
-	"path/filepath"
 )
 
 func init() {
@@ -156,14 +154,14 @@
 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
 
 type generateTask struct {
-	in          android.Paths
-	out         android.WritablePaths
-	copyTo      android.WritablePaths
-	genDir      android.WritablePath
-	sandboxOuts []string
-	cmd         string
-	shard       int
-	shards      int
+	in      android.Paths
+	out     android.WritablePaths
+	depFile android.WritablePath
+	copyTo  android.WritablePaths
+	genDir  android.WritablePath
+	cmd     string
+	shard   int
+	shards  int
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -330,19 +328,23 @@
 	var zipArgs strings.Builder
 
 	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
-		for _, out := range task.out {
-			addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
+		if len(task.out) == 0 {
+			ctx.ModuleErrorf("must have at least one output file")
+			return
 		}
 
-		referencedIn := false
+		for _, out := range task.out {
+			addLocationLabel(out.Rel(), []string{android.SboxPathForOutput(out, task.genDir)})
+		}
+
 		referencedDepfile := false
 
-		rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, error) {
+		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
 			// report the error directly without returning an error to android.Expand to catch multiple errors in a
 			// single run
-			reportError := func(fmt string, args ...interface{}) (string, bool, error) {
+			reportError := func(fmt string, args ...interface{}) (string, error) {
 				ctx.PropertyErrorf("cmd", fmt, args...)
-				return "SOONG_ERROR", false, nil
+				return "SOONG_ERROR", nil
 			}
 
 			switch name {
@@ -357,20 +359,23 @@
 					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
 						firstLabel, firstLabel)
 				}
-				return locationLabels[firstLabel][0], false, nil
+				return locationLabels[firstLabel][0], nil
 			case "in":
-				referencedIn = true
-				return "${in}", true, nil
+				return strings.Join(srcFiles.Strings(), " "), nil
 			case "out":
-				return "__SBOX_OUT_FILES__", false, nil
+				var sandboxOuts []string
+				for _, out := range task.out {
+					sandboxOuts = append(sandboxOuts, android.SboxPathForOutput(out, task.genDir))
+				}
+				return strings.Join(sandboxOuts, " "), nil
 			case "depfile":
 				referencedDepfile = true
 				if !Bool(g.properties.Depfile) {
 					return reportError("$(depfile) used without depfile property")
 				}
-				return "__SBOX_DEPFILE__", false, nil
+				return "__SBOX_DEPFILE__", nil
 			case "genDir":
-				return "__SBOX_OUT_DIR__", false, nil
+				return android.SboxPathForOutput(task.genDir, task.genDir), nil
 			default:
 				if strings.HasPrefix(name, "location ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -381,7 +386,7 @@
 							return reportError("label %q has multiple files, use $(locations %s) to reference it",
 								label, label)
 						}
-						return paths[0], false, nil
+						return paths[0], nil
 					} else {
 						return reportError("unknown location label %q", label)
 					}
@@ -391,7 +396,7 @@
 						if len(paths) == 0 {
 							return reportError("label %q has no files", label)
 						}
-						return strings.Join(paths, " "), false, nil
+						return strings.Join(paths, " "), nil
 					} else {
 						return reportError("unknown locations label %q", label)
 					}
@@ -410,50 +415,39 @@
 			ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
 			return
 		}
-
-		// tell the sbox command which directory to use as its sandbox root
-		buildDir := android.PathForOutput(ctx).String()
-		sandboxPath := shared.TempDirForOutDir(buildDir)
-
-		// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
-		// to be replaced later by ninja_strings.go
-		depfilePlaceholder := ""
-		if Bool(g.properties.Depfile) {
-			depfilePlaceholder = "$depfileArgs"
-		}
-
-		// Escape the command for the shell
-		rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'"
 		g.rawCommands = append(g.rawCommands, rawCommand)
 
-		sandboxCommand := fmt.Sprintf("rm -rf %s && $sboxCmd --sandbox-path %s --output-root %s",
-			task.genDir, sandboxPath, task.genDir)
-
-		if !referencedIn {
-			sandboxCommand = sandboxCommand + hashSrcFiles(srcFiles)
-		}
-
-		sandboxCommand = sandboxCommand + fmt.Sprintf(" -c %s %s $allouts",
-			rawCommand, depfilePlaceholder)
-
-		ruleParams := blueprint.RuleParams{
-			Command:     sandboxCommand,
-			CommandDeps: []string{"$sboxCmd"},
-		}
-		args := []string{"allouts"}
-		if Bool(g.properties.Depfile) {
-			ruleParams.Deps = blueprint.DepsGCC
-			args = append(args, "depfileArgs")
-		}
+		// Pick a unique rule name and the user-visible description.
+		desc := "generate"
 		name := "generator"
-		if task.shards > 1 {
+		if task.shards > 0 {
+			desc += " " + strconv.Itoa(task.shard)
 			name += strconv.Itoa(task.shard)
+		} else if len(task.out) == 1 {
+			desc += " " + task.out[0].Base()
 		}
-		rule := ctx.Rule(pctx, name, ruleParams, args...)
 
-		g.generateSourceFile(ctx, task, rule)
+		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
+		rule := android.NewRuleBuilder().Sbox(task.genDir)
+		cmd := rule.Command()
+		cmd.Text(rawCommand)
+		cmd.ImplicitOutputs(task.out)
+		cmd.Implicits(task.in)
+		cmd.Implicits(g.deps)
+		if Bool(g.properties.Depfile) {
+			cmd.ImplicitDepFile(task.depFile)
+		}
+
+		// Create the rule to run the genrule command inside sbox.
+		rule.Build(pctx, ctx, name, desc)
 
 		if len(task.copyTo) > 0 {
+			// If copyTo is set, multiple shards need to be copied into a single directory.
+			// task.out contains the per-shard paths, and copyTo contains the corresponding
+			// final path.  The files need to be copied into the final directory by a
+			// single rule so it can remove the directory before it starts to ensure no
+			// old files remain.  zipsync already does this, so build up zipArgs that
+			// zip all the per-shard directories into a single zip.
 			outputFiles = append(outputFiles, task.copyTo...)
 			copyFrom = append(copyFrom, task.out.Paths()...)
 			zipArgs.WriteString(" -C " + task.genDir.String())
@@ -464,6 +458,8 @@
 	}
 
 	if len(copyFrom) > 0 {
+		// Create a rule that zips all the per-shard directories into a single zip and then
+		// uses zipsync to unzip it into the final directory.
 		ctx.Build(pctx, android.BuildParams{
 			Rule:      gensrcsMerge,
 			Implicits: copyFrom,
@@ -501,51 +497,6 @@
 		}
 	}
 }
-func hashSrcFiles(srcFiles android.Paths) string {
-	h := sha256.New()
-	for _, src := range srcFiles {
-		h.Write([]byte(src.String()))
-	}
-	return fmt.Sprintf(" --input-hash %x", h.Sum(nil))
-}
-
-func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask, rule blueprint.Rule) {
-	desc := "generate"
-	if len(task.out) == 0 {
-		ctx.ModuleErrorf("must have at least one output file")
-		return
-	}
-	if len(task.out) == 1 {
-		desc += " " + task.out[0].Base()
-	}
-
-	var depFile android.ModuleGenPath
-	if Bool(g.properties.Depfile) {
-		depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
-	}
-
-	if task.shards > 1 {
-		desc += " " + strconv.Itoa(task.shard)
-	}
-
-	params := android.BuildParams{
-		Rule:            rule,
-		Description:     desc,
-		Output:          task.out[0],
-		ImplicitOutputs: task.out[1:],
-		Inputs:          task.in,
-		Implicits:       g.deps,
-		Args: map[string]string{
-			"allouts": strings.Join(task.sandboxOuts, " "),
-		},
-	}
-	if Bool(g.properties.Depfile) {
-		params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
-		params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
-	}
-
-	ctx.Build(pctx, params)
-}
 
 // Collect information for opening IDE project files in java/jdeps.go.
 func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
@@ -610,16 +561,6 @@
 func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
 }
 
-// replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>"
-func pathToSandboxOut(path android.Path, genDir android.Path) string {
-	relOut, err := filepath.Rel(genDir.String(), path.String())
-	if err != nil {
-		panic(fmt.Sprintf("Could not make ${out} relative: %v", err))
-	}
-	return filepath.Join("__SBOX_OUT_DIR__", relOut)
-
-}
-
 func NewGenSrcs() *Module {
 	properties := &genSrcsProperties{}
 
@@ -638,7 +579,7 @@
 			var outFiles android.WritablePaths
 			var copyTo android.WritablePaths
 			var shardDir android.WritablePath
-			var sandboxOuts []string
+			var depFile android.WritablePath
 
 			if len(shards) > 1 {
 				shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
@@ -646,9 +587,11 @@
 				shardDir = genDir
 			}
 
-			for _, in := range shard {
+			for j, in := range shard {
 				outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
-				sandboxOutfile := pathToSandboxOut(outFile, genDir)
+				if j == 0 {
+					depFile = outFile.ReplaceExtension(ctx, "d")
+				}
 
 				if len(shards) > 1 {
 					shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
@@ -657,14 +600,13 @@
 				}
 
 				outFiles = append(outFiles, outFile)
-				sandboxOuts = append(sandboxOuts, sandboxOutfile)
 
 				command, err := android.Expand(rawCommand, func(name string) (string, error) {
 					switch name {
 					case "in":
 						return in.String(), nil
 					case "out":
-						return sandboxOutfile, nil
+						return android.SboxPathForOutput(outFile, shardDir), nil
 					default:
 						return "$(" + name + ")", nil
 					}
@@ -680,14 +622,14 @@
 			fullCommand := strings.Join(commands, " && ")
 
 			generateTasks = append(generateTasks, generateTask{
-				in:          shard,
-				out:         outFiles,
-				copyTo:      copyTo,
-				genDir:      shardDir,
-				sandboxOuts: sandboxOuts,
-				cmd:         fullCommand,
-				shard:       i,
-				shards:      len(shards),
+				in:      shard,
+				out:     outFiles,
+				depFile: depFile,
+				copyTo:  copyTo,
+				genDir:  shardDir,
+				cmd:     fullCommand,
+				shard:   i,
+				shards:  len(shards),
 			})
 		}
 
@@ -720,18 +662,20 @@
 
 	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
 		outs := make(android.WritablePaths, len(properties.Out))
-		sandboxOuts := make([]string, len(properties.Out))
-		genDir := android.PathForModuleGen(ctx)
+		var depFile android.WritablePath
 		for i, out := range properties.Out {
-			outs[i] = android.PathForModuleGen(ctx, out)
-			sandboxOuts[i] = pathToSandboxOut(outs[i], genDir)
+			outPath := android.PathForModuleGen(ctx, out)
+			if i == 0 {
+				depFile = outPath.ReplaceExtension(ctx, "d")
+			}
+			outs[i] = outPath
 		}
 		return []generateTask{{
-			in:          srcFiles,
-			out:         outs,
-			genDir:      android.PathForModuleGen(ctx),
-			sandboxOuts: sandboxOuts,
-			cmd:         rawCommand,
+			in:      srcFiles,
+			out:     outs,
+			depFile: depFile,
+			genDir:  android.PathForModuleGen(ctx),
+			cmd:     rawCommand,
 		}}
 	}
 
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index fdbb9d9..6779c73 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -53,14 +53,14 @@
 
 func testContext(config android.Config) *android.TestContext {
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("tool", toolFactory)
 
 	registerGenruleBuildComponents(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx
 }
@@ -141,7 +141,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "empty location tool2",
@@ -150,7 +150,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "empty location tool file",
@@ -159,7 +159,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "empty location tool file fg",
@@ -168,7 +168,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "empty location tool and tool file",
@@ -178,7 +178,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool",
@@ -187,7 +187,7 @@
 				out: ["out"],
 				cmd: "$(location tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool2",
@@ -196,7 +196,7 @@
 				out: ["out"],
 				cmd: "$(location :tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool file",
@@ -205,7 +205,7 @@
 				out: ["out"],
 				cmd: "$(location tool_file1) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool file fg",
@@ -214,7 +214,7 @@
 				out: ["out"],
 				cmd: "$(location :1tool_file) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool files",
@@ -223,7 +223,7 @@
 				out: ["out"],
 				cmd: "$(locations :tool_files) > $(out)",
 			`,
-			expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 tool_file2 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "in1",
@@ -232,7 +232,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "in1 fg",
@@ -241,7 +241,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "ins",
@@ -250,7 +250,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "ins fg",
@@ -259,7 +259,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "location in1",
@@ -268,7 +268,7 @@
 				out: ["out"],
 				cmd: "cat $(location in1) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "location in1 fg",
@@ -277,7 +277,7 @@
 				out: ["out"],
 				cmd: "cat $(location :1in) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "location ins",
@@ -286,7 +286,7 @@
 				out: ["out"],
 				cmd: "cat $(location in1) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "location ins fg",
@@ -295,7 +295,7 @@
 				out: ["out"],
 				cmd: "cat $(locations :ins) > $(out)",
 			`,
-			expect: "cat in1 in2 > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "outs",
@@ -303,7 +303,7 @@
 				out: ["out", "out2"],
 				cmd: "echo foo > $(out)",
 			`,
-			expect: "echo foo > __SBOX_OUT_FILES__",
+			expect: "echo foo > __SBOX_OUT_DIR__/out __SBOX_OUT_DIR__/out2",
 		},
 		{
 			name: "location out",
@@ -320,7 +320,7 @@
 				depfile: true,
 				cmd: "echo foo > $(out) && touch $(depfile)",
 			`,
-			expect: "echo foo > __SBOX_OUT_FILES__ && touch __SBOX_DEPFILE__",
+			expect: "echo foo > __SBOX_OUT_DIR__/out && touch __SBOX_DEPFILE__",
 		},
 		{
 			name: "gendir",
@@ -328,7 +328,7 @@
 				out: ["out"],
 				cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)",
 			`,
-			expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_FILES__",
+			expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_DIR__/out",
 		},
 
 		{
@@ -443,7 +443,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__",
+			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_DIR__/out",
 		},
 		{
 			name: "tool allow missing dependencies",
@@ -455,7 +455,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "***missing tool :missing*** > __SBOX_OUT_FILES__",
+			expect: "***missing tool :missing*** > __SBOX_OUT_DIR__/out",
 		},
 	}
 
@@ -495,7 +495,7 @@
 			}
 
 			gen := ctx.ModuleForTests("gen", "").Module().(*Module)
-			if g, w := gen.rawCommands[0], "'"+test.expect+"'"; w != g {
+			if g, w := gen.rawCommands[0], test.expect; w != g {
 				t.Errorf("want %q, got %q", w, g)
 			}
 		})
@@ -542,18 +542,18 @@
 	}{
 		{
 			name: "hash0",
-			// sha256 value obtained from: echo -n 'in1.txtin2.txt' | sha256sum
-			expectedHash: "031097e11e0a8c822c960eb9742474f46336360a515744000d086d94335a9cb9",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
+			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
 		},
 		{
 			name: "hash1",
-			// sha256 value obtained from: echo -n 'in1.txtin2.txtin3.txt' | sha256sum
-			expectedHash: "de5d22a4a7ab50d250cc59fcdf7a7e0775790d270bfca3a7a9e1f18a70dd996c",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
 		},
 		{
 			name: "hash2",
-			// $(in) is present, option should not appear
-			expectedHash: "",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
 		},
 	}
 
@@ -575,7 +575,7 @@
 			if len(test.expectedHash) > 0 {
 				// We add spaces before and after to make sure that
 				// this option doesn't abutt another sbox option.
-				expectedInputHashOption := " --input-hash " + test.expectedHash + " "
+				expectedInputHashOption := " --input-hash " + test.expectedHash
 
 				if !strings.Contains(command, expectedInputHashOption) {
 					t.Errorf("Expected command \"%s\" to contain \"%s\"", command, expectedInputHashOption)
@@ -609,7 +609,7 @@
 				cmd: "$(location) $(in) > $(out)",
 			`,
 			cmds: []string{
-				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''",
+				"bash -c 'out/tool in1.txt > __SBOX_OUT_DIR__/in1.h' && bash -c 'out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'",
 			},
 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
@@ -623,8 +623,8 @@
 				shard_size: 2,
 			`,
 			cmds: []string{
-				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''",
-				"'bash -c '\\''out/tool in3.txt > __SBOX_OUT_DIR__/in3.h'\\'''",
+				"bash -c 'out/tool in1.txt > __SBOX_OUT_DIR__/in1.h' && bash -c 'out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'",
+				"bash -c 'out/tool in3.txt > __SBOX_OUT_DIR__/in3.h'",
 			},
 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
@@ -710,7 +710,7 @@
 	}
 	gen := ctx.ModuleForTests("gen", "").Module().(*Module)
 
-	expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'"
+	expectedCmd := "cp in1 __SBOX_OUT_DIR__/out"
 	if gen.rawCommands[0] != expectedCmd {
 		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0])
 	}
diff --git a/java/aar.go b/java/aar.go
index 157d677..051715d 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -109,7 +109,6 @@
 	useEmbeddedNativeLibs   bool
 	useEmbeddedDex          bool
 	usesNonSdkApis          bool
-	sdkLibraries            dexpreopt.LibraryPaths
 	hasNoCode               bool
 	LoggingParent           string
 	resourceFiles           android.Paths
@@ -259,18 +258,17 @@
 		CommandDeps: []string{"${config.Zip2ZipCmd}"},
 	})
 
-func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) {
+func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext,
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
 
-	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags, sdkLibraries :=
-		aaptLibs(ctx, sdkContext)
-
-	a.sdkLibraries = sdkLibraries
+	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
+		aaptLibs(ctx, sdkContext, classLoaderContexts)
 
 	// App manifest file
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
 
-	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, sdkLibraries,
+	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, classLoaderContexts,
 		a.isLibrary, a.useEmbeddedNativeLibs, a.usesNonSdkApis, a.useEmbeddedDex, a.hasNoCode,
 		a.LoggingParent)
 
@@ -391,29 +389,31 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, transitiveStaticLibManifests android.Paths,
-	staticRRODirs []rroDir, assets, deps android.Paths, flags []string, sdkLibraries dexpreopt.LibraryPaths) {
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, classLoaderContexts dexpreopt.ClassLoaderContextMap) (
+	transitiveStaticLibs, transitiveStaticLibManifests android.Paths, staticRRODirs []rroDir, assets, deps android.Paths, flags []string) {
 
 	var sharedLibs android.Paths
 
+	if classLoaderContexts == nil {
+		// Not all callers need to compute class loader context, those who don't just pass nil.
+		// Create a temporary class loader context here (it will be computed, but not used).
+		classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
+	}
+
 	sdkDep := decodeSdkDep(ctx, sdkContext)
 	if sdkDep.useFiles {
 		sharedLibs = append(sharedLibs, sdkDep.jars...)
 	}
 
-	sdkLibraries = make(dexpreopt.LibraryPaths)
-
 	ctx.VisitDirectDeps(func(module android.Module) {
+		depName := ctx.OtherModuleName(module)
+
 		var exportPackage android.Path
 		aarDep, _ := module.(AndroidLibraryDependency)
 		if aarDep != nil {
 			exportPackage = aarDep.ExportPackage()
 		}
 
-		if dep, ok := module.(Dependency); ok {
-			sdkLibraries.AddLibraryPaths(dep.ExportedSdkLibs())
-		}
-
 		switch ctx.OtherModuleDependencyTag(module) {
 		case instrumentationForTag:
 			// Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2.
@@ -426,7 +426,7 @@
 			// (including the java_sdk_library) itself then append any implicit sdk library
 			// names to the list of sdk libraries to be added to the manifest.
 			if component, ok := module.(SdkLibraryComponentDependency); ok {
-				sdkLibraries.MaybeAddLibraryPath(ctx, component.OptionalImplicitSdkLibrary(),
+				classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(), true,
 					component.DexJarBuildPath(), component.DexJarInstallPath())
 			}
 
@@ -439,7 +439,7 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				sdkLibraries.AddLibraryPaths(aarDep.ExportedSdkLibs())
+				classLoaderContexts.AddContextMap(aarDep.ClassLoaderContexts(), depName)
 				if aarDep.ExportedAssets().Valid() {
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
@@ -457,6 +457,12 @@
 				}
 			}
 		}
+
+		// Add nested dependencies after processing the direct dependency: if it is a <uses-library>,
+		// nested context is added as its subcontext, and should not be re-added at the top-level.
+		if dep, ok := module.(Dependency); ok {
+			classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), depName)
+		}
 	})
 
 	deps = append(deps, sharedLibs...)
@@ -473,7 +479,7 @@
 	transitiveStaticLibs = android.FirstUniquePaths(transitiveStaticLibs)
 	transitiveStaticLibManifests = android.FirstUniquePaths(transitiveStaticLibManifests)
 
-	return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags, sdkLibraries
+	return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags
 }
 
 type AndroidLibrary struct {
@@ -508,8 +514,8 @@
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
-	a.aapt.buildActions(ctx, sdkContext(a))
-	a.exportedSdkLibs = a.aapt.sdkLibraries
+	a.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
+	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -781,12 +787,11 @@
 	linkFlags = append(linkFlags, "--manifest "+a.manifest.String())
 	linkDeps = append(linkDeps, a.manifest)
 
-	transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags, sdkLibraries :=
-		aaptLibs(ctx, sdkContext(a))
+	transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags :=
+		aaptLibs(ctx, sdkContext(a), nil)
 
 	_ = staticLibManifests
 	_ = staticRRODirs
-	_ = sdkLibraries
 
 	linkDeps = append(linkDeps, libDeps...)
 	linkFlags = append(linkFlags, libFlags...)
@@ -827,7 +832,7 @@
 	return nil
 }
 
-func (a *AARImport) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (a *AARImport) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 62cd112..c76bb2f 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -43,8 +43,9 @@
 	"args", "libs")
 
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
-func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext, sdkLibraries dexpreopt.LibraryPaths,
-	isLibrary, useEmbeddedNativeLibs, usesNonSdkApis, useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
+func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
+	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
 
 	var args []string
 	if isLibrary {
@@ -70,7 +71,7 @@
 		args = append(args, "--use-embedded-dex")
 	}
 
-	for _, usesLib := range android.SortedStringKeys(sdkLibraries) {
+	for _, usesLib := range classLoaderContexts.UsesLibs() {
 		if inList(usesLib, dexpreopt.OptionalCompatUsesLibs) {
 			args = append(args, "--optional-uses-library", usesLib)
 		} else {
diff --git a/java/android_resources.go b/java/android_resources.go
index 97f7679..720d3a5 100644
--- a/java/android_resources.go
+++ b/java/android_resources.go
@@ -23,6 +23,7 @@
 
 func init() {
 	android.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
+
 }
 
 var androidResourceIgnoreFilenames = []string{
diff --git a/java/androidmk.go b/java/androidmk.go
index e1a661f..386a97f 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -115,7 +115,7 @@
 						entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 					}
 
-					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", android.SortedStringKeys(library.exportedSdkLibs)...)
+					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.classLoaderContexts.UsesLibs()...)
 
 					if len(library.additionalCheckedModules) != 0 {
 						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
@@ -160,6 +160,9 @@
 			entries.SetString("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", "true")
 		}
 		entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", j.testProperties.Test_mainline_modules...)
+		if Bool(j.testProperties.Test_options.Unit_test) {
+			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+		}
 	})
 
 	return entriesList
diff --git a/java/app.go b/java/app.go
index c24e0c5..3446739 100755
--- a/java/app.go
+++ b/java/app.go
@@ -565,9 +565,8 @@
 	aaptLinkFlags = append(aaptLinkFlags, a.additionalAaptFlags...)
 
 	a.aapt.splitNames = a.appProperties.Package_splits
-	a.aapt.sdkLibraries = a.exportedSdkLibs
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, sdkContext(a), aaptLinkFlags...)
+	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -601,7 +600,7 @@
 	return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
 }
 
-func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext, sdkLibs dexpreopt.LibraryPaths) android.Path {
+func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
 	a.dexpreopter.installPath = a.installPath(ctx)
 	if a.dexProperties.Uncompress_dex == nil {
 		// If the value was not force-set by the user, use reasonable default based on the module.
@@ -609,12 +608,8 @@
 	}
 	a.dexpreopter.uncompressedDex = *a.dexProperties.Uncompress_dex
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs
-	a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx)
-	a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx)
-	a.dexpreopter.libraryPaths.AddLibraryPaths(sdkLibs)
+	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
-	a.exportedSdkLibs = make(dexpreopt.LibraryPaths)
 
 	if ctx.ModuleName() != "framework-res" {
 		a.Module.compile(ctx, a.aaptSrcJar)
@@ -784,6 +779,8 @@
 		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
 	}
 
+	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
 
@@ -791,7 +788,7 @@
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
 	// Add implicit SDK libraries to <uses-library> list.
-	for _, usesLib := range android.SortedStringKeys(a.aapt.sdkLibraries) {
+	for _, usesLib := range a.classLoaderContexts.UsesLibs() {
 		a.usesLibrary.addLib(usesLib, inList(usesLib, dexpreopt.OptionalCompatUsesLibs))
 	}
 
@@ -808,7 +805,7 @@
 	a.linter.resources = a.aapt.resourceFiles
 	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
-	dexJarFile := a.dexBuildActions(ctx, a.aapt.sdkLibraries)
+	dexJarFile := a.dexBuildActions(ctx)
 
 	jniLibs, certificateDeps := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
 	jniJarFile := a.jniBuildActions(jniLibs, ctx)
@@ -893,7 +890,7 @@
 
 		if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) {
 			if dep, ok := module.(*cc.Module); ok {
-				if dep.IsNdk() || dep.IsStubs() {
+				if dep.IsNdk(ctx.Config()) || dep.IsStubs() {
 					return false
 				}
 
@@ -1540,9 +1537,7 @@
 	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
 
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.usesLibs = a.usesLibrary.usesLibraryProperties.Uses_libs
-	a.dexpreopter.optionalUsesLibs = a.usesLibrary.presentOptionalUsesLibs(ctx)
-	a.dexpreopter.libraryPaths = a.usesLibrary.usesLibraryPaths(ctx)
+	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	dexOutput := a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
 	if a.dexpreopter.uncompressedDex {
@@ -1852,7 +1847,7 @@
 		aaptLinkFlags = append(aaptLinkFlags,
 			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
 	}
-	r.aapt.buildActions(ctx, r, aaptLinkFlags...)
+	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
 
 	// Sign the built package
 	_, certificates := collectAppDeps(ctx, r, false, false)
@@ -1976,17 +1971,18 @@
 	return optionalUsesLibs
 }
 
-// usesLibraryPaths returns a map of module names of shared library dependencies to the paths
+// Returns a map of module names of shared library dependencies to the paths
 // to their dex jars on host and on device.
-func (u *usesLibrary) usesLibraryPaths(ctx android.ModuleContext) dexpreopt.LibraryPaths {
-	usesLibPaths := make(dexpreopt.LibraryPaths)
+func (u *usesLibrary) classLoaderContextForUsesLibDeps(ctx android.ModuleContext) dexpreopt.ClassLoaderContextMap {
+	clcMap := make(dexpreopt.ClassLoaderContextMap)
 
 	if !ctx.Config().UnbundledBuild() {
 		ctx.VisitDirectDeps(func(m android.Module) {
-			if _, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
+			if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(Dependency); ok {
-					usesLibPaths.AddLibraryPath(ctx, dep, lib.DexJarBuildPath(), lib.DexJarInstallPath())
+					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep, isSharedSdkLibrary(m),
+						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
 				} else {
@@ -1996,7 +1992,12 @@
 		})
 	}
 
-	return usesLibPaths
+	return clcMap
+}
+
+func isSharedSdkLibrary(m android.Module) bool {
+	lib, ok := m.(SdkLibraryDependency)
+	return ok && lib.IsSharedLibrary()
 }
 
 // enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs
diff --git a/java/app_test.go b/java/app_test.go
index 82577e3..6429ab8 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -59,7 +59,7 @@
 func testApp(t *testing.T, bp string) *android.TestContext {
 	config := testAppConfig(nil, bp, nil)
 
-	ctx := testContext()
+	ctx := testContext(config)
 
 	run(t, ctx, config)
 
@@ -220,7 +220,7 @@
 		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
 		config.TestProductVariables.Platform_sdk_version = &test.sdkVersion
 		config.Targets[android.Android] = test.targets
-		ctx := testContext()
+		ctx := testContext(config)
 		run(t, ctx, config)
 		module := ctx.ModuleForTests("foo", "android_common")
 		const packedSplitApks = "foo.zip"
@@ -657,7 +657,7 @@
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
 			config := testConfig(nil, fmt.Sprintf(bp, testCase.prop), fs)
-			ctx := testContext()
+			ctx := testContext(config)
 			run(t, ctx, config)
 
 			module := ctx.ModuleForTests("foo", "android_common")
@@ -973,7 +973,7 @@
 				config.TestProductVariables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays
 			}
 
-			ctx := testContext()
+			ctx := testContext(config)
 			run(t, ctx, config)
 
 			resourceListToFiles := func(module android.TestingModule, list []string) (files []string) {
@@ -1039,7 +1039,7 @@
 }
 
 func checkSdkVersion(t *testing.T, config android.Config, expectedSdkVersion string) {
-	ctx := testContext()
+	ctx := testContext(config)
 
 	run(t, ctx, config)
 
@@ -1633,7 +1633,7 @@
 			if test.certificateOverride != "" {
 				config.TestProductVariables.CertificateOverrides = []string{test.certificateOverride}
 			}
-			ctx := testContext()
+			ctx := testContext(config)
 
 			run(t, ctx, config)
 			foo := ctx.ModuleForTests("foo", "android_common")
@@ -1698,7 +1698,7 @@
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
 			config := testAppConfig(nil, test.bp, nil)
-			ctx := testContext()
+			ctx := testContext(config)
 
 			run(t, ctx, config)
 			foo := ctx.ModuleForTests("foo", "android_common")
@@ -1758,7 +1758,7 @@
 			if test.packageNameOverride != "" {
 				config.TestProductVariables.PackageNameOverrides = []string{test.packageNameOverride}
 			}
-			ctx := testContext()
+			ctx := testContext(config)
 
 			run(t, ctx, config)
 			foo := ctx.ModuleForTests("foo", "android_common")
@@ -1793,7 +1793,7 @@
 		`
 	config := testAppConfig(nil, bp, nil)
 	config.TestProductVariables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"}
-	ctx := testContext()
+	ctx := testContext(config)
 
 	run(t, ctx, config)
 
@@ -2416,7 +2416,7 @@
 		config := testAppConfig(nil, bp, nil)
 		config.TestProductVariables.AAPTPreferredConfig = test.aaptPreferredConfig
 		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-		ctx := testContext()
+		ctx := testContext(config)
 
 		run(t, ctx, config)
 
@@ -2777,7 +2777,7 @@
 	config := testAppConfig(nil, bp, nil)
 	config.TestProductVariables.MissingUsesLibraries = []string{"baz"}
 
-	ctx := testContext()
+	ctx := testContext(config)
 
 	run(t, ctx, config)
 
@@ -2815,11 +2815,11 @@
 	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
 	cmd = app.Rule("dexpreopt").RuleParams.Command
 	w := `--target-context-for-sdk any ` +
-		`PCL[/system/framework/foo.jar]#` +
-		`PCL[/system/framework/quuz.jar]#` +
 		`PCL[/system/framework/qux.jar]#` +
-		`PCL[/system/framework/runtime-library.jar]#` +
-		`PCL[/system/framework/bar.jar]`
+		`PCL[/system/framework/quuz.jar]#` +
+		`PCL[/system/framework/foo.jar]#` +
+		`PCL[/system/framework/bar.jar]#` +
+		`PCL[/system/framework/runtime-library.jar]`
 	if !strings.Contains(cmd, w) {
 		t.Errorf("wanted %q in %q", w, cmd)
 	}
@@ -3129,7 +3129,7 @@
 			config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 		}
 
-		ctx := testContext()
+		ctx := testContext(config)
 
 		run(t, ctx, config)
 
@@ -3209,7 +3209,7 @@
 		}
 		`
 	config := testAppConfig(nil, bp, fs)
-	ctx := testContext()
+	ctx := testContext(config)
 	run(t, ctx, config)
 
 	m := ctx.ModuleForTests("foo", "android_common")
@@ -3506,7 +3506,7 @@
 				config.TestProductVariables.EnforceRROExemptedTargets = testCase.enforceRROExemptTargets
 			}
 
-			ctx := testContext()
+			ctx := testContext(config)
 			run(t, ctx, config)
 
 			modules := []string{"foo", "bar"}
diff --git a/java/boot_jars.go b/java/boot_jars.go
index 900eb7a..e706547 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -87,6 +87,7 @@
 
 	rule := android.NewRuleBuilder()
 	checkBootJars := rule.Command().BuiltTool(ctx, "check_boot_jars").
+		Input(ctx.Config().HostToolPath(ctx, "dexdump")).
 		Input(android.PathForSource(ctx, "build/soong/scripts/check_boot_jars/package_allowed_list.txt"))
 
 	// If this is not an unbundled build and missing dependencies are not allowed
@@ -96,14 +97,9 @@
 	// Iterate over the module names on the boot classpath in order
 	for _, name := range android.SortedStringKeys(moduleToApex) {
 		if apexVariant, ok := nameToApexVariant[name]; ok {
-			if dep, ok := apexVariant.(Dependency); ok {
-				// Add the implementation jars for the module to be checked. This uses implementation
-				// and resources jar as that is what the previous make based check uses.
-				for _, jar := range dep.ImplementationAndResourcesJars() {
-					checkBootJars.Input(jar)
-				}
-			} else if _, ok := apexVariant.(*DexImport); ok {
-				// TODO(b/171479578): ignore deximport when doing package check until boot_jars.go can check dex jars.
+			if dep, ok := apexVariant.(interface{ DexJarBuildPath() android.Path }); ok {
+				// Add the dex implementation jar for the module to be checked.
+				checkBootJars.Input(dep.DexJarBuildPath())
 			} else {
 				ctx.Errorf("module %q is of type %q which is not supported as a boot jar", name, ctx.ModuleType(apexVariant))
 			}
diff --git a/java/builder.go b/java/builder.go
index 3043e46..cd35245 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -572,14 +572,7 @@
 }
 
 func GenerateMainClassManifest(ctx android.ModuleContext, outputFile android.WritablePath, mainClass string) {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.WriteFile,
-		Description: "manifest",
-		Output:      outputFile,
-		Args: map[string]string{
-			"content": "Main-Class: " + mainClass + "\n",
-		},
-	})
+	android.WriteFileRule(ctx, outputFile, "Main-Class: "+mainClass+"\n")
 }
 
 func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePath, inputFile android.Path) {
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 40a2280..cd395b1 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -163,7 +163,7 @@
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedSdkLibs() dexpreopt.LibraryPaths {
+func (d *DeviceHostConverter) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 20dbc66..a21fb76 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -33,11 +33,9 @@
 	isTest              bool
 	isPresignedPrebuilt bool
 
-	manifestFile     android.Path
-	usesLibs         []string
-	optionalUsesLibs []string
-	enforceUsesLibs  bool
-	libraryPaths     dexpreopt.LibraryPaths
+	manifestFile        android.Path
+	enforceUsesLibs     bool
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	builtInstalled string
 }
@@ -193,10 +191,8 @@
 		ProfileIsTextListing: profileIsTextListing,
 		ProfileBootListing:   profileBootListing,
 
-		EnforceUsesLibraries:  d.enforceUsesLibs,
-		OptionalUsesLibraries: d.optionalUsesLibs,
-		UsesLibraries:         d.usesLibs,
-		LibraryPaths:          d.libraryPaths,
+		EnforceUsesLibraries: d.enforceUsesLibs,
+		ClassLoaderContexts:  d.classLoaderContexts,
 
 		Archs:                   archs,
 		DexPreoptImages:         images,
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 629d34f..f9975ba 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -651,14 +651,8 @@
 		updatableBcpPackagesName := "updatable-bcp-packages.txt"
 		updatableBcpPackages := image.dir.Join(ctx, updatableBcpPackagesName)
 
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   android.WriteFile,
-			Output: updatableBcpPackages,
-			Args: map[string]string{
-				// WriteFile automatically adds the last end-of-line.
-				"content": strings.Join(updatablePackages, "\\n"),
-			},
-		})
+		// WriteFileRule automatically adds the last end-of-line.
+		android.WriteFileRule(ctx, updatableBcpPackages, strings.Join(updatablePackages, "\n"))
 
 		rule := android.NewRuleBuilder()
 		rule.MissingDeps(missingDeps)
@@ -720,13 +714,7 @@
 func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
 	data := dexpreopt.GetGlobalConfigRawData(ctx)
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:   android.WriteFile,
-		Output: path,
-		Args: map[string]string{
-			"content": string(data),
-		},
-	})
+	android.WriteFileRule(ctx, path, string(data))
 }
 
 // Export paths for default boot image to Make
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index ab31958..95fe5e1 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -51,7 +51,7 @@
 	dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList([]string{"platform:foo", "platform:bar", "platform:baz"})
 	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
 
-	ctx := testContext()
+	ctx := testContext(config)
 	RegisterDexpreoptBootJarsComponents(ctx)
 	run(t, ctx, config)
 
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index bbc8320..1f80e77 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -258,6 +258,8 @@
 			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").
 		FlagWithInput("--blocked ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
+		FlagWithInput("--blocked ",
+			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).
 		FlagWithInput("--unsupported ", android.PathForSource(
 			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index 7acaae7..34a4856 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -29,8 +29,8 @@
 	return config
 }
 
-func testContextWithHiddenAPI() *android.TestContext {
-	ctx := testContext()
+func testContextWithHiddenAPI(config android.Config) *android.TestContext {
+	ctx := testContext(config)
 	ctx.RegisterSingletonType("hiddenapi", hiddenAPISingletonFactory)
 	return ctx
 }
@@ -38,7 +38,7 @@
 func testHiddenAPIWithConfig(t *testing.T, config android.Config) *android.TestContext {
 	t.Helper()
 
-	ctx := testContextWithHiddenAPI()
+	ctx := testContextWithHiddenAPI(config)
 
 	run(t, ctx, config)
 	return ctx
diff --git a/java/java.go b/java/java.go
index 9f09051..a23c649 100644
--- a/java/java.go
+++ b/java/java.go
@@ -416,8 +416,8 @@
 	// manifest file to use instead of properties.Manifest
 	overrideManifest android.OptionalPath
 
-	// map of SDK libs exported by this java module to their build and install paths
-	exportedSdkLibs dexpreopt.LibraryPaths
+	// map of SDK version to class loader context
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	// list of plugins that this java module is exporting
 	exportedPluginJars android.Paths
@@ -509,7 +509,7 @@
 	ImplementationJars() android.Paths
 	ResourceJars() android.Paths
 	AidlIncludeDirs() android.Paths
-	ExportedSdkLibs() dexpreopt.LibraryPaths
+	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
 	ExportedPlugins() (android.Paths, []string)
 	SrcJarArgs() ([]string, android.Paths)
 	BaseModuleName() string
@@ -1027,7 +1027,8 @@
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.MaybeAddLibraryPath(ctx, dep.OptionalImplicitSdkLibrary(), dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(), true,
+					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
 			}
@@ -1038,7 +1039,7 @@
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1050,7 +1051,7 @@
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
 				deps.staticResourceJars = append(deps.staticResourceJars, dep.ResourceJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1902,8 +1903,8 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Module) ExportedSdkLibs() dexpreopt.LibraryPaths {
-	return j.exportedSdkLibs
+func (j *Module) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
 func (j *Module) ExportedPlugins() (android.Paths, []string) {
@@ -2041,7 +2042,7 @@
 		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
 	}
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.exportedSdkLibs = make(dexpreopt.LibraryPaths)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -2061,11 +2062,12 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), j.DexJarBuildPath(), j.DexJarInstallPath())
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+		j.DexJarBuildPath(), j.DexJarInstallPath())
 
 	// A non-SDK library may provide a <uses-library> (the name may be different from the module name).
 	if lib := proptools.String(j.usesLibraryProperties.Provides_uses_lib); lib != "" {
-		j.exportedSdkLibs.AddLibraryPath(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
+		j.classLoaderContexts.AddContext(ctx, lib, true, j.DexJarBuildPath(), j.DexJarInstallPath())
 	}
 
 	j.distFiles = j.GenerateTaggedDistFiles(ctx)
@@ -2224,6 +2226,9 @@
 type TestOptions struct {
 	// a list of extra test configuration files that should be installed with the module.
 	Extra_test_configs []string `android:"path,arch_variant"`
+
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
 }
 
 type testProperties struct {
@@ -2644,7 +2649,7 @@
 	dexJarFile android.Path
 
 	combinedClasspathFile android.Path
-	exportedSdkLibs       dexpreopt.LibraryPaths
+	classLoaderContexts   dexpreopt.ClassLoaderContextMap
 	exportAidlIncludeDirs android.Paths
 
 	hideApexVariantFromMake bool
@@ -2719,7 +2724,7 @@
 		TransformJetifier(ctx, outputFile, inputFile)
 	}
 	j.combinedClasspathFile = outputFile
-	j.exportedSdkLibs = make(dexpreopt.LibraryPaths)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
 
@@ -2733,7 +2738,7 @@
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddLibraryPaths(dep.ExportedSdkLibs())
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2742,7 +2747,8 @@
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.AddLibraryPath(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.classLoaderContexts.AddContext(ctx, otherName, dep.IsSharedLibrary(),
+					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
 	})
@@ -2757,7 +2763,8 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddLibraryPath(ctx, j.OptionalImplicitSdkLibrary(), outputFile, installFile)
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+		outputFile, installFile)
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
 
@@ -2839,8 +2846,8 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Import) ExportedSdkLibs() dexpreopt.LibraryPaths {
-	return j.exportedSdkLibs
+func (j *Import) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
 func (j *Import) ExportedPlugins() (android.Paths, []string) {
diff --git a/java/java_test.go b/java/java_test.go
index 6c0a908..845a03a 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -70,9 +70,9 @@
 	return config
 }
 
-func testContext() *android.TestContext {
+func testContext(config android.Config) *android.TestContext {
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	RegisterJavaBuildComponents(ctx)
 	RegisterAppBuildComponents(ctx)
 	RegisterAARBuildComponents(ctx)
@@ -92,8 +92,8 @@
 
 	ctx.PreDepsMutators(python.RegisterPythonPreDepsMutators)
 	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
-	ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory))
-	ctx.RegisterPreSingletonType("sdk_versions", android.SingletonFactoryAdaptor(sdkPreSingletonFactory))
+	ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(ctx.Context, OverlaySingletonFactory))
+	ctx.RegisterPreSingletonType("sdk_versions", android.SingletonFactoryAdaptor(ctx.Context, sdkPreSingletonFactory))
 
 	android.RegisterPrebuiltMutators(ctx)
 
@@ -115,7 +115,7 @@
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
@@ -129,12 +129,12 @@
 
 func testJavaErrorWithConfig(t *testing.T, pattern string, config android.Config) (*android.TestContext, android.Config) {
 	t.Helper()
-	ctx := testContext()
+	ctx := testContext(config)
 
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 	if len(errs) > 0 {
 		android.FailIfNoMatchingErrors(t, pattern, errs)
@@ -163,7 +163,7 @@
 
 func testJavaWithConfig(t *testing.T, config android.Config) (*android.TestContext, android.Config) {
 	t.Helper()
-	ctx := testContext()
+	ctx := testContext(config)
 	run(t, ctx, config)
 
 	return ctx, config
@@ -1379,8 +1379,8 @@
 	baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
 	barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
 
-	if len(jargen.Inputs) != 1 || jargen.Inputs[0].String() != foo.Output.String() {
-		t.Errorf("expected jargen inputs [%q], got %q", foo.Output.String(), jargen.Inputs.Strings())
+	if g, w := jargen.Implicits.Strings(), foo.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected jargen inputs [%q], got %q", w, g)
 	}
 
 	if !strings.Contains(bar.Args["classpath"], jargen.Output.String()) {
@@ -1440,7 +1440,7 @@
 				}
 `),
 	})
-	ctx := testContext()
+	ctx := testContext(config)
 	run(t, ctx, config)
 }
 
@@ -1458,7 +1458,7 @@
 				}
 `),
 	})
-	ctx := testContext()
+	ctx := testContext(config)
 	run(t, ctx, config)
 }
 
@@ -1593,8 +1593,8 @@
 	// test if baz has exported SDK lib names foo and bar to qux
 	qux := ctx.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := android.SortedStringKeys(quxLib.ExportedSdkLibs())
-		if w := []string{"bar", "foo", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
+		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
+		if w := []string{"foo", "bar", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
 			t.Errorf("qux should export %q but exports %q", w, sdkLibs)
 		}
 	}
diff --git a/java/robolectric.go b/java/robolectric.go
index 04fc117..62d1d99 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -389,8 +389,10 @@
 		}
 		runtimeFromSourceJar := android.OutputFileForModule(ctx, runtimeFromSourceModule, "")
 
+		// TODO(murj) Update this to ctx.Config().PlatformSdkCodename() once the platform
+		// classes like android.os.Build are updated to S.
 		runtimeName := fmt.Sprintf("android-all-%s-robolectric-r0.jar",
-			ctx.Config().PlatformSdkCodename())
+			"R")
 		installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar)
 		r.runtimes = append(r.runtimes, installedRuntime)
 	}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 21c03cd..13cd5f9 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -673,6 +673,11 @@
 	return c.namingScheme.apiModuleName(apiScope, c.moduleBase.BaseModuleName())
 }
 
+// If the SDK library is a shared library.
+func (c *commonToSdkLibraryAndImport) IsSharedLibrary() bool {
+	return c.sharedLibrary()
+}
+
 // The component names for different outputs of the java_sdk_library.
 //
 // They are similar to the names used for the child modules it creates
@@ -918,6 +923,9 @@
 	// jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise
 	// they are identical to the corresponding header jars.
 	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
+
+	// If the SDK library is a shared library.
+	IsSharedLibrary() bool
 }
 
 type SdkLibrary struct {
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 776069d..dc90ea3 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -356,7 +356,7 @@
 					config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 					config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 				}
-				ctx := testContext()
+				ctx := testContext(config)
 				run(t, ctx, config)
 
 				checkClasspath(t, ctx, true /* isJava8 */)
@@ -377,7 +377,7 @@
 					config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 					config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 				}
-				ctx := testContext()
+				ctx := testContext(config)
 				run(t, ctx, config)
 
 				checkClasspath(t, ctx, false /* isJava8 */)
@@ -401,7 +401,7 @@
 					config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 					config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 				}
-				ctx := testContext()
+				ctx := testContext(config)
 				run(t, ctx, config)
 
 				checkClasspath(t, ctx, true /* isJava8 */)
@@ -417,7 +417,7 @@
 					config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 					config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
 				}
-				ctx := testContext()
+				ctx := testContext(config)
 				run(t, ctx, config)
 
 				checkClasspath(t, ctx, false /* isJava8 */)
diff --git a/linkerconfig/linkerconfig_test.go b/linkerconfig/linkerconfig_test.go
index 13c276a..01f4657 100644
--- a/linkerconfig/linkerconfig_test.go
+++ b/linkerconfig/linkerconfig_test.go
@@ -56,9 +56,9 @@
 
 	config := android.TestArchConfig(buildDir, nil, bp, fs)
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("linker_config", linkerConfigFactory)
-	ctx.Register(config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
diff --git a/python/androidmk.go b/python/androidmk.go
index 8ad5889..040b6be 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -80,6 +80,10 @@
 			fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
 				strings.Join(android.AndroidMkDataPaths(p.data), " "))
 		}
+
+		if Bool(p.testProperties.Test_options.Unit_test) {
+			fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
+		}
 	})
 	base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
 }
diff --git a/python/python.go b/python/python.go
index 945e264..e4c8e94 100644
--- a/python/python.go
+++ b/python/python.go
@@ -300,7 +300,7 @@
 }
 
 func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool {
-	return hasSrcExt(p.properties.Srcs, protoExt)
+	return hasSrcExt(p.properties.Srcs, ext)
 }
 
 func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
diff --git a/python/python_test.go b/python/python_test.go
index 10b565a..64bc4f6 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -329,13 +329,13 @@
 	for _, d := range data {
 		t.Run(d.desc, func(t *testing.T) {
 			config := android.TestConfig(buildDir, nil, "", d.mockFiles)
-			ctx := android.NewTestContext()
+			ctx := android.NewTestContext(config)
 			ctx.PreDepsMutators(RegisterPythonPreDepsMutators)
 			ctx.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
 			ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
 			ctx.RegisterModuleType("python_defaults", defaultsFactory)
 			ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-			ctx.Register(config)
+			ctx.Register()
 			_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
 			android.FailIfErrored(t, testErrs)
 			_, actErrs := ctx.PrepareBuildActions(config)
diff --git a/python/test.go b/python/test.go
index 434e71a..f9baa46 100644
--- a/python/test.go
+++ b/python/test.go
@@ -26,6 +26,12 @@
 	android.RegisterModuleType("python_test", PythonTestFactory)
 }
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// the name of the test configuration (for example "AndroidTest.xml") that should be
 	// installed with the module.
@@ -38,6 +44,9 @@
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
 	Data []string `android:"path,arch_variant"`
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type testDecorator struct {
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 29e4bd7..f98360a 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -116,6 +116,9 @@
 		if !BoolDefault(test.Properties.Auto_gen_config, true) {
 			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
 		}
+		if Bool(test.Properties.Test_options.Unit_test) {
+			fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
+		}
 	})
 	// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 7cc0fc8..35a807b 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -258,5 +258,6 @@
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
 	deps.StaticLibs = append(deps.StaticLibs, b.ClangProperties.Static_libs...)
+	deps.HeaderLibs = append(deps.StaticLibs, b.ClangProperties.Header_libs...)
 	return deps
 }
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index c7ce42b..af04cfc 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -32,6 +32,7 @@
 			cflags: ["--clang-flag()"],
 			shared_libs: ["libfoo_shared"],
 			static_libs: ["libfoo_static"],
+			header_libs: ["libfoo_header"],
 		}
 		cc_library_shared {
 			name: "libfoo_shared",
@@ -41,6 +42,10 @@
 			name: "libfoo_static",
 			export_include_dirs: ["static_include"],
 		}
+		cc_library_headers {
+			name: "libfoo_header",
+			export_include_dirs: ["header_include"],
+		}
 		cc_defaults {
 			name: "cc_defaults_flags",
 			cflags: ["--default-flag"],
@@ -60,6 +65,9 @@
 	if !strings.Contains(libbindgen.Args["cflags"], "-Istatic_include") {
 		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
 	}
+	if !strings.Contains(libbindgen.Args["cflags"], "-Iheader_include") {
+		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
+	}
 	if !strings.Contains(libbindgen.Args["cflags"], "--default-flag") {
 		t.Errorf("rust_bindgen missing cflags defined in cc_defaults: cflags %#v", libbindgen.Args["cflags"])
 	}
diff --git a/rust/builder.go b/rust/builder.go
index 3e082dc..6079e30 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -15,6 +15,7 @@
 package rust
 
 import (
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -199,7 +200,9 @@
 		profileEmitArg := strings.TrimPrefix(cc.PwdPrefix(), "PWD=") + "/"
 
 		if outputFile.Ext() != "" {
-			gcnoFile = android.PathForModuleOut(ctx, pathtools.ReplaceExtension(outputFile.Base(), "gcno"))
+			// rustc seems to split the output filename at the first '.' when determining the gcno filename
+			// so we need to do the same here.
+			gcnoFile = android.PathForModuleOut(ctx, strings.Split(outputFile.Base(), ".")[0]+".gcno")
 			rustcFlags = append(rustcFlags, "-Z profile-emit="+profileEmitArg+android.PathForModuleOut(
 				ctx, pathtools.ReplaceExtension(outputFile.Base(), "gcda")).String())
 		} else {
@@ -235,7 +238,18 @@
 			},
 		})
 		implicits = append(implicits, outputs.Paths()...)
-		envVars = append(envVars, "OUT_DIR=$$PWD/"+moduleGenDir.String())
+
+		// We must calculate an absolute path for OUT_DIR since Rust's include! macro (which normally consumes this)
+		// assumes that paths are relative to the source file.
+		var outDirPrefix string
+		if !filepath.IsAbs(moduleGenDir.String()) {
+			// If OUT_DIR is not absolute, we use $$PWD to generate an absolute path (os.Getwd() returns '/')
+			outDirPrefix = "$$PWD/"
+		} else {
+			// If OUT_DIR is absolute, then moduleGenDir will be an absolute path, so we don't need to set this to anything.
+			outDirPrefix = ""
+		}
+		envVars = append(envVars, "OUT_DIR="+filepath.Join(outDirPrefix, moduleGenDir.String()))
 	}
 
 	if flags.Clippy {
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index 7815aab..132b7b8 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -65,8 +65,8 @@
 		t.Run("path="+tc.modulePath, func(t *testing.T) {
 
 			config := android.TestArchConfig(buildDir, nil, bp, fs)
-			ctx := CreateTestContext()
-			ctx.Register(config)
+			ctx := CreateTestContext(config)
+			ctx.Register()
 			_, errs := ctx.ParseFileList(".", []string{tc.modulePath + "Android.bp"})
 			android.FailIfErrored(t, errs)
 			_, errs = ctx.PrepareBuildActions(config)
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index a25523c..a6dba9e 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -152,8 +152,8 @@
 		t.Run("path="+tc.modulePath, func(t *testing.T) {
 
 			config := android.TestArchConfig(buildDir, nil, bp, fs)
-			ctx := CreateTestContext()
-			ctx.Register(config)
+			ctx := CreateTestContext(config)
+			ctx.Register()
 			_, errs := ctx.ParseFileList(".", []string{tc.modulePath + "Android.bp"})
 			android.FailIfErrored(t, errs)
 			_, errs = ctx.PrepareBuildActions(config)
diff --git a/rust/coverage_test.go b/rust/coverage_test.go
index 90155ca..e7f873e 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -160,7 +160,7 @@
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", fizzZipInputs)
 	}
 	if !android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_rlib_dylib-std_cov/librlib.gcno") ||
-		!android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_dylib_cov/libfoo.dylib.gcno") {
+		!android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_dylib_cov/libfoo.gcno") {
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", libfooZipInputs)
 	}
 	if !android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_cov/obj/foo.gcno") ||
diff --git a/rust/protobuf.go b/rust/protobuf.go
index ca40154..7d6e1fd 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -15,6 +15,9 @@
 package rust
 
 import (
+	"fmt"
+	"strings"
+
 	"android/soong/android"
 )
 
@@ -22,6 +25,10 @@
 	defaultProtobufFlags = []string{""}
 )
 
+const (
+	grpcSuffix = "_grpc"
+)
+
 type PluginType int
 
 const (
@@ -44,6 +51,9 @@
 
 	// List of additional flags to pass to aprotoc
 	Proto_flags []string `android:"arch_variant"`
+
+	// List of libraries which export include paths required for this module
+	Header_libs []string `android:"arch_variant,variant_prepend"`
 }
 
 type protobufDecorator struct {
@@ -72,6 +82,11 @@
 		ctx.PropertyErrorf("proto", "invalid path to proto file")
 	}
 
+	// Add exported dependency include paths
+	for _, include := range deps.depIncludePaths {
+		protoFlags.Flags = append(protoFlags.Flags, "-I"+include.String())
+	}
+
 	stem := proto.BaseSourceProvider.getStem(ctx)
 	// rust protobuf-codegen output <stem>.rs
 	stemFile := android.PathForModuleOut(ctx, stem+".rs")
@@ -79,17 +94,39 @@
 	modFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
 	// mod_<stem>.rs is the main/first output file to be included/compiled
 	outputs := android.WritablePaths{modFile, stemFile}
+	if proto.plugin == Grpc {
+		outputs = append(outputs, android.PathForModuleOut(ctx, stem+grpcSuffix+".rs"))
+	}
 	depFile := android.PathForModuleOut(ctx, "mod_"+stem+".d")
 
 	rule := android.NewRuleBuilder()
 	android.ProtoRule(ctx, rule, protoFile.Path(), protoFlags, protoFlags.Deps, outDir, depFile, outputs)
-	rule.Command().Text("printf '// @generated\\npub mod %s;\\n' '" + stem + "' >").Output(modFile)
+	rule.Command().Text("printf '" + proto.getModFileContents(ctx) + "' >").Output(modFile)
 	rule.Build(pctx, ctx, "protoc_"+protoFile.Path().Rel(), "protoc "+protoFile.Path().Rel())
 
 	proto.BaseSourceProvider.OutputFiles = android.Paths{modFile, stemFile}
 	return modFile
 }
 
+func (proto *protobufDecorator) getModFileContents(ctx ModuleContext) string {
+	stem := proto.BaseSourceProvider.getStem(ctx)
+	lines := []string{
+		"// @generated",
+		fmt.Sprintf("pub mod %s;", stem),
+	}
+
+	if proto.plugin == Grpc {
+		lines = append(lines, fmt.Sprintf("pub mod %s%s;", stem, grpcSuffix))
+		lines = append(
+			lines,
+			"pub mod empty {",
+			"    pub use protobuf::well_known_types::Empty;",
+			"}")
+	}
+
+	return strings.Join(lines, "\\n")
+}
+
 func (proto *protobufDecorator) setupPlugin(ctx ModuleContext, protoFlags android.ProtoFlags, outDir android.ModuleOutPath) (android.Paths, android.ProtoFlags) {
 	pluginPaths := []android.Path{}
 
@@ -118,6 +155,13 @@
 func (proto *protobufDecorator) SourceProviderDeps(ctx DepsContext, deps Deps) Deps {
 	deps = proto.BaseSourceProvider.SourceProviderDeps(ctx, deps)
 	deps.Rustlibs = append(deps.Rustlibs, "libprotobuf")
+	deps.HeaderLibs = append(deps.SharedLibs, proto.Properties.Header_libs...)
+
+	if proto.plugin == Grpc {
+		deps.Rustlibs = append(deps.Rustlibs, "libgrpcio", "libfutures")
+		deps.HeaderLibs = append(deps.HeaderLibs, "libprotobuf-cpp-full")
+	}
+
 	return deps
 }
 
diff --git a/rust/protobuf_test.go b/rust/protobuf_test.go
index 7c39071..845911f 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -28,6 +28,16 @@
 			proto: "buf.proto",
 			crate_name: "rust_proto",
 			source_stem: "buf",
+			shared_libs: ["libfoo_shared"],
+			static_libs: ["libfoo_static"],
+		}
+		cc_library_shared {
+			name: "libfoo_shared",
+			export_include_dirs: ["shared_include"],
+		}
+		cc_library_static {
+			name: "libfoo_static",
+			export_include_dirs: ["static_include"],
 		}
 	`)
 	// Check that libprotobuf is added as a dependency.
@@ -43,6 +53,13 @@
 		t.Errorf("expected %q in %q", w, cmd)
 	}
 
+	// Check exported include directories
+	if w := "-Ishared_include"; !strings.Contains(cmd, w) {
+		t.Errorf("expected %q in %q", w, cmd)
+	}
+	if w := "-Istatic_include"; !strings.Contains(cmd, w) {
+		t.Errorf("expected %q in %q", w, cmd)
+	}
 }
 
 func TestRustGrpcio(t *testing.T) {
@@ -52,6 +69,16 @@
 			proto: "buf.proto",
 			crate_name: "rust_grpcio",
 			source_stem: "buf",
+			shared_libs: ["libfoo_shared"],
+			static_libs: ["libfoo_static"],
+		}
+		cc_library_shared {
+			name: "libfoo_shared",
+			export_include_dirs: ["shared_include"],
+		}
+		cc_library_static {
+			name: "libfoo_static",
+			export_include_dirs: ["static_include"],
 		}
 	`)
 
@@ -61,10 +88,33 @@
 		t.Errorf("libprotobuf dependency missing for rust_grpcio (dependency missing from AndroidMkDylibs)")
 	}
 
+	// Check that libgrpcio is added as a dependency.
+	if !android.InList("libgrpcio", librust_grpcio_module.Properties.AndroidMkDylibs) {
+		t.Errorf("libgrpcio dependency missing for rust_grpcio (dependency missing from AndroidMkDylibs)")
+	}
+
+	// Check that libfutures is added as a dependency.
+	if !android.InList("libfutures", librust_grpcio_module.Properties.AndroidMkDylibs) {
+		t.Errorf("libfutures dependency missing for rust_grpcio (dependency missing from AndroidMkDylibs)")
+	}
+
 	// Make sure the correct plugin is being used.
-	librust_grpcio_out := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").Output("buf.rs")
+	librust_grpcio_out := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").Output("buf_grpc.rs")
 	cmd := librust_grpcio_out.RuleParams.Command
 	if w := "protoc-gen-grpc"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
 	}
+
+	// Check exported include directories
+	if w := "-Ishared_include"; !strings.Contains(cmd, w) {
+		t.Errorf("expected %q in %q", w, cmd)
+	}
+	if w := "-Istatic_include"; !strings.Contains(cmd, w) {
+		t.Errorf("expected %q in %q", w, cmd)
+	}
+
+	// Check that we're including the exported directory from libprotobuf-cpp-full
+	if w := "-Ilibprotobuf-cpp-full-includes"; !strings.Contains(cmd, w) {
+		t.Errorf("expected %q in %q", w, cmd)
+	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index e65b90e..5b94045 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -229,6 +229,7 @@
 	ProcMacros []string
 	SharedLibs []string
 	StaticLibs []string
+	HeaderLibs []string
 
 	CrtBegin, CrtEnd string
 }
@@ -838,6 +839,11 @@
 				directSharedLibDeps = append(directSharedLibDeps, ccDep)
 				mod.Properties.AndroidMkSharedLibs = append(mod.Properties.AndroidMkSharedLibs, depName)
 				exportDep = true
+			case cc.IsHeaderDepTag(depTag):
+				exportedInfo := ctx.OtherModuleProvider(dep, cc.FlagExporterInfoProvider).(cc.FlagExporterInfo)
+				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
+				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
+				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 			case depTag == cc.CrtBeginDepTag:
 				depPaths.CrtBegin = linkObject
 			case depTag == cc.CrtEndDepTag:
@@ -983,6 +989,8 @@
 		blueprint.Variation{Mutator: "link", Variation: "static"}),
 		cc.StaticDepTag(), deps.StaticLibs...)
 
+	actx.AddVariationDependencies(nil, cc.HeaderDepTag(), deps.HeaderLibs...)
+
 	crtVariations := cc.GetCrtVariations(ctx, mod)
 	if deps.CrtBegin != "" {
 		actx.AddVariationDependencies(crtVariations, cc.CrtBeginDepTag, deps.CrtBegin)
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 9b2f023..14bbd0b 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -142,8 +142,8 @@
 	if tctx.config == nil {
 		t.Fatalf("tctx.config not been generated yet. Please call generateConfig first.")
 	}
-	ctx := CreateTestContext()
-	ctx.Register(*tctx.config)
+	ctx := CreateTestContext(*tctx.config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(*tctx.config)
@@ -157,8 +157,8 @@
 	if tctx.config == nil {
 		t.Fatalf("tctx.config not been generated yet. Please call generateConfig first.")
 	}
-	ctx := CreateTestContext()
-	ctx.Register(*tctx.config)
+	ctx := CreateTestContext(*tctx.config)
+	ctx.Register()
 
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	if len(errs) > 0 {
diff --git a/rust/test.go b/rust/test.go
index bc7f53c..408e03a 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -19,6 +19,12 @@
 	"android/soong/tradefed"
 )
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// Disables the creation of a test-specific directory when used with
 	// relative_install_path. Useful if several tests need to be in the same
@@ -44,6 +50,9 @@
 
 	// if set, build with the standard Rust test harness. Defaults to true.
 	Test_harness *bool
+
+	// Test options.
+	Test_options TestOptions
 }
 
 // A test module is a binary module with extra --test compiler flag
diff --git a/rust/testing.go b/rust/testing.go
index 4a1894c..66877a9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -79,6 +79,13 @@
 			nocrt: true,
 			system_shared_libs: [],
 		}
+		cc_library {
+			name: "libprotobuf-cpp-full",
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
+			export_include_dirs: ["libprotobuf-cpp-full-includes"],
+		}
 		rust_library {
 			name: "libstd",
 			crate_name: "std",
@@ -103,13 +110,25 @@
 			srcs: ["foo.rs"],
 			host_supported: true,
 		}
+		rust_library {
+			name: "libgrpcio",
+			crate_name: "grpcio",
+			srcs: ["foo.rs"],
+			host_supported: true,
+		}
+		rust_library {
+			name: "libfutures",
+			crate_name: "futures",
+			srcs: ["foo.rs"],
+			host_supported: true,
+		}
 
 ` + cc.GatherRequiredDepsForTest(android.NoOsType)
 	return bp
 }
 
-func CreateTestContext() *android.TestContext {
-	ctx := android.NewTestArchContext()
+func CreateTestContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
 	android.RegisterPrebuiltMutators(ctx)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index ef5565d..89fb1a5 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -37,6 +37,14 @@
   OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
 }
 
+readonly SOONG_OUT=${OUT_DIR}/soong
+mkdir -p ${SOONG_OUT}
+
+# Some Soong build rules may require this, and the failure mode if it's missing
+# is confusing (b/172548608).
+readonly BUILD_NUMBER="$(my_get_build_var BUILD_NUMBER)"
+echo -n ${BUILD_NUMBER} > ${SOONG_OUT}/build_number.txt
+
 readonly PLATFORM_SDK_VERSION="$(my_get_build_var PLATFORM_SDK_VERSION)"
 readonly PLATFORM_VERSION="$(my_get_build_var PLATFORM_VERSION)"
 PLATFORM_VERSION_ALL_CODENAMES="$(my_get_build_var PLATFORM_VERSION_ALL_CODENAMES)"
@@ -61,8 +69,6 @@
   USE_GOMA=false
 fi
 
-readonly SOONG_OUT=${OUT_DIR}/soong
-mkdir -p ${SOONG_OUT}
 readonly SOONG_VARS=${SOONG_OUT}/soong.variables
 
 # Aml_abis: true
@@ -73,6 +79,8 @@
 #   -  Enable Bionic on host as ART needs prebuilts for it.
 cat > ${SOONG_VARS}.new << EOF
 {
+    "BuildNumberFile": "build_number.txt",
+
     "Platform_sdk_version": ${PLATFORM_SDK_VERSION},
     "Platform_sdk_codename": "${PLATFORM_VERSION}",
     "Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index cf4ef27..c271211 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -3,13 +3,14 @@
 """
 Check boot jars.
 
-Usage: check_boot_jars.py <package_allow_list_file> <jar1> <jar2> ...
+Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> <jar2> ...
 """
 import logging
 import os.path
 import re
 import subprocess
 import sys
+import xml.etree.ElementTree
 
 
 # The compiled allow list RE.
@@ -38,46 +39,57 @@
     return False
   return True
 
-
-def CheckJar(allow_list_path, jar):
-  """Check a jar file.
+def CheckDexJar(dexdump_path, allow_list_path, jar):
+  """Check a dex jar file.
   """
-  # Get the list of files inside the jar file.
-  p = subprocess.Popen(args='jar tf %s' % jar,
+  # Use dexdump to generate the XML representation of the dex jar file.
+  p = subprocess.Popen(args='%s -l xml %s' % (dexdump_path, jar),
       stdout=subprocess.PIPE, shell=True)
   stdout, _ = p.communicate()
   if p.returncode != 0:
     return False
-  items = stdout.split()
-  classes = 0
-  for f in items:
-    if f.endswith('.class'):
-      classes += 1
-      package_name = os.path.dirname(f)
-      package_name = package_name.replace('/', '.')
-      if not package_name or not allow_list_re.match(package_name):
-        print >> sys.stderr, ('Error: %s contains class file %s, whose package name %s is empty or'
-                              ' not in the allow list %s of packages allowed on the bootclasspath.'
-                              % (jar, f, package_name, allow_list_path))
-        return False
-  if classes == 0:
-    print >> sys.stderr, ('Error: %s does not contain any class files.' % jar)
+
+  packages = 0
+  try:
+    # TODO(b/172063475) - improve performance
+    root = xml.etree.ElementTree.fromstring(stdout)
+  except xml.etree.ElementTree.ParseError as e:
+    print >> sys.stderr, 'Error processing jar %s - %s' % (jar, e)
+    print >> sys.stderr, stdout
+    return False
+  for package_elt in root.iterfind('package'):
+    packages += 1
+    package_name = package_elt.get('name')
+    if not package_name or not allow_list_re.match(package_name):
+      # Report the name of a class in the package as it is easier to navigate to
+      # the source of a concrete class than to a package which is often required
+      # to investigate this failure.
+      class_name = package_elt[0].get('name')
+      if package_name != "":
+        class_name = package_name + "." + class_name
+      print >> sys.stderr, ('Error: %s contains class file %s, whose package name "%s" is empty or'
+                            ' not in the allow list %s of packages allowed on the bootclasspath.'
+                            % (jar, class_name, package_name, allow_list_path))
+      return False
+  if packages == 0:
+    print >> sys.stderr, ('Error: %s does not contain any packages.' % jar)
     return False
   return True
 
 
 def main(argv):
-  if len(argv) < 2:
+  if len(argv) < 3:
     print __doc__
     return 1
-  allow_list_path = argv[0]
+  dexdump_path = argv[0]
+  allow_list_path = argv[1]
 
   if not LoadAllowList(allow_list_path):
     return 1
 
   passed = True
-  for jar in argv[1:]:
-    if not CheckJar(allow_list_path, jar):
+  for jar in argv[2:]:
+    if not CheckDexJar(dexdump_path, allow_list_path, jar):
       passed = False
   if not passed:
     return 1
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 84e4f28..b1eebe9 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -109,6 +109,7 @@
     name: "mysdk_sdkmember@current",
     sdk_member_name: "sdkmember",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     stl: "none",
@@ -131,6 +132,7 @@
     name: "sdkmember",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "64",
@@ -353,6 +355,7 @@
     name: "mysdk_crtobj@current",
     sdk_member_name: "crtobj",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     sanitize: {
@@ -372,6 +375,7 @@
     name: "crtobj",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     sanitize: {
@@ -480,6 +484,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -511,6 +516,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -575,6 +581,7 @@
     name: "mymodule_exports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     arch: {
@@ -591,6 +598,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     arch: {
         arm64: {
@@ -654,6 +662,7 @@
     name: "myexports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -687,6 +696,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -802,6 +812,7 @@
     name: "myexports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -822,6 +833,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -841,6 +853,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -861,6 +874,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -933,6 +947,7 @@
     name: "mymodule_exports_linker@current",
     sdk_member_name: "linker",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -959,6 +974,7 @@
     name: "linker",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1167,6 +1183,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1188,6 +1205,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     shared_libs: [
@@ -1208,6 +1226,7 @@
     name: "mysdk_myothernativelib@current",
     sdk_member_name: "myothernativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1226,6 +1245,7 @@
     name: "myothernativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     system_shared_libs: ["libm"],
@@ -1243,6 +1263,7 @@
     name: "mysdk_mysystemnativelib@current",
     sdk_member_name: "mysystemnativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1260,6 +1281,7 @@
     name: "mysystemnativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     arch: {
@@ -1327,6 +1349,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1355,6 +1378,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     sdk_version: "minimum",
@@ -1449,6 +1473,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1482,6 +1507,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1572,6 +1598,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1592,6 +1619,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -1660,6 +1688,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1687,6 +1716,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1769,6 +1799,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     recovery_available: true,
     vendor_available: true,
@@ -1799,6 +1830,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     recovery_available: true,
     vendor_available: true,
     stl: "none",
@@ -1877,6 +1909,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1899,6 +1932,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1964,6 +1998,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -1973,6 +2008,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -2016,6 +2052,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -2038,6 +2075,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -2113,6 +2151,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "both",
@@ -2140,6 +2179,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "both",
@@ -2220,6 +2260,7 @@
     name: "mysdk_sslnil@current",
     sdk_member_name: "sslnil",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     arch: {
@@ -2236,6 +2277,7 @@
     name: "sslnil",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     arch: {
         arm64: {
@@ -2251,6 +2293,7 @@
     name: "mysdk_sslempty@current",
     sdk_member_name: "sslempty",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     system_shared_libs: [],
@@ -2268,6 +2311,7 @@
     name: "sslempty",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     system_shared_libs: [],
     arch: {
@@ -2284,6 +2328,7 @@
     name: "mysdk_sslnonempty@current",
     sdk_member_name: "sslnonempty",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     system_shared_libs: ["mysdk_sslnil@current"],
@@ -2301,6 +2346,7 @@
     name: "sslnonempty",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     system_shared_libs: ["sslnil"],
     arch: {
@@ -2350,6 +2396,7 @@
     name: "mysdk_sslvariants@current",
     sdk_member_name: "sslvariants",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     compile_multilib: "both",
@@ -2381,6 +2428,7 @@
     name: "sslvariants",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     compile_multilib: "both",
     target: {
@@ -2456,6 +2504,7 @@
     name: "mysdk_stubslib@current",
     sdk_member_name: "stubslib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     stubs: {
@@ -2479,6 +2528,7 @@
     name: "stubslib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     stubs: {
         versions: [
@@ -2537,6 +2587,7 @@
     name: "mysdk_stubslib@current",
     sdk_member_name: "stubslib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     compile_multilib: "both",
@@ -2572,6 +2623,7 @@
     name: "stubslib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     compile_multilib: "both",
     stubs: {
@@ -2645,6 +2697,7 @@
     name: "mysdk_mylib@current",
     sdk_member_name: "mylib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     unique_host_soname: true,
@@ -2674,6 +2727,7 @@
     name: "mylib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     unique_host_soname: true,
     compile_multilib: "both",
@@ -2755,6 +2809,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -2772,6 +2827,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
diff --git a/sdk/exports_test.go b/sdk/exports_test.go
index aa1200f..1c59244 100644
--- a/sdk/exports_test.go
+++ b/sdk/exports_test.go
@@ -50,6 +50,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -57,6 +58,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 731e528..ec8ebb3 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -122,6 +122,7 @@
     name: "mysdk_sdkmember@current",
     sdk_member_name: "sdkmember",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/sdkmember.jar"],
 }
 
@@ -129,6 +130,7 @@
     name: "sdkmember",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/sdkmember.jar"],
 }
 
@@ -247,6 +249,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -254,6 +257,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -302,6 +306,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -311,6 +316,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -357,6 +363,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -372,6 +379,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -426,6 +434,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -433,6 +442,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -481,6 +491,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -490,6 +501,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -535,6 +547,7 @@
     name: "myexports_myjavatests@current",
     sdk_member_name: "myjavatests",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavatests.jar"],
     test_config: "java/myjavatests-AndroidTest.xml",
 }
@@ -543,6 +556,7 @@
     name: "myjavatests",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavatests.jar"],
     test_config: "java/myjavatests-AndroidTest.xml",
 }
@@ -588,6 +602,7 @@
     name: "myexports_myjavatests@current",
     sdk_member_name: "myjavatests",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavatests.jar"],
@@ -598,6 +613,7 @@
     name: "myjavatests",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavatests.jar"],
@@ -655,6 +671,7 @@
     name: "mysdk_exported-system-module@current",
     sdk_member_name: "exported-system-module",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/exported-system-module.jar"],
 }
 
@@ -662,6 +679,7 @@
     name: "exported-system-module",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/exported-system-module.jar"],
 }
 
@@ -669,6 +687,7 @@
     name: "mysdk_system-module@current",
     sdk_member_name: "system-module",
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/system-module.jar"],
 }
 
@@ -676,6 +695,7 @@
     name: "mysdk_system-module",
     prefer: false,
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/system-module.jar"],
 }
 
@@ -747,6 +767,7 @@
     name: "mysdk_system-module@current",
     sdk_member_name: "system-module",
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/system-module.jar"],
@@ -756,6 +777,7 @@
     name: "mysdk_system-module",
     prefer: false,
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/system-module.jar"],
@@ -836,6 +858,7 @@
     name: "myexports_hostjavalib@current",
     sdk_member_name: "hostjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/hostjavalib.jar"],
@@ -845,6 +868,7 @@
     name: "hostjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/hostjavalib.jar"],
@@ -854,6 +878,7 @@
     name: "myexports_androidjavalib@current",
     sdk_member_name: "androidjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/androidjavalib.jar"],
 }
 
@@ -861,6 +886,7 @@
     name: "androidjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/androidjavalib.jar"],
 }
 
@@ -868,6 +894,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -883,6 +910,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -1045,6 +1073,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
@@ -1059,6 +1088,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
@@ -1112,6 +1142,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
@@ -1126,6 +1157,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
@@ -1547,6 +1579,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     doctag_files: ["doctags/docs/known_doctags"],
     public: {
@@ -1562,6 +1595,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     doctag_files: ["doctags/docs/known_doctags"],
     public: {
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 2e6c62a..c4dc41b 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -181,6 +181,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -192,6 +193,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -199,6 +201,7 @@
     name: "mysdk_mypublicjavalib@current",
     sdk_member_name: "mypublicjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mypublicjavalib.jar"],
 }
 
@@ -206,6 +209,7 @@
     name: "mypublicjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mypublicjavalib.jar"],
 }
 
@@ -217,6 +221,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mydefaultedjavalib.jar"],
 }
 
@@ -228,6 +233,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mydefaultedjavalib.jar"],
 }
 
@@ -238,6 +244,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myprivatejavalib.jar"],
 }
 
@@ -248,6 +255,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myprivatejavalib.jar"],
 }
 
diff --git a/sdk/testing.go b/sdk/testing.go
index 0b280ef..5f520e5 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -80,7 +80,7 @@
 		}
 	}
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 
 	// Enable androidmk support.
 	// * Register the singleton
@@ -129,7 +129,7 @@
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx, config
 }
diff --git a/sdk/update.go b/sdk/update.go
index 7bf5dea..ba63542 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -735,6 +735,24 @@
 		}
 	}
 
+	// Where available copy apex_available properties from the member.
+	if apexAware, ok := variant.(interface{ ApexAvailable() []string }); ok {
+		apexAvailable := apexAware.ApexAvailable()
+		if len(apexAvailable) == 0 {
+			// //apex_available:platform is the default.
+			apexAvailable = []string{android.AvailableToPlatform}
+		}
+
+		// Add in any baseline apex available settings.
+		apexAvailable = append(apexAvailable, apex.BaselineApexAvailable(member.Name())...)
+
+		// Remove duplicates and sort.
+		apexAvailable = android.FirstUniqueStrings(apexAvailable)
+		sort.Strings(apexAvailable)
+
+		m.AddProperty("apex_available", apexAvailable)
+	}
+
 	deviceSupported := false
 	hostSupported := false
 
@@ -749,22 +767,6 @@
 
 	addHostDeviceSupportedProperties(deviceSupported, hostSupported, m)
 
-	// Where available copy apex_available properties from the member.
-	if apexAware, ok := variant.(interface{ ApexAvailable() []string }); ok {
-		apexAvailable := apexAware.ApexAvailable()
-
-		// Add in any baseline apex available settings.
-		apexAvailable = append(apexAvailable, apex.BaselineApexAvailable(member.Name())...)
-
-		if len(apexAvailable) > 0 {
-			// Remove duplicates and sort.
-			apexAvailable = android.FirstUniqueStrings(apexAvailable)
-			sort.Strings(apexAvailable)
-
-			m.AddProperty("apex_available", apexAvailable)
-		}
-	}
-
 	// Disable installation in the versioned module of those modules that are ever installable.
 	if installable, ok := variant.(interface{ EverInstallable() bool }); ok {
 		if installable.EverInstallable() {
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 0aa607b..c664461 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -45,13 +45,13 @@
 
 	config := android.TestArchConfig(buildDir, nil, bp, fs)
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("sh_test", ShTestFactory)
 	ctx.RegisterModuleType("sh_test_host", ShTestHostFactory)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
 
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
diff --git a/shared/paths.go b/shared/paths.go
index f5dc5ac..24ba057 100644
--- a/shared/paths.go
+++ b/shared/paths.go
@@ -24,3 +24,17 @@
 func TempDirForOutDir(outDir string) (tempPath string) {
 	return filepath.Join(outDir, ".temp")
 }
+
+// BazelMetricsDir returns the path where a set of bazel profile
+// files are stored for later processed by the metrics pipeline.
+func BazelMetricsDir(outDir string) string {
+	return filepath.Join(outDir, "bazel_metrics")
+}
+
+// BazelMetricsFilename returns the bazel profile filename based
+// on the action name. This is to help to store a set of bazel
+// profiles since bazel may execute multiple times during a single
+// build.
+func BazelMetricsFilename(outDir, actionName string) string {
+	return filepath.Join(BazelMetricsDir(outDir), actionName+"_bazel_profile.gz")
+}
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index 7a79a1b..b8b93f6 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -57,7 +57,7 @@
 
 func testContext(config android.Config) *android.TestContext {
 
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	java.RegisterJavaBuildComponents(ctx)
 	java.RegisterAppBuildComponents(ctx)
 	java.RegisterSystemModulesBuildComponents(ctx)
@@ -76,7 +76,7 @@
 
 	ctx.RegisterModuleType("sysprop_library", syspropLibraryFactory)
 
-	ctx.Register(config)
+	ctx.Register()
 
 	return ctx
 }
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index ca9ad43..2d36f67 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -19,9 +19,17 @@
 	"os"
 	"path/filepath"
 	"strings"
+
+	"android/soong/shared"
+	"android/soong/ui/metrics"
 )
 
+// Main entry point to construct the Bazel build command line, environment
+// variables and post-processing steps (e.g. converge output directories)
 func runBazel(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunBazel, "bazel")
+	defer ctx.EndTrace()
+
 	// "droid" is the default ninja target.
 	// TODO(b/160568333): stop hardcoding 'droid' to support building any
 	// Ninja target.
@@ -33,41 +41,86 @@
 		outputGroups = strings.Join(config.ninjaArgs, ",")
 	}
 
+	// Environment variables are the primary mechanism to pass information from
+	// soong_ui configuration or context to Bazel.
+	//
+	// Use *_NINJA variables to pass the root-relative path of the combined,
+	// kati-generated, soong-generated, and packaging Ninja files to Bazel.
+	// Bazel reads these from the lunch() repository rule.
+	config.environ.Set("COMBINED_NINJA", config.CombinedNinjaFile())
+	config.environ.Set("KATI_NINJA", config.KatiBuildNinjaFile())
+	config.environ.Set("PACKAGE_NINJA", config.KatiPackageNinjaFile())
+	config.environ.Set("SOONG_NINJA", config.SoongNinjaFile())
+
+	// `tools/bazel` is the default entry point for executing Bazel in the AOSP
+	// source tree.
 	bazelExecutable := filepath.Join("tools", "bazel")
 	cmd := Command(ctx, config, "bazel", bazelExecutable)
 
-	if extra_startup_args, ok := cmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
-		cmd.Args = append(cmd.Args, strings.Fields(extra_startup_args)...)
+	// Append custom startup flags to the Bazel command. Startup flags affect
+	// the Bazel server itself, and any changes to these flags would incur a
+	// restart of the server, losing much of the in-memory incrementality.
+	if extraStartupArgs, ok := cmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
+		cmd.Args = append(cmd.Args, strings.Fields(extraStartupArgs)...)
 	}
 
+	// Start constructing the `build` command.
+	actionName := "build"
 	cmd.Args = append(cmd.Args,
-		"build",
+		actionName,
+		// Use output_groups to select the set of outputs to produce from a
+		// ninja_build target.
 		"--output_groups="+outputGroups,
+		// Generate a performance profile
+		"--profile="+filepath.Join(shared.BazelMetricsFilename(config.OutDir(), actionName)),
+		"--slim_profile=true",
 	)
 
-	if extra_build_args, ok := cmd.Environment.Get("BAZEL_BUILD_ARGS"); ok {
-		cmd.Args = append(cmd.Args, strings.Fields(extra_build_args)...)
+	// Append custom build flags to the Bazel command. Changes to these flags
+	// may invalidate Bazel's analysis cache.
+	if extraBuildArgs, ok := cmd.Environment.Get("BAZEL_BUILD_ARGS"); ok {
+		cmd.Args = append(cmd.Args, strings.Fields(extraBuildArgs)...)
 	}
 
+	// Append the label of the default ninja_build target.
 	cmd.Args = append(cmd.Args,
 		"//:"+config.TargetProduct()+"-"+config.TargetBuildVariant(),
 	)
 
+	// Ensure that the PATH environment variable value used in the action
+	// environment is the restricted set computed from soong_ui, and not a
+	// user-provided one, for hermeticity reasons.
+	if pathEnvValue, ok := config.environ.Get("PATH"); ok {
+		cmd.Environment.Set("PATH", pathEnvValue)
+		cmd.Args = append(cmd.Args, "--action_env=PATH="+pathEnvValue)
+	}
+
 	cmd.Environment.Set("DIST_DIR", config.DistDir())
 	cmd.Environment.Set("SHELL", "/bin/bash")
 
+	// Print the full command line for debugging purposes.
 	ctx.Println(cmd.Cmd)
+
+	// Execute the command at the root of the directory.
 	cmd.Dir = filepath.Join(config.OutDir(), "..")
 	ctx.Status.Status("Starting Bazel..")
+
+	// Execute the build command.
 	cmd.RunAndStreamOrFatal()
 
+	// Post-processing steps start here. Once the Bazel build completes, the
+	// output files are still stored in the execution root, not in $OUT_DIR.
+	// Ensure that the $OUT_DIR contains the expected set of files by symlinking
+	// the files from the execution root's output direction into $OUT_DIR.
+
 	// Obtain the Bazel output directory for ninja_build.
 	infoCmd := Command(ctx, config, "bazel", bazelExecutable)
 
-	if extra_startup_args, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
-		infoCmd.Args = append(infoCmd.Args, strings.Fields(extra_startup_args)...)
+	if extraStartupArgs, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
+		infoCmd.Args = append(infoCmd.Args, strings.Fields(extraStartupArgs)...)
 	}
 
+	// Obtain the output directory path in the execution root.
 	infoCmd.Args = append(infoCmd.Args,
 		"info",
 		"output_path",
diff --git a/ui/build/config.go b/ui/build/config.go
index e57c730..229bd5c 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -274,6 +274,16 @@
 		}
 	}
 
+	bpd := shared.BazelMetricsDir(ret.OutDir())
+	if err := os.RemoveAll(bpd); err != nil {
+		ctx.Fatalf("Unable to remove bazel profile directory %q: %v", bpd, err)
+	}
+	if ret.UseBazel() {
+		if err := os.MkdirAll(bpd, 0777); err != nil {
+			ctx.Fatalf("Failed to create bazel profile directory %q: %v", bpd, err)
+		}
+	}
+
 	c := Config{ret}
 	storeConfigMetrics(ctx, c)
 	return c
@@ -693,6 +703,10 @@
 	return c.ninjaArgs
 }
 
+func (c *configImpl) BazelOutDir() string {
+	return filepath.Join(c.OutDir(), "bazel")
+}
+
 func (c *configImpl) SoongOutDir() string {
 	return filepath.Join(c.OutDir(), "soong")
 }
@@ -840,6 +854,16 @@
 	return false
 }
 
+func (c *configImpl) UseBazel() bool {
+	if v, ok := c.environ.Get("USE_BAZEL"); ok {
+		v = strings.TrimSpace(v)
+		if v != "" && v != "false" {
+			return true
+		}
+	}
+	return false
+}
+
 func (c *configImpl) StartRBE() bool {
 	if !c.UseRBE() {
 		return false
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 7a85657..2eb84ca 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -27,8 +27,10 @@
 	"android/soong/ui/metrics"
 )
 
-// This file provides an interface to the Finder for use in Soong UI
-// This file stores configuration information about which files to find
+// This file provides an interface to the Finder type for soong_ui. Finder is
+// used to recursively traverse the source tree to gather paths of files, such
+// as Android.bp or Android.mk, and store the lists/database of paths in files
+// under `$OUT_DIR/.module_paths`. This directory can also be dist'd.
 
 // NewSourceFinder returns a new Finder configured to search for source files.
 // Callers of NewSourceFinder should call <f.Shutdown()> when done
@@ -36,14 +38,18 @@
 	ctx.BeginTrace(metrics.RunSetupTool, "find modules")
 	defer ctx.EndTrace()
 
+	// Set up the working directory for the Finder.
 	dir, err := os.Getwd()
 	if err != nil {
 		ctx.Fatalf("No working directory for module-finder: %v", err.Error())
 	}
 	filesystem := fs.OsFs
 
-	// if the root dir is ignored, then the subsequent error messages are very confusing,
-	// so check for that upfront
+	// .out-dir and .find-ignore are markers for Finder to ignore siblings and
+	// subdirectories of the directory Finder finds them in, hence stopping the
+	// search recursively down those branches. It's possible that these files
+	// are in the root directory, and if they are, then the subsequent error
+	// messages are very confusing, so check for that here.
 	pruneFiles := []string{".out-dir", ".find-ignore"}
 	for _, name := range pruneFiles {
 		prunePath := filepath.Join(dir, name)
@@ -53,22 +59,34 @@
 		}
 	}
 
+	// Set up configuration parameters for the Finder cache.
 	cacheParams := finder.CacheParams{
 		WorkingDirectory: dir,
 		RootDirs:         []string{"."},
 		ExcludeDirs:      []string{".git", ".repo"},
 		PruneFiles:       pruneFiles,
 		IncludeFiles: []string{
+			// Kati build definitions.
 			"Android.mk",
+			// Product configuration files.
 			"AndroidProducts.mk",
+			// General Soong build definitions, using the Blueprint syntax.
 			"Android.bp",
+			// build/blueprint build definitions, using the Blueprint syntax.
 			"Blueprints",
+			// Bazel build definitions.
 			"BUILD.bazel",
+			// Kati clean definitions.
 			"CleanSpec.mk",
+			// Ownership definition.
 			"OWNERS",
+			// Test configuration for modules in directories that contain this
+			// file.
 			"TEST_MAPPING",
+			// Bazel top-level file to mark a directory as a Bazel workspace.
 			"WORKSPACE",
 		},
+		// Bazel Starlark configuration files.
 		IncludeSuffixes: []string{".bzl"},
 	}
 	dumpDir := config.FileListDir()
@@ -80,6 +98,7 @@
 	return f
 }
 
+// Finds the list of Bazel-related files (BUILD, WORKSPACE and Starlark) in the tree.
 func findBazelFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
 	matches := []string{}
 	for _, foundName := range entries.FileNames {
@@ -98,12 +117,21 @@
 	dumpDir := config.FileListDir()
 	os.MkdirAll(dumpDir, 0777)
 
+	// Stop searching a subdirectory recursively after finding an Android.mk.
 	androidMks := f.FindFirstNamedAt(".", "Android.mk")
 	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export module list: %v", err)
 	}
 
+	// Stop searching a subdirectory recursively after finding a CleanSpec.mk.
+	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
+	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	// Only consider AndroidProducts.mk in device/, vendor/ and product/, recursively in these directories.
 	androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
@@ -112,31 +140,30 @@
 		ctx.Fatalf("Could not export product list: %v", err)
 	}
 
+	// Recursively look for all Bazel related files.
 	bazelFiles := f.FindMatching(".", findBazelFiles)
 	err = dumpListToFile(ctx, config, bazelFiles, filepath.Join(dumpDir, "bazel.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export bazel BUILD list: %v", err)
 	}
 
-	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
-	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
-	if err != nil {
-		ctx.Fatalf("Could not export module list: %v", err)
-	}
-
+	// Recursively look for all OWNERS files.
 	owners := f.FindNamedAt(".", "OWNERS")
 	err = dumpListToFile(ctx, config, owners, filepath.Join(dumpDir, "OWNERS.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find OWNERS: %v", err)
 	}
 
+	// Recursively look for all TEST_MAPPING files.
 	testMappings := f.FindNamedAt(".", "TEST_MAPPING")
 	err = dumpListToFile(ctx, config, testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
 	}
 
+	// Recursively look for all Android.bp files
 	androidBps := f.FindNamedAt(".", "Android.bp")
+	// The files are named "Blueprints" only in the build/blueprint directory.
 	androidBps = append(androidBps, f.FindNamedAt("build/blueprint", "Blueprints")...)
 	if len(androidBps) == 0 {
 		ctx.Fatalf("No Android.bp found")
@@ -148,10 +175,12 @@
 
 	if config.Dist() {
 		f.WaitForDbDump()
+		// Dist the files.db plain text database.
 		distFile(ctx, config, f.DbPath, "module_paths")
 	}
 }
 
+// Write the .list files to disk.
 func dumpListToFile(ctx Context, config Config, list []string, filePath string) (err error) {
 	desiredText := strings.Join(list, "\n")
 	desiredBytes := []byte(desiredText)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index b20237c..bb5cbf0 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -133,6 +133,13 @@
 			"-j", strconv.Itoa(config.Parallel()),
 			"--frontend_file", fifo,
 			"-f", filepath.Join(config.SoongOutDir(), file))
+
+		// For Bazel mixed builds.
+		cmd.Environment.Set("BAZEL_PATH", "./tools/bazel")
+		cmd.Environment.Set("BAZEL_HOME", filepath.Join(config.BazelOutDir(), "bazelhome"))
+		cmd.Environment.Set("BAZEL_OUTPUT_BASE", filepath.Join(config.BazelOutDir(), "output"))
+		cmd.Environment.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
+
 		cmd.Environment.Set("SOONG_SANDBOX_SOONG_BUILD", "true")
 		cmd.Sandbox = soongSandbox
 		cmd.RunAndStreamOrFatal()
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index 9434924..7031042 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -32,6 +32,7 @@
 	RunSetupTool    = "setup"
 	RunShutdownTool = "shutdown"
 	RunSoong        = "soong"
+	RunBazel        = "bazel"
 	TestRun         = "test"
 	Total           = "total"
 )
@@ -53,17 +54,14 @@
 	switch perf.GetName() {
 	case RunKati:
 		m.metrics.KatiRuns = append(m.metrics.KatiRuns, &perf)
-		break
 	case RunSoong:
 		m.metrics.SoongRuns = append(m.metrics.SoongRuns, &perf)
-		break
+	case RunBazel:
+		m.metrics.BazelRuns = append(m.metrics.BazelRuns, &perf)
 	case PrimaryNinja:
 		m.metrics.NinjaRuns = append(m.metrics.NinjaRuns, &perf)
-		break
 	case Total:
 		m.metrics.Total = &perf
-	default:
-		// ignored
 	}
 }
 
@@ -80,13 +78,10 @@
 		switch k {
 		case "BUILD_ID":
 			m.metrics.BuildId = proto.String(v)
-			break
 		case "PLATFORM_VERSION_CODENAME":
 			m.metrics.PlatformVersionCodename = proto.String(v)
-			break
 		case "TARGET_PRODUCT":
 			m.metrics.TargetProduct = proto.String(v)
-			break
 		case "TARGET_BUILD_VARIANT":
 			switch v {
 			case "user":
@@ -95,8 +90,6 @@
 				m.metrics.TargetBuildVariant = soong_metrics_proto.MetricsBase_USERDEBUG.Enum()
 			case "eng":
 				m.metrics.TargetBuildVariant = soong_metrics_proto.MetricsBase_ENG.Enum()
-			default:
-				// ignored
 			}
 		case "TARGET_ARCH":
 			m.metrics.TargetArch = m.getArch(v)
@@ -118,8 +111,6 @@
 			m.metrics.HostCross_2NdArch = proto.String(v)
 		case "OUT_DIR":
 			m.metrics.OutDir = proto.String(v)
-		default:
-			// ignored
 		}
 	}
 }
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 5db5ae5..fc2cfa4 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -205,10 +205,12 @@
 	// The system resource information such as total physical memory.
 	SystemResourceInfo *SystemResourceInfo `protobuf:"bytes,25,opt,name=system_resource_info,json=systemResourceInfo" json:"system_resource_info,omitempty"`
 	// The build command that the user entered to the build system.
-	BuildCommand         *string  `protobuf:"bytes,26,opt,name=build_command,json=buildCommand" json:"build_command,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	BuildCommand *string `protobuf:"bytes,26,opt,name=build_command,json=buildCommand" json:"build_command,omitempty"`
+	// The metrics for calling Bazel.
+	BazelRuns            []*PerfInfo `protobuf:"bytes,27,rep,name=bazel_runs,json=bazelRuns" json:"bazel_runs,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
 }
 
 func (m *MetricsBase) Reset()         { *m = MetricsBase{} }
@@ -423,6 +425,13 @@
 	return ""
 }
 
+func (m *MetricsBase) GetBazelRuns() []*PerfInfo {
+	if m != nil {
+		return m.BazelRuns
+	}
+	return nil
+}
+
 type BuildConfig struct {
 	UseGoma              *bool    `protobuf:"varint,1,opt,name=use_goma,json=useGoma" json:"use_goma,omitempty"`
 	UseRbe               *bool    `protobuf:"varint,2,opt,name=use_rbe,json=useRbe" json:"use_rbe,omitempty"`
@@ -981,91 +990,92 @@
 }
 
 var fileDescriptor_6039342a2ba47b72 = []byte{
-	// 1366 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x52, 0x1b, 0xc7,
-	0x12, 0xb6, 0x40, 0x20, 0xa9, 0xf5, 0x83, 0x18, 0xe0, 0xb0, 0xc6, 0xf6, 0x39, 0x1c, 0x25, 0x76,
-	0xa8, 0x54, 0x8c, 0x5d, 0xc4, 0x45, 0xb9, 0x28, 0x57, 0x2a, 0x20, 0x13, 0xc7, 0xa1, 0x40, 0xd4,
-	0x60, 0x1c, 0x27, 0xb9, 0x98, 0x8c, 0x56, 0x23, 0x58, 0x7b, 0x77, 0x67, 0x6b, 0x66, 0x96, 0x80,
-	0x1f, 0x25, 0x6f, 0x92, 0xeb, 0x3c, 0x4b, 0x9e, 0x20, 0x2f, 0x90, 0x9a, 0x9e, 0xdd, 0x65, 0xc1,
-	0x8a, 0xed, 0xf2, 0xdd, 0xee, 0xd7, 0xdf, 0xd7, 0xd3, 0xdd, 0x3b, 0xdd, 0x2d, 0x41, 0x3b, 0x12,
-	0x46, 0x05, 0xbe, 0x5e, 0x4f, 0x94, 0x34, 0x92, 0x2c, 0x68, 0x29, 0xe3, 0x13, 0x36, 0x4c, 0x83,
-	0x70, 0xc4, 0x32, 0x53, 0xef, 0xf7, 0x16, 0x34, 0xf7, 0xdd, 0xf3, 0x0e, 0xd7, 0x82, 0x3c, 0x84,
-	0x45, 0x47, 0x18, 0x71, 0x23, 0x98, 0x09, 0x22, 0xa1, 0x0d, 0x8f, 0x12, 0xaf, 0xb2, 0x5a, 0x59,
-	0x9b, 0xa6, 0x04, 0x6d, 0x4f, 0xb9, 0x11, 0x2f, 0x72, 0x0b, 0xb9, 0x09, 0x75, 0xa7, 0x08, 0x46,
-	0xde, 0xd4, 0x6a, 0x65, 0xad, 0x41, 0x6b, 0xf8, 0xfe, 0x7c, 0x44, 0xb6, 0xe0, 0x66, 0x12, 0x72,
-	0x33, 0x96, 0x2a, 0x62, 0x67, 0x42, 0xe9, 0x40, 0xc6, 0xcc, 0x97, 0x23, 0x11, 0xf3, 0x48, 0x78,
-	0xd3, 0xc8, 0x5d, 0xce, 0x09, 0x2f, 0x9d, 0xbd, 0x9f, 0x99, 0xc9, 0x5d, 0xe8, 0x18, 0xae, 0x4e,
-	0x84, 0x61, 0x89, 0x92, 0xa3, 0xd4, 0x37, 0x5e, 0x15, 0x05, 0x6d, 0x87, 0x1e, 0x3a, 0x90, 0x8c,
-	0x60, 0x31, 0xa3, 0xb9, 0x20, 0xce, 0xb8, 0x0a, 0x78, 0x6c, 0xbc, 0x99, 0xd5, 0xca, 0x5a, 0x67,
-	0xe3, 0xfe, 0xfa, 0x84, 0x9c, 0xd7, 0x4b, 0xf9, 0xae, 0xef, 0x58, 0xcb, 0x4b, 0x27, 0xda, 0x9a,
-	0xde, 0x3d, 0x78, 0x46, 0x89, 0xf3, 0x57, 0x36, 0x90, 0x01, 0x34, 0xb3, 0x53, 0xb8, 0xf2, 0x4f,
-	0xbd, 0x59, 0x74, 0x7e, 0xf7, 0x83, 0xce, 0xb7, 0x95, 0x7f, 0xba, 0x55, 0x3b, 0x3e, 0xd8, 0x3b,
-	0x18, 0xfc, 0x78, 0x40, 0xc1, 0xb9, 0xb0, 0x20, 0x59, 0x87, 0x85, 0x92, 0xc3, 0x22, 0xea, 0x1a,
-	0xa6, 0x38, 0x7f, 0x49, 0xcc, 0x03, 0xf8, 0x0a, 0xb2, 0xb0, 0x98, 0x9f, 0xa4, 0x05, 0xbd, 0x8e,
-	0xf4, 0xae, 0xb3, 0xf4, 0x93, 0x34, 0x67, 0xef, 0x41, 0xe3, 0x54, 0xea, 0x2c, 0xd8, 0xc6, 0x27,
-	0x05, 0x5b, 0xb7, 0x0e, 0x30, 0x54, 0x0a, 0x6d, 0x74, 0xb6, 0x11, 0x8f, 0x9c, 0x43, 0xf8, 0x24,
-	0x87, 0x4d, 0xeb, 0x64, 0x23, 0x1e, 0xa1, 0xcf, 0x65, 0xa8, 0xa1, 0x4f, 0xa9, 0xbd, 0x26, 0xe6,
-	0x30, 0x6b, 0x5f, 0x07, 0x9a, 0xf4, 0xb2, 0xc3, 0xa4, 0x66, 0xe2, 0xdc, 0x28, 0xee, 0xb5, 0xd0,
-	0xdc, 0x74, 0xe6, 0x5d, 0x0b, 0x15, 0x1c, 0x5f, 0x49, 0xad, 0xad, 0x8b, 0xf6, 0x25, 0xa7, 0x6f,
-	0xb1, 0x81, 0x26, 0xf7, 0x60, 0xae, 0xc4, 0xc1, 0xb0, 0x3b, 0xee, 0xfa, 0x14, 0x2c, 0x0c, 0xe4,
-	0x3e, 0x2c, 0x94, 0x78, 0x45, 0x8a, 0x73, 0xae, 0xb0, 0x05, 0xb7, 0x14, 0xb7, 0x4c, 0x0d, 0x1b,
-	0x05, 0xca, 0xeb, 0xba, 0xb8, 0x65, 0x6a, 0x9e, 0x06, 0x8a, 0x7c, 0x03, 0x4d, 0x2d, 0x4c, 0x9a,
-	0x30, 0x23, 0x65, 0xa8, 0xbd, 0xf9, 0xd5, 0xe9, 0xb5, 0xe6, 0xc6, 0x9d, 0x89, 0x25, 0x3a, 0x14,
-	0x6a, 0xfc, 0x3c, 0x1e, 0x4b, 0x0a, 0xa8, 0x78, 0x61, 0x05, 0x64, 0x0b, 0x1a, 0x6f, 0xb8, 0x09,
-	0x98, 0x4a, 0x63, 0xed, 0x91, 0x8f, 0x51, 0xd7, 0x2d, 0x9f, 0xa6, 0xb1, 0x26, 0x4f, 0x00, 0x1c,
-	0x13, 0xc5, 0x0b, 0x1f, 0x23, 0x6e, 0xa0, 0x35, 0x57, 0xc7, 0x41, 0xfc, 0x9a, 0x3b, 0xf5, 0xe2,
-	0x47, 0xa9, 0x51, 0x80, 0xea, 0xaf, 0x61, 0xc6, 0x48, 0xc3, 0x43, 0x6f, 0x69, 0xb5, 0xf2, 0x61,
-	0xa1, 0xe3, 0x92, 0x97, 0x30, 0x69, 0x14, 0x79, 0xff, 0x41, 0x17, 0xf7, 0x26, 0xba, 0x38, 0xb2,
-	0x18, 0xb6, 0x64, 0x76, 0xc3, 0xe8, 0xbc, 0xbe, 0x0e, 0x91, 0x3e, 0xb4, 0x9c, 0xca, 0x97, 0xf1,
-	0x38, 0x38, 0xf1, 0x96, 0xd1, 0xe1, 0xea, 0x44, 0x87, 0x28, 0xec, 0x23, 0x8f, 0x36, 0x87, 0x97,
-	0x2f, 0x64, 0x05, 0xf0, 0xea, 0xe3, 0x88, 0xf2, 0xf0, 0x1b, 0x17, 0xef, 0xe4, 0x27, 0x58, 0xd4,
-	0x17, 0xda, 0x88, 0x88, 0x29, 0xa1, 0x65, 0xaa, 0x7c, 0xc1, 0x82, 0x78, 0x2c, 0xbd, 0x9b, 0x78,
-	0xd0, 0x17, 0x93, 0x23, 0x47, 0x01, 0xcd, 0xf8, 0x58, 0x06, 0xa2, 0xdf, 0xc1, 0xc8, 0x67, 0xd0,
-	0xce, 0x63, 0x8f, 0x22, 0x1e, 0x8f, 0xbc, 0x15, 0x3c, 0xbb, 0x95, 0x85, 0x86, 0x58, 0xef, 0x21,
-	0xb4, 0xae, 0x8c, 0xa5, 0x3a, 0x54, 0x8f, 0x8f, 0x76, 0x69, 0xf7, 0x06, 0x69, 0x43, 0xc3, 0x3e,
-	0x3d, 0xdd, 0xdd, 0x39, 0x7e, 0xd6, 0xad, 0x90, 0x1a, 0xd8, 0x51, 0xd6, 0x9d, 0xea, 0x3d, 0x81,
-	0x2a, 0x5e, 0xdc, 0x26, 0xe4, 0x8d, 0xd8, 0xbd, 0x61, 0xad, 0xdb, 0x74, 0xbf, 0x5b, 0x21, 0x0d,
-	0x98, 0xd9, 0xa6, 0xfb, 0x9b, 0x8f, 0xba, 0x53, 0x16, 0x7b, 0xf5, 0x78, 0xb3, 0x3b, 0x4d, 0x00,
-	0x66, 0x5f, 0x3d, 0xde, 0x64, 0x9b, 0x8f, 0xba, 0xd5, 0xde, 0x09, 0x34, 0x4b, 0x75, 0xb2, 0x93,
-	0x3e, 0xd5, 0x82, 0x9d, 0xc8, 0x88, 0xe3, 0x3e, 0xa8, 0xd3, 0x5a, 0xaa, 0xc5, 0x33, 0x19, 0x71,
-	0xdb, 0x18, 0xd6, 0xa4, 0x86, 0x02, 0x77, 0x40, 0x9d, 0xce, 0xa6, 0x5a, 0xd0, 0xa1, 0x20, 0x9f,
-	0x43, 0x67, 0x2c, 0x6d, 0xa1, 0x0a, 0xe5, 0x34, 0xda, 0x5b, 0x88, 0x1e, 0x3b, 0x79, 0x4f, 0x02,
-	0x79, 0xb7, 0x4e, 0x64, 0x03, 0x96, 0xf0, 0xc2, 0xb0, 0xe4, 0xf4, 0x42, 0x07, 0x3e, 0x0f, 0x59,
-	0x24, 0x22, 0xa9, 0x2e, 0xf0, 0xf0, 0x2a, 0x5d, 0x40, 0xe3, 0x61, 0x66, 0xdb, 0x47, 0x93, 0x5d,
-	0x1b, 0xfc, 0x8c, 0x07, 0x21, 0x1f, 0x86, 0xc2, 0xce, 0x4a, 0x8d, 0xf1, 0xcc, 0xd0, 0x76, 0x81,
-	0xf6, 0x93, 0x54, 0xf7, 0xfe, 0xae, 0x40, 0x3d, 0xbf, 0x96, 0x84, 0x40, 0x75, 0x24, 0xb4, 0x8f,
-	0x6e, 0x1b, 0x14, 0x9f, 0x2d, 0x86, 0x57, 0xc0, 0x6d, 0x34, 0x7c, 0x26, 0x77, 0x00, 0xb4, 0xe1,
-	0xca, 0xe0, 0x5a, 0xc4, 0x3c, 0xaa, 0xb4, 0x81, 0x88, 0xdd, 0x86, 0xe4, 0x16, 0x34, 0x94, 0xe0,
-	0xa1, 0xb3, 0x56, 0xd1, 0x5a, 0xb7, 0x00, 0x1a, 0xff, 0x0f, 0xe0, 0x82, 0xb7, 0x85, 0xc0, 0xed,
-	0x54, 0xdd, 0x99, 0xf2, 0x2a, 0xb4, 0xe1, 0xd0, 0x63, 0x2d, 0xc8, 0xaf, 0xb0, 0x9c, 0x28, 0xe9,
-	0x0b, 0xad, 0x85, 0xbe, 0x76, 0xc1, 0x66, 0xb1, 0x2d, 0xd7, 0x26, 0x77, 0x97, 0xd3, 0x5c, 0xb9,
-	0x61, 0x4b, 0x85, 0xa3, 0x32, 0xdc, 0xfb, 0x63, 0x1a, 0x16, 0x26, 0xd0, 0x8b, 0x64, 0x2b, 0xa5,
-	0x64, 0xd7, 0xa0, 0x9b, 0x6a, 0xa1, 0x30, 0x1b, 0x16, 0x05, 0x76, 0x40, 0x62, 0x31, 0xaa, 0xb4,
-	0x63, 0x71, 0x9b, 0xd4, 0x3e, 0xa2, 0x76, 0x37, 0x65, 0x5d, 0x51, 0xe6, 0xba, 0xf2, 0x74, 0x9d,
-	0xa5, 0xc4, 0xbe, 0x0d, 0x10, 0xf1, 0x73, 0xa6, 0xb4, 0x66, 0x6f, 0x86, 0x79, 0x99, 0x22, 0x7e,
-	0x4e, 0xb5, 0xde, 0x1b, 0x92, 0x2f, 0x61, 0x3e, 0x0a, 0x62, 0xa9, 0x58, 0xc2, 0x4f, 0x04, 0x1b,
-	0xf3, 0x34, 0x34, 0xda, 0x55, 0x8b, 0xce, 0xa1, 0xe1, 0x90, 0x9f, 0x88, 0xef, 0x10, 0x46, 0x2e,
-	0x7f, 0x7d, 0x8d, 0x3b, 0x9b, 0x71, 0xad, 0xa1, 0xc4, 0xfd, 0x2f, 0x34, 0x03, 0xc9, 0x82, 0x38,
-	0x49, 0x8d, 0x3d, 0xb6, 0xe6, 0xbe, 0x5d, 0x20, 0x9f, 0x5b, 0x64, 0x6f, 0x48, 0x56, 0xa1, 0x15,
-	0x48, 0x26, 0x53, 0x93, 0x11, 0xea, 0x48, 0x80, 0x40, 0x0e, 0x10, 0xda, 0x1b, 0x92, 0x27, 0xb0,
-	0x72, 0x26, 0xc3, 0x34, 0x36, 0x5c, 0x5d, 0xd8, 0x01, 0x63, 0xc4, 0xb9, 0x61, 0xfa, 0xb7, 0xc0,
-	0xf8, 0xa7, 0x42, 0xe3, 0x92, 0xad, 0x52, 0xaf, 0x60, 0xf4, 0x1d, 0xe1, 0x28, 0xb3, 0x93, 0x6f,
-	0xe1, 0x76, 0x10, 0xbf, 0x47, 0x0f, 0xa8, 0x5f, 0x29, 0x71, 0xae, 0x79, 0xe8, 0xfd, 0x55, 0x81,
-	0xce, 0xbe, 0x1c, 0xa5, 0xa1, 0x78, 0x71, 0x91, 0xb8, 0xcf, 0xf6, 0x4b, 0x3e, 0xef, 0x5c, 0x91,
-	0xf1, 0xf3, 0x75, 0x36, 0x1e, 0x4c, 0x5e, 0xcc, 0x57, 0xa4, 0x6e, 0xfc, 0xb9, 0x96, 0x2b, 0xad,
-	0xe8, 0xe1, 0x25, 0x4a, 0xfe, 0x07, 0xcd, 0x08, 0x35, 0xcc, 0x5c, 0x24, 0x79, 0x1f, 0x40, 0x54,
-	0xb8, 0xb1, 0x9d, 0x1d, 0xa7, 0x11, 0x93, 0x63, 0xe6, 0x40, 0xf7, 0xc9, 0xdb, 0xb4, 0x15, 0xa7,
-	0xd1, 0x60, 0xec, 0xce, 0xd3, 0xbd, 0x07, 0xd9, 0x08, 0xc9, 0xbc, 0x5e, 0x99, 0x43, 0x0d, 0x98,
-	0x39, 0x1a, 0x0c, 0x0e, 0xec, 0xc0, 0xaa, 0x43, 0x75, 0x7f, 0x7b, 0x6f, 0xb7, 0x3b, 0xd5, 0x0b,
-	0x61, 0xa5, 0xaf, 0x02, 0x63, 0x5b, 0xfa, 0x58, 0x0b, 0xf5, 0x83, 0x4c, 0x55, 0x2c, 0x2e, 0xf2,
-	0x11, 0x3f, 0xe9, 0xa6, 0x6e, 0x41, 0x2d, 0x5f, 0x21, 0x53, 0xef, 0x99, 0xf8, 0xa5, 0x9f, 0x26,
-	0x34, 0x17, 0xf4, 0x86, 0x70, 0x6b, 0xc2, 0x69, 0xfa, 0x72, 0xa3, 0x54, 0xfd, 0xf4, 0xb5, 0xf6,
-	0x2a, 0xd8, 0x7f, 0x93, 0x2b, 0xfb, 0xef, 0xd1, 0x52, 0x14, 0xf7, 0xfe, 0xac, 0xc0, 0xfc, 0x3b,
-	0xfb, 0x8b, 0x78, 0x50, 0xcb, 0xeb, 0x56, 0xc1, 0xba, 0xe5, 0xaf, 0x76, 0x03, 0x65, 0x3f, 0xf0,
-	0x5c, 0x42, 0x6d, 0x5a, 0xbc, 0xdb, 0x3b, 0xef, 0x46, 0x22, 0x0f, 0x43, 0xe9, 0x33, 0x5f, 0xa6,
-	0xb1, 0xc9, 0x5a, 0x6d, 0x0e, 0x0d, 0xdb, 0x16, 0xef, 0x5b, 0xd8, 0x76, 0x70, 0x99, 0xab, 0x83,
-	0xb7, 0xf9, 0x58, 0xea, 0x5c, 0x52, 0x8f, 0x82, 0xb7, 0xc2, 0xfe, 0xa2, 0xb2, 0x3d, 0x79, 0x2a,
-	0x78, 0xe2, 0x68, 0xae, 0xe3, 0x9a, 0x11, 0x3f, 0xff, 0x5e, 0xf0, 0xc4, 0x72, 0x76, 0x96, 0x7e,
-	0xce, 0x96, 0x76, 0x96, 0x37, 0xc3, 0x3f, 0x15, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x86,
-	0x34, 0xa9, 0x64, 0x0c, 0x00, 0x00,
+	// 1380 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xef, 0x52, 0x1b, 0x37,
+	0x10, 0x8f, 0xc1, 0x60, 0x7b, 0xfd, 0x07, 0x23, 0xa0, 0x5c, 0x48, 0xd2, 0x52, 0xb7, 0x49, 0x99,
+	0x4e, 0x43, 0x32, 0x34, 0xc3, 0x64, 0x98, 0x4c, 0xa7, 0xe0, 0xd0, 0x34, 0x65, 0xc0, 0x8c, 0x08,
+	0x69, 0xda, 0x7e, 0x50, 0xe5, 0xb3, 0x0c, 0x97, 0xdc, 0x9d, 0x6e, 0x24, 0x1d, 0xc5, 0x79, 0xb3,
+	0x7e, 0xee, 0x4b, 0xf4, 0x05, 0xfa, 0x04, 0x7d, 0x81, 0x8e, 0x56, 0x77, 0xe6, 0x20, 0x6e, 0xc2,
+	0xe4, 0xdb, 0xe9, 0xb7, 0xbf, 0xdf, 0x6a, 0xb5, 0xd2, 0xee, 0xda, 0xd0, 0x8c, 0x84, 0x51, 0x81,
+	0xaf, 0xd7, 0x13, 0x25, 0x8d, 0x24, 0x0b, 0x5a, 0xca, 0xf8, 0x84, 0xf5, 0xd3, 0x20, 0x1c, 0xb0,
+	0xcc, 0xd4, 0xf9, 0xbb, 0x01, 0xf5, 0x7d, 0xf7, 0xbd, 0xc3, 0xb5, 0x20, 0x0f, 0x61, 0xd1, 0x11,
+	0x06, 0xdc, 0x08, 0x66, 0x82, 0x48, 0x68, 0xc3, 0xa3, 0xc4, 0x2b, 0xad, 0x96, 0xd6, 0xa6, 0x29,
+	0x41, 0xdb, 0x53, 0x6e, 0xc4, 0x8b, 0xdc, 0x42, 0x6e, 0x42, 0xd5, 0x29, 0x82, 0x81, 0x37, 0xb5,
+	0x5a, 0x5a, 0xab, 0xd1, 0x0a, 0xae, 0x9f, 0x0f, 0xc8, 0x16, 0xdc, 0x4c, 0x42, 0x6e, 0x86, 0x52,
+	0x45, 0xec, 0x4c, 0x28, 0x1d, 0xc8, 0x98, 0xf9, 0x72, 0x20, 0x62, 0x1e, 0x09, 0x6f, 0x1a, 0xb9,
+	0xcb, 0x39, 0xe1, 0xa5, 0xb3, 0x77, 0x33, 0x33, 0xb9, 0x0b, 0x2d, 0xc3, 0xd5, 0x89, 0x30, 0x2c,
+	0x51, 0x72, 0x90, 0xfa, 0xc6, 0x2b, 0xa3, 0xa0, 0xe9, 0xd0, 0x43, 0x07, 0x92, 0x01, 0x2c, 0x66,
+	0x34, 0x17, 0xc4, 0x19, 0x57, 0x01, 0x8f, 0x8d, 0x37, 0xb3, 0x5a, 0x5a, 0x6b, 0x6d, 0xdc, 0x5f,
+	0x9f, 0x70, 0xe6, 0xf5, 0xc2, 0x79, 0xd7, 0x77, 0xac, 0xe5, 0xa5, 0x13, 0x6d, 0x4d, 0xef, 0x1e,
+	0x3c, 0xa3, 0xc4, 0xf9, 0x2b, 0x1a, 0x48, 0x0f, 0xea, 0xd9, 0x2e, 0x5c, 0xf9, 0xa7, 0xde, 0x2c,
+	0x3a, 0xbf, 0xfb, 0x41, 0xe7, 0xdb, 0xca, 0x3f, 0xdd, 0xaa, 0x1c, 0x1f, 0xec, 0x1d, 0xf4, 0x7e,
+	0x3e, 0xa0, 0xe0, 0x5c, 0x58, 0x90, 0xac, 0xc3, 0x42, 0xc1, 0xe1, 0x38, 0xea, 0x0a, 0x1e, 0x71,
+	0xfe, 0x82, 0x98, 0x07, 0xf0, 0x0d, 0x64, 0x61, 0x31, 0x3f, 0x49, 0xc7, 0xf4, 0x2a, 0xd2, 0xdb,
+	0xce, 0xd2, 0x4d, 0xd2, 0x9c, 0xbd, 0x07, 0xb5, 0x53, 0xa9, 0xb3, 0x60, 0x6b, 0x1f, 0x15, 0x6c,
+	0xd5, 0x3a, 0xc0, 0x50, 0x29, 0x34, 0xd1, 0xd9, 0x46, 0x3c, 0x70, 0x0e, 0xe1, 0xa3, 0x1c, 0xd6,
+	0xad, 0x93, 0x8d, 0x78, 0x80, 0x3e, 0x97, 0xa1, 0x82, 0x3e, 0xa5, 0xf6, 0xea, 0x78, 0x86, 0x59,
+	0xbb, 0xec, 0x69, 0xd2, 0xc9, 0x36, 0x93, 0x9a, 0x89, 0x73, 0xa3, 0xb8, 0xd7, 0x40, 0x73, 0xdd,
+	0x99, 0x77, 0x2d, 0x34, 0xe6, 0xf8, 0x4a, 0x6a, 0x6d, 0x5d, 0x34, 0x2f, 0x38, 0x5d, 0x8b, 0xf5,
+	0x34, 0xb9, 0x07, 0x73, 0x05, 0x0e, 0x86, 0xdd, 0x72, 0xcf, 0x67, 0xcc, 0xc2, 0x40, 0xee, 0xc3,
+	0x42, 0x81, 0x37, 0x3e, 0xe2, 0x9c, 0x4b, 0xec, 0x98, 0x5b, 0x88, 0x5b, 0xa6, 0x86, 0x0d, 0x02,
+	0xe5, 0xb5, 0x5d, 0xdc, 0x32, 0x35, 0x4f, 0x03, 0x45, 0xbe, 0x83, 0xba, 0x16, 0x26, 0x4d, 0x98,
+	0x91, 0x32, 0xd4, 0xde, 0xfc, 0xea, 0xf4, 0x5a, 0x7d, 0xe3, 0xce, 0xc4, 0x14, 0x1d, 0x0a, 0x35,
+	0x7c, 0x1e, 0x0f, 0x25, 0x05, 0x54, 0xbc, 0xb0, 0x02, 0xb2, 0x05, 0xb5, 0x37, 0xdc, 0x04, 0x4c,
+	0xa5, 0xb1, 0xf6, 0xc8, 0x75, 0xd4, 0x55, 0xcb, 0xa7, 0x69, 0xac, 0xc9, 0x13, 0x00, 0xc7, 0x44,
+	0xf1, 0xc2, 0x75, 0xc4, 0x35, 0xb4, 0xe6, 0xea, 0x38, 0x88, 0x5f, 0x73, 0xa7, 0x5e, 0xbc, 0x96,
+	0x1a, 0x05, 0xa8, 0xfe, 0x16, 0x66, 0x8c, 0x34, 0x3c, 0xf4, 0x96, 0x56, 0x4b, 0x1f, 0x16, 0x3a,
+	0x2e, 0x79, 0x09, 0x93, 0x5a, 0x91, 0xf7, 0x09, 0xba, 0xb8, 0x37, 0xd1, 0xc5, 0x91, 0xc5, 0xb0,
+	0x24, 0xb3, 0x17, 0x46, 0xe7, 0xf5, 0x55, 0x88, 0x74, 0xa1, 0xe1, 0x54, 0xbe, 0x8c, 0x87, 0xc1,
+	0x89, 0xb7, 0x8c, 0x0e, 0x57, 0x27, 0x3a, 0x44, 0x61, 0x17, 0x79, 0xb4, 0xde, 0xbf, 0x58, 0x90,
+	0x15, 0xc0, 0xa7, 0x8f, 0x2d, 0xca, 0xc3, 0x3b, 0x1e, 0xaf, 0xc9, 0x2f, 0xb0, 0xa8, 0x47, 0xda,
+	0x88, 0x88, 0x29, 0xa1, 0x65, 0xaa, 0x7c, 0xc1, 0x82, 0x78, 0x28, 0xbd, 0x9b, 0xb8, 0xd1, 0x57,
+	0x93, 0x23, 0x47, 0x01, 0xcd, 0xf8, 0x98, 0x06, 0xa2, 0xdf, 0xc1, 0xc8, 0x17, 0xd0, 0xcc, 0x63,
+	0x8f, 0x22, 0x1e, 0x0f, 0xbc, 0x15, 0xdc, 0xbb, 0x91, 0x85, 0x86, 0x98, 0xbd, 0xab, 0x3e, 0x7f,
+	0x2b, 0x42, 0x77, 0x57, 0xb7, 0xae, 0x75, 0x57, 0x28, 0xb0, 0x77, 0xd5, 0x79, 0x08, 0x8d, 0x4b,
+	0x4d, 0xad, 0x0a, 0xe5, 0xe3, 0xa3, 0x5d, 0xda, 0xbe, 0x41, 0x9a, 0x50, 0xb3, 0x5f, 0x4f, 0x77,
+	0x77, 0x8e, 0x9f, 0xb5, 0x4b, 0xa4, 0x02, 0xb6, 0x11, 0xb6, 0xa7, 0x3a, 0x4f, 0xa0, 0x8c, 0xcf,
+	0xbe, 0x0e, 0x79, 0x19, 0xb7, 0x6f, 0x58, 0xeb, 0x36, 0xdd, 0x6f, 0x97, 0x48, 0x0d, 0x66, 0xb6,
+	0xe9, 0xfe, 0xe6, 0xa3, 0xf6, 0x94, 0xc5, 0x5e, 0x3d, 0xde, 0x6c, 0x4f, 0x13, 0x80, 0xd9, 0x57,
+	0x8f, 0x37, 0xd9, 0xe6, 0xa3, 0x76, 0xb9, 0x73, 0x02, 0xf5, 0x42, 0x96, 0xed, 0x9c, 0x48, 0xb5,
+	0x60, 0x27, 0x32, 0xe2, 0x38, 0x4d, 0xaa, 0xb4, 0x92, 0x6a, 0xf1, 0x4c, 0x46, 0xdc, 0x96, 0x95,
+	0x35, 0xa9, 0xbe, 0xc0, 0x09, 0x52, 0xa5, 0xb3, 0xa9, 0x16, 0xb4, 0x2f, 0xc8, 0x97, 0xd0, 0x1a,
+	0x4a, 0x9b, 0xe6, 0xb1, 0x72, 0x1a, 0xed, 0x0d, 0x44, 0x8f, 0x9d, 0xbc, 0x23, 0x81, 0xbc, 0x9b,
+	0x65, 0xb2, 0x01, 0x4b, 0xf8, 0xdc, 0x58, 0x72, 0x3a, 0xd2, 0x81, 0xcf, 0x43, 0x16, 0x89, 0x48,
+	0xaa, 0x11, 0x6e, 0x5e, 0xa6, 0x0b, 0x68, 0x3c, 0xcc, 0x6c, 0xfb, 0x68, 0xb2, 0x43, 0x87, 0x9f,
+	0xf1, 0x20, 0xe4, 0xfd, 0x50, 0xd8, 0x4e, 0xab, 0x31, 0x9e, 0x19, 0xda, 0x1c, 0xa3, 0xdd, 0x24,
+	0xd5, 0x9d, 0x7f, 0x4b, 0x50, 0xcd, 0x33, 0x4c, 0x08, 0x94, 0x07, 0x42, 0xfb, 0xe8, 0xb6, 0x46,
+	0xf1, 0xdb, 0x62, 0xf8, 0x80, 0xdc, 0x3c, 0xc4, 0x6f, 0x72, 0x07, 0x40, 0x1b, 0xae, 0x0c, 0x0e,
+	0x55, 0x3c, 0x47, 0x99, 0xd6, 0x10, 0xb1, 0xb3, 0x94, 0xdc, 0x82, 0x9a, 0x12, 0x3c, 0x74, 0xd6,
+	0x32, 0x5a, 0xab, 0x16, 0x40, 0xe3, 0xe7, 0x00, 0x2e, 0x78, 0x9b, 0x08, 0x9c, 0x6d, 0xe5, 0x9d,
+	0x29, 0xaf, 0x44, 0x6b, 0x0e, 0x3d, 0xd6, 0x82, 0xfc, 0x0e, 0xcb, 0x89, 0x92, 0xbe, 0xd0, 0x5a,
+	0xe8, 0x2b, 0xcf, 0x73, 0x16, 0x1f, 0xca, 0xda, 0xe4, 0x87, 0xe2, 0x34, 0x97, 0xde, 0xe7, 0xd2,
+	0xd8, 0x51, 0x11, 0xee, 0xfc, 0x39, 0x0d, 0x0b, 0x13, 0xe8, 0xe3, 0xc3, 0x96, 0x0a, 0x87, 0x5d,
+	0x83, 0x76, 0xaa, 0x85, 0xc2, 0xd3, 0xb0, 0x28, 0xb0, 0xed, 0x15, 0x93, 0x51, 0xa6, 0x2d, 0x8b,
+	0xdb, 0x43, 0xed, 0x23, 0x6a, 0x27, 0x5b, 0x56, 0x53, 0x45, 0xae, 0x4b, 0x4f, 0xdb, 0x59, 0x0a,
+	0xec, 0xdb, 0x00, 0x11, 0x3f, 0x67, 0x4a, 0x6b, 0xf6, 0xa6, 0x9f, 0xa7, 0x29, 0xe2, 0xe7, 0x54,
+	0xeb, 0xbd, 0x3e, 0xf9, 0x1a, 0xe6, 0xa3, 0x20, 0x96, 0x8a, 0x25, 0xfc, 0x44, 0xb0, 0x21, 0x4f,
+	0x43, 0xa3, 0x5d, 0xb6, 0xe8, 0x1c, 0x1a, 0x0e, 0xf9, 0x89, 0xf8, 0x01, 0x61, 0xe4, 0xf2, 0xd7,
+	0x57, 0xb8, 0xb3, 0x19, 0xd7, 0x1a, 0x0a, 0xdc, 0x4f, 0xa1, 0x1e, 0x48, 0x16, 0xc4, 0x49, 0x6a,
+	0xec, 0xb6, 0x15, 0x77, 0x77, 0x81, 0x7c, 0x6e, 0x91, 0xbd, 0x3e, 0x59, 0x85, 0x46, 0x20, 0x99,
+	0x4c, 0x4d, 0x46, 0xa8, 0x22, 0x01, 0x02, 0xd9, 0x43, 0x68, 0xaf, 0x4f, 0x9e, 0xc0, 0xca, 0x99,
+	0x0c, 0xd3, 0xd8, 0x70, 0x35, 0xb2, 0xed, 0xc9, 0x88, 0x73, 0xc3, 0xf4, 0x1f, 0x81, 0xf1, 0x4f,
+	0x85, 0xc6, 0x11, 0x5d, 0xa6, 0xde, 0x98, 0xd1, 0x75, 0x84, 0xa3, 0xcc, 0x4e, 0xbe, 0x87, 0xdb,
+	0x41, 0xfc, 0x1e, 0x3d, 0xa0, 0x7e, 0xa5, 0xc0, 0xb9, 0xe2, 0xa1, 0xf3, 0x4f, 0x09, 0x5a, 0xfb,
+	0x72, 0x90, 0x86, 0xe2, 0xc5, 0x28, 0x71, 0xd7, 0xf6, 0x5b, 0xde, 0x2d, 0x5d, 0x92, 0xf1, 0xfa,
+	0x5a, 0x1b, 0x0f, 0x26, 0x8f, 0xf5, 0x4b, 0x52, 0xd7, 0x3c, 0x5d, 0xc9, 0x15, 0x06, 0x7c, 0xff,
+	0x02, 0x25, 0x9f, 0x41, 0x3d, 0x42, 0x0d, 0x33, 0xa3, 0x24, 0xaf, 0x03, 0x88, 0xc6, 0x6e, 0x6c,
+	0x65, 0xc7, 0x69, 0xc4, 0xe4, 0x90, 0x39, 0xd0, 0x5d, 0x79, 0x93, 0x36, 0xe2, 0x34, 0xea, 0x0d,
+	0xdd, 0x7e, 0xba, 0xf3, 0x20, 0x6b, 0x21, 0x99, 0xd7, 0x4b, 0x7d, 0xa8, 0x06, 0x33, 0x47, 0xbd,
+	0xde, 0x81, 0x6d, 0x58, 0x55, 0x28, 0xef, 0x6f, 0xef, 0xed, 0xb6, 0xa7, 0x3a, 0x21, 0xac, 0x74,
+	0x55, 0x60, 0x6c, 0x49, 0x1f, 0x6b, 0xa1, 0x7e, 0x92, 0xa9, 0x8a, 0xc5, 0x28, 0x1f, 0x10, 0x93,
+	0x5e, 0xea, 0x16, 0x54, 0xf2, 0x01, 0x34, 0xf5, 0x9e, 0x79, 0x51, 0xf8, 0x61, 0x43, 0x73, 0x41,
+	0xa7, 0x0f, 0xb7, 0x26, 0xec, 0xa6, 0x2f, 0xe6, 0x51, 0xd9, 0x4f, 0x5f, 0x6b, 0xaf, 0x84, 0xf5,
+	0x37, 0x39, 0xb3, 0xff, 0x1f, 0x2d, 0x45, 0x71, 0xe7, 0xaf, 0x12, 0xcc, 0xbf, 0x33, 0xfd, 0x88,
+	0x07, 0x95, 0x3c, 0x6f, 0x25, 0xcc, 0x5b, 0xbe, 0xb4, 0xf3, 0x2b, 0xfb, 0x79, 0xe8, 0x0e, 0xd4,
+	0xa4, 0xe3, 0xb5, 0x7d, 0xf3, 0xae, 0x25, 0xf2, 0x30, 0x94, 0x3e, 0xf3, 0x65, 0x1a, 0x9b, 0xac,
+	0xd4, 0xe6, 0xd0, 0xb0, 0x6d, 0xf1, 0xae, 0x85, 0x6d, 0x05, 0x17, 0xb9, 0x3a, 0x78, 0x9b, 0xb7,
+	0xa5, 0xd6, 0x05, 0xf5, 0x28, 0x78, 0x2b, 0xec, 0xef, 0x31, 0x5b, 0x93, 0xa7, 0x82, 0x27, 0x8e,
+	0xe6, 0x2a, 0xae, 0x1e, 0xf1, 0xf3, 0x1f, 0x05, 0x4f, 0x2c, 0x67, 0x67, 0xe9, 0xd7, 0x6c, 0xe4,
+	0x67, 0xe7, 0x66, 0xf8, 0x97, 0xe4, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xfb, 0x8e, 0xf5,
+	0xa2, 0x0c, 0x00, 0x00,
 }
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 8d3933f..91d8dd9 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -105,6 +105,9 @@
 
   // The build command that the user entered to the build system.
   optional string build_command = 26;
+
+  // The metrics for calling Bazel.
+  repeated PerfInfo bazel_runs = 27;
 }
 
 message BuildConfig {
diff --git a/xml/xml_test.go b/xml/xml_test.go
index abcb108..138503c 100644
--- a/xml/xml_test.go
+++ b/xml/xml_test.go
@@ -57,10 +57,10 @@
 		"baz.xml": nil,
 	}
 	config := android.TestArchConfig(buildDir, nil, bp, fs)
-	ctx := android.NewTestArchContext()
+	ctx := android.NewTestArchContext(config)
 	ctx.RegisterModuleType("prebuilt_etc", etc.PrebuiltEtcFactory)
 	ctx.RegisterModuleType("prebuilt_etc_xml", PrebuiltEtcXmlFactory)
-	ctx.Register(config)
+	ctx.Register()
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index d603586..fc976f6 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -12,6 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// soong_zip is a utility used during the build to create a zip archive by pulling the entries from
+// various sources:
+//  * explicitly specified files
+//  * files whose paths are read from a file
+//  * directories traversed recursively
+// It can optionally change the recorded path of an entry.
+
 package main
 
 import (
diff --git a/zip/zip.go b/zip/zip.go
index e27432c..cb85f5c 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -126,6 +126,7 @@
 	return b
 }
 
+// List reads the file names from the given file and adds them to the source files list.
 func (b *FileArgsBuilder) List(name string) *FileArgsBuilder {
 	if b.err != nil {
 		return b
@@ -150,6 +151,7 @@
 	return b
 }
 
+// RspFile reads the file names from given .rsp file and adds them to the source files list.
 func (b *FileArgsBuilder) RspFile(name string) *FileArgsBuilder {
 	if b.err != nil {
 		return b
@@ -291,7 +293,7 @@
 	return args
 }
 
-func ZipTo(args ZipArgs, w io.Writer) error {
+func zipTo(args ZipArgs, w io.Writer) error {
 	if args.EmulateJar {
 		args.AddDirectoryEntriesToZip = true
 	}
@@ -392,6 +394,7 @@
 	return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SrcJar, args.NumParallelJobs)
 }
 
+// Zip creates an output zip archive from given sources.
 func Zip(args ZipArgs) error {
 	if args.OutputFilePath == "" {
 		return fmt.Errorf("output file path must be nonempty")
@@ -416,7 +419,7 @@
 		out = f
 	}
 
-	err := ZipTo(args, out)
+	err := zipTo(args, out)
 	if err != nil {
 		return err
 	}
@@ -450,7 +453,6 @@
 				RelativeRoot: fa.SourcePrefixToStrip,
 			}
 		}
-
 	}
 	dest = filepath.Join(fa.PathPrefixInZip, dest)
 
@@ -465,10 +467,9 @@
 }
 
 func jarSort(mappings []pathMapping) {
-	less := func(i int, j int) (smaller bool) {
+	sort.SliceStable(mappings, func(i int, j int) bool {
 		return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
-	}
-	sort.SliceStable(mappings, less)
+	})
 }
 
 func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar, srcJar bool,
@@ -709,7 +710,7 @@
 	}
 }
 
-func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
+func (z *ZipWriter) addManifest(dest string, src string, _ uint16) error {
 	if prev, exists := z.createdDirs[dest]; exists {
 		return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
 	}
@@ -963,7 +964,7 @@
 	dir = filepath.Clean(dir)
 
 	// discover any uncreated directories in the path
-	zipDirs := []string{}
+	var zipDirs []string
 	for dir != "" && dir != "." {
 		if _, exists := z.createdDirs[dir]; exists {
 			break
diff --git a/zip/zip_test.go b/zip/zip_test.go
index 302a749..a16e092 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -442,7 +442,7 @@
 			args.Stderr = &bytes.Buffer{}
 
 			buf := &bytes.Buffer{}
-			err := ZipTo(args, buf)
+			err := zipTo(args, buf)
 
 			if (err != nil) != (test.err != nil) {
 				t.Fatalf("want error %v, got %v", test.err, err)
@@ -627,7 +627,7 @@
 	args.Stderr = &bytes.Buffer{}
 
 	buf := &bytes.Buffer{}
-	err := ZipTo(args, buf)
+	err := zipTo(args, buf)
 	if err != nil {
 		t.Fatalf("got error %v", err)
 	}