Merge "Soong: remove output file before running signapk"
diff --git a/Android.bp b/Android.bp
index e7a5de2..866ed25 100644
--- a/Android.bp
+++ b/Android.bp
@@ -75,6 +75,10 @@
     product_available: true,
     recovery_available: true,
     native_bridge_supported: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 
     arch: {
         arm: {
diff --git a/android/Android.bp b/android/Android.bp
index 7bd1450..f8c1d55 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -4,8 +4,10 @@
     deps: [
         "blueprint",
         "blueprint-bootstrap",
+        "sbox_proto",
         "soong",
         "soong-android-soongconfig",
+        "soong-bazel",
         "soong-env",
         "soong-shared",
         "soong-ui-metrics_proto",
@@ -15,12 +17,15 @@
         "apex.go",
         "api_levels.go",
         "arch.go",
+        "arch_list.go",
         "bazel_handler.go",
         "config.go",
         "csuite_config.go",
         "defaults.go",
         "defs.go",
-        "depset.go",
+        "depset_generic.go",
+        "depset_paths.go",
+        "deptag.go",
         "expand.go",
         "filegroup.go",
         "hooks.go",
@@ -32,11 +37,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",
@@ -68,13 +75,16 @@
         "config_test.go",
         "csuite_config_test.go",
         "depset_test.go",
+        "deptag_test.go",
         "expand_test.go",
         "module_test.go",
         "mutator_test.go",
         "namespace_test.go",
         "neverallow_test.go",
+        "ninja_deps_test.go",
         "onceper_test.go",
         "package_test.go",
+        "packaging_test.go",
         "path_properties_test.go",
         "paths_test.go",
         "prebuilt_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index cfd7c91..42c5d00 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -12,6 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This file offers AndroidMkEntriesProvider, which individual modules implement to output
+// Android.mk entries that contain information about the modules built through Soong. Kati reads
+// and combines them with the legacy Make-based module definitions to produce the complete view of
+// the source tree, which makes this a critical point of Make-Soong interoperability.
+//
+// Naturally, Soong-only builds do not rely on this mechanism.
+
 package android
 
 import (
@@ -36,8 +43,8 @@
 	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
 }
 
-// Deprecated: consider using AndroidMkEntriesProvider instead, especially if you're not going to
-// use the Custom function.
+// Deprecated: Use AndroidMkEntriesProvider instead, especially if you're not going to use the
+// Custom function. It's easier to use and test.
 type AndroidMkDataProvider interface {
 	AndroidMk() AndroidMkData
 	BaseModuleName() string
@@ -63,37 +70,82 @@
 
 type AndroidMkExtraFunc func(w io.Writer, outputFile Path)
 
-// Allows modules to customize their Android*.mk output.
+// Interface for modules to declare their Android.mk outputs. Note that every module needs to
+// implement this in order to be included in the final Android-<product_name>.mk output, even if
+// they only need to output the common set of entries without any customizations.
 type AndroidMkEntriesProvider interface {
+	// Returns AndroidMkEntries objects that contain all basic info plus extra customization data
+	// if needed. This is the core func to implement.
+	// Note that one can return multiple objects. For example, java_library may return an additional
+	// AndroidMkEntries object for its hostdex sub-module.
 	AndroidMkEntries() []AndroidMkEntries
+	// Modules don't need to implement this as it's already implemented by ModuleBase.
+	// AndroidMkEntries uses BaseModuleName() instead of ModuleName() because certain modules
+	// e.g. Prebuilts, override the Name() func and return modified names.
+	// If a different name is preferred, use SubName or OverrideName in AndroidMkEntries.
 	BaseModuleName() string
 }
 
+// The core data struct that modules use to provide their Android.mk data.
 type AndroidMkEntries struct {
-	Class           string
-	SubName         string
-	OverrideName    string
-	DistFiles       TaggedDistFiles
-	OutputFile      OptionalPath
-	Disabled        bool
-	Include         string
-	Required        []string
-	Host_required   []string
+	// Android.mk class string, e.g EXECUTABLES, JAVA_LIBRARIES, ETC
+	Class string
+	// Optional suffix to append to the module name. Useful when a module wants to return multiple
+	// AndroidMkEntries objects. For example, when a java_library returns an additional entry for
+	// its hostdex sub-module, this SubName field is set to "-hostdex" so that it can have a
+	// different name than the parent's.
+	SubName string
+	// If set, this value overrides the base module name. SubName is still appended.
+	OverrideName string
+	// Dist files to output
+	DistFiles TaggedDistFiles
+	// The output file for Kati to process and/or install. If absent, the module is skipped.
+	OutputFile OptionalPath
+	// If true, the module is skipped and does not appear on the final Android-<product name>.mk
+	// file. Useful when a module needs to be skipped conditionally.
+	Disabled bool
+	// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_prebuilt.mk
+	// If not set, $(BUILD_SYSTEM)/prebuilt.mk is used.
+	Include string
+	// Required modules that need to be built and included in the final build output when building
+	// this module.
+	Required []string
+	// Required host modules that need to be built and included in the final build output when
+	// building this module.
+	Host_required []string
+	// Required device modules that need to be built and included in the final build output when
+	// building this module.
 	Target_required []string
 
 	header bytes.Buffer
 	footer bytes.Buffer
 
+	// Funcs to append additional Android.mk entries or modify the common ones. Multiple funcs are
+	// accepted so that common logic can be factored out as a shared func.
 	ExtraEntries []AndroidMkExtraEntriesFunc
+	// Funcs to add extra lines to the module's Android.mk output. Unlike AndroidMkExtraEntriesFunc,
+	// which simply sets Make variable values, this can be used for anything since it can write any
+	// Make statements directly to the final Android-*.mk file.
+	// Primarily used to call macros or declare/update Make targets.
 	ExtraFooters []AndroidMkExtraFootersFunc
 
-	EntryMap   map[string][]string
+	// A map that holds the up-to-date Make variable values. Can be accessed from tests.
+	EntryMap map[string][]string
+	// A list of EntryMap keys in insertion order. This serves a few purposes:
+	// 1. Prevents churns. Golang map doesn't provide consistent iteration order, so without this,
+	// the outputted Android-*.mk file may change even though there have been no content changes.
+	// 2. Allows modules to refer to other variables, like LOCAL_BAR_VAR := $(LOCAL_FOO_VAR),
+	// without worrying about the variables being mixed up in the actual mk file.
+	// 3. Makes troubleshooting and spotting errors easier.
 	entryOrder []string
 }
 
 type AndroidMkExtraEntriesFunc func(entries *AndroidMkEntries)
 type AndroidMkExtraFootersFunc func(w io.Writer, name, prefix, moduleDir string, entries *AndroidMkEntries)
 
+// Utility funcs to manipulate Android.mk variable entries.
+
+// SetString sets a Make variable with the given name to the given value.
 func (a *AndroidMkEntries) SetString(name, value string) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -101,6 +153,7 @@
 	a.EntryMap[name] = []string{value}
 }
 
+// SetPath sets a Make variable with the given name to the given path string.
 func (a *AndroidMkEntries) SetPath(name string, path Path) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -108,12 +161,15 @@
 	a.EntryMap[name] = []string{path.String()}
 }
 
+// SetOptionalPath sets a Make variable with the given name to the given path string if it is valid.
+// It is a no-op if the given path is invalid.
 func (a *AndroidMkEntries) SetOptionalPath(name string, path OptionalPath) {
 	if path.Valid() {
 		a.SetPath(name, path.Path())
 	}
 }
 
+// AddPath appends the given path string to a Make variable with the given name.
 func (a *AndroidMkEntries) AddPath(name string, path Path) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -121,12 +177,15 @@
 	a.EntryMap[name] = append(a.EntryMap[name], path.String())
 }
 
+// AddOptionalPath appends the given path string to a Make variable with the given name if it is
+// valid. It is a no-op if the given path is invalid.
 func (a *AndroidMkEntries) AddOptionalPath(name string, path OptionalPath) {
 	if path.Valid() {
 		a.AddPath(name, path.Path())
 	}
 }
 
+// SetPaths sets a Make variable with the given name to a slice of the given path strings.
 func (a *AndroidMkEntries) SetPaths(name string, paths Paths) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -134,12 +193,15 @@
 	a.EntryMap[name] = paths.Strings()
 }
 
+// SetOptionalPaths sets a Make variable with the given name to a slice of the given path strings
+// only if there are a non-zero amount of paths.
 func (a *AndroidMkEntries) SetOptionalPaths(name string, paths Paths) {
 	if len(paths) > 0 {
 		a.SetPaths(name, paths)
 	}
 }
 
+// AddPaths appends the given path strings to a Make variable with the given name.
 func (a *AndroidMkEntries) AddPaths(name string, paths Paths) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -147,6 +209,8 @@
 	a.EntryMap[name] = append(a.EntryMap[name], paths.Strings()...)
 }
 
+// SetBoolIfTrue sets a Make variable with the given name to true if the given flag is true.
+// It is a no-op if the given flag is false.
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
@@ -156,6 +220,7 @@
 	}
 }
 
+// SetBool sets a Make variable with the given name to if the given bool flag value.
 func (a *AndroidMkEntries) SetBool(name string, flag bool) {
 	if _, ok := a.EntryMap[name]; !ok {
 		a.entryOrder = append(a.entryOrder, name)
@@ -167,6 +232,7 @@
 	}
 }
 
+// AddStrings appends the given strings to a Make variable with the given name.
 func (a *AndroidMkEntries) AddStrings(name string, value ...string) {
 	if len(value) == 0 {
 		return
@@ -177,24 +243,96 @@
 	a.EntryMap[name] = append(a.EntryMap[name], value...)
 }
 
-// Compute the list of Make strings to declare phone goals and dist-for-goals
-// calls from the module's dist and dists properties.
-func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+// AddCompatibilityTestSuites adds the supplied test suites to the EntryMap, with special handling
+// for partial MTS test suites.
+func (a *AndroidMkEntries) AddCompatibilityTestSuites(suites ...string) {
+	// MTS supports a full test suite and partial per-module MTS test suites, with naming mts-${MODULE}.
+	// To reduce repetition, if we find a partial MTS test suite without an full MTS test suite,
+	// we add the full test suite to our list.
+	if PrefixInList(suites, "mts-") && !InList("mts", suites) {
+		suites = append(suites, "mts")
+	}
+	a.AddStrings("LOCAL_COMPATIBILITY_SUITE", suites...)
+}
+
+// The contributions to the dist.
+type distContributions struct {
+	// List of goals and the dist copy instructions.
+	copiesForGoals []*copiesForGoals
+}
+
+// getCopiesForGoals returns a copiesForGoals into which copy instructions that
+// must be processed when building one or more of those goals can be added.
+func (d *distContributions) getCopiesForGoals(goals string) *copiesForGoals {
+	copiesForGoals := &copiesForGoals{goals: goals}
+	d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
+	return copiesForGoals
+}
+
+// Associates a list of dist copy instructions with a set of goals for which they
+// should be run.
+type copiesForGoals struct {
+	// goals are a space separated list of build targets that will trigger the
+	// copy instructions.
+	goals string
+
+	// A list of instructions to copy a module's output files to somewhere in the
+	// dist directory.
+	copies []distCopy
+}
+
+// Adds a copy instruction.
+func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
+	d.copies = append(d.copies, distCopy{from, dest})
+}
+
+// Instruction on a path that must be copied into the dist.
+type distCopy struct {
+	// The path to copy from.
+	from Path
+
+	// The destination within the dist directory to copy to.
+	dest string
+}
+
+// Compute the contributions that the module makes to the dist.
+func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
 	amod := mod.(Module).base()
 	name := amod.BaseModuleName()
 
-	var ret []string
+	// Collate the set of associated tag/paths available for copying to the dist.
+	// Start with an empty (nil) set.
 	var availableTaggedDists TaggedDistFiles
 
+	// Then merge in any that are provided explicitly by the module.
 	if a.DistFiles != nil {
-		availableTaggedDists = a.DistFiles
-	} else if a.OutputFile.Valid() {
-		availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
-	} else {
+		// Merge the DistFiles into the set.
+		availableTaggedDists = availableTaggedDists.merge(a.DistFiles)
+	}
+
+	// If no paths have been provided for the DefaultDistTag and the output file is
+	// valid then add that as the default dist path.
+	if _, ok := availableTaggedDists[DefaultDistTag]; !ok && a.OutputFile.Valid() {
+		availableTaggedDists = availableTaggedDists.addPathsForTag(DefaultDistTag, a.OutputFile.Path())
+	}
+
+	// If the distFiles created by GenerateTaggedDistFiles contains paths for the
+	// DefaultDistTag then that takes priority so delete any existing paths.
+	if _, ok := amod.distFiles[DefaultDistTag]; ok {
+		delete(availableTaggedDists, DefaultDistTag)
+	}
+
+	// Finally, merge the distFiles created by GenerateTaggedDistFiles.
+	availableTaggedDists = availableTaggedDists.merge(amod.distFiles)
+
+	if len(availableTaggedDists) == 0 {
 		// Nothing dist-able for this module.
 		return nil
 	}
 
+	// Collate the contributions this module makes to the dist.
+	distContributions := &distContributions{}
+
 	// Iterate over this module's dist structs, merged from the dist and dists properties.
 	for _, dist := range amod.Dists() {
 		// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
@@ -204,7 +342,7 @@
 		var tag string
 		if dist.Tag == nil {
 			// If the dist struct does not specify a tag, use the default output files tag.
-			tag = ""
+			tag = DefaultDistTag
 		} else {
 			tag = *dist.Tag
 		}
@@ -218,15 +356,15 @@
 		}
 
 		if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
-			errorMessage := "Cannot apply dest/suffix for more than one dist " +
-				"file for %s goals in module %s. The list of dist files, " +
+			errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
+				"file for %q goals tag %q in module %s. The list of dist files, " +
 				"which should have a single element, is:\n%s"
-			panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+			panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
 		}
 
-		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+		copiesForGoals := distContributions.getCopiesForGoals(goals)
 
-		// Create dist-for-goals calls for each path in the dist'd files.
+		// Iterate over each path adding a copy instruction to copiesForGoals
 		for _, path := range tagPaths {
 			// It's possible that the Path is nil from errant modules. Be defensive here.
 			if path == nil {
@@ -261,15 +399,43 @@
 				}
 			}
 
+			copiesForGoals.addCopyInstruction(path, dest)
+		}
+	}
+
+	return distContributions
+}
+
+// generateDistContributionsForMake generates make rules that will generate the
+// dist according to the instructions in the supplied distContribution.
+func generateDistContributionsForMake(distContributions *distContributions) []string {
+	var ret []string
+	for _, d := range distContributions.copiesForGoals {
+		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
+		// Create dist-for-goals calls for each of the copy instructions.
+		for _, c := range d.copies {
 			ret = append(
 				ret,
-				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
 		}
 	}
 
 	return ret
 }
 
+// Compute the list of Make strings to declare phony goals and dist-for-goals
+// calls from the module's dist and dists properties.
+func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+	distContributions := a.getDistContributions(mod)
+	if distContributions == nil {
+		return nil
+	}
+
+	return generateDistContributionsForMake(distContributions)
+}
+
+// fillInEntries goes through the common variable processing and calls the extra data funcs to
+// generate and fill in AndroidMkEntries's in-struct data, ready to be flushed to a file.
 func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
 	a.EntryMap = make(map[string][]string)
 	amod := mod.(Module).base()
@@ -333,7 +499,9 @@
 			}
 		}
 
-		a.AddStrings("LOCAL_INIT_RC", amod.commonProperties.Init_rc...)
+		if !amod.InRamdisk() && !amod.InVendorRamdisk() {
+			a.AddStrings("LOCAL_INIT_RC", amod.commonProperties.Init_rc...)
+		}
 		a.AddStrings("LOCAL_VINTF_FRAGMENTS", amod.commonProperties.Vintf_fragments...)
 		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary))
 		if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
@@ -390,6 +558,8 @@
 	}
 }
 
+// write  flushes the AndroidMkEntries's in-struct data populated by AndroidMkEntries into the
+// given Writer object.
 func (a *AndroidMkEntries) write(w io.Writer) {
 	if a.Disabled {
 		return
@@ -410,6 +580,8 @@
 	return strings.Split(string(a.footer.Bytes()), "\n")
 }
 
+// AndroidMkSingleton is a singleton to collect Android.mk data from all modules and dump them into
+// the final Android-<product_name>.mk file output.
 func AndroidMkSingleton() Singleton {
 	return &androidMkSingleton{}
 }
@@ -417,7 +589,8 @@
 type androidMkSingleton struct{}
 
 func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
-	if !ctx.Config().EmbeddedInMake() {
+	// Skip if Soong wasn't invoked from Make.
+	if !ctx.Config().KatiEnabled() {
 		return
 	}
 
@@ -427,6 +600,8 @@
 		androidMkModulesList = append(androidMkModulesList, module)
 	})
 
+	// Sort the module list by the module names to eliminate random churns, which may erroneously
+	// invoke additional build processes.
 	sort.SliceStable(androidMkModulesList, func(i, j int) bool {
 		return ctx.ModuleName(androidMkModulesList[i]) < ctx.ModuleName(androidMkModulesList[j])
 	})
@@ -519,6 +694,8 @@
 	}
 }
 
+// A simple, special Android.mk entry output func to make it possible to build blueprint tools using
+// m by making them phony targets.
 func translateGoBinaryModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
 	goBinary bootstrap.GoBinaryTool) error {
 
@@ -551,6 +728,8 @@
 	data.Target_required = data.Entries.Target_required
 }
 
+// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
+// instead.
 func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
 	provider AndroidMkDataProvider) error {
 
@@ -597,6 +776,8 @@
 	return nil
 }
 
+// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
+// instead.
 func WriteAndroidMkData(w io.Writer, data AndroidMkData) {
 	if data.Disabled {
 		return
@@ -644,11 +825,14 @@
 		module.Os() == LinuxBionic
 }
 
+// A utility func to format LOCAL_TEST_DATA outputs. See the comments on DataPath to understand how
+// to use this func.
 func AndroidMkDataPaths(data []DataPath) []string {
 	var testFiles []string
 	for _, d := range data {
 		rel := d.SrcPath.Rel()
 		path := d.SrcPath.String()
+		// LOCAL_TEST_DATA requires the rel portion of the path to be removed from the path.
 		if !strings.HasSuffix(path, rel) {
 			panic(fmt.Errorf("path %q does not end with %q", path, rel))
 		}
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 10527b9..347b92e 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,17 +18,77 @@
 	"fmt"
 	"io"
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type customModule struct {
 	ModuleBase
-	data      AndroidMkData
-	distFiles TaggedDistFiles
+
+	properties struct {
+		Default_dist_files *string
+		Dist_output_file   *bool
+	}
+
+	data       AndroidMkData
+	distFiles  TaggedDistFiles
+	outputFile OptionalPath
+
+	// The paths that will be used as the default dist paths if no tag is
+	// specified.
+	defaultDistPaths Paths
 }
 
+const (
+	defaultDistFiles_None    = "none"
+	defaultDistFiles_Default = "default"
+	defaultDistFiles_Tagged  = "tagged"
+)
+
 func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	m.distFiles = m.GenerateTaggedDistFiles(ctx)
+
+	// If the dist_output_file: true then create an output file that is stored in
+	// the OutputFile property of the AndroidMkEntry.
+	if proptools.BoolDefault(m.properties.Dist_output_file, true) {
+		path := PathForTesting("dist-output-file.out")
+		m.outputFile = OptionalPathForPath(path)
+
+		// Previous code would prioritize the DistFiles property over the OutputFile
+		// property in AndroidMkEntry when determining the default dist paths.
+		// Setting this first allows it to be overridden based on the
+		// default_dist_files setting replicating that previous behavior.
+		m.defaultDistPaths = Paths{path}
+	}
+
+	// Based on the setting of the default_dist_files property possibly create a
+	// TaggedDistFiles structure that will be stored in the DistFiles property of
+	// the AndroidMkEntry.
+	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
+	switch defaultDistFiles {
+	case defaultDistFiles_None:
+		// Do nothing
+
+	case defaultDistFiles_Default:
+		path := PathForTesting("default-dist.out")
+		m.defaultDistPaths = Paths{path}
+		m.distFiles = MakeDefaultDistFiles(path)
+
+	case defaultDistFiles_Tagged:
+		// Module types that set AndroidMkEntry.DistFiles to the result of calling
+		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
+		// meant that the default dist paths would be whatever was returned by
+		// OutputFiles(""). In order to preserve that behavior when treating no tag
+		// as being equal to DefaultDistTag this ensures that
+		// OutputFiles(DefaultDistTag) will return the same as OutputFiles("").
+		m.defaultDistPaths = PathsForTesting("one.out")
+
+		// This must be called after setting defaultDistPaths/outputFile as
+		// GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those
+		// fields.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+	}
 }
 
 func (m *customModule) AndroidMk() AndroidMkData {
@@ -41,6 +101,12 @@
 
 func (m *customModule) OutputFiles(tag string) (Paths, error) {
 	switch tag {
+	case DefaultDistTag:
+		if m.defaultDistPaths != nil {
+			return m.defaultDistPaths, nil
+		} else {
+			return nil, fmt.Errorf("default dist tag is not available")
+		}
 	case "":
 		return PathsForTesting("one.out"), nil
 	case ".multiple":
@@ -55,18 +121,43 @@
 func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
 	return []AndroidMkEntries{
 		{
-			Class:     "CUSTOM_MODULE",
-			DistFiles: m.distFiles,
+			Class:      "CUSTOM_MODULE",
+			DistFiles:  m.distFiles,
+			OutputFile: m.outputFile,
 		},
 	}
 }
 
 func customModuleFactory() Module {
 	module := &customModule{}
+
+	module.AddProperties(&module.properties)
+
 	InitAndroidModule(module)
 	return module
 }
 
+// buildConfigAndCustomModuleFoo creates a config object, processes the supplied
+// bp module and then returns the config and the custom module called "foo".
+func buildConfigAndCustomModuleFoo(t *testing.T, bp string) (Config, *customModule) {
+	t.Helper()
+	config := TestConfig(buildDir, nil, bp, nil)
+	config.katiEnabled = true // Enable androidmk Singleton
+
+	ctx := NewTestContext(config)
+	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
+	ctx.RegisterModuleType("custom", customModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	return config, module
+}
+
 func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
 	bp := `
 	custom {
@@ -77,20 +168,7 @@
 	}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.inMake = true // Enable androidmk Singleton
-
-	ctx := NewTestContext(config)
-	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-	ctx.RegisterModuleType("custom", customModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	m := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	_, m := buildConfigAndCustomModuleFoo(t, bp)
 
 	assertEqual := func(expected interface{}, actual interface{}) {
 		if !reflect.DeepEqual(expected, actual) {
@@ -102,101 +180,29 @@
 	assertEqual([]string{"qux"}, m.data.Target_required)
 }
 
-func TestGetDistForGoals(t *testing.T) {
-	testCases := []struct {
-		bp                     string
-		expectedAndroidMkLines []string
-	}{
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"]
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
-					tag: ".another-tag",
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-						tag: ".another-tag",
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-					},
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
+func TestGenerateDistContributionsForMake(t *testing.T) {
+	dc := &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+					distCopyForTest("two.out", "other.out"),
 				},
-				dists: [
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
 			},
 		},
-		{
-			bp: `
+	}
+
+	makeOutput := generateDistContributionsForMake(dc)
+
+	assertStringEquals(t, `.PHONY: my_goal
+$(call dist-for-goals,my_goal,one.out:one.out)
+$(call dist-for-goals,my_goal,two.out:other.out)
+`, strings.Join(makeOutput, ""))
+}
+
+func TestGetDistForGoals(t *testing.T) {
+	bp := `
 			custom {
 				name: "foo",
 				dist: {
@@ -228,64 +234,522 @@
 					},
 				],
 			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal\n",
-				"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
-				".PHONY: my_third_goal\n",
-				"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
-				".PHONY: my_fourth_goal\n",
-				"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
-				".PHONY: my_fifth_goal\n",
-				"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
-				".PHONY: my_sixth_goal\n",
-				"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
-				".PHONY: my_goal my_other_goal\n",
-				"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
-			},
-		},
+			`
+
+	expectedAndroidMkLines := []string{
+		".PHONY: my_second_goal\n",
+		"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
+		".PHONY: my_third_goal\n",
+		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
+		".PHONY: my_fourth_goal\n",
+		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
+		".PHONY: my_fifth_goal\n",
+		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
+		".PHONY: my_sixth_goal\n",
+		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
+		".PHONY: my_goal my_other_goal\n",
+		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
 	}
 
-	for _, testCase := range testCases {
-		config := TestConfig(buildDir, nil, testCase.bp, nil)
-		config.inMake = true // Enable androidmk Singleton
+	config, module := buildConfigAndCustomModuleFoo(t, bp)
+	entries := AndroidMkEntriesForTest(t, config, "", module)
+	if len(entries) != 1 {
+		t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+	}
+	androidMkLines := entries[0].GetDistForGoals(module)
 
-		ctx := NewTestContext(config)
-		ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register()
-
-		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-		FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		FailIfErrored(t, errs)
-
-		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
-		entries := AndroidMkEntriesForTest(t, config, "", module)
-		if len(entries) != 1 {
-			t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
-		}
-		androidMkLines := entries[0].GetDistForGoals(module)
-
-		if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
+	if len(androidMkLines) != len(expectedAndroidMkLines) {
+		t.Errorf(
+			"Expected %d AndroidMk lines, got %d:\n%v",
+			len(expectedAndroidMkLines),
+			len(androidMkLines),
+			androidMkLines,
+		)
+	}
+	for idx, line := range androidMkLines {
+		expectedLine := expectedAndroidMkLines[idx]
+		if line != expectedLine {
 			t.Errorf(
-				"Expected %d AndroidMk lines, got %d:\n%v",
-				len(testCase.expectedAndroidMkLines),
-				len(androidMkLines),
-				androidMkLines,
+				"Expected AndroidMk line to be '%s', got '%s'",
+				expectedLine,
+				line,
 			)
 		}
-		for idx, line := range androidMkLines {
-			expectedLine := testCase.expectedAndroidMkLines[idx]
-			if line != expectedLine {
-				t.Errorf(
-					"Expected AndroidMk line to be '%s', got '%s'",
-					line,
-					expectedLine,
-				)
+	}
+}
+
+func distCopyForTest(from, to string) distCopy {
+	return distCopy{PathForTesting(from), to}
+}
+
+func TestGetDistContributions(t *testing.T) {
+	compareContributions := func(d1 *distContributions, d2 *distContributions) error {
+		if d1 == nil || d2 == nil {
+			if d1 != d2 {
+				return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2)
+			} else {
+				return nil
 			}
 		}
+		if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual {
+			return fmt.Errorf("length mismatch, expected %d found %d", expected, actual)
+		}
+
+		for i, copies1 := range d1.copiesForGoals {
+			copies2 := d2.copiesForGoals[i]
+			if expected, actual := copies1.goals, copies2.goals; expected != actual {
+				return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual)
+			}
+
+			if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual {
+				return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual)
+			}
+
+			for j, c1 := range copies1.copies {
+				c2 := copies2.copies[j]
+				if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual {
+					return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+
+				if expected, actual := c1.dest, c2.dest; expected != actual {
+					return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+			}
+		}
+
+		return nil
 	}
+
+	formatContributions := func(d *distContributions) string {
+		buf := &strings.Builder{}
+		if d == nil {
+			fmt.Fprint(buf, "nil")
+		} else {
+			for _, copiesForGoals := range d.copiesForGoals {
+				fmt.Fprintf(buf, "    Goals: %q {\n", copiesForGoals.goals)
+				for _, c := range copiesForGoals.copies {
+					fmt.Fprintf(buf, "        %s -> %s\n", NormalizePathForTesting(c.from), c.dest)
+				}
+				fmt.Fprint(buf, "    }\n")
+			}
+		}
+		return buf.String()
+	}
+
+	testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) {
+		t.Helper()
+		t.Run(name, func(t *testing.T) {
+			t.Helper()
+
+			config, module := buildConfigAndCustomModuleFoo(t, bp)
+			entries := AndroidMkEntriesForTest(t, config, "", module)
+			if len(entries) != 1 {
+				t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+			}
+			distContributions := entries[0].getDistContributions(module)
+
+			if err := compareContributions(expectedContributions, distContributions); err != nil {
+				t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s",
+					err,
+					formatContributions(expectedContributions),
+					formatContributions(distContributions))
+			}
+		})
+	}
+
+	testHelper(t, "dist-without-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"]
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-with-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+					tag: ".another-tag",
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dists-with-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+						tag: ".another-tag",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "multiple-dists-with-and-without-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-without-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+				},
+				dists: [
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-with-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal", "my_other_goal"],
+					tag: ".multiple",
+				},
+				dists: [
+					{
+						targets: ["my_second_goal"],
+						tag: ".multiple",
+					},
+					{
+						targets: ["my_third_goal"],
+						dir: "test/dir",
+					},
+					{
+						targets: ["my_fourth_goal"],
+						suffix: ".suffix",
+					},
+					{
+						targets: ["my_fifth_goal"],
+						dest: "new-name",
+					},
+					{
+						targets: ["my_sixth_goal"],
+						dest: "new-name",
+						dir: "some/dir",
+						suffix: ".suffix",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+				{
+					goals: "my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "test/dir/one.out"),
+					},
+				},
+				{
+					goals: "my_fourth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.suffix.out"),
+					},
+				},
+				{
+					goals: "my_fifth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "new-name"),
+					},
+				},
+				{
+					goals: "my_sixth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "some/dir/new-name.suffix"),
+					},
+				},
+				{
+					goals: "my_goal my_other_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+			},
+		})
+
+	// The above test the default values of default_dist_files and use_output_file.
+
+	// The following tests explicitly test the different combinations of those settings.
+	testHelper(t, "tagged-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: false,
+				dists: [
+					// The following is silently ignored because there is not default file
+					// in either the dist files or the output file.
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "tagged-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("dist-output-file.out", "dist-output-file.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
 }
diff --git a/android/apex.go b/android/apex.go
index e70ec4f..a4ff0f9 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -25,32 +25,54 @@
 )
 
 var (
+	// This is the sdk version when APEX was first introduced
 	SdkVersion_Android10 = uncheckedFinalApiLevel(29)
 )
 
-// ApexInfo describes the metadata common to all modules in an apexBundle.
+// ApexInfo describes the metadata about one or more apexBundles that an apex variant of a module is
+// part of.  When an apex variant is created, the variant is associated with one apexBundle. But
+// when multiple apex variants are merged for deduping (see mergeApexVariations), this holds the
+// information about the apexBundles that are merged together.
+// Accessible via `ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)`
 type ApexInfo struct {
-	// Name of the apex variation that this module is mutated into, or "" for
-	// a platform variant.  Note that a module can be included in multiple APEXes,
-	// in which case, the module is mutated into one or more variants, each of
-	// which is for one or more APEXes.
+	// Name of the apex variation that this module (i.e. the apex variant of the module) is
+	// mutated into, or "" for a platform (i.e. non-APEX) variant. Note that a module can be
+	// included in multiple APEXes, in which case, the module is mutated into one or more
+	// variants, each of which is for an APEX. The variants then can later be deduped if they
+	// don't need to be compiled differently. This is an optimization done in
+	// mergeApexVariations.
 	ApexVariationName string
 
-	// Serialized ApiLevel. Use via MinSdkVersion() method. Cannot be stored in
-	// its struct form because this is cloned into properties structs, and
-	// ApiLevel has private members.
+	// Serialized ApiLevel that this module has to support at minimum. Should be accessed via
+	// MinSdkVersion() method. Cannot be stored in its struct form because this is cloned into
+	// properties structs, and ApiLevel has private members.
 	MinSdkVersionStr string
 
-	// True if the module comes from an updatable APEX.
-	Updatable    bool
+	// True if this module comes from an updatable apexBundle.
+	Updatable bool
+
+	// The list of SDK modules that the containing apexBundle depends on.
 	RequiredSdks SdkRefs
 
-	InApexes     []string
+	// List of apexBundles that this apex variant of the module is associated with. Initially,
+	// the size of this list is one because one apex variant is associated with one apexBundle.
+	// When multiple apex variants are merged in mergeApexVariations, ApexInfo struct of the
+	// merged variant holds the list of apexBundles that are merged together.
+	InApexes []string
+
+	// Pointers to the ApexContents struct each of which is for apexBundle modules that this
+	// module is part of. The ApexContents gives information about which modules the apexBundle
+	// has and whether a module became part of the apexBundle via a direct dependency or not.
 	ApexContents []*ApexContents
 }
 
 var ApexInfoProvider = blueprint.NewMutatorProvider(ApexInfo{}, "apex")
 
+// mergedName gives the name of the alias variation that will be used when multiple apex variations
+// of a module can be deduped into one variation. For example, if libfoo is included in both apex.a
+// and apex.b, and if the two APEXes have the same min_sdk_version (say 29), then libfoo doesn't
+// have to be built twice, but only once. In that case, the two apex variations apex.a and apex.b
+// are configured to have the same alias variation named apex29.
 func (i ApexInfo) mergedName(ctx PathContext) string {
 	name := "apex" + strconv.Itoa(i.MinSdkVersion(ctx).FinalOrFutureInt())
 	for _, sdk := range i.RequiredSdks {
@@ -59,14 +81,19 @@
 	return name
 }
 
-func (this *ApexInfo) MinSdkVersion(ctx PathContext) ApiLevel {
-	return ApiLevelOrPanic(ctx, this.MinSdkVersionStr)
+// MinSdkVersion gives the api level that this module has to support at minimum. This is from the
+// min_sdk_version property of the containing apexBundle.
+func (i ApexInfo) MinSdkVersion(ctx PathContext) ApiLevel {
+	return ApiLevelOrPanic(ctx, i.MinSdkVersionStr)
 }
 
+// IsForPlatform tells whether this module is for the platform or not. If false is returned, it
+// means that this apex variant of the module is built for an APEX.
 func (i ApexInfo) IsForPlatform() bool {
 	return i.ApexVariationName == ""
 }
 
+// InApex tells whether this apex variant of the module is part of the given apexBundle or not.
 func (i ApexInfo) InApex(apex string) bool {
 	for _, a := range i.InApexes {
 		if a == apex {
@@ -76,102 +103,111 @@
 	return false
 }
 
-// ApexTestForInfo stores the contents of APEXes for which this module is a test and thus has
-// access to APEX internals.
+// ApexTestForInfo stores the contents of APEXes for which this module is a test - although this
+// module is not part of the APEX - and thus has access to APEX internals.
 type ApexTestForInfo struct {
 	ApexContents []*ApexContents
 }
 
 var ApexTestForInfoProvider = blueprint.NewMutatorProvider(ApexTestForInfo{}, "apex_test_for")
 
-// Extracted from ApexModule to make it easier to define custom subsets of the
-// ApexModule interface and improve code navigation within the IDE.
+// DepIsInSameApex defines an interface that should be used to determine whether a given dependency
+// should be considered as part of the same APEX as the current module or not. Note: this was
+// extracted from ApexModule to make it easier to define custom subsets of the ApexModule interface
+// and improve code navigation within the IDE.
 type DepIsInSameApex interface {
-	// DepIsInSameApex tests if the other module 'dep' is installed to the same
-	// APEX as this module
+	// DepIsInSameApex tests if the other module 'dep' is considered as part of the same APEX as
+	// this module. For example, a static lib dependency usually returns true here, while a
+	// shared lib dependency to a stub library returns false.
 	DepIsInSameApex(ctx BaseModuleContext, dep Module) bool
 }
 
-// ApexModule is the interface that a module type is expected to implement if
-// the module has to be built differently depending on whether the module
-// is destined for an apex or not (installed to one of the regular partitions).
+// ApexModule is the interface that a module type is expected to implement if the module has to be
+// built differently depending on whether the module is destined for an APEX or not (i.e., installed
+// to one of the regular partitions).
 //
-// Native shared libraries are one such module type; when it is built for an
-// APEX, it should depend only on stable interfaces such as NDK, stable AIDL,
-// or C APIs from other APEXs.
+// Native shared libraries are one such module type; when it is built for an APEX, it should depend
+// only on stable interfaces such as NDK, stable AIDL, or C APIs from other APEXes.
 //
-// A module implementing this interface will be mutated into multiple
-// variations by apex.apexMutator if it is directly or indirectly included
-// in one or more APEXs. Specifically, if a module is included in apex.foo and
-// apex.bar then three apex variants are created: platform, apex.foo and
-// apex.bar. The platform variant is for the regular partitions
-// (e.g., /system or /vendor, etc.) while the other two are for the APEXs,
-// respectively.
+// A module implementing this interface will be mutated into multiple variations by apex.apexMutator
+// if it is directly or indirectly included in one or more APEXes. Specifically, if a module is
+// included in apex.foo and apex.bar then three apex variants are created: platform, apex.foo and
+// apex.bar. The platform variant is for the regular partitions (e.g., /system or /vendor, etc.)
+// while the other two are for the APEXs, respectively. The latter two variations can be merged (see
+// mergedName) when the two APEXes have the same min_sdk_version requirement.
 type ApexModule interface {
 	Module
 	DepIsInSameApex
 
 	apexModuleBase() *ApexModuleBase
 
-	// Marks that this module should be built for the specified APEX.
-	// Call this before apex.apexMutator is run.
+	// Marks that this module should be built for the specified APEX. Call this BEFORE
+	// apex.apexMutator is run.
 	BuildForApex(apex ApexInfo)
 
-	// Returns true if this module is present in any APEXes
-	// directly or indirectly.
-	// Call this after apex.apexMutator is run.
+	// Returns true if this module is present in any APEX either directly or indirectly. Call
+	// this after apex.apexMutator is run.
 	InAnyApex() bool
 
-	// Returns true if this module is directly in any APEXes.
-	// Call this after apex.apexMutator is run.
+	// Returns true if this module is directly in any APEX. Call this AFTER apex.apexMutator is
+	// run.
 	DirectlyInAnyApex() bool
 
-	// Returns true if any variant of this module is directly in any APEXes.
-	// Call this after apex.apexMutator is run.
+	// Returns true in the primary variant of a module if _any_ variant of the module is
+	// directly in any apex. This includes host, arch, asan, etc. variants. It is unused in any
+	// variant that is not the primary variant. Ideally this wouldn't be used, as it incorrectly
+	// mixes arch variants if only one arch is in an apex, but a few places depend on it, for
+	// example when an ASAN variant is created before the apexMutator. Call this after
+	// apex.apexMutator is run.
 	AnyVariantDirectlyInAnyApex() bool
 
-	// Tests if this module could have APEX variants. APEX variants are
-	// created only for the modules that returns true here. This is useful
-	// for not creating APEX variants for certain types of shared libraries
-	// such as NDK stubs.
+	// Tests if this module could have APEX variants. Even when a module type implements
+	// ApexModule interface, APEX variants are created only for the module instances that return
+	// true here. This is useful for not creating APEX variants for certain types of shared
+	// libraries such as NDK stubs.
 	CanHaveApexVariants() bool
 
-	// Tests if this module can be installed to APEX as a file. For example,
-	// this would return true for shared libs while return false for static
-	// libs.
+	// Tests if this module can be installed to APEX as a file. For example, this would return
+	// true for shared libs while return false for static libs because static libs are not
+	// installable module (but it can still be mutated for APEX)
 	IsInstallableToApex() bool
 
-	// Tests if this module is available for the specified APEX or ":platform"
+	// Tests if this module is available for the specified APEX or ":platform". This is from the
+	// apex_available property of the module.
 	AvailableFor(what string) bool
 
-	// Return true if this module is not available to platform (i.e. apex_available
-	// property doesn't have "//apex_available:platform"), or shouldn't be available
-	// to platform, which is the case when this module depends on other module that
-	// isn't available to platform.
+	// Returns true if this module is not available to platform (i.e. apex_available property
+	// doesn't have "//apex_available:platform"), or shouldn't be available to platform, which
+	// is the case when this module depends on other module that isn't available to platform.
 	NotAvailableForPlatform() bool
 
-	// Mark that this module is not available to platform. Set by the
+	// Marks that this module is not available to platform. Set by the
 	// check-platform-availability mutator in the apex package.
 	SetNotAvailableForPlatform()
 
-	// List of APEXes that this module tests. The module has access to
-	// the private part of the listed APEXes even when it is not included in the
-	// APEXes.
+	// Returns the list of APEXes that this module is a test for. The module has access to the
+	// private part of the listed APEXes even when it is not included in the APEXes. This by
+	// default returns nil. A module type should override the default implementation. For
+	// example, cc_test module type returns the value of test_for here.
 	TestFor() []string
 
-	// Returns nil if this module supports sdkVersion
-	// Otherwise, returns error with reason
+	// Returns nil (success) if this module should support the given sdk version. Returns an
+	// error if not. No default implementation is provided for this method. A module type
+	// implementing this interface should provide an implementation. A module supports an sdk
+	// version when the module's min_sdk_version is equal to or less than the given sdk version.
 	ShouldSupportSdkVersion(ctx BaseModuleContext, sdkVersion ApiLevel) error
 
-	// Returns true if this module needs a unique variation per apex, for example if
-	// use_apex_name_macro is set.
+	// Returns true if this module needs a unique variation per apex, effectively disabling the
+	// deduping. This is turned on when, for example if use_apex_name_macro is set so that each
+	// apex variant should be built with different macro definitions.
 	UniqueApexVariations() bool
 }
 
+// Properties that are common to all module types implementing ApexModule interface.
 type ApexProperties struct {
-	// Availability of this module in APEXes. Only the listed APEXes can contain
-	// this module. If the module has stubs then other APEXes and the platform may
-	// access it through them (subject to visibility).
+	// Availability of this module in APEXes. Only the listed APEXes can contain this module. If
+	// the module has stubs then other APEXes and the platform may access it through them
+	// (subject to visibility).
 	//
 	// "//apex_available:anyapex" is a pseudo APEX name that matches to any APEX.
 	// "//apex_available:platform" refers to non-APEX partitions like "system.img".
@@ -179,29 +215,23 @@
 	// Default is ["//apex_available:platform"].
 	Apex_available []string
 
-	// AnyVariantDirectlyInAnyApex is true in the primary variant of a module if _any_ variant
-	// of the module is directly in any apex.  This includes host, arch, asan, etc. variants.
-	// It is unused in any variant that is not the primary variant.
-	// Ideally this wouldn't be used, as it incorrectly mixes arch variants if only one arch
-	// is in an apex, but a few places depend on it, for example when an ASAN variant is
-	// created before the apexMutator.
-	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// DirectlyInAnyApex is true if any APEX variant (including the "" variant used for the
-	// platform) of this module is directly in any APEX.
-	DirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// DirectlyInAnyApex is true if any APEX variant (including the "" variant used for the
-	// platform) of this module is directly or indirectly in any APEX.
+	// See ApexModule.InAnyApex()
 	InAnyApex bool `blueprint:"mutated"`
 
+	// See ApexModule.DirectlyInAnyApex()
+	DirectlyInAnyApex bool `blueprint:"mutated"`
+
+	// See ApexModule.AnyVariantDirectlyInAnyApex()
+	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
+
+	// See ApexModule.NotAvailableForPlatform()
 	NotAvailableForPlatform bool `blueprint:"mutated"`
 
+	// See ApexModule.UniqueApexVariants()
 	UniqueApexVariationsForDeps bool `blueprint:"mutated"`
 }
 
-// Marker interface that identifies dependencies that are excluded from APEX
-// contents.
+// Marker interface that identifies dependencies that are excluded from APEX contents.
 type ExcludeFromApexContentsTag interface {
 	blueprint.DependencyTag
 
@@ -209,85 +239,122 @@
 	ExcludeFromApexContents()
 }
 
-// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex
-// state from the parent to the child.  For example, stubs libraries are marked as
-// DirectlyInAnyApex if their implementation is in an apex.
+// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
+// from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
+// their implementation is in an apex.
 type CopyDirectlyInAnyApexTag interface {
 	blueprint.DependencyTag
 
+	// Method that differentiates this interface from others.
 	CopyDirectlyInAnyApex()
 }
 
-// Provides default implementation for the ApexModule interface. APEX-aware
+// ApexModuleBase provides the default implementation for the ApexModule interface. APEX-aware
 // modules are expected to include this struct and call InitApexModule().
 type ApexModuleBase struct {
 	ApexProperties ApexProperties
 
 	canHaveApexVariants bool
 
-	apexVariationsLock sync.Mutex // protects apexVariations during parallel apexDepsMutator
-	apexVariations     []ApexInfo
+	apexInfos     []ApexInfo
+	apexInfosLock sync.Mutex // protects apexInfos during parallel apexDepsMutator
 }
 
+// Initializes ApexModuleBase struct. Not calling this (even when inheriting from ApexModuleBase)
+// prevents the module from being mutated for apexBundle.
+func InitApexModule(m ApexModule) {
+	base := m.apexModuleBase()
+	base.canHaveApexVariants = true
+
+	m.AddProperties(&base.ApexProperties)
+}
+
+// Implements ApexModule
 func (m *ApexModuleBase) apexModuleBase() *ApexModuleBase {
 	return m
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) ApexAvailable() []string {
 	return m.ApexProperties.Apex_available
 }
 
-func (m *ApexModuleBase) TestFor() []string {
-	// To be implemented by concrete types inheriting ApexModuleBase
-	return nil
-}
-
-func (m *ApexModuleBase) UniqueApexVariations() bool {
-	return false
-}
-
+// Implements ApexModule
 func (m *ApexModuleBase) BuildForApex(apex ApexInfo) {
-	m.apexVariationsLock.Lock()
-	defer m.apexVariationsLock.Unlock()
-	for _, v := range m.apexVariations {
+	m.apexInfosLock.Lock()
+	defer m.apexInfosLock.Unlock()
+	for _, v := range m.apexInfos {
 		if v.ApexVariationName == apex.ApexVariationName {
 			return
 		}
 	}
-	m.apexVariations = append(m.apexVariations, apex)
+	m.apexInfos = append(m.apexInfos, apex)
 }
 
-func (m *ApexModuleBase) DirectlyInAnyApex() bool {
-	return m.ApexProperties.DirectlyInAnyApex
-}
-
-func (m *ApexModuleBase) AnyVariantDirectlyInAnyApex() bool {
-	return m.ApexProperties.AnyVariantDirectlyInAnyApex
-}
-
+// Implements ApexModule
 func (m *ApexModuleBase) InAnyApex() bool {
 	return m.ApexProperties.InAnyApex
 }
 
+// Implements ApexModule
+func (m *ApexModuleBase) DirectlyInAnyApex() bool {
+	return m.ApexProperties.DirectlyInAnyApex
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) AnyVariantDirectlyInAnyApex() bool {
+	return m.ApexProperties.AnyVariantDirectlyInAnyApex
+}
+
+// Implements ApexModule
 func (m *ApexModuleBase) CanHaveApexVariants() bool {
 	return m.canHaveApexVariants
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) IsInstallableToApex() bool {
-	// should be overriden if needed
+	// If needed, this will bel overridden by concrete types inheriting
+	// ApexModuleBase
 	return false
 }
 
+// Implements ApexModule
+func (m *ApexModuleBase) TestFor() []string {
+	// If needed, this will be overridden by concrete types inheriting
+	// ApexModuleBase
+	return nil
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) UniqueApexVariations() bool {
+	// If needed, this will bel overridden by concrete types inheriting
+	// ApexModuleBase
+	return false
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
+	// By default, if there is a dependency from A to B, we try to include both in the same
+	// APEX, unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning
+	// true. This is overridden by some module types like apex.ApexBundle, cc.Module,
+	// java.Module, etc.
+	return true
+}
+
 const (
 	AvailableToPlatform = "//apex_available:platform"
 	AvailableToAnyApex  = "//apex_available:anyapex"
 	AvailableToGkiApex  = "com.android.gki.*"
 )
 
+// CheckAvailableForApex provides the default algorithm for checking the apex availability. When the
+// availability is empty, it defaults to ["//apex_available:platform"] which means "available to the
+// platform but not available to any APEX". When the list is not empty, `what` is matched against
+// the list. If there is any matching element in the list, thus function returns true. The special
+// availability "//apex_available:anyapex" matches with anything except for
+// "//apex_available:platform".
 func CheckAvailableForApex(what string, apex_available []string) bool {
 	if len(apex_available) == 0 {
-		// apex_available defaults to ["//apex_available:platform"],
-		// which means 'available to the platform but no apexes'.
 		return what == AvailableToPlatform
 	}
 	return InList(what, apex_available) ||
@@ -295,25 +362,22 @@
 		(strings.HasPrefix(what, "com.android.gki.") && InList(AvailableToGkiApex, apex_available))
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) AvailableFor(what string) bool {
 	return CheckAvailableForApex(what, m.ApexProperties.Apex_available)
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) NotAvailableForPlatform() bool {
 	return m.ApexProperties.NotAvailableForPlatform
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) SetNotAvailableForPlatform() {
 	m.ApexProperties.NotAvailableForPlatform = true
 }
 
-func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
-	// By default, if there is a dependency from A to B, we try to include both in the same APEX,
-	// unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning true.
-	// This is overridden by some module types like apex.ApexBundle, cc.Module, java.Module, etc.
-	return true
-}
-
+// This function makes sure that the apex_available property is valid
 func (m *ApexModuleBase) checkApexAvailableProperty(mctx BaseModuleContext) {
 	for _, n := range m.ApexProperties.Apex_available {
 		if n == AvailableToPlatform || n == AvailableToAnyApex || n == AvailableToGkiApex {
@@ -331,22 +395,23 @@
 func (a byApexName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a byApexName) Less(i, j int) bool { return a[i].ApexVariationName < a[j].ApexVariationName }
 
-// mergeApexVariations deduplicates APEX variations that would build identically into a common
-// variation.  It returns the reduced list of variations and a list of aliases from the original
+// mergeApexVariations deduplicates apex variations that would build identically into a common
+// variation. It returns the reduced list of variations and a list of aliases from the original
 // variation names to the new variation names.
-func mergeApexVariations(ctx PathContext, apexVariations []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
-	sort.Sort(byApexName(apexVariations))
+func mergeApexVariations(ctx PathContext, apexInfos []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
+	sort.Sort(byApexName(apexInfos))
 	seen := make(map[string]int)
-	for _, apexInfo := range apexVariations {
+	for _, apexInfo := range apexInfos {
 		apexName := apexInfo.ApexVariationName
 		mergedName := apexInfo.mergedName(ctx)
 		if index, exists := seen[mergedName]; exists {
+			// Variants having the same mergedName are deduped
 			merged[index].InApexes = append(merged[index].InApexes, apexName)
 			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
 			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
 		} else {
 			seen[mergedName] = len(merged)
-			apexInfo.ApexVariationName = apexInfo.mergedName(ctx)
+			apexInfo.ApexVariationName = mergedName
 			apexInfo.InApexes = CopyOf(apexInfo.InApexes)
 			apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
 			merged = append(merged, apexInfo)
@@ -356,75 +421,78 @@
 	return merged, aliases
 }
 
+// CreateApexVariations mutates a given module into multiple apex variants each of which is for an
+// apexBundle (and/or the platform) where the module is part of.
 func CreateApexVariations(mctx BottomUpMutatorContext, module ApexModule) []Module {
 	base := module.apexModuleBase()
-	if len(base.apexVariations) > 0 {
-		base.checkApexAvailableProperty(mctx)
 
-		var apexVariations []ApexInfo
-		var aliases [][2]string
-		if !mctx.Module().(ApexModule).UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
-			apexVariations, aliases = mergeApexVariations(mctx, base.apexVariations)
-		} else {
-			apexVariations = base.apexVariations
-		}
-		// base.apexVariations is only needed to propagate the list of apexes from
-		// apexDepsMutator to apexMutator.  It is no longer accurate after
-		// mergeApexVariations, and won't be copied to all but the first created
-		// variant.  Clear it so it doesn't accidentally get used later.
-		base.apexVariations = nil
-
-		sort.Sort(byApexName(apexVariations))
-		variations := []string{}
-		variations = append(variations, "") // Original variation for platform
-		for _, apex := range apexVariations {
-			variations = append(variations, apex.ApexVariationName)
-		}
-
-		defaultVariation := ""
-		mctx.SetDefaultDependencyVariation(&defaultVariation)
-
-		var inApex ApexMembership
-		for _, a := range apexVariations {
-			for _, apexContents := range a.ApexContents {
-				inApex = inApex.merge(apexContents.contents[mctx.ModuleName()])
-			}
-		}
-
-		base.ApexProperties.InAnyApex = true
-		base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
-
-		modules := mctx.CreateVariations(variations...)
-		for i, mod := range modules {
-			platformVariation := i == 0
-			if platformVariation && !mctx.Host() && !mod.(ApexModule).AvailableFor(AvailableToPlatform) {
-				// Do not install the module for platform, but still allow it to output
-				// uninstallable AndroidMk entries in certain cases when they have
-				// side effects.
-				mod.MakeUninstallable()
-			}
-			if !platformVariation {
-				mctx.SetVariationProvider(mod, ApexInfoProvider, apexVariations[i-1])
-			}
-		}
-
-		for _, alias := range aliases {
-			mctx.CreateAliasVariation(alias[0], alias[1])
-		}
-
-		return modules
+	// Shortcut
+	if len(base.apexInfos) == 0 {
+		return nil
 	}
-	return nil
+
+	// Do some validity checks.
+	// TODO(jiyong): is this the right place?
+	base.checkApexAvailableProperty(mctx)
+
+	var apexInfos []ApexInfo
+	var aliases [][2]string
+	if !mctx.Module().(ApexModule).UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
+		apexInfos, aliases = mergeApexVariations(mctx, base.apexInfos)
+	} else {
+		apexInfos = base.apexInfos
+	}
+	// base.apexInfos is only needed to propagate the list of apexes from apexDepsMutator to
+	// apexMutator. It is no longer accurate after mergeApexVariations, and won't be copied to
+	// all but the first created variant. Clear it so it doesn't accidentally get used later.
+	base.apexInfos = nil
+	sort.Sort(byApexName(apexInfos))
+
+	var inApex ApexMembership
+	for _, a := range apexInfos {
+		for _, apexContents := range a.ApexContents {
+			inApex = inApex.merge(apexContents.contents[mctx.ModuleName()])
+		}
+	}
+	base.ApexProperties.InAnyApex = true
+	base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
+
+	defaultVariation := ""
+	mctx.SetDefaultDependencyVariation(&defaultVariation)
+
+	variations := []string{defaultVariation}
+	for _, a := range apexInfos {
+		variations = append(variations, a.ApexVariationName)
+	}
+	modules := mctx.CreateVariations(variations...)
+	for i, mod := range modules {
+		platformVariation := i == 0
+		if platformVariation && !mctx.Host() && !mod.(ApexModule).AvailableFor(AvailableToPlatform) {
+			// Do not install the module for platform, but still allow it to output
+			// uninstallable AndroidMk entries in certain cases when they have side
+			// effects.  TODO(jiyong): move this routine to somewhere else
+			mod.MakeUninstallable()
+		}
+		if !platformVariation {
+			mctx.SetVariationProvider(mod, ApexInfoProvider, apexInfos[i-1])
+		}
+	}
+
+	for _, alias := range aliases {
+		mctx.CreateAliasVariation(alias[0], alias[1])
+	}
+
+	return modules
 }
 
-// UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies
-// that are in the same APEX have unique APEX variations so that the module can link against
-// the right variant.
+// UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies that are
+// in the same APEX have unique APEX variations so that the module can link against the right
+// variant.
 func UpdateUniqueApexVariationsForDeps(mctx BottomUpMutatorContext, am ApexModule) {
-	// anyInSameApex returns true if the two ApexInfo lists contain any values in an InApexes list
-	// in common.  It is used instead of DepIsInSameApex because it needs to determine if the dep
-	// is in the same APEX due to being directly included, not only if it is included _because_ it
-	// is a dependency.
+	// anyInSameApex returns true if the two ApexInfo lists contain any values in an InApexes
+	// list in common. It is used instead of DepIsInSameApex because it needs to determine if
+	// the dep is in the same APEX due to being directly included, not only if it is included
+	// _because_ it is a dependency.
 	anyInSameApex := func(a, b []ApexInfo) bool {
 		collectApexes := func(infos []ApexInfo) []string {
 			var ret []string
@@ -446,9 +514,10 @@
 		return false
 	}
 
+	// If any of the dependencies requires unique apex variations, so does this module.
 	mctx.VisitDirectDeps(func(dep Module) {
 		if depApexModule, ok := dep.(ApexModule); ok {
-			if anyInSameApex(depApexModule.apexModuleBase().apexVariations, am.apexModuleBase().apexVariations) &&
+			if anyInSameApex(depApexModule.apexModuleBase().apexInfos, am.apexModuleBase().apexInfos) &&
 				(depApexModule.UniqueApexVariations() ||
 					depApexModule.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps) {
 				am.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps = true
@@ -457,10 +526,10 @@
 	})
 }
 
-// UpdateDirectlyInAnyApex uses the final module to store if any variant of this
-// module is directly in any APEX, and then copies the final value to all the modules.
-// It also copies the DirectlyInAnyApex value to any direct dependencies with a
-// CopyDirectlyInAnyApexTag dependency tag.
+// UpdateDirectlyInAnyApex uses the final module to store if any variant of this module is directly
+// in any APEX, and then copies the final value to all the modules. It also copies the
+// DirectlyInAnyApex value to any direct dependencies with a CopyDirectlyInAnyApexTag dependency
+// tag.
 func UpdateDirectlyInAnyApex(mctx BottomUpMutatorContext, am ApexModule) {
 	base := am.apexModuleBase()
 	// Copy DirectlyInAnyApex and InAnyApex from any direct dependencies with a
@@ -475,14 +544,13 @@
 
 	if base.ApexProperties.DirectlyInAnyApex {
 		// Variants of a module are always visited sequentially in order, so it is safe to
-		// write to another variant of this module.
-		// For a BottomUpMutator the PrimaryModule() is visited first and FinalModule() is
-		// visited last.
+		// write to another variant of this module. For a BottomUpMutator the
+		// PrimaryModule() is visited first and FinalModule() is visited last.
 		mctx.FinalModule().(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex = true
 	}
 
-	// If this is the FinalModule (last visited module) copy AnyVariantDirectlyInAnyApex to
-	// all the other variants
+	// If this is the FinalModule (last visited module) copy
+	// AnyVariantDirectlyInAnyApex to all the other variants
 	if am == mctx.FinalModule().(ApexModule) {
 		mctx.VisitAllModuleVariants(func(variant Module) {
 			variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
@@ -491,6 +559,7 @@
 	}
 }
 
+// ApexMembership tells how a module became part of an APEX.
 type ApexMembership int
 
 const (
@@ -499,20 +568,21 @@
 	directlyInApex
 )
 
-// Each apexBundle has an apexContents, and modules in that apex have a provider containing the
-// apexContents of each apexBundle they are part of.
+// ApexContents gives an information about member modules of an apexBundle.  Each apexBundle has an
+// apexContents, and modules in that apex have a provider containing the apexContents of each
+// apexBundle they are part of.
 type ApexContents struct {
-	ApexName string
+	// map from a module name to its membership to this apexBUndle
 	contents map[string]ApexMembership
 }
 
-func NewApexContents(name string, contents map[string]ApexMembership) *ApexContents {
+func NewApexContents(contents map[string]ApexMembership) *ApexContents {
 	return &ApexContents{
-		ApexName: name,
 		contents: contents,
 	}
 }
 
+// Updates an existing membership by adding a new direct (or indirect) membership
 func (i ApexMembership) Add(direct bool) ApexMembership {
 	if direct || i == directlyInApex {
 		return directlyInApex
@@ -520,6 +590,10 @@
 	return indirectlyInApex
 }
 
+// Merges two membership into one. Merging is needed because a module can be a part of an apexBundle
+// in many different paths. For example, it could be dependend on by the apexBundle directly, but at
+// the same time, there might be an indirect dependency to the module. In that case, the more
+// specific dependency (the direct one) is chosen.
 func (i ApexMembership) merge(other ApexMembership) ApexMembership {
 	if other == directlyInApex || i == directlyInApex {
 		return directlyInApex
@@ -531,16 +605,19 @@
 	return notInApex
 }
 
-func (ac *ApexContents) DirectlyInApex(name string) bool {
-	return ac.contents[name] == directlyInApex
+// Tests whether a module named moduleName is directly included in the apexBundle where this
+// ApexContents is tagged.
+func (ac *ApexContents) DirectlyInApex(moduleName string) bool {
+	return ac.contents[moduleName] == directlyInApex
 }
 
-func (ac *ApexContents) InApex(name string) bool {
-	return ac.contents[name] != notInApex
+// Tests whether a module named moduleName is included in the apexBundle where this ApexContent is
+// tagged.
+func (ac *ApexContents) InApex(moduleName string) bool {
+	return ac.contents[moduleName] != notInApex
 }
 
-// Tests whether a module named moduleName is directly depended on by all APEXes
-// in an ApexInfo.
+// Tests whether a module named moduleName is directly depended on by all APEXes in an ApexInfo.
 func DirectlyInAllApexes(apexInfo ApexInfo, moduleName string) bool {
 	for _, contents := range apexInfo.ApexContents {
 		if !contents.DirectlyInApex(moduleName) {
@@ -550,12 +627,15 @@
 	return true
 }
 
-func InitApexModule(m ApexModule) {
-	base := m.apexModuleBase()
-	base.canHaveApexVariants = true
-
-	m.AddProperties(&base.ApexProperties)
-}
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//Below are routines for extra safety checks.
+//
+// BuildDepsInfoLists is to flatten the dependency graph for an apexBundle into a text file
+// (actually two in slightly different formats). The files are mostly for debugging, for example to
+// see why a certain module is included in an APEX via which dependency path.
+//
+// CheckMinSdkVersion is to make sure that all modules in an apexBundle satisfy the min_sdk_version
+// requirement of the apexBundle.
 
 // A dependency info for a single ApexModule, either direct or transitive.
 type ApexModuleDepInfo struct {
@@ -598,36 +678,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
@@ -719,7 +785,8 @@
 	WalkPayloadDeps(ctx ModuleContext, do PayloadDepsCallback)
 }
 
-// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version accordingly
+// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version
+// accordingly
 func CheckMinSdkVersion(m UpdatableModule, ctx ModuleContext, minSdkVersion ApiLevel) {
 	// do not enforce min_sdk_version for host
 	if ctx.Host() {
@@ -739,8 +806,9 @@
 
 	m.WalkPayloadDeps(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
 		if externalDep {
-			// external deps are outside the payload boundary, which is "stable" interface.
-			// We don't have to check min_sdk_version for external dependencies.
+			// external deps are outside the payload boundary, which is "stable"
+			// interface. We don't have to check min_sdk_version for external
+			// dependencies.
 			return false
 		}
 		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
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..34f9b29 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -27,28 +27,6 @@
 	"github.com/google/blueprint/proptools"
 )
 
-const COMMON_VARIANT = "common"
-
-var (
-	archTypeList []ArchType
-
-	Arm    = newArch("arm", "lib32")
-	Arm64  = newArch("arm64", "lib64")
-	X86    = newArch("x86", "lib32")
-	X86_64 = newArch("x86_64", "lib64")
-
-	Common = ArchType{
-		Name: COMMON_VARIANT,
-	}
-)
-
-var archTypeMap = map[string]ArchType{
-	"arm":    Arm,
-	"arm64":  Arm64,
-	"x86":    X86,
-	"x86_64": X86_64,
-}
-
 /*
 Example blueprints file containing all variant property groups, with comment listing what type
 of variants get properties in that group:
@@ -111,405 +89,26 @@
 }
 */
 
-var archVariants = map[ArchType][]string{
-	Arm: {
-		"armv7-a",
-		"armv7-a-neon",
-		"armv8-a",
-		"armv8-2a",
-		"cortex-a7",
-		"cortex-a8",
-		"cortex-a9",
-		"cortex-a15",
-		"cortex-a53",
-		"cortex-a53-a57",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"krait",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
-	},
-	Arm64: {
-		"armv8_a",
-		"armv8_2a",
-		"armv8-2a-dotprod",
-		"cortex-a53",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
-	},
-	X86: {
-		"amberlake",
-		"atom",
-		"broadwell",
-		"haswell",
-		"icelake",
-		"ivybridge",
-		"kabylake",
-		"sandybridge",
-		"silvermont",
-		"skylake",
-		"stoneyridge",
-		"tigerlake",
-		"whiskeylake",
-		"x86_64",
-	},
-	X86_64: {
-		"amberlake",
-		"broadwell",
-		"haswell",
-		"icelake",
-		"ivybridge",
-		"kabylake",
-		"sandybridge",
-		"silvermont",
-		"skylake",
-		"stoneyridge",
-		"tigerlake",
-		"whiskeylake",
-	},
-}
-
-var archFeatures = map[ArchType][]string{
-	Arm: {
-		"neon",
-	},
-	Arm64: {
-		"dotprod",
-	},
-	X86: {
-		"ssse3",
-		"sse4",
-		"sse4_1",
-		"sse4_2",
-		"aes_ni",
-		"avx",
-		"avx2",
-		"avx512",
-		"popcnt",
-		"movbe",
-	},
-	X86_64: {
-		"ssse3",
-		"sse4",
-		"sse4_1",
-		"sse4_2",
-		"aes_ni",
-		"avx",
-		"avx2",
-		"avx512",
-		"popcnt",
-	},
-}
-
-var archFeatureMap = map[ArchType]map[string][]string{
-	Arm: {
-		"armv7-a-neon": {
-			"neon",
-		},
-		"armv8-a": {
-			"neon",
-		},
-		"armv8-2a": {
-			"neon",
-		},
-	},
-	Arm64: {
-		"armv8-2a-dotprod": {
-			"dotprod",
-		},
-	},
-	X86: {
-		"amberlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"atom": {
-			"ssse3",
-			"movbe",
-		},
-		"broadwell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"haswell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-			"movbe",
-		},
-		"icelake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"ivybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"kabylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"sandybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-		"silvermont": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"popcnt",
-			"movbe",
-		},
-		"skylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"stoneyridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"popcnt",
-			"movbe",
-		},
-		"tigerlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"whiskeylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"x86_64": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-	},
-	X86_64: {
-		"amberlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"broadwell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"haswell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"icelake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"ivybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"kabylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"sandybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-		"silvermont": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"popcnt",
-		},
-		"skylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"stoneyridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"popcnt",
-		},
-		"tigerlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"whiskeylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-	},
-}
-
-var defaultArchFeatureMap = map[OsType]map[ArchType][]string{}
-
-func RegisterDefaultArchVariantFeatures(os OsType, arch ArchType, features ...string) {
-	checkCalledFromInit()
-
-	for _, feature := range features {
-		if !InList(feature, archFeatures[arch]) {
-			panic(fmt.Errorf("Invalid feature %q for arch %q variant \"\"", feature, arch))
-		}
-	}
-
-	if defaultArchFeatureMap[os] == nil {
-		defaultArchFeatureMap[os] = make(map[ArchType][]string)
-	}
-	defaultArchFeatureMap[os][arch] = features
-}
-
 // An Arch indicates a single CPU architecture.
 type Arch struct {
-	ArchType     ArchType
-	ArchVariant  string
-	CpuVariant   string
-	Abi          []string
+	// The type of the architecture (arm, arm64, x86, or x86_64).
+	ArchType ArchType
+
+	// The variant of the architecture, for example "armv7-a" or "armv7-a-neon" for arm.
+	ArchVariant string
+
+	// The variant of the CPU, for example "cortex-a53" for arm64.
+	CpuVariant string
+
+	// The list of Android app ABIs supported by the CPU architecture, for example "arm64-v8a".
+	Abi []string
+
+	// The list of arch-specific features supported by the CPU architecture, for example "neon".
 	ArchFeatures []string
 }
 
+// String returns the Arch as a string.  The value is used as the name of the variant created
+// by archMutator.
 func (a Arch) String() string {
 	s := a.ArchType.String()
 	if a.ArchVariant != "" {
@@ -521,12 +120,42 @@
 	return s
 }
 
+// ArchType is used to define the 4 supported architecture types (arm, arm64, x86, x86_64), as
+// well as the "common" architecture used for modules that support multiple architectures, for
+// example Java modules.
 type ArchType struct {
-	Name     string
-	Field    string
+	// Name is the name of the architecture type, "arm", "arm64", "x86", or "x86_64".
+	Name string
+
+	// Field is the name of the field used in properties that refer to the architecture, e.g. "Arm64".
+	Field string
+
+	// Multilib is either "lib32" or "lib64" for 32-bit or 64-bit architectures.
 	Multilib string
 }
 
+// String returns the name of the ArchType.
+func (a ArchType) String() string {
+	return a.Name
+}
+
+const COMMON_VARIANT = "common"
+
+var (
+	archTypeList []ArchType
+
+	Arm    = newArch("arm", "lib32")
+	Arm64  = newArch("arm64", "lib64")
+	X86    = newArch("x86", "lib32")
+	X86_64 = newArch("x86_64", "lib64")
+
+	Common = ArchType{
+		Name: COMMON_VARIANT,
+	}
+)
+
+var archTypeMap = map[string]ArchType{}
+
 func newArch(name, multilib string) ArchType {
 	archType := ArchType{
 		Name:     name,
@@ -534,25 +163,25 @@
 		Multilib: multilib,
 	}
 	archTypeList = append(archTypeList, archType)
+	archTypeMap[name] = archType
 	return archType
 }
 
+// ArchTypeList returns the 4 supported ArchTypes for arm, arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
 }
 
-func (a ArchType) String() string {
-	return a.Name
-}
-
-var _ encoding.TextMarshaler = ArchType{}
-
+// MarshalText allows an ArchType to be serialized through any encoder that supports
+// encoding.TextMarshaler.
 func (a ArchType) MarshalText() ([]byte, error) {
 	return []byte(strconv.Quote(a.String())), nil
 }
 
-var _ encoding.TextUnmarshaler = &ArchType{}
+var _ encoding.TextMarshaler = ArchType{}
 
+// UnmarshalText allows an ArchType to be deserialized through any decoder that supports
+// encoding.TextUnmarshaler.
 func (a *ArchType) UnmarshalText(text []byte) error {
 	if u, ok := archTypeMap[string(text)]; ok {
 		*a = u
@@ -562,67 +191,22 @@
 	return fmt.Errorf("unknown ArchType %q", text)
 }
 
-var BuildOs = func() OsType {
-	switch runtime.GOOS {
-	case "linux":
-		return Linux
-	case "darwin":
-		return Darwin
-	default:
-		panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
-	}
-}()
+var _ encoding.TextUnmarshaler = &ArchType{}
 
-var BuildArch = func() ArchType {
-	switch runtime.GOARCH {
-	case "amd64":
-		return X86_64
-	default:
-		panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
-	}
-}()
-
-var (
-	OsTypeList      []OsType
-	commonTargetMap = make(map[string]Target)
-
-	NoOsType    OsType
-	Linux       = NewOsType("linux_glibc", Host, false)
-	Darwin      = NewOsType("darwin", Host, false)
-	LinuxBionic = NewOsType("linux_bionic", Host, false)
-	Windows     = NewOsType("windows", Host, true)
-	Android     = NewOsType("android", Device, false)
-	Fuchsia     = NewOsType("fuchsia", Device, false)
-
-	// A pseudo OSType for a common os variant, which is OSType agnostic and which
-	// has dependencies on all the OS variants.
-	CommonOS = NewOsType("common_os", Generic, false)
-
-	osArchTypeMap = map[OsType][]ArchType{
-		Linux:       []ArchType{X86, X86_64},
-		LinuxBionic: []ArchType{Arm64, X86_64},
-		Darwin:      []ArchType{X86_64},
-		Windows:     []ArchType{X86, X86_64},
-		Android:     []ArchType{Arm, Arm64, X86, X86_64},
-		Fuchsia:     []ArchType{Arm64, X86_64},
-	}
-)
-
-type OsType struct {
-	Name, Field string
-	Class       OsClass
-
-	DefaultDisabled bool
-}
-
+// OsClass is an enum that describes whether a variant of a module runs on the host, on the device,
+// or is generic.
 type OsClass int
 
 const (
+	// Generic is used for variants of modules that are not OS-specific.
 	Generic OsClass = iota
+	// Device is used for variants of modules that run on the device.
 	Device
+	// Host is used for variants of modules that run on the host.
 	Host
 )
 
+// String returns the OsClass as a string.
 func (class OsClass) String() string {
 	switch class {
 	case Generic:
@@ -636,22 +220,48 @@
 	}
 }
 
+// OsType describes an OS variant of a module.
+type OsType struct {
+	// Name is the name of the OS.  It is also used as the name of the property in Android.bp
+	// files.
+	Name string
+
+	// Field is the name of the OS converted to an exported field name, i.e. with the first
+	// character capitalized.
+	Field string
+
+	// Class is the OsClass of the OS.
+	Class OsClass
+
+	// DefaultDisabled is set when the module variants for the OS should not be created unless
+	// the module explicitly requests them.  This is used to limit Windows cross compilation to
+	// only modules that need it.
+	DefaultDisabled bool
+}
+
+// String returns the name of the OsType.
 func (os OsType) String() string {
 	return os.Name
 }
 
+// Bionic returns true if the OS uses the Bionic libc runtime, i.e. if the OS is Android or
+// is Linux with Bionic.
 func (os OsType) Bionic() bool {
 	return os == Android || os == LinuxBionic
 }
 
+// Linux returns true if the OS uses the Linux kernel, i.e. if the OS is Android or is Linux
+// with or without the Bionic libc runtime.
 func (os OsType) Linux() bool {
 	return os == Android || os == Linux || os == LinuxBionic
 }
 
-func NewOsType(name string, class OsClass, defDisabled bool) OsType {
+// newOsType constructs an OsType and adds it to the global lists.
+func newOsType(name string, class OsClass, defDisabled bool, archTypes ...ArchType) OsType {
+	checkCalledFromInit()
 	os := OsType{
 		Name:  name,
-		Field: strings.Title(name),
+		Field: proptools.FieldNameForProperty(name),
 		Class: class,
 
 		DefaultDisabled: defDisabled,
@@ -661,12 +271,14 @@
 	if _, found := commonTargetMap[name]; found {
 		panic(fmt.Errorf("Found Os type duplicate during OsType registration: %q", name))
 	} else {
-		commonTargetMap[name] = Target{Os: os, Arch: Arch{ArchType: Common}}
+		commonTargetMap[name] = Target{Os: os, Arch: CommonArch}
 	}
+	osArchTypeMap[os] = archTypes
 
 	return os
 }
 
+// osByName returns the OsType that has the given name, or NoOsType if none match.
 func osByName(name string) OsType {
 	for _, os := range OsTypeList {
 		if os.Name == name {
@@ -677,18 +289,78 @@
 	return NoOsType
 }
 
-type NativeBridgeSupport bool
+// BuildOs returns the OsType for the OS that the build is running on.
+var BuildOs = func() OsType {
+	switch runtime.GOOS {
+	case "linux":
+		return Linux
+	case "darwin":
+		return Darwin
+	default:
+		panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
+	}
+}()
 
-const (
-	NativeBridgeDisabled NativeBridgeSupport = false
-	NativeBridgeEnabled  NativeBridgeSupport = true
+// BuildArch returns the ArchType for the CPU that the build is running on.
+var BuildArch = func() ArchType {
+	switch runtime.GOARCH {
+	case "amd64":
+		return X86_64
+	default:
+		panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
+	}
+}()
+
+var (
+	// OsTypeList contains a list of all the supported OsTypes, including ones not supported
+	// by the current build host or the target device.
+	OsTypeList []OsType
+	// commonTargetMap maps names of OsTypes to the corresponding common Target, i.e. the
+	// Target with the same OsType and the common ArchType.
+	commonTargetMap = make(map[string]Target)
+	// osArchTypeMap maps OsTypes to the list of supported ArchTypes for that OS.
+	osArchTypeMap = map[OsType][]ArchType{}
+
+	// NoOsType is a placeholder for when no OS is needed.
+	NoOsType OsType
+	// Linux is the OS for the Linux kernel plus the glibc runtime.
+	Linux = newOsType("linux_glibc", Host, false, X86, X86_64)
+	// Darwin is the OS for MacOS/Darwin host machines.
+	Darwin = newOsType("darwin", Host, false, X86_64)
+	// LinuxBionic is the OS for the Linux kernel plus the Bionic libc runtime, but without the
+	// rest of Android.
+	LinuxBionic = newOsType("linux_bionic", Host, false, Arm64, X86_64)
+	// Windows the OS for Windows host machines.
+	Windows = newOsType("windows", Host, true, X86, X86_64)
+	// Android is the OS for target devices that run all of Android, including the Linux kernel
+	// and the Bionic libc runtime.
+	Android = newOsType("android", Device, false, Arm, Arm64, X86, X86_64)
+	// Fuchsia is the OS for target devices that run Fuchsia.
+	Fuchsia = newOsType("fuchsia", Device, false, Arm64, X86_64)
+
+	// CommonOS is a pseudo OSType for a common OS variant, which is OsType agnostic and which
+	// has dependencies on all the OS variants.
+	CommonOS = newOsType("common_os", Generic, false)
+
+	// CommonArch is the Arch for all modules that are os-specific but not arch specific,
+	// for example most Java modules.
+	CommonArch = Arch{ArchType: Common}
 )
 
+// Target specifies the OS and architecture that a module is being compiled for.
 type Target struct {
-	Os                       OsType
-	Arch                     Arch
-	NativeBridge             NativeBridgeSupport
+	// Os the OS that the module is being compiled for (e.g. "linux_glibc", "android").
+	Os OsType
+	// Arch is the architecture that the module is being compiled for.
+	Arch Arch
+	// NativeBridge is NativeBridgeEnabled if the architecture is supported using NativeBridge
+	// (i.e. arm on x86) for this device.
+	NativeBridge NativeBridgeSupport
+	// NativeBridgeHostArchName is the name of the real architecture that is used to implement
+	// the NativeBridge architecture.  For example, for arm on x86 this would be "x86".
 	NativeBridgeHostArchName string
+	// NativeBridgeRelativePath is the name of the subdirectory that will contain NativeBridge
+	// libraries and binaries.
 	NativeBridgeRelativePath string
 
 	// HostCross is true when the target cannot run natively on the current build host.
@@ -697,14 +369,25 @@
 	HostCross bool
 }
 
+// NativeBridgeSupport is an enum that specifies if a Target supports NativeBridge.
+type NativeBridgeSupport bool
+
+const (
+	NativeBridgeDisabled NativeBridgeSupport = false
+	NativeBridgeEnabled  NativeBridgeSupport = true
+)
+
+// String returns the OS and arch variations used for the Target.
 func (target Target) String() string {
 	return target.OsVariation() + "_" + target.ArchVariation()
 }
 
+// OsVariation returns the name of the variation used by the osMutator for the Target.
 func (target Target) OsVariation() string {
 	return target.Os.String()
 }
 
+// ArchVariation returns the name of the variation used by the archMutator for the Target.
 func (target Target) ArchVariation() string {
 	var variation string
 	if target.NativeBridge {
@@ -715,6 +398,8 @@
 	return variation
 }
 
+// Variations returns a list of blueprint.Variations for the osMutator and archMutator for the
+// Target.
 func (target Target) Variations() []blueprint.Variation {
 	return []blueprint.Variation{
 		{Mutator: "os", Variation: target.OsVariation()},
@@ -722,10 +407,16 @@
 	}
 }
 
+// osMutator splits an arch-specific module into a variant for each OS that is enabled for the
+// module.  It uses the HostOrDevice value passed to InitAndroidArchModule and the
+// device_supported and host_supported properties to determine which OsTypes are enabled for this
+// module, then searches through the Targets to determine which have enabled Targets for this
+// module.
 func osMutator(bpctx blueprint.BottomUpMutatorContext) {
 	var module Module
 	var ok bool
 	if module, ok = bpctx.Module().(Module); !ok {
+		// The module is not a Soong module, it is a Blueprint module.
 		if bootstrap.IsBootstrapModule(bpctx.Module()) {
 			// Bootstrap Go modules are always the build OS or linux bionic.
 			config := bpctx.Config().(Config)
@@ -749,35 +440,38 @@
 
 	base := module.base()
 
+	// Nothing to do for modules that are not architecture specific (e.g. a genrule).
 	if !base.ArchSpecific() {
 		return
 	}
 
+	// Collect a list of OSTypes supported by this module based on the HostOrDevice value
+	// passed to InitAndroidArchModule and the device_supported and host_supported properties.
 	var moduleOSList []OsType
-
 	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
 			}
 		}
 	}
 
+	// If there are no supported OSes then disable the module.
 	if len(moduleOSList) == 0 {
 		base.Disable()
 		return
 	}
 
+	// Convert the list of supported OsTypes to the variation names.
 	osNames := make([]string, len(moduleOSList))
-
 	for i, os := range moduleOSList {
 		osNames[i] = os.String()
 	}
 
 	createCommonOSVariant := base.commonProperties.CreateCommonOSVariant
 	if createCommonOSVariant {
-		// A CommonOS variant was requested so add it to the list of OS's variants to
+		// A CommonOS variant was requested so add it to the list of OS variants to
 		// create. It needs to be added to the end because it needs to depend on the
 		// the other variants in the list returned by CreateVariations(...) and inter
 		// variant dependencies can only be created from a later variant in that list to
@@ -787,6 +481,8 @@
 		moduleOSList = append(moduleOSList, CommonOS)
 	}
 
+	// Create the variations, annotate each one with which OS it was created for, and
+	// squash the appropriate OS-specific properties into the top level properties.
 	modules := mctx.CreateVariations(osNames...)
 	for i, m := range modules {
 		m.base().commonProperties.CompileOS = moduleOSList[i]
@@ -819,9 +515,6 @@
 // Identifies the dependency from CommonOS variant to the os specific variants.
 var commonOsToOsSpecificVariantTag = archDepTag{name: "common os to os specific"}
 
-// Identifies the dependency from arch variant to the common variant for a "common_first" multilib.
-var firstArchToCommonArchDepTag = archDepTag{name: "first arch to common arch"}
-
 // Get the OsType specific variants for the current CommonOS variant.
 //
 // The returned list will only contain enabled OsType specific variants of the
@@ -841,7 +534,7 @@
 }
 
 // archMutator splits a module into a variant for each Target requested by the module.  Target selection
-// for a module is in three levels, OsClass, mulitlib, and then Target.
+// for a module is in three levels, OsClass, multilib, and then Target.
 // OsClass selection is determined by:
 //    - The HostOrDeviceSupported value passed in to InitAndroidArchModule by the module type factory, which selects
 //      whether the module type can compile for host, device or both.
@@ -858,7 +551,11 @@
 //        but may be arm for a 32-bit only build.
 //    "32": compile for only a single 32-bit Target supported by the OsClass.
 //    "64": compile for only a single 64-bit Target supported by the OsClass.
-//    "common": compile a for a single Target that will work on all Targets suported by the OsClass (for example Java).
+//    "common": compile a for a single Target that will work on all Targets supported by the OsClass (for example Java).
+//    "common_first": compile a for a Target that will work on all Targets supported by the OsClass
+//        (same as "common"), plus a second Target for the preferred Target supported by the OsClass
+//        (same as "first").  This is used for java_binary that produces a common .jar and a wrapper
+//        executable script.
 //
 // Once the list of Targets is determined, the module is split into a variant for each Target.
 //
@@ -899,8 +596,8 @@
 
 	osTargets := mctx.Config().Targets[os]
 	image := base.commonProperties.ImageVariation
-	// Filter NativeBridge targets unless they are explicitly supported
-	// Skip creating native bridge variants for vendor modules
+	// Filter NativeBridge targets unless they are explicitly supported.
+	// Skip creating native bridge variants for non-core modules.
 	if os == Android &&
 		!(Bool(base.commonProperties.Native_bridge_supported) && image == CoreVariation) {
 
@@ -919,17 +616,24 @@
 		osTargets = []Target{osTargets[0]}
 	}
 
+	// Some modules want compile_multilib: "first" to mean 32-bit, not 64-bit.
+	// This is used for Windows support and for HOST_PREFER_32_BIT=true support for Art modules.
 	prefer32 := false
 	if base.prefer32 != nil {
 		prefer32 = base.prefer32(mctx, base, os)
 	}
 
+	// Determine the multilib selection for this module.
 	multilib, extraMultilib := decodeMultilib(base, os.Class)
+
+	// Convert the multilib selection into a list of Targets.
 	targets, err := decodeMultilibTargets(multilib, osTargets, prefer32)
 	if err != nil {
 		mctx.ModuleErrorf("%s", err.Error())
 	}
 
+	// If the module is using extraMultilib, decode the extraMultilib selection into
+	// a separate list of Targets.
 	var multiTargets []Target
 	if extraMultilib != "" {
 		multiTargets, err = decodeMultilibTargets(extraMultilib, osTargets, prefer32)
@@ -938,52 +642,62 @@
 		}
 	}
 
+	// Recovery is always the primary architecture, filter out any other architectures.
 	if image == RecoveryVariation {
 		primaryArch := mctx.Config().DevicePrimaryArchType()
 		targets = filterToArch(targets, primaryArch)
 		multiTargets = filterToArch(multiTargets, primaryArch)
 	}
 
+	// If there are no supported targets disable the module.
 	if len(targets) == 0 {
 		base.Disable()
 		return
 	}
 
+	// Convert the targets into a list of arch variation names.
 	targetNames := make([]string, len(targets))
-
 	for i, target := range targets {
 		targetNames[i] = target.ArchVariation()
 	}
 
+	// Create the variations, annotate each one with which Target it was created for, and
+	// squash the appropriate arch-specific properties into the top level properties.
 	modules := mctx.CreateVariations(targetNames...)
 	for i, m := range modules {
 		addTargetProperties(m, targets[i], multiTargets, i == 0)
 		m.base().setArchProperties(mctx)
 	}
-
-	if multilib == "common_first" && len(modules) >= 2 {
-		for i := range modules[1:] {
-			mctx.AddInterVariantDependency(firstArchToCommonArchDepTag, modules[i+1], modules[0])
-		}
-	}
 }
 
+// addTargetProperties annotates a variant with the Target is is being compiled for, the list
+// of additional Targets it is supporting (if any), and whether it is the primary Target for
+// the module.
 func addTargetProperties(m Module, target Target, multiTargets []Target, primaryTarget bool) {
 	m.base().commonProperties.CompileTarget = target
 	m.base().commonProperties.CompileMultiTargets = multiTargets
 	m.base().commonProperties.CompilePrimary = primaryTarget
 }
 
+// decodeMultilib returns the appropriate compile_multilib property for the module, or the default
+// multilib from the factory's call to InitAndroidArchModule if none was set.  For modules that
+// called InitAndroidMultiTargetsArchModule it always returns "common" for multilib, and returns
+// the actual multilib in extraMultilib.
 func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib string) {
+	// First check the "android.compile_multilib" or "host.compile_multilib" properties.
 	switch class {
 	case Device:
 		multilib = String(base.commonProperties.Target.Android.Compile_multilib)
 	case Host:
 		multilib = String(base.commonProperties.Target.Host.Compile_multilib)
 	}
+
+	// If those aren't set, try the "compile_multilib" property.
 	if multilib == "" {
 		multilib = String(base.commonProperties.Compile_multilib)
 	}
+
+	// If that wasn't set, use the default multilib set by the factory.
 	if multilib == "" {
 		multilib = base.commonProperties.Default_multilib
 	}
@@ -1000,6 +714,8 @@
 	}
 }
 
+// filterToArch takes a list of Targets and an ArchType, and returns a modified list that contains
+// only Targets that have the specified ArchType.
 func filterToArch(targets []Target, arch ArchType) []Target {
 	for i := 0; i < len(targets); i++ {
 		if targets[i].Arch.ArchType != arch {
@@ -1010,17 +726,27 @@
 	return targets
 }
 
-type archPropTypeDesc struct {
-	arch, multilib, target reflect.Type
-}
-
+// archPropRoot is a struct type used as the top level of the arch-specific properties.  It
+// contains the "arch", "multilib", and "target" property structs.  It is used to split up the
+// property structs to limit how much is allocated when a single arch-specific property group is
+// used.  The types are interface{} because they will hold instances of runtime-created types.
 type archPropRoot struct {
 	Arch, Multilib, Target interface{}
 }
 
+// archPropTypeDesc holds the runtime-created types for the property structs to instantiate to
+// create an archPropRoot property struct.
+type archPropTypeDesc struct {
+	arch, multilib, target reflect.Type
+}
+
 // createArchPropTypeDesc takes a reflect.Type that is either a struct or a pointer to a struct, and
 // returns lists of reflect.Types that contains the arch-variant properties inside structs for each
 // arch, multilib and target property.
+//
+// This is a relatively expensive operation, so the results are cached in the global
+// archPropTypeMap.  It is constructed entirely based on compile-time data, so there is no need
+// to isolate the results between multiple tests running in parallel.
 func createArchPropTypeDesc(props reflect.Type) []archPropTypeDesc {
 	// Each property struct shard will be nested many times under the runtime generated arch struct,
 	// which can hit the limit of 64kB for the name of runtime generated structs.  They are nested
@@ -1029,7 +755,12 @@
 	// could cause problems if a single deeply nested property no longer fits in the name.
 	const maxArchTypeNameSize = 500
 
+	// Convert the type to a new set of types that contains only the arch-specific properties
+	// (those that are tagged with `android:"arch_specific"`), and sharded into multiple types
+	// to keep the runtime-generated names under the limit.
 	propShards, _ := proptools.FilterPropertyStructSharded(props, maxArchTypeNameSize, filterArchStruct)
+
+	// If the type has no arch-specific properties there is nothing to do.
 	if len(propShards) == 0 {
 		return nil
 	}
@@ -1037,6 +768,8 @@
 	var ret []archPropTypeDesc
 	for _, props := range propShards {
 
+		// variantFields takes a list of variant property field names and returns a list the
+		// StructFields with the names and the type of the current shard.
 		variantFields := func(names []string) []reflect.StructField {
 			ret := make([]reflect.StructField, len(names))
 
@@ -1048,9 +781,11 @@
 			return ret
 		}
 
+		// Create a type that contains the properties in this shard repeated for each
+		// architecture, architecture variant, and architecture feature.
 		archFields := make([]reflect.StructField, len(archTypeList))
 		for i, arch := range archTypeList {
-			variants := []string{}
+			var variants []string
 
 			for _, archVariant := range archVariants[arch] {
 				archVariant := variantReplacer.Replace(archVariant)
@@ -1061,8 +796,13 @@
 				variants = append(variants, proptools.FieldNameForProperty(feature))
 			}
 
+			// Create the StructFields for each architecture variant architecture feature
+			// (e.g. "arch.arm.cortex-a53" or "arch.arm.neon").
 			fields := variantFields(variants)
 
+			// Create the StructField for the architecture itself (e.g. "arch.arm").  The special
+			// "BlueprintEmbed" name is used by Blueprint to put the properties in the
+			// parent struct.
 			fields = append([]reflect.StructField{{
 				Name:      "BlueprintEmbed",
 				Type:      props,
@@ -1074,10 +814,15 @@
 				Type: reflect.StructOf(fields),
 			}
 		}
+
+		// Create the type of the "arch" property struct for this shard.
 		archType := reflect.StructOf(archFields)
 
+		// Create the type for the "multilib" property struct for this shard, containing the
+		// "multilib.lib32" and "multilib.lib64" property structs.
 		multilibType := reflect.StructOf(variantFields([]string{"Lib32", "Lib64"}))
 
+		// Start with a list of the special targets
 		targets := []string{
 			"Host",
 			"Android64",
@@ -1090,11 +835,14 @@
 			"Native_bridge",
 		}
 		for _, os := range OsTypeList {
+			// Add all the OSes.
 			targets = append(targets, os.Field)
 
+			// Add the OS/Arch combinations, e.g. "android_arm64".
 			for _, archType := range osArchTypeMap[os] {
 				targets = append(targets, os.Field+"_"+archType.Name)
 
+				// Also add the special "linux_<arch>" and "bionic_<arch>" property structs.
 				if os.Linux() {
 					target := "Linux_" + archType.Name
 					if !InList(target, targets) {
@@ -1110,8 +858,10 @@
 			}
 		}
 
+		// Create the type for the "target" property struct for this shard.
 		targetType := reflect.StructOf(variantFields(targets))
 
+		// Return a descriptor of the 3 runtime-created types.
 		ret = append(ret, archPropTypeDesc{
 			arch:     reflect.PtrTo(archType),
 			multilib: reflect.PtrTo(multilibType),
@@ -1121,6 +871,11 @@
 	return ret
 }
 
+// variantReplacer converts architecture variant or architecture feature names into names that
+// are valid for an Android.bp file.
+var variantReplacer = strings.NewReplacer("-", "_", ".", "_")
+
+// filterArchStruct returns true if the given field is an architecture specific property.
 func filterArchStruct(field reflect.StructField, prefix string) (bool, reflect.StructField) {
 	if proptools.HasTag(field, "android", "arch_variant") {
 		// The arch_variant field isn't necessary past this point
@@ -1147,12 +902,17 @@
 	return false, field
 }
 
+// archPropTypeMap contains a cache of the results of createArchPropTypeDesc for each type.  It is
+// shared across all Contexts, but is constructed based only on compile-time information so there
+// is no risk of contaminating one Context with data from another.
 var archPropTypeMap OncePer
 
-func InitArchModule(m Module) {
+// initArchModule adds the architecture-specific property structs to a Module.
+func initArchModule(m Module) {
 
 	base := m.base()
 
+	// Store the original list of top level property structs
 	base.generalProperties = m.GetProperties()
 
 	for _, properties := range base.generalProperties {
@@ -1169,10 +929,13 @@
 				propertiesValue.Interface()))
 		}
 
+		// Get or create the arch-specific property struct types for this property struct type.
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
 			return createArchPropTypeDesc(t)
 		}).([]archPropTypeDesc)
 
+		// Instantiate one of each arch-specific property struct type and add it to the
+		// properties for the Module.
 		var archProperties []interface{}
 		for _, t := range archPropTypes {
 			archProperties = append(archProperties, &archPropRoot{
@@ -1185,14 +948,18 @@
 		m.AddProperties(archProperties...)
 	}
 
+	// Update the list of properties that can be set by a defaults module or a call to
+	// AppendMatchingProperties or PrependMatchingProperties.
 	base.customizableProperties = m.GetProperties()
 }
 
-var variantReplacer = strings.NewReplacer("-", "_", ".", "_")
-
+// appendProperties squashes properties from the given field of the given src property struct
+// into the dst property struct.  Returns the reflect.Value of the field in the src property
+// struct to be used for further appendProperties calls on fields of that property struct.
 func (m *ModuleBase) appendProperties(ctx BottomUpMutatorContext,
 	dst interface{}, src reflect.Value, field, srcPrefix string) reflect.Value {
 
+	// Step into non-nil pointers to structs in the src value.
 	if src.Kind() == reflect.Ptr {
 		if src.IsNil() {
 			return src
@@ -1200,18 +967,25 @@
 		src = src.Elem()
 	}
 
+	// Find the requested field in the src struct.
 	src = src.FieldByName(field)
 	if !src.IsValid() {
 		ctx.ModuleErrorf("field %q does not exist", srcPrefix)
 		return src
 	}
 
+	// Save the value of the field in the src struct to return.
 	ret := src
 
+	// If the value of the field is a struct (as opposed to a pointer to a struct) then step
+	// into the BlueprintEmbed field.
 	if src.Kind() == reflect.Struct {
 		src = src.FieldByName("BlueprintEmbed")
 	}
 
+	// order checks the `android:"variant_prepend"` tag to handle properties where the
+	// arch-specific value needs to come before the generic value, for example for lists of
+	// include directories.
 	order := func(property string,
 		dstField, srcField reflect.StructField,
 		dstValue, srcValue interface{}) (proptools.Order, error) {
@@ -1222,6 +996,7 @@
 		}
 	}
 
+	// Squash the located property struct into the destination property struct.
 	err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, order)
 	if err != nil {
 		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
@@ -1234,7 +1009,8 @@
 	return ret
 }
 
-// Rewrite the module's properties structs to contain os-specific values.
+// Squash the appropriate OS-specific property structs into the matching top level property structs
+// based on the CompileOS value that was annotated on the variant.
 func (m *ModuleBase) setOSProperties(ctx BottomUpMutatorContext) {
 	os := m.commonProperties.CompileOS
 
@@ -1328,7 +1104,8 @@
 	}
 }
 
-// Rewrite the module's properties structs to contain arch-specific values.
+// Squash the appropriate arch-specific property structs into the matching top level property
+// structs based on the CompileTarget value that was annotated on the variant.
 func (m *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) {
 	arch := m.Arch()
 	os := m.Os()
@@ -1480,22 +1257,7 @@
 	}
 }
 
-func forEachInterface(v reflect.Value, f func(reflect.Value)) {
-	switch v.Kind() {
-	case reflect.Interface:
-		f(v)
-	case reflect.Struct:
-		for i := 0; i < v.NumField(); i++ {
-			forEachInterface(v.Field(i), f)
-		}
-	case reflect.Ptr:
-		forEachInterface(v.Elem(), f)
-	default:
-		panic(fmt.Errorf("Unsupported kind %s", v.Kind()))
-	}
-}
-
-// Convert the arch product variables into a list of targets for each os class structs
+// Convert the arch product variables into a list of targets for each OsType.
 func decodeTargetProductVariables(config *config) (map[OsType][]Target, error) {
 	variables := config.productVariables
 
@@ -1567,19 +1329,15 @@
 		return nil, fmt.Errorf("No host primary architecture set")
 	}
 
+	// The primary host target, which must always exist.
 	addTarget(BuildOs, *variables.HostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 
+	// An optional secondary host target.
 	if variables.HostSecondaryArch != nil && *variables.HostSecondaryArch != "" {
 		addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 	}
 
-	if Bool(config.Host_bionic) {
-		addTarget(LinuxBionic, "x86_64", nil, nil, nil, NativeBridgeDisabled, nil, nil)
-	}
-	if Bool(config.Host_bionic_arm64) {
-		addTarget(LinuxBionic, "arm64", nil, nil, nil, NativeBridgeDisabled, nil, nil)
-	}
-
+	// Optional cross-compiled host targets, generally Windows.
 	if String(variables.CrossHost) != "" {
 		crossHostOs := osByName(*variables.CrossHost)
 		if crossHostOs == NoOsType {
@@ -1590,28 +1348,34 @@
 			return nil, fmt.Errorf("No cross-host primary architecture set")
 		}
 
+		// The primary cross-compiled host target.
 		addTarget(crossHostOs, *variables.CrossHostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 
+		// An optional secondary cross-compiled host target.
 		if variables.CrossHostSecondaryArch != nil && *variables.CrossHostSecondaryArch != "" {
 			addTarget(crossHostOs, *variables.CrossHostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 		}
 	}
 
+	// Optional device targets
 	if variables.DeviceArch != nil && *variables.DeviceArch != "" {
 		var target = Android
 		if Bool(variables.Fuchsia) {
 			target = Fuchsia
 		}
 
+		// The primary device target.
 		addTarget(target, *variables.DeviceArch, variables.DeviceArchVariant,
 			variables.DeviceCpuVariant, variables.DeviceAbi, NativeBridgeDisabled, nil, nil)
 
+		// An optional secondary device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" {
 			addTarget(Android, *variables.DeviceSecondaryArch,
 				variables.DeviceSecondaryArchVariant, variables.DeviceSecondaryCpuVariant,
 				variables.DeviceSecondaryAbi, NativeBridgeDisabled, nil, nil)
 		}
 
+		// An optional NativeBridge device target.
 		if variables.NativeBridgeArch != nil && *variables.NativeBridgeArch != "" {
 			addTarget(Android, *variables.NativeBridgeArch,
 				variables.NativeBridgeArchVariant, variables.NativeBridgeCpuVariant,
@@ -1619,6 +1383,7 @@
 				variables.NativeBridgeRelativePath)
 		}
 
+		// An optional secondary NativeBridge device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" &&
 			variables.NativeBridgeSecondaryArch != nil && *variables.NativeBridgeSecondaryArch != "" {
 			addTarget(Android, *variables.NativeBridgeSecondaryArch,
@@ -1653,6 +1418,7 @@
 	return false
 }
 
+// archConfig describes a built-in configuration.
 type archConfig struct {
 	arch        string
 	archVariant string
@@ -1660,52 +1426,7 @@
 	abi         []string
 }
 
-func getMegaDeviceConfig() []archConfig {
-	return []archConfig{
-		{"arm", "armv7-a", "generic", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "generic", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a7", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a8", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a9", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a15", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a53", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a53.a57", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a72", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a73", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a75", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "cortex-a76", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "krait", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "kryo", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "kryo385", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "exynos-m1", []string{"armeabi-v7a"}},
-		{"arm", "armv7-a-neon", "exynos-m2", []string{"armeabi-v7a"}},
-		{"arm64", "armv8-a", "cortex-a53", []string{"arm64-v8a"}},
-		{"arm64", "armv8-a", "cortex-a72", []string{"arm64-v8a"}},
-		{"arm64", "armv8-a", "cortex-a73", []string{"arm64-v8a"}},
-		{"arm64", "armv8-a", "kryo", []string{"arm64-v8a"}},
-		{"arm64", "armv8-a", "exynos-m1", []string{"arm64-v8a"}},
-		{"arm64", "armv8-a", "exynos-m2", []string{"arm64-v8a"}},
-		{"arm64", "armv8-2a", "kryo385", []string{"arm64-v8a"}},
-		{"arm64", "armv8-2a-dotprod", "cortex-a55", []string{"arm64-v8a"}},
-		{"arm64", "armv8-2a-dotprod", "cortex-a75", []string{"arm64-v8a"}},
-		{"arm64", "armv8-2a-dotprod", "cortex-a76", []string{"arm64-v8a"}},
-		{"x86", "", "", []string{"x86"}},
-		{"x86", "atom", "", []string{"x86"}},
-		{"x86", "haswell", "", []string{"x86"}},
-		{"x86", "ivybridge", "", []string{"x86"}},
-		{"x86", "sandybridge", "", []string{"x86"}},
-		{"x86", "silvermont", "", []string{"x86"}},
-		{"x86", "stoneyridge", "", []string{"x86"}},
-		{"x86", "x86_64", "", []string{"x86"}},
-		{"x86_64", "", "", []string{"x86_64"}},
-		{"x86_64", "haswell", "", []string{"x86_64"}},
-		{"x86_64", "ivybridge", "", []string{"x86_64"}},
-		{"x86_64", "sandybridge", "", []string{"x86_64"}},
-		{"x86_64", "silvermont", "", []string{"x86_64"}},
-		{"x86_64", "stoneyridge", "", []string{"x86_64"}},
-	}
-}
-
+// getNdkAbisConfig returns a list of archConfigs for the ABIs supported by the NDK.
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
@@ -1715,6 +1436,7 @@
 	}
 }
 
+// getAmlAbisConfig returns a list of archConfigs for the ABIs supported by mainline modules.
 func getAmlAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a-neon", "", []string{"armeabi-v7a"}},
@@ -1724,6 +1446,7 @@
 	}
 }
 
+// decodeArchSettings converts a list of archConfigs into a list of Targets for the given OsType.
 func decodeArchSettings(os OsType, archConfigs []archConfig) ([]Target, error) {
 	var ret []Target
 
@@ -1743,15 +1466,9 @@
 	return ret, nil
 }
 
-// Convert a set of strings from product variables into a single Arch struct
+// decodeArch converts a set of strings from product variables into an Arch struct.
 func decodeArch(os OsType, arch string, archVariant, cpuVariant *string, abi []string) (Arch, error) {
-	stringPtr := func(p *string) string {
-		if p != nil {
-			return *p
-		}
-		return ""
-	}
-
+	// Verify the arch is valid
 	archType, ok := archTypeMap[arch]
 	if !ok {
 		return Arch{}, fmt.Errorf("unknown arch %q", arch)
@@ -1759,19 +1476,22 @@
 
 	a := Arch{
 		ArchType:    archType,
-		ArchVariant: stringPtr(archVariant),
-		CpuVariant:  stringPtr(cpuVariant),
+		ArchVariant: String(archVariant),
+		CpuVariant:  String(cpuVariant),
 		Abi:         abi,
 	}
 
+	// Convert generic arch variants into the empty string.
 	if a.ArchVariant == a.ArchType.Name || a.ArchVariant == "generic" {
 		a.ArchVariant = ""
 	}
 
+	// Convert generic CPU variants into the empty string.
 	if a.CpuVariant == a.ArchType.Name || a.CpuVariant == "generic" {
 		a.CpuVariant = ""
 	}
 
+	// Filter empty ABIs out of the list.
 	for i := 0; i < len(a.Abi); i++ {
 		if a.Abi[i] == "" {
 			a.Abi = append(a.Abi[:i], a.Abi[i+1:]...)
@@ -1780,10 +1500,12 @@
 	}
 
 	if a.ArchVariant == "" {
+		// Set ArchFeatures from the default arch features.
 		if featureMap, ok := defaultArchFeatureMap[os]; ok {
 			a.ArchFeatures = featureMap[archType]
 		}
 	} else {
+		// Set ArchFeatures from the arch type.
 		if featureMap, ok := archFeatureMap[archType]; ok {
 			a.ArchFeatures = featureMap[a.ArchVariant]
 		}
@@ -1792,6 +1514,8 @@
 	return a, nil
 }
 
+// filterMultilibTargets takes a list of Targets and a multilib value and returns a new list of
+// Targets containing only those that have the given multilib value.
 func filterMultilibTargets(targets []Target, multilib string) []Target {
 	var ret []Target
 	for _, t := range targets {
@@ -1802,8 +1526,8 @@
 	return ret
 }
 
-// Return the set of Os specific common architecture targets for each Os in a list of
-// targets.
+// getCommonTargets returns the set of Os specific common architecture targets for each Os in a list
+// of targets.
 func getCommonTargets(targets []Target) []Target {
 	var ret []Target
 	set := make(map[string]bool)
@@ -1818,6 +1542,9 @@
 	return ret
 }
 
+// firstTarget takes a list of Targets and a list of multilib values and returns a list of Targets
+// that contains zero or one Target for each OsType, selecting the one that matches the earliest
+// filter.
 func firstTarget(targets []Target, filters ...string) []Target {
 	// find the first target from each OS
 	var ret []Target
@@ -1837,9 +1564,10 @@
 	return ret
 }
 
-// Use the module multilib setting to select one or more targets from a target list
+// decodeMultilibTargets uses the module's multilib setting to select one or more targets from a
+// list of Targets.
 func decodeMultilibTargets(multilib string, targets []Target, prefer32 bool) ([]Target, error) {
-	buildTargets := []Target{}
+	var buildTargets []Target
 
 	switch multilib {
 	case "common":
diff --git a/android/arch_list.go b/android/arch_list.go
new file mode 100644
index 0000000..0c33b9d
--- /dev/null
+++ b/android/arch_list.go
@@ -0,0 +1,410 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import "fmt"
+
+var archVariants = map[ArchType][]string{
+	Arm: {
+		"armv7-a",
+		"armv7-a-neon",
+		"armv8-a",
+		"armv8-2a",
+		"cortex-a7",
+		"cortex-a8",
+		"cortex-a9",
+		"cortex-a15",
+		"cortex-a53",
+		"cortex-a53-a57",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"krait",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	Arm64: {
+		"armv8_a",
+		"armv8_2a",
+		"armv8-2a-dotprod",
+		"cortex-a53",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	X86: {
+		"amberlake",
+		"atom",
+		"broadwell",
+		"haswell",
+		"icelake",
+		"ivybridge",
+		"kabylake",
+		"sandybridge",
+		"silvermont",
+		"skylake",
+		"stoneyridge",
+		"tigerlake",
+		"whiskeylake",
+		"x86_64",
+	},
+	X86_64: {
+		"amberlake",
+		"broadwell",
+		"haswell",
+		"icelake",
+		"ivybridge",
+		"kabylake",
+		"sandybridge",
+		"silvermont",
+		"skylake",
+		"stoneyridge",
+		"tigerlake",
+		"whiskeylake",
+	},
+}
+
+var archFeatures = map[ArchType][]string{
+	Arm: {
+		"neon",
+	},
+	Arm64: {
+		"dotprod",
+	},
+	X86: {
+		"ssse3",
+		"sse4",
+		"sse4_1",
+		"sse4_2",
+		"aes_ni",
+		"avx",
+		"avx2",
+		"avx512",
+		"popcnt",
+		"movbe",
+	},
+	X86_64: {
+		"ssse3",
+		"sse4",
+		"sse4_1",
+		"sse4_2",
+		"aes_ni",
+		"avx",
+		"avx2",
+		"avx512",
+		"popcnt",
+	},
+}
+
+var archFeatureMap = map[ArchType]map[string][]string{
+	Arm: {
+		"armv7-a-neon": {
+			"neon",
+		},
+		"armv8-a": {
+			"neon",
+		},
+		"armv8-2a": {
+			"neon",
+		},
+	},
+	Arm64: {
+		"armv8-2a-dotprod": {
+			"dotprod",
+		},
+	},
+	X86: {
+		"amberlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"atom": {
+			"ssse3",
+			"movbe",
+		},
+		"broadwell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"haswell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+			"movbe",
+		},
+		"icelake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"ivybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"kabylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"sandybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+		"silvermont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+			"movbe",
+		},
+		"skylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"stoneyridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"avx2",
+			"popcnt",
+			"movbe",
+		},
+		"tigerlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"whiskeylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"x86_64": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+	},
+	X86_64: {
+		"amberlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"broadwell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"haswell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"icelake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"ivybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"kabylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"sandybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+		"silvermont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+		},
+		"skylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"stoneyridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"avx2",
+			"popcnt",
+		},
+		"tigerlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"whiskeylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+	},
+}
+
+var defaultArchFeatureMap = map[OsType]map[ArchType][]string{}
+
+// RegisterDefaultArchVariantFeatures is called by files that define Toolchains to specify the
+// arch features that are available for the default arch variant.  It must be called from an
+// init() function.
+func RegisterDefaultArchVariantFeatures(os OsType, arch ArchType, features ...string) {
+	checkCalledFromInit()
+
+	for _, feature := range features {
+		if !InList(feature, archFeatures[arch]) {
+			panic(fmt.Errorf("Invalid feature %q for arch %q variant \"\"", feature, arch))
+		}
+	}
+
+	if defaultArchFeatureMap[os] == nil {
+		defaultArchFeatureMap[os] = make(map[ArchType][]string)
+	}
+	defaultArchFeatureMap[os][arch] = features
+}
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..453e074 100644
--- a/android/config.go
+++ b/android/config.go
@@ -14,6 +14,9 @@
 
 package android
 
+// This is the primary location to write and read all configuration values and
+// product variables necessary for soong_build's operation.
+
 import (
 	"encoding/json"
 	"fmt"
@@ -32,63 +35,61 @@
 	"android/soong/android/soongconfig"
 )
 
+// Bool re-exports proptools.Bool for the android package.
 var Bool = proptools.Bool
+
+// String re-exports proptools.String for the android package.
 var String = proptools.String
+
+// StringDefault re-exports proptools.StringDefault for the android package.
 var StringDefault = proptools.StringDefault
 
+// FutureApiLevelInt is a placeholder constant for unreleased API levels.
 const FutureApiLevelInt = 10000
 
+// FutureApiLevel represents unreleased API levels.
 var FutureApiLevel = ApiLevel{
 	value:     "current",
 	number:    FutureApiLevelInt,
 	isPreview: true,
 }
 
-// The configuration file name
-const configFileName = "soong.config"
+// The product variables file name, containing product config from Kati.
 const productVariablesFileName = "soong.variables"
 
-// A FileConfigurableOptions contains options which can be configured by the
-// config file. These will be included in the config struct.
-type FileConfigurableOptions struct {
-	Mega_device       *bool `json:",omitempty"`
-	Host_bionic       *bool `json:",omitempty"`
-	Host_bionic_arm64 *bool `json:",omitempty"`
-}
-
-func (f *FileConfigurableOptions) SetDefaultConfig() {
-	*f = FileConfigurableOptions{}
-}
-
 // A Config object represents the entire build configuration for Android.
 type Config struct {
 	*config
 }
 
+// BuildDir returns the build output directory for the configuration.
 func (c Config) BuildDir() string {
 	return c.buildDir
 }
 
-// A DeviceConfig object represents the configuration for a particular device being built.  For
-// now there will only be one of these, but in the future there may be multiple devices being
-// built
+// A DeviceConfig object represents the configuration for a particular device
+// being built. For now there will only be one of these, but in the future there
+// may be multiple devices being built.
 type DeviceConfig struct {
 	*deviceConfig
 }
 
+// VendorConfig represents the configuration for vendor-specific behavior.
 type VendorConfig soongconfig.SoongConfig
 
+// Definition of general build configuration for soong_build. Some of these
+// product configuration values are read from Kati-generated soong.variables.
 type config struct {
-	FileConfigurableOptions
+	// Options configurable with soong.variables
 	productVariables productVariables
 
 	// Only available on configs created by TestConfig
 	TestProductVariables *productVariables
 
+	// A specialized context object for Bazel/Soong mixed builds and migration
+	// purposes.
 	BazelContext BazelContext
 
-	PrimaryBuilder           string
-	ConfigFileName           string
 	ProductVariablesFileName string
 
 	Targets                  map[OsType][]Target
@@ -97,8 +98,8 @@
 	AndroidCommonTarget      Target // the Target for common modules for the Android device
 	AndroidFirstDeviceTarget Target // the first Target for modules for the Android device
 
-	// multilibConflicts for an ArchType is true if there is earlier configured device architecture with the same
-	// multilib value.
+	// multilibConflicts for an ArchType is true if there is earlier configured
+	// device architecture with the same multilib value.
 	multilibConflicts map[ArchType]bool
 
 	deviceConfig *deviceConfig
@@ -112,7 +113,9 @@
 	envDeps   map[string]string
 	envFrozen bool
 
-	inMake bool
+	// Changes behavior based on whether Kati runs after soong_build, or if soong_build
+	// runs standalone.
+	katiEnabled bool
 
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
@@ -126,6 +129,10 @@
 	// in tests when a path doesn't exist.
 	testAllowNonExistentPaths bool
 
+	// The list of files that when changed, must invalidate soong_build to
+	// regenerate build.ninja.
+	ninjaFileDepsSet sync.Map
+
 	OncePer
 }
 
@@ -139,15 +146,11 @@
 }
 
 func loadConfig(config *config) error {
-	err := loadFromConfigFile(&config.FileConfigurableOptions, absolutePath(config.ConfigFileName))
-	if err != nil {
-		return err
-	}
-
 	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
 
-// loads configuration options from a JSON file in the cwd.
+// loadFromConfigFile loads and decodes configuration options from a JSON file
+// in the current working directory.
 func loadFromConfigFile(configurable jsonConfigurable, filename string) error {
 	// Try to open the file
 	configFileReader, err := os.Open(filename)
@@ -187,7 +190,7 @@
 
 	f, err := ioutil.TempFile(filepath.Dir(filename), "config")
 	if err != nil {
-		return fmt.Errorf("cannot create empty config file %s: %s\n", filename, err.Error())
+		return fmt.Errorf("cannot create empty config file %s: %s", filename, err.Error())
 	}
 	defer os.Remove(f.Name())
 	defer f.Close()
@@ -219,14 +222,15 @@
 	}
 }
 
-// TestConfig returns a Config object suitable for using for tests
+// TestConfig returns a Config object for testing.
 func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	envCopy := make(map[string]string)
 	for k, v := range env {
 		envCopy[k] = v
 	}
 
-	// Copy the real PATH value to the test environment, it's needed by HostSystemTool() used in x86_darwin_host.go
+	// Copy the real PATH value to the test environment, it's needed by
+	// NonHermeticHostSystemTool() used in x86_darwin_host.go
 	envCopy["PATH"] = originalEnv["PATH"]
 
 	config := &config{
@@ -261,13 +265,12 @@
 
 	config.mockFileSystem(bp, fs)
 
-	if err := config.fromEnv(); err != nil {
-		panic(err)
-	}
-
 	return Config{config}
 }
 
+// TestArchConfigNativeBridge returns a Config object suitable for using
+// for tests that need to run the arch mutator for native bridge supported
+// archs.
 func TestArchConfigNativeBridge(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestArchConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -282,6 +285,8 @@
 	return testConfig
 }
 
+// TestArchConfigFuchsia returns a Config object suitable for using for
+// tests that need to run the arch mutator for the Fuchsia arch.
 func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -298,7 +303,8 @@
 	return testConfig
 }
 
-// TestConfig returns a Config object suitable for using for tests that need to run the arch mutator
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
 func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -330,10 +336,10 @@
 	return testConfig
 }
 
-// Returns a config object which is "reset" for another bootstrap run.
-// Only per-run data is reset. Data which needs to persist across multiple
-// runs in the same program execution is carried over (such as Bazel context
-// or environment deps).
+// ConfigForAdditionalRun is a config object which is "reset" for another
+// bootstrap run. Only per-run data is reset. Data which needs to persist across
+// multiple runs in the same program execution is carried over (such as Bazel
+// context or environment deps).
 func ConfigForAdditionalRun(c Config) (Config, error) {
 	newConfig, err := NewConfig(c.srcDir, c.buildDir, c.moduleListFile)
 	if err != nil {
@@ -344,12 +350,11 @@
 	return newConfig, nil
 }
 
-// New creates a new Config object.  The srcDir argument specifies the path to
-// the root source directory. It also loads the config file, if found.
+// NewConfig creates a new Config object. The srcDir argument specifies the path
+// to the root source directory. It also loads the config file, if found.
 func NewConfig(srcDir, buildDir string, moduleListFile string) (Config, error) {
-	// Make a config with default options
+	// Make a config with default options.
 	config := &config{
-		ConfigFileName:           filepath.Join(buildDir, configFileName),
 		ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName),
 
 		env: originalEnv,
@@ -388,11 +393,13 @@
 		return Config{}, err
 	}
 
-	inMakeFile := filepath.Join(buildDir, ".soong.in_make")
-	if _, err := os.Stat(absolutePath(inMakeFile)); err == nil {
-		config.inMake = true
+	KatiEnabledMarkerFile := filepath.Join(buildDir, ".soong.kati_enabled")
+	if _, err := os.Stat(absolutePath(KatiEnabledMarkerFile)); err == nil {
+		config.katiEnabled = true
 	}
 
+	// Sets up the map of target OSes to the finer grained compilation targets
+	// that are configured from the product variables.
 	targets, err := decodeTargetProductVariables(config)
 	if err != nil {
 		return Config{}, err
@@ -402,9 +409,7 @@
 	targets[CommonOS] = []Target{commonTargetMap[CommonOS.Name]}
 
 	var archConfig []archConfig
-	if Bool(config.Mega_device) {
-		archConfig = getMegaDeviceConfig()
-	} else if config.NdkAbis() {
+	if config.NdkAbis() {
 		archConfig = getNdkAbisConfig()
 	} else if config.AmlAbis() {
 		archConfig = getAmlAbisConfig()
@@ -426,18 +431,19 @@
 		multilib[target.Arch.ArchType.Multilib] = true
 	}
 
+	// Map of OS to compilation targets.
 	config.Targets = targets
+
+	// Compilation targets for host tools.
 	config.BuildOSTarget = config.Targets[BuildOs][0]
 	config.BuildOSCommonTarget = getCommonTargets(config.Targets[BuildOs])[0]
+
+	// Compilation targets for Android.
 	if len(config.Targets[Android]) > 0 {
 		config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0]
 		config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	if err := config.fromEnv(); err != nil {
-		return Config{}, err
-	}
-
 	if Bool(config.productVariables.GcovCoverage) && Bool(config.productVariables.ClangCoverage) {
 		return Config{}, fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
 	}
@@ -447,13 +453,9 @@
 			Bool(config.productVariables.ClangCoverage))
 
 	config.BazelContext, err = NewBazelContext(config)
-	if err != nil {
-		return Config{}, err
-	}
-	return Config{config}, nil
-}
 
-var TestConfigOsFs = map[string][]byte{}
+	return Config{config}, err
+}
 
 // mockFileSystem replaces all reads with accesses to the provided map of
 // filenames to contents stored as a byte slice.
@@ -485,27 +487,19 @@
 	c.mockBpList = blueprint.MockModuleListFile
 }
 
-func (c *config) fromEnv() error {
-	switch c.Getenv("EXPERIMENTAL_JAVA_LANGUAGE_LEVEL_9") {
-	case "", "true":
-		// Do nothing
-	default:
-		return fmt.Errorf("The environment variable EXPERIMENTAL_JAVA_LANGUAGE_LEVEL_9 is no longer supported. Java language level 9 is now the global default.")
-	}
-
-	return nil
-}
-
 func (c *config) StopBefore() bootstrap.StopBefore {
 	return c.stopBefore
 }
 
+// SetStopBefore configures soong_build to exit earlier at a specific point.
 func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) {
 	c.stopBefore = stopBefore
 }
 
 var _ bootstrap.ConfigStopBefore = (*config)(nil)
 
+// BlueprintToolLocation returns the directory containing build system tools
+// from Blueprint, like soong_zip and merge_zips.
 func (c *config) BlueprintToolLocation() string {
 	return filepath.Join(c.buildDir, "host", c.PrebuiltOS(), "bin")
 }
@@ -528,9 +522,12 @@
 	return PathForOutput(ctx, "host", c.PrebuiltOS(), "framework", path)
 }
 
-// HostSystemTool looks for non-hermetic tools from the system we're running on.
-// Generally shouldn't be used, but useful to find the XCode SDK, etc.
-func (c *config) HostSystemTool(name string) string {
+// NonHermeticHostSystemTool looks for non-hermetic tools from the system we're
+// running on. These tools are not checked-in to AOSP, and therefore could lead
+// to reproducibility problems. Should not be used for other than finding the
+// XCode SDK (xcrun, sw_vers), etc. See ui/build/paths/config.go for the
+// allowlist of host system tools.
+func (c *config) NonHermeticHostSystemTool(name string) string {
 	for _, dir := range filepath.SplitList(c.Getenv("PATH")) {
 		path := filepath.Join(dir, name)
 		if s, err := os.Stat(path); err != nil {
@@ -539,10 +536,13 @@
 			return path
 		}
 	}
-	return name
+	panic(fmt.Errorf(
+		"Unable to use '%s' as a host system tool for build system "+
+			"hermeticity reasons. See build/soong/ui/build/paths/config.go "+
+			"for the full list of allowed host tools on your system.", name))
 }
 
-// PrebuiltOS returns the name of the host OS used in prebuilts directories
+// PrebuiltOS returns the name of the host OS used in prebuilts directories.
 func (c *config) PrebuiltOS() string {
 	switch runtime.GOOS {
 	case "linux":
@@ -559,10 +559,14 @@
 	return fmt.Sprintf("%s/prebuilts/go/%s", c.srcDir, c.PrebuiltOS())
 }
 
+// PrebuiltBuildTool returns the path to a tool in the prebuilts directory containing
+// checked-in tools, like Kati, Ninja or Toybox, for the current host OS.
 func (c *config) PrebuiltBuildTool(ctx PathContext, tool string) Path {
 	return PathForSource(ctx, "prebuilts/build-tools", c.PrebuiltOS(), "bin", tool)
 }
 
+// CpPreserveSymlinksFlags returns the host-specific flag for the cp(1) command
+// to preserve symlinks.
 func (c *config) CpPreserveSymlinksFlags() string {
 	switch runtime.GOOS {
 	case "darwin":
@@ -610,6 +614,8 @@
 	return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
 }
 
+// EnvDeps returns the environment variables this build depends on. The first
+// call to this function blocks future reads from the environment.
 func (c *config) EnvDeps() map[string]string {
 	c.envLock.Lock()
 	defer c.envLock.Unlock()
@@ -617,19 +623,26 @@
 	return c.envDeps
 }
 
-func (c *config) EmbeddedInMake() bool {
-	return c.inMake
+func (c *config) KatiEnabled() bool {
+	return c.katiEnabled
 }
 
 func (c *config) BuildId() string {
 	return String(c.productVariables.BuildId)
 }
 
+// BuildNumberFile returns the path to a text file containing metadata
+// representing the current build's number.
+//
+// Rules that want to reference the build number should read from this file
+// without depending on it. They will run whenever their other dependencies
+// require them to run and get the current build number. This ensures they don't
+// rebuild on every incremental build when the build number changes.
 func (c *config) BuildNumberFile(ctx PathContext) Path {
 	return PathForOutput(ctx, String(c.productVariables.BuildNumberFile))
 }
 
-// DeviceName returns the name of the current device target
+// DeviceName returns the name of the current device target.
 // TODO: take an AndroidModuleContext to select the device name for multi-device builds
 func (c *config) DeviceName() string {
 	return *c.productVariables.DeviceName
@@ -701,19 +714,20 @@
 	return append(levels, c.PreviewApiLevels()...)
 }
 
+// DefaultAppTargetSdk returns the API level that platform apps are targeting.
+// This converts a codename to the exact ApiLevel it represents.
 func (c *config) DefaultAppTargetSdk(ctx EarlyModuleContext) ApiLevel {
 	if Bool(c.productVariables.Platform_sdk_final) {
 		return c.PlatformSdkVersion()
-	} else {
-		codename := c.PlatformSdkCodename()
-		if codename == "" {
-			return NoneApiLevel
-		}
-		if codename == "REL" {
-			panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
-		}
-		return ApiLevelOrPanic(ctx, codename)
 	}
+	codename := c.PlatformSdkCodename()
+	if codename == "" {
+		return NoneApiLevel
+	}
+	if codename == "REL" {
+		panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
+	}
+	return ApiLevelOrPanic(ctx, codename)
 }
 
 func (c *config) AppsDefaultVersionName() string {
@@ -745,19 +759,17 @@
 	defaultCert := String(c.productVariables.DefaultAppCertificate)
 	if defaultCert != "" {
 		return PathForSource(ctx, filepath.Dir(defaultCert))
-	} else {
-		return PathForSource(ctx, "build/make/target/product/security")
 	}
+	return PathForSource(ctx, "build/make/target/product/security")
 }
 
 func (c *config) DefaultAppCertificate(ctx PathContext) (pem, key SourcePath) {
 	defaultCert := String(c.productVariables.DefaultAppCertificate)
 	if defaultCert != "" {
 		return PathForSource(ctx, defaultCert+".x509.pem"), PathForSource(ctx, defaultCert+".pk8")
-	} else {
-		defaultDir := c.DefaultAppCertificateDir(ctx)
-		return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
 	}
+	defaultDir := c.DefaultAppCertificateDir(ctx)
+	return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
 }
 
 func (c *config) ApexKeyDir(ctx ModuleContext) SourcePath {
@@ -767,12 +779,14 @@
 		// When defaultCert is unset or is set to the testkeys path, use the APEX keys
 		// that is under the module dir
 		return pathForModuleSrc(ctx)
-	} else {
-		// If not, APEX keys are under the specified directory
-		return PathForSource(ctx, filepath.Dir(defaultCert))
 	}
+	// If not, APEX keys are under the specified directory
+	return PathForSource(ctx, filepath.Dir(defaultCert))
 }
 
+// AllowMissingDependencies configures Blueprint/Soong to not fail when modules
+// are configured to depend on non-existent modules. Note that this does not
+// affect missing input dependencies at the Ninja level.
 func (c *config) AllowMissingDependencies() bool {
 	return Bool(c.productVariables.Allow_missing_dependencies)
 }
@@ -818,11 +832,6 @@
 	return c.Targets[Android][0].Arch.ArchType
 }
 
-func (c *config) SkipMegaDeviceInstall(path string) bool {
-	return Bool(c.Mega_device) &&
-		strings.HasPrefix(path, filepath.Join(c.buildDir, "target", "product"))
-}
-
 func (c *config) SanitizeHost() []string {
 	return append([]string(nil), c.productVariables.SanitizeHost...)
 }
@@ -842,9 +851,8 @@
 func (c *config) EnableCFI() bool {
 	if c.productVariables.EnableCFI == nil {
 		return true
-	} else {
-		return *c.productVariables.EnableCFI
 	}
+	return *c.productVariables.EnableCFI
 }
 
 func (c *config) DisableScudo() bool {
@@ -889,11 +897,13 @@
 	return c.IsEnvTrue("RUN_ERROR_PRONE")
 }
 
+// XrefCorpusName returns the Kythe cross-reference corpus name.
 func (c *config) XrefCorpusName() string {
 	return c.Getenv("XREF_CORPUS")
 }
 
-// Returns Compilation Unit encoding to use. Can be 'json' (default), 'proto' or 'all'.
+// XrefCuEncoding returns the compilation unit encoding to use for Kythe code
+// xrefs. Can be 'json' (default), 'proto' or 'all'.
 func (c *config) XrefCuEncoding() string {
 	if enc := c.Getenv("KYTHE_KZIP_ENCODING"); enc != "" {
 		return enc
@@ -928,6 +938,10 @@
 	return Bool(c.productVariables.ArtUseReadBarrier)
 }
 
+// Enforce Runtime Resource Overlays for a module. RROs supersede static RROs,
+// but some modules still depend on it.
+//
+// More info: https://source.android.com/devices/architecture/rros
 func (c *config) EnforceRROForModule(name string) bool {
 	enforceList := c.productVariables.EnforceRROTargets
 	// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
@@ -974,6 +988,9 @@
 	return c.productVariables.ModulesLoadedByPrivilegedModules
 }
 
+// DexpreoptGlobalConfigPath returns the path to the dexpreopt.config file in
+// the output directory, if it was created during the product configuration
+// phase by Kati.
 func (c *config) DexpreoptGlobalConfigPath(ctx PathContext) OptionalPath {
 	if c.productVariables.DexpreoptGlobalConfig == nil {
 		return OptionalPathForPath(nil)
@@ -982,6 +999,12 @@
 		pathForBuildToolDep(ctx, *c.productVariables.DexpreoptGlobalConfig))
 }
 
+// DexpreoptGlobalConfig returns the raw byte contents of the dexpreopt global
+// configuration. Since the configuration file was created by Kati during
+// product configuration (externally of soong_build), it's not tracked, so we
+// also manually add a Ninja file dependency on the configuration file to the
+// rule that creates the main build.ninja file. This ensures that build.ninja is
+// regenerated correctly if dexpreopt.config changes.
 func (c *config) DexpreoptGlobalConfig(ctx PathContext) ([]byte, error) {
 	path := c.DexpreoptGlobalConfigPath(ctx)
 	if !path.Valid() {
@@ -1249,6 +1272,10 @@
 	return Bool(c.productVariables.Flatten_apex)
 }
 
+func (c *config) CompressedApex() bool {
+	return Bool(c.productVariables.CompressedApex)
+}
+
 func (c *config) EnforceSystemCertificate() bool {
 	return Bool(c.productVariables.EnforceSystemCertificate)
 }
@@ -1261,6 +1288,14 @@
 	return Bool(c.productVariables.EnforceProductPartitionInterface)
 }
 
+func (c *config) EnforceInterPartitionJavaSdkLibrary() bool {
+	return Bool(c.productVariables.EnforceInterPartitionJavaSdkLibrary)
+}
+
+func (c *config) InterPartitionJavaLibraryAllowList() []string {
+	return c.productVariables.InterPartitionJavaLibraryAllowList
+}
+
 func (c *config) InstallExtraFlattenedApexes() bool {
 	return Bool(c.productVariables.InstallExtraFlattenedApexes)
 }
@@ -1340,26 +1375,31 @@
 //   - "system_ext:foo"
 //
 type ConfiguredJarList struct {
-	apexes []string // A list of apex components.
-	jars   []string // A list of jar components.
+	// A list of apex components, which can be an apex name,
+	// or special names like "platform" or "system_ext".
+	apexes []string
+
+	// A list of jar module name components.
+	jars []string
 }
 
-// The length of the list.
+// Len returns the length of the list of jars.
 func (l *ConfiguredJarList) Len() int {
 	return len(l.jars)
 }
 
-// Jar component of idx-th pair on the list.
+// Jar returns the idx-th jar component of (apex, jar) pairs.
 func (l *ConfiguredJarList) Jar(idx int) string {
 	return l.jars[idx]
 }
 
-// Apex component of idx-th pair on the list.
+// Apex returns the idx-th apex component of (apex, jar) pairs.
 func (l *ConfiguredJarList) Apex(idx int) string {
 	return l.apexes[idx]
 }
 
-// If the list contains a pair with the given jar.
+// ContainsJar returns true if the (apex, jar) pairs contains a pair with the
+// given jar module name.
 func (l *ConfiguredJarList) ContainsJar(jar string) bool {
 	return InList(jar, l.jars)
 }
@@ -1374,7 +1414,8 @@
 	return false
 }
 
-// Index of the first pair with the given jar on the list, or -1 if none.
+// IndexOfJar returns the first pair with the given jar name on the list, or -1
+// if not found.
 func (l *ConfiguredJarList) IndexOfJar(jar string) int {
 	return IndexList(jar, l.jars)
 }
@@ -1402,7 +1443,7 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
-// Filter out sublist.
+// RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
 func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
 	jars := make([]string, 0, l.Len())
@@ -1418,12 +1459,14 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
-// A copy of the list of strings containing jar components.
+// CopyOfJars returns a copy of the list of strings containing jar module name
+// components.
 func (l *ConfiguredJarList) CopyOfJars() []string {
 	return CopyOf(l.jars)
 }
 
-// A copy of the list of strings with colon-separated (apex, jar) pairs.
+// CopyOfApexJarPairs returns a copy of the list of strings with colon-separated
+// (apex, jar) pairs.
 func (l *ConfiguredJarList) CopyOfApexJarPairs() []string {
 	pairs := make([]string, 0, l.Len())
 
@@ -1435,7 +1478,7 @@
 	return pairs
 }
 
-// A list of build paths based on the given directory prefix.
+// BuildPaths returns a list of build paths based on the given directory prefix.
 func (l *ConfiguredJarList) BuildPaths(ctx PathContext, dir OutputPath) WritablePaths {
 	paths := make(WritablePaths, l.Len())
 	for i, jar := range l.jars {
@@ -1444,7 +1487,8 @@
 	return paths
 }
 
-// Called when loading configuration from JSON into a configuration structure.
+// UnmarshalJSON converts JSON configuration from raw bytes into a
+// ConfiguredJarList structure.
 func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
 	// Try and unmarshal into a []string each item of which contains a pair
 	// <apex>:<jar>.
@@ -1464,16 +1508,19 @@
 	return nil
 }
 
+// ModuleStem hardcodes the stem of framework-minus-apex to return "framework".
+//
+// TODO(b/139391334): hard coded until we find a good way to query the stem of a
+// module before any other mutators are run.
 func ModuleStem(module string) string {
-	// b/139391334: the stem of framework-minus-apex is framework. This is hard coded here until we
-	// find a good way to query the stem of a module before any other mutators are run.
 	if module == "framework-minus-apex" {
 		return "framework"
 	}
 	return module
 }
 
-// A list of on-device paths.
+// DevicePaths computes the on-device paths for the list of (apex, jar) pairs,
+// based on the operating system.
 func (l *ConfiguredJarList) DevicePaths(cfg Config, ostype OsType) []string {
 	paths := make([]string, l.Len())
 	for i, jar := range l.jars {
@@ -1534,6 +1581,8 @@
 	}
 }
 
+// CreateTestConfiguredJarList is a function to create ConfiguredJarList for
+// tests.
 func CreateTestConfiguredJarList(list []string) ConfiguredJarList {
 	apexes, jars, err := splitListOfPairsIntoPairOfLists(list)
 	if err != nil {
@@ -1543,6 +1592,7 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// EmptyConfiguredJarList returns an empty jar list.
 func EmptyConfiguredJarList() ConfiguredJarList {
 	return ConfiguredJarList{}
 }
@@ -1552,8 +1602,7 @@
 func (c *config) BootJars() []string {
 	return c.Once(earlyBootJarsKey, func() interface{} {
 		list := c.productVariables.BootJars.CopyOfJars()
-		list = append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
-		return list
+		return append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
 	}).([]string)
 }
 
diff --git a/android/config_test.go b/android/config_test.go
index 68f68a0..7bfc800 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -78,11 +78,6 @@
 	if err != nil {
 		t.Errorf(err.Error())
 	}
-
-	validateConfigAnnotations(&FileConfigurableOptions{})
-	if err != nil {
-		t.Errorf(err.Error())
-	}
 }
 
 func TestMissingVendorConfig(t *testing.T) {
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 15c518a..bf24d98 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -14,11 +14,6 @@
 
 package android
 
-import (
-	"fmt"
-	"io"
-)
-
 func init() {
 	RegisterModuleType("csuite_config", CSuiteConfigFactory)
 }
@@ -38,22 +33,21 @@
 	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
 }
 
-func (me *CSuiteConfig) AndroidMk() AndroidMkData {
-	androidMkData := AndroidMkData{
+func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
+	androidMkEntries := AndroidMkEntries{
 		Class:      "FAKE",
 		Include:    "$(BUILD_SYSTEM)/suite_host_config.mk",
 		OutputFile: OptionalPathForPath(me.OutputFilePath),
 	}
-	androidMkData.Extra = []AndroidMkExtraFunc{
-		func(w io.Writer, outputFile Path) {
+	androidMkEntries.ExtraEntries = []AndroidMkExtraEntriesFunc{
+		func(entries *AndroidMkEntries) {
 			if me.properties.Test_config != nil {
-				fmt.Fprintf(w, "LOCAL_TEST_CONFIG := %s\n",
-					*me.properties.Test_config)
+				entries.SetString("LOCAL_TEST_CONFIG", *me.properties.Test_config)
 			}
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := csuite")
+			entries.AddCompatibilityTestSuites("csuite")
 		},
 	}
-	return androidMkData
+	return []AndroidMkEntries{androidMkEntries}
 }
 
 func InitCSuiteConfigModule(me *CSuiteConfig) {
diff --git a/android/defaults.go b/android/defaults.go
index eb013d7..44753ce 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -183,7 +183,7 @@
 
 	initAndroidModuleBase(module)
 	initProductVariableModule(module)
-	InitArchModule(module)
+	initArchModule(module)
 	InitDefaultableModule(module)
 
 	// Add properties that will not have defaults applied to them.
diff --git a/android/defs.go b/android/defs.go
index 2b1bd85..f5bd362 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -15,8 +15,13 @@
 package android
 
 import (
+	"fmt"
+	"strings"
+	"testing"
+
 	"github.com/google/blueprint"
 	_ "github.com/google/blueprint/bootstrap"
+	"github.com/google/blueprint/proptools"
 )
 
 var (
@@ -91,9 +96,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 -n "$$0" > $out' $content`,
 			Description: "writing file $out",
 		},
 		"content")
@@ -111,3 +116,87 @@
 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(`'\''`, `'`)
+)
+
+func buildWriteFileRule(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,
+		},
+	})
+}
+
+// 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) {
+	// This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes
+	const SHARD_SIZE = 131072 - 10000
+
+	content += "\n"
+	if len(content) > SHARD_SIZE {
+		var chunks WritablePaths
+		for i, c := range ShardString(content, SHARD_SIZE) {
+			tempPath := outputFile.ReplaceExtension(ctx, fmt.Sprintf("%s.%d", outputFile.Ext(), i))
+			buildWriteFileRule(ctx, tempPath, c)
+			chunks = append(chunks, tempPath)
+		}
+		ctx.Build(pctx, BuildParams{
+			Rule:        Cat,
+			Inputs:      chunks.Paths(),
+			Output:      outputFile,
+			Description: "Merging to " + outputFile.Base(),
+		})
+		return
+	}
+	buildWriteFileRule(ctx, outputFile, 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/depset.go b/android/depset.go
deleted file mode 100644
index 60ebcac..0000000
--- a/android/depset.go
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android
-
-import "fmt"
-
-// DepSet is designed to be conceptually compatible with Bazel's depsets:
-// https://docs.bazel.build/versions/master/skylark/depsets.html
-
-// A DepSet efficiently stores Paths from transitive dependencies without copying. It is stored
-// as a DAG of DepSet nodes, each of which has some direct contents and a list of dependency
-// DepSet nodes.
-//
-// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
-// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
-// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
-// elements of children are listed after all of their parents (unless there are duplicate direct
-// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
-// duplicated element is not guaranteed).
-//
-// A DepSet is created by NewDepSet or NewDepSetBuilder.Build from the Paths for direct contents
-// and the *DepSets of dependencies. A DepSet is immutable once created.
-type DepSet struct {
-	preorder   bool
-	reverse    bool
-	order      DepSetOrder
-	direct     Paths
-	transitive []*DepSet
-}
-
-// DepSetBuilder is used to create an immutable DepSet.
-type DepSetBuilder struct {
-	order      DepSetOrder
-	direct     Paths
-	transitive []*DepSet
-}
-
-type DepSetOrder int
-
-const (
-	PREORDER DepSetOrder = iota
-	POSTORDER
-	TOPOLOGICAL
-)
-
-func (o DepSetOrder) String() string {
-	switch o {
-	case PREORDER:
-		return "PREORDER"
-	case POSTORDER:
-		return "POSTORDER"
-	case TOPOLOGICAL:
-		return "TOPOLOGICAL"
-	default:
-		panic(fmt.Errorf("Invalid DepSetOrder %d", o))
-	}
-}
-
-// NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
-func NewDepSet(order DepSetOrder, direct Paths, transitive []*DepSet) *DepSet {
-	var directCopy Paths
-	transitiveCopy := make([]*DepSet, 0, len(transitive))
-
-	for _, dep := range transitive {
-		if dep != nil {
-			if dep.order != order {
-				panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
-					order, dep.order))
-			}
-			transitiveCopy = append(transitiveCopy, dep)
-		}
-	}
-
-	if order == TOPOLOGICAL {
-		directCopy = ReversePaths(direct)
-		reverseDepSetsInPlace(transitiveCopy)
-	} else {
-		// Use copy instead of append(nil, ...) to make a slice that is exactly the size of the input
-		// slice.  The DepSet is immutable, there is no need for additional capacity.
-		directCopy = make(Paths, len(direct))
-		copy(directCopy, direct)
-	}
-
-	return &DepSet{
-		preorder:   order == PREORDER,
-		reverse:    order == TOPOLOGICAL,
-		order:      order,
-		direct:     directCopy,
-		transitive: transitiveCopy,
-	}
-}
-
-// NewDepSetBuilder returns a DepSetBuilder to create an immutable DepSet with the given order.
-func NewDepSetBuilder(order DepSetOrder) *DepSetBuilder {
-	return &DepSetBuilder{order: order}
-}
-
-// Direct adds direct contents to the DepSet being built by a DepSetBuilder. Newly added direct
-// contents are to the right of any existing direct contents.
-func (b *DepSetBuilder) Direct(direct ...Path) *DepSetBuilder {
-	b.direct = append(b.direct, direct...)
-	return b
-}
-
-// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
-// transitive contents are to the right of any existing transitive contents.
-func (b *DepSetBuilder) Transitive(transitive ...*DepSet) *DepSetBuilder {
-	b.transitive = append(b.transitive, transitive...)
-	return b
-}
-
-// Returns the DepSet being built by this DepSetBuilder.  The DepSetBuilder retains its contents
-// for creating more DepSets.
-func (b *DepSetBuilder) Build() *DepSet {
-	return NewDepSet(b.order, b.direct, b.transitive)
-}
-
-// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
-// otherwise postordered.
-func (d *DepSet) walk(visit func(Paths)) {
-	visited := make(map[*DepSet]bool)
-
-	var dfs func(d *DepSet)
-	dfs = func(d *DepSet) {
-		visited[d] = true
-		if d.preorder {
-			visit(d.direct)
-		}
-		for _, dep := range d.transitive {
-			if !visited[dep] {
-				dfs(dep)
-			}
-		}
-
-		if !d.preorder {
-			visit(d.direct)
-		}
-	}
-
-	dfs(d)
-}
-
-// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
-// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
-// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
-// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
-// its transitive dependencies, in which case the ordering of the duplicated element is not
-// guaranteed).
-func (d *DepSet) ToList() Paths {
-	if d == nil {
-		return nil
-	}
-	var list Paths
-	d.walk(func(paths Paths) {
-		list = append(list, paths...)
-	})
-	list = FirstUniquePaths(list)
-	if d.reverse {
-		reversePathsInPlace(list)
-	}
-	return list
-}
-
-// ToSortedList returns the direct and transitive contents of a DepSet in lexically sorted order
-// with duplicates removed.
-func (d *DepSet) ToSortedList() Paths {
-	list := d.ToList()
-	return SortedUniquePaths(list)
-}
-
-func reversePathsInPlace(list Paths) {
-	for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
-		list[i], list[j] = list[j], list[i]
-	}
-}
-
-func reverseDepSetsInPlace(list []*DepSet) {
-	for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
-		list[i], list[j] = list[j], list[i]
-	}
-
-}
diff --git a/android/depset_generic.go b/android/depset_generic.go
new file mode 100644
index 0000000..f00e462
--- /dev/null
+++ b/android/depset_generic.go
@@ -0,0 +1,351 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"reflect"
+)
+
+// depSet is designed to be conceptually compatible with Bazel's depsets:
+// https://docs.bazel.build/versions/master/skylark/depsets.html
+
+type DepSetOrder int
+
+const (
+	PREORDER DepSetOrder = iota
+	POSTORDER
+	TOPOLOGICAL
+)
+
+func (o DepSetOrder) String() string {
+	switch o {
+	case PREORDER:
+		return "PREORDER"
+	case POSTORDER:
+		return "POSTORDER"
+	case TOPOLOGICAL:
+		return "TOPOLOGICAL"
+	default:
+		panic(fmt.Errorf("Invalid DepSetOrder %d", o))
+	}
+}
+
+// A depSet efficiently stores a slice of an arbitrary type from transitive dependencies without
+// copying. It is stored as a DAG of depSet nodes, each of which has some direct contents and a list
+// of dependency depSet nodes.
+//
+// A depSet has an order that will be used to walk the DAG when ToList() is called.  The order
+// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
+// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
+// elements of children are listed after all of their parents (unless there are duplicate direct
+// elements in the depSet or any of its transitive dependencies, in which case the ordering of the
+// duplicated element is not guaranteed).
+//
+// A depSet is created by newDepSet or newDepSetBuilder.Build from the slice for direct contents
+// and the *depSets of dependencies. A depSet is immutable once created.
+//
+// This object uses reflection to remain agnostic to the type it contains.  It should be replaced
+// with generics once those exist in Go.  Callers should generally use a thin wrapper around depSet
+// that provides type-safe methods like DepSet for Paths.
+type depSet struct {
+	preorder   bool
+	reverse    bool
+	order      DepSetOrder
+	direct     interface{}
+	transitive []*depSet
+}
+
+type depSetInterface interface {
+	embeddedDepSet() *depSet
+}
+
+func (d *depSet) embeddedDepSet() *depSet {
+	return d
+}
+
+var _ depSetInterface = (*depSet)(nil)
+
+// newDepSet returns an immutable depSet with the given order, direct and transitive contents.
+// direct must be a slice, but is not type-safe due to the lack of generics in Go.  It can be a
+// nil slice, but not a nil interface{}, i.e. []string(nil) but not nil.
+func newDepSet(order DepSetOrder, direct interface{}, transitive interface{}) *depSet {
+	var directCopy interface{}
+	transitiveDepSet := sliceToDepSets(transitive, order)
+
+	if order == TOPOLOGICAL {
+		directCopy = reverseSlice(direct)
+		reverseSliceInPlace(transitiveDepSet)
+	} else {
+		directCopy = copySlice(direct)
+	}
+
+	return &depSet{
+		preorder:   order == PREORDER,
+		reverse:    order == TOPOLOGICAL,
+		order:      order,
+		direct:     directCopy,
+		transitive: transitiveDepSet,
+	}
+}
+
+// depSetBuilder is used to create an immutable depSet.
+type depSetBuilder struct {
+	order      DepSetOrder
+	direct     reflect.Value
+	transitive []*depSet
+}
+
+// newDepSetBuilder returns a depSetBuilder to create an immutable depSet with the given order and
+// type, represented by a slice of type that will be in the depSet.
+func newDepSetBuilder(order DepSetOrder, typ interface{}) *depSetBuilder {
+	empty := reflect.Zero(reflect.TypeOf(typ))
+	return &depSetBuilder{
+		order:  order,
+		direct: empty,
+	}
+}
+
+// sliceToDepSets converts a slice of any type that implements depSetInterface (by having a depSet
+// embedded in it) into a []*depSet.
+func sliceToDepSets(in interface{}, order DepSetOrder) []*depSet {
+	slice := reflect.ValueOf(in)
+	length := slice.Len()
+	out := make([]*depSet, length)
+	for i := 0; i < length; i++ {
+		vi := slice.Index(i)
+		depSetIntf, ok := vi.Interface().(depSetInterface)
+		if !ok {
+			panic(fmt.Errorf("element %d is a %s, not a depSetInterface", i, vi.Type()))
+		}
+		depSet := depSetIntf.embeddedDepSet()
+		if depSet.order != order {
+			panic(fmt.Errorf("incompatible order, new depSet is %s but transitive depSet is %s",
+				order, depSet.order))
+		}
+		out[i] = depSet
+	}
+	return out
+}
+
+// DirectSlice adds direct contents to the depSet being built by a depSetBuilder. Newly added direct
+// contents are to the right of any existing direct contents.  The argument must be a slice, but
+// is not type-safe due to the lack of generics in Go.
+func (b *depSetBuilder) DirectSlice(direct interface{}) *depSetBuilder {
+	b.direct = reflect.AppendSlice(b.direct, reflect.ValueOf(direct))
+	return b
+}
+
+// Direct adds direct contents to the depSet being built by a depSetBuilder. Newly added direct
+// contents are to the right of any existing direct contents.  The argument must be the same type
+// as the element of the slice passed to newDepSetBuilder, but is not type-safe due to the lack of
+// generics in Go.
+func (b *depSetBuilder) Direct(direct interface{}) *depSetBuilder {
+	b.direct = reflect.Append(b.direct, reflect.ValueOf(direct))
+	return b
+}
+
+// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
+// transitive contents are to the right of any existing transitive contents.  The argument can
+// be any slice of type that has depSet embedded in it.
+func (b *depSetBuilder) Transitive(transitive interface{}) *depSetBuilder {
+	depSets := sliceToDepSets(transitive, b.order)
+	b.transitive = append(b.transitive, depSets...)
+	return b
+}
+
+// Returns the depSet being built by this depSetBuilder.  The depSetBuilder retains its contents
+// for creating more depSets.
+func (b *depSetBuilder) Build() *depSet {
+	return newDepSet(b.order, b.direct.Interface(), b.transitive)
+}
+
+// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
+// otherwise postordered.
+func (d *depSet) walk(visit func(interface{})) {
+	visited := make(map[*depSet]bool)
+
+	var dfs func(d *depSet)
+	dfs = func(d *depSet) {
+		visited[d] = true
+		if d.preorder {
+			visit(d.direct)
+		}
+		for _, dep := range d.transitive {
+			if !visited[dep] {
+				dfs(dep)
+			}
+		}
+
+		if !d.preorder {
+			visit(d.direct)
+		}
+	}
+
+	dfs(d)
+}
+
+// ToList returns the depSet flattened to a list.  The order in the list is based on the order
+// of the depSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
+// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
+// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
+// its transitive dependencies, in which case the ordering of the duplicated element is not
+// guaranteed).
+//
+// This method uses a reflection-based implementation to find the unique elements in slice, which
+// is around 3x slower than a concrete implementation.  Type-safe wrappers around depSet can
+// provide their own implementation of ToList that calls depSet.toList with a method that
+// uses a concrete implementation.
+func (d *depSet) ToList() interface{} {
+	return d.toList(firstUnique)
+}
+
+// toList returns the depSet flattened to a list.  The order in the list is based on the order
+// of the depSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
+// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
+// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
+// its transitive dependencies, in which case the ordering of the duplicated element is not
+// guaranteed).  The firstUniqueFunc is used to remove duplicates from the list.
+func (d *depSet) toList(firstUniqueFunc func(interface{}) interface{}) interface{} {
+	if d == nil {
+		return nil
+	}
+	slice := reflect.Zero(reflect.TypeOf(d.direct))
+	d.walk(func(paths interface{}) {
+		slice = reflect.AppendSlice(slice, reflect.ValueOf(paths))
+	})
+	list := slice.Interface()
+	list = firstUniqueFunc(list)
+	if d.reverse {
+		reverseSliceInPlace(list)
+	}
+	return list
+}
+
+// firstUnique returns all unique elements of a slice, keeping the first copy of each.  It
+// modifies the slice contents in place, and returns a subslice of the original slice.  The
+// argument must be a slice, but is not type-safe due to the lack of reflection in Go.
+//
+// Performance of the reflection-based firstUnique is up to 3x slower than a concrete type
+// version such as FirstUniqueStrings.
+func firstUnique(slice interface{}) interface{} {
+	// 4 was chosen based on Benchmark_firstUnique results.
+	if reflect.ValueOf(slice).Len() > 4 {
+		return firstUniqueMap(slice)
+	}
+	return firstUniqueList(slice)
+}
+
+// firstUniqueList is an implementation of firstUnique using an O(N^2) list comparison to look for
+// duplicates.
+func firstUniqueList(in interface{}) interface{} {
+	writeIndex := 0
+	slice := reflect.ValueOf(in)
+	length := slice.Len()
+outer:
+	for readIndex := 0; readIndex < length; readIndex++ {
+		readValue := slice.Index(readIndex)
+		for compareIndex := 0; compareIndex < writeIndex; compareIndex++ {
+			compareValue := slice.Index(compareIndex)
+			// These two Interface() calls seem to cause an allocation and significantly
+			// slow down this list-based implementation.  The map implementation below doesn't
+			// have this issue because reflect.Value.MapIndex takes a Value and appears to be
+			// able to do the map lookup without an allocation.
+			if readValue.Interface() == compareValue.Interface() {
+				// The value at readIndex already exists somewhere in the output region
+				// of the slice before writeIndex, skip it.
+				continue outer
+			}
+		}
+		if readIndex != writeIndex {
+			writeValue := slice.Index(writeIndex)
+			writeValue.Set(readValue)
+		}
+		writeIndex++
+	}
+	return slice.Slice(0, writeIndex).Interface()
+}
+
+var trueValue = reflect.ValueOf(true)
+
+// firstUniqueList is an implementation of firstUnique using an O(N) hash set lookup to look for
+// duplicates.
+func firstUniqueMap(in interface{}) interface{} {
+	writeIndex := 0
+	slice := reflect.ValueOf(in)
+	length := slice.Len()
+	seen := reflect.MakeMapWithSize(reflect.MapOf(slice.Type().Elem(), trueValue.Type()), slice.Len())
+	for readIndex := 0; readIndex < length; readIndex++ {
+		readValue := slice.Index(readIndex)
+		if seen.MapIndex(readValue).IsValid() {
+			continue
+		}
+		seen.SetMapIndex(readValue, trueValue)
+		if readIndex != writeIndex {
+			writeValue := slice.Index(writeIndex)
+			writeValue.Set(readValue)
+		}
+		writeIndex++
+	}
+	return slice.Slice(0, writeIndex).Interface()
+}
+
+// reverseSliceInPlace reverses the elements of a slice in place.  The argument must be a slice, but
+// is not type-safe due to the lack of reflection in Go.
+func reverseSliceInPlace(in interface{}) {
+	swapper := reflect.Swapper(in)
+	slice := reflect.ValueOf(in)
+	length := slice.Len()
+	for i, j := 0, length-1; i < j; i, j = i+1, j-1 {
+		swapper(i, j)
+	}
+}
+
+// reverseSlice returns a copy of a slice in reverse order.  The argument must be a slice, but is
+// not type-safe due to the lack of reflection in Go.
+func reverseSlice(in interface{}) interface{} {
+	slice := reflect.ValueOf(in)
+	if !slice.IsValid() || slice.IsNil() {
+		return in
+	}
+	if slice.Kind() != reflect.Slice {
+		panic(fmt.Errorf("%t is not a slice", in))
+	}
+	length := slice.Len()
+	if length == 0 {
+		return in
+	}
+	out := reflect.MakeSlice(slice.Type(), length, length)
+	for i := 0; i < length; i++ {
+		out.Index(i).Set(slice.Index(length - 1 - i))
+	}
+	return out.Interface()
+}
+
+// copySlice returns a copy of a slice.  The argument must be a slice, but is not type-safe due to
+// the lack of reflection in Go.
+func copySlice(in interface{}) interface{} {
+	slice := reflect.ValueOf(in)
+	if !slice.IsValid() || slice.IsNil() {
+		return in
+	}
+	length := slice.Len()
+	if length == 0 {
+		return in
+	}
+	out := reflect.MakeSlice(slice.Type(), length, length)
+	reflect.Copy(out, slice)
+	return out.Interface()
+}
diff --git a/android/depset_paths.go b/android/depset_paths.go
new file mode 100644
index 0000000..ed561ba
--- /dev/null
+++ b/android/depset_paths.go
@@ -0,0 +1,94 @@
+// 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
+
+// This file implements DepSet, a thin type-safe wrapper around depSet that contains Paths.
+
+// A DepSet efficiently stores Paths from transitive dependencies without copying. It is stored
+// as a DAG of DepSet nodes, each of which has some direct contents and a list of dependency
+// DepSet nodes.
+//
+// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
+// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
+// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
+// elements of children are listed after all of their parents (unless there are duplicate direct
+// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
+// duplicated element is not guaranteed).
+//
+// A DepSet is created by NewDepSet or NewDepSetBuilder.Build from the Paths for direct contents
+// and the *DepSets of dependencies. A DepSet is immutable once created.
+type DepSet struct {
+	depSet
+}
+
+// DepSetBuilder is used to create an immutable DepSet.
+type DepSetBuilder struct {
+	depSetBuilder
+}
+
+// NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
+func NewDepSet(order DepSetOrder, direct Paths, transitive []*DepSet) *DepSet {
+	return &DepSet{*newDepSet(order, direct, transitive)}
+}
+
+// NewDepSetBuilder returns a DepSetBuilder to create an immutable DepSet with the given order.
+func NewDepSetBuilder(order DepSetOrder) *DepSetBuilder {
+	return &DepSetBuilder{*newDepSetBuilder(order, Paths(nil))}
+}
+
+// Direct adds direct contents to the DepSet being built by a DepSetBuilder. Newly added direct
+// contents are to the right of any existing direct contents.
+func (b *DepSetBuilder) Direct(direct ...Path) *DepSetBuilder {
+	b.depSetBuilder.DirectSlice(direct)
+	return b
+}
+
+// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
+// transitive contents are to the right of any existing transitive contents.
+func (b *DepSetBuilder) Transitive(transitive ...*DepSet) *DepSetBuilder {
+	b.depSetBuilder.Transitive(transitive)
+	return b
+}
+
+// Returns the DepSet being built by this DepSetBuilder.  The DepSetBuilder retains its contents
+// for creating more DepSets.
+func (b *DepSetBuilder) Build() *DepSet {
+	return &DepSet{*b.depSetBuilder.Build()}
+}
+
+// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
+// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
+// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
+// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
+// its transitive dependencies, in which case the ordering of the duplicated element is not
+// guaranteed).
+func (d *DepSet) ToList() Paths {
+	if d == nil {
+		return nil
+	}
+	return d.toList(func(paths interface{}) interface{} {
+		return FirstUniquePaths(paths.(Paths))
+	}).(Paths)
+}
+
+// ToSortedList returns the direct and transitive contents of a DepSet in lexically sorted order
+// with duplicates removed.
+func (d *DepSet) ToSortedList() Paths {
+	if d == nil {
+		return nil
+	}
+	paths := d.ToList()
+	return SortedUniquePaths(paths)
+}
diff --git a/android/depset_test.go b/android/depset_test.go
index c328127..955ccb0 100644
--- a/android/depset_test.go
+++ b/android/depset_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 )
@@ -108,6 +109,7 @@
 			name: "builderReuse",
 			depSet: func(t *testing.T, order DepSetOrder) *DepSet {
 				assertEquals := func(t *testing.T, w, g Paths) {
+					t.Helper()
 					if !reflect.DeepEqual(w, g) {
 						t.Errorf("want %q, got %q", w, g)
 					}
@@ -302,3 +304,87 @@
 		})
 	}
 }
+
+func Test_firstUnique(t *testing.T) {
+	f := func(t *testing.T, imp func([]string) []string, in, want []string) {
+		t.Helper()
+		out := imp(in)
+		if !reflect.DeepEqual(out, want) {
+			t.Errorf("incorrect output:")
+			t.Errorf("     input: %#v", in)
+			t.Errorf("  expected: %#v", want)
+			t.Errorf("       got: %#v", out)
+		}
+	}
+
+	for _, testCase := range firstUniqueStringsTestCases {
+		t.Run("list", func(t *testing.T) {
+			f(t, func(s []string) []string {
+				return firstUniqueList(s).([]string)
+			}, testCase.in, testCase.out)
+		})
+		t.Run("map", func(t *testing.T) {
+			f(t, func(s []string) []string {
+				return firstUniqueMap(s).([]string)
+			}, testCase.in, testCase.out)
+		})
+	}
+}
+
+func Benchmark_firstUnique(b *testing.B) {
+	implementations := []struct {
+		name string
+		f    func([]string) []string
+	}{
+		{
+			name: "list",
+			f: func(slice []string) []string {
+				return firstUniqueList(slice).([]string)
+			},
+		},
+		{
+			name: "map",
+			f: func(slice []string) []string {
+				return firstUniqueMap(slice).([]string)
+			},
+		},
+		{
+			name: "optimal",
+			f: func(slice []string) []string {
+				return firstUnique(slice).([]string)
+			},
+		},
+	}
+	const maxSize = 1024
+	uniqueStrings := make([]string, maxSize)
+	for i := range uniqueStrings {
+		uniqueStrings[i] = strconv.Itoa(i)
+	}
+	sameString := make([]string, maxSize)
+	for i := range sameString {
+		sameString[i] = uniqueStrings[0]
+	}
+
+	f := func(b *testing.B, imp func([]string) []string, s []string) {
+		for i := 0; i < b.N; i++ {
+			b.ReportAllocs()
+			s = append([]string(nil), s...)
+			imp(s)
+		}
+	}
+
+	for n := 1; n <= maxSize; n <<= 1 {
+		b.Run(strconv.Itoa(n), func(b *testing.B) {
+			for _, implementation := range implementations {
+				b.Run(implementation.name, func(b *testing.B) {
+					b.Run("same", func(b *testing.B) {
+						f(b, implementation.f, sameString[:n])
+					})
+					b.Run("unique", func(b *testing.B) {
+						f(b, implementation.f, uniqueStrings[:n])
+					})
+				})
+			}
+		})
+	}
+}
diff --git a/android/deptag.go b/android/deptag.go
new file mode 100644
index 0000000..be5c35c
--- /dev/null
+++ b/android/deptag.go
@@ -0,0 +1,45 @@
+// 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 "github.com/google/blueprint"
+
+// Dependency tags can implement this interface and return true from InstallDepNeeded to annotate
+// that the installed files of the parent should depend on the installed files of the child.
+type InstallNeededDependencyTag interface {
+	// If InstallDepNeeded returns true then the installed files of the parent will depend on the
+	// installed files of the child.
+	InstallDepNeeded() bool
+}
+
+// Dependency tags can embed this struct to annotate that the installed files of the parent should
+// depend on the installed files of the child.
+type InstallAlwaysNeededDependencyTag struct{}
+
+func (i InstallAlwaysNeededDependencyTag) InstallDepNeeded() bool {
+	return true
+}
+
+var _ InstallNeededDependencyTag = InstallAlwaysNeededDependencyTag{}
+
+// IsInstallDepNeeded returns true if the dependency tag implements the InstallNeededDependencyTag
+// interface and the InstallDepNeeded returns true, meaning that the installed files of the parent
+// should depend on the installed files of the child.
+func IsInstallDepNeeded(tag blueprint.DependencyTag) bool {
+	if i, ok := tag.(InstallNeededDependencyTag); ok {
+		return i.InstallDepNeeded()
+	}
+	return false
+}
diff --git a/android/deptag_test.go b/android/deptag_test.go
new file mode 100644
index 0000000..bdd449e
--- /dev/null
+++ b/android/deptag_test.go
@@ -0,0 +1,135 @@
+// 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"
+
+	"github.com/google/blueprint"
+)
+
+type testInstallDependencyTagModule struct {
+	ModuleBase
+	Properties struct {
+		Install_deps []string
+		Deps         []string
+	}
+}
+
+func (t *testInstallDependencyTagModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	outputFile := PathForModuleOut(ctx, "out")
+	ctx.Build(pctx, BuildParams{
+		Rule:   Touch,
+		Output: outputFile,
+	})
+	ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+}
+
+var testInstallDependencyTagAlwaysDepTag = struct {
+	blueprint.DependencyTag
+	InstallAlwaysNeededDependencyTag
+}{}
+
+var testInstallDependencyTagNeverDepTag = struct {
+	blueprint.DependencyTag
+}{}
+
+func (t *testInstallDependencyTagModule) DepsMutator(ctx BottomUpMutatorContext) {
+	ctx.AddVariationDependencies(nil, testInstallDependencyTagAlwaysDepTag, t.Properties.Install_deps...)
+	ctx.AddVariationDependencies(nil, testInstallDependencyTagNeverDepTag, t.Properties.Deps...)
+}
+
+func testInstallDependencyTagModuleFactory() Module {
+	module := &testInstallDependencyTagModule{}
+	InitAndroidArchModule(module, HostAndDeviceDefault, MultilibCommon)
+	module.AddProperties(&module.Properties)
+	return module
+}
+
+func TestInstallDependencyTag(t *testing.T) {
+	bp := `
+		test_module {
+			name: "foo",
+			deps: ["dep"],
+			install_deps: ["install_dep"],
+		}
+
+		test_module {
+			name: "install_dep",
+			install_deps: ["transitive"],
+		}
+
+		test_module {
+			name: "transitive",
+		}
+
+		test_module {
+			name: "dep",
+		}
+	`
+
+	config := TestArchConfig(buildDir, nil, bp, nil)
+	ctx := NewTestArchContext(config)
+
+	ctx.RegisterModuleType("test_module", testInstallDependencyTagModuleFactory)
+
+	ctx.Register()
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	hostFoo := ctx.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
+	hostInstallDep := ctx.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostTransitive := ctx.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
+	hostDep := ctx.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
+
+	if g, w := hostFoo.Implicits.Strings(), hostInstallDep.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostFoo.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostInstallDep.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected host dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostFoo.Implicits.Strings(), hostDep.Output.String(); InList(w, g) {
+		t.Errorf("expected no host dependency %q, got %q", w, g)
+	}
+
+	deviceFoo := ctx.ModuleForTests("foo", "android_common").Description("install")
+	deviceInstallDep := ctx.ModuleForTests("install_dep", "android_common").Description("install")
+	deviceTransitive := ctx.ModuleForTests("transitive", "android_common").Description("install")
+	deviceDep := ctx.ModuleForTests("dep", "android_common").Description("install")
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceInstallDep.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceInstallDep.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+		t.Errorf("expected device dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceFoo.OrderOnly.Strings(), deviceDep.Output.String(); InList(w, g) {
+		t.Errorf("expected no device dependency %q, got %q", w, g)
+	}
+}
diff --git a/android/filegroup.go b/android/filegroup.go
index 68311e3..9425616 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"android/soong/bazel"
 	"strings"
 )
 
@@ -37,6 +38,9 @@
 	// Create a make variable with the specified name that contains the list of files in the
 	// filegroup, relative to the root of the source tree.
 	Export_to_make_var *string
+
+	// Properties for Bazel migration purposes.
+	bazel.Properties
 }
 
 type fileGroup struct {
diff --git a/android/makevars.go b/android/makevars.go
index 3ca7792..5101436 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
@@ -207,7 +213,7 @@
 }
 
 func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	if !ctx.Config().EmbeddedInMake() {
+	if !ctx.Config().KatiEnabled() {
 		return
 	}
 
@@ -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..b6729c0 100644
--- a/android/module.go
+++ b/android/module.go
@@ -440,6 +440,7 @@
 	TargetRequiredModuleNames() []string
 
 	FilesToInstall() InstallPaths
+	PackagingSpecs() []PackagingSpec
 }
 
 // Qualified id for a module
@@ -502,8 +503,12 @@
 	// A suffix to add to the artifact file name (before any extension).
 	Suffix *string `android:"arch_variant"`
 
-	// A string tag to select the OutputFiles associated with the tag. Defaults to the
-	// the empty "" string.
+	// A string tag to select the OutputFiles associated with the tag.
+	//
+	// If no tag is specified then it will select the default dist paths provided
+	// by the module type. If a tag of "" is specified then it will return the
+	// default output files provided by the modules, i.e. the result of calling
+	// OutputFiles("").
 	Tag *string `android:"arch_variant"`
 }
 
@@ -716,7 +721,9 @@
 	DebugMutators   []string `blueprint:"mutated"`
 	DebugVariations []string `blueprint:"mutated"`
 
-	// set by ImageMutator
+	// ImageVariation is set by ImageMutator to specify which image this variation is for,
+	// for example "" for core or "recovery" for recovery.  It will often be set to one of the
+	// constants in image.go, but can also be set to a custom value by individual module types.
 	ImageVariation string `blueprint:"mutated"`
 }
 
@@ -730,9 +737,45 @@
 	Dists []Dist `android:"arch_variant"`
 }
 
+// The key to use in TaggedDistFiles when a Dist structure does not specify a
+// tag property. This intentionally does not use "" as the default because that
+// would mean that an empty tag would have a different meaning when used in a dist
+// structure that when used to reference a specific set of output paths using the
+// :module{tag} syntax, which passes tag to the OutputFiles(tag) method.
+const DefaultDistTag = "<default-dist-tag>"
+
 // A map of OutputFile tag keys to Paths, for disting purposes.
 type TaggedDistFiles map[string]Paths
 
+// addPathsForTag adds a mapping from the tag to the paths. If the map is nil
+// then it will create a map, update it and then return it. If a mapping already
+// exists for the tag then the paths are appended to the end of the current list
+// of paths, ignoring any duplicates.
+func (t TaggedDistFiles) addPathsForTag(tag string, paths ...Path) TaggedDistFiles {
+	if t == nil {
+		t = make(TaggedDistFiles)
+	}
+
+	for _, distFile := range paths {
+		if distFile != nil && !t[tag].containsPath(distFile) {
+			t[tag] = append(t[tag], distFile)
+		}
+	}
+
+	return t
+}
+
+// merge merges the entries from the other TaggedDistFiles object into this one.
+// If the TaggedDistFiles is nil then it will create a new instance, merge the
+// other into it, and then return it.
+func (t TaggedDistFiles) merge(other TaggedDistFiles) TaggedDistFiles {
+	for tag, paths := range other {
+		t = t.addPathsForTag(tag, paths...)
+	}
+
+	return t
+}
+
 func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
 	for _, path := range paths {
 		if path == nil {
@@ -741,7 +784,7 @@
 	}
 
 	// The default OutputFile tag is the empty "" string.
-	return TaggedDistFiles{"": paths}
+	return TaggedDistFiles{DefaultDistTag: paths}
 }
 
 type hostAndDeviceProperties struct {
@@ -765,27 +808,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
@@ -819,6 +867,8 @@
 	m.base().module = m
 }
 
+// InitAndroidModule initializes the Module as an Android module that is not architecture-specific.
+// It adds the common properties, for example "name" and "enabled".
 func InitAndroidModule(m Module) {
 	initAndroidModuleBase(m)
 	base := m.base()
@@ -838,6 +888,12 @@
 	setPrimaryVisibilityProperty(m, "visibility", &base.commonProperties.Visibility)
 }
 
+// InitAndroidArchModule initializes the Module as an Android module that is architecture-specific.
+// It adds the common properties, for example "name" and "enabled", as well as runtime generated
+// property structs for architecture-specific versions of generic properties tagged with
+// `android:"arch_variant"`.
+//
+//  InitAndroidModule should not be called if InitAndroidArchModule was called.
 func InitAndroidArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidModule(m)
 
@@ -847,21 +903,37 @@
 	base.commonProperties.ArchSpecific = true
 	base.commonProperties.UseTargetVariants = true
 
-	switch hod {
-	case HostAndDeviceSupported, HostAndDeviceDefault:
+	if hod&hostSupported != 0 && hod&deviceSupported != 0 {
 		m.AddProperties(&base.hostAndDeviceProperties)
 	}
 
-	InitArchModule(m)
+	initArchModule(m)
 }
 
+// InitAndroidMultiTargetsArchModule initializes the Module as an Android module that is
+// architecture-specific, but will only have a single variant per OS that handles all the
+// architectures simultaneously.  The list of Targets that it must handle will be available from
+// ModuleContext.MultiTargets. It adds the common properties, for example "name" and "enabled", as
+// well as runtime generated property structs for architecture-specific versions of generic
+// properties tagged with `android:"arch_variant"`.
+//
+// InitAndroidModule or InitAndroidArchModule should not be called if
+// InitAndroidMultiTargetsArchModule was called.
 func InitAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidArchModule(m, hod, defaultMultilib)
 	m.base().commonProperties.UseTargetVariants = false
 }
 
-// As InitAndroidMultiTargetsArchModule except it creates an additional CommonOS variant that
-// has dependencies on all the OsType specific variants.
+// InitCommonOSAndroidMultiTargetsArchModule initializes the Module as an Android module that is
+// architecture-specific, but will only have a single variant per OS that handles all the
+// architectures simultaneously, and will also have an additional CommonOS variant that has
+// dependencies on all the OS-specific variants.  The list of Targets that it must handle will be
+// available from ModuleContext.MultiTargets.  It adds the common properties, for example "name" and
+// "enabled", as well as runtime generated property structs for architecture-specific versions of
+// generic properties tagged with `android:"arch_variant"`.
+//
+// InitAndroidModule, InitAndroidArchModule or InitAndroidMultiTargetsArchModule should not be
+// called if InitCommonOSAndroidMultiTargetsArchModule was called.
 func InitCommonOSAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidArchModule(m, hod, defaultMultilib)
 	m.base().commonProperties.UseTargetVariants = false
@@ -934,9 +1006,13 @@
 	noAddressSanitizer bool
 	installFiles       InstallPaths
 	checkbuildFiles    Paths
+	packagingSpecs     []PackagingSpec
 	noticeFiles        Paths
 	phonies            map[string]Paths
 
+	// The files to copy to the dist as explicitly specified in the .bp file.
+	distFiles TaggedDistFiles
+
 	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
 	// Only set on the final variant of each module
 	installTarget    WritablePath
@@ -1038,23 +1114,30 @@
 }
 
 func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
-	distFiles := make(TaggedDistFiles)
+	var distFiles TaggedDistFiles
 	for _, dist := range m.Dists() {
-		var tag string
-		var distFilesForTag Paths
-		if dist.Tag == nil {
-			tag = ""
-		} else {
-			tag = *dist.Tag
-		}
-		distFilesForTag, err := m.base().module.(OutputFileProducer).OutputFiles(tag)
-		if err != nil {
-			ctx.PropertyErrorf("dist.tag", "%s", err.Error())
-		}
-		for _, distFile := range distFilesForTag {
-			if distFile != nil && !distFiles[tag].containsPath(distFile) {
-				distFiles[tag] = append(distFiles[tag], distFile)
+		// If no tag is specified then it means to use the default dist paths so use
+		// the special tag name which represents that.
+		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
+
+		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
+			// Call the OutputFiles(tag) method to get the paths associated with the tag.
+			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
+
+			// If the tag was not supported and is not DefaultDistTag then it is an error.
+			// Failing to find paths for DefaultDistTag is not an error. It just means
+			// that the module type requires the legacy behavior.
+			if err != nil && tag != DefaultDistTag {
+				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
 			}
+
+			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
+		} else if tag != DefaultDistTag {
+			// If the tag was specified then it is an error if the module does not
+			// implement OutputFileProducer because there is no other way of accessing
+			// the paths for the specified tag.
+			ctx.PropertyErrorf("dist.tag",
+				"tag %s not supported because the module does not implement OutputFileProducer", tag)
 		}
 	}
 
@@ -1098,43 +1181,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 {
@@ -1242,14 +1336,18 @@
 	return m.commonProperties.NamespaceExportedToMake
 }
 
+// computeInstallDeps finds the installed paths of all dependencies that have a dependency
+// tag that is annotated as needing installation via the IsInstallDepNeeded method.
 func (m *ModuleBase) computeInstallDeps(ctx blueprint.ModuleContext) InstallPaths {
-
 	var result InstallPaths
-	// TODO(ccross): we need to use WalkDeps and have some way to know which dependencies require installation
-	ctx.VisitDepsDepthFirst(func(m blueprint.Module) {
-		if a, ok := m.(Module); ok {
-			result = append(result, a.FilesToInstall()...)
+	ctx.WalkDeps(func(child, parent blueprint.Module) bool {
+		if a, ok := child.(Module); ok {
+			if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
+				result = append(result, a.FilesToInstall()...)
+				return true
+			}
 		}
+		return false
 	})
 
 	return result
@@ -1259,6 +1357,10 @@
 	return m.installFiles
 }
 
+func (m *ModuleBase) PackagingSpecs() []PackagingSpec {
+	return m.packagingSpecs
+}
+
 func (m *ModuleBase) NoAddressSanitizer() bool {
 	return m.noAddressSanitizer
 }
@@ -1392,7 +1494,7 @@
 
 	if len(deps) > 0 {
 		suffix := ""
-		if ctx.Config().EmbeddedInMake() {
+		if ctx.Config().KatiEnabled() {
 			suffix = "-soong"
 		}
 
@@ -1527,22 +1629,9 @@
 	ctx.Variable(pctx, "moduleDescSuffix", s)
 
 	// Some common property checks for properties that will be used later in androidmk.go
-	if m.distProperties.Dist.Dest != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dest)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dest", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Dir != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dir)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dir", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Suffix != nil {
-		if strings.Contains(*m.distProperties.Dist.Suffix, "/") {
-			ctx.PropertyErrorf("dist.suffix", "Suffix may not contain a '/' character.")
-		}
+	checkDistProperties(ctx, "dist", &m.distProperties.Dist)
+	for i, _ := range m.distProperties.Dists {
+		checkDistProperties(ctx, fmt.Sprintf("dists[%d]", i), &m.distProperties.Dists[i])
 	}
 
 	if m.Enabled() {
@@ -1579,8 +1668,18 @@
 			return
 		}
 
+		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
+		// as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
+		// output paths being set which must be done before or during
+		// GenerateAndroidBuildActions.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+		if ctx.Failed() {
+			return
+		}
+
 		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 {
@@ -1606,6 +1705,32 @@
 	m.variables = ctx.variables
 }
 
+// Check the supplied dist structure to make sure that it is valid.
+//
+// property - the base property, e.g. dist or dists[1], which is combined with the
+// name of the nested property to produce the full property, e.g. dist.dest or
+// dists[1].dir.
+func checkDistProperties(ctx *moduleContext, property string, dist *Dist) {
+	if dist.Dest != nil {
+		_, err := validateSafePath(*dist.Dest)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dest", "%s", err.Error())
+		}
+	}
+	if dist.Dir != nil {
+		_, err := validateSafePath(*dist.Dir)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dir", "%s", err.Error())
+		}
+	}
+	if dist.Suffix != nil {
+		if strings.Contains(*dist.Suffix, "/") {
+			ctx.PropertyErrorf(property+".suffix", "Suffix may not contain a '/' character.")
+		}
+	}
+
+}
+
 type earlyModuleContext struct {
 	blueprint.EarlyModuleContext
 
@@ -1748,6 +1873,7 @@
 type moduleContext struct {
 	bp blueprint.ModuleContext
 	baseModuleContext
+	packagingSpecs  []PackagingSpec
 	installDeps     InstallPaths
 	installFiles    InstallPaths
 	checkbuildFiles Paths
@@ -2270,11 +2396,7 @@
 	}
 
 	if m.Device() {
-		if m.Config().EmbeddedInMake() && !m.InstallBypassMake() {
-			return true
-		}
-
-		if m.Config().SkipMegaDeviceInstall(fullInstallPath.String()) {
+		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
 			return true
 		}
 	}
@@ -2284,16 +2406,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 +2433,11 @@
 			orderOnlyDeps = deps
 		}
 
+		rule := Cp
+		if executable {
+			rule = CpExecutable
+		}
+
 		m.Build(pctx, BuildParams{
 			Rule:        rule,
 			Description: "install " + fullInstallPath.Base(),
@@ -2319,11 +2445,19 @@
 			Input:       srcPath,
 			Implicits:   implicitDeps,
 			OrderOnly:   orderOnlyDeps,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 		})
 
 		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,18 +2466,18 @@
 	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(),
 			Output:      fullInstallPath,
 			Input:       srcPath,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 			Args: map[string]string{
 				"fromPath": relPath,
 			},
@@ -2352,6 +2486,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
 }
 
@@ -2366,7 +2508,7 @@
 			Rule:        Symlink,
 			Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
 			Output:      fullInstallPath,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 			Args: map[string]string{
 				"fromPath": absPath,
 			},
@@ -2374,6 +2516,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
 }
 
@@ -2500,6 +2650,15 @@
 			return nil, fmt.Errorf("failed to get output files from module %q", pathContextName(ctx, module))
 		}
 		return paths, nil
+	} else if sourceFileProducer, ok := module.(SourceFileProducer); ok {
+		if tag != "" {
+			return nil, fmt.Errorf("module %q is a SourceFileProducer, not an OutputFileProducer, and so does not support tag %q", pathContextName(ctx, module), tag)
+		}
+		paths := sourceFileProducer.Srcs()
+		if len(paths) == 0 {
+			return nil, fmt.Errorf("failed to get output files from module %q", pathContextName(ctx, module))
+		}
+		return paths, nil
 	} else {
 		return nil, fmt.Errorf("module %q is not an OutputFileProducer", pathContextName(ctx, module))
 	}
@@ -2587,7 +2746,7 @@
 	})
 
 	suffix := ""
-	if ctx.Config().EmbeddedInMake() {
+	if ctx.Config().KatiEnabled() {
 		suffix = "-soong"
 	}
 
@@ -2595,7 +2754,7 @@
 	ctx.Phony("checkbuild"+suffix, checkbuildDeps...)
 
 	// Make will generate the MODULES-IN-* targets
-	if ctx.Config().EmbeddedInMake() {
+	if ctx.Config().KatiEnabled() {
 		return
 	}
 
diff --git a/android/module_test.go b/android/module_test.go
index 6cc1813..e3cc613 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -232,3 +232,51 @@
 		t.Errorf("Expected build params to fail validation: %+v", bparams)
 	}
 }
+
+func TestDistErrorChecking(t *testing.T) {
+	bp := `
+		deps {
+			name: "foo",
+      dist: {
+        dest: "../invalid-dest",
+        dir: "../invalid-dir",
+        suffix: "invalid/suffix",
+      },
+      dists: [
+        {
+          dest: "../invalid-dest0",
+          dir: "../invalid-dir0",
+          suffix: "invalid/suffix0",
+        },
+        {
+          dest: "../invalid-dest1",
+          dir: "../invalid-dir1",
+          suffix: "invalid/suffix1",
+        },
+      ],
+ 		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("deps", depsModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+
+	expectedErrs := []string{
+		"\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E",
+		"\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E",
+		"\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E",
+		"\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E",
+		"\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E",
+		"\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E",
+		"\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E",
+	}
+	CheckErrorsAgainstExpectations(t, errs, expectedErrs)
+}
diff --git a/android/mutator.go b/android/mutator.go
index 7a10477..31edea3 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -44,6 +44,11 @@
 	}
 }
 
+func registerMutatorsForBazelConversion(ctx *blueprint.Context) {
+	// FIXME(b/171263886): Start bringing in mutators to make the Bionic
+	// module subgraph suitable for automated conversion.
+}
+
 func registerMutators(ctx *blueprint.Context, preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{}
 
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/packaging.go b/android/packaging.go
new file mode 100644
index 0000000..09432e6
--- /dev/null
+++ b/android/packaging.go
@@ -0,0 +1,205 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License")
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"github.com/google/blueprint"
+)
+
+// 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
+}
+
+// Get file name of installed package
+func (p *PackagingSpec) FileName() string {
+	if p.relPathInPackage != "" {
+		return filepath.Base(p.relPathInPackage)
+	}
+
+	return ""
+}
+
+type PackageModule interface {
+	Module
+	packagingBase() *PackagingBase
+
+	// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
+	// When adding the dependencies, depTag is used as the tag.
+	AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
+
+	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
+	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
+	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
+	// etc.) from the extracted files
+	CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string
+}
+
+// PackagingBase provides basic functionality for packaging dependencies. A module is expected to
+// include this struct and call InitPackageModule.
+type PackagingBase struct {
+	properties PackagingProperties
+
+	// Allows this module to skip missing dependencies. In most cases, this is not required, but
+	// for rare cases like when there's a dependency to a module which exists in certain repo
+	// checkouts, this is needed.
+	IgnoreMissingDependencies bool
+}
+
+type depsProperty struct {
+	// Modules to include in this package
+	Deps []string `android:"arch_variant"`
+}
+
+type packagingMultilibProperties struct {
+	First  depsProperty `android:"arch_variant"`
+	Common depsProperty `android:"arch_variant"`
+	Lib32  depsProperty `android:"arch_variant"`
+	Lib64  depsProperty `android:"arch_variant"`
+}
+
+type PackagingProperties struct {
+	Deps     []string                    `android:"arch_variant"`
+	Multilib packagingMultilibProperties `android:"arch_variant"`
+}
+
+func InitPackageModule(p PackageModule) {
+	base := p.packagingBase()
+	p.AddProperties(&base.properties)
+}
+
+func (p *PackagingBase) packagingBase() *PackagingBase {
+	return p
+}
+
+// From deps and multilib.*.deps, select the dependencies that are for the given arch deps is for
+// the current archicture when this module is not configured for multi target. When configured for
+// multi target, deps is selected for each of the targets and is NOT selected for the current
+// architecture which would be Common.
+func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
+	var ret []string
+	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
+		ret = append(ret, p.properties.Deps...)
+	} else if arch.Multilib == "lib32" {
+		ret = append(ret, p.properties.Multilib.Lib32.Deps...)
+	} else if arch.Multilib == "lib64" {
+		ret = append(ret, p.properties.Multilib.Lib64.Deps...)
+	} else if arch == Common {
+		ret = append(ret, p.properties.Multilib.Common.Deps...)
+	}
+	for i, t := range ctx.MultiTargets() {
+		if t.Arch.ArchType == arch {
+			ret = append(ret, p.properties.Deps...)
+			if i == 0 {
+				ret = append(ret, p.properties.Multilib.First.Deps...)
+			}
+		}
+	}
+	return FirstUniqueStrings(ret)
+}
+
+func (p *PackagingBase) getSupportedTargets(ctx BaseModuleContext) []Target {
+	var ret []Target
+	// The current and the common OS targets are always supported
+	ret = append(ret, ctx.Target())
+	if ctx.Arch().ArchType != Common {
+		ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}})
+	}
+	// If this module is configured for multi targets, those should be supported as well
+	ret = append(ret, ctx.MultiTargets()...)
+	return ret
+}
+
+// See PackageModule.AddDeps
+func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
+	for _, t := range p.getSupportedTargets(ctx) {
+		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
+			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
+				continue
+			}
+			ctx.AddFarVariationDependencies(t.Variations(), depTag, dep)
+		}
+	}
+}
+
+// See PackageModule.CopyDepsToZip
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
+	m := make(map[string]PackagingSpec)
+	ctx.WalkDeps(func(child Module, parent Module) bool {
+		if !IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
+			return false
+		}
+		for _, ps := range child.PackagingSpecs() {
+			if _, ok := m[ps.relPathInPackage]; !ok {
+				m[ps.relPathInPackage] = ps
+			}
+		}
+		return true
+	})
+
+	builder := NewRuleBuilder(pctx, ctx)
+
+	dir := PathForModuleOut(ctx, ".zip").OutputPath
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
+
+	seenDir := make(map[string]bool)
+	for _, k := range SortedStringKeys(m) {
+		ps := m[k]
+		destPath := dir.Join(ctx, ps.relPathInPackage).String()
+		destDir := filepath.Dir(destPath)
+		entries = append(entries, ps.relPathInPackage)
+		if _, ok := seenDir[destDir]; !ok {
+			seenDir[destDir] = true
+			builder.Command().Text("mkdir").Flag("-p").Text(destDir)
+		}
+		if ps.symlinkTarget == "" {
+			builder.Command().Text("cp").Input(ps.srcPath).Text(destPath)
+		} else {
+			builder.Command().Text("ln").Flag("-sf").Text(ps.symlinkTarget).Text(destPath)
+		}
+		if ps.executable {
+			builder.Command().Text("chmod").Flag("a+x").Text(destPath)
+		}
+	}
+
+	builder.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", zipOut).
+		FlagWithArg("-C ", dir.String()).
+		Flag("-L 0"). // no compression because this will be unzipped soon
+		FlagWithArg("-D ", dir.String())
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+
+	builder.Build("zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName()))
+	return entries
+}
diff --git a/android/packaging_test.go b/android/packaging_test.go
new file mode 100644
index 0000000..7269bfb
--- /dev/null
+++ b/android/packaging_test.go
@@ -0,0 +1,196 @@
+// 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 (
+	"reflect"
+	"testing"
+
+	"github.com/google/blueprint"
+)
+
+// Module to be packaged
+type componentTestModule struct {
+	ModuleBase
+	props struct {
+		Deps []string
+	}
+}
+
+// dep tag used in this test. All dependencies are considered as installable.
+type installDepTag struct {
+	blueprint.BaseDependencyTag
+	InstallAlwaysNeededDependencyTag
+}
+
+func componentTestModuleFactory() Module {
+	m := &componentTestModule{}
+	m.AddProperties(&m.props)
+	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibBoth)
+	return m
+}
+
+func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
+}
+
+func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	builtFile := PathForModuleOut(ctx, m.Name())
+	dir := ctx.Target().Arch.ArchType.Multilib
+	installDir := PathForModuleInstall(ctx, dir)
+	ctx.InstallFile(installDir, m.Name(), builtFile)
+}
+
+// Module that itself is a package
+type packageTestModule struct {
+	ModuleBase
+	PackagingBase
+
+	entries []string
+}
+
+func packageTestModuleFactory() Module {
+	module := &packageTestModule{}
+	InitPackageModule(module)
+	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
+
+func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
+	m.AddDeps(ctx, installDepTag{})
+}
+
+func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	zipFile := PathForModuleOut(ctx, "myzip.zip").OutputPath
+	m.entries = m.CopyDepsToZip(ctx, zipFile)
+}
+
+func runPackagingTest(t *testing.T, bp string, expected []string) {
+	t.Helper()
+
+	config := TestArchConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestArchContext(config)
+	ctx.RegisterModuleType("component", componentTestModuleFactory)
+	ctx.RegisterModuleType("package_module", packageTestModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	p := ctx.ModuleForTests("package", "android_common").Module().(*packageTestModule)
+	actual := p.entries
+	actual = SortedUniqueStrings(actual)
+	expected = SortedUniqueStrings(expected)
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
+	}
+}
+
+func TestPackagingBase(t *testing.T) {
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+			compile_multilib: "32",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				lib32: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				first: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
+}
diff --git a/android/path_properties.go b/android/path_properties.go
index 6b1cdb3..4bb706a 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -21,27 +21,31 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// This file implements support for automatically adding dependencies on any module referenced
+// with the ":module" module reference syntax in a property that is annotated with `android:"path"`.
+// The dependency is used by android.PathForModuleSrc to convert the module reference into the path
+// to the output file of the referenced module.
+
 func registerPathDepsMutator(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("pathdeps", pathDepsMutator).Parallel()
 }
 
-// The pathDepsMutator automatically adds dependencies on any module that is listed with ":module" syntax in a
-// property that is tagged with android:"path".
+// The pathDepsMutator automatically adds dependencies on any module that is listed with the
+// ":module" module reference syntax in a property that is tagged with `android:"path"`.
 func pathDepsMutator(ctx BottomUpMutatorContext) {
-	m := ctx.Module().(Module)
-	if m == nil {
-		return
-	}
+	props := ctx.Module().base().generalProperties
 
-	props := m.base().generalProperties
-
+	// Iterate through each property struct of the module extracting the contents of all properties
+	// tagged with `android:"path"`.
 	var pathProperties []string
 	for _, ps := range props {
-		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ctx, ps)...)
+		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ps)...)
 	}
 
+	// Remove duplicates to avoid multiple dependencies.
 	pathProperties = FirstUniqueStrings(pathProperties)
 
+	// Add dependencies to anything that is a module reference.
 	for _, s := range pathProperties {
 		if m, t := SrcIsModuleWithTag(s); m != "" {
 			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
@@ -49,34 +53,45 @@
 	}
 }
 
-// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with android:"path" to extract
-// all their values from a property struct, returning them as a single slice of strings..
-func pathPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}) []string {
+// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with
+// android:"path" to extract all their values from a property struct, returning them as a single
+// slice of strings.
+func pathPropertiesForPropertyStruct(ps interface{}) []string {
 	v := reflect.ValueOf(ps)
 	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
 		panic(fmt.Errorf("type %s is not a pointer to a struct", v.Type()))
 	}
+
+	// If the property struct is a nil pointer it can't have any paths set in it.
 	if v.IsNil() {
 		return nil
 	}
+
+	// v is now the reflect.Value for the concrete property struct.
 	v = v.Elem()
 
+	// Get or create the list of indexes of properties that are tagged with `android:"path"`.
 	pathPropertyIndexes := pathPropertyIndexesForPropertyStruct(ps)
 
 	var ret []string
 
 	for _, i := range pathPropertyIndexes {
+		// Turn an index into a field.
 		sv := fieldByIndex(v, i)
 		if !sv.IsValid() {
+			// Skip properties inside a nil pointer.
 			continue
 		}
 
+		// If the field is a non-nil pointer step into it.
 		if sv.Kind() == reflect.Ptr {
 			if sv.IsNil() {
 				continue
 			}
 			sv = sv.Elem()
 		}
+
+		// Collect paths from all strings and slices of strings.
 		switch sv.Kind() {
 		case reflect.String:
 			ret = append(ret, sv.String())
@@ -91,8 +106,8 @@
 	return ret
 }
 
-// fieldByIndex is like reflect.Value.FieldByIndex, but returns an invalid reflect.Value when traversing a nil pointer
-// to a struct.
+// fieldByIndex is like reflect.Value.FieldByIndex, but returns an invalid reflect.Value when
+// traversing a nil pointer to a struct.
 func fieldByIndex(v reflect.Value, index []int) reflect.Value {
 	if len(index) == 1 {
 		return v.Field(index[0])
@@ -111,9 +126,9 @@
 
 var pathPropertyIndexesCache OncePer
 
-// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in property struct type that
-// are tagged with android:"path".  Each index is a []int suitable for passing to reflect.Value.FieldByIndex.  The value
-// is cached in a global cache by type.
+// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in
+// property struct type that are tagged with `android:"path"`.  Each index is a []int suitable for
+// passing to reflect.Value.FieldByIndex.  The value is cached in a global cache by type.
 func pathPropertyIndexesForPropertyStruct(ps interface{}) [][]int {
 	key := NewCustomOnceKey(reflect.TypeOf(ps))
 	return pathPropertyIndexesCache.Once(key, func() interface{} {
diff --git a/android/paths.go b/android/paths.go
index b13979d..5a41cf1 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -138,6 +138,8 @@
 	// the writablePath method doesn't directly do anything,
 	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
 	writablePath()
+
+	ReplaceExtension(ctx PathContext, ext string) OutputPath
 }
 
 type genPathProvider interface {
@@ -665,7 +667,7 @@
 	return Paths(ret)
 }
 
-// WritablePaths is a slice of WritablePaths, used for multiple outputs.
+// WritablePaths is a slice of WritablePath, used for multiple outputs.
 type WritablePaths []WritablePath
 
 // Strings returns the string forms of the writable paths.
@@ -1249,6 +1251,10 @@
 	return p.config.buildDir
 }
 
+func (p InstallPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
+	panic("Not implemented")
+}
+
 var _ Path = InstallPath{}
 var _ WritablePath = InstallPath{}
 
@@ -1312,7 +1318,7 @@
 
 	ret := pathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
 
-	if ctx.InstallBypassMake() && ctx.Config().EmbeddedInMake() {
+	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
 		ret = ret.ToMakePath()
 	}
 
@@ -1511,6 +1517,10 @@
 	return p.config.buildDir
 }
 
+func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
+	panic("Not implemented")
+}
+
 var _ Path = PhonyPath{}
 var _ WritablePath = PhonyPath{}
 
diff --git a/android/phony.go b/android/phony.go
index f8e5a44..0adbb55 100644
--- a/android/phony.go
+++ b/android/phony.go
@@ -53,7 +53,7 @@
 		p.phonyMap[phony] = SortedUniquePaths(p.phonyMap[phony])
 	}
 
-	if !ctx.Config().EmbeddedInMake() {
+	if !ctx.Config().KatiEnabled() {
 		for _, phony := range p.phonyList {
 			ctx.Build(pctx, BuildParams{
 				Rule:      blueprint.Phony,
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 294a6e0..bb98ed4 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -178,6 +178,9 @@
 	srcPropertyName := proptools.PropertyNameForField(srcField)
 
 	srcsSupplier := func(ctx BaseModuleContext) []string {
+		if !module.Enabled() {
+			return nil
+		}
 		value := srcPropsValue.FieldByIndex(srcFieldIndex)
 		if value.Kind() == reflect.Ptr {
 			value = value.Elem()
diff --git a/android/proto.go b/android/proto.go
index b712258..0be7893 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -122,7 +122,7 @@
 	} `android:"arch_variant"`
 }
 
-func ProtoRule(ctx ModuleContext, rule *RuleBuilder, protoFile Path, flags ProtoFlags, deps Paths,
+func ProtoRule(rule *RuleBuilder, protoFile Path, flags ProtoFlags, deps Paths,
 	outDir WritablePath, depFile WritablePath, outputs WritablePaths) {
 
 	var protoBase string
@@ -134,7 +134,7 @@
 	}
 
 	rule.Command().
-		BuiltTool(ctx, "aprotoc").
+		BuiltTool("aprotoc").
 		FlagWithArg(flags.OutTypeFlag+"=", strings.Join(flags.OutParams, ",")+":"+outDir.String()).
 		FlagWithDepFile("--dependency_out=", depFile).
 		FlagWithArg("-I ", protoBase).
@@ -144,5 +144,5 @@
 		ImplicitOutputs(outputs)
 
 	rule.Command().
-		BuiltTool(ctx, "dep_fixer").Flag(depFile.String())
+		BuiltTool("dep_fixer").Flag(depFile.String())
 }
diff --git a/android/queryview.go b/android/queryview.go
index 970ae01..1b7e77d 100644
--- a/android/queryview.go
+++ b/android/queryview.go
@@ -26,15 +26,40 @@
 // for calling the soong_build primary builder in the main build.ninja file.
 func init() {
 	RegisterSingletonType("bazel_queryview", BazelQueryViewSingleton)
+	RegisterSingletonType("bazel_converter", BazelConverterSingleton)
 }
 
+// BazelQueryViewSingleton is the singleton responsible for registering the
+// soong_build build statement that will convert the Soong module graph after
+// applying *all* mutators, enabing the feature to query the final state of the
+// Soong graph. This mode is meant for querying the build graph state, and not meant
+// for generating BUILD files to be checked in.
 func BazelQueryViewSingleton() Singleton {
 	return &bazelQueryViewSingleton{}
 }
 
-type bazelQueryViewSingleton struct{}
+// BazelConverterSingleton is the singleton responsible for registering the soong_build
+// build statement that will convert the Soong module graph by applying an alternate
+// pipeline of mutators, with the goal of reaching semantic equivalence between the original
+// Blueprint and final BUILD files. Using this mode, the goal is to be able to
+// build with these BUILD files directly in the source tree.
+func BazelConverterSingleton() Singleton {
+	return &bazelConverterSingleton{}
+}
 
-func (c *bazelQueryViewSingleton) GenerateBuildActions(ctx SingletonContext) {
+type bazelQueryViewSingleton struct{}
+type bazelConverterSingleton struct{}
+
+func generateBuildActionsForBazelConversion(ctx SingletonContext, converterMode bool) {
+	name := "queryview"
+	additionalEnvVars := ""
+	descriptionTemplate := "[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir"
+	if converterMode {
+		name = "bp2build"
+		additionalEnvVars = "CONVERT_TO_BAZEL=true"
+		descriptionTemplate = "[EXPERIMENTAL, PRE-PRODUCTION] Converting all Android.bp to Bazel BUILD files with %s at $outDir"
+	}
+
 	// Create a build and rule statement, using the Bazel QueryView's WORKSPACE
 	// file as the output file marker.
 	var deps Paths
@@ -42,22 +67,23 @@
 	deps = append(deps, moduleListFilePath)
 	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().ProductVariablesFileName))
 
-	bazelQueryViewDirectory := PathForOutput(ctx, "queryview")
+	bazelQueryViewDirectory := PathForOutput(ctx, name)
 	bazelQueryViewWorkspaceFile := bazelQueryViewDirectory.Join(ctx, "WORKSPACE")
 	primaryBuilder := primaryBuilderPath(ctx)
 	bazelQueryView := ctx.Rule(pctx, "bazelQueryView",
 		blueprint.RuleParams{
 			Command: fmt.Sprintf(
 				"rm -rf ${outDir}/* && "+
-					"%s --bazel_queryview_dir ${outDir} %s && "+
+					"%s %s --bazel_queryview_dir ${outDir} %s && "+
 					"echo WORKSPACE: `cat %s` > ${outDir}/.queryview-depfile.d",
+				additionalEnvVars,
 				primaryBuilder.String(),
 				strings.Join(os.Args[1:], " "),
 				moduleListFilePath.String(), // Use the contents of Android.bp.list as the depfile.
 			),
 			CommandDeps: []string{primaryBuilder.String()},
 			Description: fmt.Sprintf(
-				"[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir",
+				descriptionTemplate,
 				primaryBuilder.Base()),
 			Deps:    blueprint.DepsGCC,
 			Depfile: "${outDir}/.queryview-depfile.d",
@@ -73,6 +99,14 @@
 		},
 	})
 
-	// Add a phony target for building the Bazel QueryView
-	ctx.Phony("queryview", bazelQueryViewWorkspaceFile)
+	// Add a phony target for generating the workspace
+	ctx.Phony(name, bazelQueryViewWorkspaceFile)
+}
+
+func (c *bazelQueryViewSingleton) GenerateBuildActions(ctx SingletonContext) {
+	generateBuildActionsForBazelConversion(ctx, false)
+}
+
+func (c *bazelConverterSingleton) GenerateBuildActions(ctx SingletonContext) {
+	generateBuildActionsForBazelConversion(ctx, true)
 }
diff --git a/android/register.go b/android/register.go
index bd824c9..b26f9b9 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,11 +72,11 @@
 }
 
 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 {
@@ -90,9 +90,24 @@
 	return ctx
 }
 
+// RegisterForBazelConversion registers an alternate shadow pipeline of
+// singletons, module types and mutators to register for converting Blueprint
+// files to semantically equivalent BUILD files.
+func (ctx *Context) RegisterForBazelConversion() {
+	for _, t := range moduleTypes {
+		ctx.RegisterModuleType(t.name, ModuleFactoryAdaptor(t.factory))
+	}
+
+	bazelConverterSingleton := singleton{"bp2build", BazelConverterSingleton}
+	ctx.RegisterSingletonType(bazelConverterSingleton.name,
+		SingletonFactoryAdaptor(ctx, bazelConverterSingleton.factory))
+
+	registerMutatorsForBazelConversion(ctx.Context)
+}
+
 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 {
@@ -100,21 +115,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 {
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 8dc9d6a..e2d8187 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -15,33 +15,48 @@
 package android
 
 import (
+	"crypto/sha256"
 	"fmt"
+	"path/filepath"
 	"sort"
 	"strings"
+	"testing"
 
+	"github.com/golang/protobuf/proto"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
+	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/shared"
 )
 
+const sboxSandboxBaseDir = "__SBOX_SANDBOX_DIR__"
+const sboxOutSubDir = "out"
+const sboxOutDir = sboxSandboxBaseDir + "/" + sboxOutSubDir
+
 // RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
 // graph.
 type RuleBuilder struct {
-	commands       []*RuleBuilderCommand
-	installs       RuleBuilderInstalls
-	temporariesSet map[WritablePath]bool
-	restat         bool
-	sbox           bool
-	highmem        bool
-	remoteable     RemoteRuleSupports
-	sboxOutDir     WritablePath
-	missingDeps    []string
+	pctx PackageContext
+	ctx  BuilderContext
+
+	commands         []*RuleBuilderCommand
+	installs         RuleBuilderInstalls
+	temporariesSet   map[WritablePath]bool
+	restat           bool
+	sbox             bool
+	highmem          bool
+	remoteable       RemoteRuleSupports
+	outDir           WritablePath
+	sboxManifestPath WritablePath
+	missingDeps      []string
 }
 
 // NewRuleBuilder returns a newly created RuleBuilder.
-func NewRuleBuilder() *RuleBuilder {
+func NewRuleBuilder(pctx PackageContext, ctx BuilderContext) *RuleBuilder {
 	return &RuleBuilder{
+		pctx:           pctx,
+		ctx:            ctx,
 		temporariesSet: make(map[WritablePath]bool),
 	}
 }
@@ -102,12 +117,14 @@
 	return r
 }
 
-// Sbox marks the rule as needing to be wrapped by sbox. The WritablePath should point to the output
-// directory that sbox will wipe. It should not be written to by any other rule. sbox will ensure
-// that all outputs have been written, and will discard any output files that were not specified.
+// Sbox marks the rule as needing to be wrapped by sbox. The outputDir should point to the output
+// directory that sbox will wipe. It should not be written to by any other rule. manifestPath should
+// point to a location where sbox's manifest will be written and must be outside outputDir. sbox
+// will ensure that all outputs have been written, and will discard any output files that were not
+// specified.
 //
 // Sbox is not compatible with Restat()
-func (r *RuleBuilder) Sbox(outputDir WritablePath) *RuleBuilder {
+func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
 	if r.sbox {
 		panic("Sbox() may not be called more than once")
 	}
@@ -118,7 +135,8 @@
 		panic("Sbox() is not compatible with Restat()")
 	}
 	r.sbox = true
-	r.sboxOutDir = outputDir
+	r.outDir = outputDir
+	r.sboxManifestPath = manifestPath
 	return r
 }
 
@@ -133,8 +151,7 @@
 // race with any call to Build.
 func (r *RuleBuilder) Command() *RuleBuilderCommand {
 	command := &RuleBuilderCommand{
-		sbox:       r.sbox,
-		sboxOutDir: r.sboxOutDir,
+		rule: r,
 	}
 	r.commands = append(r.commands, command)
 	return command
@@ -163,7 +180,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 +379,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
@@ -382,19 +399,19 @@
 var _ BuilderContext = ModuleContext(nil)
 var _ BuilderContext = SingletonContext(nil)
 
-func (r *RuleBuilder) depFileMergerCmd(ctx PathContext, depFiles WritablePaths) *RuleBuilderCommand {
+func (r *RuleBuilder) depFileMergerCmd(depFiles WritablePaths) *RuleBuilderCommand {
 	return r.Command().
-		BuiltTool(ctx, "dep_fixer").
+		BuiltTool("dep_fixer").
 		Inputs(depFiles.Paths())
 }
 
 // Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
 // Outputs.
-func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string, desc string) {
+func (r *RuleBuilder) Build(name string, desc string) {
 	name = ninjaNameEscape(name)
 
 	if len(r.missingDeps) > 0 {
-		ctx.Build(pctx, BuildParams{
+		r.ctx.Build(pctx, BuildParams{
 			Rule:        ErrorRule,
 			Outputs:     r.Outputs(),
 			OrderOnly:   r.OrderOnlys(),
@@ -413,12 +430,13 @@
 		depFormat = blueprint.DepsGCC
 		if len(depFiles) > 1 {
 			// Add a command locally that merges all depfiles together into the first depfile.
-			r.depFileMergerCmd(ctx, depFiles)
+			r.depFileMergerCmd(depFiles)
 
 			if r.sbox {
-				// Check for Rel() errors, as all depfiles should be in the output dir
+				// Check for Rel() errors, as all depfiles should be in the output dir.  Errors
+				// will be reported to the ctx.
 				for _, path := range depFiles[1:] {
-					Rel(ctx, r.sboxOutDir.String(), path.String())
+					Rel(r.ctx, r.outDir.String(), path.String())
 				}
 			}
 		}
@@ -427,6 +445,7 @@
 	tools := r.Tools()
 	commands := r.NinjaEscapedCommands()
 	outputs := r.Outputs()
+	inputs := r.Inputs()
 
 	if len(commands) == 0 {
 		return
@@ -438,30 +457,69 @@
 	commandString := strings.Join(commands, " && ")
 
 	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())
-		}
-
-		commandString = proptools.ShellEscape(commandString)
-		if !strings.HasPrefix(commandString, `'`) {
-			commandString = `'` + commandString + `'`
-		}
-
-		sboxCmd := &RuleBuilderCommand{}
-		sboxCmd.BuiltTool(ctx, "sbox").
-			Flag("-c").Text(commandString).
-			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
-			Flag("--output-root").Text(r.sboxOutDir.String())
+		// If running the command inside sbox, write the rule data out to an sbox
+		// manifest.textproto.
+		manifest := sbox_proto.Manifest{}
+		command := sbox_proto.Command{}
+		manifest.Commands = append(manifest.Commands, &command)
+		command.Command = proto.String(commandString)
 
 		if depFile != nil {
-			sboxCmd.Flag("--depfile-out").Text(depFile.String())
+			manifest.OutputDepfile = proto.String(depFile.String())
 		}
 
-		sboxCmd.Flags(sboxOutputs)
+		// Add copy rules to the manifest to copy each output file from the sbox directory.
+		// to the output directory.
+		sboxOutputs := make([]string, len(outputs))
+		for i, output := range outputs {
+			rel := Rel(r.ctx, r.outDir.String(), output.String())
+			sboxOutputs[i] = filepath.Join(sboxOutDir, rel)
+			command.CopyAfter = append(command.CopyAfter, &sbox_proto.Copy{
+				From: proto.String(filepath.Join(sboxOutSubDir, rel)),
+				To:   proto.String(output.String()),
+			})
+		}
 
+		// Add a hash of the list of input files to the manifest so that the textproto file
+		// changes when the list of input files changes and causes the sbox rule that
+		// depends on it to rerun.
+		command.InputHash = proto.String(hashSrcFiles(inputs))
+
+		// Verify that the manifest textproto is not inside the sbox output directory, otherwise
+		// it will get deleted when the sbox rule clears its output directory.
+		_, manifestInOutDir := MaybeRel(r.ctx, r.outDir.String(), r.sboxManifestPath.String())
+		if manifestInOutDir {
+			ReportPathErrorf(r.ctx, "sbox rule %q manifestPath %q must not be in outputDir %q",
+				name, r.sboxManifestPath.String(), r.outDir.String())
+		}
+
+		// Create a rule to write the manifest as a the textproto.
+		WriteFileRule(r.ctx, r.sboxManifestPath, proto.MarshalTextString(&manifest))
+
+		// Generate a new string to use as the command line of the sbox rule.  This uses
+		// a RuleBuilderCommand as a convenience method of building the command line, then
+		// converts it to a string to replace commandString.
+		sboxCmd := &RuleBuilderCommand{
+			rule: &RuleBuilder{
+				ctx: r.ctx,
+			},
+		}
+		sboxCmd.Text("rm -rf").Output(r.outDir)
+		sboxCmd.Text("&&")
+		sboxCmd.BuiltTool("sbox").
+			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
+			Flag("--manifest").Input(r.sboxManifestPath)
+
+		// Replace the command string, and add the sbox tool and manifest textproto to the
+		// dependencies of the final sbox rule.
 		commandString = sboxCmd.buf.String()
 		tools = append(tools, sboxCmd.tools...)
+		inputs = append(inputs, sboxCmd.inputs...)
+	} 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
@@ -478,19 +536,19 @@
 	}
 
 	var pool blueprint.Pool
-	if ctx.Config().UseGoma() && r.remoteable.Goma {
+	if r.ctx.Config().UseGoma() && r.remoteable.Goma {
 		// When USE_GOMA=true is set and the rule is supported by goma, allow jobs to run outside the local pool.
-	} else if ctx.Config().UseRBE() && r.remoteable.RBE {
+	} else if r.ctx.Config().UseRBE() && r.remoteable.RBE {
 		// When USE_RBE=true is set and the rule is supported by RBE, use the remotePool.
 		pool = remotePool
 	} else if r.highmem {
 		pool = highmemPool
-	} else if ctx.Config().UseRemoteBuild() {
+	} else if r.ctx.Config().UseRemoteBuild() {
 		pool = localPool
 	}
 
-	ctx.Build(pctx, BuildParams{
-		Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
+	r.ctx.Build(r.pctx, BuildParams{
+		Rule: r.ctx.Rule(pctx, name, blueprint.RuleParams{
 			Command:        commandString,
 			CommandDeps:    tools.Strings(),
 			Restat:         r.restat,
@@ -499,7 +557,7 @@
 			Pool:           pool,
 		}),
 		Inputs:          rspFileInputs,
-		Implicits:       r.Inputs(),
+		Implicits:       inputs,
 		Output:          output,
 		ImplicitOutputs: implicitOutputs,
 		SymlinkOutputs:  r.SymlinkOutputs(),
@@ -514,6 +572,8 @@
 // RuleBuilderCommand, so they can be used chained or unchained.  All methods that add text implicitly add a single
 // space as a separator from the previous method.
 type RuleBuilderCommand struct {
+	rule *RuleBuilder
+
 	buf            strings.Builder
 	inputs         Paths
 	implicits      Paths
@@ -526,15 +586,12 @@
 
 	// spans [start,end) of the command that should not be ninja escaped
 	unescapedSpans [][2]int
-
-	sbox       bool
-	sboxOutDir 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 c.rule.sbox {
+		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.inputs = append(c.inputs, path)
@@ -542,9 +599,9 @@
 }
 
 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 c.rule.sbox {
+		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.implicits = append(c.implicits, path)
@@ -555,11 +612,15 @@
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
-func (c *RuleBuilderCommand) outputStr(path Path) string {
-	if c.sbox {
+// PathForOutput takes an output path and returns the appropriate path to use on the command
+// line.  If sbox was enabled via a call to RuleBuilder.Sbox(), it returns a path with the
+// placeholder prefix used for outputs in sbox.  If sbox is not enabled it returns the
+// original path.
+func (c *RuleBuilderCommand) PathForOutput(path WritablePath) string {
+	if c.rule.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
+		rel, _, _ := maybeRelErr(c.rule.outDir.String(), path.String())
+		return filepath.Join(sboxOutDir, rel)
 	}
 	return path.String()
 }
@@ -640,8 +701,8 @@
 //
 // It is equivalent to:
 //  cmd.Tool(ctx.Config().HostToolPath(ctx, tool))
-func (c *RuleBuilderCommand) BuiltTool(ctx PathContext, tool string) *RuleBuilderCommand {
-	return c.Tool(ctx.Config().HostToolPath(ctx, tool))
+func (c *RuleBuilderCommand) BuiltTool(tool string) *RuleBuilderCommand {
+	return c.Tool(c.rule.ctx.Config().HostToolPath(c.rule.ctx, tool))
 }
 
 // PrebuiltBuildTool adds the specified tool path from prebuils/build-tools.  The path will be also added to the
@@ -709,7 +770,7 @@
 // RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
 	c.outputs = append(c.outputs, path)
-	return c.Text(c.outputStr(path))
+	return c.Text(c.PathForOutput(path))
 }
 
 // Outputs adds the specified output paths to the command line, separated by spaces.  The paths will also be added to
@@ -724,10 +785,10 @@
 // OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox,
 // and will be the temporary output directory managed by sbox, not the final one.
 func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand {
-	if !c.sbox {
+	if !c.rule.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
@@ -735,7 +796,7 @@
 // commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
 func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
 	c.depFiles = append(c.depFiles, path)
-	return c.Text(c.outputStr(path))
+	return c.Text(c.PathForOutput(path))
 }
 
 // ImplicitOutput adds the specified output path to the dependencies returned by RuleBuilder.Outputs without modifying
@@ -826,14 +887,14 @@
 // will also be added to the outputs returned by RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) FlagWithOutput(flag string, path WritablePath) *RuleBuilderCommand {
 	c.outputs = append(c.outputs, path)
-	return c.Text(flag + c.outputStr(path))
+	return c.Text(flag + c.PathForOutput(path))
 }
 
 // FlagWithDepFile adds the specified flag and depfile path to the command line, with no separator between them.  The path
 // will also be added to the outputs returned by RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *RuleBuilderCommand {
 	c.depFiles = append(c.depFiles, path)
-	return c.Text(flag + c.outputStr(path))
+	return c.Text(flag + c.PathForOutput(path))
 }
 
 // FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
@@ -867,6 +928,19 @@
 	return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
 }
 
+// RuleBuilderSboxProtoForTests takes the BuildParams for the manifest passed to RuleBuilder.Sbox()
+// and returns sbox testproto generated by the RuleBuilder.
+func RuleBuilderSboxProtoForTests(t *testing.T, params TestingBuildParams) *sbox_proto.Manifest {
+	t.Helper()
+	content := ContentFromFileRuleForTests(t, params)
+	manifest := sbox_proto.Manifest{}
+	err := proto.UnmarshalText(content, &manifest)
+	if err != nil {
+		t.Fatalf("failed to unmarshal manifest: %s", err.Error())
+	}
+	return &manifest
+}
+
 func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
 	if len(spans) == 0 {
 		return proptools.NinjaEscape(s)
@@ -906,3 +980,30 @@
 	}
 	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))
+}
+
+// BuilderContextForTesting returns a BuilderContext for the given config that can be used for tests
+// that need to call methods that take a BuilderContext.
+func BuilderContextForTesting(config Config) BuilderContext {
+	pathCtx := PathContextForTesting(config)
+	return builderContextForTests{
+		PathContext: pathCtx,
+	}
+}
+
+type builderContextForTests struct {
+	PathContext
+}
+
+func (builderContextForTests) Rule(PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule {
+	return nil
+}
+func (builderContextForTests) Build(PackageContext, BuildParams) {}
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index ca6359d..e676e4a 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -26,8 +27,8 @@
 	"android/soong/shared"
 )
 
-func pathContext() PathContext {
-	return PathContextForTesting(TestConfig("out", nil, "", map[string][]byte{
+func builderContext() BuilderContext {
+	return BuilderContextForTesting(TestConfig("out", nil, "", map[string][]byte{
 		"ld":      nil,
 		"a.o":     nil,
 		"b.o":     nil,
@@ -43,9 +44,9 @@
 }
 
 func ExampleRuleBuilder() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "ld")).
@@ -54,7 +55,7 @@
 	rule.Command().Text("echo success")
 
 	// To add the command to the build graph:
-	// rule.Build(pctx, ctx, "link", "link")
+	// rule.Build("link", "link")
 
 	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
 	fmt.Printf("tools: %q\n", rule.Tools())
@@ -69,9 +70,9 @@
 }
 
 func ExampleRuleBuilder_SymlinkOutputs() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "ln")).
@@ -95,9 +96,9 @@
 }
 
 func ExampleRuleBuilder_Temporary() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "cp")).
@@ -122,9 +123,9 @@
 }
 
 func ExampleRuleBuilder_DeleteTemporaryFiles() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "cp")).
@@ -150,9 +151,9 @@
 }
 
 func ExampleRuleBuilder_Installs() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	out := PathForOutput(ctx, "linked")
 
@@ -170,9 +171,9 @@
 }
 
 func ExampleRuleBuilderCommand() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	// chained
 	rule.Command().
@@ -193,24 +194,24 @@
 }
 
 func ExampleRuleBuilderCommand_Flag() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "ls")).Flag("-l"))
 	// Output:
 	// ls -l
 }
 
 func ExampleRuleBuilderCommand_Flags() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "ls")).Flags([]string{"-l", "-a"}))
 	// Output:
 	// ls -l -a
 }
 
 func ExampleRuleBuilderCommand_FlagWithArg() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "ls")).
 		FlagWithArg("--sort=", "time"))
 	// Output:
@@ -218,8 +219,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagForEachArg() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "ls")).
 		FlagForEachArg("--sort=", []string{"time", "size"}))
 	// Output:
@@ -227,8 +228,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagForEachInput() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "turbine")).
 		FlagForEachInput("--classpath ", PathsForTesting("a.jar", "b.jar")))
 	// Output:
@@ -236,8 +237,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagWithInputList() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "java")).
 		FlagWithInputList("-classpath=", PathsForTesting("a.jar", "b.jar"), ":"))
 	// Output:
@@ -245,8 +246,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagWithInput() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "java")).
 		FlagWithInput("-classpath=", PathForSource(ctx, "a")))
 	// Output:
@@ -254,8 +255,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagWithList() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "ls")).
 		FlagWithList("--sort=", []string{"time", "size"}, ","))
 	// Output:
@@ -263,8 +264,8 @@
 }
 
 func ExampleRuleBuilderCommand_FlagWithRspFileInputList() {
-	ctx := pathContext()
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "javac")).
 		FlagWithRspFileInputList("@", PathsForTesting("a.java", "b.java")).
 		NinjaEscapedString())
@@ -273,7 +274,8 @@
 }
 
 func ExampleRuleBuilderCommand_String() {
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Text("FOO=foo").
 		Text("echo $FOO").
 		String())
@@ -282,7 +284,8 @@
 }
 
 func ExampleRuleBuilderCommand_NinjaEscapedString() {
-	fmt.Println(NewRuleBuilder().Command().
+	ctx := builderContext()
+	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Text("FOO=foo").
 		Text("echo $FOO").
 		NinjaEscapedString())
@@ -304,7 +307,10 @@
 		"input3":     nil,
 	}
 
-	ctx := PathContextForTesting(TestConfig("out", nil, "", fs))
+	pathCtx := PathContextForTesting(TestConfig("out", nil, "", fs))
+	ctx := builderContextForTests{
+		PathContext: pathCtx,
+	}
 
 	addCommands := func(rule *RuleBuilder) {
 		cmd := rule.Command().
@@ -354,7 +360,7 @@
 	wantSymlinkOutputs := PathsForOutput(ctx, []string{"ImplicitSymlinkOutput", "SymlinkOutput"})
 
 	t.Run("normal", func(t *testing.T) {
-		rule := NewRuleBuilder()
+		rule := NewRuleBuilder(pctx, ctx)
 		addCommands(rule)
 
 		wantCommands := []string{
@@ -388,22 +394,23 @@
 			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
 		}
 
-		if g, w := rule.depFileMergerCmd(ctx, rule.DepFiles()).String(), wantDepMergerCommand; g != w {
+		if g, w := rule.depFileMergerCmd(rule.DepFiles()).String(), wantDepMergerCommand; g != w {
 			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
 		}
 	})
 
 	t.Run("sbox", func(t *testing.T) {
-		rule := NewRuleBuilder().Sbox(PathForOutput(ctx))
+		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, ""),
+			PathForOutput(ctx, "sbox.textproto"))
 		addCommands(rule)
 
 		wantCommands := []string{
-			"__SBOX_OUT_DIR__/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_OUT_DIR__/depfile FlagWithInput=input FlagWithOutput=__SBOX_OUT_DIR__/output Input __SBOX_OUT_DIR__/Output __SBOX_OUT_DIR__/SymlinkOutput Text Tool after command2 old cmd",
-			"command2 __SBOX_OUT_DIR__/depfile2 input2 __SBOX_OUT_DIR__/output2 tool2",
-			"command3 input3 __SBOX_OUT_DIR__/output2 __SBOX_OUT_DIR__/output3",
+			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output Input __SBOX_SANDBOX_DIR__/out/Output __SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
+			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
+			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3",
 		}
 
-		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_OUT_DIR__/DepFile __SBOX_OUT_DIR__/depfile __SBOX_OUT_DIR__/ImplicitDepFile __SBOX_OUT_DIR__/depfile2"
+		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
 			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
@@ -425,7 +432,7 @@
 			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
 		}
 
-		if g, w := rule.depFileMergerCmd(ctx, rule.DepFiles()).String(), wantDepMergerCommand; g != w {
+		if g, w := rule.depFileMergerCmd(rule.DepFiles()).String(), wantDepMergerCommand; g != w {
 			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
 		}
 	})
@@ -441,7 +448,7 @@
 type testRuleBuilderModule struct {
 	ModuleBase
 	properties struct {
-		Src string
+		Srcs []string
 
 		Restat bool
 		Sbox   bool
@@ -449,12 +456,13 @@
 }
 
 func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	in := PathForSource(ctx, t.properties.Src)
-	out := PathForModuleOut(ctx, ctx.ModuleName())
-	outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
-	outDir := PathForModuleOut(ctx)
+	in := PathsForSource(ctx, t.properties.Srcs)
+	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
+	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
+	outDir := PathForModuleOut(ctx, "gen")
+	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
 
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, t.properties.Restat, t.properties.Sbox)
+	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
 }
 
 type testRuleBuilderSingleton struct{}
@@ -465,26 +473,27 @@
 
 func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
 	in := PathForSource(ctx, "bar")
-	out := PathForOutput(ctx, "baz")
-	outDep := PathForOutput(ctx, "baz.d")
-	outDir := PathForOutput(ctx)
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, true, false)
+	out := PathForOutput(ctx, "singleton/gen/baz")
+	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
+	outDir := PathForOutput(ctx, "singleton/gen")
+	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
+	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir WritablePath, restat, sbox bool) {
-	rule := NewRuleBuilder()
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
+	rule := NewRuleBuilder(pctx, ctx)
 
 	if sbox {
-		rule.Sbox(outDir)
+		rule.Sbox(outDir, manifestPath)
 	}
 
-	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()
 	}
 
-	rule.Build(pctx, ctx, "rule", "desc")
+	rule.Build("rule", "desc")
 }
 
 func TestRuleBuilder_Build(t *testing.T) {
@@ -496,12 +505,12 @@
 	bp := `
 		rule_builder_test {
 			name: "foo",
-			src: "bar",
+			srcs: ["bar"],
 			restat: true,
 		}
 		rule_builder_test {
 			name: "foo_sbox",
-			src: "bar",
+			srcs: ["bar"],
 			sbox: true,
 		}
 	`
@@ -517,9 +526,12 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	FailIfErrored(t, errs)
 
-	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
+	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
 		t.Helper()
-		if params.RuleParams.Command != wantCommand {
+		command := params.RuleParams.Command
+		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
+		command = re.ReplaceAllLiteralString(command, "")
+		if command != wantCommand {
 			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
 		}
 
@@ -532,7 +544,8 @@
 			t.Errorf("want RuleParams.Restat = %v, got %v", wantRestat, params.RuleParams.Restat)
 		}
 
-		if len(params.Implicits) != 1 || params.Implicits[0].String() != "bar" {
+		wantImplicits := append([]string{"bar"}, extraImplicits...)
+		if !reflect.DeepEqual(params.Implicits.Strings(), wantImplicits) {
 			t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
 		}
 
@@ -554,27 +567,29 @@
 	}
 
 	t.Run("module", func(t *testing.T) {
-		outFile := filepath.Join(buildDir, ".intermediates", "foo", "foo")
+		outFile := filepath.Join(buildDir, ".intermediates", "foo", "gen", "foo")
 		check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
 			"cp bar "+outFile,
-			outFile, outFile+".d", true, nil)
+			outFile, outFile+".d", true, nil, nil)
 	})
 	t.Run("sbox", func(t *testing.T) {
 		outDir := filepath.Join(buildDir, ".intermediates", "foo_sbox")
-		outFile := filepath.Join(outDir, "foo_sbox")
-		depFile := filepath.Join(outDir, "foo_sbox.d")
+		outFile := filepath.Join(outDir, "gen/foo_sbox")
+		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join(buildDir, "host", config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir(buildDir)
 
-		cmd := sbox + ` -c 'cp bar __SBOX_OUT_DIR__/foo_sbox' --sandbox-path ` + sandboxPath + " --output-root " + outDir + " --depfile-out " + depFile + " __SBOX_OUT_DIR__/foo_sbox"
+		cmd := `rm -rf ` + outDir + `/gen && ` +
+			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
 
-		check(t, ctx.ModuleForTests("foo_sbox", "").Rule("rule"),
-			cmd, outFile, depFile, false, []string{sbox})
+		check(t, ctx.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
+			cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("singleton", func(t *testing.T) {
-		outFile := filepath.Join(buildDir, "baz")
+		outFile := filepath.Join(buildDir, "singleton/gen/baz")
 		check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
-			"cp bar "+outFile, outFile, outFile+".d", true, nil)
+			"cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
 	})
 }
 
@@ -651,3 +666,80 @@
 		})
 	}
 }
+
+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", "")
+				manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto"))
+				hash := manifest.Commands[0].GetInputHash()
+
+				if g, w := hash, test.expectedHash; g != w {
+					t.Errorf("Expected has %q, got %q", w, g)
+				}
+			})
+			t.Run("", func(t *testing.T) {
+				gen := ctx.ModuleForTests(test.name+"", "")
+				command := gen.Output("gen/" + 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/test_suites.go b/android/test_suites.go
index 19444a8..7ecb8d2 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -63,13 +63,13 @@
 	testCasesDir := pathForInstall(ctx, BuildOs, X86, "testcases", false).ToMakePath()
 
 	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
-	rule := NewRuleBuilder()
-	rule.Command().BuiltTool(ctx, "soong_zip").
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
 		FlagWithArg("-C ", testCasesDir.String()).
 		FlagWithRspFileInputList("-r ", installedPaths.Paths())
-	rule.Build(pctx, ctx, "robolectric_tests_zip", "robolectric-tests.zip")
+	rule.Build("robolectric_tests_zip", "robolectric-tests.zip")
 
 	return outputFile
 }
diff --git a/android/testing.go b/android/testing.go
index d83cecc..6539063 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -104,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 {
@@ -424,8 +424,8 @@
 
 }
 
-func SetInMakeForTests(config Config) {
-	config.inMake = true
+func SetKatiEnabledForTests(config Config) {
+	config.katiEnabled = true
 }
 
 func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) []AndroidMkEntries {
diff --git a/android/util.go b/android/util.go
index 65c5f1b..0f940fa 100644
--- a/android/util.go
+++ b/android/util.go
@@ -29,56 +29,44 @@
 	return append([]string(nil), s...)
 }
 
+// JoinWithPrefix prepends the prefix to each string in the list and
+// returns them joined together with " " as separator.
 func JoinWithPrefix(strs []string, prefix string) string {
 	if len(strs) == 0 {
 		return ""
 	}
 
-	if len(strs) == 1 {
-		return prefix + strs[0]
+	var buf strings.Builder
+	buf.WriteString(prefix)
+	buf.WriteString(strs[0])
+	for i := 1; i < len(strs); i++ {
+		buf.WriteString(" ")
+		buf.WriteString(prefix)
+		buf.WriteString(strs[i])
 	}
-
-	n := len(" ") * (len(strs) - 1)
-	for _, s := range strs {
-		n += len(prefix) + len(s)
-	}
-
-	ret := make([]byte, 0, n)
-	for i, s := range strs {
-		if i != 0 {
-			ret = append(ret, ' ')
-		}
-		ret = append(ret, prefix...)
-		ret = append(ret, s...)
-	}
-	return string(ret)
+	return buf.String()
 }
 
+// JoinWithSuffix appends the suffix to each string in the list and
+// returns them joined together with given separator.
 func JoinWithSuffix(strs []string, suffix string, separator string) string {
 	if len(strs) == 0 {
 		return ""
 	}
 
-	if len(strs) == 1 {
-		return strs[0] + suffix
+	var buf strings.Builder
+	buf.WriteString(strs[0])
+	buf.WriteString(suffix)
+	for i := 1; i < len(strs); i++ {
+		buf.WriteString(separator)
+		buf.WriteString(strs[i])
+		buf.WriteString(suffix)
 	}
-
-	n := len(" ") * (len(strs) - 1)
-	for _, s := range strs {
-		n += len(suffix) + len(s)
-	}
-
-	ret := make([]byte, 0, n)
-	for i, s := range strs {
-		if i != 0 {
-			ret = append(ret, separator...)
-		}
-		ret = append(ret, s...)
-		ret = append(ret, suffix...)
-	}
-	return string(ret)
+	return buf.String()
 }
 
+// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
+// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
 func SortedIntKeys(m interface{}) []int {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -93,6 +81,7 @@
 	return s
 }
 
+// SorterStringKeys returns the keys of the given string-keyed map in the ascending order
 func SortedStringKeys(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -107,6 +96,7 @@
 	return s
 }
 
+// SortedStringMapValues returns the values of the string-values map in the ascending order
 func SortedStringMapValues(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
@@ -121,6 +111,7 @@
 	return s
 }
 
+// IndexList returns the index of the first occurrence of the given string in the list or -1
 func IndexList(s string, list []string) int {
 	for i, l := range list {
 		if l == s {
@@ -131,6 +122,7 @@
 	return -1
 }
 
+// InList checks if the string belongs to the list
 func InList(s string, list []string) bool {
 	return IndexList(s, list) != -1
 }
@@ -176,7 +168,10 @@
 	return -1
 }
 
+// FilterList divides the string list into two lists: one with the strings belonging
+// to the given filter list, and the other with the remaining ones
 func FilterList(list []string, filter []string) (remainder []string, filtered []string) {
+	// InList is O(n). May be worth using more efficient lookup for longer lists.
 	for _, l := range list {
 		if InList(l, filter) {
 			filtered = append(filtered, l)
@@ -188,6 +183,8 @@
 	return
 }
 
+// RemoveListFromList removes the strings belonging to the filter list from the
+// given list and returns the result
 func RemoveListFromList(list []string, filter_out []string) (result []string) {
 	result = make([]string, 0, len(list))
 	for _, l := range list {
@@ -198,20 +195,18 @@
 	return
 }
 
+// RemoveFromList removes given string from the string list.
 func RemoveFromList(s string, list []string) (bool, []string) {
-	i := IndexList(s, list)
-	if i == -1 {
-		return false, list
-	}
-
-	result := make([]string, 0, len(list)-1)
-	result = append(result, list[:i]...)
-	for _, l := range list[i+1:] {
-		if l != s {
-			result = append(result, l)
+	result := make([]string, 0, len(list))
+	var removed bool
+	for _, item := range list {
+		if item != s {
+			result = append(result, item)
+		} else {
+			removed = true
 		}
 	}
-	return true, result
+	return removed, result
 }
 
 // FirstUniqueStrings returns all unique elements of a slice of strings, keeping the first copy of
@@ -317,11 +312,10 @@
 	return s[1], s[2], true
 }
 
+// GetNumericSdkVersion removes the first occurrence of system_ in a string,
+// which is assumed to be something like "system_1.2.3"
 func GetNumericSdkVersion(v string) string {
-	if strings.Contains(v, "system_") {
-		return strings.Replace(v, "system_", "", 1)
-	}
-	return v
+	return strings.Replace(v, "system_", "", 1)
 }
 
 // copied from build/kati/strutil.go
@@ -334,17 +328,17 @@
 		return str
 	}
 	in := str
-	trimed := str
+	trimmed := str
 	if ps[0] != "" {
-		trimed = strings.TrimPrefix(in, ps[0])
-		if trimed == in {
+		trimmed = strings.TrimPrefix(in, ps[0])
+		if trimmed == in {
 			return str
 		}
 	}
-	in = trimed
+	in = trimmed
 	if ps[1] != "" {
-		trimed = strings.TrimSuffix(in, ps[1])
-		if trimed == in {
+		trimmed = strings.TrimSuffix(in, ps[1])
+		if trimmed == in {
 			return str
 		}
 	}
@@ -353,7 +347,7 @@
 	if len(rs) != 2 {
 		return repl
 	}
-	return rs[0] + trimed + rs[1]
+	return rs[0] + trimmed + rs[1]
 }
 
 // copied from build/kati/strutil.go
@@ -407,6 +401,23 @@
 	return ret
 }
 
+// ShardString takes a string and returns a slice of strings where the length of each one is
+// at most shardSize.
+func ShardString(s string, shardSize int) []string {
+	if len(s) == 0 {
+		return nil
+	}
+	ret := make([]string, 0, (len(s)+shardSize-1)/shardSize)
+	for len(s) > shardSize {
+		ret = append(ret, s[0:shardSize])
+		s = s[shardSize:]
+	}
+	if len(s) > 0 {
+		ret = append(ret, s)
+	}
+	return ret
+}
+
 // ShardStrings takes a slice of strings, and returns a slice of slices of strings where each one has at most shardSize
 // elements.
 func ShardStrings(s []string, shardSize int) [][]string {
@@ -424,13 +435,15 @@
 	return ret
 }
 
+// CheckDuplicate checks if there are duplicates in given string list.
+// If there are, it returns first such duplicate and true.
 func CheckDuplicate(values []string) (duplicate string, found bool) {
 	seen := make(map[string]string)
 	for _, v := range values {
 		if duplicate, found = seen[v]; found {
-			return
+			return duplicate, true
 		}
 		seen[v] = v
 	}
-	return
+	return "", false
 }
diff --git a/android/util_test.go b/android/util_test.go
index 25b52ca..fa26c77 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -593,6 +593,10 @@
 			name: "map",
 			f:    firstUniqueStringsMap,
 		},
+		{
+			name: "optimal",
+			f:    FirstUniqueStrings,
+		},
 	}
 	const maxSize = 1024
 	uniqueStrings := make([]string, maxSize)
diff --git a/android/variable.go b/android/variable.go
index a9a9c87..aed145c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -319,8 +319,9 @@
 	Ndk_abis               *bool `json:",omitempty"`
 	Exclude_draft_ndk_apis *bool `json:",omitempty"`
 
-	Flatten_apex *bool `json:",omitempty"`
-	Aml_abis     *bool `json:",omitempty"`
+	Flatten_apex   *bool `json:",omitempty"`
+	CompressedApex *bool `json:",omitempty"`
+	Aml_abis       *bool `json:",omitempty"`
 
 	DexpreoptGlobalConfig *string `json:",omitempty"`
 
@@ -347,6 +348,9 @@
 
 	EnforceProductPartitionInterface *bool `json:",omitempty"`
 
+	EnforceInterPartitionJavaSdkLibrary *bool    `json:",omitempty"`
+	InterPartitionJavaLibraryAllowList  []string `json:",omitempty"`
+
 	InstallExtraFlattenedApexes *bool `json:",omitempty"`
 
 	BoardUsesRecoveryAsBoot *bool `json:",omitempty"`
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/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index 03cf74d..f4e5fa0 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -137,7 +137,14 @@
 
 		switch x := node.(type) {
 		case *mkparser.Comment:
-			file.insertComment("//" + x.Comment)
+			// Split the comment on escaped newlines and then
+			// add each chunk separately.
+			chunks := strings.Split(x.Comment, "\\\n")
+			file.insertComment("//" + chunks[0])
+			for i := 1; i < len(chunks); i++ {
+				file.bpPos.Line++
+				file.insertComment("//" + chunks[i])
+			}
 		case *mkparser.Assignment:
 			handleAssignment(file, x, assignmentCond)
 		case *mkparser.Directive:
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 16cb138..f32ff2a 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -1260,10 +1260,10 @@
 		desc: "comment with ESC",
 		in: `
 # Comment line 1 \
-# Comment line 2
+ Comment line 2
 `,
 		expected: `
-// Comment line 1 \
+// Comment line 1
 // Comment line 2
 `,
 	},
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 4b782a2..3c4815e 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -15,8 +15,10 @@
 package parser
 
 import (
+	"fmt"
 	"strings"
 	"unicode"
+	"unicode/utf8"
 )
 
 // A MakeString is a string that may contain variable substitutions in it.
@@ -130,8 +132,85 @@
 	})
 }
 
+// Words splits MakeString into multiple makeStrings separated by whitespace.
+// Thus, " a $(X)b  c " will be split into ["a", "$(X)b", "c"].
+// Splitting a MakeString consisting solely of whitespace yields empty array.
 func (ms *MakeString) Words() []*MakeString {
-	return ms.splitNFunc(-1, splitWords)
+	var ch rune    // current character
+	const EOF = -1 // no more characters
+	const EOS = -2 // at the end of a string chunk
+
+	// Next character's chunk and position
+	iString := 0
+	iChar := 0
+
+	var words []*MakeString
+	word := SimpleMakeString("", ms.Pos())
+
+	nextChar := func() {
+		if iString >= len(ms.Strings) {
+			ch = EOF
+		} else if iChar >= len(ms.Strings[iString]) {
+			iString++
+			iChar = 0
+			ch = EOS
+		} else {
+			var w int
+			ch, w = utf8.DecodeRuneInString(ms.Strings[iString][iChar:])
+			iChar += w
+		}
+	}
+
+	appendVariableAndAdvance := func() {
+		if iString-1 < len(ms.Variables) {
+			word.appendVariable(ms.Variables[iString-1])
+		}
+		nextChar()
+	}
+
+	appendCharAndAdvance := func(c rune) {
+		if c != EOF {
+			word.appendString(string(c))
+		}
+		nextChar()
+	}
+
+	nextChar()
+	for ch != EOF {
+		// Skip whitespace
+		for ch == ' ' || ch == '\t' {
+			nextChar()
+		}
+		if ch == EOS {
+			// "... $(X)... " case. The current word should be empty.
+			if !word.Empty() {
+				panic(fmt.Errorf("%q: EOS while current word %q is not empty, iString=%d",
+					ms.Dump(), word.Dump(), iString))
+			}
+			appendVariableAndAdvance()
+		}
+		// Copy word
+		for ch != EOF {
+			if ch == ' ' || ch == '\t' {
+				words = append(words, word)
+				word = SimpleMakeString("", ms.Pos())
+				break
+			}
+			if ch == EOS {
+				// "...a$(X)..." case. Append variable to the current word
+				appendVariableAndAdvance()
+			} else {
+				if ch == '\\' {
+					appendCharAndAdvance('\\')
+				}
+				appendCharAndAdvance(ch)
+			}
+		}
+	}
+	if !word.Empty() {
+		words = append(words, word)
+	}
+	return words
 }
 
 func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
@@ -166,9 +245,7 @@
 		}
 	}
 
-	if !curMs.Empty() {
-		ret = append(ret, curMs)
-	}
+	ret = append(ret, curMs)
 	return ret
 }
 
@@ -219,44 +296,6 @@
 	return ret
 }
 
-func splitWords(s string, n int) []string {
-	ret := []string{}
-	preserve := ""
-	for n == -1 || n > 1 {
-		index := strings.IndexAny(s, " \t")
-		if index == 0 && len(preserve) == 0 {
-			s = s[1:]
-		} else if index >= 0 {
-			escapeCount := 0
-			for i := index - 1; i >= 0; i-- {
-				if s[i] != '\\' {
-					break
-				}
-				escapeCount += 1
-			}
-
-			if escapeCount%2 == 1 {
-				preserve += s[0 : index+1]
-				s = s[index+1:]
-				continue
-			}
-
-			ret = append(ret, preserve+s[0:index])
-			s = s[index+1:]
-			preserve = ""
-			if n > 0 {
-				n--
-			}
-		} else {
-			break
-		}
-	}
-	if preserve != "" || s != "" || len(ret) == 0 {
-		ret = append(ret, preserve+s)
-	}
-	return ret
-}
-
 func unescape(s string) string {
 	ret := ""
 	for {
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index 6995e89..fbb289b 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -26,64 +26,53 @@
 	n        int
 }{
 	{
-		in: &MakeString{
-			Strings: []string{
-				"a b c",
-				"d e f",
-				" h i j",
-			},
-			Variables: []Variable{
-				Variable{Name: SimpleMakeString("var1", NoPos)},
-				Variable{Name: SimpleMakeString("var2", NoPos)},
-			},
-		},
+		// "a b c$(var1)d e f$(var2) h i j"
+		in:  genMakeString("a b c", "var1", "d e f", "var2", " h i j"),
 		sep: " ",
 		n:   -1,
 		expected: []*MakeString{
-			SimpleMakeString("a", NoPos),
-			SimpleMakeString("b", NoPos),
-			&MakeString{
-				Strings: []string{"c", "d"},
-				Variables: []Variable{
-					Variable{Name: SimpleMakeString("var1", NoPos)},
-				},
-			},
-			SimpleMakeString("e", NoPos),
-			&MakeString{
-				Strings: []string{"f", ""},
-				Variables: []Variable{
-					Variable{Name: SimpleMakeString("var2", NoPos)},
-				},
-			},
-			SimpleMakeString("h", NoPos),
-			SimpleMakeString("i", NoPos),
-			SimpleMakeString("j", NoPos),
+			genMakeString("a"),
+			genMakeString("b"),
+			genMakeString("c", "var1", "d"),
+			genMakeString("e"),
+			genMakeString("f", "var2", ""),
+			genMakeString("h"),
+			genMakeString("i"),
+			genMakeString("j"),
 		},
 	},
 	{
-		in: &MakeString{
-			Strings: []string{
-				"a b c",
-				"d e f",
-				" h i j",
-			},
-			Variables: []Variable{
-				Variable{Name: SimpleMakeString("var1", NoPos)},
-				Variable{Name: SimpleMakeString("var2", NoPos)},
-			},
-		},
+		// "a b c$(var1)d e f$(var2) h i j"
+		in:  genMakeString("a b c", "var1", "d e f", "var2", " h i j"),
 		sep: " ",
 		n:   3,
 		expected: []*MakeString{
-			SimpleMakeString("a", NoPos),
-			SimpleMakeString("b", NoPos),
-			&MakeString{
-				Strings: []string{"c", "d e f", " h i j"},
-				Variables: []Variable{
-					Variable{Name: SimpleMakeString("var1", NoPos)},
-					Variable{Name: SimpleMakeString("var2", NoPos)},
-				},
-			},
+			genMakeString("a"),
+			genMakeString("b"),
+			genMakeString("c", "var1", "d e f", "var2", " h i j"),
+		},
+	},
+	{
+		// "$(var1) $(var2)"
+		in:  genMakeString("", "var1", " ", "var2", ""),
+		sep: " ",
+		n:   -1,
+		expected: []*MakeString{
+			genMakeString("", "var1", ""),
+			genMakeString("", "var2", ""),
+		},
+	},
+	{
+		// "a,,b,c,"
+		in:  genMakeString("a,,b,c,"),
+		sep: ",",
+		n:   -1,
+		expected: []*MakeString{
+			genMakeString("a"),
+			genMakeString(""),
+			genMakeString("b"),
+			genMakeString("c"),
+			genMakeString(""),
 		},
 	},
 }
@@ -104,15 +93,15 @@
 	expected string
 }{
 	{
-		in:       SimpleMakeString("a b", NoPos),
+		in:       genMakeString("a b"),
 		expected: "a b",
 	},
 	{
-		in:       SimpleMakeString("a\\ \\\tb\\\\", NoPos),
+		in:       genMakeString("a\\ \\\tb\\\\"),
 		expected: "a \tb\\",
 	},
 	{
-		in:       SimpleMakeString("a\\b\\", NoPos),
+		in:       genMakeString("a\\b\\"),
 		expected: "a\\b\\",
 	},
 }
@@ -131,31 +120,88 @@
 	expected []*MakeString
 }{
 	{
-		in:       SimpleMakeString("", NoPos),
+		in:       genMakeString(""),
 		expected: []*MakeString{},
 	},
 	{
-		in: SimpleMakeString(" a b\\ c d", NoPos),
+		in: genMakeString(` a b\ c d`),
 		expected: []*MakeString{
-			SimpleMakeString("a", NoPos),
-			SimpleMakeString("b\\ c", NoPos),
-			SimpleMakeString("d", NoPos),
+			genMakeString("a"),
+			genMakeString(`b\ c`),
+			genMakeString("d"),
 		},
 	},
 	{
-		in: SimpleMakeString("  a\tb\\\t\\ c d  ", NoPos),
+		in: SimpleMakeString("  a\tb"+`\`+"\t"+`\ c d  `, NoPos),
 		expected: []*MakeString{
-			SimpleMakeString("a", NoPos),
-			SimpleMakeString("b\\\t\\ c", NoPos),
-			SimpleMakeString("d", NoPos),
+			genMakeString("a"),
+			genMakeString("b" + `\` + "\t" + `\ c`),
+			genMakeString("d"),
 		},
 	},
 	{
-		in: SimpleMakeString(`a\\ b\\\ c d`, NoPos),
+		in: genMakeString(`a\\ b\\\ c d`),
 		expected: []*MakeString{
-			SimpleMakeString(`a\\`, NoPos),
-			SimpleMakeString(`b\\\ c`, NoPos),
-			SimpleMakeString("d", NoPos),
+			genMakeString(`a\\`),
+			genMakeString(`b\\\ c`),
+			genMakeString("d"),
+		},
+	},
+	{
+		in: genMakeString(`\\ a`),
+		expected: []*MakeString{
+			genMakeString(`\\`),
+			genMakeString("a"),
+		},
+	},
+	{
+		// "  "
+		in: &MakeString{
+			Strings:   []string{" \t \t"},
+			Variables: nil,
+		},
+		expected: []*MakeString{},
+	},
+	{
+		// " a $(X)b c "
+		in: genMakeString(" a ", "X", "b c "),
+		expected: []*MakeString{
+			genMakeString("a"),
+			genMakeString("", "X", "b"),
+			genMakeString("c"),
+		},
+	},
+	{
+		// " a b$(X)c d"
+		in: genMakeString(" a b", "X", "c d"),
+		expected: []*MakeString{
+			genMakeString("a"),
+			genMakeString("b", "X", "c"),
+			genMakeString("d"),
+		},
+	},
+	{
+		// "$(X) $(Y)"
+		in: genMakeString("", "X", " ", "Y", ""),
+		expected: []*MakeString{
+			genMakeString("", "X", ""),
+			genMakeString("", "Y", ""),
+		},
+	},
+	{
+		// " a$(X) b"
+		in: genMakeString(" a", "X", " b"),
+		expected: []*MakeString{
+			genMakeString("a", "X", ""),
+			genMakeString("b"),
+		},
+	},
+	{
+		// "a$(X) b$(Y) "
+		in: genMakeString("a", "X", " b", "Y", " "),
+		expected: []*MakeString{
+			genMakeString("a", "X", ""),
+			genMakeString("b", "Y", ""),
 		},
 	},
 }
@@ -180,3 +226,20 @@
 
 	return strings.Join(ret, "|||")
 }
+
+// generates MakeString from alternating string chunks and variable names,
+// e.g., genMakeString("a", "X", "b") returns MakeString for "a$(X)b"
+func genMakeString(items ...string) *MakeString {
+	n := len(items) / 2
+	if len(items) != (2*n + 1) {
+		panic("genMakeString expects odd number of arguments")
+	}
+
+	ms := &MakeString{Strings: make([]string, n+1), Variables: make([]Variable, n)}
+	ms.Strings[0] = items[0]
+	for i := 1; i <= n; i++ {
+		ms.Variables[i-1] = Variable{Name: SimpleMakeString(items[2*i-1], NoPos)}
+		ms.Strings[i] = items[2*i]
+	}
+	return ms
+}
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
index 86dabf9..c14910a 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:
@@ -484,12 +497,6 @@
 		switch p.tok {
 		case '\\':
 			p.parseEscape()
-			if p.tok == '\n' {
-				// Special case: '\' does not "escape" newline in comment (b/127521510)
-				comment += "\\"
-				p.accept(p.tok)
-				break loop
-			}
 			comment += "\\" + p.scanner.TokenText()
 			p.accept(p.tok)
 		case '\n':
diff --git a/apex/Android.bp b/apex/Android.bp
index 1a5f683..9e8c30d 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,7 @@
         "soong-cc",
         "soong-java",
         "soong-python",
+        "soong-rust",
         "soong-sh",
     ],
     srcs: [
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index 5b8563d..b416d31 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -333,6 +333,7 @@
 libminijail_gen_syscall_obj(minSdkVersion:29)
 libminijail_generated(minSdkVersion:29)
 libmkvextractor(minSdkVersion:29)
+libmodules-utils-build(minSdkVersion:29)
 libmp3extractor(minSdkVersion:29)
 libmp4extractor(minSdkVersion:29)
 libmpeg2dec(minSdkVersion:29)
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 993260c..6c76ad3f 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -36,6 +36,33 @@
 	return a.androidMkForType()
 }
 
+// nameInMake converts apexFileClass into the corresponding class name in Make.
+func (class apexFileClass) nameInMake() string {
+	switch class {
+	case etc:
+		return "ETC"
+	case nativeSharedLib:
+		return "SHARED_LIBRARIES"
+	case nativeExecutable, shBinary, pyBinary, goBinary:
+		return "EXECUTABLES"
+	case javaSharedLib:
+		return "JAVA_LIBRARIES"
+	case nativeTest:
+		return "NATIVE_TESTS"
+	case app, appSet:
+		// b/142537672 Why isn't this APP? We want to have full control over
+		// the paths and file names of the apk file under the flattend APEX.
+		// If this is set to APP, then the paths and file names are modified
+		// by the Make build system. For example, it is installed to
+		// /system/apex/<apexname>/app/<Appname>/<apexname>.<Appname>/ instead of
+		// /system/apex/<apexname>/app/<Appname> because the build system automatically
+		// appends module name (which is <apexname>.<Appname> to the path.
+		return "ETC"
+	default:
+		panic(fmt.Errorf("unknown class %d", class))
+	}
+}
+
 func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, apexName, moduleDir string,
 	apexAndroidMkData android.AndroidMkData) []string {
 
@@ -68,10 +95,10 @@
 
 	var postInstallCommands []string
 	for _, fi := range a.filesInfo {
-		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
 			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			linkTarget := filepath.Join("/system", fi.Path())
-			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.Path())
+			linkTarget := filepath.Join("/system", fi.path())
+			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.path())
 			mkdirCmd := "mkdir -p " + filepath.Dir(linkPath)
 			linkCmd := "ln -sfn " + linkTarget + " " + linkPath
 			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
@@ -85,7 +112,7 @@
 			continue
 		}
 
-		linkToSystemLib := a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform()
+		linkToSystemLib := a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform()
 
 		var moduleName string
 		if linkToSystemLib {
@@ -151,7 +178,7 @@
 			fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
 		}
 		fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", fi.builtFile.String())
-		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.NameInMake())
+		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.nameInMake())
 		if fi.module != nil {
 			archStr := fi.module.Target().Arch.ArchType.String()
 			host := false
@@ -189,7 +216,7 @@
 			// soong_java_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .jar  Therefore
 			// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 			// we will have foo.jar.jar
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.Stem(), ".jar"))
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.stem(), ".jar"))
 			if javaModule, ok := fi.module.(java.ApexDependency); ok {
 				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", javaModule.ImplementationAndResourcesJars()[0].String())
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", javaModule.HeaderJars()[0].String())
@@ -205,7 +232,7 @@
 			// soong_app_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .apk  Therefore
 			// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 			// we will have foo.apk.apk
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.Stem(), ".apk"))
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.stem(), ".apk"))
 			if app, ok := fi.module.(*java.AndroidApp); ok {
 				if jniCoverageOutputs := app.JniCoverageOutputs(); len(jniCoverageOutputs) > 0 {
 					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", strings.Join(jniCoverageOutputs.Strings(), " "))
@@ -224,7 +251,7 @@
 			fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String())
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk")
 		case nativeSharedLib, nativeExecutable, nativeTest:
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.Stem())
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if ccMod, ok := fi.module.(*cc.Module); ok {
 				if ccMod.UnstrippedOutputFile() != nil {
 					fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", ccMod.UnstrippedOutputFile().String())
@@ -236,7 +263,7 @@
 			}
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_prebuilt.mk")
 		default:
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.Stem())
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if fi.builtFile == a.manifestPbOut && apexType == flattenedApex {
 				if a.primaryApexType {
 					// To install companion files (init_rc, vintf_fragments)
@@ -307,7 +334,6 @@
 
 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
@@ -334,7 +360,11 @@
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
 				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
-				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+apexType.suffix())
+				stemSuffix := apexType.suffix()
+				if a.isCompressed {
+					stemSuffix = ".capex"
+				}
+				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+stemSuffix)
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
 
 				// Because apex writes .mk with Custom(), we need to write manually some common properties
diff --git a/apex/apex.go b/apex/apex.go
index 91770f4..28ae4bd 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// package apex implements build rules for creating the APEX files which are container for
+// lower-level system components. See https://source.android.com/devices/tech/ota/apex
 package apex
 
 import (
@@ -30,14 +32,956 @@
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
 	"android/soong/python"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
+func init() {
+	android.RegisterModuleType("apex", BundleFactory)
+	android.RegisterModuleType("apex_test", testApexBundleFactory)
+	android.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
+	android.RegisterModuleType("apex_defaults", defaultsFactory)
+	android.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
+	android.RegisterModuleType("override_apex", overrideApexFactory)
+	android.RegisterModuleType("apex_set", apexSetFactory)
+
+	android.PreDepsMutators(RegisterPreDepsMutators)
+	android.PostDepsMutators(RegisterPostDepsMutators)
+}
+
+func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
+	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
+}
+
+func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.TopDown("apex_deps", apexDepsMutator).Parallel()
+	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
+	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
+	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
+	ctx.BottomUp("apex", apexMutator).Parallel()
+	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
+	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
+	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
+}
+
+type apexBundleProperties struct {
+	// Json manifest file describing meta info of this APEX bundle. Refer to
+	// system/apex/proto/apex_manifest.proto for the schema. Default: "apex_manifest.json"
+	Manifest *string `android:"path"`
+
+	// AndroidManifest.xml file used for the zip container of this APEX bundle. If unspecified,
+	// a default one is automatically generated.
+	AndroidManifest *string `android:"path"`
+
+	// Canonical name of this APEX bundle. Used to determine the path to the activated APEX on
+	// device (/apex/<apex_name>). If unspecified, follows the name property.
+	Apex_name *string
+
+	// Determines the file contexts file for setting the security contexts to files in this APEX
+	// bundle. For platform APEXes, this should points to a file under /system/sepolicy Default:
+	// /system/sepolicy/apex/<module_name>_file_contexts.
+	File_contexts *string `android:"path"`
+
+	ApexNativeDependencies
+
+	Multilib apexMultilibProperties
+
+	// List of java libraries that are embedded inside this APEX bundle.
+	Java_libs []string
+
+	// List of prebuilt files that are embedded inside this APEX bundle.
+	Prebuilts []string
+
+	// List of BPF programs inside this APEX bundle.
+	Bpfs []string
+
+	// Name of the apex_key module that provides the private key to sign this APEX bundle.
+	Key *string
+
+	// Specifies the certificate and the private key to sign the zip container of this APEX. If
+	// this is "foo", foo.x509.pem and foo.pk8 under PRODUCT_DEFAULT_DEV_CERTIFICATE are used
+	// as the certificate and the private key, respectively. If this is ":module", then the
+	// certificate and the private key are provided from the android_app_certificate module
+	// named "module".
+	Certificate *string
+
+	// The minimum SDK version that this APEX must support at minimum. This is usually set to
+	// the SDK version that the APEX was first introduced.
+	Min_sdk_version *string
+
+	// Whether this APEX is considered updatable or not. When set to true, this will enforce
+	// additional rules for making sure that the APEX is truly updatable. To be updatable,
+	// min_sdk_version should be set as well. This will also disable the size optimizations like
+	// symlinking to the system libs. Default is false.
+	Updatable *bool
+
+	// Whether this APEX is installable to one of the partitions like system, vendor, etc.
+	// Default: true.
+	Installable *bool
+
+	// Whether this APEX can be compressed or not. Setting this property to false means this
+	// APEX will never be compressed. When set to true, APEX will be compressed if other
+	// conditions, e.g, target device needs to support APEX compression, are also fulfilled.
+	// Default: true.
+	Compressible *bool
+
+	// For native libraries and binaries, use the vendor variant instead of the core (platform)
+	// variant. Default is false. DO NOT use this for APEXes that are installed to the system or
+	// system_ext partition.
+	Use_vendor *bool
+
+	// If set true, VNDK libs are considered as stable libs and are not included in this APEX.
+	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
+	Use_vndk_as_stable *bool
+
+	// List of SDKs that are used to build this APEX. A reference to an SDK should be either
+	// `name#version` or `name` which is an alias for `name#current`. If left empty,
+	// `platform#current` is implied. This value affects all modules included in this APEX. In
+	// other words, they are also built with the SDKs specified here.
+	Uses_sdks []string
+
+	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
+	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
+	// container. When set to zip, contents are stored in a zip container directly. This type is
+	// mostly for host-side debugging. When set to both, the two types are both built. Default
+	// is 'image'.
+	Payload_type *string
+
+	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4' or 'f2fs'.
+	// Default 'ext4'.
+	Payload_fs_type *string
+
+	// For telling the APEX to ignore special handling for system libraries such as bionic.
+	// Default is false.
+	Ignore_system_library_special_case *bool
+
+	// Whenever apex_payload.img of the APEX should include dm-verity hashtree. Should be only
+	// used in tests.
+	Test_only_no_hashtree *bool
+
+	// Whenever apex_payload.img of the APEX should not be dm-verity signed. Should be only
+	// used in tests.
+	Test_only_unsigned_payload *bool
+
+	IsCoverageVariant bool `blueprint:"mutated"`
+
+	// List of sanitizer names that this APEX is enabled for
+	SanitizerNames []string `blueprint:"mutated"`
+
+	PreventInstall bool `blueprint:"mutated"`
+
+	HideFromMake bool `blueprint:"mutated"`
+
+	// Internal package method for this APEX. When payload_type is image, this can be either
+	// imageApex or flattenedApex depending on Config.FlattenApex(). When payload_type is zip,
+	// this becomes zipApex.
+	ApexType apexPackaging `blueprint:"mutated"`
+}
+
+type ApexNativeDependencies struct {
+	// List of native libraries that are embedded inside this APEX.
+	Native_shared_libs []string
+
+	// List of JNI libraries that are embedded inside this APEX.
+	Jni_libs []string
+
+	// List of rust dyn libraries
+	Rust_dyn_libs []string
+
+	// List of native executables that are embedded inside this APEX.
+	Binaries []string
+
+	// List of native tests that are embedded inside this APEX.
+	Tests []string
+}
+
+type apexMultilibProperties struct {
+	// Native dependencies whose compile_multilib is "first"
+	First ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "both"
+	Both ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "prefer32"
+	Prefer32 ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "32"
+	Lib32 ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "64"
+	Lib64 ApexNativeDependencies
+}
+
+type apexTargetBundleProperties struct {
+	Target struct {
+		// Multilib properties only for android.
+		Android struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host.
+		Host struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host linux_bionic.
+		Linux_bionic struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host linux_glibc.
+		Linux_glibc struct {
+			Multilib apexMultilibProperties
+		}
+	}
+}
+
+// These properties can be used in override_apex to override the corresponding properties in the
+// base apex.
+type overridableProperties struct {
+	// List of APKs that are embedded inside this APEX.
+	Apps []string
+
+	// List of runtime resource overlays (RROs) that are embedded inside this APEX.
+	Rros []string
+
+	// Names of modules to be overridden. Listed modules can only be other binaries (in Make or
+	// Soong). This does not completely prevent installation of the overridden binaries, but if
+	// both binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will
+	// be removed from PRODUCT_PACKAGES.
+	Overrides []string
+
+	// Logging parent value.
+	Logging_parent string
+
+	// Apex Container package name. Override value for attribute package:name in
+	// AndroidManifest.xml
+	Package_name string
+
+	// A txt file containing list of files that are allowed to be included in this APEX.
+	Allowed_files *string `android:"path"`
+}
+
+type apexBundle struct {
+	// Inherited structs
+	android.ModuleBase
+	android.DefaultableModuleBase
+	android.OverridableModuleBase
+	android.SdkBase
+
+	// Properties
+	properties            apexBundleProperties
+	targetProperties      apexTargetBundleProperties
+	overridableProperties overridableProperties
+	vndkProperties        apexVndkProperties // only for apex_vndk modules
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Inputs
+
+	// Keys for apex_paylaod.img
+	public_key_file  android.Path
+	private_key_file android.Path
+
+	// Cert/priv-key for the zip container
+	container_certificate_file android.Path
+	container_private_key_file android.Path
+
+	// Flags for special variants of APEX
+	testApex bool
+	vndkApex bool
+	artApex  bool
+
+	// Tells whether this variant of the APEX bundle is the primary one or not. Only the primary
+	// one gets installed to the device.
+	primaryApexType bool
+
+	// Suffix of module name in Android.mk ".flattened", ".apex", ".zipapex", or ""
+	suffix string
+
+	// File system type of apex_payload.img
+	payloadFsType fsType
+
+	// Whether to create symlink to the system file instead of having a file inside the apex or
+	// not
+	linkToSystemLib bool
+
+	// List of files to be included in this APEX. This is filled in the first part of
+	// GenerateAndroidBuildActions.
+	filesInfo []apexFile
+
+	// List of other module names that should be installed when this APEX gets installed.
+	requiredDeps []string
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Outputs (final and intermediates)
+
+	// Processed apex manifest in JSONson format (for Q)
+	manifestJsonOut android.WritablePath
+
+	// Processed apex manifest in PB format (for R+)
+	manifestPbOut android.WritablePath
+
+	// Processed file_contexts files
+	fileContexts android.WritablePath
+
+	// Struct holding the merged notice file paths in different formats
+	mergedNotices android.NoticeOutputs
+
+	// The built APEX file. This is the main product.
+	outputFile android.WritablePath
+
+	// The built APEX file in app bundle format. This file is not directly installed to the
+	// device. For an APEX, multiple app bundles are created each of which is for a specific ABI
+	// like arm, arm64, x86, etc. Then they are processed again (outside of the Android build
+	// system) to be merged into a single app bundle file that Play accepts. See
+	// vendor/google/build/build_unbundled_mainline_module.sh for more detail.
+	bundleModuleFile android.WritablePath
+
+	// Target path to install this APEX. Usually out/target/product/<device>/<partition>/apex.
+	installDir android.InstallPath
+
+	// List of commands to create symlinks for backward compatibility. These commands will be
+	// attached as LOCAL_POST_INSTALL_CMD to apex package itself (for unflattened build) or
+	// apex_manifest (for flattened build) so that compat symlinks are always installed
+	// regardless of TARGET_FLATTEN_APEX setting.
+	compatSymlinks []string
+
+	// Text file having the list of individual files that are included in this APEX. Used for
+	// debugging purpose.
+	installedFilesFile android.WritablePath
+
+	// List of module names that this APEX is including (to be shown via *-deps-info target).
+	// Used for debugging purpose.
+	android.ApexBundleDepsInfo
+
+	// Optional list of lint report zip files for apexes that contain java or app modules
+	lintReports android.Paths
+
+	prebuiltFileToDelete string
+
+	isCompressed bool
+
+	// Path of API coverage generate file
+	coverageOutputPath android.ModuleOutPath
+}
+
+// apexFileClass represents a type of file that can be included in APEX.
+type apexFileClass int
+
 const (
+	app apexFileClass = iota
+	appSet
+	etc
+	goBinary
+	javaSharedLib
+	nativeExecutable
+	nativeSharedLib
+	nativeTest
+	pyBinary
+	shBinary
+)
+
+// apexFile represents a file in an APEX bundle. This is created during the first half of
+// GenerateAndroidBuildActions by traversing the dependencies of the APEX. Then in the second half
+// of the function, this is used to create commands that copies the files into a staging directory,
+// where they are packaged into the APEX file. This struct is also used for creating Make modules
+// for each of the files in case when the APEX is flattened.
+type apexFile struct {
+	// buildFile is put in the installDir inside the APEX.
+	builtFile   android.Path
+	noticeFiles android.Paths
+	installDir  string
+	customStem  string
+	symlinks    []string // additional symlinks
+
+	// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
+	// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
+	// suffix>]
+	androidMkModuleName       string             // becomes LOCAL_MODULE
+	class                     apexFileClass      // becomes LOCAL_MODULE_CLASS
+	moduleDir                 string             // becomes LOCAL_PATH
+	requiredModuleNames       []string           // becomes LOCAL_REQUIRED_MODULES
+	targetRequiredModuleNames []string           // becomes LOCAL_TARGET_REQUIRED_MODULES
+	hostRequiredModuleNames   []string           // becomes LOCAL_HOST_REQUIRED_MODULES
+	dataPaths                 []android.DataPath // becomes LOCAL_TEST_DATA
+
+	jacocoReportClassesFile android.Path     // only for javalibs and apps
+	lintDepSets             java.LintDepSets // only for javalibs and apps
+	certificate             java.Certificate // only for apps
+	overriddenPackageName   string           // only for apps
+
+	transitiveDep bool
+	isJniLib      bool
+
+	// TODO(jiyong): remove this
+	module android.Module
+}
+
+// TODO(jiyong): shorten the arglist using an option struct
+func newApexFile(ctx android.BaseModuleContext, builtFile android.Path, androidMkModuleName string, installDir string, class apexFileClass, module android.Module) apexFile {
+	ret := apexFile{
+		builtFile:           builtFile,
+		installDir:          installDir,
+		androidMkModuleName: androidMkModuleName,
+		class:               class,
+		module:              module,
+	}
+	if module != nil {
+		ret.noticeFiles = module.NoticeFiles()
+		ret.moduleDir = ctx.OtherModuleDir(module)
+		ret.requiredModuleNames = module.RequiredModuleNames()
+		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
+		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
+	}
+	return ret
+}
+
+func (af *apexFile) ok() bool {
+	return af.builtFile != nil && af.builtFile.String() != ""
+}
+
+// apexRelativePath returns the relative path of the given path from the install directory of this
+// apexFile.
+// TODO(jiyong): rename this
+func (af *apexFile) apexRelativePath(path string) string {
+	return filepath.Join(af.installDir, path)
+}
+
+// path returns path of this apex file relative to the APEX root
+func (af *apexFile) path() string {
+	return af.apexRelativePath(af.stem())
+}
+
+// stem returns the base filename of this apex file
+func (af *apexFile) stem() string {
+	if af.customStem != "" {
+		return af.customStem
+	}
+	return af.builtFile.Base()
+}
+
+// symlinkPaths returns paths of the symlinks (if any) relative to the APEX root
+func (af *apexFile) symlinkPaths() []string {
+	var ret []string
+	for _, symlink := range af.symlinks {
+		ret = append(ret, af.apexRelativePath(symlink))
+	}
+	return ret
+}
+
+// availableToPlatform tests whether this apexFile is from a module that can be installed to the
+// platform.
+func (af *apexFile) availableToPlatform() bool {
+	if af.module == nil {
+		return false
+	}
+	if am, ok := af.module.(android.ApexModule); ok {
+		return am.AvailableFor(android.AvailableToPlatform)
+	}
+	return false
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Mutators
+//
+// Brief description about mutators for APEX. The following three mutators are the most important
+// ones.
+//
+// 1) DepsMutator: from the properties like native_shared_libs, java_libs, etc., modules are added
+// to the (direct) dependencies of this APEX bundle.
+//
+// 2) apexDepsMutator: this is a post-deps mutator, so runs after DepsMutator. Its goal is to
+// collect modules that are direct and transitive dependencies of each APEX bundle. The collected
+// modules are marked as being included in the APEX via BuildForApex().
+//
+// 3) apexMutator: this is a post-deps mutator that runs after apexDepsMutator. For each module that
+// are marked by the apexDepsMutator, apex variations are created using CreateApexVariations().
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+
+	// Determines if the dependent will be part of the APEX payload. Can be false for the
+	// dependencies to the signing key module, etc.
+	payload bool
+}
+
+var (
+	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
+	bpfTag         = dependencyTag{name: "bpf", payload: true}
+	certificateTag = dependencyTag{name: "certificate"}
+	executableTag  = dependencyTag{name: "executable", payload: true}
+	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
+	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
+	keyTag         = dependencyTag{name: "key"}
+	prebuiltTag    = dependencyTag{name: "prebuilt", payload: true}
+	rroTag         = dependencyTag{name: "rro", payload: true}
+	sharedLibTag   = dependencyTag{name: "sharedLib", payload: true}
+	testForTag     = dependencyTag{name: "test for"}
+	testTag        = dependencyTag{name: "test", payload: true}
+)
+
+// TODO(jiyong): shorten this function signature
+func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
+	binVariations := target.Variations()
+	libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+	rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
+
+	if ctx.Device() {
+		binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
+		libVariations = append(libVariations,
+			blueprint.Variation{Mutator: "image", Variation: imageVariation},
+			blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
+		rustLibVariations = append(rustLibVariations,
+			blueprint.Variation{Mutator: "image", Variation: imageVariation})
+	}
+
+	// Use *FarVariation* to be able to depend on modules having conflicting variations with
+	// this module. This is required since arch variant of an APEX bundle is 'common' but it is
+	// 'arm' or 'arm64' for native shared libs.
+	ctx.AddFarVariationDependencies(binVariations, executableTag, nativeModules.Binaries...)
+	ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
+	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
+	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
+	ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
+}
+
+func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
+	if ctx.Device() {
+		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Android.Multilib, nil)
+	} else {
+		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Host.Multilib, nil)
+		if ctx.Os().Bionic() {
+			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_bionic.Multilib, nil)
+		} else {
+			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_glibc.Multilib, nil)
+		}
+	}
+}
+
+// getImageVariation returns the image variant name for this apexBundle. In most cases, it's simply
+// android.CoreVariation, but gets complicated for the vendor APEXes and the VNDK APEX.
+func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
+	deviceConfig := ctx.DeviceConfig()
+	if a.vndkApex {
+		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
+	}
+
+	var prefix string
+	var vndkVersion string
+	if deviceConfig.VndkVersion() != "" {
+		if proptools.Bool(a.properties.Use_vendor) {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.PlatformVndkVersion()
+		} else if a.SocSpecific() || a.DeviceSpecific() {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.VndkVersion()
+		} else if a.ProductSpecific() {
+			prefix = cc.ProductVariationPrefix
+			vndkVersion = deviceConfig.ProductVndkVersion()
+		}
+	}
+	if vndkVersion == "current" {
+		vndkVersion = deviceConfig.PlatformVndkVersion()
+	}
+	if vndkVersion != "" {
+		return prefix + vndkVersion
+	}
+
+	return android.CoreVariation // The usual case
+}
+
+func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// TODO(jiyong): move this kind of checks to GenerateAndroidBuildActions?
+	checkUseVendorProperty(ctx, a)
+
+	// apexBundle is a multi-arch targets module. Arch variant of apexBundle is set to 'common'.
+	// arch-specific targets are enabled by the compile_multilib setting of the apex bundle. For
+	// each target os/architectures, appropriate dependencies are selected by their
+	// target.<os>.multilib.<type> groups and are added as (direct) dependencies.
+	targets := ctx.MultiTargets()
+	config := ctx.DeviceConfig()
+	imageVariation := a.getImageVariation(ctx)
+
+	a.combineProperties(ctx)
+
+	has32BitTarget := false
+	for _, target := range targets {
+		if target.Arch.ArchType.Multilib == "lib32" {
+			has32BitTarget = true
+		}
+	}
+	for i, target := range targets {
+		// Don't include artifacts for the host cross targets because there is no way for us
+		// to run those artifacts natively on host
+		if target.HostCross {
+			continue
+		}
+
+		var depsList []ApexNativeDependencies
+
+		// Add native modules targeting both ABIs. When multilib.* is omitted for
+		// native_shared_libs/jni_libs/tests, it implies multilib.both
+		depsList = append(depsList, a.properties.Multilib.Both)
+		depsList = append(depsList, ApexNativeDependencies{
+			Native_shared_libs: a.properties.Native_shared_libs,
+			Tests:              a.properties.Tests,
+			Jni_libs:           a.properties.Jni_libs,
+			Binaries:           nil,
+		})
+
+		// Add native modules targeting the first ABI When multilib.* is omitted for
+		// binaries, it implies multilib.first
+		isPrimaryAbi := i == 0
+		if isPrimaryAbi {
+			depsList = append(depsList, a.properties.Multilib.First)
+			depsList = append(depsList, ApexNativeDependencies{
+				Native_shared_libs: nil,
+				Tests:              nil,
+				Jni_libs:           nil,
+				Binaries:           a.properties.Binaries,
+			})
+		}
+
+		// Add native modules targeting either 32-bit or 64-bit ABI
+		switch target.Arch.ArchType.Multilib {
+		case "lib32":
+			depsList = append(depsList, a.properties.Multilib.Lib32)
+			depsList = append(depsList, a.properties.Multilib.Prefer32)
+		case "lib64":
+			depsList = append(depsList, a.properties.Multilib.Lib64)
+			if !has32BitTarget {
+				depsList = append(depsList, a.properties.Multilib.Prefer32)
+			}
+		}
+
+		for _, d := range depsList {
+			addDependenciesForNativeModules(ctx, d, target, imageVariation)
+		}
+	}
+
+	// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
+	// regardless of the TARGET_PREFER_* setting. See b/144532908
+	archForPrebuiltEtc := config.Arches()[0]
+	for _, arch := range config.Arches() {
+		// Prefer 64-bit arch if there is any
+		if arch.ArchType.Multilib == "lib64" {
+			archForPrebuiltEtc = arch
+			break
+		}
+	}
+	ctx.AddFarVariationDependencies([]blueprint.Variation{
+		{Mutator: "os", Variation: ctx.Os().String()},
+		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
+	}, prebuiltTag, a.properties.Prebuilts...)
+
+	// Common-arch dependencies come next
+	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
+	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
+	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
+
+	// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
+	if a.artApex && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
+		ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
+	}
+
+	// Dependencies for signing
+	if String(a.properties.Key) == "" {
+		ctx.PropertyErrorf("key", "missing")
+		return
+	}
+	ctx.AddDependency(ctx.Module(), keyTag, String(a.properties.Key))
+
+	cert := android.SrcIsModule(a.getCertString(ctx))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+		// empty cert is not an error. Cert and private keys will be directly found under
+		// PRODUCT_DEFAULT_DEV_CERTIFICATE
+	}
+
+	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
+	// This field currently isn't used.
+	// TODO(jiyong): consider dropping this feature
+	// TODO(jiyong): ensure that all apexes are with non-empty uses_sdks
+	if len(a.properties.Uses_sdks) > 0 {
+		sdkRefs := []android.SdkRef{}
+		for _, str := range a.properties.Uses_sdks {
+			parsed := android.ParseSdkRef(ctx, str, "uses_sdks")
+			sdkRefs = append(sdkRefs, parsed)
+		}
+		a.BuildWithSdks(sdkRefs)
+	}
+}
+
+// DepsMutator for the overridden properties.
+func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
+	if a.overridableProperties.Allowed_files != nil {
+		android.ExtractSourceDeps(ctx, a.overridableProperties.Allowed_files)
+	}
+
+	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
+	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps...)
+	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.overridableProperties.Rros...)
+}
+
+type ApexBundleInfo struct {
+	Contents *android.ApexContents
+}
+
+var ApexBundleInfoProvider = blueprint.NewMutatorProvider(ApexBundleInfo{}, "apex_deps")
+
+// apexDepsMutator is responsible for collecting modules that need to have apex variants. They are
+// identified by doing a graph walk starting from an apexBundle. Basically, all the (direct and
+// indirect) dependencies are collected. But a few types of modules that shouldn't be included in
+// the apexBundle (e.g. stub libraries) are not collected. Note that a single module can be depended
+// on by multiple apexBundles. In that case, the module is collected for all of the apexBundles.
+func apexDepsMutator(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+
+	a, ok := mctx.Module().(*apexBundle)
+	if !ok {
+		return
+	}
+
+	// The VNDK APEX is special. For the APEX, the membership is described in a very different
+	// way. There is no dependency from the VNDK APEX to the VNDK libraries. Instead, VNDK
+	// libraries are self-identified by their vndk.enabled properties. There is no need to run
+	// this mutator for the APEX as nothing will be collected. So, let's return fast.
+	if a.vndkApex {
+		return
+	}
+
+	// Special casing for APEXes on non-system (e.g., vendor, odm, etc.) partitions. They are
+	// provided with a property named use_vndk_as_stable, which when set to true doesn't collect
+	// VNDK libraries as transitive dependencies. This option is useful for reducing the size of
+	// the non-system APEXes because the VNDK libraries won't be included (and duped) in the
+	// APEX, but shared across APEXes via the VNDK APEX.
+	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
+	excludeVndkLibs := useVndk && proptools.Bool(a.properties.Use_vndk_as_stable)
+	if !useVndk && proptools.Bool(a.properties.Use_vndk_as_stable) {
+		mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
+		return
+	}
+
+	continueApexDepsWalk := func(child, parent android.Module) bool {
+		am, ok := child.(android.ApexModule)
+		if !ok || !am.CanHaveApexVariants() {
+			return false
+		}
+		if !parent.(android.DepIsInSameApex).DepIsInSameApex(mctx, child) {
+			return false
+		}
+		if excludeVndkLibs {
+			if c, ok := child.(*cc.Module); ok && c.IsVndk() {
+				return false
+			}
+		}
+		// By default, all the transitive dependencies are collected, unless filtered out
+		// above.
+		return true
+	}
+
+	// Records whether a certain module is included in this apexBundle via direct dependency or
+	// inndirect dependency.
+	contents := make(map[string]android.ApexMembership)
+	mctx.WalkDeps(func(child, parent android.Module) bool {
+		if !continueApexDepsWalk(child, parent) {
+			return false
+		}
+		// If the parent is apexBundle, this child is directly depended.
+		_, directDep := parent.(*apexBundle)
+		depName := mctx.OtherModuleName(child)
+		contents[depName] = contents[depName].Add(directDep)
+		return true
+	})
+
+	// The membership information is saved for later access
+	apexContents := android.NewApexContents(contents)
+	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
+		Contents: apexContents,
+	})
+
+	// This is the main part of this mutator. Mark the collected dependencies that they need to
+	// be built for this apexBundle.
+	apexInfo := android.ApexInfo{
+		ApexVariationName: mctx.ModuleName(),
+		MinSdkVersionStr:  a.minSdkVersion(mctx).String(),
+		RequiredSdks:      a.RequiredSdks(),
+		Updatable:         a.Updatable(),
+		InApexes:          []string{mctx.ModuleName()},
+		ApexContents:      []*android.ApexContents{apexContents},
+	}
+	mctx.WalkDeps(func(child, parent android.Module) bool {
+		if !continueApexDepsWalk(child, parent) {
+			return false
+		}
+		child.(android.ApexModule).BuildForApex(apexInfo) // leave a mark!
+		return true
+	})
+}
+
+// apexUniqueVariationsMutator checks if any dependencies use unique apex variations. If so, use
+// unique apex variations for this module. See android/apex.go for more about unique apex variant.
+// TODO(jiyong): move this to android/apex.go?
+func apexUniqueVariationsMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		android.UpdateUniqueApexVariationsForDeps(mctx, am)
+	}
+}
+
+// apexTestForDepsMutator checks if this module is a test for an apex. If so, add a dependency on
+// the apex in order to retrieve its contents later.
+// TODO(jiyong): move this to android/apex.go?
+func apexTestForDepsMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		if testFor := am.TestFor(); len(testFor) > 0 {
+			mctx.AddFarVariationDependencies([]blueprint.Variation{
+				{Mutator: "os", Variation: am.Target().OsVariation()},
+				{"arch", "common"},
+			}, testForTag, testFor...)
+		}
+	}
+}
+
+// TODO(jiyong): move this to android/apex.go?
+func apexTestForMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if _, ok := mctx.Module().(android.ApexModule); ok {
+		var contents []*android.ApexContents
+		for _, testFor := range mctx.GetDirectDepsWithTag(testForTag) {
+			abInfo := mctx.OtherModuleProvider(testFor, ApexBundleInfoProvider).(ApexBundleInfo)
+			contents = append(contents, abInfo.Contents)
+		}
+		mctx.SetProvider(android.ApexTestForInfoProvider, android.ApexTestForInfo{
+			ApexContents: contents,
+		})
+	}
+}
+
+// markPlatformAvailability marks whether or not a module can be available to platform. A module
+// cannot be available to platform if 1) it is explicitly marked as not available (i.e.
+// "//apex_available:platform" is absent) or 2) it depends on another module that isn't (or can't
+// be) available to platform
+// TODO(jiyong): move this to android/apex.go?
+func markPlatformAvailability(mctx android.BottomUpMutatorContext) {
+	// Host and recovery are not considered as platform
+	if mctx.Host() || mctx.Module().InstallInRecovery() {
+		return
+	}
+
+	am, ok := mctx.Module().(android.ApexModule)
+	if !ok {
+		return
+	}
+
+	availableToPlatform := am.AvailableFor(android.AvailableToPlatform)
+
+	// If any of the dep is not available to platform, this module is also considered as being
+	// not available to platform even if it has "//apex_available:platform"
+	mctx.VisitDirectDeps(func(child android.Module) {
+		if !am.DepIsInSameApex(mctx, child) {
+			// if the dependency crosses apex boundary, don't consider it
+			return
+		}
+		if dep, ok := child.(android.ApexModule); ok && dep.NotAvailableForPlatform() {
+			availableToPlatform = false
+			// TODO(b/154889534) trigger an error when 'am' has
+			// "//apex_available:platform"
+		}
+	})
+
+	// Exception 1: stub libraries and native bridge libraries are always available to platform
+	if cc, ok := mctx.Module().(*cc.Module); ok &&
+		(cc.IsStubs() || cc.Target().NativeBridge == android.NativeBridgeEnabled) {
+		availableToPlatform = true
+	}
+
+	// Exception 2: bootstrap bionic libraries are also always available to platform
+	if cc.InstallToBootstrap(mctx.ModuleName(), mctx.Config()) {
+		availableToPlatform = true
+	}
+
+	if !availableToPlatform {
+		am.SetNotAvailableForPlatform()
+	}
+}
+
+// apexMutator visits each module and creates apex variations if the module was marked in the
+// previous run of apexDepsMutator.
+func apexMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+
+	// This is the usual path.
+	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
+		android.CreateApexVariations(mctx, am)
+		return
+	}
+
+	// apexBundle itself is mutated so that it and its dependencies have the same apex variant.
+	// TODO(jiyong): document the reason why the VNDK APEX is an exception here.
+	if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
+		apexBundleName := mctx.ModuleName()
+		mctx.CreateVariations(apexBundleName)
+	} else if o, ok := mctx.Module().(*OverrideApex); ok {
+		apexBundleName := o.GetOverriddenModuleName()
+		if apexBundleName == "" {
+			mctx.ModuleErrorf("base property is not set")
+			return
+		}
+		mctx.CreateVariations(apexBundleName)
+	}
+}
+
+// See android.UpdateDirectlyInAnyApex
+// TODO(jiyong): move this to android/apex.go?
+func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		android.UpdateDirectlyInAnyApex(mctx, am)
+	}
+}
+
+// apexPackaging represents a specific packaging method for an APEX.
+type apexPackaging int
+
+const (
+	// imageApex is a packaging method where contents are included in a filesystem image which
+	// is then included in a zip container. This is the most typical way of packaging.
+	imageApex apexPackaging = iota
+
+	// zipApex is a packaging method where contents are directly included in the zip container.
+	// This is used for host-side testing - because the contents are easily accessible by
+	// unzipping the container.
+	zipApex
+
+	// flattendApex is a packaging method where contents are not included in the APEX file, but
+	// installed to /apex/<apexname> directory on the device. This packaging method is used for
+	// old devices where the filesystem-based APEX file can't be supported.
+	flattenedApex
+)
+
+const (
+	// File extensions of an APEX for different packaging methods
 	imageApexSuffix = ".apex"
 	zipApexSuffix   = ".zipapex"
 	flattenedSuffix = ".flattened"
 
+	// variant names each of which is for a packaging method
 	imageApexType     = "image"
 	zipApexType       = "zip"
 	flattenedApexType = "flattened"
@@ -46,33 +990,1168 @@
 	f2fsFsType = "f2fs"
 )
 
-type dependencyTag struct {
-	blueprint.BaseDependencyTag
-	name string
+// The suffix for the output "file", not the module
+func (a apexPackaging) suffix() string {
+	switch a {
+	case imageApex:
+		return imageApexSuffix
+	case zipApex:
+		return zipApexSuffix
+	default:
+		panic(fmt.Errorf("unknown APEX type %d", a))
+	}
+}
 
-	// determines if the dependent will be part of the APEX payload
-	payload bool
+func (a apexPackaging) name() string {
+	switch a {
+	case imageApex:
+		return imageApexType
+	case zipApex:
+		return zipApexType
+	default:
+		panic(fmt.Errorf("unknown APEX type %d", a))
+	}
+}
+
+// apexFlattenedMutator creates one or more variations each of which is for a packaging method.
+// TODO(jiyong): give a better name to this mutator
+func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if ab, ok := mctx.Module().(*apexBundle); ok {
+		var variants []string
+		switch proptools.StringDefault(ab.properties.Payload_type, "image") {
+		case "image":
+			// This is the normal case. Note that both image and flattend APEXes are
+			// created. The image type is installed to the system partition, while the
+			// flattened APEX is (optionally) installed to the system_ext partition.
+			// This is mostly for GSI which has to support wide range of devices. If GSI
+			// is installed on a newer (APEX-capable) device, the image APEX in the
+			// system will be used. However, if the same GSI is installed on an old
+			// device which can't support image APEX, the flattened APEX in the
+			// system_ext partion (which still is part of GSI) is used instead.
+			variants = append(variants, imageApexType, flattenedApexType)
+		case "zip":
+			variants = append(variants, zipApexType)
+		case "both":
+			variants = append(variants, imageApexType, zipApexType, flattenedApexType)
+		default:
+			mctx.PropertyErrorf("payload_type", "%q is not one of \"image\", \"zip\", or \"both\".", *ab.properties.Payload_type)
+			return
+		}
+
+		modules := mctx.CreateLocalVariations(variants...)
+
+		for i, v := range variants {
+			switch v {
+			case imageApexType:
+				modules[i].(*apexBundle).properties.ApexType = imageApex
+			case zipApexType:
+				modules[i].(*apexBundle).properties.ApexType = zipApex
+			case flattenedApexType:
+				modules[i].(*apexBundle).properties.ApexType = flattenedApex
+				// See the comment above for why system_ext.
+				if !mctx.Config().FlattenApex() && ab.Platform() {
+					modules[i].(*apexBundle).MakeAsSystemExt()
+				}
+			}
+		}
+	} else if _, ok := mctx.Module().(*OverrideApex); ok {
+		// payload_type is forcibly overridden to "image"
+		// TODO(jiyong): is this the right decision?
+		mctx.CreateVariations(imageApexType, flattenedApexType)
+	}
+}
+
+// checkUseVendorProperty checks if the use of `use_vendor` property is allowed for the given APEX.
+// When use_vendor is used, native modules are built with __ANDROID_VNDK__ and __ANDROID_APEX__,
+// which may cause compatibility issues. (e.g. libbinder) Even though libbinder restricts its
+// availability via 'apex_available' property and relies on yet another macro
+// __ANDROID_APEX_<NAME>__, we restrict usage of "use_vendor:" from other APEX modules to avoid
+// similar problems.
+func checkUseVendorProperty(ctx android.BottomUpMutatorContext, a *apexBundle) {
+	if proptools.Bool(a.properties.Use_vendor) && !android.InList(a.Name(), useVendorAllowList(ctx.Config())) {
+		ctx.PropertyErrorf("use_vendor", "not allowed to set use_vendor: true")
+	}
 }
 
 var (
-	sharedLibTag   = dependencyTag{name: "sharedLib", payload: true}
-	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
-	executableTag  = dependencyTag{name: "executable", payload: true}
-	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
-	prebuiltTag    = dependencyTag{name: "prebuilt", payload: true}
-	testTag        = dependencyTag{name: "test", payload: true}
-	keyTag         = dependencyTag{name: "key"}
-	certificateTag = dependencyTag{name: "certificate"}
-	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
-	rroTag         = dependencyTag{name: "rro", payload: true}
-	bpfTag         = dependencyTag{name: "bpf", payload: true}
-	testForTag     = dependencyTag{name: "test for"}
+	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
+)
 
-	apexAvailBaseline = makeApexAvailableBaseline()
+func useVendorAllowList(config android.Config) []string {
+	return config.Once(useVendorAllowListKey, func() interface{} {
+		return []string{
+			// swcodec uses "vendor" variants for smaller size
+			"com.android.media.swcodec",
+			"test_com.android.media.swcodec",
+		}
+	}).([]string)
+}
 
+// setUseVendorAllowListForTest overrides useVendorAllowList and must be called before the first
+// call to useVendorAllowList()
+func setUseVendorAllowListForTest(config android.Config, allowList []string) {
+	config.Once(useVendorAllowListKey, func() interface{} {
+		return allowList
+	})
+}
+
+var _ android.DepIsInSameApex = (*apexBundle)(nil)
+
+// Implements android.DepInInSameApex
+func (a *apexBundle) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	// direct deps of an APEX bundle are all part of the APEX bundle
+	// TODO(jiyong): shouldn't we look into the payload field of the dependencyTag?
+	return true
+}
+
+var _ android.OutputFileProducer = (*apexBundle)(nil)
+
+// Implements android.OutputFileProducer
+func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "", android.DefaultDistTag:
+		// This is the default dist path.
+		return android.Paths{a.outputFile}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+var _ cc.Coverage = (*apexBundle)(nil)
+
+// Implements cc.Coverage
+func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
+	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) PreventInstall() {
+	a.properties.PreventInstall = true
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) HideFromMake() {
+	a.properties.HideFromMake = true
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) MarkAsCoverageVariant(coverage bool) {
+	a.properties.IsCoverageVariant = coverage
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) EnableCoverageIfNeeded() {}
+
+var _ android.ApexBundleDepsInfoIntf = (*apexBundle)(nil)
+
+// Implements android.ApexBudleDepsInfoIntf
+func (a *apexBundle) Updatable() bool {
+	return proptools.Bool(a.properties.Updatable)
+}
+
+// getCertString returns the name of the cert that should be used to sign this APEX. This is
+// basically from the "certificate" property, but could be overridden by the device config.
+func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
+	moduleName := ctx.ModuleName()
+	// VNDK APEXes share the same certificate. To avoid adding a new VNDK version to the
+	// OVERRIDE_* list, we check with the pseudo module name to see if its certificate is
+	// overridden.
+	if a.vndkApex {
+		moduleName = vndkApexName
+	}
+	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
+	if overridden {
+		return ":" + certificate
+	}
+	return String(a.properties.Certificate)
+}
+
+// See the installable property
+func (a *apexBundle) installable() bool {
+	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
+}
+
+// See the test_only_no_hashtree property
+func (a *apexBundle) testOnlyShouldSkipHashtreeGeneration() bool {
+	return proptools.Bool(a.properties.Test_only_no_hashtree)
+}
+
+// See the test_only_unsigned_payload property
+func (a *apexBundle) testOnlyShouldSkipPayloadSign() bool {
+	return proptools.Bool(a.properties.Test_only_unsigned_payload)
+}
+
+// These functions are interfacing with cc/sanitizer.go. The entire APEX (along with all of its
+// members) can be sanitized, either forcibly, or by the global configuration. For some of the
+// sanitizers, extra dependencies can be forcibly added as well.
+
+func (a *apexBundle) EnableSanitizer(sanitizerName string) {
+	if !android.InList(sanitizerName, a.properties.SanitizerNames) {
+		a.properties.SanitizerNames = append(a.properties.SanitizerNames, sanitizerName)
+	}
+}
+
+func (a *apexBundle) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
+	if android.InList(sanitizerName, a.properties.SanitizerNames) {
+		return true
+	}
+
+	// Then follow the global setting
+	globalSanitizerNames := []string{}
+	if a.Host() {
+		globalSanitizerNames = ctx.Config().SanitizeHost()
+	} else {
+		arches := ctx.Config().SanitizeDeviceArch()
+		if len(arches) == 0 || android.InList(a.Arch().ArchType.Name, arches) {
+			globalSanitizerNames = ctx.Config().SanitizeDevice()
+		}
+	}
+	return android.InList(sanitizerName, globalSanitizerNames)
+}
+
+func (a *apexBundle) AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string) {
+	// TODO(jiyong): move this info (the sanitizer name, the lib name, etc.) to cc/sanitize.go
+	// Keep only the mechanism here.
+	if ctx.Device() && sanitizerName == "hwaddress" && strings.HasPrefix(a.Name(), "com.android.runtime") {
+		imageVariation := a.getImageVariation(ctx)
+		for _, target := range ctx.MultiTargets() {
+			if target.Arch.ArchType.Multilib == "lib64" {
+				addDependenciesForNativeModules(ctx, ApexNativeDependencies{
+					Native_shared_libs: []string{"libclang_rt.hwasan-aarch64-android"},
+					Tests:              nil,
+					Jni_libs:           nil,
+					Binaries:           nil,
+				}, target, imageVariation)
+				break
+			}
+		}
+	}
+}
+
+// apexFileFor<Type> functions below create an apexFile struct for a given Soong module. The
+// returned apexFile saves information about the Soong module that will be used for creating the
+// build rules.
+func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile {
+	// Decide the APEX-local directory by the multilib of the library In the future, we may
+	// query this to the module.
+	// TODO(jiyong): use the new PackagingSpec
+	var dirInApex string
+	switch ccMod.Arch().ArchType.Multilib {
+	case "lib32":
+		dirInApex = "lib"
+	case "lib64":
+		dirInApex = "lib64"
+	}
+	if ccMod.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, ccMod.Target().NativeBridgeRelativePath)
+	}
+	dirInApex = filepath.Join(dirInApex, ccMod.RelativeInstallPath())
+	if handleSpecialLibs && cc.InstallToBootstrap(ccMod.BaseModuleName(), ctx.Config()) {
+		// Special case for Bionic libs and other libs installed with them. This is to
+		// prevent those libs from being included in the search path
+		// /apex/com.android.runtime/${LIB}. This exclusion is required because those libs
+		// in the Runtime APEX are available via the legacy paths in /system/lib/. By the
+		// init process, the libs in the APEX are bind-mounted to the legacy paths and thus
+		// will be loaded into the default linker namespace (aka "platform" namespace). If
+		// the libs are directly in /apex/com.android.runtime/${LIB} then the same libs will
+		// be loaded again into the runtime linker namespace, which will result in double
+		// loading of them, which isn't supported.
+		dirInApex = filepath.Join(dirInApex, "bionic")
+	}
+
+	fileToCopy := ccMod.OutputFile().Path()
+	androidMkModuleName := ccMod.BaseModuleName() + ccMod.Properties.SubName
+	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, ccMod)
+}
+
+func apexFileForExecutable(ctx android.BaseModuleContext, cc *cc.Module) apexFile {
+	dirInApex := "bin"
+	if cc.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
+	}
+	dirInApex = filepath.Join(dirInApex, cc.RelativeInstallPath())
+	fileToCopy := cc.OutputFile().Path()
+	androidMkModuleName := cc.BaseModuleName() + cc.Properties.SubName
+	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, cc)
+	af.symlinks = cc.Symlinks()
+	af.dataPaths = cc.DataPaths()
+	return af
+}
+
+func apexFileForRustExecutable(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	dirInApex := "bin"
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, rustm)
+	return af
+}
+
+func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	// Decide the APEX-local directory by the multilib of the library
+	// In the future, we may query this to the module.
+	var dirInApex string
+	switch rustm.Arch().ArchType.Multilib {
+	case "lib32":
+		dirInApex = "lib"
+	case "lib64":
+		dirInApex = "lib64"
+	}
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
+}
+
+func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
+	dirInApex := "bin"
+	fileToCopy := py.HostToolPath().Path()
+	return newApexFile(ctx, fileToCopy, py.BaseModuleName(), dirInApex, pyBinary, py)
+}
+
+func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
+	dirInApex := "bin"
+	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
+	if err != nil {
+		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
+		return apexFile{}
+	}
+	fileToCopy := android.PathForOutput(ctx, s)
+	// NB: Since go binaries are static we don't need the module for anything here, which is
+	// good since the go tool is a blueprint.Module not an android.Module like we would
+	// normally use.
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, goBinary, nil)
+}
+
+func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
+	dirInApex := filepath.Join("bin", sh.SubDir())
+	fileToCopy := sh.OutputFile()
+	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
+	af.symlinks = sh.Symlinks()
+	return af
+}
+
+func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, depName string) apexFile {
+	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
+	fileToCopy := prebuilt.OutputFile()
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, prebuilt)
+}
+
+func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
+	dirInApex := filepath.Join("etc", config.SubDir())
+	fileToCopy := config.CompatConfig()
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, config)
+}
+
+// javaModule is an interface to handle all Java modules (java_library, dex_import, etc) in the same
+// way.
+type javaModule interface {
+	android.Module
+	BaseModuleName() string
+	DexJarBuildPath() android.Path
+	JacocoReportClassesFile() android.Path
+	LintDepSets() java.LintDepSets
+	Stem() string
+}
+
+var _ javaModule = (*java.Library)(nil)
+var _ javaModule = (*java.SdkLibrary)(nil)
+var _ javaModule = (*java.DexImport)(nil)
+var _ javaModule = (*java.SdkLibraryImport)(nil)
+
+func apexFileForJavaModule(ctx android.BaseModuleContext, module javaModule) apexFile {
+	dirInApex := "javalib"
+	fileToCopy := module.DexJarBuildPath()
+	af := newApexFile(ctx, fileToCopy, module.BaseModuleName(), dirInApex, javaSharedLib, module)
+	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
+	af.lintDepSets = module.LintDepSets()
+	af.customStem = module.Stem() + ".jar"
+	return af
+}
+
+// androidApp is an interface to handle all app modules (android_app, android_app_import, etc.) in
+// the same way.
+type androidApp interface {
+	android.Module
+	Privileged() bool
+	InstallApkName() string
+	OutputFile() android.Path
+	JacocoReportClassesFile() android.Path
+	Certificate() java.Certificate
+	BaseModuleName() string
+}
+
+var _ androidApp = (*java.AndroidApp)(nil)
+var _ androidApp = (*java.AndroidAppImport)(nil)
+
+func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp androidApp) apexFile {
+	appDir := "app"
+	if aapp.Privileged() {
+		appDir = "priv-app"
+	}
+	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
+	fileToCopy := aapp.OutputFile()
+	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
+	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
+	af.certificate = aapp.Certificate()
+
+	if app, ok := aapp.(interface {
+		OverriddenManifestPackageName() string
+	}); ok {
+		af.overriddenPackageName = app.OverriddenManifestPackageName()
+	}
+	return af
+}
+
+func apexFileForRuntimeResourceOverlay(ctx android.BaseModuleContext, rro java.RuntimeResourceOverlayModule) apexFile {
+	rroDir := "overlay"
+	dirInApex := filepath.Join(rroDir, rro.Theme())
+	fileToCopy := rro.OutputFile()
+	af := newApexFile(ctx, fileToCopy, rro.Name(), dirInApex, app, rro)
+	af.certificate = rro.Certificate()
+
+	if a, ok := rro.(interface {
+		OverriddenManifestPackageName() string
+	}); ok {
+		af.overriddenPackageName = a.OverriddenManifestPackageName()
+	}
+	return af
+}
+
+func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile {
+	dirInApex := filepath.Join("etc", "bpf")
+	return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram)
+}
+
+// WalyPayloadDeps visits dependencies that contributes to the payload of this APEX. For each of the
+// visited module, the `do` callback is executed. Returning true in the callback continues the visit
+// to the child modules. Returning false makes the visit to continue in the sibling or the parent
+// modules. This is used in check* functions below.
+func (a *apexBundle) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		am, ok := child.(android.ApexModule)
+		if !ok || !am.CanHaveApexVariants() {
+			return false
+		}
+
+		// Filter-out unwanted depedendencies
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+		if dt, ok := depTag.(dependencyTag); ok && !dt.payload {
+			return false
+		}
+
+		ai := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
+		externalDep := !android.InList(ctx.ModuleName(), ai.InApexes)
+
+		// Visit actually
+		return do(ctx, parent, am, externalDep)
+	})
+}
+
+// filesystem type of the apex_payload.img inside the APEX. Currently, ext4 and f2fs are supported.
+type fsType int
+
+const (
+	ext4 fsType = iota
+	f2fs
+)
+
+func (f fsType) string() string {
+	switch f {
+	case ext4:
+		return ext4FsType
+	case f2fs:
+		return f2fsFsType
+	default:
+		panic(fmt.Errorf("unknown APEX payload type %d", f))
+	}
+}
+
+// Creates build rules for an APEX. It consists of the following major steps:
+//
+// 1) do some validity checks such as apex_available, min_sdk_version, etc.
+// 2) traverse the dependency tree to collect apexFile structs from them.
+// 3) some fields in apexBundle struct are configured
+// 4) generate the build rules to create the APEX. This is mostly done in builder.go.
+func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 1) do some validity checks such as apex_available, min_sdk_version, etc.
+	a.checkApexAvailability(ctx)
+	a.checkUpdatable(ctx)
+	a.checkMinSdkVersion(ctx)
+	a.checkStaticLinkingToStubLibraries(ctx)
+	if len(a.properties.Tests) > 0 && !a.testApex {
+		ctx.PropertyErrorf("tests", "property allowed only in apex_test module type")
+		return
+	}
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 2) traverse the dependency tree to collect apexFile structs from them.
+
+	// all the files that will be included in this APEX
+	var filesInfo []apexFile
+
+	// native lib dependencies
+	var provideNativeLibs []string
+	var requireNativeLibs []string
+
+	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
+
+	// TODO(jiyong): do this using WalkPayloadDeps
+	// TODO(jiyong): make this clean!!!
+	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+		depName := ctx.OtherModuleName(child)
+		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
+			switch depTag {
+			case sharedLibTag, jniLibTag:
+				isJniLib := depTag == jniLibTag
+				if c, ok := child.(*cc.Module); ok {
+					fi := apexFileForNativeLibrary(ctx, c, handleSpecialLibs)
+					fi.isJniLib = isJniLib
+					filesInfo = append(filesInfo, fi)
+					// Collect the list of stub-providing libs except:
+					// - VNDK libs are only for vendors
+					// - bootstrap bionic libs are treated as provided by system
+					if c.HasStubsVariants() && !a.vndkApex && !cc.InstallToBootstrap(c.BaseModuleName(), ctx.Config()) {
+						provideNativeLibs = append(provideNativeLibs, fi.stem())
+					}
+					return true // track transitive dependencies
+				} else {
+					propertyName := "native_shared_libs"
+					if isJniLib {
+						propertyName = "jni_libs"
+					}
+					ctx.PropertyErrorf(propertyName, "%q is not a cc_library or cc_library_shared module", depName)
+				}
+			case executableTag:
+				if cc, ok := child.(*cc.Module); ok {
+					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
+					return true // track transitive dependencies
+				} else if sh, ok := child.(*sh.ShBinary); ok {
+					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
+				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
+					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
+				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
+					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
+				} else if rust, ok := child.(*rust.Module); ok {
+					filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
+					return true // track transitive dependencies
+				} else {
+					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+				}
+			case javaLibTag:
+				switch child.(type) {
+				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport:
+					af := apexFileForJavaModule(ctx, child.(javaModule))
+					if !af.ok() {
+						ctx.PropertyErrorf("java_libs", "%q is not configured to be compiled into dex", depName)
+						return false
+					}
+					filesInfo = append(filesInfo, af)
+					return true // track transitive dependencies
+				default:
+					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
+				}
+			case androidAppTag:
+				if ap, ok := child.(*java.AndroidApp); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+					return true // track transitive dependencies
+				} else if ap, ok := child.(*java.AndroidAppImport); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+				} else if ap, ok := child.(*java.AndroidTestHelperApp); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+				} else if ap, ok := child.(*java.AndroidAppSet); ok {
+					appDir := "app"
+					if ap.Privileged() {
+						appDir = "priv-app"
+					}
+					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(),
+						filepath.Join(appDir, ap.BaseModuleName()), appSet, ap)
+					af.certificate = java.PresignedCertificate
+					filesInfo = append(filesInfo, af)
+				} else {
+					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
+				}
+			case rroTag:
+				if rro, ok := child.(java.RuntimeResourceOverlayModule); ok {
+					filesInfo = append(filesInfo, apexFileForRuntimeResourceOverlay(ctx, rro))
+				} else {
+					ctx.PropertyErrorf("rros", "%q is not an runtime_resource_overlay module", depName)
+				}
+			case bpfTag:
+				if bpfProgram, ok := child.(bpf.BpfModule); ok {
+					filesToCopy, _ := bpfProgram.OutputFiles("")
+					for _, bpfFile := range filesToCopy {
+						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram))
+					}
+				} else {
+					ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName)
+				}
+			case prebuiltTag:
+				if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
+					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
+				} else if prebuilt, ok := child.(java.PlatformCompatConfigIntf); ok {
+					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, prebuilt, depName))
+				} else {
+					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc and not a platform_compat_config module", depName)
+				}
+			case testTag:
+				if ccTest, ok := child.(*cc.Module); ok {
+					if ccTest.IsTestPerSrcAllTestsVariation() {
+						// Multiple-output test module (where `test_per_src: true`).
+						//
+						// `ccTest` is the "" ("all tests") variation of a `test_per_src` module.
+						// We do not add this variation to `filesInfo`, as it has no output;
+						// however, we do add the other variations of this module as indirect
+						// dependencies (see below).
+					} else {
+						// Single-output test module (where `test_per_src: false`).
+						af := apexFileForExecutable(ctx, ccTest)
+						af.class = nativeTest
+						filesInfo = append(filesInfo, af)
+					}
+					return true // track transitive dependencies
+				} else {
+					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
+				}
+			case keyTag:
+				if key, ok := child.(*apexKey); ok {
+					a.private_key_file = key.private_key_file
+					a.public_key_file = key.public_key_file
+				} else {
+					ctx.PropertyErrorf("key", "%q is not an apex_key module", depName)
+				}
+				return false
+			case certificateTag:
+				if dep, ok := child.(*java.AndroidAppCertificate); ok {
+					a.container_certificate_file = dep.Certificate.Pem
+					a.container_private_key_file = dep.Certificate.Key
+				} else {
+					ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
+				}
+			case android.PrebuiltDepTag:
+				// If the prebuilt is force disabled, remember to delete the prebuilt file
+				// that might have been installed in the previous builds
+				if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
+					a.prebuiltFileToDelete = prebuilt.InstallFilename()
+				}
+			}
+		} else if !a.vndkApex {
+			// indirect dependencies
+			if am, ok := child.(android.ApexModule); ok {
+				// We cannot use a switch statement on `depTag` here as the checked
+				// tags used below are private (e.g. `cc.sharedDepTag`).
+				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
+					if cc, ok := child.(*cc.Module); ok {
+						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
+							requireNativeLibs = append(requireNativeLibs, ":vndk")
+							return false
+						}
+						af := apexFileForNativeLibrary(ctx, cc, handleSpecialLibs)
+						af.transitiveDep = true
+						abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
+						if !a.Host() && !abInfo.Contents.DirectlyInApex(depName) && (cc.IsStubs() || cc.HasStubsVariants()) {
+							// If the dependency is a stubs lib, don't include it in this APEX,
+							// but make sure that the lib is installed on the device.
+							// In case no APEX is having the lib, the lib is installed to the system
+							// partition.
+							//
+							// Always include if we are a host-apex however since those won't have any
+							// system libraries.
+							if !am.DirectlyInAnyApex() {
+								// we need a module name for Make
+								name := cc.ImplementationModuleNameForMake(ctx)
+
+								if !proptools.Bool(a.properties.Use_vendor) {
+									// we don't use subName(.vendor) for a "use_vendor: true" apex
+									// which is supposed to be installed in /system
+									name += cc.Properties.SubName
+								}
+								if !android.InList(name, a.requiredDeps) {
+									a.requiredDeps = append(a.requiredDeps, name)
+								}
+							}
+							requireNativeLibs = append(requireNativeLibs, af.stem())
+							// Don't track further
+							return false
+						}
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if cc.IsTestPerSrcDepTag(depTag) {
+					if cc, ok := child.(*cc.Module); ok {
+						af := apexFileForExecutable(ctx, cc)
+						// Handle modules created as `test_per_src` variations of a single test module:
+						// use the name of the generated test binary (`fileToCopy`) instead of the name
+						// of the original test module (`depName`, shared by all `test_per_src`
+						// variations of that module).
+						af.androidMkModuleName = filepath.Base(af.builtFile.String())
+						// these are not considered transitive dep
+						af.transitiveDep = false
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if java.IsJniDepTag(depTag) {
+					// Because APK-in-APEX embeds jni_libs transitively, we don't need to track transitive deps
+					return false
+				} else if java.IsXmlPermissionsFileDepTag(depTag) {
+					if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
+						filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
+					}
+				} else if rust.IsDylibDepTag(depTag) {
+					if rustm, ok := child.(*rust.Module); ok && rustm.IsInstallableToApex() {
+						af := apexFileForRustLibrary(ctx, rustm)
+						af.transitiveDep = true
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
+					// nothing
+				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
+					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
+				}
+			}
+		}
+		return false
+	})
+	if a.private_key_file == nil {
+		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.properties.Key))
+		return
+	}
+
+	// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries. Build rules are
+	// generated by the dexpreopt singleton, and here we access build artifacts via the global
+	// boot image config.
+	if a.artApex {
+		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
+			dirInApex := filepath.Join("javalib", arch.String())
+			for _, f := range files {
+				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
+				af := newApexFile(ctx, f, localModule, dirInApex, etc, nil)
+				filesInfo = append(filesInfo, af)
+			}
+		}
+	}
+
+	// Remove duplicates in filesInfo
+	removeDup := func(filesInfo []apexFile) []apexFile {
+		encountered := make(map[string]apexFile)
+		for _, f := range filesInfo {
+			dest := filepath.Join(f.installDir, f.builtFile.Base())
+			if e, ok := encountered[dest]; !ok {
+				encountered[dest] = f
+			} else {
+				// If a module is directly included and also transitively depended on
+				// consider it as directly included.
+				e.transitiveDep = e.transitiveDep && f.transitiveDep
+				encountered[dest] = e
+			}
+		}
+		var result []apexFile
+		for _, v := range encountered {
+			result = append(result, v)
+		}
+		return result
+	}
+	filesInfo = removeDup(filesInfo)
+
+	// Sort to have consistent build rules
+	sort.Slice(filesInfo, func(i, j int) bool {
+		return filesInfo[i].builtFile.String() < filesInfo[j].builtFile.String()
+	})
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 3) some fields in apexBundle struct are configured
+	a.installDir = android.PathForModuleInstall(ctx, "apex")
+	a.filesInfo = filesInfo
+
+	// Set suffix and primaryApexType depending on the ApexType
+	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
+	switch a.properties.ApexType {
+	case imageApex:
+		if buildFlattenedAsDefault {
+			a.suffix = imageApexSuffix
+		} else {
+			a.suffix = ""
+			a.primaryApexType = true
+
+			if ctx.Config().InstallExtraFlattenedApexes() {
+				a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix)
+			}
+		}
+	case zipApex:
+		if proptools.String(a.properties.Payload_type) == "zip" {
+			a.suffix = ""
+			a.primaryApexType = true
+		} else {
+			a.suffix = zipApexSuffix
+		}
+	case flattenedApex:
+		if buildFlattenedAsDefault {
+			a.suffix = ""
+			a.primaryApexType = true
+		} else {
+			a.suffix = flattenedSuffix
+		}
+	}
+
+	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
+	case ext4FsType:
+		a.payloadFsType = ext4
+	case f2fsFsType:
+		a.payloadFsType = f2fs
+	default:
+		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
+	}
+
+	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
+	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
+	// the same library in the system partition, thus effectively sharing the same libraries
+	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
+	// in the APEX.
+	a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable() && !proptools.Bool(a.properties.Use_vendor)
+
+	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
+	// So we can't link them to /system/lib libs which are core variants.
+	if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
+		a.linkToSystemLib = false
+	}
+
+	// We don't need the optimization for updatable APEXes, as it might give false signal
+	// to the system health when the APEXes are still bundled (b/149805758)
+	if a.Updatable() && a.properties.ApexType == imageApex {
+		a.linkToSystemLib = false
+	}
+
+	// We also don't want the optimization for host APEXes, because it doesn't make sense.
+	if ctx.Host() {
+		a.linkToSystemLib = false
+	}
+
+	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 4) generate the build rules to create the APEX. This is done in builder.go.
+	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
+	if a.properties.ApexType == flattenedApex {
+		a.buildFlattenedApex(ctx)
+	} else {
+		a.buildUnflattenedApex(ctx)
+	}
+	a.buildApexDependencyInfo(ctx)
+	a.buildLintReports(ctx)
+
+	// Append meta-files to the filesInfo list so that they are reflected in Android.mk as well.
+	if a.installable() {
+		// For flattened APEX, make sure that APEX manifest and apex_pubkey are also copied
+		// along with other ordinary files. (Note that this is done by apexer for
+		// non-flattened APEXes)
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
+
+		// Place the public key as apex_pubkey. This is also done by apexer for
+		// non-flattened APEXes case.
+		// TODO(jiyong): Why do we need this CP rule?
+		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  a.public_key_file,
+			Output: copiedPubkey,
+		})
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Factory functions
+//
+
+func newApexBundle() *apexBundle {
+	module := &apexBundle{}
+
+	module.AddProperties(&module.properties)
+	module.AddProperties(&module.targetProperties)
+	module.AddProperties(&module.overridableProperties)
+
+	android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitSdkAwareModule(module)
+	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
+	return module
+}
+
+func ApexBundleFactory(testApex bool, artApex bool) android.Module {
+	bundle := newApexBundle()
+	bundle.testApex = testApex
+	bundle.artApex = artApex
+	return bundle
+}
+
+// apex_test is an APEX for testing. The difference from the ordinary apex module type is that
+// certain compatibility checks such as apex_available are not done for apex_test.
+func testApexBundleFactory() android.Module {
+	bundle := newApexBundle()
+	bundle.testApex = true
+	return bundle
+}
+
+// apex packages other modules into an APEX file which is a packaging format for system-level
+// components like binaries, shared libraries, etc.
+func BundleFactory() android.Module {
+	return newApexBundle()
+}
+
+type Defaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+// apex_defaults provides defaultable properties to other apex modules.
+func defaultsFactory() android.Module {
+	return DefaultsFactory()
+}
+
+func DefaultsFactory(props ...interface{}) android.Module {
+	module := &Defaults{}
+
+	module.AddProperties(props...)
+	module.AddProperties(
+		&apexBundleProperties{},
+		&apexTargetBundleProperties{},
+		&overridableProperties{},
+	)
+
+	android.InitDefaultsModule(module)
+	return module
+}
+
+type OverrideApex struct {
+	android.ModuleBase
+	android.OverrideModuleBase
+}
+
+func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// All the overrides happen in the base module.
+}
+
+// override_apex is used to create an apex module based on another apex module by overriding some of
+// its properties.
+func overrideApexFactory() android.Module {
+	m := &OverrideApex{}
+
+	m.AddProperties(&overridableProperties{})
+
+	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	android.InitOverrideModule(m)
+	return m
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Vality check routines
+//
+// These are called in at the very beginning of GenerateAndroidBuildActions to flag an error when
+// certain conditions are not met.
+//
+// TODO(jiyong): move these checks to a separate go file.
+
+// Entures that min_sdk_version of the included modules are equal or less than the min_sdk_version
+// of this apexBundle.
+func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
+	if a.testApex || a.vndkApex {
+		return
+	}
+	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
+	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
+		return
+	}
+	// apexBundle::minSdkVersion reports its own errors.
+	minSdkVersion := a.minSdkVersion(ctx)
+	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+}
+
+func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
+	ver := proptools.String(a.properties.Min_sdk_version)
+	if ver == "" {
+		return android.FutureApiLevel
+	}
+	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
+	if err != nil {
+		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
+		return android.NoneApiLevel
+	}
+	if apiLevel.IsPreview() {
+		// All codenames should build against "current".
+		return android.FutureApiLevel
+	}
+	return apiLevel
+}
+
+// Ensures that a lib providing stub isn't statically linked
+func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext) {
+	// Practically, we only care about regular APEXes on the device.
+	if ctx.Host() || a.testApex || a.vndkApex {
+		return
+	}
+
+	abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
+
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+		if ccm, ok := to.(*cc.Module); ok {
+			apexName := ctx.ModuleName()
+			fromName := ctx.OtherModuleName(from)
+			toName := ctx.OtherModuleName(to)
+
+			// If `to` is not actually in the same APEX as `from` then it does not need
+			// apex_available and neither do any of its dependencies.
+			if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+				// As soon as the dependency graph crosses the APEX boundary, don't go further.
+				return false
+			}
+
+			// The dynamic linker and crash_dump tool in the runtime APEX is the only
+			// exception to this rule. It can't make the static dependencies dynamic
+			// because it can't do the dynamic linking for itself.
+			// Same rule should be applied to linkerconfig, because it should be executed
+			// only with static linked libraries before linker is available with ld.config.txt
+			if apexName == "com.android.runtime" && (fromName == "linker" || fromName == "crash_dump" || fromName == "linkerconfig") {
+				return false
+			}
+
+			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
+			if isStubLibraryFromOtherApex && !externalDep {
+				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
+					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
+			}
+
+		}
+		return true
+	})
+}
+
+// Enforce that Java deps of the apex are using stable SDKs to compile
+func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
+	if a.Updatable() {
+		if String(a.properties.Min_sdk_version) == "" {
+			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
+		}
+		a.checkJavaStableSdkVersion(ctx)
+	}
+}
+
+func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
+	// Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs,
+	// java's checkLinkType guarantees correct usage for transitive deps
+	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		switch tag {
+		case javaLibTag, androidAppTag:
+			if m, ok := module.(interface{ CheckStableSdkVersion() error }); ok {
+				if err := m.CheckStableSdkVersion(); err != nil {
+					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
+				}
+			}
+		}
+	})
+}
+
+// Ensures that the all the dependencies are marked as available for this APEX
+func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) {
+	// Let's be practical. Availability for test, host, and the VNDK apex isn't important
+	if ctx.Host() || a.testApex || a.vndkApex {
+		return
+	}
+
+	// Because APEXes targeting other than system/system_ext partitions can't set
+	// apex_available, we skip checks for these APEXes
+	if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
+		return
+	}
+
+	// Coverage build adds additional dependencies for the coverage-only runtime libraries.
+	// Requiring them and their transitive depencies with apex_available is not right
+	// because they just add noise.
+	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || a.IsNativeCoverageNeeded(ctx) {
+		return
+	}
+
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+		// As soon as the dependency graph crosses the APEX boundary, don't go further.
+		if externalDep {
+			return false
+		}
+
+		apexName := ctx.ModuleName()
+		fromName := ctx.OtherModuleName(from)
+		toName := ctx.OtherModuleName(to)
+
+		// If `to` is not actually in the same APEX as `from` then it does not need
+		// apex_available and neither do any of its dependencies.
+		if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+			// As soon as the dependency graph crosses the APEX boundary, don't go
+			// further.
+			return false
+		}
+
+		if to.AvailableFor(apexName) || baselineApexAvailable(apexName, toName) {
+			return true
+		}
+		ctx.ModuleErrorf("%q requires %q that doesn't list the APEX under 'apex_available'. Dependency path:%s",
+			fromName, toName, ctx.GetPathString(true))
+		// Visit this module's dependencies to check and report any issues with their availability.
+		return true
+	})
+}
+
+var (
+	apexAvailBaseline        = makeApexAvailableBaseline()
 	inverseApexAvailBaseline = invertApexBaseline(apexAvailBaseline)
 )
 
+func baselineApexAvailable(apex, moduleName string) bool {
+	key := apex
+	moduleName = normalizeModuleName(moduleName)
+
+	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
+		return true
+	}
+
+	key = android.AvailableToAnyApex
+	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
+		return true
+	}
+
+	return false
+}
+
+func normalizeModuleName(moduleName string) string {
+	// Prebuilt modules (e.g. java_import, etc.) have "prebuilt_" prefix added by the build
+	// system. Trim the prefix for the check since they are confusing
+	moduleName = strings.TrimPrefix(moduleName, "prebuilt_")
+	if strings.HasPrefix(moduleName, "libclang_rt.") {
+		// This module has many arch variants that depend on the product being built.
+		// We don't want to list them all
+		moduleName = "libclang_rt"
+	}
+	if strings.HasPrefix(moduleName, "androidx.") {
+		// TODO(b/156996905) Set apex_available/min_sdk_version for androidx support libraries
+		moduleName = "androidx"
+	}
+	return moduleName
+}
+
 // Transform the map of apex -> modules to module -> apexes.
 func invertApexBaseline(m map[string][]string) map[string][]string {
 	r := make(map[string][]string)
@@ -89,9 +2168,8 @@
 	return inverseApexAvailBaseline[normalizeModuleName(moduleName)]
 }
 
-// This is a map from apex to modules, which overrides the
-// apex_available setting for that particular module to make
-// it available for the apex regardless of its setting.
+// This is a map from apex to modules, which overrides the apex_available setting for that
+// particular module to make it available for the apex regardless of its setting.
 // TODO(b/147364041): remove this
 func makeApexAvailableBaseline() map[string][]string {
 	// The "Module separator"s below are employed to minimize merge conflicts.
@@ -670,6 +2748,26 @@
 	return m
 }
 
+func init() {
+	android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
+	android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
+}
+
+func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
+	rules := make([]android.Rule, 0, len(modules_packages))
+	for module_name, module_packages := range modules_packages {
+		permitted_packages_rule := android.NeverAllow().
+			BootclasspathJar().
+			With("apex_available", module_name).
+			WithMatcher("permitted_packages", android.NotInList(module_packages)).
+			Because("jars that are part of the " + module_name +
+				" module may only allow these packages: " + strings.Join(module_packages, ",") +
+				". Please jarjar or move code around.")
+		rules = append(rules, permitted_packages_rule)
+	}
+	return rules
+}
+
 // DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func qModulesPackages() map[string][]string {
@@ -718,1900 +2816,3 @@
 		},
 	}
 }
-
-func init() {
-	android.RegisterModuleType("apex", BundleFactory)
-	android.RegisterModuleType("apex_test", testApexBundleFactory)
-	android.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
-	android.RegisterModuleType("apex_defaults", defaultsFactory)
-	android.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	android.RegisterModuleType("override_apex", overrideApexFactory)
-	android.RegisterModuleType("apex_set", apexSetFactory)
-
-	android.PreDepsMutators(RegisterPreDepsMutators)
-	android.PostDepsMutators(RegisterPostDepsMutators)
-
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
-}
-
-func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
-	rules := make([]android.Rule, 0, len(modules_packages))
-	for module_name, module_packages := range modules_packages {
-		permitted_packages_rule := android.NeverAllow().
-			BootclasspathJar().
-			With("apex_available", module_name).
-			WithMatcher("permitted_packages", android.NotInList(module_packages)).
-			Because("jars that are part of the " + module_name +
-				" module may only allow these packages: " + strings.Join(module_packages, ",") +
-				". Please jarjar or move code around.")
-		rules = append(rules, permitted_packages_rule)
-	}
-	return rules
-}
-
-func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
-	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
-}
-
-func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.TopDown("apex_deps", apexDepsMutator).Parallel()
-	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
-	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
-	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
-	ctx.BottomUp("apex", apexMutator).Parallel()
-	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
-	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
-	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
-}
-
-// Mark the direct and transitive dependencies of apex bundles so that they
-// can be built for the apex bundles.
-func apexDepsMutator(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	a, ok := mctx.Module().(*apexBundle)
-	if !ok || a.vndkApex {
-		return
-	}
-
-	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
-	excludeVndkLibs := useVndk && proptools.Bool(a.properties.Use_vndk_as_stable)
-	if !useVndk && proptools.Bool(a.properties.Use_vndk_as_stable) {
-		mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
-		return
-	}
-
-	contents := make(map[string]android.ApexMembership)
-
-	continueApexDepsWalk := func(child, parent android.Module) bool {
-		am, ok := child.(android.ApexModule)
-		if !ok || !am.CanHaveApexVariants() {
-			return false
-		}
-		if !parent.(android.DepIsInSameApex).DepIsInSameApex(mctx, child) {
-			return false
-		}
-		if excludeVndkLibs {
-			if c, ok := child.(*cc.Module); ok && c.IsVndk() {
-				return false
-			}
-		}
-		return true
-	}
-
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-
-		depName := mctx.OtherModuleName(child)
-		// If the parent is apexBundle, this child is directly depended.
-		_, directDep := parent.(*apexBundle)
-		contents[depName] = contents[depName].Add(directDep)
-		return true
-	})
-
-	apexContents := android.NewApexContents(mctx.ModuleName(), contents)
-	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
-		Contents: apexContents,
-	})
-
-	apexInfo := android.ApexInfo{
-		ApexVariationName: mctx.ModuleName(),
-		MinSdkVersionStr:  a.minSdkVersion(mctx).String(),
-		RequiredSdks:      a.RequiredSdks(),
-		Updatable:         a.Updatable(),
-		InApexes:          []string{mctx.ModuleName()},
-		ApexContents:      []*android.ApexContents{apexContents},
-	}
-
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-
-		child.(android.ApexModule).BuildForApex(apexInfo)
-		return true
-	})
-}
-
-func apexUniqueVariationsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		// Check if any dependencies use unique apex variations.  If so, use unique apex variations
-		// for this module.
-		android.UpdateUniqueApexVariationsForDeps(mctx, am)
-	}
-}
-
-func apexTestForDepsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	// Check if this module is a test for an apex.  If so, add a dependency on the apex
-	// in order to retrieve its contents later.
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		if testFor := am.TestFor(); len(testFor) > 0 {
-			mctx.AddFarVariationDependencies([]blueprint.Variation{
-				{Mutator: "os", Variation: am.Target().OsVariation()},
-				{"arch", "common"},
-			}, testForTag, testFor...)
-		}
-	}
-}
-
-func apexTestForMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-
-	if _, ok := mctx.Module().(android.ApexModule); ok {
-		var contents []*android.ApexContents
-		for _, testFor := range mctx.GetDirectDepsWithTag(testForTag) {
-			abInfo := mctx.OtherModuleProvider(testFor, ApexBundleInfoProvider).(ApexBundleInfo)
-			contents = append(contents, abInfo.Contents)
-		}
-		mctx.SetProvider(android.ApexTestForInfoProvider, android.ApexTestForInfo{
-			ApexContents: contents,
-		})
-	}
-}
-
-// mark if a module cannot be available to platform. A module cannot be available
-// to platform if 1) it is explicitly marked as not available (i.e. "//apex_available:platform"
-// is absent) or 2) it depends on another module that isn't (or can't be) available to platform
-func markPlatformAvailability(mctx android.BottomUpMutatorContext) {
-	// Host and recovery are not considered as platform
-	if mctx.Host() || mctx.Module().InstallInRecovery() {
-		return
-	}
-
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		availableToPlatform := am.AvailableFor(android.AvailableToPlatform)
-
-		// If any of the dep is not available to platform, this module is also considered
-		// as being not available to platform even if it has "//apex_available:platform"
-		mctx.VisitDirectDeps(func(child android.Module) {
-			if !am.DepIsInSameApex(mctx, child) {
-				// if the dependency crosses apex boundary, don't consider it
-				return
-			}
-			if dep, ok := child.(android.ApexModule); ok && dep.NotAvailableForPlatform() {
-				availableToPlatform = false
-				// TODO(b/154889534) trigger an error when 'am' has "//apex_available:platform"
-			}
-		})
-
-		// Exception 1: stub libraries and native bridge libraries are always available to platform
-		if cc, ok := mctx.Module().(*cc.Module); ok &&
-			(cc.IsStubs() || cc.Target().NativeBridge == android.NativeBridgeEnabled) {
-			availableToPlatform = true
-		}
-
-		// Exception 2: bootstrap bionic libraries are also always available to platform
-		if cc.InstallToBootstrap(mctx.ModuleName(), mctx.Config()) {
-			availableToPlatform = true
-		}
-
-		if !availableToPlatform {
-			am.SetNotAvailableForPlatform()
-		}
-	}
-}
-
-// If a module in an APEX depends on a module from an SDK then it needs an APEX
-// specific variant created for it. Refer to sdk.sdkDepsReplaceMutator.
-func inAnySdk(module android.Module) bool {
-	if sa, ok := module.(android.SdkAware); ok {
-		return sa.IsInAnySdk()
-	}
-
-	return false
-}
-
-// Create apex variations if a module is included in APEX(s).
-func apexMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-
-	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
-		android.CreateApexVariations(mctx, am)
-	} else if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
-		// apex bundle itself is mutated so that it and its modules have same
-		// apex variant.
-		apexBundleName := mctx.ModuleName()
-		mctx.CreateVariations(apexBundleName)
-	} else if o, ok := mctx.Module().(*OverrideApex); ok {
-		apexBundleName := o.GetOverriddenModuleName()
-		if apexBundleName == "" {
-			mctx.ModuleErrorf("base property is not set")
-			return
-		}
-		mctx.CreateVariations(apexBundleName)
-	}
-
-}
-
-func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		android.UpdateDirectlyInAnyApex(mctx, am)
-	}
-}
-
-func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if ab, ok := mctx.Module().(*apexBundle); ok {
-		var variants []string
-		switch proptools.StringDefault(ab.properties.Payload_type, "image") {
-		case "image":
-			variants = append(variants, imageApexType, flattenedApexType)
-		case "zip":
-			variants = append(variants, zipApexType)
-		case "both":
-			variants = append(variants, imageApexType, zipApexType, flattenedApexType)
-		default:
-			mctx.PropertyErrorf("type", "%q is not one of \"image\", \"zip\", or \"both\".", *ab.properties.Payload_type)
-			return
-		}
-
-		modules := mctx.CreateLocalVariations(variants...)
-
-		for i, v := range variants {
-			switch v {
-			case imageApexType:
-				modules[i].(*apexBundle).properties.ApexType = imageApex
-			case zipApexType:
-				modules[i].(*apexBundle).properties.ApexType = zipApex
-			case flattenedApexType:
-				modules[i].(*apexBundle).properties.ApexType = flattenedApex
-				if !mctx.Config().FlattenApex() && ab.Platform() {
-					modules[i].(*apexBundle).MakeAsSystemExt()
-				}
-			}
-		}
-	} else if _, ok := mctx.Module().(*OverrideApex); ok {
-		mctx.CreateVariations(imageApexType, flattenedApexType)
-	}
-}
-
-var (
-	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
-)
-
-// useVendorAllowList returns the list of APEXes which are allowed to use_vendor.
-// When use_vendor is used, native modules are built with __ANDROID_VNDK__ and __ANDROID_APEX__,
-// which may cause compatibility issues. (e.g. libbinder)
-// Even though libbinder restricts its availability via 'apex_available' property and relies on
-// yet another macro __ANDROID_APEX_<NAME>__, we restrict usage of "use_vendor:" from other APEX modules
-// to avoid similar problems.
-func useVendorAllowList(config android.Config) []string {
-	return config.Once(useVendorAllowListKey, func() interface{} {
-		return []string{
-			// swcodec uses "vendor" variants for smaller size
-			"com.android.media.swcodec",
-			"test_com.android.media.swcodec",
-		}
-	}).([]string)
-}
-
-// setUseVendorAllowListForTest overrides useVendorAllowList and must be
-// called before the first call to useVendorAllowList()
-func setUseVendorAllowListForTest(config android.Config, allowList []string) {
-	config.Once(useVendorAllowListKey, func() interface{} {
-		return allowList
-	})
-}
-
-type ApexNativeDependencies struct {
-	// List of native libraries
-	Native_shared_libs []string
-
-	// List of JNI libraries
-	Jni_libs []string
-
-	// List of native executables
-	Binaries []string
-
-	// List of native tests
-	Tests []string
-}
-
-type apexMultilibProperties struct {
-	// Native dependencies whose compile_multilib is "first"
-	First ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "both"
-	Both ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "prefer32"
-	Prefer32 ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "32"
-	Lib32 ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "64"
-	Lib64 ApexNativeDependencies
-}
-
-type apexBundleProperties struct {
-	// Json manifest file describing meta info of this APEX bundle. Default:
-	// "apex_manifest.json"
-	Manifest *string `android:"path"`
-
-	// AndroidManifest.xml file used for the zip container of this APEX bundle.
-	// If unspecified, a default one is automatically generated.
-	AndroidManifest *string `android:"path"`
-
-	// Canonical name of the APEX bundle. Used to determine the path to the activated APEX on
-	// device (/apex/<apex_name>).
-	// If unspecified, defaults to the value of name.
-	Apex_name *string
-
-	// Determines the file contexts file for setting security context to each file in this APEX bundle.
-	// For platform APEXes, this should points to a file under /system/sepolicy
-	// Default: /system/sepolicy/apex/<module_name>_file_contexts.
-	File_contexts *string `android:"path"`
-
-	ApexNativeDependencies
-
-	// List of java libraries that are embedded inside this APEX bundle
-	Java_libs []string
-
-	// List of prebuilt files that are embedded inside this APEX bundle
-	Prebuilts []string
-
-	// List of BPF programs inside APEX
-	Bpfs []string
-
-	// Name of the apex_key module that provides the private key to sign APEX
-	Key *string
-
-	// The type of APEX to build. Controls what the APEX payload is. Either
-	// 'image', 'zip' or 'both'. Default: 'image'.
-	Payload_type *string
-
-	// The name of a certificate in the default certificate directory, blank to use the default product certificate,
-	// or an android_app_certificate module name in the form ":module".
-	Certificate *string
-
-	// Whether this APEX is installable to one of the partitions. Default: true.
-	Installable *bool
-
-	// For native libraries and binaries, use the vendor variant instead of the core (platform) variant.
-	// Default is false.
-	Use_vendor *bool
-
-	// For telling the apex to ignore special handling for system libraries such as bionic. Default is false.
-	Ignore_system_library_special_case *bool
-
-	Multilib apexMultilibProperties
-
-	// List of sanitizer names that this APEX is enabled for
-	SanitizerNames []string `blueprint:"mutated"`
-
-	PreventInstall bool `blueprint:"mutated"`
-
-	HideFromMake bool `blueprint:"mutated"`
-
-	// package format of this apex variant; could be non-flattened, flattened, or zip.
-	// imageApex, zipApex or flattened
-	ApexType apexPackaging `blueprint:"mutated"`
-
-	// List of SDKs that are used to build this APEX. A reference to an SDK should be either
-	// `name#version` or `name` which is an alias for `name#current`. If left empty, `platform#current`
-	// is implied. This value affects all modules included in this APEX. In other words, they are
-	// also built with the SDKs specified here.
-	Uses_sdks []string
-
-	// Whenever apex_payload.img of the APEX should include dm-verity hashtree.
-	// Should be only used in tests#.
-	Test_only_no_hashtree *bool
-
-	// Whenever apex_payload.img of the APEX should not be dm-verity signed.
-	// Should be only used in tests#.
-	Test_only_unsigned_payload *bool
-
-	IsCoverageVariant bool `blueprint:"mutated"`
-
-	// Whether this APEX is considered updatable or not. When set to true, this will enforce additional
-	// rules for making sure that the APEX is truly updatable.
-	// - To be updatable, min_sdk_version should be set as well
-	// This will also disable the size optimizations like symlinking to the system libs.
-	// Default is false.
-	Updatable *bool
-
-	// The minimum SDK version that this apex must be compatibile with.
-	Min_sdk_version *string
-
-	// If set true, VNDK libs are considered as stable libs and are not included in this apex.
-	// Should be only used in non-system apexes (e.g. vendor: true).
-	// Default is false.
-	Use_vndk_as_stable *bool
-
-	// The type of filesystem to use for an image apex. Either 'ext4' or 'f2fs'.
-	// Default 'ext4'.
-	Payload_fs_type *string
-}
-
-type ApexBundleInfo struct {
-	Contents *android.ApexContents
-}
-
-var ApexBundleInfoProvider = blueprint.NewMutatorProvider(ApexBundleInfo{}, "apex_deps")
-
-type apexTargetBundleProperties struct {
-	Target struct {
-		// Multilib properties only for android.
-		Android struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host.
-		Host struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host linux_bionic.
-		Linux_bionic struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host linux_glibc.
-		Linux_glibc struct {
-			Multilib apexMultilibProperties
-		}
-	}
-}
-
-type overridableProperties struct {
-	// List of APKs to package inside APEX
-	Apps []string
-
-	// List of runtime resource overlays (RROs) inside APEX
-	Rros []string
-
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-
-	// Logging Parent value
-	Logging_parent string
-
-	// Apex Container Package Name.
-	// Override value for attribute package:name in AndroidManifest.xml
-	Package_name string
-
-	// A txt file containing list of files that are allowed to be included in this APEX.
-	Allowed_files *string `android:"path"`
-}
-
-type apexPackaging int
-
-const (
-	imageApex apexPackaging = iota
-	zipApex
-	flattenedApex
-)
-
-// The suffix for the output "file", not the module
-func (a apexPackaging) suffix() string {
-	switch a {
-	case imageApex:
-		return imageApexSuffix
-	case zipApex:
-		return zipApexSuffix
-	default:
-		panic(fmt.Errorf("unknown APEX type %d", a))
-	}
-}
-
-func (a apexPackaging) name() string {
-	switch a {
-	case imageApex:
-		return imageApexType
-	case zipApex:
-		return zipApexType
-	default:
-		panic(fmt.Errorf("unknown APEX type %d", a))
-	}
-}
-
-type apexFileClass int
-
-const (
-	etc apexFileClass = iota
-	nativeSharedLib
-	nativeExecutable
-	shBinary
-	pyBinary
-	goBinary
-	javaSharedLib
-	nativeTest
-	app
-	appSet
-)
-
-func (class apexFileClass) NameInMake() string {
-	switch class {
-	case etc:
-		return "ETC"
-	case nativeSharedLib:
-		return "SHARED_LIBRARIES"
-	case nativeExecutable, shBinary, pyBinary, goBinary:
-		return "EXECUTABLES"
-	case javaSharedLib:
-		return "JAVA_LIBRARIES"
-	case nativeTest:
-		return "NATIVE_TESTS"
-	case app, appSet:
-		// b/142537672 Why isn't this APP? We want to have full control over
-		// the paths and file names of the apk file under the flattend APEX.
-		// If this is set to APP, then the paths and file names are modified
-		// by the Make build system. For example, it is installed to
-		// /system/apex/<apexname>/app/<Appname>/<apexname>.<Appname>/ instead of
-		// /system/apex/<apexname>/app/<Appname> because the build system automatically
-		// appends module name (which is <apexname>.<Appname> to the path.
-		return "ETC"
-	default:
-		panic(fmt.Errorf("unknown class %d", class))
-	}
-}
-
-// apexFile represents a file in an APEX bundle
-type apexFile struct {
-	builtFile android.Path
-	stem      string
-	// Module name of `module` in AndroidMk. Note the generated AndroidMk module for
-	// apexFile is named something like <AndroidMk module name>.<apex name>[<apex suffix>]
-	androidMkModuleName string
-	installDir          string
-	class               apexFileClass
-	module              android.Module
-	// list of symlinks that will be created in installDir that point to this apexFile
-	symlinks      []string
-	dataPaths     []android.DataPath
-	transitiveDep bool
-	moduleDir     string
-
-	requiredModuleNames       []string
-	targetRequiredModuleNames []string
-	hostRequiredModuleNames   []string
-
-	jacocoReportClassesFile android.Path     // only for javalibs and apps
-	lintDepSets             java.LintDepSets // only for javalibs and apps
-	certificate             java.Certificate // only for apps
-	overriddenPackageName   string           // only for apps
-
-	isJniLib bool
-
-	noticeFiles android.Paths
-}
-
-func newApexFile(ctx android.BaseModuleContext, builtFile android.Path, androidMkModuleName string, installDir string, class apexFileClass, module android.Module) apexFile {
-	ret := apexFile{
-		builtFile:           builtFile,
-		androidMkModuleName: androidMkModuleName,
-		installDir:          installDir,
-		class:               class,
-		module:              module,
-	}
-	if module != nil {
-		ret.moduleDir = ctx.OtherModuleDir(module)
-		ret.requiredModuleNames = module.RequiredModuleNames()
-		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
-		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
-		ret.noticeFiles = module.NoticeFiles()
-	}
-	return ret
-}
-
-func (af *apexFile) Ok() bool {
-	return af.builtFile != nil && af.builtFile.String() != ""
-}
-
-func (af *apexFile) apexRelativePath(path string) string {
-	return filepath.Join(af.installDir, path)
-}
-
-// Path() returns path of this apex file relative to the APEX root
-func (af *apexFile) Path() string {
-	return af.apexRelativePath(af.Stem())
-}
-
-func (af *apexFile) Stem() string {
-	if af.stem != "" {
-		return af.stem
-	}
-	return af.builtFile.Base()
-}
-
-// SymlinkPaths() returns paths of the symlinks (if any) relative to the APEX root
-func (af *apexFile) SymlinkPaths() []string {
-	var ret []string
-	for _, symlink := range af.symlinks {
-		ret = append(ret, af.apexRelativePath(symlink))
-	}
-	return ret
-}
-
-func (af *apexFile) AvailableToPlatform() bool {
-	if af.module == nil {
-		return false
-	}
-	if am, ok := af.module.(android.ApexModule); ok {
-		return am.AvailableFor(android.AvailableToPlatform)
-	}
-	return false
-}
-
-type fsType int
-
-const (
-	ext4 fsType = iota
-	f2fs
-)
-
-func (f fsType) string() string {
-	switch f {
-	case ext4:
-		return ext4FsType
-	case f2fs:
-		return f2fsFsType
-	default:
-		panic(fmt.Errorf("unknown APEX payload type %d", f))
-	}
-}
-
-type apexBundle struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.OverridableModuleBase
-	android.SdkBase
-
-	properties            apexBundleProperties
-	targetProperties      apexTargetBundleProperties
-	overridableProperties overridableProperties
-
-	// specific to apex_vndk modules
-	vndkProperties apexVndkProperties
-
-	bundleModuleFile android.WritablePath
-	outputFile       android.WritablePath
-	installDir       android.InstallPath
-
-	prebuiltFileToDelete string
-
-	public_key_file  android.Path
-	private_key_file android.Path
-
-	container_certificate_file android.Path
-	container_private_key_file android.Path
-
-	fileContexts android.WritablePath
-
-	// list of files to be included in this apex
-	filesInfo []apexFile
-
-	// list of module names that should be installed along with this APEX
-	requiredDeps []string
-
-	// list of module names that this APEX is including (to be shown via *-deps-info target)
-	android.ApexBundleDepsInfo
-
-	testApex        bool
-	vndkApex        bool
-	artApex         bool
-	primaryApexType bool
-
-	manifestJsonOut android.WritablePath
-	manifestPbOut   android.WritablePath
-
-	// list of commands to create symlinks for backward compatibility.
-	// these commands will be attached as LOCAL_POST_INSTALL_CMD to
-	// apex package itself(for unflattened build) or apex_manifest(for flattened build)
-	// so that compat symlinks are always installed regardless of TARGET_FLATTEN_APEX setting.
-	compatSymlinks []string
-
-	// Suffix of module name in Android.mk
-	// ".flattened", ".apex", ".zipapex", or ""
-	suffix string
-
-	installedFilesFile android.WritablePath
-
-	// Whether to create symlink to the system file instead of having a file
-	// inside the apex or not
-	linkToSystemLib bool
-
-	// Struct holding the merged notice file paths in different formats
-	mergedNotices android.NoticeOutputs
-
-	// Optional list of lint report zip files for apexes that contain java or app modules
-	lintReports android.Paths
-
-	payloadFsType fsType
-
-	distFiles android.TaggedDistFiles
-}
-
-func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
-	nativeModules ApexNativeDependencies,
-	target android.Target, imageVariation string) {
-	// Use *FarVariation* to be able to depend on modules having
-	// conflicting variations with this module. This is required since
-	// arch variant of an APEX bundle is 'common' but it is 'arm' or 'arm64'
-	// for native shared libs.
-
-	binVariations := target.Variations()
-	libVariations := append(target.Variations(),
-		blueprint.Variation{Mutator: "link", Variation: "shared"})
-
-	if ctx.Device() {
-		binVariations = append(binVariations,
-			blueprint.Variation{Mutator: "image", Variation: imageVariation})
-		libVariations = append(libVariations,
-			blueprint.Variation{Mutator: "image", Variation: imageVariation},
-			blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
-	}
-
-	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
-
-	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
-
-	ctx.AddFarVariationDependencies(binVariations, executableTag, nativeModules.Binaries...)
-
-	ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
-}
-
-func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
-	if ctx.Os().Class == android.Device {
-		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Android.Multilib, nil)
-	} else {
-		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Host.Multilib, nil)
-		if ctx.Os().Bionic() {
-			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_bionic.Multilib, nil)
-		} else {
-			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_glibc.Multilib, nil)
-		}
-	}
-}
-
-func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
-	if proptools.Bool(a.properties.Use_vendor) && !android.InList(a.Name(), useVendorAllowList(ctx.Config())) {
-		ctx.PropertyErrorf("use_vendor", "not allowed to set use_vendor: true")
-	}
-
-	targets := ctx.MultiTargets()
-	config := ctx.DeviceConfig()
-	imageVariation := a.getImageVariation(ctx)
-
-	a.combineProperties(ctx)
-
-	has32BitTarget := false
-	for _, target := range targets {
-		if target.Arch.ArchType.Multilib == "lib32" {
-			has32BitTarget = true
-		}
-	}
-	for i, target := range targets {
-		if target.HostCross {
-			// Don't include artifats for the host cross targets because there is no way
-			// for us to run those artifacts natively on host
-			continue
-		}
-
-		// When multilib.* is omitted for native_shared_libs/jni_libs/tests, it implies
-		// multilib.both
-		addDependenciesForNativeModules(ctx,
-			ApexNativeDependencies{
-				Native_shared_libs: a.properties.Native_shared_libs,
-				Tests:              a.properties.Tests,
-				Jni_libs:           a.properties.Jni_libs,
-				Binaries:           nil,
-			},
-			target, imageVariation)
-
-		// Add native modules targetting both ABIs
-		addDependenciesForNativeModules(ctx,
-			a.properties.Multilib.Both,
-			target,
-			imageVariation)
-
-		isPrimaryAbi := i == 0
-		if isPrimaryAbi {
-			// When multilib.* is omitted for binaries, it implies
-			// multilib.first
-			addDependenciesForNativeModules(ctx,
-				ApexNativeDependencies{
-					Native_shared_libs: nil,
-					Tests:              nil,
-					Jni_libs:           nil,
-					Binaries:           a.properties.Binaries,
-				},
-				target, imageVariation)
-
-			// Add native modules targetting the first ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.First,
-				target,
-				imageVariation)
-		}
-
-		switch target.Arch.ArchType.Multilib {
-		case "lib32":
-			// Add native modules targetting 32-bit ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Lib32,
-				target,
-				imageVariation)
-
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Prefer32,
-				target,
-				imageVariation)
-		case "lib64":
-			// Add native modules targetting 64-bit ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Lib64,
-				target,
-				imageVariation)
-
-			if !has32BitTarget {
-				addDependenciesForNativeModules(ctx,
-					a.properties.Multilib.Prefer32,
-					target,
-					imageVariation)
-			}
-		}
-	}
-
-	// For prebuilt_etc, use the first variant (64 on 64/32bit device,
-	// 32 on 32bit device) regardless of the TARGET_PREFER_* setting.
-	// b/144532908
-	archForPrebuiltEtc := config.Arches()[0]
-	for _, arch := range config.Arches() {
-		// Prefer 64-bit arch if there is any
-		if arch.ArchType.Multilib == "lib64" {
-			archForPrebuiltEtc = arch
-			break
-		}
-	}
-	ctx.AddFarVariationDependencies([]blueprint.Variation{
-		{Mutator: "os", Variation: ctx.Os().String()},
-		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
-	}, prebuiltTag, a.properties.Prebuilts...)
-
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		javaLibTag, a.properties.Java_libs...)
-
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		bpfTag, a.properties.Bpfs...)
-
-	// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
-	if a.artApex && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-		ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-			javaLibTag, "jacocoagent")
-	}
-
-	if String(a.properties.Key) == "" {
-		ctx.ModuleErrorf("key is missing")
-		return
-	}
-	ctx.AddDependency(ctx.Module(), keyTag, String(a.properties.Key))
-
-	cert := android.SrcIsModule(a.getCertString(ctx))
-	if cert != "" {
-		ctx.AddDependency(ctx.Module(), certificateTag, cert)
-	}
-
-	// TODO(jiyong): ensure that all apexes are with non-empty uses_sdks
-	if len(a.properties.Uses_sdks) > 0 {
-		sdkRefs := []android.SdkRef{}
-		for _, str := range a.properties.Uses_sdks {
-			parsed := android.ParseSdkRef(ctx, str, "uses_sdks")
-			sdkRefs = append(sdkRefs, parsed)
-		}
-		a.BuildWithSdks(sdkRefs)
-	}
-}
-
-func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
-	if a.overridableProperties.Allowed_files != nil {
-		android.ExtractSourceDeps(ctx, a.overridableProperties.Allowed_files)
-	}
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		androidAppTag, a.overridableProperties.Apps...)
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		rroTag, a.overridableProperties.Rros...)
-}
-
-func (a *apexBundle) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	// direct deps of an APEX bundle are all part of the APEX bundle
-	return true
-}
-
-func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
-	moduleName := ctx.ModuleName()
-	// VNDK APEXes share the same certificate. To avoid adding a new VNDK version to the OVERRIDE_* list,
-	// we check with the pseudo module name to see if its certificate is overridden.
-	if a.vndkApex {
-		moduleName = vndkApexName
-	}
-	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
-	if overridden {
-		return ":" + certificate
-	}
-	return String(a.properties.Certificate)
-}
-
-func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{a.outputFile}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (a *apexBundle) installable() bool {
-	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
-}
-
-func (a *apexBundle) testOnlyShouldSkipHashtreeGeneration() bool {
-	return proptools.Bool(a.properties.Test_only_no_hashtree)
-}
-
-func (a *apexBundle) testOnlyShouldSkipPayloadSign() bool {
-	return proptools.Bool(a.properties.Test_only_unsigned_payload)
-}
-
-func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
-	deviceConfig := ctx.DeviceConfig()
-	if a.vndkApex {
-		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
-	}
-
-	var prefix string
-	var vndkVersion string
-	if deviceConfig.VndkVersion() != "" {
-		if proptools.Bool(a.properties.Use_vendor) {
-			prefix = cc.VendorVariationPrefix
-			vndkVersion = deviceConfig.PlatformVndkVersion()
-		} else if a.SocSpecific() || a.DeviceSpecific() {
-			prefix = cc.VendorVariationPrefix
-			vndkVersion = deviceConfig.VndkVersion()
-		} else if a.ProductSpecific() {
-			prefix = cc.ProductVariationPrefix
-			vndkVersion = deviceConfig.ProductVndkVersion()
-		}
-	}
-	if vndkVersion == "current" {
-		vndkVersion = deviceConfig.PlatformVndkVersion()
-	}
-	if vndkVersion != "" {
-		return prefix + vndkVersion
-	}
-	return android.CoreVariation
-}
-
-func (a *apexBundle) EnableSanitizer(sanitizerName string) {
-	if !android.InList(sanitizerName, a.properties.SanitizerNames) {
-		a.properties.SanitizerNames = append(a.properties.SanitizerNames, sanitizerName)
-	}
-}
-
-func (a *apexBundle) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
-	if android.InList(sanitizerName, a.properties.SanitizerNames) {
-		return true
-	}
-
-	// Then follow the global setting
-	globalSanitizerNames := []string{}
-	if a.Host() {
-		globalSanitizerNames = ctx.Config().SanitizeHost()
-	} else {
-		arches := ctx.Config().SanitizeDeviceArch()
-		if len(arches) == 0 || android.InList(a.Arch().ArchType.Name, arches) {
-			globalSanitizerNames = ctx.Config().SanitizeDevice()
-		}
-	}
-	return android.InList(sanitizerName, globalSanitizerNames)
-}
-
-func (a *apexBundle) AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string) {
-	if ctx.Device() && sanitizerName == "hwaddress" && strings.HasPrefix(a.Name(), "com.android.runtime") {
-		for _, target := range ctx.MultiTargets() {
-			if target.Arch.ArchType.Multilib == "lib64" {
-				ctx.AddFarVariationDependencies(append(target.Variations(), []blueprint.Variation{
-					{Mutator: "image", Variation: a.getImageVariation(ctx)},
-					{Mutator: "link", Variation: "shared"},
-					{Mutator: "version", Variation: ""}, // "" is the non-stub variant
-				}...), sharedLibTag, "libclang_rt.hwasan-aarch64-android")
-				break
-			}
-		}
-	}
-}
-
-var _ cc.Coverage = (*apexBundle)(nil)
-
-func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
-	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
-}
-
-func (a *apexBundle) PreventInstall() {
-	a.properties.PreventInstall = true
-}
-
-func (a *apexBundle) HideFromMake() {
-	a.properties.HideFromMake = true
-}
-
-func (a *apexBundle) MarkAsCoverageVariant(coverage bool) {
-	a.properties.IsCoverageVariant = coverage
-}
-
-func (a *apexBundle) EnableCoverageIfNeeded() {}
-
-// TODO(jiyong) move apexFileFor* close to the apexFile type definition
-func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile {
-	// Decide the APEX-local directory by the multilib of the library
-	// In the future, we may query this to the module.
-	var dirInApex string
-	switch ccMod.Arch().ArchType.Multilib {
-	case "lib32":
-		dirInApex = "lib"
-	case "lib64":
-		dirInApex = "lib64"
-	}
-	if ccMod.Target().NativeBridge == android.NativeBridgeEnabled {
-		dirInApex = filepath.Join(dirInApex, ccMod.Target().NativeBridgeRelativePath)
-	}
-	dirInApex = filepath.Join(dirInApex, ccMod.RelativeInstallPath())
-	if handleSpecialLibs && cc.InstallToBootstrap(ccMod.BaseModuleName(), ctx.Config()) {
-		// Special case for Bionic libs and other libs installed with them. This is
-		// to prevent those libs from being included in the search path
-		// /apex/com.android.runtime/${LIB}. This exclusion is required because
-		// those libs in the Runtime APEX are available via the legacy paths in
-		// /system/lib/. By the init process, the libs in the APEX are bind-mounted
-		// to the legacy paths and thus will be loaded into the default linker
-		// namespace (aka "platform" namespace). If the libs are directly in
-		// /apex/com.android.runtime/${LIB} then the same libs will be loaded again
-		// into the runtime linker namespace, which will result in double loading of
-		// them, which isn't supported.
-		dirInApex = filepath.Join(dirInApex, "bionic")
-	}
-
-	fileToCopy := ccMod.OutputFile().Path()
-	androidMkModuleName := ccMod.BaseModuleName() + ccMod.Properties.SubName
-	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, ccMod)
-}
-
-func apexFileForExecutable(ctx android.BaseModuleContext, cc *cc.Module) apexFile {
-	dirInApex := "bin"
-	if cc.Target().NativeBridge == android.NativeBridgeEnabled {
-		dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
-	}
-	dirInApex = filepath.Join(dirInApex, cc.RelativeInstallPath())
-	fileToCopy := cc.OutputFile().Path()
-	androidMkModuleName := cc.BaseModuleName() + cc.Properties.SubName
-	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, cc)
-	af.symlinks = cc.Symlinks()
-	af.dataPaths = cc.DataPaths()
-	return af
-}
-
-func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
-	dirInApex := "bin"
-	fileToCopy := py.HostToolPath().Path()
-	return newApexFile(ctx, fileToCopy, py.BaseModuleName(), dirInApex, pyBinary, py)
-}
-func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
-	dirInApex := "bin"
-	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
-	if err != nil {
-		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
-		return apexFile{}
-	}
-	fileToCopy := android.PathForOutput(ctx, s)
-	// NB: Since go binaries are static we don't need the module for anything here, which is
-	// good since the go tool is a blueprint.Module not an android.Module like we would
-	// normally use.
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, goBinary, nil)
-}
-
-func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
-	dirInApex := filepath.Join("bin", sh.SubDir())
-	fileToCopy := sh.OutputFile()
-	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
-	af.symlinks = sh.Symlinks()
-	return af
-}
-
-type javaModule interface {
-	android.Module
-	BaseModuleName() string
-	DexJarBuildPath() android.Path
-	JacocoReportClassesFile() android.Path
-	LintDepSets() java.LintDepSets
-
-	Stem() string
-}
-
-var _ javaModule = (*java.Library)(nil)
-var _ javaModule = (*java.SdkLibrary)(nil)
-var _ javaModule = (*java.DexImport)(nil)
-var _ javaModule = (*java.SdkLibraryImport)(nil)
-
-func apexFileForJavaLibrary(ctx android.BaseModuleContext, module javaModule) apexFile {
-	dirInApex := "javalib"
-	fileToCopy := module.DexJarBuildPath()
-	af := newApexFile(ctx, fileToCopy, module.BaseModuleName(), dirInApex, javaSharedLib, module)
-	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
-	af.lintDepSets = module.LintDepSets()
-	af.stem = module.Stem() + ".jar"
-	return af
-}
-
-func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, depName string) apexFile {
-	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
-	fileToCopy := prebuilt.OutputFile()
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, prebuilt)
-}
-
-func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
-	dirInApex := filepath.Join("etc", config.SubDir())
-	fileToCopy := config.CompatConfig()
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, config)
-}
-
-func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp interface {
-	android.Module
-	Privileged() bool
-	InstallApkName() string
-	OutputFile() android.Path
-	JacocoReportClassesFile() android.Path
-	Certificate() java.Certificate
-	BaseModuleName() string
-}) apexFile {
-	appDir := "app"
-	if aapp.Privileged() {
-		appDir = "priv-app"
-	}
-	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
-	fileToCopy := aapp.OutputFile()
-	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
-	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
-	af.certificate = aapp.Certificate()
-
-	if app, ok := aapp.(interface {
-		OverriddenManifestPackageName() string
-	}); ok {
-		af.overriddenPackageName = app.OverriddenManifestPackageName()
-	}
-	return af
-}
-
-func apexFileForRuntimeResourceOverlay(ctx android.BaseModuleContext, rro java.RuntimeResourceOverlayModule) apexFile {
-	rroDir := "overlay"
-	dirInApex := filepath.Join(rroDir, rro.Theme())
-	fileToCopy := rro.OutputFile()
-	af := newApexFile(ctx, fileToCopy, rro.Name(), dirInApex, app, rro)
-	af.certificate = rro.Certificate()
-
-	if a, ok := rro.(interface {
-		OverriddenManifestPackageName() string
-	}); ok {
-		af.overriddenPackageName = a.OverriddenManifestPackageName()
-	}
-	return af
-}
-
-func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile {
-	dirInApex := filepath.Join("etc", "bpf")
-	return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram)
-}
-
-// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
-type flattenedApexContext struct {
-	android.ModuleContext
-}
-
-func (c *flattenedApexContext) InstallBypassMake() bool {
-	return true
-}
-
-// Visit dependencies that contributes to the payload of this APEX
-func (a *apexBundle) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		am, ok := child.(android.ApexModule)
-		if !ok || !am.CanHaveApexVariants() {
-			return false
-		}
-
-		childApexInfo := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
-
-		dt := ctx.OtherModuleDependencyTag(child)
-
-		if _, ok := dt.(android.ExcludeFromApexContentsTag); ok {
-			return false
-		}
-
-		// Check for the direct dependencies that contribute to the payload
-		if adt, ok := dt.(dependencyTag); ok {
-			if adt.payload {
-				return do(ctx, parent, am, false /* externalDep */)
-			}
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		// Check for the indirect dependencies if it is considered as part of the APEX
-		if android.InList(ctx.ModuleName(), childApexInfo.InApexes) {
-			return do(ctx, parent, am, false /* externalDep */)
-		}
-
-		return do(ctx, parent, am, true /* externalDep */)
-	})
-}
-
-func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
-	ver := proptools.String(a.properties.Min_sdk_version)
-	if ver == "" {
-		return android.FutureApiLevel
-	}
-	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
-	if err != nil {
-		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
-		return android.NoneApiLevel
-	}
-	if apiLevel.IsPreview() {
-		// All codenames should build against "current".
-		return android.FutureApiLevel
-	}
-	return apiLevel
-}
-
-func (a *apexBundle) Updatable() bool {
-	return proptools.Bool(a.properties.Updatable)
-}
-
-var _ android.ApexBundleDepsInfoIntf = (*apexBundle)(nil)
-
-// Ensures that the dependencies are marked as available for this APEX
-func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) {
-	// Let's be practical. Availability for test, host, and the VNDK apex isn't important
-	if ctx.Host() || a.testApex || a.vndkApex {
-		return
-	}
-
-	// Because APEXes targeting other than system/system_ext partitions
-	// can't set apex_available, we skip checks for these APEXes
-	if a.SocSpecific() || a.DeviceSpecific() ||
-		(a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
-		return
-	}
-
-	// Coverage build adds additional dependencies for the coverage-only runtime libraries.
-	// Requiring them and their transitive depencies with apex_available is not right
-	// because they just add noise.
-	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || a.IsNativeCoverageNeeded(ctx) {
-		return
-	}
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if externalDep {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		apexName := ctx.ModuleName()
-		fromName := ctx.OtherModuleName(from)
-		toName := ctx.OtherModuleName(to)
-
-		// If `to` is not actually in the same APEX as `from` then it does not need apex_available and neither
-		// do any of its dependencies.
-		if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		if to.AvailableFor(apexName) || baselineApexAvailable(apexName, toName) {
-			return true
-		}
-		ctx.ModuleErrorf("%q requires %q that doesn't list the APEX under 'apex_available'. Dependency path:%s", fromName, toName, ctx.GetPathString(true))
-		// Visit this module's dependencies to check and report any issues with their availability.
-		return true
-	})
-}
-
-func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
-	if a.Updatable() {
-		if String(a.properties.Min_sdk_version) == "" {
-			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
-		}
-
-		a.checkJavaStableSdkVersion(ctx)
-	}
-}
-
-func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
-	if a.testApex || a.vndkApex {
-		return
-	}
-	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
-	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
-		return
-	}
-	// apexBundle::minSdkVersion reports its own errors.
-	minSdkVersion := a.minSdkVersion(ctx)
-	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
-}
-
-// Ensures that a lib providing stub isn't statically linked
-func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext) {
-	// Practically, we only care about regular APEXes on the device.
-	if ctx.Host() || a.testApex || a.vndkApex {
-		return
-	}
-
-	abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if ccm, ok := to.(*cc.Module); ok {
-			apexName := ctx.ModuleName()
-			fromName := ctx.OtherModuleName(from)
-			toName := ctx.OtherModuleName(to)
-
-			// If `to` is not actually in the same APEX as `from` then it does not need apex_available and neither
-			// do any of its dependencies.
-			if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
-				// As soon as the dependency graph crosses the APEX boundary, don't go further.
-				return false
-			}
-
-			// The dynamic linker and crash_dump tool in the runtime APEX is the only exception to this rule.
-			// It can't make the static dependencies dynamic because it can't
-			// do the dynamic linking for itself.
-			if apexName == "com.android.runtime" && (fromName == "linker" || fromName == "crash_dump") {
-				return false
-			}
-
-			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
-			if isStubLibraryFromOtherApex && !externalDep {
-				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
-					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
-			}
-
-		}
-		return true
-	})
-}
-
-func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
-	switch a.properties.ApexType {
-	case imageApex:
-		if buildFlattenedAsDefault {
-			a.suffix = imageApexSuffix
-		} else {
-			a.suffix = ""
-			a.primaryApexType = true
-
-			if ctx.Config().InstallExtraFlattenedApexes() {
-				a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix)
-			}
-		}
-	case zipApex:
-		if proptools.String(a.properties.Payload_type) == "zip" {
-			a.suffix = ""
-			a.primaryApexType = true
-		} else {
-			a.suffix = zipApexSuffix
-		}
-	case flattenedApex:
-		if buildFlattenedAsDefault {
-			a.suffix = ""
-			a.primaryApexType = true
-		} else {
-			a.suffix = flattenedSuffix
-		}
-	}
-
-	if len(a.properties.Tests) > 0 && !a.testApex {
-		ctx.PropertyErrorf("tests", "property not allowed in apex module type")
-		return
-	}
-
-	a.checkApexAvailability(ctx)
-	a.checkUpdatable(ctx)
-	a.checkMinSdkVersion(ctx)
-	a.checkStaticLinkingToStubLibraries(ctx)
-
-	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
-
-	// native lib dependencies
-	var provideNativeLibs []string
-	var requireNativeLibs []string
-
-	var filesInfo []apexFile
-	// TODO(jiyong) do this using WalkPayloadDeps
-	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
-		depTag := ctx.OtherModuleDependencyTag(child)
-		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
-			return false
-		}
-		depName := ctx.OtherModuleName(child)
-		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
-			switch depTag {
-			case sharedLibTag, jniLibTag:
-				isJniLib := depTag == jniLibTag
-				if c, ok := child.(*cc.Module); ok {
-					fi := apexFileForNativeLibrary(ctx, c, handleSpecialLibs)
-					fi.isJniLib = isJniLib
-					filesInfo = append(filesInfo, fi)
-					// Collect the list of stub-providing libs except:
-					// - VNDK libs are only for vendors
-					// - bootstrap bionic libs are treated as provided by system
-					if c.HasStubsVariants() && !a.vndkApex && !cc.InstallToBootstrap(c.BaseModuleName(), ctx.Config()) {
-						provideNativeLibs = append(provideNativeLibs, fi.Stem())
-					}
-					return true // track transitive dependencies
-				} else {
-					propertyName := "native_shared_libs"
-					if isJniLib {
-						propertyName = "jni_libs"
-					}
-					ctx.PropertyErrorf(propertyName, "%q is not a cc_library or cc_library_shared module", depName)
-				}
-			case executableTag:
-				if cc, ok := child.(*cc.Module); ok {
-					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
-					return true // track transitive dependencies
-				} else if sh, ok := child.(*sh.ShBinary); ok {
-					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
-				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
-					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
-				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
-					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
-				} else {
-					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
-				}
-			case javaLibTag:
-				switch child.(type) {
-				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport:
-					af := apexFileForJavaLibrary(ctx, child.(javaModule))
-					if !af.Ok() {
-						ctx.PropertyErrorf("java_libs", "%q is not configured to be compiled into dex", depName)
-						return false
-					}
-					filesInfo = append(filesInfo, af)
-					return true // track transitive dependencies
-				default:
-					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
-				}
-			case androidAppTag:
-				if ap, ok := child.(*java.AndroidApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-					return true // track transitive dependencies
-				} else if ap, ok := child.(*java.AndroidAppImport); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-				} else if ap, ok := child.(*java.AndroidTestHelperApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-				} else if ap, ok := child.(*java.AndroidAppSet); ok {
-					appDir := "app"
-					if ap.Privileged() {
-						appDir = "priv-app"
-					}
-					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(),
-						filepath.Join(appDir, ap.BaseModuleName()), appSet, ap)
-					af.certificate = java.PresignedCertificate
-					filesInfo = append(filesInfo, af)
-				} else {
-					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
-				}
-			case rroTag:
-				if rro, ok := child.(java.RuntimeResourceOverlayModule); ok {
-					filesInfo = append(filesInfo, apexFileForRuntimeResourceOverlay(ctx, rro))
-				} else {
-					ctx.PropertyErrorf("rros", "%q is not an runtime_resource_overlay module", depName)
-				}
-			case bpfTag:
-				if bpfProgram, ok := child.(bpf.BpfModule); ok {
-					filesToCopy, _ := bpfProgram.OutputFiles("")
-					for _, bpfFile := range filesToCopy {
-						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram))
-					}
-				} else {
-					ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName)
-				}
-			case prebuiltTag:
-				if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
-				} else if prebuilt, ok := child.(java.PlatformCompatConfigIntf); ok {
-					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, prebuilt, depName))
-				} else {
-					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc and not a platform_compat_config module", depName)
-				}
-			case testTag:
-				if ccTest, ok := child.(*cc.Module); ok {
-					if ccTest.IsTestPerSrcAllTestsVariation() {
-						// Multiple-output test module (where `test_per_src: true`).
-						//
-						// `ccTest` is the "" ("all tests") variation of a `test_per_src` module.
-						// We do not add this variation to `filesInfo`, as it has no output;
-						// however, we do add the other variations of this module as indirect
-						// dependencies (see below).
-					} else {
-						// Single-output test module (where `test_per_src: false`).
-						af := apexFileForExecutable(ctx, ccTest)
-						af.class = nativeTest
-						filesInfo = append(filesInfo, af)
-					}
-					return true // track transitive dependencies
-				} else {
-					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
-				}
-			case keyTag:
-				if key, ok := child.(*apexKey); ok {
-					a.private_key_file = key.private_key_file
-					a.public_key_file = key.public_key_file
-				} else {
-					ctx.PropertyErrorf("key", "%q is not an apex_key module", depName)
-				}
-				return false
-			case certificateTag:
-				if dep, ok := child.(*java.AndroidAppCertificate); ok {
-					a.container_certificate_file = dep.Certificate.Pem
-					a.container_private_key_file = dep.Certificate.Key
-				} else {
-					ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
-				}
-			case android.PrebuiltDepTag:
-				// If the prebuilt is force disabled, remember to delete the prebuilt file
-				// that might have been installed in the previous builds
-				if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
-					a.prebuiltFileToDelete = prebuilt.InstallFilename()
-				}
-			}
-		} else if !a.vndkApex {
-			// indirect dependencies
-			if am, ok := child.(android.ApexModule); ok {
-				// We cannot use a switch statement on `depTag` here as the checked
-				// tags used below are private (e.g. `cc.sharedDepTag`).
-				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
-					if cc, ok := child.(*cc.Module); ok {
-						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
-							requireNativeLibs = append(requireNativeLibs, ":vndk")
-							return false
-						}
-						af := apexFileForNativeLibrary(ctx, cc, handleSpecialLibs)
-						af.transitiveDep = true
-						abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
-						if !a.Host() && !abInfo.Contents.DirectlyInApex(depName) && (cc.IsStubs() || cc.HasStubsVariants()) {
-							// If the dependency is a stubs lib, don't include it in this APEX,
-							// but make sure that the lib is installed on the device.
-							// In case no APEX is having the lib, the lib is installed to the system
-							// partition.
-							//
-							// Always include if we are a host-apex however since those won't have any
-							// system libraries.
-							if !am.DirectlyInAnyApex() {
-								// we need a module name for Make
-								name := cc.ImplementationModuleName(ctx)
-
-								if !proptools.Bool(a.properties.Use_vendor) {
-									// we don't use subName(.vendor) for a "use_vendor: true" apex
-									// which is supposed to be installed in /system
-									name += cc.Properties.SubName
-								}
-								if !android.InList(name, a.requiredDeps) {
-									a.requiredDeps = append(a.requiredDeps, name)
-								}
-							}
-							requireNativeLibs = append(requireNativeLibs, af.Stem())
-							// Don't track further
-							return false
-						}
-						filesInfo = append(filesInfo, af)
-						return true // track transitive dependencies
-					}
-				} else if cc.IsTestPerSrcDepTag(depTag) {
-					if cc, ok := child.(*cc.Module); ok {
-						af := apexFileForExecutable(ctx, cc)
-						// Handle modules created as `test_per_src` variations of a single test module:
-						// use the name of the generated test binary (`fileToCopy`) instead of the name
-						// of the original test module (`depName`, shared by all `test_per_src`
-						// variations of that module).
-						af.androidMkModuleName = filepath.Base(af.builtFile.String())
-						// these are not considered transitive dep
-						af.transitiveDep = false
-						filesInfo = append(filesInfo, af)
-						return true // track transitive dependencies
-					}
-				} else if java.IsJniDepTag(depTag) {
-					// Because APK-in-APEX embeds jni_libs transitively, we don't need to track transitive deps
-					return false
-				} else if java.IsXmlPermissionsFileDepTag(depTag) {
-					if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-						filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
-					}
-				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
-					// nothing
-				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
-					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
-				}
-			}
-		}
-		return false
-	})
-
-	// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries.
-	// Build rules are generated by the dexpreopt singleton, and here we access build artifacts
-	// via the global boot image config.
-	if a.artApex {
-		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
-			dirInApex := filepath.Join("javalib", arch.String())
-			for _, f := range files {
-				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-				af := newApexFile(ctx, f, localModule, dirInApex, etc, nil)
-				filesInfo = append(filesInfo, af)
-			}
-		}
-	}
-
-	if a.private_key_file == nil {
-		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.properties.Key))
-		return
-	}
-
-	// remove duplicates in filesInfo
-	removeDup := func(filesInfo []apexFile) []apexFile {
-		encountered := make(map[string]apexFile)
-		for _, f := range filesInfo {
-			dest := filepath.Join(f.installDir, f.builtFile.Base())
-			if e, ok := encountered[dest]; !ok {
-				encountered[dest] = f
-			} else {
-				// If a module is directly included and also transitively depended on
-				// consider it as directly included.
-				e.transitiveDep = e.transitiveDep && f.transitiveDep
-				encountered[dest] = e
-			}
-		}
-		var result []apexFile
-		for _, v := range encountered {
-			result = append(result, v)
-		}
-		return result
-	}
-	filesInfo = removeDup(filesInfo)
-
-	// to have consistent build rules
-	sort.Slice(filesInfo, func(i, j int) bool {
-		return filesInfo[i].builtFile.String() < filesInfo[j].builtFile.String()
-	})
-
-	a.installDir = android.PathForModuleInstall(ctx, "apex")
-	a.filesInfo = filesInfo
-
-	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
-	case ext4FsType:
-		a.payloadFsType = ext4
-	case f2fsFsType:
-		a.payloadFsType = f2fs
-	default:
-		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
-	}
-
-	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
-	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
-	// the same library in the system partition, thus effectively sharing the same libraries
-	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
-	// in the APEX.
-	a.linkToSystemLib = !ctx.Config().UnbundledBuild() &&
-		a.installable() &&
-		!proptools.Bool(a.properties.Use_vendor)
-
-	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
-	// So we can't link them to /system/lib libs which are core variants.
-	if a.SocSpecific() || a.DeviceSpecific() ||
-		(a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
-		a.linkToSystemLib = false
-	}
-
-	// We don't need the optimization for updatable APEXes, as it might give false signal
-	// to the system health when the APEXes are still bundled (b/149805758)
-	if a.Updatable() && a.properties.ApexType == imageApex {
-		a.linkToSystemLib = false
-	}
-
-	// We also don't want the optimization for host APEXes, because it doesn't make sense.
-	if ctx.Host() {
-		a.linkToSystemLib = false
-	}
-
-	// prepare apex_manifest.json
-	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
-
-	a.buildFileContexts(ctx)
-
-	a.setCertificateAndPrivateKey(ctx)
-	if a.properties.ApexType == flattenedApex {
-		a.buildFlattenedApex(ctx)
-	} else {
-		a.buildUnflattenedApex(ctx)
-	}
-
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
-
-	a.buildApexDependencyInfo(ctx)
-
-	a.buildLintReports(ctx)
-
-	a.distFiles = a.GenerateTaggedDistFiles(ctx)
-}
-
-// Enforce that Java deps of the apex are using stable SDKs to compile
-func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
-	// Visit direct deps only. As long as we guarantee top-level deps are using
-	// stable SDKs, java's checkLinkType guarantees correct usage for transitive deps
-	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		switch tag {
-		case javaLibTag, androidAppTag:
-			if m, ok := module.(interface{ CheckStableSdkVersion() error }); ok {
-				if err := m.CheckStableSdkVersion(); err != nil {
-					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
-				}
-			}
-		}
-	})
-}
-
-func baselineApexAvailable(apex, moduleName string) bool {
-	key := apex
-	moduleName = normalizeModuleName(moduleName)
-
-	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
-		return true
-	}
-
-	key = android.AvailableToAnyApex
-	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
-		return true
-	}
-
-	return false
-}
-
-func normalizeModuleName(moduleName string) string {
-	// Prebuilt modules (e.g. java_import, etc.) have "prebuilt_" prefix added by the build
-	// system. Trim the prefix for the check since they are confusing
-	moduleName = strings.TrimPrefix(moduleName, "prebuilt_")
-	if strings.HasPrefix(moduleName, "libclang_rt.") {
-		// This module has many arch variants that depend on the product being built.
-		// We don't want to list them all
-		moduleName = "libclang_rt"
-	}
-	if strings.HasPrefix(moduleName, "androidx.") {
-		// TODO(b/156996905) Set apex_available/min_sdk_version for androidx support libraries
-		moduleName = "androidx"
-	}
-	return moduleName
-}
-
-func newApexBundle() *apexBundle {
-	module := &apexBundle{}
-	module.AddProperties(&module.properties)
-	module.AddProperties(&module.targetProperties)
-	module.AddProperties(&module.overridableProperties)
-	android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-	android.InitSdkAwareModule(module)
-	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
-	return module
-}
-
-func ApexBundleFactory(testApex bool, artApex bool) android.Module {
-	bundle := newApexBundle()
-	bundle.testApex = testApex
-	bundle.artApex = artApex
-	return bundle
-}
-
-// apex_test is an APEX for testing. The difference from the ordinary apex module type is that
-// certain compatibility checks such as apex_available are not done for apex_test.
-func testApexBundleFactory() android.Module {
-	bundle := newApexBundle()
-	bundle.testApex = true
-	return bundle
-}
-
-// apex packages other modules into an APEX file which is a packaging format for system-level
-// components like binaries, shared libraries, etc.
-func BundleFactory() android.Module {
-	return newApexBundle()
-}
-
-//
-// Defaults
-//
-type Defaults struct {
-	android.ModuleBase
-	android.DefaultsModuleBase
-}
-
-func defaultsFactory() android.Module {
-	return DefaultsFactory()
-}
-
-func DefaultsFactory(props ...interface{}) android.Module {
-	module := &Defaults{}
-
-	module.AddProperties(props...)
-	module.AddProperties(
-		&apexBundleProperties{},
-		&apexTargetBundleProperties{},
-		&overridableProperties{},
-	)
-
-	android.InitDefaultsModule(module)
-	return module
-}
-
-//
-// OverrideApex
-//
-type OverrideApex struct {
-	android.ModuleBase
-	android.OverrideModuleBase
-}
-
-func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// All the overrides happen in the base module.
-}
-
-// override_apex is used to create an apex module based on another apex module
-// by overriding some of its properties.
-func overrideApexFactory() android.Module {
-	m := &OverrideApex{}
-	m.AddProperties(&overridableProperties{})
-
-	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
-	android.InitOverrideModule(m)
-	return m
-}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 524549a..7ffd226 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -32,6 +32,7 @@
 	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
@@ -136,6 +137,8 @@
 
 	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
 
+	bp = bp + rust.GatherRequiredDepsForTest()
+
 	bp = bp + java.GatherRequiredDepsForTest()
 
 	fs := map[string][]byte{
@@ -185,6 +188,7 @@
 		"bar/baz":                                    nil,
 		"testdata/baz":                               nil,
 		"AppSet.apks":                                nil,
+		"foo.rs":                                     nil,
 	}
 
 	cc.GatherRequiredFilesForTest(fs)
@@ -241,6 +245,7 @@
 	ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	rust.RegisterRequiredBuildComponentsForTest(ctx)
 
 	ctx.RegisterModuleType("cc_test", cc.TestFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@@ -341,6 +346,13 @@
 	}
 }
 
+func ensureListNotEmpty(t *testing.T, result []string) {
+	t.Helper()
+	if len(result) == 0 {
+		t.Errorf("%q is expected to be not empty", result)
+	}
+}
+
 // Minimal test
 func TestBasicApex(t *testing.T) {
 	ctx, config := testApex(t, `
@@ -349,10 +361,12 @@
 			manifest: ":myapex.manifest",
 			androidManifest: ":myapex.androidmanifest",
 			key: "myapex.key",
+			binaries: ["foo.rust"],
 			native_shared_libs: ["mylib"],
+			rust_dyn_libs: ["libfoo.dylib.rust"],
 			multilib: {
 				both: {
-					binaries: ["foo",],
+					binaries: ["foo"],
 				}
 			},
 			java_libs: [
@@ -415,6 +429,28 @@
 			apex_available: [ "myapex", "com.android.gki.*" ],
 		}
 
+		rust_binary {
+		        name: "foo.rust",
+			srcs: ["foo.rs"],
+			rlibs: ["libfoo.rlib.rust"],
+			dylibs: ["libfoo.dylib.rust"],
+			apex_available: ["myapex"],
+		}
+
+		rust_library_rlib {
+		        name: "libfoo.rlib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
+		rust_library_dylib {
+		        name: "libfoo.dylib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
 		apex {
 			name: "com.android.gki.fake",
 			binaries: ["foo"],
@@ -529,16 +565,20 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("foo.rust"), "android_arm64_armv8-a_apex10000")
 
 	// Ensure that apex variant is created for the indirect dep
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
 
 	// Ensure that both direct and indirect deps are copied into apex
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
+	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
 	// .. but not for java libs
 	ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
 	ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")
@@ -2798,18 +2838,18 @@
 	// non-APEX variant does not have __ANDROID_APEX__ defined
 	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_SDK_VERSION__")
+	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_SDK_VERSION__=10000")
+	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=10000")
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex29").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_SDK_VERSION__=29")
+	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=29")
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
 
 	// When a cc_library sets use_apex_name_macro: true each apex gets a unique variant and
@@ -2831,10 +2871,10 @@
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
 
-	// recovery variant does not set __ANDROID_SDK_VERSION__
+	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_SDK_VERSION__")
+	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 
 	// When a dependency of a cc_library sets use_apex_name_macro: true each apex gets a unique
 	// variant.
@@ -2855,10 +2895,10 @@
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
 
-	// recovery variant does not set __ANDROID_SDK_VERSION__
+	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_SDK_VERSION__")
+	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 }
 
 func TestHeaderLibsDependency(t *testing.T) {
@@ -5447,7 +5487,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.*"]}`)
@@ -5473,7 +5513,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"]
@@ -6153,6 +6193,91 @@
 	`)
 }
 
+func TestCompressedApex(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			compressible: true,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`, func(fs map[string][]byte, config android.Config) {
+		config.TestProductVariables.CompressedApex = proptools.BoolPtr(true)
+	})
+
+	compressRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("compressRule")
+	ensureContains(t, compressRule.Output.String(), "myapex.capex.unsigned")
+
+	signApkRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Description("sign compressedApex")
+	ensureEquals(t, signApkRule.Input.String(), compressRule.Output.String())
+
+	// Make sure output of bundle is .capex
+	ab := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	ensureContains(t, ab.outputFile.String(), "myapex.capex")
+
+	// Verify android.mk rules
+	data := android.AndroidMkDataForTest(t, config, "", ab)
+	var builder strings.Builder
+	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.capex\n")
+}
+
+func TestPreferredPrebuiltSharedLibDep(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			apex_available: ["myapex"],
+			shared_libs: ["otherlib"],
+			system_shared_libs: [],
+		}
+
+		cc_library {
+			name: "otherlib",
+			srcs: ["mylib.cpp"],
+			stubs: {
+				versions: ["current"],
+			},
+		}
+
+		cc_prebuilt_library_shared {
+			name: "otherlib",
+			prefer: true,
+			srcs: ["prebuilt.so"],
+			stubs: {
+				versions: ["current"],
+			},
+		}
+	`)
+
+	ab := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, config, "", ab)
+	var builder strings.Builder
+	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+
+	// The make level dependency needs to be on otherlib - prebuilt_otherlib isn't
+	// a thing there.
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += otherlib\n")
+}
+
 func TestMain(m *testing.M) {
 	run := func() int {
 		setUp()
diff --git a/apex/builder.go b/apex/builder.go
index ad673d6..9db8e59 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -37,10 +37,11 @@
 
 func init() {
 	pctx.Import("android/soong/android")
+	pctx.Import("android/soong/cc/config")
 	pctx.Import("android/soong/java")
 	pctx.HostBinToolVariable("apexer", "apexer")
 	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
-	// projects, and hence cannot built 'aapt2'. Use the SDK prebuilt instead.
+	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
 	hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
 		pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
 			if !ctx.Config().FrameworksBaseDirExists(ctx) {
@@ -65,6 +66,8 @@
 	pctx.HostBinToolVariable("extract_apks", "extract_apks")
 	pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
 	pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
+	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
+	pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
 }
 
 var (
@@ -175,37 +178,49 @@
 			`exit 1); touch ${out}`,
 		Description: "Diff ${image_content_file} and ${allowed_files_file}",
 	}, "image_content_file", "allowed_files_file", "apex_module_name")
+
+	generateAPIsUsedbyApexRule = pctx.StaticRule("generateAPIsUsedbyApexRule", blueprint.RuleParams{
+		Command:     "$genNdkUsedbyApexPath ${image_dir} ${readelf} ${out}",
+		CommandDeps: []string{"${genNdkUsedbyApexPath}"},
+		Description: "Generate symbol list used by Apex",
+	}, "image_dir", "readelf")
+
+	// Don't add more rules here. Consider using android.NewRuleBuilder instead.
 )
 
+// buildManifest creates buile rules to modify the input apex_manifest.json to add information
+// gathered by the build system such as provided/required native libraries. Two output files having
+// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and
+// a.manifest.PbOut is protobuf format for R+ devices.
+// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle
 func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) {
-	manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
+	src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
 
-	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
-
-	// put dependency({provide|require}NativeLibs) in apex_manifest.json
+	// Put dependency({provide|require}NativeLibs) in apex_manifest.json
 	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
 	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
 
-	// apex name can be overridden
+	// APEX name can be overridden
 	optCommands := []string{}
 	if a.properties.Apex_name != nil {
 		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
 	}
 
-	// collect jniLibs. Notice that a.filesInfo is already sorted
+	// Collect jniLibs. Notice that a.filesInfo is already sorted
 	var jniLibs []string
 	for _, fi := range a.filesInfo {
-		if fi.isJniLib && !android.InList(fi.Stem(), jniLibs) {
-			jniLibs = append(jniLibs, fi.Stem())
+		if fi.isJniLib && !android.InList(fi.stem(), jniLibs) {
+			jniLibs = append(jniLibs, fi.stem())
 		}
 	}
 	if len(jniLibs) > 0 {
 		optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
 	}
 
+	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexManifestRule,
-		Input:  manifestSrc,
+		Input:  src,
 		Output: manifestJsonFullOut,
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
@@ -214,10 +229,10 @@
 		},
 	})
 
+	// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare
+	// stripped-down version so that APEX modules built from R+ can be installed to Q
 	minSdkVersion := a.minSdkVersion(ctx)
 	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json
-		// prepare stripped-down version so that APEX modules built from R+ can be installed to Q
 		a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   stripApexManifestRule,
@@ -226,7 +241,7 @@
 		})
 	}
 
-	// from R+, protobuf binary format (.pb) is the standard format for apex_manifest
+	// From R+, protobuf binary format (.pb) is the standard format for apex_manifest
 	a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   pbApexManifestRule,
@@ -235,10 +250,11 @@
 	})
 }
 
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) {
-	if a.properties.ApexType == zipApex {
-		return
-	}
+// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts
+// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
+// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
+// labeled as system_file.
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
 	var fileContexts android.Path
 	if a.properties.File_contexts == nil {
 		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
@@ -248,18 +264,17 @@
 	if a.Platform() {
 		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
 			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
-			return
 		}
 	}
 	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
-		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
-		return
+		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String())
 	}
 
 	output := android.PathForModuleOut(ctx, "file_contexts")
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
-	if a.properties.ApexType == imageApex {
+	switch a.properties.ApexType {
+	case imageApex:
 		// remove old file
 		rule.Command().Text("rm").FlagWithOutput("-f ", output)
 		// copy file_contexts
@@ -269,7 +284,7 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
 		rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
-	} else {
+	case flattenedApex:
 		// For flattened apexes, install path should be prepended.
 		// File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS
 		// so that it can be merged into file_contexts.bin
@@ -284,13 +299,16 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output)
 		rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output)
+	default:
+		panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
 	}
 
-	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
-
-	a.fileContexts = output.OutputPath
+	rule.Build("file_contexts."+a.Name(), "Generate file_contexts")
+	return output.OutputPath
 }
 
+// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
+// contributes to this APEX. The notice files are merged into a big notice file.
 func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
 	var noticeFiles android.Paths
 
@@ -299,13 +317,11 @@
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
 			return false
 		}
-
-		notices := to.NoticeFiles()
-		noticeFiles = append(noticeFiles, notices...)
-
+		noticeFiles = append(noticeFiles, to.NoticeFiles()...)
 		return true
 	})
 
+	// TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
 	for _, fi := range a.filesInfo {
 		noticeFiles = append(noticeFiles, fi.noticeFiles...)
 	}
@@ -317,19 +333,24 @@
 	return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
 }
 
+// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
+// files included in this APEX is shown. The text file is dist'ed so that people can see what's
+// included in the APEX without actually downloading and extracting it.
 func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "installed-files.txt")
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		Implicit(builtApex).
 		Text("(cd " + imageDir.String() + " ; ").
 		Text("find . \\( -type f -o -type l \\) -printf \"%s %p\\n\") ").
 		Text(" | sort -nr > ").
 		Output(output)
-	rule.Build(pctx, ctx, "installed-files."+a.Name(), "Installed files")
+	rule.Build("installed-files."+a.Name(), "Installed files")
 	return output.OutputPath
 }
 
+// buildBundleConfig creates a build rule for the bundle config file that will control the bundle
+// creation process.
 func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "bundle_config.json")
 
@@ -351,8 +372,8 @@
 		"apex_manifest.*",
 	}
 
-	// collect the manifest names and paths of android apps
-	// if their manifest names are overridden
+	// Collect the manifest names and paths of android apps if their manifest names are
+	// overridden.
 	for _, fi := range a.filesInfo {
 		if fi.class != app && fi.class != appSet {
 			continue
@@ -363,7 +384,7 @@
 				config.Apex_config.Apex_embedded_apk_config,
 				ApkConfig{
 					Package_name: packageName,
-					Apk_path:     fi.Path(),
+					Apk_path:     fi.path(),
 				})
 		}
 	}
@@ -373,45 +394,39 @@
 		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
 }
 
+// buildUnflattendApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
-	var abis []string
-	for _, target := range ctx.MultiTargets() {
-		if len(target.Arch.Abi) > 0 {
-			abis = append(abis, target.Arch.Abi[0])
-		}
-	}
-
-	abis = android.FirstUniqueStrings(abis)
-
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
-	var implicitInputs []android.Path
-	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 
-	// TODO(jiyong): construct the copy rules using RuleBuilder
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1: copy built files to appropriate directories under the image directory
+
+	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
+
+	// TODO(jiyong): use the RuleBuilder
 	var copyCommands []string
+	var implicitInputs []android.Path
 	for _, fi := range a.filesInfo {
-		destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.Path()).String()
+		destPath := imageDir.Join(ctx, fi.path()).String()
+
+		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
 			copyCommands = append(copyCommands, "rm -rf "+destPathDir)
 		}
 		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
-		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+
+		// Copy the built file to the directory. But if the symlink optimization is turned
+		// on, place a symlink to the corresponding file in /system partition instead.
+		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
 			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			pathOnDevice := filepath.Join("/system", fi.Path())
+			pathOnDevice := filepath.Join("/system", fi.path())
 			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
 		} else {
 			if fi.class == appSet {
@@ -422,11 +437,15 @@
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
 		}
-		// create additional symlinks pointing the file inside the APEX
-		for _, symlinkPath := range fi.SymlinkPaths() {
-			symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String()
+
+		// Create additional symlinks pointing the file inside the APEX (if any). Note that
+		// this is independent from the symlink optimization.
+		for _, symlinkPath := range fi.symlinkPaths() {
+			symlinkDest := imageDir.Join(ctx, symlinkPath).String()
 			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
 		}
+
+		// Copy the test files (if any)
 		for _, d := range fi.dataPaths {
 			// TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible
 			relPath := d.SrcPath.Rel()
@@ -435,28 +454,37 @@
 				panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath))
 			}
 
-			dataDest := android.PathForModuleOut(ctx, "image"+suffix, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
+			dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
 
 			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
 			implicitInputs = append(implicitInputs, d.SrcPath)
 		}
 	}
-
-	// TODO(jiyong): use RuleBuilder
-	var emitCommands []string
-	imageContentFile := android.PathForModuleOut(ctx, "content.txt")
-	emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
-	minSdkVersion := a.minSdkVersion(ctx)
-	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
-	}
-	for _, fi := range a.filesInfo {
-		emitCommands = append(emitCommands, "echo './"+fi.Path()+"' >> "+imageContentFile.String())
-	}
-	emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
+	// the allowed list given via the allowed_files property. Build fails when the two lists
+	// differ.
+	//
+	// TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems
+	// to be using this at this moment. Furthermore, this looks very similar to what
+	// buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't
+	// hurt readability.
+	// TODO(jiyong): use RuleBuilder
 	if a.overridableProperties.Allowed_files != nil {
+		// Build content.txt
+		var emitCommands []string
+		imageContentFile := android.PathForModuleOut(ctx, "content.txt")
+		emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
+		minSdkVersion := a.minSdkVersion(ctx)
+		if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
+			emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
+		}
+		for _, fi := range a.filesInfo {
+			emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
+		}
+		emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        emitApexContentRule,
 			Implicits:   implicitInputs,
@@ -467,8 +495,9 @@
 			},
 		})
 		implicitInputs = append(implicitInputs, imageContentFile)
-		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 
+		// Compare content.txt against allowed_files.
+		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 		phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        diffApexContentRule,
@@ -481,22 +510,25 @@
 				"apex_module_name":   a.Name(),
 			},
 		})
-
 		implicitInputs = append(implicitInputs, phonyOutput)
 	}
 
+	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
-	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 	if apexType == imageApex {
-		// files and dirs that will be created in APEX
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
+		// in this APEX. The file will be used by apexer in later steps.
+		// TODO(jiyong): make this as a function
+		// TODO(jiyong): use the RuleBuilder
 		var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
 		var executablePaths []string // this also includes dirs
 		var extractedAppSetPaths android.Paths
 		var extractedAppSetDirs []string
 		for _, f := range a.filesInfo {
-			pathInApex := f.Path()
+			pathInApex := f.path()
 			if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
 				executablePaths = append(executablePaths, pathInApex)
 				for _, d := range f.dataPaths {
@@ -535,11 +567,17 @@
 				"apk_paths":  strings.Join(extractedAppSetDirs, " "),
 			},
 		})
+		implicitInputs = append(implicitInputs, cannedFsConfig)
 
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX.
+		// TODO(jiyong): use the RuleBuilder
 		optFlags := []string{}
 
-		// Additional implicit inputs.
-		implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file)
+		fileContexts := a.buildFileContexts(ctx)
+		implicitInputs = append(implicitInputs, fileContexts)
+
+		implicitInputs = append(implicitInputs, a.private_key_file, a.public_key_file)
 		optFlags = append(optFlags, "--pubkey "+a.public_key_file.String())
 
 		manifestPackageName := a.getOverrideManifestPackageName(ctx)
@@ -553,15 +591,18 @@
 			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
 		}
 
+		// Determine target/min sdk version from the context
+		// TODO(jiyong): make this as a function
 		moduleMinSdkVersion := a.minSdkVersion(ctx)
 		minSdkVersion := moduleMinSdkVersion.String()
 
-		// bundletool doesn't understand what "current" is. We need to transform it to codename
+		// bundletool doesn't understand what "current" is. We need to transform it to
+		// codename
 		if moduleMinSdkVersion.IsCurrent() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 		}
-		// apex module doesn't have a concept of target_sdk_version, hence for the time being
-		// targetSdkVersion == default targetSdkVersion of the branch.
+		// apex module doesn't have a concept of target_sdk_version, hence for the time
+		// being targetSdkVersion == default targetSdkVersion of the branch.
 		targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
 
 		if java.UseApiFingerprint(ctx) {
@@ -602,8 +643,8 @@
 		}
 
 		if a.properties.Apex_name != nil {
-			// If apex_name is set, apexer can skip checking if key name matches with apex name.
-			// Note that apex_manifest is also mended.
+			// If apex_name is set, apexer can skip checking if key name matches with
+			// apex name.  Note that apex_manifest is also mended.
 			optFlags = append(optFlags, "--do_not_check_keyname")
 		}
 
@@ -624,13 +665,14 @@
 				"image_dir":        imageDir.String(),
 				"copy_commands":    strings.Join(copyCommands, " && "),
 				"manifest":         a.manifestPbOut.String(),
-				"file_contexts":    a.fileContexts.String(),
+				"file_contexts":    fileContexts.String(),
 				"canned_fs_config": cannedFsConfig.String(),
 				"key":              a.private_key_file.String(),
 				"opt_flags":        strings.Join(optFlags, " "),
 			},
 		})
 
+		// TODO(jiyong): make the two rules below as separate functions
 		apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix)
 		bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip")
 		a.bundleModuleFile = bundleModuleFile
@@ -642,8 +684,33 @@
 			Description: "apex proto convert",
 		})
 
+		implicitInputs = append(implicitInputs, unsignedOutputFile)
+
+		// Run coverage analysis
+		apisUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+".txt")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        generateAPIsUsedbyApexRule,
+			Implicits:   implicitInputs,
+			Description: "coverage",
+			Output:      apisUsedbyOutputFile,
+			Args: map[string]string{
+				"image_dir": imageDir.String(),
+				"readelf":   "${config.ClangBin}/llvm-readelf",
+			},
+		})
+		a.coverageOutputPath = apisUsedbyOutputFile
+
 		bundleConfig := a.buildBundleConfig(ctx)
 
+		var abis []string
+		for _, target := range ctx.MultiTargets() {
+			if len(target.Arch.Abi) > 0 {
+				abis = append(abis, target.Arch.Abi[0])
+			}
+		}
+
+		abis = android.FirstUniqueStrings(abis)
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        apexBundleRule,
 			Input:       apexProtoFile,
@@ -655,7 +722,7 @@
 				"config": bundleConfig.String(),
 			},
 		})
-	} else {
+	} else { // zipApex
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        zipApexRule,
 			Implicits:   implicitInputs,
@@ -670,92 +737,124 @@
 		})
 	}
 
-	a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+	////////////////////////////////////////////////////////////////////////////////////
+	// Step 4: Sign the APEX using signapk
+	signedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix)
+
+	pem, key := a.getCertificateAndPrivateKey(ctx)
 	rule := java.Signapk
 	args := map[string]string{
-		"certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(),
+		"certificates": pem.String() + " " + key.String(),
 		"flags":        "-a 4096", //alignment
 	}
-	implicits := android.Paths{
-		a.container_certificate_file,
-		a.container_private_key_file,
-	}
+	implicits := android.Paths{pem, key}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
 		rule = java.SignapkRE
 		args["implicits"] = strings.Join(implicits.Strings(), ",")
-		args["outCommaList"] = a.outputFile.String()
+		args["outCommaList"] = signedOutputFile.String()
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
 		Description: "signapk",
-		Output:      a.outputFile,
+		Output:      signedOutputFile,
 		Input:       unsignedOutputFile,
 		Implicits:   implicits,
 		Args:        args,
 	})
+	a.outputFile = signedOutputFile
+
+	// Process APEX compression if enabled
+	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, true)
+	if compressionEnabled && apexType == imageApex {
+		a.isCompressed = true
+		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
+
+		compressRule := android.NewRuleBuilder(pctx, ctx)
+		compressRule.Command().
+			Text("rm").
+			FlagWithOutput("-f ", unsignedCompressedOutputFile)
+		compressRule.Command().
+			BuiltTool("apex_compression_tool").
+			Flag("compress").
+			FlagWithArg("--apex_compression_tool ", outHostBinDir+":"+prebuiltSdkToolsBinDir).
+			FlagWithInput("--input ", signedOutputFile).
+			FlagWithOutput("--output ", unsignedCompressedOutputFile)
+		compressRule.Build("compressRule", "Generate unsigned compressed APEX file")
+
+		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        rule,
+			Description: "sign compressedApex",
+			Output:      signedCompressedOutputFile,
+			Input:       unsignedCompressedOutputFile,
+			Implicits:   implicits,
+			Args:        args,
+		})
+		a.outputFile = signedCompressedOutputFile
+	}
 
 	// Install to $OUT/soong/{target,host}/.../apex
 	if a.installable() {
 		ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
 	}
-	a.buildFilesInfo(ctx)
 
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
 }
 
+// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
+type flattenedApexContext struct {
+	android.ModuleContext
+}
+
+func (c *flattenedApexContext) InstallBypassMake() bool {
+	return true
+}
+
+// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
+// single output file. It is a phony target for all the files under /system/apex/<name> directory.
+// This function creates the installation rules for the files.
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
-	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it
-	// reply true to `InstallBypassMake()` (thus making the call
-	// `android.PathForModuleInstall` below use `android.pathForInstallInMakeDir`
-	// instead of `android.PathForOutput`) to return the correct path to the flattened
-	// APEX (as its contents is installed by Make, not Soong).
-	factx := flattenedApexContext{ctx}
-	a.outputFile = android.PathForModuleInstall(&factx, "apex", a.Name())
-	a.buildFilesInfo(ctx)
-}
-
-func (a *apexBundle) setCertificateAndPrivateKey(ctx android.ModuleContext) {
-	if a.container_certificate_file == nil {
-		cert := String(a.properties.Certificate)
-		if cert == "" {
-			pem, key := ctx.Config().DefaultAppCertificate(ctx)
-			a.container_certificate_file = pem
-			a.container_private_key_file = key
-		} else {
-			defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
-			a.container_certificate_file = defaultDir.Join(ctx, cert+".x509.pem")
-			a.container_private_key_file = defaultDir.Join(ctx, cert+".pk8")
-		}
-	}
-}
-
-func (a *apexBundle) buildFilesInfo(ctx android.ModuleContext) {
+	bundleName := a.Name()
 	if a.installable() {
-		// For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along
-		// with other ordinary files.
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
-
-		// rename to apex_pubkey
-		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   android.Cp,
-			Input:  a.public_key_file,
-			Output: copiedPubkey,
-		})
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
-
-		if a.properties.ApexType == flattenedApex {
-			apexBundleName := a.Name()
-			for _, fi := range a.filesInfo {
-				dir := filepath.Join("apex", apexBundleName, fi.installDir)
-				target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.Stem(), fi.builtFile)
-				for _, sym := range fi.symlinks {
-					ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
-				}
+		for _, fi := range a.filesInfo {
+			dir := filepath.Join("apex", bundleName, fi.installDir)
+			target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
+			for _, sym := range fi.symlinks {
+				ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
 			}
 		}
 	}
+
+	a.fileContexts = a.buildFileContexts(ctx)
+
+	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
+	// to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
+	// `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
+	// correct path to the flattened APEX (as its contents is installed by Make, not Soong).
+	// TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
+	// it and it actually points to a path that can never be built. Remove this.
+	factx := flattenedApexContext{ctx}
+	a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+}
+
+// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
+// the zip container of this APEX. See the description of the 'certificate' property for how
+// the cert and the private key are found.
+func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) {
+	if a.container_certificate_file != nil {
+		return a.container_certificate_file, a.container_private_key_file
+	}
+
+	cert := String(a.properties.Certificate)
+	if cert == "" {
+		return ctx.Config().DefaultAppCertificate(ctx)
+	}
+
+	defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+	pem = defaultDir.Join(ctx, cert+".x509.pem")
+	key = defaultDir.Join(ctx, cert+".pk8")
+	return pem, key
 }
 
 func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string {
diff --git a/apex/key.go b/apex/key.go
index d2d5786..d9e3c10 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 {
@@ -127,13 +127,14 @@
 	apexKeyMap := make(map[string]apexKeyEntry)
 	ctx.VisitAllModules(func(module android.Module) {
 		if m, ok := module.(*apexBundle); ok && m.Enabled() && m.installable() {
+			pem, key := m.getCertificateAndPrivateKey(ctx)
 			apexKeyMap[m.Name()] = apexKeyEntry{
 				name:                  m.Name() + ".apex",
 				presigned:             false,
 				public_key:            m.public_key_file.String(),
 				private_key:           m.private_key_file.String(),
-				container_certificate: m.container_certificate_file.String(),
-				container_private_key: m.container_private_key_file.String(),
+				container_certificate: pem.String(),
+				container_private_key: key.String(),
 				partition:             m.PartitionTag(ctx.DeviceConfig()),
 			}
 		}
@@ -173,17 +174,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/Android.bp b/bazel/Android.bp
new file mode 100644
index 0000000..0113726
--- /dev/null
+++ b/bazel/Android.bp
@@ -0,0 +1,10 @@
+bootstrap_go_package {
+    name: "soong-bazel",
+    pkgPath: "android/soong/bazel",
+    srcs: [
+        "properties.go",
+    ],
+    pluginFor: [
+        "soong_build",
+    ],
+}
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/bazel/properties.go b/bazel/properties.go
new file mode 100644
index 0000000..8bb1956
--- /dev/null
+++ b/bazel/properties.go
@@ -0,0 +1,27 @@
+// 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 bazel
+
+type bazelModuleProperties struct {
+	// The label of the Bazel target replacing this Soong module.
+	Label string
+}
+
+// Properties contains common module properties for migration purposes.
+type Properties struct {
+	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
+	// this Soong module.
+	Bazel_module bazelModuleProperties
+}
diff --git a/cc/Android.bp b/cc/Android.bp
index ff2cdf3..88104e2 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -72,6 +72,8 @@
         "vendor_public_library.go",
 
         "testing.go",
+
+        "stub_library.go",
     ],
     testSrcs: [
         "cc_test.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 38269cb..320e69b 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -334,8 +334,7 @@
 	entries.Class = "NATIVE_TESTS"
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		if len(benchmark.Properties.Test_suites) > 0 {
-			entries.SetString("LOCAL_COMPATIBILITY_SUITE",
-				strings.Join(benchmark.Properties.Test_suites, " "))
+			entries.AddCompatibilityTestSuites(benchmark.Properties.Test_suites...)
 		}
 		if benchmark.testConfig != nil {
 			entries.SetString("LOCAL_FULL_TEST_CONFIG", benchmark.testConfig.String())
@@ -360,8 +359,7 @@
 	}
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		if len(test.Properties.Test_suites) > 0 {
-			entries.SetString("LOCAL_COMPATIBILITY_SUITE",
-				strings.Join(test.Properties.Test_suites, " "))
+			entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
 		}
 		if test.testConfig != nil {
 			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
@@ -370,6 +368,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 +506,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 +550,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 +564,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..fa3966f 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -42,6 +42,7 @@
 	// extension (if any) appended
 	Symlinks []string `android:"arch_variant"`
 
+	// override the dynamic linker
 	DynamicLinker string `blueprint:"mutated"`
 
 	// Names of modules to be overridden. Listed modules can only be other binaries
@@ -80,6 +81,7 @@
 // Executables
 //
 
+// binaryDecorator is a decorator containing information for C++ binary modules.
 type binaryDecorator struct {
 	*baseLinker
 	*baseInstaller
@@ -95,17 +97,25 @@
 	// 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
 
 	// Location of the files that should be copied to dist dir when requested
 	distFiles android.TaggedDistFiles
 
+	// Action command lines to run directly after the binary is installed. For example,
+	// may be used to symlink runtime dependencies (such as bionic) alongside installation.
 	post_install_cmds []string
 }
 
 var _ linker = (*binaryDecorator)(nil)
 
+// linkerProps returns the list of individual properties objects relevant
+// for this binary.
 func (binary *binaryDecorator) linkerProps() []interface{} {
 	return append(binary.baseLinker.linkerProps(),
 		&binary.Properties,
@@ -113,6 +123,10 @@
 
 }
 
+// getStemWithoutSuffix returns the main section of the name to use for the symlink of
+// the main output file of this binary module. This may be derived from the module name
+// or other property overrides.
+// For the full symlink name, the `Suffix` property of a binary module must be appended.
 func (binary *binaryDecorator) getStemWithoutSuffix(ctx BaseModuleContext) string {
 	stem := ctx.baseModuleName()
 	if String(binary.Properties.Stem) != "" {
@@ -122,10 +136,14 @@
 	return stem
 }
 
+// getStem returns the full name to use for the symlink of the main output file of this binary
+// module. This may be derived from the module name and/or other property overrides.
 func (binary *binaryDecorator) getStem(ctx BaseModuleContext) string {
 	return binary.getStemWithoutSuffix(ctx) + String(binary.Properties.Suffix)
 }
 
+// linkerDeps augments and returns the given `deps` to contain dependencies on
+// modules common to most binaries, such as bionic libraries.
 func (binary *binaryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = binary.baseLinker.linkerDeps(ctx, deps)
 	if ctx.toolchain().Bionic() {
@@ -151,6 +169,13 @@
 			deps.LateStaticLibs = append(groupLibs, deps.LateStaticLibs...)
 		}
 
+		// Embed the linker into host bionic binaries. This is needed to support host bionic,
+		// as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
+		// either an absolute path, or relative from CWD. To work around this, we extract
+		// the load sections from the runtime linker ELF binary and embed them into each host
+		// bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
+		// binary, and then we use a special entry point to fix up the arguments passed by
+		// the kernel before jumping to the embedded linker.
 		if ctx.Os() == android.LinuxBionic && !binary.static() {
 			deps.DynamicLinker = "linker"
 			deps.LinkerFlagsFile = "host_bionic_linker_flags"
@@ -166,9 +191,13 @@
 }
 
 func (binary *binaryDecorator) isDependencyRoot() bool {
+	// Binaries are always the dependency root.
 	return true
 }
 
+// NewBinary builds and returns a new Module corresponding to a C++ binary.
+// Individual module implementations which comprise a C++ binary should call this function,
+// set some fields on the result, and then call the Init function.
 func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
 	module := newModule(hod, android.MultilibFirst)
 	binary := &binaryDecorator{
@@ -186,11 +215,15 @@
 	return module, binary
 }
 
+// linkerInit initializes dynamic properties of the linker (such as runpath) based
+// on properties of this binary.
 func (binary *binaryDecorator) linkerInit(ctx BaseModuleContext) {
 	binary.baseLinker.linkerInit(ctx)
 
 	if !ctx.toolchain().Bionic() {
 		if ctx.Os() == android.Linux {
+			// Unless explicitly specified otherwise, host static binaries are built with -static
+			// if HostStaticBinaries is true for the product configuration.
 			if binary.Properties.Static_executable == nil && ctx.Config().HostStaticBinaries() {
 				binary.Properties.Static_executable = BoolPtr(true)
 			}
@@ -213,9 +246,13 @@
 	return true
 }
 
+// linkerFlags returns a Flags object containing linker flags that are defined
+// by this binary, or that are implied by attributes of this binary. These flags are
+// combined with the given flags.
 func (binary *binaryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = binary.baseLinker.linkerFlags(ctx, flags)
 
+	// Passing -pie to clang for Windows binaries causes a warning that -pie is unused.
 	if ctx.Host() && !ctx.Windows() && !binary.static() {
 		if !ctx.Config().IsEnvTrue("DISABLE_HOST_PIE") {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-pie")
@@ -244,7 +281,7 @@
 				"-Bstatic",
 				"-Wl,--gc-sections",
 			)
-		} else {
+		} else { // not static
 			if flags.DynamicLinker == "" {
 				if binary.Properties.DynamicLinker != "" {
 					flags.DynamicLinker = binary.Properties.DynamicLinker
@@ -284,7 +321,7 @@
 				"-Wl,-z,nocopyreloc",
 			)
 		}
-	} else {
+	} else { // not bionic
 		if binary.static() {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-static")
 		}
@@ -296,6 +333,9 @@
 	return flags
 }
 
+// link registers actions to link this binary, and sets various fields
+// on this binary to reflect information that should be exported up the build
+// tree (for example, exported flags and include paths).
 func (binary *binaryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
@@ -305,6 +345,7 @@
 
 	var linkerDeps android.Paths
 
+	// Add flags from linker flags file.
 	if deps.LinkerFlagsFile.Valid() {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "$$(cat "+deps.LinkerFlagsFile.String()+")")
 		linkerDeps = append(linkerDeps, deps.LinkerFlagsFile.Path())
@@ -332,18 +373,21 @@
 	if String(binary.Properties.Prefix_symbols) != "" {
 		afterPrefixSymbols := outputFile
 		outputFile = android.PathForModuleOut(ctx, "unprefixed", fileName)
-		TransformBinaryPrefixSymbols(ctx, String(binary.Properties.Prefix_symbols), outputFile,
+		transformBinaryPrefixSymbols(ctx, String(binary.Properties.Prefix_symbols), outputFile,
 			builderFlags, afterPrefixSymbols)
 	}
 
 	outputFile = maybeInjectBoringSSLHash(ctx, outputFile, binary.Properties.Inject_bssl_hash, fileName)
 
+	// If use_version_lib is true, make an android::build::GetBuildNumber() function available.
 	if Bool(binary.baseLinker.Properties.Use_version_lib) {
 		if ctx.Host() {
 			versionedOutputFile := outputFile
 			outputFile = android.PathForModuleOut(ctx, "unversioned", fileName)
 			binary.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		} else {
+			// When dist'ing a library or binary that has use_version_lib set, always
+			// distribute the stamped version, even for the device.
 			versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
 			binary.distFiles = android.MakeDefaultDistFiles(versionedOutputFile)
 
@@ -357,6 +401,7 @@
 		}
 	}
 
+	// Handle host bionic linker symbols.
 	if ctx.Os() == android.LinuxBionic && !binary.static() {
 		injectedOutputFile := outputFile
 		outputFile = android.PathForModuleOut(ctx, "prelinker", fileName)
@@ -382,13 +427,14 @@
 	linkerDeps = append(linkerDeps, objs.tidyFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
-	TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
+	// Register link action.
+	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
 		builderFlags, outputFile, nil)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
-	binary.coverageOutputFile = TransformCoverageFilesToZip(ctx, objs, binary.getStem(ctx))
+	binary.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, binary.getStem(ctx))
 
 	// Need to determine symlinks early since some targets (ie APEX) need this
 	// information but will not call 'install'
@@ -402,7 +448,10 @@
 			ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix")
 		}
 		if ctx.TargetPrimary() {
-			binary.symlinks = append(binary.symlinks, binary.getStemWithoutSuffix(ctx))
+			// Install a symlink to the preferred architecture
+			symlinkName := binary.getStemWithoutSuffix(ctx)
+			binary.symlinks = append(binary.symlinks, symlinkName)
+			binary.preferredArchSymlink = symlinkName
 		}
 	}
 
@@ -455,12 +504,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/builder.go b/cc/builder.go
index 81c09b1..9cd78d5 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -19,7 +19,6 @@
 // functions.
 
 import (
-	"fmt"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -40,6 +39,7 @@
 var (
 	pctx = android.NewPackageContext("android/soong/cc")
 
+	// Rule to invoke gcc with given command, flags, and dependencies. Outputs a .d depfile.
 	cc = pctx.AndroidRemoteStaticRule("cc", android.RemoteRuleSupports{Goma: true, RBE: true},
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
@@ -49,6 +49,7 @@
 		},
 		"ccCmd", "cFlags")
 
+	// Rule to invoke gcc with given command and flags, but no dependencies.
 	ccNoDeps = pctx.AndroidStaticRule("ccNoDeps",
 		blueprint.RuleParams{
 			Command:     "$relPwd $ccCmd -c $cFlags -o $out $in",
@@ -56,6 +57,8 @@
 		},
 		"ccCmd", "cFlags")
 
+	// Rules to invoke ld to link binaries. Uses a .rsp file to list dependencies, as there may
+	// be many.
 	ld, ldRE = remoteexec.StaticRules(pctx, "ld",
 		blueprint.RuleParams{
 			Command: "$reTemplate$ldCmd ${crtBegin} @${out}.rsp " +
@@ -76,6 +79,7 @@
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
 		}, []string{"ldCmd", "crtBegin", "libFlags", "crtEnd", "ldFlags", "extraLibFlags"}, []string{"implicitInputs", "implicitOutputs"})
 
+	// Rules for .o files to combine to other .o files, using ld partial linking.
 	partialLd, partialLdRE = remoteexec.StaticRules(pctx, "partialLd",
 		blueprint.RuleParams{
 			// Without -no-pie, clang 7.0 adds -pie to link Android files,
@@ -91,6 +95,7 @@
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
 		}, []string{"ldCmd", "ldFlags"}, []string{"implicitInputs", "inCommaList", "implicitOutputs"})
 
+	// Rule to invoke `ar` with given cmd and flags, but no static library depenencies.
 	ar = pctx.AndroidStaticRule("ar",
 		blueprint.RuleParams{
 			Command:        "rm -f ${out} && $arCmd $arFlags $out @${out}.rsp",
@@ -100,6 +105,8 @@
 		},
 		"arCmd", "arFlags")
 
+	// Rule to invoke `ar` with given cmd, flags, and library dependencies. Generates a .a
+	// (archive) file from .o files.
 	arWithLibs = pctx.AndroidStaticRule("arWithLibs",
 		blueprint.RuleParams{
 			Command:        "rm -f ${out} && $arCmd $arObjFlags $out @${out}.rsp && $arCmd $arLibFlags $out $arLibs",
@@ -109,12 +116,7 @@
 		},
 		"arCmd", "arObjFlags", "arObjs", "arLibFlags", "arLibs")
 
-	darwinStrip = pctx.AndroidStaticRule("darwinStrip",
-		blueprint.RuleParams{
-			Command:     "${config.MacStripPath} -u -r -o $out $in",
-			CommandDeps: []string{"${config.MacStripPath}"},
-		})
-
+	// Rule to run objcopy --prefix-symbols (to prefix all symbols in a file with a given string).
 	prefixSymbols = pctx.AndroidStaticRule("prefixSymbols",
 		blueprint.RuleParams{
 			Command:     "$objcopyCmd --prefix-symbols=${prefix} ${in} ${out}",
@@ -125,6 +127,24 @@
 	_ = pctx.SourcePathVariable("stripPath", "build/soong/scripts/strip.sh")
 	_ = pctx.SourcePathVariable("xzCmd", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/xz")
 
+	// Rule to invoke `strip` (to discard symbols and data from object files).
+	strip = pctx.AndroidStaticRule("strip",
+		blueprint.RuleParams{
+			Depfile:     "${out}.d",
+			Deps:        blueprint.DepsGCC,
+			Command:     "CROSS_COMPILE=$crossCompile XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
+			CommandDeps: []string{"$stripPath", "$xzCmd"},
+			Pool:        darwinStripPool,
+		},
+		"args", "crossCompile")
+
+	// Rule to invoke `strip` (to discard symbols and data from object files) on darwin architecture.
+	darwinStrip = pctx.AndroidStaticRule("darwinStrip",
+		blueprint.RuleParams{
+			Command:     "${config.MacStripPath} -u -r -o $out $in",
+			CommandDeps: []string{"${config.MacStripPath}"},
+		})
+
 	// b/132822437: objcopy uses a file descriptor per .o file when called on .a files, which runs the system out of
 	// file descriptors on darwin.  Limit concurrent calls to 5 on darwin.
 	darwinStripPool = func() blueprint.Pool {
@@ -137,18 +157,9 @@
 		}
 	}()
 
-	strip = pctx.AndroidStaticRule("strip",
-		blueprint.RuleParams{
-			Depfile:     "${out}.d",
-			Deps:        blueprint.DepsGCC,
-			Command:     "CROSS_COMPILE=$crossCompile XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
-			CommandDeps: []string{"$stripPath", "$xzCmd"},
-			Pool:        darwinStripPool,
-		},
-		"args", "crossCompile")
-
 	_ = pctx.SourcePathVariable("archiveRepackPath", "build/soong/scripts/archive_repack.sh")
 
+	// Rule to repack an archive (.a) file with a subset of object files.
 	archiveRepack = pctx.AndroidStaticRule("archiveRepack",
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
@@ -158,6 +169,7 @@
 		},
 		"objects")
 
+	// Rule to create an empty file at a given path.
 	emptyFile = pctx.AndroidStaticRule("emptyFile",
 		blueprint.RuleParams{
 			Command: "rm -f $out && touch $out",
@@ -165,6 +177,7 @@
 
 	_ = pctx.SourcePathVariable("tocPath", "build/soong/scripts/toc.sh")
 
+	// A rule for extracting a table of contents from a shared library (.so).
 	toc = pctx.AndroidStaticRule("toc",
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
@@ -175,6 +188,7 @@
 		},
 		"crossCompile", "format")
 
+	// Rule for invoking clang-tidy (a clang-based linter).
 	clangTidy, clangTidyRE = remoteexec.StaticRules(pctx, "clangTidy",
 		blueprint.RuleParams{
 			Command:     "rm -f $out && $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $in -- $cFlags && touch $out",
@@ -193,6 +207,7 @@
 
 	_ = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
 
+	// Rule for invoking yasm to compile .asm assembly files.
 	yasm = pctx.AndroidStaticRule("yasm",
 		blueprint.RuleParams{
 			Command:     "$yasmCmd $asFlags -o $out $in && $yasmCmd $asFlags -M $in >$out.d",
@@ -202,6 +217,7 @@
 		},
 		"asFlags")
 
+	// Rule to invoke windres, for interaction with Windows resources.
 	windres = pctx.AndroidStaticRule("windres",
 		blueprint.RuleParams{
 			Command:     "$windresCmd $flags -I$$(dirname $in) -i $in -o $out --preprocessor \"${config.ClangBin}/clang -E -xc-header -DRC_INVOKED\"",
@@ -219,14 +235,17 @@
 		}, &remoteexec.REParams{
 			Labels:       map[string]string{"type": "abi-dump", "tool": "header-abi-dumper"},
 			ExecStrategy: "${config.REAbiDumperExecStrategy}",
+			Inputs:       []string{"$sAbiLinkerLibs"},
 			Platform: map[string]string{
-				remoteexec.PoolKey:      "${config.RECXXPool}",
+				remoteexec.PoolKey: "${config.RECXXPool}",
 			},
 		}, []string{"cFlags", "exportDirs"}, nil)
 
 	_ = pctx.SourcePathVariable("sAbiLinker", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-linker")
 	_ = pctx.SourcePathVariable("sAbiLinkerLibs", "prebuilts/clang-tools/${config.HostPrebuiltTag}/lib64")
 
+	// Rule to combine .dump sAbi dump files from multiple source files into a single .ldump
+	// sAbi dump file.
 	sAbiLink, sAbiLinkRE = remoteexec.StaticRules(pctx, "sAbiLink",
 		blueprint.RuleParams{
 			Command:        "$reTemplate$sAbiLinker -o ${out} $symbolFilter -arch $arch  $exportedHeaderFlags @${out}.rsp ",
@@ -245,6 +264,7 @@
 
 	_ = pctx.SourcePathVariable("sAbiDiffer", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-diff")
 
+	// Rule to compare linked sAbi dump files (.ldump).
 	sAbiDiff = pctx.RuleFunc("sAbiDiff",
 		func(ctx android.PackageRuleContext) blueprint.RuleParams {
 			commandStr := "($sAbiDiffer ${extraFlags} -lib ${libName} -arch ${arch} -o ${out} -new ${in} -old ${referenceDump})"
@@ -258,11 +278,13 @@
 		},
 		"extraFlags", "referenceDump", "libName", "arch", "createReferenceDumpFlags")
 
+	// Rule to unzip a reference abi dump.
 	unzipRefSAbiDump = pctx.AndroidStaticRule("unzipRefSAbiDump",
 		blueprint.RuleParams{
 			Command: "gunzip -c $in > $out",
 		})
 
+	// Rule to zip files.
 	zip = pctx.AndroidStaticRule("zip",
 		blueprint.RuleParams{
 			Command:        "${SoongZipCmd} -o ${out} -C $$OUT_DIR -r ${out}.rsp",
@@ -278,6 +300,8 @@
 		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() })
 	_ = pctx.VariableFunc("kytheCuEncoding",
 		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCuEncoding() })
+
+	// Rule to use kythe extractors to generate .kzip files, used to build code cross references.
 	kytheExtract = pctx.StaticRule("kythe",
 		blueprint.RuleParams{
 			Command: `rm -f $out && ` +
@@ -310,7 +334,11 @@
 	pctx.Import("android/soong/remoteexec")
 }
 
+// builderFlags contains various types of command line flags (and settings) for use in building
+// build statements related to C++.
 type builderFlags struct {
+	// Global flags (which build system or toolchain is responsible for). These are separate from
+	// local flags because they should appear first (so that they may be overridden by local flags).
 	globalCommonFlags     string
 	globalAsFlags         string
 	globalYasmFlags       string
@@ -321,6 +349,7 @@
 	globalCppFlags        string
 	globalLdFlags         string
 
+	// Local flags (which individual modules are responsible for). These may override global flags.
 	localCommonFlags     string
 	localAsFlags         string
 	localYasmFlags       string
@@ -331,32 +360,37 @@
 	localCppFlags        string
 	localLdFlags         string
 
-	libFlags      string
-	extraLibFlags string
-	tidyFlags     string
-	sAbiFlags     string
-	aidlFlags     string
-	rsFlags       string
+	libFlags      string // Flags to add to the linker directly after specifying libraries to link.
+	extraLibFlags string // Flags to add to the linker last.
+	tidyFlags     string // Flags that apply to clang-tidy
+	sAbiFlags     string // Flags that apply to header-abi-dumps
+	aidlFlags     string // Flags that apply to aidl source files
+	rsFlags       string // Flags that apply to renderscript source files
 	toolchain     config.Toolchain
-	tidy          bool
-	gcovCoverage  bool
-	sAbiDump      bool
-	emitXrefs     bool
 
-	assemblerWithCpp bool
+	// True if these extra features are enabled.
+	tidy         bool
+	gcovCoverage bool
+	sAbiDump     bool
+	emitXrefs    bool
+
+	assemblerWithCpp bool // True if .s files should be processed with the c preprocessor.
 
 	systemIncludeFlags string
 
+	// True if static libraries should be grouped (using `-Wl,--start-group` and `-Wl,--end-group`).
 	groupStaticLibs bool
 
 	proto            android.ProtoFlags
-	protoC           bool
-	protoOptionsFile bool
+	protoC           bool // If true, compile protos as `.c` files. Otherwise, output as `.cc`.
+	protoOptionsFile bool // If true, output a proto options file.
 
 	yacc *YaccProperties
 	lex  *LexProperties
 }
 
+// StripFlags represents flags related to stripping. This is separate from builderFlags, as these
+// flags are useful outside of this package (such as for Rust).
 type StripFlags struct {
 	Toolchain                     config.Toolchain
 	StripKeepSymbols              bool
@@ -367,6 +401,7 @@
 	StripUseGnuStrip              bool
 }
 
+// Objects is a collection of file paths corresponding to outputs for C++ related build statements.
 type Objects struct {
 	objFiles      android.Paths
 	tidyFiles     android.Paths
@@ -396,9 +431,10 @@
 }
 
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
-func TransformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles android.Paths,
+func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles android.Paths,
 	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
 
+	// Source files are one-to-one with tidy, coverage, or kythe files, if enabled.
 	objFiles := make(android.Paths, len(srcFiles))
 	var tidyFiles android.Paths
 	if flags.tidy {
@@ -468,6 +504,7 @@
 
 		objFiles[i] = objFile
 
+		// Register compilation build statements. The actual rule used depends on the source file type.
 		switch srcFile.Ext() {
 		case ".asm":
 			ctx.Build(pctx, android.BuildParams{
@@ -532,8 +569,11 @@
 			ccCmd = "clang++"
 			moduleFlags = cppflags
 			moduleToolingFlags = toolingCppflags
+		case ".h", ".hpp":
+			ctx.PropertyErrorf("srcs", "Header file %s is not supported, instead use export_include_dirs or local_include_dirs.", srcFile)
+			continue
 		default:
-			ctx.ModuleErrorf("File %s has unknown extension", srcFile)
+			ctx.PropertyErrorf("srcs", "File %s has unknown extension. Supported extensions: .s, .S, .c, .cpp, .cc, .cxx, .mm", srcFile)
 			continue
 		}
 
@@ -562,6 +602,7 @@
 			},
 		})
 
+		// Register post-process build statements (such as for tidy or kythe).
 		if emitXref {
 			kytheFile := android.ObjPathWithExt(ctx, subdir, srcFile, "kzip")
 			ctx.Build(pctx, android.BuildParams{
@@ -639,7 +680,7 @@
 }
 
 // Generate a rule for compiling multiple .o files to a static library (.a)
-func TransformObjToStaticLib(ctx android.ModuleContext,
+func transformObjToStaticLib(ctx android.ModuleContext,
 	objFiles android.Paths, wholeStaticLibs android.Paths,
 	flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
 
@@ -682,7 +723,7 @@
 
 // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
 // and shared libraries, to a shared library (.so) or dynamic executable
-func TransformObjToDynamicBinary(ctx android.ModuleContext,
+func transformObjToDynamicBinary(ctx android.ModuleContext,
 	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps android.Paths,
 	crtBegin, crtEnd android.OptionalPath, groupLate bool, flags builderFlags, outputFile android.WritablePath, implicitOutputs android.WritablePaths) {
 
@@ -763,7 +804,7 @@
 
 // Generate a rule to combine .dump sAbi dump files from multiple source files
 // into a single .ldump sAbi dump file
-func TransformDumpToLinkedDump(ctx android.ModuleContext, sAbiDumps android.Paths, soFile android.Path,
+func transformDumpToLinkedDump(ctx android.ModuleContext, sAbiDumps android.Paths, soFile android.Path,
 	baseName, exportedHeaderFlags string, symbolFile android.OptionalPath,
 	excludedSymbolVersions, excludedSymbolTags []string) android.OptionalPath {
 
@@ -810,7 +851,8 @@
 	return android.OptionalPathForPath(outputFile)
 }
 
-func UnzipRefDump(ctx android.ModuleContext, zippedRefDump android.Path, baseName string) android.Path {
+// unzipRefDump registers a build statement to unzip a reference abi dump.
+func unzipRefDump(ctx android.ModuleContext, zippedRefDump android.Path, baseName string) android.Path {
 	outputFile := android.PathForModuleOut(ctx, baseName+"_ref.lsdump")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        unzipRefSAbiDump,
@@ -821,7 +863,8 @@
 	return outputFile
 }
 
-func SourceAbiDiff(ctx android.ModuleContext, inputDump android.Path, referenceDump android.Path,
+// sourceAbiDiff registers a build statement to compare linked sAbi dump files (.ldump).
+func sourceAbiDiff(ctx android.ModuleContext, inputDump android.Path, referenceDump android.Path,
 	baseName, exportedHeaderFlags string, checkAllApis, isLlndk, isNdk, isVndkExt bool) android.OptionalPath {
 
 	outputFile := android.PathForModuleOut(ctx, baseName+".abidiff")
@@ -872,7 +915,7 @@
 }
 
 // Generate a rule for extracting a table of contents from a shared library (.so)
-func TransformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path,
+func transformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path,
 	outputFile android.WritablePath, flags builderFlags) {
 
 	var format string
@@ -901,7 +944,7 @@
 }
 
 // Generate a rule for compiling multiple .o files to a .o using ld partial linking
-func TransformObjsToObj(ctx android.ModuleContext, objFiles android.Paths,
+func transformObjsToObj(ctx android.ModuleContext, objFiles android.Paths,
 	flags builderFlags, outputFile android.WritablePath, deps android.Paths) {
 
 	ldCmd := "${config.ClangBin}/clang++"
@@ -926,8 +969,8 @@
 	})
 }
 
-// Generate a rule for runing objcopy --prefix-symbols on a binary
-func TransformBinaryPrefixSymbols(ctx android.ModuleContext, prefix string, inputFile android.Path,
+// Generate a rule for running objcopy --prefix-symbols on a binary
+func transformBinaryPrefixSymbols(ctx android.ModuleContext, prefix string, inputFile android.Path,
 	flags builderFlags, outputFile android.WritablePath) {
 
 	objcopyCmd := gccCmd(flags.toolchain, "objcopy")
@@ -944,7 +987,8 @@
 	})
 }
 
-func TransformStrip(ctx android.ModuleContext, inputFile android.Path,
+// Registers a build statement to invoke `strip` (to discard symbols and data from object files).
+func transformStrip(ctx android.ModuleContext, inputFile android.Path,
 	outputFile android.WritablePath, flags StripFlags) {
 
 	crossCompile := gccCmd(flags.Toolchain, "")
@@ -980,7 +1024,8 @@
 	})
 }
 
-func TransformDarwinStrip(ctx android.ModuleContext, inputFile android.Path,
+// Registers build statement to invoke `strip` on darwin architecture.
+func transformDarwinStrip(ctx android.ModuleContext, inputFile android.Path,
 	outputFile android.WritablePath) {
 
 	ctx.Build(pctx, android.BuildParams{
@@ -991,7 +1036,8 @@
 	})
 }
 
-func TransformCoverageFilesToZip(ctx android.ModuleContext,
+// Registers build statement to zip one or more coverage files.
+func transformCoverageFilesToZip(ctx android.ModuleContext,
 	inputs Objects, baseName string) android.OptionalPath {
 
 	if len(inputs.coverageFiles) > 0 {
@@ -1010,7 +1056,8 @@
 	return android.OptionalPath{}
 }
 
-func TransformArchiveRepack(ctx android.ModuleContext, inputFile android.Path,
+// Rule to repack an archive (.a) file with a subset of object files.
+func transformArchiveRepack(ctx android.ModuleContext, inputFile android.Path,
 	outputFile android.WritablePath, objects []string) {
 
 	ctx.Build(pctx, android.BuildParams{
@@ -1027,33 +1074,3 @@
 func gccCmd(toolchain config.Toolchain, cmd string) string {
 	return filepath.Join(toolchain.GccRoot(), "bin", toolchain.GccTriple()+"-"+cmd)
 }
-
-func splitListForSize(list android.Paths, limit int) (lists []android.Paths, err error) {
-	var i int
-
-	start := 0
-	bytes := 0
-	for i = range list {
-		l := len(list[i].String())
-		if l > limit {
-			return nil, fmt.Errorf("list element greater than size limit (%d)", limit)
-		}
-		if bytes+l > limit {
-			lists = append(lists, list[start:i])
-			start = i
-			bytes = 0
-		}
-		bytes += l + 1 // count a space between each list element
-	}
-
-	lists = append(lists, list[start:])
-
-	totalLen := 0
-	for _, l := range lists {
-		totalLen += len(l)
-	}
-	if totalLen != len(list) {
-		panic(fmt.Errorf("Failed breaking up list, %d != %d", len(list), totalLen))
-	}
-	return lists, nil
-}
diff --git a/cc/cc.go b/cc/cc.go
index 5e4faf2..b1c1264 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -88,9 +88,17 @@
 		ctx.TopDown("double_loadable", checkDoubleLoadableLibraries).Parallel()
 	})
 
-	android.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
+	ctx.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
 }
 
+// Deps is a struct containing module names of dependencies, separated by the kind of dependency.
+// Mutators should use `AddVariationDependencies` or its sibling methods to add actual dependency
+// edges to these modules.
+// This object is constructed in DepsMutator, by calling to various module delegates to set
+// relevant fields. For example, `module.compiler.compilerDeps()` may append type-specific
+// dependencies.
+// This is then consumed by the same DepsMutator, which will call `ctx.AddVariationDependencies()`
+// (or its sibling methods) to set real dependencies on the given modules.
 type Deps struct {
 	SharedLibs, LateSharedLibs                  []string
 	StaticLibs, LateStaticLibs, WholeStaticLibs []string
@@ -103,6 +111,7 @@
 	// Used by DepsMutator to pass system_shared_libs information to check_elf_file.py.
 	SystemSharedLibs []string
 
+	// If true, statically link the unwinder into native libraries/binaries.
 	StaticUnwinderIfLegacy bool
 
 	ReexportSharedLibHeaders, ReexportStaticLibHeaders, ReexportHeaderLibHeaders []string
@@ -122,6 +131,11 @@
 	DynamicLinker   string
 }
 
+// PathDeps is a struct containing file paths to dependencies of a module.
+// It's constructed in depsToPath() by traversing the direct dependencies of the current module.
+// It's used to construct flags for various build statements (such as for compiling and linking).
+// It is then passed to module decorator functions responsible for registering build statements
+// (such as `module.compiler.compile()`).`
 type PathDeps struct {
 	// Paths to .so files
 	SharedLibs, EarlySharedLibs, LateSharedLibs android.Paths
@@ -182,8 +196,12 @@
 	LdFlags         []string // Flags that apply to linker command lines
 }
 
+// Flags contains various types of command line flags (and settings) for use in building build
+// statements related to C++.
 type Flags struct {
-	Local  LocalOrGlobalFlags
+	// Local flags (which individual modules are responsible for). These may override global flags.
+	Local LocalOrGlobalFlags
+	// Global flags (which build system or toolchain is responsible for).
 	Global LocalOrGlobalFlags
 
 	aidlFlags     []string // Flags that apply to aidl source files
@@ -198,19 +216,23 @@
 	SystemIncludeFlags []string
 
 	Toolchain    config.Toolchain
-	Tidy         bool
-	GcovCoverage bool
-	SAbiDump     bool
+	Tidy         bool // True if clang-tidy is enabled.
+	GcovCoverage bool // True if coverage files should be generated.
+	SAbiDump     bool // True if header abi dumps should be generated.
 	EmitXrefs    bool // If true, generate Ninja rules to generate emitXrefs input files for Kythe
 
+	// The instruction set required for clang ("arm" or "thumb").
 	RequiredInstructionSet string
-	DynamicLinker          string
+	// The target-device system path to the dynamic linker.
+	DynamicLinker string
 
 	CFlagsDeps  android.Paths // Files depended on by compiler flags
 	LdFlagsDeps android.Paths // Files depended on by linker flags
 
+	// True if .s files should be processed with the c preprocessor.
 	AssemblerWithCpp bool
-	GroupStaticLibs  bool
+	// True if static libraries should be grouped (using `-Wl,--start-group` and `-Wl,--end-group`).
+	GroupStaticLibs bool
 
 	proto            android.ProtoFlags
 	protoC           bool // Whether to use C instead of C++
@@ -305,10 +327,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 {
@@ -357,6 +380,10 @@
 	Double_loadable *bool
 }
 
+// ModuleContextIntf is an interface (on a module context helper) consisting of functions related
+// to understanding  details about the type of the current module.
+// For example, one might call these functions to determine whether the current module is a static
+// library and/or is installed in vendor directories.
 type ModuleContextIntf interface {
 	static() bool
 	staticBinary() bool
@@ -368,7 +395,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
@@ -411,6 +438,8 @@
 	ModuleContextIntf
 }
 
+// feature represents additional (optional) steps to building cc-related modules, such as invocation
+// of clang-tidy.
 type feature interface {
 	begin(ctx BaseModuleContext)
 	deps(ctx DepsContext, deps Deps) Deps
@@ -418,6 +447,9 @@
 	props() []interface{}
 }
 
+// compiler is the interface for a compiler helper object. Different module decorators may implement
+// this helper differently. For example, compiling a `cc_library` may use a different build
+// statement than building a `toolchain_library`.
 type compiler interface {
 	compilerInit(ctx BaseModuleContext)
 	compilerDeps(ctx DepsContext, deps Deps) Deps
@@ -429,6 +461,9 @@
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects
 }
 
+// linker is the interface for a linker decorator object. Individual module types can provide
+// their own implementation for this decorator, and thus specify custom logic regarding build
+// statements pertaining to linking.
 type linker interface {
 	linkerInit(ctx BaseModuleContext)
 	linkerDeps(ctx DepsContext, deps Deps) Deps
@@ -444,15 +479,19 @@
 	coverageOutputFilePath() android.OptionalPath
 
 	// Get the deps that have been explicitly specified in the properties.
-	// Only updates the
 	linkerSpecifiedDeps(specifiedDeps specifiedDeps) specifiedDeps
 }
 
+// specifiedDeps is a tuple struct representing dependencies of a linked binary owned by the linker.
 type specifiedDeps struct {
-	sharedLibs       []string
-	systemSharedLibs []string // Note nil and [] are semantically distinct.
+	sharedLibs []string
+	// Note nil and [] are semantically distinct. [] prevents linking against the defaults (usually
+	// libc, libm, etc.)
+	systemSharedLibs []string
 }
 
+// installer is the interface for an installer helper object. This helper is responsible for
+// copying build outputs to the appropriate locations so that they may be installed on device.
 type installer interface {
 	installerProps() []interface{}
 	install(ctx ModuleContext, path android.Path)
@@ -550,7 +589,15 @@
 	return d.Kind == staticLibraryDependency
 }
 
-// dependencyTag is used for tagging miscellanous dependency types that don't fit into
+// InstallDepNeeded returns true for shared libraries so that shared library dependencies of
+// binaries or other shared libraries are installed as dependencies.
+func (d libraryDependencyTag) InstallDepNeeded() bool {
+	return d.shared()
+}
+
+var _ android.InstallNeededDependencyTag = libraryDependencyTag{}
+
+// dependencyTag is used for tagging miscellaneous dependency types that don't fit into
 // libraryDependencyTag.  Each tag object is created globally and reused for multiple
 // dependencies (although since the object contains no references, assigning a tag to a
 // variable and modifying it will not modify the original).  Users can compare the tag
@@ -560,18 +607,27 @@
 	name string
 }
 
+// installDependencyTag is used for tagging miscellaneous dependency types that don't fit into
+// libraryDependencyTag, but where the dependency needs to be installed when the parent is
+// installed.
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 var (
 	genSourceDepTag       = dependencyTag{name: "gen source"}
 	genHeaderDepTag       = dependencyTag{name: "gen header"}
 	genHeaderExportDepTag = dependencyTag{name: "gen header export"}
 	objDepTag             = dependencyTag{name: "obj"}
 	linkerFlagsDepTag     = dependencyTag{name: "linker flags file"}
-	dynamicLinkerDepTag   = dependencyTag{name: "dynamic linker"}
+	dynamicLinkerDepTag   = installDependencyTag{name: "dynamic linker"}
 	reuseObjTag           = dependencyTag{name: "reuse objects"}
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
 	dataLibDepTag         = dependencyTag{name: "data lib"}
-	runtimeDepTag         = dependencyTag{name: "runtime lib"}
+	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
 )
@@ -598,8 +654,7 @@
 }
 
 func IsRuntimeDepTag(depTag blueprint.DependencyTag) bool {
-	ccDepTag, ok := depTag.(dependencyTag)
-	return ok && ccDepTag == runtimeDepTag
+	return depTag == runtimeDepTag
 }
 
 func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool {
@@ -609,7 +664,18 @@
 
 // Module contains the properties and members used by all C/C++ module types, and implements
 // the blueprint.Module interface.  It delegates to compiler, linker, and installer interfaces
-// to construct the output file.  Behavior can be customized with a Customizer interface
+// to construct the output file.  Behavior can be customized with a Customizer, or "decorator",
+// interface.
+//
+// To define a C/C++ related module, construct a new Module object and point its delegates to
+// type-specific structs. These delegates will be invoked to register module-specific build
+// statements which may be unique to the module type. For example, module.compiler.compile() should
+// be defined so as to register build statements which are responsible for compiling the module.
+//
+// Another example: to construct a cc_binary module, one can create a `cc.binaryDecorator` struct
+// which implements the `linker` and `installer` interfaces, and points the `linker` and `installer`
+// members of the cc.Module to this decorator. Thus, a cc_binary module has custom linker and
+// installer logic.
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -626,18 +692,23 @@
 	// Allowable SdkMemberTypes of this module type.
 	sdkMemberTypes []android.SdkMemberType
 
-	// delegates, initialize before calling Init
-	features  []feature
+	// decorator delegates, initialize before calling Init
+	// these may contain module-specific implementations, and effectively allow for custom
+	// type-specific logic. These members may reference different objects or the same object.
+	// Functions of these decorators will be invoked to initialize and register type-specific
+	// build statements.
 	compiler  compiler
 	linker    linker
 	installer installer
-	stl       *stl
-	sanitize  *sanitize
-	coverage  *coverage
-	sabi      *sabi
-	vndkdep   *vndkdep
-	lto       *lto
-	pgo       *pgo
+
+	features []feature
+	stl      *stl
+	sanitize *sanitize
+	coverage *coverage
+	sabi     *sabi
+	vndkdep  *vndkdep
+	lto      *lto
+	pgo      *pgo
 
 	library libraryInterface
 
@@ -938,8 +1009,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 {
@@ -1028,6 +1099,19 @@
 	return name
 }
 
+// Similar to ImplementationModuleName, but uses the Make variant of the module
+// name as base name, for use in AndroidMk output. E.g. for a prebuilt module
+// where the Soong name is prebuilt_foo, this returns foo (which works in Make
+// under the premise that the prebuilt module overrides its source counterpart
+// if it is exposed to Make).
+func (c *Module) ImplementationModuleNameForMake(ctx android.BaseModuleContext) string {
+	name := c.BaseModuleName()
+	if versioned, ok := c.linker.(versionedInterface); ok {
+		name = versioned.implementationModuleName(name)
+	}
+	return name
+}
+
 func (c *Module) bootstrap() bool {
 	return Bool(c.Properties.Bootstrap)
 }
@@ -1051,9 +1135,13 @@
 	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":
+	case "libc", "libm", "libdl", "libdl_android", "linker", "linkerconfig":
 		return true
 	}
 	return false
@@ -1140,8 +1228,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 {
@@ -1761,7 +1849,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))
@@ -1836,6 +1924,11 @@
 		return
 	}
 
+	// sysprop_library has to support both C++ and Java. So sysprop_library internally creates one
+	// C++ implementation library and one Java implementation library. When a module links against
+	// sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a
+	// map from sysprop_library to implementation library; it will be used in whole_static_libs,
+	// static_libs, and shared_libs.
 	syspropImplLibraries := syspropImplLibraries(actx.Config())
 	vendorSnapshotStaticLibs := vendorSnapshotStaticLibs(actx.Config())
 
diff --git a/cc/cc_test.go b/cc/cc_test.go
index b803cba..af9b943 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -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)
 }
 
@@ -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"`,
 	})
 }
 
@@ -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", `
@@ -2785,114 +2913,6 @@
 	}
 }
 
-var (
-	str11 = "01234567891"
-	str10 = str11[:10]
-	str9  = str11[:9]
-	str5  = str11[:5]
-	str4  = str11[:4]
-)
-
-var splitListForSizeTestCases = []struct {
-	in   []string
-	out  [][]string
-	size int
-}{
-	{
-		in:   []string{str10},
-		out:  [][]string{{str10}},
-		size: 10,
-	},
-	{
-		in:   []string{str9},
-		out:  [][]string{{str9}},
-		size: 10,
-	},
-	{
-		in:   []string{str5},
-		out:  [][]string{{str5}},
-		size: 10,
-	},
-	{
-		in:   []string{str11},
-		out:  nil,
-		size: 10,
-	},
-	{
-		in:   []string{str10, str10},
-		out:  [][]string{{str10}, {str10}},
-		size: 10,
-	},
-	{
-		in:   []string{str9, str10},
-		out:  [][]string{{str9}, {str10}},
-		size: 10,
-	},
-	{
-		in:   []string{str10, str9},
-		out:  [][]string{{str10}, {str9}},
-		size: 10,
-	},
-	{
-		in:   []string{str5, str4},
-		out:  [][]string{{str5, str4}},
-		size: 10,
-	},
-	{
-		in:   []string{str5, str4, str5},
-		out:  [][]string{{str5, str4}, {str5}},
-		size: 10,
-	},
-	{
-		in:   []string{str5, str4, str5, str4},
-		out:  [][]string{{str5, str4}, {str5, str4}},
-		size: 10,
-	},
-	{
-		in:   []string{str5, str4, str5, str5},
-		out:  [][]string{{str5, str4}, {str5}, {str5}},
-		size: 10,
-	},
-	{
-		in:   []string{str5, str5, str5, str4},
-		out:  [][]string{{str5}, {str5}, {str5, str4}},
-		size: 10,
-	},
-	{
-		in:   []string{str9, str11},
-		out:  nil,
-		size: 10,
-	},
-	{
-		in:   []string{str11, str9},
-		out:  nil,
-		size: 10,
-	},
-}
-
-func TestSplitListForSize(t *testing.T) {
-	for _, testCase := range splitListForSizeTestCases {
-		out, _ := splitListForSize(android.PathsForTesting(testCase.in...), testCase.size)
-
-		var outStrings [][]string
-
-		if len(out) > 0 {
-			outStrings = make([][]string, len(out))
-			for i, o := range out {
-				outStrings[i] = o.Strings()
-			}
-		}
-
-		if !reflect.DeepEqual(outStrings, testCase.out) {
-			t.Errorf("incorrect output:")
-			t.Errorf("     input: %#v", testCase.in)
-			t.Errorf("      size: %d", testCase.size)
-			t.Errorf("  expected: %#v", testCase.out)
-			t.Errorf("       got: %#v", outStrings)
-		}
-	}
-}
-
 var staticLinkDepOrderTestCases = []struct {
 	// This is a string representation of a map[moduleName][]moduleDependency .
 	// It models the dependencies declared in an Android.bp file.
@@ -3941,3 +3961,98 @@
 	}
 
 }
+
+func TestInstallSharedLibs(t *testing.T) {
+	bp := `
+		cc_binary {
+			name: "bin",
+			host_supported: true,
+			shared_libs: ["libshared"],
+			runtime_libs: ["libruntime"],
+			srcs: [":gen"],
+		}
+
+		cc_library_shared {
+			name: "libshared",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+		}
+
+		cc_library_shared {
+			name: "libtransitive",
+			host_supported: true,
+		}
+
+		cc_library_shared {
+			name: "libruntime",
+			host_supported: true,
+		}
+
+		cc_binary_host {
+			name: "tool",
+			srcs: ["foo.cpp"],
+		}
+
+		genrule {
+			name: "gen",
+			tools: ["tool"],
+			out: ["gen.cpp"],
+			cmd: "$(location tool) $(out)",
+		}
+	`
+
+	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	ctx := testCcWithConfig(t, config)
+
+	hostBin := ctx.ModuleForTests("bin", config.BuildOSTarget.String()).Description("install")
+	hostShared := ctx.ModuleForTests("libshared", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostRuntime := ctx.ModuleForTests("libruntime", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTransitive := ctx.ModuleForTests("libtransitive", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTool := ctx.ModuleForTests("tool", config.BuildOSTarget.String()).Description("install")
+
+	if g, w := hostBin.Implicits.Strings(), hostShared.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostShared.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostRuntime.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected host bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := hostBin.Implicits.Strings(), hostTool.Output.String(); android.InList(w, g) {
+		t.Errorf("expected no host bin dependency %q, got %q", w, g)
+	}
+
+	deviceBin := ctx.ModuleForTests("bin", "android_arm64_armv8-a").Description("install")
+	deviceShared := ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared").Description("install")
+	deviceTransitive := ctx.ModuleForTests("libtransitive", "android_arm64_armv8-a_shared").Description("install")
+	deviceRuntime := ctx.ModuleForTests("libruntime", "android_arm64_armv8-a_shared").Description("install")
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceShared.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceShared.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), deviceRuntime.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected device bin dependency %q, got %q", w, g)
+	}
+
+	if g, w := deviceBin.OrderOnly.Strings(), hostTool.Output.String(); android.InList(w, g) {
+		t.Errorf("expected no device bin dependency %q, got %q", w, g)
+	}
+
+}
diff --git a/cc/cflag_artifacts.go b/cc/cflag_artifacts.go
index 855ff25..be46fc0 100644
--- a/cc/cflag_artifacts.go
+++ b/cc/cflag_artifacts.go
@@ -68,7 +68,7 @@
 
 	cleanedName := strings.Replace(flag, "=", "_", -1)
 	filename, filepath := s.incrementFile(ctx, cleanedName, part)
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().Textf("rm -f %s", filepath.String())
 
 	if using {
@@ -84,7 +84,7 @@
 	length := len(modules)
 
 	if length == 0 {
-		rule.Build(pctx, ctx, filename, "gen "+filename)
+		rule.Build(filename, "gen "+filename)
 		part++
 	}
 
@@ -98,11 +98,11 @@
 				strings.Join(proptools.ShellEscapeList(shard), " ")).
 			FlagWithOutput(">> ", filepath).
 			Text("; done")
-		rule.Build(pctx, ctx, filename, "gen "+filename)
+		rule.Build(filename, "gen "+filename)
 
 		if index+1 != len(moduleShards) {
 			filename, filepath = s.incrementFile(ctx, cleanedName, part+index+1)
-			rule = android.NewRuleBuilder()
+			rule = android.NewRuleBuilder(pctx, ctx)
 			rule.Command().Textf("rm -f %s", filepath.String())
 		}
 	}
@@ -121,14 +121,14 @@
 	for _, flag := range TrackedCFlags {
 		// Generate build rule to combine related intermediary files into a
 		// C Flag artifact
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		filename := s.genFlagFilename(flag)
 		outputpath := android.PathForOutput(ctx, "cflags", filename)
 		rule.Command().
 			Text("cat").
 			Inputs(s.interOutputs[flag].Paths()).
 			FlagWithOutput("> ", outputpath)
-		rule.Build(pctx, ctx, filename, "gen "+filename)
+		rule.Build(filename, "gen "+filename)
 		s.outputs = append(s.outputs, outputpath)
 	}
 }
diff --git a/cc/compiler.go b/cc/compiler.go
index 3c86d20..b78bb6c 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -366,7 +366,7 @@
 		}
 		if ctx.Device() {
 			flags.Global.CommonFlags = append(flags.Global.CommonFlags,
-				fmt.Sprintf("-D__ANDROID_SDK_VERSION__=%d",
+				fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
 					ctx.apexSdkVersion().FinalOrFutureInt()))
 		}
 	}
@@ -649,7 +649,7 @@
 func compileObjs(ctx android.ModuleContext, flags builderFlags,
 	subdir string, srcFiles, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
 
-	return TransformSourceToObj(ctx, subdir, srcFiles, flags, pathDeps, cFlagsDeps)
+	return transformSourceToObj(ctx, subdir, srcFiles, flags, pathDeps, cFlagsDeps)
 }
 
 var thirdPartyDirPrefixExceptions = []*regexp.Regexp{
@@ -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..a548452 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -21,6 +21,7 @@
 	"android.hardware.automotive.occupant_awareness-ndk_platform",
 	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-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/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index 81c907d..d7ff580 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -135,7 +135,7 @@
 
 func getMacTools(ctx android.PackageVarContext) *macPlatformTools {
 	macTools.once.Do(func() {
-		xcrunTool := ctx.Config().HostSystemTool("xcrun")
+		xcrunTool := ctx.Config().NonHermeticHostSystemTool("xcrun")
 
 		xcrun := func(args ...string) string {
 			if macTools.err != nil {
diff --git a/cc/fuzz.go b/cc/fuzz.go
index fe3c12b..6b17c48 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -246,25 +246,25 @@
 	fuzz.binaryDecorator.baseInstaller.install(ctx, file)
 
 	fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
-	builder := android.NewRuleBuilder()
+	builder := android.NewRuleBuilder(pctx, ctx)
 	intermediateDir := android.PathForModuleOut(ctx, "corpus")
 	for _, entry := range fuzz.corpus {
 		builder.Command().Text("cp").
 			Input(entry).
 			Output(intermediateDir.Join(ctx, entry.Base()))
 	}
-	builder.Build(pctx, ctx, "copy_corpus", "copy corpus")
+	builder.Build("copy_corpus", "copy corpus")
 	fuzz.corpusIntermediateDir = intermediateDir
 
 	fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
-	builder = android.NewRuleBuilder()
+	builder = android.NewRuleBuilder(pctx, ctx)
 	intermediateDir = android.PathForModuleOut(ctx, "data")
 	for _, entry := range fuzz.data {
 		builder.Command().Text("cp").
 			Input(entry).
 			Output(intermediateDir.Join(ctx, entry.Rel()))
 	}
-	builder.Build(pctx, ctx, "copy_data", "copy data")
+	builder.Build("copy_data", "copy data")
 	fuzz.dataIntermediateDir = intermediateDir
 
 	if fuzz.Properties.Dictionary != nil {
@@ -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
 	}
 
@@ -426,12 +419,12 @@
 		sharedLibraries := collectAllSharedDependencies(ctx, module)
 
 		var files []fileToZip
-		builder := android.NewRuleBuilder()
+		builder := android.NewRuleBuilder(pctx, ctx)
 
 		// Package the corpora into a zipfile.
 		if fuzzModule.corpus != nil {
 			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
-			command := builder.Command().BuiltTool(ctx, "soong_zip").
+			command := builder.Command().BuiltTool("soong_zip").
 				Flag("-j").
 				FlagWithOutput("-o ", corpusZip)
 			command.FlagWithRspFileInputList("-r ", fuzzModule.corpus)
@@ -441,7 +434,7 @@
 		// Package the data into a zipfile.
 		if fuzzModule.data != nil {
 			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
-			command := builder.Command().BuiltTool(ctx, "soong_zip").
+			command := builder.Command().BuiltTool("soong_zip").
 				FlagWithOutput("-o ", dataZip)
 			for _, f := range fuzzModule.data {
 				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
@@ -499,7 +492,7 @@
 		}
 
 		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
-		command := builder.Command().BuiltTool(ctx, "soong_zip").
+		command := builder.Command().BuiltTool("soong_zip").
 			Flag("-j").
 			FlagWithOutput("-o ", fuzzZip)
 		for _, file := range files {
@@ -511,7 +504,7 @@
 			command.FlagWithInput("-f ", file.SourceFilePath)
 		}
 
-		builder.Build(pctx, ctx, "create-"+fuzzZip.String(),
+		builder.Build("create-"+fuzzZip.String(),
 			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
 
 		// Don't add modules to 'make haiku' that are set to not be exported to the
@@ -538,11 +531,11 @@
 		filesToZip := archDirs[archOs]
 		arch := archOs.arch
 		hostOrTarget := archOs.hostOrTarget
-		builder := android.NewRuleBuilder()
+		builder := android.NewRuleBuilder(pctx, ctx)
 		outputFile := android.PathForOutput(ctx, "fuzz-"+hostOrTarget+"-"+arch+".zip")
 		s.packages = append(s.packages, outputFile)
 
-		command := builder.Command().BuiltTool(ctx, "soong_zip").
+		command := builder.Command().BuiltTool("soong_zip").
 			Flag("-j").
 			FlagWithOutput("-o ", outputFile).
 			Flag("-L 0") // No need to try and re-compress the zipfiles.
@@ -556,7 +549,7 @@
 			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
 		}
 
-		builder.Build(pctx, ctx, "create-fuzz-package-"+arch+"-"+hostOrTarget,
+		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
 			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
 	}
 }
diff --git a/cc/gen.go b/cc/gen.go
index ccc3d0e..75b9e49 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -75,7 +75,10 @@
 	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.
+	sboxOutDir := cmd.PathForOutput(outDir)
+	sedCmd := "sed -i.bak 's#" + sboxOutDir + "#" + outDir.String() + "#'"
 	rule.Command().Text(sedCmd).Input(outFile)
 	rule.Command().Text(sedCmd).Input(headerFile)
 
@@ -133,7 +136,7 @@
 	}
 
 	cmd := rule.Command()
-	cmd.BuiltTool(ctx, "aidl-cpp").
+	cmd.BuiltTool("aidl-cpp").
 		FlagWithDepFile("-d", depFile).
 		Flag("--ninja").
 		Flag(aidlFlags).
@@ -228,7 +231,8 @@
 	var yaccRule_ *android.RuleBuilder
 	yaccRule := func() *android.RuleBuilder {
 		if yaccRule_ == nil {
-			yaccRule_ = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "yacc"))
+			yaccRule_ = android.NewRuleBuilder(pctx, ctx).Sbox(android.PathForModuleGen(ctx, "yacc"),
+				android.PathForModuleGen(ctx, "yacc.sbox.textproto"))
 		}
 		return yaccRule_
 	}
@@ -257,7 +261,8 @@
 			deps = append(deps, headerFile)
 		case ".aidl":
 			if aidlRule == nil {
-				aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl"))
+				aidlRule = android.NewRuleBuilder(pctx, ctx).Sbox(android.PathForModuleGen(ctx, "aidl"),
+					android.PathForModuleGen(ctx, "aidl.sbox.textproto"))
 			}
 			cppFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp")
 			depFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp.d")
@@ -279,11 +284,11 @@
 	}
 
 	if aidlRule != nil {
-		aidlRule.Build(pctx, ctx, "aidl", "gen aidl")
+		aidlRule.Build("aidl", "gen aidl")
 	}
 
 	if yaccRule_ != nil {
-		yaccRule_.Build(pctx, ctx, "yacc", "gen yacc")
+		yaccRule_.Build("yacc", "gen yacc")
 	}
 
 	if len(rsFiles) > 0 {
diff --git a/cc/gen_test.go b/cc/gen_test.go
index 4b9a36e..41ef95c 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -18,6 +18,8 @@
 	"path/filepath"
 	"strings"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestGen(t *testing.T) {
@@ -56,13 +58,14 @@
 		}`)
 
 		aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
+		aidlManifest := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
 		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
 		if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.Local.CommonFlags) {
 			t.Errorf("missing aidl includes in global flags")
 		}
 
-		aidlCommand := aidl.RuleParams.Command
+		aidlCommand := android.RuleBuilderSboxProtoForTests(t, aidlManifest).Commands[0].GetCommand()
 		if !strings.Contains(aidlCommand, "-Isub") {
 			t.Errorf("aidl command for c.aidl should contain \"-Isub\", but was %q", aidlCommand)
 		}
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index 0c7952b..fa0c6f2 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -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..c626b03 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()) {
@@ -897,9 +897,9 @@
 		}
 	}
 
-	TransformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, objs.tidyFiles)
+	transformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, objs.tidyFiles)
 
-	library.coverageOutputFile = TransformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
+	library.coverageOutputFile = transformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
 
 	ctx.CheckbuildFile(outputFile)
 
@@ -974,7 +974,7 @@
 	// depending on a table of contents file instead of the library itself.
 	tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.ShlibSuffix()[1:]+".toc")
 	library.tocFile = android.OptionalPathForPath(tocFile)
-	TransformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+	transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
 
 	stripFlags := flagsToStripFlags(flags)
 	if library.stripper.NeedsStrip(ctx) {
@@ -1019,7 +1019,7 @@
 
 	if Bool(library.Properties.Sort_bss_symbols_by_size) {
 		unsortedOutputFile := android.PathForModuleOut(ctx, "unsorted", fileName)
-		TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
+		transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
 			deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
 			linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, unsortedOutputFile, implicitOutputs)
 
@@ -1029,7 +1029,7 @@
 		linkerDeps = append(linkerDeps, symbolOrderingFile)
 	}
 
-	TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
+	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
 		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
 		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs)
 
@@ -1039,7 +1039,7 @@
 	objs.sAbiDumpFiles = append(objs.sAbiDumpFiles, deps.StaticLibObjs.sAbiDumpFiles...)
 	objs.sAbiDumpFiles = append(objs.sAbiDumpFiles, deps.WholeStaticLibObjs.sAbiDumpFiles...)
 
-	library.coverageOutputFile = TransformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
+	library.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
 	library.linkSAbiDumpFiles(ctx, objs, fileName, unstrippedOutputFile)
 
 	var staticAnalogue *StaticLibraryInfo
@@ -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)
@@ -1115,7 +1115,7 @@
 		return refAbiDumpTextFile.Path()
 	}
 	if refAbiDumpGzipFile.Valid() {
-		return UnzipRefDump(ctx, refAbiDumpGzipFile.Path(), fileName)
+		return unzipRefDump(ctx, refAbiDumpGzipFile.Path(), fileName)
 	}
 	return nil
 }
@@ -1141,7 +1141,7 @@
 			SourceAbiFlags = append(SourceAbiFlags, "-I"+reexportedInclude)
 		}
 		exportedHeaderFlags := strings.Join(SourceAbiFlags, " ")
-		library.sAbiOutputFile = TransformDumpToLinkedDump(ctx, objs.sAbiDumpFiles, soFile, fileName, exportedHeaderFlags,
+		library.sAbiOutputFile = transformDumpToLinkedDump(ctx, objs.sAbiDumpFiles, soFile, fileName, exportedHeaderFlags,
 			android.OptionalPathForModuleSrc(ctx, library.symbolFileForAbiCheck(ctx)),
 			library.Properties.Header_abi_checker.Exclude_symbol_versions,
 			library.Properties.Header_abi_checker.Exclude_symbol_tags)
@@ -1150,10 +1150,10 @@
 
 		refAbiDumpFile := getRefAbiDumpFile(ctx, vndkVersion, fileName)
 		if refAbiDumpFile != nil {
-			library.sAbiDiff = SourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
+			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())
 		}
 	}
 }
@@ -1202,15 +1202,21 @@
 		}
 	}
 
+	// If the library is sysprop_library, expose either public or internal header selectively.
 	if library.baseCompiler.hasSrcExt(".sysprop") {
 		dir := android.PathForModuleGen(ctx, "sysprop", "include")
 		if library.Properties.Sysprop.Platform != nil {
-			isProduct := ctx.ProductSpecific() && !ctx.useVndk()
-			isVendor := ctx.useVndk()
+			isClientProduct := ctx.ProductSpecific() && !ctx.useVndk()
+			isClientVendor := ctx.useVndk()
 			isOwnerPlatform := Bool(library.Properties.Sysprop.Platform)
 
+			// If the owner is different from the user, expose public header. That is,
+			// 1) if the user is product (as owner can only be platform / vendor)
+			// 2) if one is platform and the other is vendor
+			// Exceptions are ramdisk and recovery. They are not enforced at all. So
+			// they always use internal header.
 			if !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() &&
-				(isProduct || (isOwnerPlatform == isVendor)) {
+				(isClientProduct || (isOwnerPlatform == isClientVendor)) {
 				dir = android.PathForModuleGen(ctx, "sysprop/public", "include")
 			}
 		}
@@ -1748,13 +1754,13 @@
 		hashedOutputfile := outputFile
 		outputFile = android.PathForModuleOut(ctx, "unhashed", fileName)
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
-			BuiltTool(ctx, "bssl_inject_hash").
+			BuiltTool("bssl_inject_hash").
 			Flag("-sha256").
 			FlagWithInput("-in-object ", outputFile).
 			FlagWithOutput("-o ", hashedOutputfile)
-		rule.Build(pctx, ctx, "injectCryptoHash", "inject crypto hash")
+		rule.Build("injectCryptoHash", "inject crypto hash")
 	}
 
 	return outputFile
diff --git a/cc/linker.go b/cc/linker.go
index cbf8898..9d4a643 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -211,6 +211,7 @@
 	linker.Properties.Ldflags = append(linker.Properties.Ldflags, flags...)
 }
 
+// linkerInit initializes dynamic properties of the linker (such as runpath).
 func (linker *baseLinker) linkerInit(ctx BaseModuleContext) {
 	if ctx.toolchain().Is64Bit() {
 		linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, "../lib64", "lib64")
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/object.go b/cc/object.go
index ab2672b..3ce7676 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -124,7 +124,7 @@
 
 		if String(object.Properties.Prefix_symbols) != "" {
 			output := android.PathForModuleOut(ctx, ctx.ModuleName()+objectExtension)
-			TransformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), outputFile,
+			transformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), outputFile,
 				builderFlags, output)
 			outputFile = output
 		}
@@ -134,12 +134,12 @@
 
 		if String(object.Properties.Prefix_symbols) != "" {
 			input := android.PathForModuleOut(ctx, "unprefixed", ctx.ModuleName()+objectExtension)
-			TransformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), input,
+			transformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), input,
 				builderFlags, output)
 			output = input
 		}
 
-		TransformObjsToObj(ctx, objs.objFiles, builderFlags, output, flags.LdFlagsDeps)
+		transformObjsToObj(ctx, objs.objFiles, builderFlags, output, flags.LdFlagsDeps)
 	}
 
 	ctx.CheckbuildFile(outputFile)
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 8873883..37df4ba 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -146,7 +146,7 @@
 			// depending on a table of contents file instead of the library itself.
 			tocFile := android.PathForModuleOut(ctx, libName+".toc")
 			p.tocFile = android.OptionalPathForPath(tocFile)
-			TransformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+			transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
 
 			if ctx.Windows() && p.properties.Windows_import_lib != nil {
 				// Consumers of this library actually links to the import library in build
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 5bf334e..ee4de6e 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -32,7 +32,7 @@
 	// * Configure that we are inside make
 	// * Add CommonOS to ensure that androidmk processing works.
 	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetInMakeForTests(config)
+	android.SetKatiEnabledForTests(config)
 
 	for _, handler := range handlers {
 		handler(config)
diff --git a/cc/proto.go b/cc/proto.go
index ae988ec..4466144 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -50,11 +50,11 @@
 	depFile := ccFile.ReplaceExtension(ctx, "d")
 	outputs := android.WritablePaths{ccFile, headerFile}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
-	android.ProtoRule(ctx, rule, protoFile, flags.proto, protoDeps, outDir, depFile, outputs)
+	android.ProtoRule(rule, protoFile, flags.proto, protoDeps, outDir, depFile, outputs)
 
-	rule.Build(pctx, ctx, "protoc_"+protoFile.Rel(), "protoc "+protoFile.Rel())
+	rule.Build("protoc_"+protoFile.Rel(), "protoc "+protoFile.Rel())
 
 	return ccFile, headerFile
 }
@@ -130,6 +130,8 @@
 			flags.protoC = true
 			flags.protoOptionsFile = true
 			flags.proto.OutTypeFlag = "--nanopb_out"
+			// Disable nanopb timestamps to support remote caching.
+			flags.proto.OutParams = append(flags.proto.OutParams, "-T")
 			plugin = "protoc-gen-nanopb"
 		case "full":
 			flags.proto.OutTypeFlag = "--cpp_out"
diff --git a/cc/sanitize.go b/cc/sanitize.go
index b1326d9..22ee25f 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -169,8 +169,14 @@
 		Cfi              *bool    `android:"arch_variant"`
 		Integer_overflow *bool    `android:"arch_variant"`
 		Misc_undefined   []string `android:"arch_variant"`
-		No_recover       []string
-	}
+		No_recover       []string `android:"arch_variant"`
+	} `android:"arch_variant"`
+
+	// Sanitizers to run with flag configuration specified
+	Config struct {
+		// Enables CFI support flags for assembly-heavy libraries
+		Cfi_assembly_support *bool `android:"arch_variant"`
+	} `android:"arch_variant"`
 
 	// value to pass to -fsanitize-recover=
 	Recover []string
@@ -543,6 +549,9 @@
 
 		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
+		if Bool(sanitize.Properties.Sanitize.Config.Cfi_assembly_support) {
+			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-cfi-canonical-jump-tables")
+		}
 		// Only append the default visibility flag if -fvisibility has not already been set
 		// to hidden.
 		if !inList("-fvisibility=hidden", flags.Local.CFlags) {
@@ -847,7 +856,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/strip.go b/cc/strip.go
index 18150dc..b1f34bb 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -20,29 +20,49 @@
 	"android/soong/android"
 )
 
+// StripProperties defines the type of stripping applied to the module.
 type StripProperties struct {
 	Strip struct {
-		None                         *bool    `android:"arch_variant"`
-		All                          *bool    `android:"arch_variant"`
-		Keep_symbols                 *bool    `android:"arch_variant"`
-		Keep_symbols_list            []string `android:"arch_variant"`
-		Keep_symbols_and_debug_frame *bool    `android:"arch_variant"`
+		// none forces all stripping to be disabled.
+		// Device modules default to stripping enabled leaving mini debuginfo.
+		// Host modules default to stripping disabled, but can be enabled by setting any other
+		// strip boolean property.
+		None *bool `android:"arch_variant"`
+
+		// all forces stripping everything, including the mini debug info.
+		All *bool `android:"arch_variant"`
+
+		// keep_symbols enables stripping but keeps all symbols.
+		Keep_symbols *bool `android:"arch_variant"`
+
+		// keep_symbols_list specifies a list of symbols to keep if keep_symbols is enabled.
+		// If it is unset then all symbols are kept.
+		Keep_symbols_list []string `android:"arch_variant"`
+
+		// keep_symbols_and_debug_frame enables stripping but keeps all symbols and debug frames.
+		Keep_symbols_and_debug_frame *bool `android:"arch_variant"`
 	} `android:"arch_variant"`
 }
 
+// Stripper defines the stripping actions and properties for a module.
 type Stripper struct {
 	StripProperties StripProperties
 }
 
+// NeedsStrip determines if stripping is required for a module.
 func (stripper *Stripper) NeedsStrip(actx android.ModuleContext) bool {
-	// TODO(ccross): enable host stripping when embedded in make?  Make never had support for stripping host binaries.
-	return (!actx.Config().EmbeddedInMake() || actx.Device()) && !Bool(stripper.StripProperties.Strip.None)
+	forceDisable := Bool(stripper.StripProperties.Strip.None)
+	defaultEnable := (!actx.Config().KatiEnabled() || actx.Device())
+	forceEnable := Bool(stripper.StripProperties.Strip.All) ||
+		Bool(stripper.StripProperties.Strip.Keep_symbols) ||
+		Bool(stripper.StripProperties.Strip.Keep_symbols_and_debug_frame)
+	return !forceDisable && (forceEnable || defaultEnable)
 }
 
 func (stripper *Stripper) strip(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
 	flags StripFlags, isStaticLib bool) {
 	if actx.Darwin() {
-		TransformDarwinStrip(actx, in, out)
+		transformDarwinStrip(actx, in, out)
 	} else {
 		if Bool(stripper.StripProperties.Strip.Keep_symbols) {
 			flags.StripKeepSymbols = true
@@ -56,15 +76,21 @@
 		if actx.Config().Debuggable() && !flags.StripKeepMiniDebugInfo && !isStaticLib {
 			flags.StripAddGnuDebuglink = true
 		}
-		TransformStrip(actx, in, out, flags)
+		transformStrip(actx, in, out, flags)
 	}
 }
 
+// StripExecutableOrSharedLib strips a binary or shared library from its debug
+// symbols and other debugging information. The helper function
+// flagsToStripFlags may be used to generate the flags argument.
 func (stripper *Stripper) StripExecutableOrSharedLib(actx android.ModuleContext, in android.Path,
 	out android.ModuleOutPath, flags StripFlags) {
 	stripper.strip(actx, in, out, flags, false)
 }
 
+// StripStaticLib strips a static library from its debug symbols and other
+// debugging information. The helper function flagsToStripFlags may be used to
+// generate the flags argument.
 func (stripper *Stripper) StripStaticLib(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
 	flags StripFlags) {
 	stripper.strip(actx, in, out, flags, true)
diff --git a/cc/stub_library.go b/cc/stub_library.go
new file mode 100644
index 0000000..76d236c
--- /dev/null
+++ b/cc/stub_library.go
@@ -0,0 +1,82 @@
+// 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 cc
+
+import (
+	"strings"
+
+	"android/soong/android"
+)
+
+func init() {
+	// Use singleton type to gather all generated soong modules.
+	android.RegisterSingletonType("stublibraries", stubLibrariesSingleton)
+}
+
+type stubLibraries struct {
+	stubLibraryMap map[string]bool
+}
+
+// Check if the module defines stub, or itself is stub
+func isStubTarget(m *Module) bool {
+	if m.IsStubs() || m.HasStubsVariants() {
+		return true
+	}
+
+	// Library which defines LLNDK Stub is also Stub target.
+	// Pure LLNDK Stub target would not contain any packaging
+	// with target file path.
+	if library, ok := m.linker.(*libraryDecorator); ok {
+		if library.Properties.Llndk_stubs != nil {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Get target file name to be installed from this module
+func getInstalledFileName(m *Module) string {
+	for _, ps := range m.PackagingSpecs() {
+		if name := ps.FileName(); name != "" {
+			return name
+		}
+	}
+	return ""
+}
+
+func (s *stubLibraries) GenerateBuildActions(ctx android.SingletonContext) {
+	// Visit all generated soong modules and store stub library file names.
+	ctx.VisitAllModules(func(module android.Module) {
+		if m, ok := module.(*Module); ok {
+			if isStubTarget(m) {
+				if name := getInstalledFileName(m); name != "" {
+					s.stubLibraryMap[name] = true
+				}
+			}
+		}
+	})
+}
+
+func stubLibrariesSingleton() android.Singleton {
+	return &stubLibraries{
+		stubLibraryMap: make(map[string]bool),
+	}
+}
+
+func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
+	// Convert stub library file names into Makefile variable.
+	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+}
diff --git a/cc/sysprop.go b/cc/sysprop.go
index 6cac7fb..f578b50 100644
--- a/cc/sysprop.go
+++ b/cc/sysprop.go
@@ -14,6 +14,25 @@
 
 package cc
 
+// This file contains a map to redirect dependencies towards sysprop_library.
+// As sysprop_library has to support both Java and C++, sysprop_library internally
+// generates cc_library and java_library. For example, the following sysprop_library
+//
+//     sysprop_library {
+//         name: "foo",
+//     }
+//
+// will internally generate with prefix "lib"
+//
+//     cc_library {
+//         name: "libfoo",
+//     }
+//
+// When a cc module links against "foo", build system will redirect the
+// dependency to "libfoo". To do that, SyspropMutator gathers all sysprop_library,
+// records their cc implementation library names to a map. The map will be used in
+// cc.Module.DepsMutator.
+
 import (
 	"sync"
 
@@ -22,7 +41,7 @@
 
 type syspropLibraryInterface interface {
 	BaseModuleName() string
-	CcModuleName() string
+	CcImplementationModuleName() string
 }
 
 var (
@@ -43,6 +62,8 @@
 		syspropImplLibrariesLock.Lock()
 		defer syspropImplLibrariesLock.Unlock()
 
-		syspropImplLibraries[m.BaseModuleName()] = m.CcModuleName()
+		// BaseModuleName is the name of sysprop_library
+		// CcImplementationModuleName is the name of cc_library generated by sysprop_library
+		syspropImplLibraries[m.BaseModuleName()] = m.CcImplementationModuleName()
 	}
 }
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/testing.go b/cc/testing.go
index 5a311f4..95a93a0 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/genrule"
 )
 
 func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
@@ -24,6 +25,7 @@
 	RegisterBinaryBuildComponents(ctx)
 	RegisterLibraryBuildComponents(ctx)
 	RegisterLibraryHeadersBuildComponents(ctx)
+	genrule.RegisterGenruleBuildComponents(ctx)
 
 	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("llndk_library", LlndkLibraryFactory)
@@ -164,6 +166,10 @@
 			product_available: true,
 			recovery_available: true,
 			src: "",
+			apex_available: [
+				"//apex_available:platform",
+				"//apex_available:anyapex",
+			],
 		}
 
 		toolchain_library {
@@ -563,6 +569,7 @@
 	RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
+	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
 
 	return ctx
 }
diff --git a/cc/toolchain_library.go b/cc/toolchain_library.go
index 0c934ad..bda73ea 100644
--- a/cc/toolchain_library.go
+++ b/cc/toolchain_library.go
@@ -90,7 +90,7 @@
 	if library.Properties.Repack_objects_to_keep != nil {
 		fileName := ctx.ModuleName() + staticLibraryExtension
 		repackedPath := android.PathForModuleOut(ctx, fileName)
-		TransformArchiveRepack(ctx, outputFile, repackedPath, library.Properties.Repack_objects_to_keep)
+		transformArchiveRepack(ctx, outputFile, repackedPath, library.Properties.Repack_objects_to_keep)
 		outputFile = repackedPath
 	}
 
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 78bde38..3ef0b62 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()]
@@ -222,7 +339,7 @@
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		TransformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
 			SharedLibrary:           in,
@@ -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")
-	zipRule := android.NewRuleBuilder()
+	zipPath := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+".zip")
+	zipRule := android.NewRuleBuilder(pctx, ctx)
 
 	// 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 ", "\\'").
@@ -908,18 +1140,20 @@
 	zipRule.Temporary(snapshotOutputList)
 
 	zipRule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", zipPath).
 		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
 		FlagWithInput("-l ", snapshotOutputList)
 
-	zipRule.Build(pctx, ctx, zipPath.String(), "vendor snapshot "+zipPath.String())
+	zipRule.Build(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..d57cdf7 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)
@@ -734,7 +727,7 @@
 		var txtBuilder strings.Builder
 		for idx, k := range android.SortedStringKeys(m) {
 			if idx > 0 {
-				txtBuilder.WriteString("\\n")
+				txtBuilder.WriteString("\n")
 			}
 			txtBuilder.WriteString(k)
 			txtBuilder.WriteString(" ")
@@ -769,7 +762,7 @@
 	})
 
 	zipPath := android.PathForOutput(ctx, snapshotDir, "android-vndk-"+ctx.DeviceConfig().DeviceArch()+".zip")
-	zipRule := android.NewRuleBuilder()
+	zipRule := android.NewRuleBuilder(pctx, ctx)
 
 	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with xargs
 	snapshotOutputList := android.PathForOutput(ctx, snapshotDir, "android-vndk-"+ctx.DeviceConfig().DeviceArch()+"_list")
@@ -782,12 +775,12 @@
 	zipRule.Temporary(snapshotOutputList)
 
 	zipRule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", zipPath).
 		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
 		FlagWithInput("-l ", snapshotOutputList)
 
-	zipRule.Build(pctx, ctx, zipPath.String(), "vndk snapshot "+zipPath.String())
+	zipRule.Build(zipPath.String(), "vndk snapshot "+zipPath.String())
 	zipRule.DeleteTemporaryFiles()
 	c.vndkSnapshotZipFile = android.OptionalPathForPath(zipPath)
 }
@@ -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/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index c0320eb..e6e2ad8 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -154,7 +154,7 @@
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		TransformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
 
 		p.androidMkSuffix = p.NameSuffix()
 
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index 6fa304e..f5e87c0 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -14,8 +14,20 @@
 
 blueprint_go_binary {
     name: "sbox",
-    deps: ["soong-makedeps"],
+    deps: [
+        "sbox_proto",
+        "soong-makedeps",
+    ],
     srcs: [
         "sbox.go",
     ],
 }
+
+bootstrap_go_package {
+    name: "sbox_proto",
+    pkgPath: "android/soong/cmd/sbox/sbox_proto",
+    deps: ["golang-protobuf-proto"],
+    srcs: [
+        "sbox_proto/sbox.pb.go",
+    ],
+}
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 65a34fd..a4f57ea 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -16,44 +16,44 @@
 
 import (
 	"bytes"
+	"crypto/sha1"
+	"encoding/hex"
 	"errors"
 	"flag"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 
+	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/makedeps"
+
+	"github.com/golang/protobuf/proto"
 )
 
 var (
 	sandboxesRoot string
-	rawCommand    string
-	outputRoot    string
+	manifestFile  string
 	keepOutDir    bool
-	depfileOut    string
-	inputHash     string
+)
+
+const (
+	depFilePlaceholder    = "__SBOX_DEPFILE__"
+	sandboxDirPlaceholder = "__SBOX_SANDBOX_DIR__"
 )
 
 func init() {
 	flag.StringVar(&sandboxesRoot, "sandbox-path", "",
 		"root of temp directory to put the sandbox into")
-	flag.StringVar(&rawCommand, "c", "",
-		"command to run")
-	flag.StringVar(&outputRoot, "output-root", "",
-		"root of directory to copy outputs into")
+	flag.StringVar(&manifestFile, "manifest", "",
+		"textproto manifest describing the sandboxed command(s)")
 	flag.BoolVar(&keepOutDir, "keep-out-dir", false,
 		"whether to keep the sandbox directory when done")
-
-	flag.StringVar(&depfileOut, "depfile-out", "",
-		"file path of the depfile to generate. This value will replace '__SBOX_DEPFILE__' in the command and will be treated as an output but won't be added to __SBOX_OUT_FILES__")
-
-	flag.StringVar(&inputHash, "input-hash", "",
-		"This option is ignored. Typical usage is to supply a hash of the list of input names so that the module will be rebuilt if the list (and thus the hash) changes.")
 }
 
 func usageViolation(violation string) {
@@ -62,11 +62,7 @@
 	}
 
 	fmt.Fprintf(os.Stderr,
-		"Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> [--depfile-out depFile] [--input-hash hash] <outputFile> [<outputFile>...]\n"+
-			"\n"+
-			"Deletes <outputRoot>,"+
-			"runs <commandToRun>,"+
-			"and moves each <outputFile> out of <sandboxPath> and into <outputRoot>\n")
+		"Usage: sbox --manifest <manifest> --sandbox-path <sandboxPath>\n")
 
 	flag.PrintDefaults()
 
@@ -103,8 +99,8 @@
 }
 
 func run() error {
-	if rawCommand == "" {
-		usageViolation("-c <commandToRun> is required and must be non-empty")
+	if manifestFile == "" {
+		usageViolation("--manifest <manifest> is required and must be non-empty")
 	}
 	if sandboxesRoot == "" {
 		// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
@@ -114,61 +110,43 @@
 		// and by passing it as a parameter we don't need to duplicate its value
 		usageViolation("--sandbox-path <sandboxPath> is required and must be non-empty")
 	}
-	if len(outputRoot) == 0 {
-		usageViolation("--output-root <outputRoot> is required and must be non-empty")
+
+	manifest, err := readManifest(manifestFile)
+
+	if len(manifest.Commands) == 0 {
+		return fmt.Errorf("at least one commands entry is required in %q", manifestFile)
 	}
 
-	// the contents of the __SBOX_OUT_FILES__ variable
-	outputsVarEntries := flag.Args()
-	if len(outputsVarEntries) == 0 {
-		usageViolation("at least one output file must be given")
+	// setup sandbox directory
+	err = os.MkdirAll(sandboxesRoot, 0777)
+	if err != nil {
+		return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
 	}
 
-	// all outputs
-	var allOutputs []string
+	// This tool assumes that there are no two concurrent runs with the same
+	// manifestFile. It should therefore be safe to use the hash of the
+	// manifestFile as the temporary directory name. We do this because it
+	// makes the temporary directory name deterministic. There are some
+	// tools that embed the name of the temporary output in the output, and
+	// they otherwise cause non-determinism, which then poisons actions
+	// depending on this one.
+	hash := sha1.New()
+	hash.Write([]byte(manifestFile))
+	tempDir := filepath.Join(sandboxesRoot, "sbox", hex.EncodeToString(hash.Sum(nil)))
 
-	// setup directories
-	err := os.MkdirAll(sandboxesRoot, 0777)
+	err = os.RemoveAll(tempDir)
 	if err != nil {
 		return err
 	}
-	err = os.RemoveAll(outputRoot)
+	err = os.MkdirAll(tempDir, 0777)
 	if err != nil {
-		return err
-	}
-	err = os.MkdirAll(outputRoot, 0777)
-	if err != nil {
-		return err
-	}
-
-	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
-
-	for i, filePath := range outputsVarEntries {
-		if !strings.HasPrefix(filePath, "__SBOX_OUT_DIR__/") {
-			return fmt.Errorf("output files must start with `__SBOX_OUT_DIR__/`")
-		}
-		outputsVarEntries[i] = strings.TrimPrefix(filePath, "__SBOX_OUT_DIR__/")
-	}
-
-	allOutputs = append([]string(nil), outputsVarEntries...)
-
-	if depfileOut != "" {
-		sandboxedDepfile, err := filepath.Rel(outputRoot, depfileOut)
-		if err != nil {
-			return err
-		}
-		allOutputs = append(allOutputs, sandboxedDepfile)
-		rawCommand = strings.Replace(rawCommand, "__SBOX_DEPFILE__", filepath.Join(tempDir, sandboxedDepfile), -1)
-
-	}
-
-	if err != nil {
-		return fmt.Errorf("Failed to create temp dir: %s", err)
+		return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
 	}
 
 	// In the common case, the following line of code is what removes the sandbox
 	// If a fatal error occurs (such as if our Go process is killed unexpectedly),
-	// then at the beginning of the next build, Soong will retry the cleanup
+	// then at the beginning of the next build, Soong will wipe the temporary
+	// directory.
 	defer func() {
 		// in some cases we decline to remove the temp dir, to facilitate debugging
 		if !keepOutDir {
@@ -176,27 +154,95 @@
 		}
 	}()
 
-	if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
-		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
-	}
+	// If there is more than one command in the manifest use a separate directory for each one.
+	useSubDir := len(manifest.Commands) > 1
+	var commandDepFiles []string
 
-	if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
-		// expands into a space-separated list of output files to be generated into the sandbox directory
-		tempOutPaths := []string{}
-		for _, outputPath := range outputsVarEntries {
-			tempOutPath := path.Join(tempDir, outputPath)
-			tempOutPaths = append(tempOutPaths, tempOutPath)
+	for i, command := range manifest.Commands {
+		localTempDir := tempDir
+		if useSubDir {
+			localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
 		}
-		pathsText := strings.Join(tempOutPaths, " ")
-		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
-	}
-
-	for _, filePath := range allOutputs {
-		dir := path.Join(tempDir, filepath.Dir(filePath))
-		err = os.MkdirAll(dir, 0777)
+		depFile, err := runCommand(command, localTempDir)
 		if err != nil {
+			// Running the command failed, keep the temporary output directory around in
+			// case a user wants to inspect it for debugging purposes.  Soong will delete
+			// it at the beginning of the next build anyway.
+			keepOutDir = true
 			return err
 		}
+		if depFile != "" {
+			commandDepFiles = append(commandDepFiles, depFile)
+		}
+	}
+
+	outputDepFile := manifest.GetOutputDepfile()
+	if len(commandDepFiles) > 0 && outputDepFile == "" {
+		return fmt.Errorf("Sandboxed commands used %s but output depfile is not set in manifest file",
+			depFilePlaceholder)
+	}
+
+	if outputDepFile != "" {
+		// Merge the depfiles from each command in the manifest to a single output depfile.
+		err = rewriteDepFiles(commandDepFiles, outputDepFile)
+		if err != nil {
+			return fmt.Errorf("failed merging depfiles: %w", err)
+		}
+	}
+
+	return nil
+}
+
+// readManifest reads an sbox manifest from a textproto file.
+func readManifest(file string) (*sbox_proto.Manifest, error) {
+	manifestData, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, fmt.Errorf("error reading manifest %q: %w", file, err)
+	}
+
+	manifest := sbox_proto.Manifest{}
+
+	err = proto.UnmarshalText(string(manifestData), &manifest)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
+	}
+
+	return &manifest, nil
+}
+
+// runCommand runs a single command from a manifest.  If the command references the
+// __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
+func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
+	rawCommand := command.GetCommand()
+	if rawCommand == "" {
+		return "", fmt.Errorf("command is required")
+	}
+
+	err = os.MkdirAll(tempDir, 0777)
+	if err != nil {
+		return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
+	}
+
+	// Copy in any files specified by the manifest.
+	err = copyFiles(command.CopyBefore, "", tempDir)
+	if err != nil {
+		return "", err
+	}
+
+	if strings.Contains(rawCommand, depFilePlaceholder) {
+		depFile = filepath.Join(tempDir, "deps.d")
+		rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
+	}
+
+	if strings.Contains(rawCommand, sandboxDirPlaceholder) {
+		rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, tempDir, -1)
+	}
+
+	// Emulate ninja's behavior of creating the directories for any output files before
+	// running the command.
+	err = makeOutputDirs(command.CopyAfter, tempDir)
+	if err != nil {
+		return "", err
 	}
 
 	commandDescription := rawCommand
@@ -205,27 +251,20 @@
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
+
+	if command.GetChdir() {
+		cmd.Dir = tempDir
+	}
 	err = cmd.Run()
 
 	if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
-		return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
+		return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
 	} else if err != nil {
-		return err
+		return "", err
 	}
 
-	// validate that all files are created properly
-	var missingOutputErrors []string
-	for _, filePath := range allOutputs {
-		tempPath := filepath.Join(tempDir, filePath)
-		fileInfo, err := os.Stat(tempPath)
-		if err != nil {
-			missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: does not exist", filePath))
-			continue
-		}
-		if fileInfo.IsDir() {
-			missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: not a file", filePath))
-		}
-	}
+	missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
+
 	if len(missingOutputErrors) > 0 {
 		// find all created files for making a more informative error message
 		createdFiles := findAllFilesUnder(tempDir)
@@ -236,7 +275,7 @@
 		errorMessage += "in sandbox " + tempDir + ",\n"
 		errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
 		for _, missingOutputError := range missingOutputErrors {
-			errorMessage += "  " + missingOutputError + "\n"
+			errorMessage += "  " + missingOutputError.Error() + "\n"
 		}
 		if len(createdFiles) < 1 {
 			errorMessage += "created 0 files."
@@ -253,19 +292,120 @@
 			}
 		}
 
-		// Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
-		// Soong will delete it later anyway.
-		keepOutDir = true
-		return errors.New(errorMessage)
+		return "", errors.New(errorMessage)
 	}
 	// the created files match the declared files; now move them
-	for _, filePath := range allOutputs {
-		tempPath := filepath.Join(tempDir, filePath)
-		destPath := filePath
-		if len(outputRoot) != 0 {
-			destPath = filepath.Join(outputRoot, filePath)
+	err = moveFiles(command.CopyAfter, tempDir, "")
+
+	return depFile, nil
+}
+
+// makeOutputDirs creates directories in the sandbox dir for every file that has a rule to be copied
+// out of the sandbox.  This emulate's Ninja's behavior of creating directories for output files
+// so that the tools don't have to.
+func makeOutputDirs(copies []*sbox_proto.Copy, sandboxDir string) error {
+	for _, copyPair := range copies {
+		dir := joinPath(sandboxDir, filepath.Dir(copyPair.GetFrom()))
+		err := os.MkdirAll(dir, 0777)
+		if err != nil {
+			return err
 		}
-		err := os.MkdirAll(filepath.Dir(destPath), 0777)
+	}
+	return nil
+}
+
+// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
+// were created by the command.
+func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
+	var missingOutputErrors []error
+	for _, copyPair := range copies {
+		fromPath := joinPath(sandboxDir, copyPair.GetFrom())
+		fileInfo, err := os.Stat(fromPath)
+		if err != nil {
+			missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: does not exist", fromPath))
+			continue
+		}
+		if fileInfo.IsDir() {
+			missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
+		}
+	}
+	return missingOutputErrors
+}
+
+// copyFiles copies files in or out of the sandbox.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+	for _, copyPair := range copies {
+		fromPath := joinPath(fromDir, copyPair.GetFrom())
+		toPath := joinPath(toDir, copyPair.GetTo())
+		err := copyOneFile(fromPath, toPath)
+		if err != nil {
+			return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
+		}
+	}
+	return nil
+}
+
+// copyOneFile copies a file.
+func copyOneFile(from string, to string) error {
+	err := os.MkdirAll(filepath.Dir(to), 0777)
+	if err != nil {
+		return err
+	}
+
+	stat, err := os.Stat(from)
+	if err != nil {
+		return err
+	}
+
+	perm := stat.Mode()
+
+	in, err := os.Open(from)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	out, err := os.Create(to)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		out.Close()
+		if err != nil {
+			os.Remove(to)
+		}
+	}()
+
+	_, err = io.Copy(out, in)
+	if err != nil {
+		return err
+	}
+
+	if err = out.Close(); err != nil {
+		return err
+	}
+
+	if err = os.Chmod(to, perm); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// moveFiles moves files specified by a set of copy rules.  It uses os.Rename, so it is restricted
+// to moving files where the source and destination are in the same filesystem.  This is OK for
+// sbox because the temporary directory is inside the out directory.  It updates the timestamp
+// of the new file.
+func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+	for _, copyPair := range copies {
+		fromPath := joinPath(fromDir, copyPair.GetFrom())
+		toPath := joinPath(toDir, copyPair.GetTo())
+		err := os.MkdirAll(filepath.Dir(toPath), 0777)
+		if err != nil {
+			return err
+		}
+
+		err = os.Rename(fromPath, toPath)
 		if err != nil {
 			return err
 		}
@@ -273,37 +413,53 @@
 		// Update the timestamp of the output file in case the tool wrote an old timestamp (for example, tar can extract
 		// files with old timestamps).
 		now := time.Now()
-		err = os.Chtimes(tempPath, now, now)
-		if err != nil {
-			return err
-		}
-
-		err = os.Rename(tempPath, destPath)
+		err = os.Chtimes(toPath, now, now)
 		if err != nil {
 			return err
 		}
 	}
-
-	// Rewrite the depfile so that it doesn't include the (randomized) sandbox directory
-	if depfileOut != "" {
-		in, err := ioutil.ReadFile(depfileOut)
-		if err != nil {
-			return err
-		}
-
-		deps, err := makedeps.Parse(depfileOut, bytes.NewBuffer(in))
-		if err != nil {
-			return err
-		}
-
-		deps.Output = "outputfile"
-
-		err = ioutil.WriteFile(depfileOut, deps.Print(), 0666)
-		if err != nil {
-			return err
-		}
-	}
-
-	// TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
 	return nil
 }
+
+// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
+// to an output file.
+func rewriteDepFiles(ins []string, out string) error {
+	var mergedDeps []string
+	for _, in := range ins {
+		data, err := ioutil.ReadFile(in)
+		if err != nil {
+			return err
+		}
+
+		deps, err := makedeps.Parse(in, bytes.NewBuffer(data))
+		if err != nil {
+			return err
+		}
+		mergedDeps = append(mergedDeps, deps.Inputs...)
+	}
+
+	deps := makedeps.Deps{
+		// Ninja doesn't care what the output file is, so we can use any string here.
+		Output: "outputfile",
+		Inputs: mergedDeps,
+	}
+
+	// Make the directory for the output depfile in case it is in a different directory
+	// than any of the output files.
+	outDir := filepath.Dir(out)
+	err := os.MkdirAll(outDir, 0777)
+	if err != nil {
+		return fmt.Errorf("failed to create %q: %w", outDir, err)
+	}
+
+	return ioutil.WriteFile(out, deps.Print(), 0666)
+}
+
+// joinPath wraps filepath.Join but returns file without appending to dir if file is
+// absolute.
+func joinPath(dir, file string) string {
+	if filepath.IsAbs(file) {
+		return file
+	}
+	return filepath.Join(dir, file)
+}
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
new file mode 100644
index 0000000..6584bdf
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -0,0 +1,233 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: sbox.proto
+
+package sbox_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// A set of commands to run in a sandbox.
+type Manifest struct {
+	// A list of commands to run in the sandbox.
+	Commands []*Command `protobuf:"bytes,1,rep,name=commands" json:"commands,omitempty"`
+	// If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+	// merged into the given output file relative to the $PWD when sbox was started.
+	OutputDepfile        *string  `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Manifest) Reset()         { *m = Manifest{} }
+func (m *Manifest) String() string { return proto.CompactTextString(m) }
+func (*Manifest) ProtoMessage()    {}
+func (*Manifest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{0}
+}
+
+func (m *Manifest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Manifest.Unmarshal(m, b)
+}
+func (m *Manifest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Manifest.Marshal(b, m, deterministic)
+}
+func (m *Manifest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Manifest.Merge(m, src)
+}
+func (m *Manifest) XXX_Size() int {
+	return xxx_messageInfo_Manifest.Size(m)
+}
+func (m *Manifest) XXX_DiscardUnknown() {
+	xxx_messageInfo_Manifest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Manifest proto.InternalMessageInfo
+
+func (m *Manifest) GetCommands() []*Command {
+	if m != nil {
+		return m.Commands
+	}
+	return nil
+}
+
+func (m *Manifest) GetOutputDepfile() string {
+	if m != nil && m.OutputDepfile != nil {
+		return *m.OutputDepfile
+	}
+	return ""
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+type Command struct {
+	// A list of copy rules to run before the sandboxed command.  The from field is relative to the
+	// $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+	CopyBefore []*Copy `protobuf:"bytes,1,rep,name=copy_before,json=copyBefore" json:"copy_before,omitempty"`
+	// If true, change the working directory to the top of the temporary sandbox directory before
+	// running the command.  If false, leave the working directory where it was when sbox was started.
+	Chdir *bool `protobuf:"varint,2,opt,name=chdir" json:"chdir,omitempty"`
+	// The command to run.
+	Command *string `protobuf:"bytes,3,req,name=command" json:"command,omitempty"`
+	// A list of copy rules to run after the sandboxed command.  The from field is relative to the
+	// top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+	CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
+	// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+	// when the lists of inputs changes, even if the inputs are not on the command line.
+	InputHash            *string  `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Command) Reset()         { *m = Command{} }
+func (m *Command) String() string { return proto.CompactTextString(m) }
+func (*Command) ProtoMessage()    {}
+func (*Command) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{1}
+}
+
+func (m *Command) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Command.Unmarshal(m, b)
+}
+func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Command.Marshal(b, m, deterministic)
+}
+func (m *Command) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Command.Merge(m, src)
+}
+func (m *Command) XXX_Size() int {
+	return xxx_messageInfo_Command.Size(m)
+}
+func (m *Command) XXX_DiscardUnknown() {
+	xxx_messageInfo_Command.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Command proto.InternalMessageInfo
+
+func (m *Command) GetCopyBefore() []*Copy {
+	if m != nil {
+		return m.CopyBefore
+	}
+	return nil
+}
+
+func (m *Command) GetChdir() bool {
+	if m != nil && m.Chdir != nil {
+		return *m.Chdir
+	}
+	return false
+}
+
+func (m *Command) GetCommand() string {
+	if m != nil && m.Command != nil {
+		return *m.Command
+	}
+	return ""
+}
+
+func (m *Command) GetCopyAfter() []*Copy {
+	if m != nil {
+		return m.CopyAfter
+	}
+	return nil
+}
+
+func (m *Command) GetInputHash() string {
+	if m != nil && m.InputHash != nil {
+		return *m.InputHash
+	}
+	return ""
+}
+
+// Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+type Copy struct {
+	From                 *string  `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+	To                   *string  `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Copy) Reset()         { *m = Copy{} }
+func (m *Copy) String() string { return proto.CompactTextString(m) }
+func (*Copy) ProtoMessage()    {}
+func (*Copy) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{2}
+}
+
+func (m *Copy) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Copy.Unmarshal(m, b)
+}
+func (m *Copy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Copy.Marshal(b, m, deterministic)
+}
+func (m *Copy) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Copy.Merge(m, src)
+}
+func (m *Copy) XXX_Size() int {
+	return xxx_messageInfo_Copy.Size(m)
+}
+func (m *Copy) XXX_DiscardUnknown() {
+	xxx_messageInfo_Copy.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Copy proto.InternalMessageInfo
+
+func (m *Copy) GetFrom() string {
+	if m != nil && m.From != nil {
+		return *m.From
+	}
+	return ""
+}
+
+func (m *Copy) GetTo() string {
+	if m != nil && m.To != nil {
+		return *m.To
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
+	proto.RegisterType((*Command)(nil), "sbox.Command")
+	proto.RegisterType((*Copy)(nil), "sbox.Copy")
+}
+
+func init() {
+	proto.RegisterFile("sbox.proto", fileDescriptor_9d0425bf0de86ed1)
+}
+
+var fileDescriptor_9d0425bf0de86ed1 = []byte{
+	// 252 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x40,
+	0x10, 0x85, 0x49, 0x9a, 0xd2, 0x66, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x08, 0x01, 0x21, 0x55,
+	0xe8, 0xc1, 0x7f, 0x60, 0xf5, 0xe0, 0xc5, 0xcb, 0x1e, 0x45, 0x08, 0xdb, 0x64, 0x97, 0x04, 0x4c,
+	0x66, 0xd9, 0xdd, 0x82, 0xfd, 0x57, 0xfe, 0x44, 0xd9, 0x49, 0xea, 0xc5, 0xdb, 0xcc, 0xfb, 0x78,
+	0xf3, 0x1e, 0x03, 0xe0, 0x4f, 0xf4, 0x7d, 0xb0, 0x8e, 0x02, 0x61, 0x16, 0xe7, 0xf2, 0x13, 0xd6,
+	0xef, 0x6a, 0xec, 0x8d, 0xf6, 0x01, 0xf7, 0xb0, 0x6e, 0x68, 0x18, 0xd4, 0xd8, 0x7a, 0x91, 0x14,
+	0x8b, 0x6a, 0xf3, 0xb4, 0x3d, 0xb0, 0xe1, 0x65, 0x52, 0xe5, 0x1f, 0xc6, 0x7b, 0xd8, 0xd1, 0x39,
+	0xd8, 0x73, 0xa8, 0x5b, 0x6d, 0x4d, 0xff, 0xa5, 0x45, 0x5a, 0x24, 0x55, 0x2e, 0xb7, 0x93, 0xfa,
+	0x3a, 0x89, 0xe5, 0x4f, 0x02, 0xab, 0xd9, 0x8c, 0x8f, 0xb0, 0x69, 0xc8, 0x5e, 0xea, 0x93, 0x36,
+	0xe4, 0xf4, 0x1c, 0x00, 0xd7, 0x00, 0x7b, 0x91, 0x10, 0xf1, 0x91, 0x29, 0xde, 0xc2, 0xb2, 0xe9,
+	0xda, 0xde, 0xf1, 0xd9, 0xb5, 0x9c, 0x16, 0x14, 0xb0, 0x9a, 0x1b, 0x88, 0x45, 0x91, 0x56, 0xb9,
+	0xbc, 0xae, 0xb8, 0x07, 0x76, 0xd7, 0xca, 0x04, 0xed, 0x44, 0xf6, 0xef, 0x76, 0x1e, 0xe9, 0x73,
+	0x84, 0x78, 0x07, 0xd0, 0x8f, 0xb1, 0x79, 0xa7, 0x7c, 0x27, 0x96, 0x5c, 0x3b, 0x67, 0xe5, 0x4d,
+	0xf9, 0xae, 0x7c, 0x80, 0x2c, 0x3a, 0x10, 0x21, 0x33, 0x8e, 0x06, 0x91, 0x70, 0x10, 0xcf, 0xb8,
+	0x83, 0x34, 0x90, 0x48, 0x59, 0x49, 0x03, 0x1d, 0x6f, 0x3e, 0xf8, 0xa1, 0x35, 0x3f, 0xf4, 0x37,
+	0x00, 0x00, 0xff, 0xff, 0x95, 0x4d, 0xee, 0x7d, 0x5d, 0x01, 0x00, 0x00,
+}
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
new file mode 100644
index 0000000..ab95545
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -0,0 +1,58 @@
+// 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.
+
+syntax = "proto2";
+
+package sbox;
+option go_package = "sbox_proto";
+
+// A set of commands to run in a sandbox.
+message Manifest {
+  // A list of commands to run in the sandbox.
+  repeated Command commands = 1;
+
+  // If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+  // merged into the given output file relative to the $PWD when sbox was started.
+  optional string output_depfile = 2;
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+message Command {
+  // A list of copy rules to run before the sandboxed command.  The from field is relative to the
+  // $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+  repeated Copy copy_before = 1;
+
+  // If true, change the working directory to the top of the temporary sandbox directory before
+  // running the command.  If false, leave the working directory where it was when sbox was started.
+  optional bool chdir = 2;
+
+  // The command to run.
+  required string command = 3;
+
+  // A list of copy rules to run after the sandboxed command.  The from field is relative to the
+  // top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+  repeated Copy copy_after = 4;
+
+  // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+  // when the lists of inputs changes, even if the inputs are not on the command line.
+  optional string input_hash = 5;
+}
+
+// Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+message Copy {
+  required string from = 1;
+  required string to = 2;
+}
\ No newline at end of file
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 680c00a..441ea0d 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -27,6 +27,7 @@
         "main.go",
         "writedocs.go",
         "queryview.go",
+        "queryview_templates.go",
     ],
     testSrcs: [
         "queryview_test.go",
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index b88803a..907bed3 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -51,10 +51,22 @@
 	return android.NewNameResolver(exportFilter)
 }
 
+// bazelConversionRequested checks that the user is intending to convert
+// Blueprint to Bazel BUILD files.
+func bazelConversionRequested(configuration android.Config) bool {
+	return configuration.IsEnvTrue("CONVERT_TO_BAZEL")
+}
+
 func newContext(srcDir string, configuration android.Config) *android.Context {
 	ctx := android.NewContext(configuration)
-	ctx.Register()
-	if !shouldPrepareBuildActions() {
+	if bazelConversionRequested(configuration) {
+		// Register an alternate set of singletons and mutators for bazel
+		// conversion for Bazel conversion.
+		ctx.RegisterForBazelConversion()
+	} else {
+		ctx.Register()
+	}
+	if !shouldPrepareBuildActions(configuration) {
 		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
 	}
 	ctx.SetNameInterface(newNameResolver(configuration))
@@ -79,7 +91,7 @@
 	srcDir := filepath.Dir(flag.Arg(0))
 	var ctx *android.Context
 	configuration := newConfig(srcDir)
-	extraNinjaDeps := []string{configuration.ConfigFileName, configuration.ProductVariablesFileName}
+	extraNinjaDeps := []string{configuration.ProductVariablesFileName}
 
 	// Read the SOONG_DELVE again through configuration so that there is a dependency on the environment variable
 	// and soong_build will rerun when it is set for the first time.
@@ -114,6 +126,8 @@
 		ctx = newContext(srcDir, configuration)
 		bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
 	}
+
+	// Convert the Soong module graph into Bazel BUILD files.
 	if bazelQueryViewDir != "" {
 		if err := createBazelQueryView(ctx, bazelQueryViewDir); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
@@ -130,7 +144,7 @@
 
 	// TODO(ccross): make this a command line argument.  Requires plumbing through blueprint
 	//  to affect the command line of the primary builder.
-	if shouldPrepareBuildActions() {
+	if shouldPrepareBuildActions(configuration) {
 		metricsFile := filepath.Join(bootstrap.BuildDir, "soong_build_metrics.pb")
 		err := android.WriteMetrics(configuration, metricsFile)
 		if err != nil {
@@ -140,8 +154,19 @@
 	}
 }
 
-func shouldPrepareBuildActions() bool {
-	// If we're writing soong_docs or queryview, don't write build.ninja or
-	// collect metrics.
-	return docFile == "" && bazelQueryViewDir == ""
+// shouldPrepareBuildActions reads configuration and flags if build actions
+// should be generated.
+func shouldPrepareBuildActions(configuration android.Config) bool {
+	// Generating Soong docs
+	if docFile != "" {
+		return false
+	}
+
+	// Generating a directory for Soong query (queryview)
+	if bazelQueryViewDir != "" {
+		return false
+	}
+
+	// Generating a directory for converted Bazel BUILD files
+	return !bazelConversionRequested(configuration)
 }
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 27856b5..f5aa685 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -28,115 +28,6 @@
 	"github.com/google/blueprint/proptools"
 )
 
-const (
-	// The default `load` preamble for every generated BUILD file.
-	soongModuleLoad = `package(default_visibility = ["//visibility:public"])
-load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
-
-`
-
-	// A macro call in the BUILD file representing a Soong module, with space
-	// for expanding more attributes.
-	soongModuleTarget = `soong_module(
-    name = "%s",
-    module_name = "%s",
-    module_type = "%s",
-    module_variant = "%s",
-    module_deps = %s,
-%s)`
-
-	// A simple provider to mark and differentiate Soong module rule shims from
-	// regular Bazel rules. Every Soong module rule shim returns a
-	// SoongModuleInfo provider, and can only depend on rules returning
-	// SoongModuleInfo in the `module_deps` attribute.
-	providersBzl = `SoongModuleInfo = provider(
-    fields = {
-        "name": "Name of module",
-        "type": "Type of module",
-        "variant": "Variant of module",
-    },
-)
-`
-
-	// The soong_module rule implementation in a .bzl file.
-	soongModuleBzl = `
-%s
-
-load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
-
-def _generic_soong_module_impl(ctx):
-    return [
-        SoongModuleInfo(
-            name = ctx.attr.module_name,
-            type = ctx.attr.module_type,
-            variant = ctx.attr.module_variant,
-        ),
-    ]
-
-generic_soong_module = rule(
-    implementation = _generic_soong_module_impl,
-    attrs = {
-        "module_name": attr.string(mandatory = True),
-        "module_type": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-    },
-)
-
-soong_module_rule_map = {
-%s}
-
-_SUPPORTED_TYPES = ["bool", "int", "string"]
-
-def _is_supported_type(value):
-    if type(value) in _SUPPORTED_TYPES:
-        return True
-    elif type(value) == "list":
-        supported = True
-        for v in value:
-            supported = supported and type(v) in _SUPPORTED_TYPES
-        return supported
-    else:
-        return False
-
-# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
-# expand to the right underlying shim.
-def soong_module(name, module_type, **kwargs):
-    soong_module_rule = soong_module_rule_map.get(module_type)
-
-    if soong_module_rule == None:
-        # This module type does not have an existing rule to map to, so use the
-        # generic_soong_module rule instead.
-        generic_soong_module(
-            name = name,
-            module_type = module_type,
-            module_name = kwargs.pop("module_name", ""),
-            module_variant = kwargs.pop("module_variant", ""),
-            module_deps = kwargs.pop("module_deps", []),
-        )
-    else:
-        supported_kwargs = dict()
-        for key, value in kwargs.items():
-            if _is_supported_type(value):
-                supported_kwargs[key] = value
-        soong_module_rule(
-            name = name,
-            **supported_kwargs,
-        )
-`
-
-	// A rule shim for representing a Soong module type and its properties.
-	moduleRuleShim = `
-def _%[1]s_impl(ctx):
-    return [SoongModuleInfo()]
-
-%[1]s = rule(
-    implementation = _%[1]s_impl,
-    attrs = %[2]s
-)
-`
-)
-
 var (
 	// An allowlist of prop types that are surfaced from module props to rule
 	// attributes. (nested) dictionaries are notably absent here, because while
diff --git a/cmd/soong_build/queryview_templates.go b/cmd/soong_build/queryview_templates.go
new file mode 100644
index 0000000..359c0d8
--- /dev/null
+++ b/cmd/soong_build/queryview_templates.go
@@ -0,0 +1,124 @@
+// 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 main
+
+const (
+	// The default `load` preamble for every generated BUILD file.
+	soongModuleLoad = `package(default_visibility = ["//visibility:public"])
+load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
+
+`
+
+	// A macro call in the BUILD file representing a Soong module, with space
+	// for expanding more attributes.
+	soongModuleTarget = `soong_module(
+    name = "%s",
+    module_name = "%s",
+    module_type = "%s",
+    module_variant = "%s",
+    module_deps = %s,
+%s)`
+
+	// A simple provider to mark and differentiate Soong module rule shims from
+	// regular Bazel rules. Every Soong module rule shim returns a
+	// SoongModuleInfo provider, and can only depend on rules returning
+	// SoongModuleInfo in the `module_deps` attribute.
+	providersBzl = `SoongModuleInfo = provider(
+    fields = {
+        "name": "Name of module",
+        "type": "Type of module",
+        "variant": "Variant of module",
+    },
+)
+`
+
+	// The soong_module rule implementation in a .bzl file.
+	soongModuleBzl = `
+%s
+
+load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
+
+def _generic_soong_module_impl(ctx):
+    return [
+        SoongModuleInfo(
+            name = ctx.attr.module_name,
+            type = ctx.attr.module_type,
+            variant = ctx.attr.module_variant,
+        ),
+    ]
+
+generic_soong_module = rule(
+    implementation = _generic_soong_module_impl,
+    attrs = {
+        "module_name": attr.string(mandatory = True),
+        "module_type": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+    },
+)
+
+soong_module_rule_map = {
+%s}
+
+_SUPPORTED_TYPES = ["bool", "int", "string"]
+
+def _is_supported_type(value):
+    if type(value) in _SUPPORTED_TYPES:
+        return True
+    elif type(value) == "list":
+        supported = True
+        for v in value:
+            supported = supported and type(v) in _SUPPORTED_TYPES
+        return supported
+    else:
+        return False
+
+# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
+# expand to the right underlying shim.
+def soong_module(name, module_type, **kwargs):
+    soong_module_rule = soong_module_rule_map.get(module_type)
+
+    if soong_module_rule == None:
+        # This module type does not have an existing rule to map to, so use the
+        # generic_soong_module rule instead.
+        generic_soong_module(
+            name = name,
+            module_type = module_type,
+            module_name = kwargs.pop("module_name", ""),
+            module_variant = kwargs.pop("module_variant", ""),
+            module_deps = kwargs.pop("module_deps", []),
+        )
+    else:
+        supported_kwargs = dict()
+        for key, value in kwargs.items():
+            if _is_supported_type(value):
+                supported_kwargs[key] = value
+        soong_module_rule(
+            name = name,
+            **supported_kwargs,
+        )
+`
+
+	// A rule shim for representing a Soong module type and its properties.
+	moduleRuleShim = `
+def _%[1]s_impl(ctx):
+    return [SoongModuleInfo()]
+
+%[1]s = rule(
+    implementation = _%[1]s_impl,
+    attrs = %[2]s
+)
+`
+)
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
index 525802a..9471a91 100644
--- a/cmd/soong_build/queryview_test.go
+++ b/cmd/soong_build/queryview_test.go
@@ -53,6 +53,12 @@
 	android.ModuleBase
 }
 
+// OutputFiles is needed because some instances of this module use dist with a
+// tag property which requires the module implements OutputFileProducer.
+func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
+	return android.PathsForTesting("path" + tag), nil
+}
+
 func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// nothing for now.
 }
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index 5fb6e6b..253979e 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -44,9 +44,10 @@
 	"name":             0,
 	"src":              1,
 	"srcs":             2,
-	"defaults":         3,
-	"host_supported":   4,
-	"device_supported": 5,
+	"exclude_srcs":     3,
+	"defaults":         4,
+	"host_supported":   5,
+	"device_supported": 6,
 }
 
 // For each module type, extract its documentation and convert it to the template data.
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 aee8e5a..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)
 }
@@ -493,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/cmd/zipsync/zipsync.go b/cmd/zipsync/zipsync.go
index 294e5ef..aecdc3d 100644
--- a/cmd/zipsync/zipsync.go
+++ b/cmd/zipsync/zipsync.go
@@ -53,6 +53,16 @@
 	return out.Close()
 }
 
+func writeSymlink(filename string, in io.Reader) error {
+	b, err := ioutil.ReadAll(in)
+	if err != nil {
+		return err
+	}
+	dest := string(b)
+	err = os.Symlink(dest, filename)
+	return err
+}
+
 func main() {
 	flag.Usage = func() {
 		fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...")
@@ -122,7 +132,11 @@
 				if err != nil {
 					log.Fatal(err)
 				}
-				must(writeFile(filename, in, f.FileInfo().Mode()))
+				if f.FileInfo().Mode()&os.ModeSymlink != 0 {
+					must(writeSymlink(filename, in))
+				} else {
+					must(writeFile(filename, in, f.FileInfo().Mode()))
+				}
 				in.Close()
 				files = append(files, filename)
 			}
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 77d6ee9..deaf77f 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -22,17 +22,214 @@
 	"android/soong/android"
 )
 
-// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
-// app manifest is less than the specified version. This is needed because these libraries haven't
-// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
-// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
-// so that if this library is missing this in not a build or run-time error.
+// This comment describes the following:
+//   1. the concept of class loader context (CLC) and its relation to classpath
+//   2. how PackageManager constructs CLC from shared libraries and their dependencies
+//   3. build-time vs. run-time CLC and why this matters for dexpreopt
+//   4. manifest fixer: a tool that adds missing <uses-library> tags to the manifests
+//   5. build system support for CLC
+//
+// 1. Class loader context
+// -----------------------
+//
+// Java libraries and apps that have run-time dependency on other libraries should list the used
+// libraries in their manifest (AndroidManifest.xml file). Each used library should be specified in
+// a <uses-library> tag that has the library name and an optional attribute specifying if the
+// library is optional or required. Required libraries are necessary for the library/app to run (it
+// will fail at runtime if the library cannot be loaded), and optional libraries are used only if
+// they are present (if not, the library/app can run without them).
+//
+// The libraries listed in <uses-library> tags are in the classpath of a library/app.
+//
+// Besides libraries, an app may also use another APK (for example in the case of split APKs), or
+// anything that gets added by the app dynamically. In general, it is impossible to know at build
+// time what the app may use at runtime. In the build system we focus on the known part: libraries.
+//
+// Class loader context (CLC) is a tree-like structure that describes class loader hierarchy. The
+// build system uses CLC in a more narrow sense: it is a tree of libraries that represents
+// transitive closure of all <uses-library> dependencies of a library/app. The top-level elements of
+// a CLC are the direct <uses-library> dependencies specified in the manifest (aka. classpath). Each
+// node of a CLC tree is a <uses-library> which may have its own <uses-library> sub-nodes.
+//
+// Because <uses-library> dependencies are, in general, a graph and not necessarily a tree, CLC may
+// contain subtrees for the same library multiple times. In other words, CLC is the dependency graph
+// "unfolded" to a tree. The duplication is only on a logical level, and the actual underlying class
+// loaders are not duplicated (at runtime there is a single class loader instance for each library).
+//
+// Example: A has <uses-library> tags B, C and D; C has <uses-library tags> B and D;
+//          D has <uses-library> E; B and E have no <uses-library> dependencies. The CLC is:
+//    A
+//    ├── B
+//    ├── C
+//    │   ├── B
+//    │   └── D
+//    │       └── E
+//    └── D
+//        └── E
+//
+// CLC defines the lookup order of libraries when resolving Java classes used by the library/app.
+// The lookup order is important because libraries may contain duplicate classes, and the class is
+// resolved to the first match.
+//
+// 2. PackageManager and "shared" libraries
+// ----------------------------------------
+//
+// In order to load an APK at runtime, PackageManager (in frameworks/base) creates a CLC. It adds
+// the libraries listed in the <uses-library> tags in the app's manifest as top-level CLC elements.
+// For each of the used libraries PackageManager gets all its <uses-library> dependencies (specified
+// as tags in the manifest of that library) and adds a nested CLC for each dependency. This process
+// continues recursively until all leaf nodes of the constructed CLC tree are libraries that have no
+// <uses-library> dependencies.
+//
+// PackageManager is aware only of "shared" libraries. The definition of "shared" here differs from
+// its usual meaning (as in shared vs. static). In Android, Java "shared" libraries are those listed
+// in /system/etc/permissions/platform.xml file. This file is installed on device. Each entry in it
+// contains the name of a "shared" library, a path to its DEX jar file and a list of dependencies
+// (other "shared" libraries that this one uses at runtime and specifies them in <uses-library> tags
+// in its manifest).
+//
+// In other words, there are two sources of information that allow PackageManager to construct CLC
+// at runtime: <uses-library> tags in the manifests and "shared" library dependencies in
+// /system/etc/permissions/platform.xml.
+//
+// 3. Build-time and run-time CLC and dexpreopt
+// --------------------------------------------
+//
+// CLC is needed not only when loading a library/app, but also when compiling it. Compilation may
+// happen either on device (known as "dexopt") or during the build (known as "dexpreopt"). Since
+// dexopt takes place on device, it has the same information as PackageManager (manifests and
+// shared library dependencies). Dexpreopt, on the other hand, takes place on host and in a totally
+// different environment, and it has to get the same information from the build system (see the
+// section about build system support below).
+//
+// Thus, the build-time CLC used by dexpreopt and the run-time CLC used by PackageManager are
+// the same thing, but computed in two different ways.
+//
+// It is important that build-time and run-time CLCs coincide, otherwise the AOT-compiled code
+// created by dexpreopt will be rejected. In order to check the equality of build-time and
+// run-time CLCs, the dex2oat compiler records build-time CLC in the *.odex files (in the
+// "classpath" field of the OAT file header). To find the stored CLC, use the following command:
+// `oatdump --oat-file=<FILE> | grep '^classpath = '`.
+//
+// Mismatch between build-time and run-time CLC is reported in logcat during boot (search with
+// `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'`. Mismatch is bad for performance, as it
+// forces the library/app to either be dexopted, or to run without any optimizations (e.g. the app's
+// code may need to be extracted in memory from the APK, a very expensive operation).
+//
+// A <uses-library> can be either optional or required. From dexpreopt standpoint, required library
+// must be present at build time (its absence is a build error). An optional library may be either
+// present or absent at build time: if present, it will be added to the CLC, passed to dex2oat and
+// recorded in the *.odex file; otherwise, if the library is absent, it will be skipped and not
+// added to CLC. If there is a mismatch between built-time and run-time status (optional library is
+// present in one case, but not the other), then the build-time and run-time CLCs won't match and
+// the compiled code will be rejected. It is unknown at build time if the library will be present at
+// runtime, therefore either including or excluding it may cause CLC mismatch.
+//
+// 4. Manifest fixer
+// -----------------
+//
+// Sometimes <uses-library> tags are missing from the source manifest of a library/app. This may
+// happen for example if one of the transitive dependencies of the library/app starts using another
+// <uses-library>, and the library/app's manifest isn't updated to include it.
+//
+// Soong can compute some of the missing <uses-library> tags for a given library/app automatically
+// as SDK libraries in the transitive dependency closure of the library/app. The closure is needed
+// because a library/app may depend on a static library that may in turn depend on an SDK library,
+// (possibly transitively via another library).
+//
+// Not all <uses-library> tags can be computed in this way, because some of the <uses-library>
+// dependencies are not SDK libraries, or they are not reachable via transitive dependency closure.
+// But when possible, allowing Soong to calculate the manifest entries is less prone to errors and
+// simplifies maintenance. For example, consider a situation when many apps use some static library
+// that adds a new <uses-library> dependency -- all the apps will have to be updated. That is
+// difficult to maintain.
+//
+// Soong computes the libraries that need to be in the manifest as the top-level libraries in CLC.
+// These libraries are passed to the manifest_fixer.
+//
+// All libraries added to the manifest should be "shared" libraries, so that PackageManager can look
+// up their dependencies and reconstruct the nested subcontexts at runtime. There is no build check
+// to ensure this, it is an assumption.
+//
+// 5. Build system support
+// -----------------------
+//
+// In order to construct CLC for dexpreopt and manifest_fixer, the build system needs to know all
+// <uses-library> dependencies of the dexpreopted library/app (including transitive dependencies).
+// For each <uses-librarry> dependency it needs to know the following information:
+//
+//   - the real name of the <uses-library> (it may be different from the module name)
+//   - build-time (on host) and run-time (on device) paths to the DEX jar file of the library
+//   - whether this library is optional or required
+//   - all <uses-library> dependencies
+//
+// Since the build system doesn't have access to the manifest contents (it cannot read manifests at
+// the time of build rule generation), it is necessary to copy this information to the Android.bp
+// and Android.mk files. For blueprints, the relevant properties are `uses_libs` and
+// `optional_uses_libs`. For makefiles, relevant variables are `LOCAL_USES_LIBRARIES` and
+// `LOCAL_OPTIONAL_USES_LIBRARIES`. It is preferable to avoid specifying these properties explicilty
+// when they can be computed automatically by Soong (as the transitive closure of SDK library
+// dependencies).
+//
+// Some of the Java libraries that are used as <uses-library> are not SDK libraries (they are
+// defined as `java_library` rather than `java_sdk_library` in the Android.bp files). In order for
+// the build system to handle them automatically like SDK libraries, it is possible to set a
+// property `provides_uses_lib` or variable `LOCAL_PROVIDES_USES_LIBRARY` on the blueprint/makefile
+// module of such library. This property can also be used to specify real library name in cases
+// when it differs from the module name.
+//
+// Because the information from the manifests has to be duplicated in the Android.bp/Android.mk
+// files, there is a danger that it may get out of sync. To guard against that, the build system
+// generates a rule that checks the metadata in the build files against the contents of a manifest
+// (verify_uses_libraries). The manifest can be available as a source file, or as part of a prebuilt
+// APK. Note that reading the manifests at the Ninja stage of the build is fine, unlike the build
+// rule generation phase.
+//
+// ClassLoaderContext is a structure that represents CLC.
+//
+type ClassLoaderContext struct {
+	// The name of the library.
+	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 sub-CLC for dependencies.
+	Subcontexts []*ClassLoaderContext
+}
+
+// ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
+// AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
+//
+// Conditional CLC is for compatibility libraries which didn't exist prior to a certain SDK version
+// (say, N), but classes in them were in the bootclasspath jars, etc., and in version N they have
+// been separated into a standalone <uses-library>. Compatibility libraries should only be in the
+// CLC if the library/app that uses them has `targetSdkVersion` less than N in the manifest.
+//
+// Currently only apps (but not libraries) use conditional CLC.
+//
+// Target SDK version information is unavailable to the build system at rule generation time, so
+// the build system doesn't know whether conditional CLC is needed for a given app or not. So it
+// generates a build rule that includes conditional CLC for all versions, extracts the target SDK
+// version from the manifest, and filters the CLCs based on that version. Exact final CLC that is
+// passed to dex2oat is unknown to the build system, and gets known only at Ninja stage.
+//
+type ClassLoaderContextMap map[int][]*ClassLoaderContext
+
+// Compatibility libraries. Some are optional, and some are required: this is the default that
+// affects how they are handled by the Soong logic that automatically adds implicit SDK libraries
+// to the manifest_fixer, but an explicit `uses_libs`/`optional_uses_libs` can override this.
 var OrgApacheHttpLegacy = "org.apache.http.legacy"
 var AndroidTestBase = "android.test.base"
 var AndroidTestMock = "android.test.mock"
 var AndroidHidlBase = "android.hidl.base-V1.0-java"
 var AndroidHidlManager = "android.hidl.manager-V1.0-java"
 
+// Compatibility libraries grouped by version/optionality (for convenience, to avoid repeating the
+// same lists in multiple places).
 var OptionalCompatUsesLibs28 = []string{
 	OrgApacheHttpLegacy,
 }
@@ -55,30 +252,6 @@
 // last). We use the converntional "current" SDK level (10000), but any big number would do as well.
 const AnySdkVersion int = android.FutureApiLevelInt
 
-// 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
-}
-
-// 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
-
 // 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,
 	hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
@@ -143,7 +316,6 @@
 	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
 	if err != nil {
 		ctx.ModuleErrorf(err.Error())
-		android.ReportPathErrorf(ctx, err.Error())
 	}
 }
 
@@ -204,20 +376,16 @@
 	}
 }
 
-// List of libraries in the unconditional class loader context, excluding dependencies of shared libraries.
+// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
+// included). This is the list of libraries that should be in the <uses-library> tags in the
+// manifest. Some of them may be present in the source manifest, others are added by 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 usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
-	for _, clc := range clcs {
-		ulibs = append(ulibs, clc.Name)
-		ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
+		clcs := clcMap[AnySdkVersion]
+		ulibs = make([]string, 0, len(clcs))
+		for _, clc := range clcs {
+			ulibs = append(ulibs, clc.Name)
+		}
 	}
 	return ulibs
 }
@@ -262,13 +430,18 @@
 	return true, nil
 }
 
+// Helper function for validateClassLoaderContext() that handles recursion.
 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)
+				if clc.Host == nil {
+					return false, fmt.Errorf("invalid build path for <uses-library> \"%s\"", clc.Name)
+				} else {
+					return false, fmt.Errorf("invalid install 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.
@@ -301,6 +474,7 @@
 	return clcStr, android.FirstUniquePaths(paths)
 }
 
+// Helper function for ComputeClassLoaderContext() that handles recursion.
 func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
 	var paths android.Paths
 	var clcsHost, clcsTarget []string
@@ -325,7 +499,7 @@
 	return clcHost, clcTarget, paths
 }
 
-// Paths to a <uses-library> on host and on device.
+// JSON representation of <uses-library> paths on host and on device.
 type jsonLibraryPath struct {
 	Host   string
 	Device string
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index e0a75bf..be7d4c6 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -153,7 +153,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", "f", "a3", "b3"}
 		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
 			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
 		}
@@ -195,7 +195,7 @@
 	// 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\"")
+		checkError(t, err, "invalid build path for <uses-library> \"a\"")
 	})
 }
 
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index cfa4c55..f52ecb4 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -486,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 65380fe..b0a684e 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -51,7 +51,7 @@
 
 // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
 // ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
-func GenerateDexpreoptRule(ctx android.PathContext, globalSoong *GlobalSoongConfig,
+func GenerateDexpreoptRule(ctx android.BuilderContext, globalSoong *GlobalSoongConfig,
 	global *GlobalConfig, module *ModuleConfig) (rule *android.RuleBuilder, err error) {
 
 	defer func() {
@@ -67,7 +67,7 @@
 		}
 	}()
 
-	rule = android.NewRuleBuilder()
+	rule = android.NewRuleBuilder(pctx, ctx)
 
 	generateProfile := module.ProfileClassListing.Valid() && !global.DisableGenerateProfile
 	generateBootProfile := module.ProfileBootListing.Valid() && !global.DisableGenerateProfile
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index e89f045..32c4f84 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -27,6 +27,7 @@
 	"android/soong/android"
 	"android/soong/dexpreopt"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/pathtools"
 )
 
@@ -38,12 +39,16 @@
 	outDir                = flag.String("out_dir", "", "path to output directory")
 )
 
-type pathContext struct {
+type builderContext struct {
 	config android.Config
 }
 
-func (x *pathContext) Config() android.Config     { return x.config }
-func (x *pathContext) AddNinjaFileDeps(...string) {}
+func (x *builderContext) Config() android.Config                            { return x.config }
+func (x *builderContext) AddNinjaFileDeps(...string)                        {}
+func (x *builderContext) Build(android.PackageContext, android.BuildParams) {}
+func (x *builderContext) Rule(android.PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule {
+	return nil
+}
 
 func main() {
 	flag.Parse()
@@ -76,7 +81,7 @@
 		usage("--module configuration file is required")
 	}
 
-	ctx := &pathContext{android.NullConfig(*outDir)}
+	ctx := &builderContext{android.NullConfig(*outDir)}
 
 	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
 	if err != nil {
@@ -133,7 +138,7 @@
 	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
 }
 
-func writeScripts(ctx android.PathContext, globalSoong *dexpreopt.GlobalSoongConfig,
+func writeScripts(ctx android.BuilderContext, globalSoong *dexpreopt.GlobalSoongConfig,
 	global *dexpreopt.GlobalConfig, module *dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
 	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
 	if err != nil {
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index feabd70..59278fd 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -60,7 +60,7 @@
 
 func TestDexPreopt(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
+	ctx := android.BuilderContextForTesting(config)
 	globalSoong := GlobalSoongConfigForTests(config)
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
@@ -82,7 +82,7 @@
 
 func TestDexPreoptSystemOther(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
+	ctx := android.BuilderContextForTesting(config)
 	globalSoong := GlobalSoongConfigForTests(config)
 	global := GlobalConfigForTests(ctx)
 	systemModule := testSystemModuleConfig(ctx, "Stest")
@@ -142,7 +142,7 @@
 
 func TestDexPreoptProfile(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
+	ctx := android.BuilderContextForTesting(config)
 	globalSoong := GlobalSoongConfigForTests(config)
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
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.go b/etc/prebuilt_etc.go
index 44b8149..850c8f9 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -14,9 +14,20 @@
 
 package etc
 
-import (
-	"strconv"
+// This file implements module types that install prebuilt artifacts.
+//
+// There exist two classes of prebuilt modules in the Android tree. The first class are the ones
+// based on `android.Prebuilt`, such as `cc_prebuilt_library` and `java_import`. This kind of
+// modules may exist both as prebuilts and source at the same time, though only one would be
+// installed and the other would be marked disabled. The `prebuilt_postdeps` mutator would select
+// the actual modules to be installed. More details in android/prebuilt.go.
+//
+// The second class is described in this file. Unlike `android.Prebuilt` based module types,
+// `prebuilt_etc` exist only as prebuilts and cannot have a same-named source module counterpart.
+// This makes the logic of `prebuilt_etc` to be much simpler as they don't need to go through the
+// various `prebuilt_*` mutators.
 
+import (
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -42,19 +53,22 @@
 }
 
 type prebuiltEtcProperties struct {
-	// Source file of this prebuilt.
+	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
 	Src *string `android:"path,arch_variant"`
 
-	// optional subdirectory under which this file is installed into, cannot be specified with relative_install_path, prefer relative_install_path
+	// Optional subdirectory under which this file is installed into, cannot be specified with
+	// relative_install_path, prefer relative_install_path.
 	Sub_dir *string `android:"arch_variant"`
 
-	// optional subdirectory under which this file is installed into, cannot be specified with sub_dir
+	// Optional subdirectory under which this file is installed into, cannot be specified with
+	// sub_dir.
 	Relative_install_path *string `android:"arch_variant"`
 
-	// optional name for the installed file. If unspecified, name of the module is used as the file name
+	// Optional name for the installed file. If unspecified, name of the module is used as the file
+	// name.
 	Filename *string `android:"arch_variant"`
 
-	// when set to true, and filename property is not set, the name for the installed file
+	// When set to true, and filename property is not set, the name for the installed file
 	// is the same as the file name of the source file.
 	Filename_from_src *bool `android:"arch_variant"`
 
@@ -84,8 +98,15 @@
 
 type PrebuiltEtcModule interface {
 	android.Module
+
+	// Returns the base install directory, such as "etc", "usr/share".
 	BaseDir() string
+
+	// Returns the sub install directory relative to BaseDir().
 	SubDir() string
+
+	// Returns an android.OutputPath to the intermeidate file, which is the renamed prebuilt source
+	// file.
 	OutputFile() android.OutputPath
 }
 
@@ -98,7 +119,8 @@
 	outputFilePath android.OutputPath
 	// The base install location, e.g. "etc" for prebuilt_etc, "usr/share" for prebuilt_usr_share.
 	installDirBase string
-	// The base install location when soc_specific property is set to true, e.g. "firmware" for prebuilt_firmware.
+	// The base install location when soc_specific property is set to true, e.g. "firmware" for
+	// prebuilt_firmware.
 	socInstallDirBase      string
 	installDirPath         android.InstallPath
 	additionalDependencies *android.Paths
@@ -168,14 +190,8 @@
 func (p *PrebuiltEtc) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
 }
 
-func (p *PrebuiltEtc) DepsMutator(ctx android.BottomUpMutatorContext) {
-	if p.properties.Src == nil {
-		ctx.PropertyErrorf("src", "missing prebuilt source file")
-	}
-}
-
 func (p *PrebuiltEtc) SourceFilePath(ctx android.ModuleContext) android.Path {
-	return android.PathForModuleSrc(ctx, android.String(p.properties.Src))
+	return android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
 }
 
 func (p *PrebuiltEtc) InstallDirPath() android.InstallPath {
@@ -204,31 +220,41 @@
 }
 
 func (p *PrebuiltEtc) Installable() bool {
-	return p.properties.Installable == nil || android.Bool(p.properties.Installable)
+	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
 }
 
 func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.sourceFilePath = android.PathForModuleSrc(ctx, android.String(p.properties.Src))
-	filename := android.String(p.properties.Filename)
-	filename_from_src := android.Bool(p.properties.Filename_from_src)
-	if filename == "" {
-		if filename_from_src {
-			filename = p.sourceFilePath.Base()
-		} else {
-			filename = ctx.ModuleName()
-		}
-	} else if filename_from_src {
-		ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
+	if p.properties.Src == nil {
+		ctx.PropertyErrorf("src", "missing prebuilt source file")
 		return
 	}
+	p.sourceFilePath = android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
+
+	// Determine the output file basename.
+	// If Filename is set, use the name specified by the property.
+	// If Filename_from_src is set, use the source file name.
+	// Otherwise use the module name.
+	filename := proptools.String(p.properties.Filename)
+	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
+	if filename != "" {
+		if filenameFromSrc {
+			ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
+			return
+		}
+	} else if filenameFromSrc {
+		filename = p.sourceFilePath.Base()
+	} else {
+		filename = ctx.ModuleName()
+	}
 	p.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
 
+	// Check that `sub_dir` and `relative_install_path` are not set at the same time.
 	if p.properties.Sub_dir != nil && p.properties.Relative_install_path != nil {
 		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
 	}
 
-	// If soc install dir was specified and SOC specific is set, set the installDirPath to the specified
-	// socInstallDirBase.
+	// If soc install dir was specified and SOC specific is set, set the installDirPath to the
+	// specified socInstallDirBase.
 	installBaseDir := p.installDirBase
 	if p.SocSpecific() && p.socInstallDirBase != "" {
 		installBaseDir = p.socInstallDirBase
@@ -274,11 +300,9 @@
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
 				}
-				entries.SetString("LOCAL_UNINSTALLABLE_MODULE", strconv.FormatBool(!p.Installable()))
+				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.Installable())
 				if p.additionalDependencies != nil {
-					for _, path := range *p.additionalDependencies {
-						entries.AddStrings("LOCAL_ADDITIONAL_DEPENDENCIES", path.String())
-					}
+					entries.AddStrings("LOCAL_ADDITIONAL_DEPENDENCIES", p.additionalDependencies.Strings()...)
 				}
 			},
 		},
@@ -339,9 +363,10 @@
 	return module
 }
 
-// prebuilt_firmware installs a firmware file to <partition>/etc/firmware directory for system image.
-// If soc_specific property is set to true, the firmware file is installed to the vendor <partition>/firmware
-// directory for vendor image.
+// prebuilt_firmware installs a firmware file to <partition>/etc/firmware directory for system
+// image.
+// If soc_specific property is set to true, the firmware file is installed to the
+// vendor <partition>/firmware directory for vendor image.
 func PrebuiltFirmwareFactory() android.Module {
 	module := &PrebuiltEtc{}
 	module.socInstallDirBase = "firmware"
@@ -352,8 +377,8 @@
 }
 
 // prebuilt_dsp installs a DSP related file to <partition>/etc/dsp directory for system image.
-// If soc_specific property is set to true, the DSP related file is installed to the vendor <partition>/dsp
-// directory for vendor image.
+// If soc_specific property is set to true, the DSP related file is installed to the
+// vendor <partition>/dsp directory for vendor image.
 func PrebuiltDSPFactory() android.Module {
 	module := &PrebuiltEtc{}
 	module.socInstallDirBase = "dsp"
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
new file mode 100644
index 0000000..926df6e
--- /dev/null
+++ b/filesystem/Android.bp
@@ -0,0 +1,15 @@
+bootstrap_go_package {
+    name: "soong-filesystem",
+    pkgPath: "android/soong/filesystem",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+    ],
+    srcs: [
+        "filesystem.go",
+    ],
+    testSrcs: [
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
new file mode 100644
index 0000000..c6181bc
--- /dev/null
+++ b/filesystem/filesystem.go
@@ -0,0 +1,111 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// 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 filesystem
+
+import (
+	"fmt"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+func init() {
+	android.RegisterModuleType("android_filesystem", filesystemFactory)
+}
+
+type filesystem struct {
+	android.ModuleBase
+	android.PackagingBase
+
+	output     android.OutputPath
+	installDir android.InstallPath
+}
+
+// android_filesystem packages a set of modules and their transitive dependencies into a filesystem
+// image. The filesystem images are expected to be mounted in the target device, which means the
+// modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
+// The modules are placed in the filesystem image just like they are installed to the ordinary
+// partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
+func filesystemFactory() android.Module {
+	module := &filesystem{}
+	android.InitPackageModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+var dependencyTag = struct{ blueprint.BaseDependencyTag }{}
+
+func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
+	f.AddDeps(ctx, dependencyTag)
+}
+
+func (f *filesystem) installFileName() string {
+	return f.BaseModuleName() + ".img"
+}
+
+var pctx = android.NewPackageContext("android/soong/filesystem")
+
+func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
+	f.CopyDepsToZip(ctx, zipFile)
+
+	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		BuiltTool("zipsync").
+		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
+		Input(zipFile)
+
+	mkuserimg := ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs")
+	propFile := android.PathForModuleOut(ctx, "prop").OutputPath
+	// TODO(jiyong): support more filesystem types other than ext4
+	propsText := fmt.Sprintf(`mount_point=system\n`+
+		`fs_type=ext4\n`+
+		`use_dynamic_partition_size=true\n`+
+		`ext_mkuserimg=%s\n`, mkuserimg.String())
+	builder.Command().Text("echo").Flag("-e").Flag(`"` + propsText + `"`).
+		Text(">").Output(propFile).
+		Implicit(mkuserimg)
+
+	f.output = android.PathForModuleOut(ctx, "filesystem.img").OutputPath
+	builder.Command().BuiltTool("build_image").
+		Text(rootDir.String()). // input directory
+		Input(propFile).
+		Output(f.output).
+		Text(rootDir.String()) // directory where to find fs_config_files|dirs
+
+	// rootDir is not deleted. Might be useful for quick inspection.
+	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
+
+	f.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
+}
+
+var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(f.output),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+			},
+		},
+	}}
+}
diff --git a/genrule/Android.bp b/genrule/Android.bp
index ff543a6..0e27d4e 100644
--- a/genrule/Android.bp
+++ b/genrule/Android.bp
@@ -4,8 +4,10 @@
     deps: [
         "blueprint",
         "blueprint-pathtools",
+        "sbox_proto",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-shared",
     ],
     srcs: [
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 53b9dbe..93938c9 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -12,11 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// A genrule module takes a list of source files ("srcs" property), an optional
+// list of tools ("tools" property), and a command line ("cmd" property), to
+// generate output files ("out" property).
+
 package genrule
 
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
@@ -25,16 +30,14 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/shared"
-	"crypto/sha256"
-	"path/filepath"
+	"android/soong/bazel"
 )
 
 func init() {
-	registerGenruleBuildComponents(android.InitRegistrationContext)
+	RegisterGenruleBuildComponents(android.InitRegistrationContext)
 }
 
-func registerGenruleBuildComponents(ctx android.RegistrationContext) {
+func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
 
 	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
@@ -48,6 +51,8 @@
 var (
 	pctx = android.NewPackageContext("android/soong/genrule")
 
+	// Used by gensrcs when there is more than 1 shard to merge the outputs
+	// of each shard into a zip file.
 	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
 		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
 		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
@@ -80,13 +85,6 @@
 	blueprint.BaseDependencyTag
 	label string
 }
-
-// TODO(cparsons): Move to a common location when there is more than just
-// genrule with a bazel_module property.
-type bazelModuleProperties struct {
-	Label string
-}
-
 type generatorProperties struct {
 	// The command to run on one or more input files. Cmd supports substitution of a few variables
 	//
@@ -120,9 +118,10 @@
 	// input files to exclude
 	Exclude_srcs []string `android:"path,arch_variant"`
 
-	// in bazel-enabled mode, the bazel label to evaluate instead of this module
-	Bazel_module bazelModuleProperties
+	// Properties for Bazel migration purposes.
+	bazel.Properties
 }
+
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -135,6 +134,9 @@
 
 	properties generatorProperties
 
+	// For the different tasks that genrule and gensrc generate. genrule will
+	// generate 1 task, and gensrc will generate 1 or more tasks based on the
+	// number of shards the input files are sharded into.
 	taskGenerator taskFunc
 
 	deps        android.Paths
@@ -156,14 +158,17 @@
 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 // For gensrcs to set on gensrcsMerge rule.
+	genDir     android.WritablePath
+	extraTools android.Paths // dependencies on tools used by the generator
+
+	cmd string
+	// For gensrsc sharding.
+	shard  int
+	shards int
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -208,6 +213,7 @@
 	}
 	return ok
 }
+
 func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	g.subName = ctx.ModuleSubDir()
 
@@ -329,20 +335,44 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	// Generate tasks, either from genrule or gensrcs.
 	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
+		// Pick a unique path outside the task.genDir for the sbox manifest textproto,
+		// a unique rule name, and the user-visible description.
+		manifestName := "genrule.sbox.textproto"
+		desc := "generate"
+		name := "generator"
+		if task.shards > 0 {
+			manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto"
+			desc += " " + strconv.Itoa(task.shard)
+			name += strconv.Itoa(task.shard)
+		} else if len(task.out) == 1 {
+			desc += " " + task.out[0].Base()
+		}
+
+		manifestPath := android.PathForModuleOut(ctx, manifestName)
+
+		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
+		rule := android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath)
+		cmd := rule.Command()
+
+		for _, out := range task.out {
+			addLocationLabel(out.Rel(), []string{cmd.PathForOutput(out)})
+		}
+
 		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 +387,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, cmd.PathForOutput(out))
+				}
+				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 cmd.PathForOutput(task.genDir), nil
 			default:
 				if strings.HasPrefix(name, "location ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -381,7 +414,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 +424,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 +443,27 @@
 			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"}
+		cmd.Text(rawCommand)
+		cmd.ImplicitOutputs(task.out)
+		cmd.Implicits(task.in)
+		cmd.Implicits(g.deps)
+		cmd.Implicits(task.extraTools)
 		if Bool(g.properties.Depfile) {
-			ruleParams.Deps = blueprint.DepsGCC
-			args = append(args, "depfileArgs")
+			cmd.ImplicitDepFile(task.depFile)
 		}
-		name := "generator"
-		if task.shards > 1 {
-			name += strconv.Itoa(task.shard)
-		}
-		rule := ctx.Rule(pctx, name, ruleParams, args...)
 
-		g.generateSourceFile(ctx, task, rule)
+		// Create the rule to run the genrule command inside sbox.
+		rule.Build(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,10 +474,13 @@
 	}
 
 	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,
-			Outputs:   outputFiles,
+			Rule:        gensrcsMerge,
+			Implicits:   copyFrom,
+			Outputs:     outputFiles,
+			Description: "merge shards",
 			Args: map[string]string{
 				"zipArgs": zipArgs.String(),
 				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
@@ -501,51 +514,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,61 +578,74 @@
 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{}
 
+	// finalSubDir is the name of the subdirectory that output files will be generated into.
+	// It is used so that per-shard directories can be placed alongside it an then finally
+	// merged into it.
+	const finalSubDir = "gensrcs"
+
 	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
-		genDir := android.PathForModuleGen(ctx, "gensrcs")
 		shardSize := defaultShardSize
 		if s := properties.Shard_size; s != nil {
 			shardSize = int(*s)
 		}
 
+		// gensrcs rules can easily hit command line limits by repeating the command for
+		// every input file.  Shard the input files into groups.
 		shards := android.ShardPaths(srcFiles, shardSize)
 		var generateTasks []generateTask
 
 		for i, shard := range shards {
 			var commands []string
 			var outFiles android.WritablePaths
+			var commandDepFiles []string
 			var copyTo android.WritablePaths
-			var shardDir android.WritablePath
-			var sandboxOuts []string
 
+			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
+			// shard will be write to their own directories and then be merged together
+			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
+			// the sbox rule will write directly to finalSubDir.
+			genSubDir := finalSubDir
 			if len(shards) > 1 {
-				shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
-			} else {
-				shardDir = genDir
+				genSubDir = strconv.Itoa(i)
 			}
 
-			for _, in := range shard {
-				outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
-				sandboxOutfile := pathToSandboxOut(outFile, genDir)
+			genDir := android.PathForModuleGen(ctx, genSubDir)
+			// TODO(ccross): this RuleBuilder is a hack to be able to call
+			// rule.Command().PathForOutput.  Replace this with passing the rule into the
+			// generator.
+			rule := android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil)
 
+			for _, in := range shard {
+				outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
+
+				// If sharding is enabled, then outFile is the path to the output file in
+				// the shard directory, and copyTo is the path to the output file in the
+				// final directory.
 				if len(shards) > 1 {
-					shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
+					shardFile := android.GenPathWithExt(ctx, genSubDir, in, String(properties.Output_extension))
 					copyTo = append(copyTo, outFile)
 					outFile = shardFile
 				}
 
 				outFiles = append(outFiles, outFile)
-				sandboxOuts = append(sandboxOuts, sandboxOutfile)
 
+				// pre-expand the command line to replace $in and $out with references to
+				// a single input and output file.
 				command, err := android.Expand(rawCommand, func(name string) (string, error) {
 					switch name {
 					case "in":
 						return in.String(), nil
 					case "out":
-						return sandboxOutfile, nil
+						return rule.Command().PathForOutput(outFile), nil
+					case "depfile":
+						// Generate a depfile for each output file.  Store the list for
+						// later in order to combine them all into a single depfile.
+						depFile := rule.Command().PathForOutput(outFile.ReplaceExtension(ctx, "d"))
+						commandDepFiles = append(commandDepFiles, depFile)
+						return depFile, nil
 					default:
 						return "$(" + name + ")", nil
 					}
@@ -679,15 +660,29 @@
 			}
 			fullCommand := strings.Join(commands, " && ")
 
+			var outputDepfile android.WritablePath
+			var extraTools android.Paths
+			if len(commandDepFiles) > 0 {
+				// Each command wrote to a depfile, but ninja can only handle one
+				// depfile per rule.  Use the dep_fixer tool at the end of the
+				// command to combine all the depfiles into a single output depfile.
+				outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
+				depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
+				fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
+					depFixerTool.String(), strings.Join(commandDepFiles, " "))
+				extraTools = append(extraTools, depFixerTool)
+			}
+
 			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:    outputDepfile,
+				copyTo:     copyTo,
+				genDir:     genDir,
+				cmd:        fullCommand,
+				shard:      i,
+				shards:     len(shards),
+				extraTools: extraTools,
 			})
 		}
 
@@ -695,7 +690,7 @@
 	}
 
 	g := generatorFactory(taskGenerator, properties)
-	g.subDir = "gensrcs"
+	g.subDir = finalSubDir
 	return g
 }
 
@@ -713,25 +708,27 @@
 	Shard_size *int64
 }
 
-const defaultShardSize = 100
+const defaultShardSize = 50
 
 func NewGenRule() *Module {
 	properties := &genRuleProperties{}
 
 	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 c19078f..8d3cfcb 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -57,7 +57,7 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("tool", toolFactory)
 
-	registerGenruleBuildComponents(ctx)
+	RegisterGenruleBuildComponents(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.Register()
@@ -141,7 +141,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "empty location tool2",
@@ -150,7 +150,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool",
@@ -187,7 +187,7 @@
 				out: ["out"],
 				cmd: "$(location tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool2",
@@ -196,7 +196,7 @@
 				out: ["out"],
 				cmd: "$(location :tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "in1",
@@ -232,7 +232,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "in1 fg",
@@ -241,7 +241,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "ins",
@@ -250,7 +250,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "outs",
@@ -303,7 +303,7 @@
 				out: ["out", "out2"],
 				cmd: "echo foo > $(out)",
 			`,
-			expect: "echo foo > __SBOX_OUT_FILES__",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2",
 		},
 		{
 			name: "location out",
@@ -311,7 +311,7 @@
 				out: ["out", "out2"],
 				cmd: "echo foo > $(location out2)",
 			`,
-			expect: "echo foo > __SBOX_OUT_DIR__/out2",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2",
 		},
 		{
 			name: "depfile",
@@ -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_SANDBOX_DIR__/out/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_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out",
 		},
 
 		{
@@ -443,7 +443,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__",
+			expect: "cat ***missing srcs :missing*** > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool allow missing dependencies",
@@ -455,7 +455,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "***missing tool :missing*** > __SBOX_OUT_FILES__",
+			expect: "***missing tool :missing*** > __SBOX_SANDBOX_DIR__/out/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",
 		},
 	}
 
@@ -570,20 +570,11 @@
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
 			gen := ctx.ModuleForTests(test.name, "")
-			command := gen.Rule("generator").RuleParams.Command
+			manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+			hash := manifest.Commands[0].GetInputHash()
 
-			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 + " "
-
-				if !strings.Contains(command, expectedInputHashOption) {
-					t.Errorf("Expected command \"%s\" to contain \"%s\"", command, expectedInputHashOption)
-				}
-			} else {
-				if strings.Contains(command, "--input-hash") {
-					t.Errorf("Unexpected \"--input-hash\" found in command: \"%s\"", command)
-				}
+			if g, w := hash, test.expectedHash; g != w {
+				t.Errorf("Expected has %q, got %q", w, g)
 			}
 		})
 	}
@@ -609,7 +600,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_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/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 +614,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_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
+				"bash -c 'out/tool in3.txt > __SBOX_SANDBOX_DIR__/out/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 +701,7 @@
 	}
 	gen := ctx.ModuleForTests("gen", "").Module().(*Module)
 
-	expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'"
+	expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
 	if gen.rawCommands[0] != expectedCmd {
 		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0])
 	}
diff --git a/java/Android.bp b/java/Android.bp
index 9e8dc78..39502b3 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -48,6 +48,7 @@
         "robolectric.go",
         "sdk.go",
         "sdk_library.go",
+        "sdk_library_external.go",
         "support_libraries.go",
         "sysprop.go",
         "system_modules.go",
diff --git a/java/aapt2.go b/java/aapt2.go
index 04e4de5..5346ddf 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -25,12 +25,9 @@
 	"android/soong/android"
 )
 
-const AAPT2_SHARD_SIZE = 100
-
 // Convert input resource file path to output file path.
 // values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
-// For other resource file, just replace the last "/" with "_" and
-// add .flat extension.
+// For other resource file, just replace the last "/" with "_" and add .flat extension.
 func pathToAapt2Path(ctx android.ModuleContext, res android.Path) android.WritablePath {
 
 	name := res.Base()
@@ -43,6 +40,7 @@
 	return android.PathForModuleOut(ctx, "aapt2", subDir, name)
 }
 
+// pathsToAapt2Paths Calls pathToAapt2Path on each entry of the given Paths, i.e. []Path.
 func pathsToAapt2Paths(ctx android.ModuleContext, resPaths android.Paths) android.WritablePaths {
 	outPaths := make(android.WritablePaths, len(resPaths))
 
@@ -53,6 +51,9 @@
 	return outPaths
 }
 
+// Shard resource files for efficiency. See aapt2Compile for details.
+const AAPT2_SHARD_SIZE = 100
+
 var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
 	blueprint.RuleParams{
 		Command:     `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
@@ -60,14 +61,26 @@
 	},
 	"outDir", "cFlags")
 
+// aapt2Compile compiles resources and puts the results in the requested directory.
 func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
 	flags []string) android.WritablePaths {
 
+	// Shard the input paths so that they can be processed in parallel. If we shard them into too
+	// small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
+	// current shard size, 100, seems to be a good balance between the added cost and the gain.
+	// The aapt2 compile actions are trivially short, but each action in ninja takes on the order of
+	// ~10 ms to run. frameworks/base/core/res/res has >10k resource files, so compiling each one
+	// with an individual action could take 100 CPU seconds. Sharding them reduces the overhead of
+	// starting actions by a factor of 100, at the expense of recompiling more files when one
+	// changes.  Since the individual compiles are trivial it's a good tradeoff.
 	shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
 
 	ret := make(android.WritablePaths, 0, len(paths))
 
 	for i, shard := range shards {
+		// This should be kept in sync with pathToAapt2Path. The aapt2 compile command takes an
+		// output directory path, but not output file paths. So, outPaths is just where we expect
+		// the output files will be located.
 		outPaths := pathsToAapt2Paths(ctx, shard)
 		ret = append(ret, outPaths...)
 
@@ -82,6 +95,12 @@
 			Inputs:      shard,
 			Outputs:     outPaths,
 			Args: map[string]string{
+				// The aapt2 compile command takes an output directory path, but not output file paths.
+				// outPaths specified above is only used for dependency management purposes. In order for
+				// the outPaths values to match the actual outputs from aapt2, the dir parameter value
+				// must be a common prefix path of the paths values, and the top-level path segment used
+				// below, "aapt2", must always be kept in sync with the one in pathToAapt2Path.
+				// TODO(b/174505750): Make this easier and robust to use.
 				"outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(),
 				"cFlags": strings.Join(flags, " "),
 			},
@@ -104,6 +123,8 @@
 		},
 	}, "cFlags", "resZipDir", "zipSyncFlags")
 
+// Unzips the given compressed file and compiles the resource source files in it. The zipPrefix
+// parameter points to the subdirectory in the zip file where the resource files are located.
 func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string,
 	flags []string) {
 
@@ -163,6 +184,7 @@
 	var inFlags []string
 
 	if len(compiledRes) > 0 {
+		// Create a file that contains the list of all compiled resource file paths.
 		resFileList := android.PathForModuleOut(ctx, "aapt2", "res.list")
 		// Write out file lists to files
 		ctx.Build(pctx, android.BuildParams{
@@ -174,10 +196,12 @@
 
 		deps = append(deps, compiledRes...)
 		deps = append(deps, resFileList)
+		// aapt2 filepath arguments that start with "@" mean file-list files.
 		inFlags = append(inFlags, "@"+resFileList.String())
 	}
 
 	if len(compiledOverlay) > 0 {
+		// Compiled overlay files are processed the same way as compiled resources.
 		overlayFileList := android.PathForModuleOut(ctx, "aapt2", "overlay.list")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        fileListToFileRule,
@@ -188,9 +212,11 @@
 
 		deps = append(deps, compiledOverlay...)
 		deps = append(deps, overlayFileList)
+		// Compiled overlay files are passed over to aapt2 using -R option.
 		inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
 	}
 
+	// Set auxiliary outputs as implicit outputs to establish correct dependency chains.
 	implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages)
 	linkOutput := packageRes
 
@@ -212,6 +238,10 @@
 		Implicits:       deps,
 		Output:          linkOutput,
 		ImplicitOutputs: implicitOutputs,
+		// Note the absence of splitPackages. The caller is supposed to compose and provide --split flag
+		// values via the flags parameter when it wants to split outputs.
+		// TODO(b/174509108): Perhaps we can process it in this func while keeping the code reasonably
+		// tidy.
 		Args: map[string]string{
 			"flags":           strings.Join(flags, " "),
 			"inFlags":         strings.Join(inFlags, " "),
@@ -230,6 +260,8 @@
 		CommandDeps: []string{"${config.Aapt2Cmd}"},
 	})
 
+// Converts xml files and resource tables (resources.arsc) in the given jar/apk file to a proto
+// format. The proto definition is available at frameworks/base/tools/aapt2/Resources.proto.
 func aapt2Convert(ctx android.ModuleContext, out android.WritablePath, in android.Path) {
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        aapt2ConvertRule,
diff --git a/java/aar.go b/java/aar.go
index 7c3840b..3b6b34e 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -259,16 +259,16 @@
 	})
 
 func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext,
-	sdkLibraries dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
 
 	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
-		aaptLibs(ctx, sdkContext, sdkLibraries)
+		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)
 
@@ -389,15 +389,15 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, sdkLibraries dexpreopt.ClassLoaderContextMap) (
+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 sdkLibraries == nil {
+	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).
-		sdkLibraries = make(dexpreopt.ClassLoaderContextMap)
+		classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 	}
 
 	sdkDep := decodeSdkDep(ctx, sdkContext)
@@ -407,6 +407,7 @@
 
 	ctx.VisitDirectDeps(func(module android.Module) {
 		depName := ctx.OtherModuleName(module)
+		depTag := ctx.OtherModuleDependencyTag(module)
 
 		var exportPackage android.Path
 		aarDep, _ := module.(AndroidLibraryDependency)
@@ -414,7 +415,7 @@
 			exportPackage = aarDep.ExportPackage()
 		}
 
-		switch ctx.OtherModuleDependencyTag(module) {
+		switch depTag {
 		case instrumentationForTag:
 			// Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2.
 		case libTag:
@@ -426,7 +427,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.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
+				classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
 					component.DexJarBuildPath(), component.DexJarInstallPath())
 			}
 
@@ -439,7 +440,6 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				sdkLibraries.AddContextMap(aarDep.ExportedSdkLibs(), depName)
 				if aarDep.ExportedAssets().Valid() {
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
@@ -458,11 +458,8 @@
 			}
 		}
 
-		// 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 {
-			sdkLibraries.AddContextMap(dep.ExportedSdkLibs(), depName)
-		}
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, depTag, depName, classLoaderContexts)
 	})
 
 	deps = append(deps, sharedLibs...)
@@ -514,8 +511,8 @@
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
-	a.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
-	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs)
+	a.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
+	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -832,12 +829,12 @@
 	return nil
 }
 
-func (a *AARImport) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
+func (a *AARImport) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
-func (d *AARImport) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (d *AARImport) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (a *AARImport) SrcJarArgs() ([]string, android.Paths) {
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 6b39c35..c76bb2f 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -44,7 +44,7 @@
 
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
 func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
-	sdkLibraries dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
 	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
 
 	var args []string
@@ -71,7 +71,7 @@
 		args = append(args, "--use-embedded-dex")
 	}
 
-	for _, usesLib := range sdkLibraries.UsesLibs() {
+	for _, usesLib := range classLoaderContexts.UsesLibs() {
 		if inList(usesLib, dexpreopt.OptionalCompatUsesLibs) {
 			args = append(args, "--optional-uses-library", usesLib)
 		} else {
diff --git a/java/androidmk.go b/java/androidmk.go
index c606245..fc573c8 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -85,7 +85,6 @@
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
-			DistFiles:  library.distFiles,
 			OutputFile: android.OptionalPathForPath(library.outputFile),
 			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
@@ -115,7 +114,7 @@
 						entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 					}
 
-					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.exportedSdkLibs.UsesLibs()...)
+					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.classLoaderContexts.UsesLibs()...)
 
 					if len(library.additionalCheckedModules) != 0 {
 						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
@@ -140,9 +139,9 @@
 func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
-		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test_suites...)
+		entries.AddCompatibilityTestSuites(test_suites...)
 	} else {
-		entries.SetString("LOCAL_COMPATIBILITY_SUITE", "null-suite")
+		entries.AddCompatibilityTestSuites("null-suite")
 	}
 }
 
@@ -160,6 +159,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
@@ -521,17 +523,12 @@
 	// Note that dstubs.apiFile can be also be nil if WITHOUT_CHECKS_API is true.
 	// TODO(b/146727827): Revert when we do not need to generate stubs and API separately.
 
-	var distFiles android.TaggedDistFiles
-	if dstubs.apiFile != nil {
-		distFiles = android.MakeDefaultDistFiles(dstubs.apiFile)
-	}
 	outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar)
 	if !outputFile.Valid() {
 		outputFile = android.OptionalPathForPath(dstubs.apiFile)
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		DistFiles:  distFiles,
 		OutputFile: outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 075b7aa..233e9d5 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"reflect"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -135,182 +134,6 @@
 	}
 }
 
-func TestDistWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-			},
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-				tag: ".jar",
-			},
-		}
-	`)
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-	if len(withTagEntries[0].DistFiles[".jar"]) != 1 ||
-		!strings.Contains(withTagEntries[0].DistFiles[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected DistFiles to contain classes.jar, got %v", withTagEntries[0].DistFiles)
-	}
-	if len(withoutTagEntries[0].DistFiles[".jar"]) > 0 {
-		t.Errorf("did not expect explicit DistFile for .jar tag, got %v", withoutTagEntries[0].DistFiles[".jar"])
-	}
-}
-
-func TestDistWithDest(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["my_goal"],
-				dest: "my/custom/dest/dir",
-			},
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 2 {
-		t.Errorf("Expected 2 entries for dist: PHONY and dist-for-goals, but got %q", distStrings)
-	}
-
-	if distStrings[0] != ".PHONY: my_goal\n" {
-		t.Errorf("Expected .PHONY entry to declare my_goal, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,my_goal") ||
-		!strings.Contains(distStrings[1], ".intermediates/foo/android_common/dex/foo.jar:my/custom/dest/dir") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_goal and new dest dir, but got: %s", distStrings[1])
-	}
-}
-
-func TestDistsWithAllProperties(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["baz"],
-			},
-			dists: [
-				{
-					targets: ["bar"],
-					tag: ".jar",
-					dest: "bar.jar",
-					dir: "bar/dir",
-					suffix: ".qux",
-				},
-			]
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 4 {
-		t.Errorf("Expected 4 entries for dist: PHONY and dist-for-goals, but got %d", len(distStrings))
-	}
-
-	if distStrings[0] != ".PHONY: bar\n" {
-		t.Errorf("Expected .PHONY entry to declare bar, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,bar") ||
-		!strings.Contains(
-			distStrings[1],
-			".intermediates/foo/android_common/javac/foo.jar:bar/dir/bar.qux.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain bar and new dest dir, but got: %s", distStrings[1])
-	}
-
-	if distStrings[2] != ".PHONY: baz\n" {
-		t.Errorf("Expected .PHONY entry to declare baz, but got: %s", distStrings[2])
-	}
-
-	if !strings.Contains(distStrings[3], "$(call dist-for-goals,baz") ||
-		!strings.Contains(distStrings[3], ".intermediates/foo/android_common/dex/foo.jar:foo.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_other_goal and new dest dir, but got: %s",
-			distStrings[3])
-	}
-}
-
-func TestDistsWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-				},
-			],
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-					tag: ".jar",
-				},
-			],
-		}
-	`)
-
-	moduleWithoutTag := ctx.ModuleForTests("foo_without_tag", "android_common").Module()
-	moduleWithTag := ctx.ModuleForTests("foo_with_tag", "android_common").Module()
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithoutTag)
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithTag)
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-
-	distFilesWithoutTag := withoutTagEntries[0].DistFiles
-	distFilesWithTag := withTagEntries[0].DistFiles
-
-	if len(distFilesWithTag[".jar"]) != 1 ||
-		!strings.Contains(distFilesWithTag[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected foo_with_tag's .jar-tagged DistFiles to contain classes.jar, got %v", distFilesWithTag[".jar"])
-	}
-	if len(distFilesWithoutTag[".jar"]) > 0 {
-		t.Errorf("did not expect foo_without_tag's .jar-tagged DistFiles to contain files, but got %v", distFilesWithoutTag[".jar"])
-	}
-}
-
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
 	ctx, config := testJava(t, `
 		java_sdk_library {
diff --git a/java/app.go b/java/app.go
index 9ff413c..e6d9550 100755
--- a/java/app.go
+++ b/java/app.go
@@ -566,7 +566,7 @@
 
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs, 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
@@ -608,7 +608,7 @@
 	}
 	a.dexpreopter.uncompressedDex = *a.dexProperties.Uncompress_dex
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.classLoaderContexts = a.exportedSdkLibs
+	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
 
 	if ctx.ModuleName() != "framework-res" {
@@ -779,7 +779,7 @@
 		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
 	}
 
-	a.exportedSdkLibs = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
@@ -788,7 +788,7 @@
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
 	// Add implicit SDK libraries to <uses-library> list.
-	for _, usesLib := range a.exportedSdkLibs.UsesLibs() {
+	for _, usesLib := range a.classLoaderContexts.UsesLibs() {
 		a.usesLibrary.addLib(usesLib, inList(usesLib, dexpreopt.OptionalCompatUsesLibs))
 	}
 
@@ -890,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
 				}
 
@@ -1115,8 +1115,8 @@
 	}
 
 	fixedConfig := android.PathForModuleOut(ctx, "test_config_fixer", "AndroidTest.xml")
-	rule := android.NewRuleBuilder()
-	command := rule.Command().BuiltTool(ctx, "test_config_fixer").Input(testConfig).Output(fixedConfig)
+	rule := android.NewRuleBuilder(pctx, ctx)
+	command := rule.Command().BuiltTool("test_config_fixer").Input(testConfig).Output(fixedConfig)
 	fixNeeded := false
 
 	if ctx.ModuleName() != a.installApkName {
@@ -1131,7 +1131,7 @@
 	}
 
 	if fixNeeded {
-		rule.Build(pctx, ctx, "fix_test_config", "fix test config")
+		rule.Build("fix_test_config", "fix test config")
 		return fixedConfig
 	}
 	return testConfig
@@ -1400,6 +1400,13 @@
 	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
 	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
 	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
+
+	if String(a.properties.Apk) == "" {
+		// Disable this module since the apk property is still empty after processing all matching
+		// variants. This likely means there is no matching variant, and the default variant doesn't
+		// have an apk property value either.
+		a.Disable()
+	}
 }
 
 func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
@@ -1440,15 +1447,15 @@
 		})
 		return
 	}
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
-		BuiltTool(ctx, "zip2zip").
+		BuiltTool("zip2zip").
 		FlagWithInput("-i ", inputPath).
 		FlagWithOutput("-o ", outputPath).
 		FlagWithArg("-0 ", "'lib/**/*.so'").
 		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
-	rule.Build(pctx, ctx, "uncompress-embedded-jni-libs", "Uncompress embedded JIN libs")
+	rule.Build("uncompress-embedded-jni-libs", "Uncompress embedded JIN libs")
 }
 
 // Returns whether this module should have the dex file stored uncompressed in the APK.
@@ -1467,15 +1474,15 @@
 
 func (a *AndroidAppImport) uncompressDex(
 	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
-		BuiltTool(ctx, "zip2zip").
+		BuiltTool("zip2zip").
 		FlagWithInput("-i ", inputPath).
 		FlagWithOutput("-o ", outputPath).
 		FlagWithArg("-0 ", "'classes*.dex'").
 		Textf(`; else cp -f %s %s; fi`, inputPath, outputPath)
-	rule.Build(pctx, ctx, "uncompress-dex", "Uncompress dex files")
+	rule.Build("uncompress-dex", "Uncompress dex files")
 }
 
 func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1982,7 +1989,7 @@
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(Dependency); ok {
 					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
-						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ExportedSdkLibs())
+						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
 				} else {
@@ -2015,8 +2022,8 @@
 func (u *usesLibrary) verifyUsesLibrariesManifest(ctx android.ModuleContext, manifest android.Path) android.Path {
 	outputFile := android.PathForModuleOut(ctx, "manifest_check", "AndroidManifest.xml")
 
-	rule := android.NewRuleBuilder()
-	cmd := rule.Command().BuiltTool(ctx, "manifest_check").
+	rule := android.NewRuleBuilder(pctx, ctx)
+	cmd := rule.Command().BuiltTool("manifest_check").
 		Flag("--enforce-uses-libraries").
 		Input(manifest).
 		FlagWithOutput("-o ", outputFile)
@@ -2029,7 +2036,7 @@
 		cmd.FlagWithArg("--optional-uses-library ", lib)
 	}
 
-	rule.Build(pctx, ctx, "verify_uses_libraries", "verify <uses-library>")
+	rule.Build("verify_uses_libraries", "verify <uses-library>")
 
 	return outputFile
 }
@@ -2039,7 +2046,7 @@
 func (u *usesLibrary) verifyUsesLibrariesAPK(ctx android.ModuleContext, apk android.Path) android.Path {
 	outputFile := android.PathForModuleOut(ctx, "verify_uses_libraries", apk.Base())
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	aapt := ctx.Config().HostToolPath(ctx, "aapt")
 	rule.Command().
 		Textf("aapt_binary=%s", aapt.String()).Implicit(aapt).
@@ -2048,7 +2055,7 @@
 		Tool(android.PathForSource(ctx, "build/make/core/verify_uses_libraries.sh")).Input(apk)
 	rule.Command().Text("cp -f").Input(apk).Output(outputFile)
 
-	rule.Build(pctx, ctx, "verify_uses_libraries", "verify <uses-library>")
+	rule.Build("verify_uses_libraries", "verify <uses-library>")
 
 	return outputFile
 }
diff --git a/java/app_test.go b/java/app_test.go
index 6429ab8..e13c6b9 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -291,7 +291,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -335,7 +335,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2524,6 +2524,24 @@
 			`,
 			expected: "prebuilts/apk/app.apk",
 		},
+		{
+			name: "no matching arch without default",
+			bp: `
+				android_app_import {
+					name: "foo",
+					arch: {
+						arm: {
+							apk: "prebuilts/apk/app_arm.apk",
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected: "",
+		},
 	}
 
 	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
@@ -2531,6 +2549,12 @@
 		ctx, _ := testJava(t, test.bp)
 
 		variant := ctx.ModuleForTests("foo", "android_common")
+		if test.expected == "" {
+			if variant.Module().Enabled() {
+				t.Error("module should have been disabled, but wasn't")
+			}
+			continue
+		}
 		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
 		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
 		if len(matches) != 2 {
@@ -2542,6 +2566,34 @@
 	}
 }
 
+func TestAndroidAppImport_overridesDisabledAndroidApp(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			enabled: false,
+		}
+ 
+ 		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			prefer: true,
+		}
+		`)
+
+	variant := ctx.ModuleForTests("prebuilt_foo", "android_common")
+	a := variant.Module().(*AndroidAppImport)
+	// The prebuilt module should still be enabled and active even if the source-based counterpart
+	// is disabled.
+	if !a.prebuilt.UsePrebuilt() {
+		t.Errorf("prebuilt foo module is not active")
+	}
+	if !a.Enabled() {
+		t.Errorf("prebuilt foo module is disabled")
+	}
+}
+
 func TestAndroidTestImport(t *testing.T) {
 	ctx, config := testJava(t, `
 		android_test_import {
@@ -2730,6 +2782,13 @@
 		}
 
 		java_sdk_library {
+			name: "fred",
+			srcs: ["a.java"],
+			api_packages: ["fred"],
+			sdk_version: "current",
+		}
+
+		java_sdk_library {
 			name: "bar",
 			srcs: ["a.java"],
 			api_packages: ["bar"],
@@ -2753,7 +2812,12 @@
 			name: "app",
 			srcs: ["a.java"],
 			libs: ["qux", "quuz.stubs"],
-			static_libs: ["static-runtime-helper"],
+			static_libs: [
+				"static-runtime-helper",
+				// statically linked component libraries should not pull their SDK libraries,
+				// so "fred" should not be added to class loader context
+				"fred.stubs",
+			],
 			uses_libs: ["foo"],
 			sdk_version: "current",
 			optional_uses_libs: [
diff --git a/java/boot_jars.go b/java/boot_jars.go
index e706547..823275b 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -85,8 +85,8 @@
 
 	timestamp := android.PathForOutput(ctx, "boot-jars-package-check/stamp")
 
-	rule := android.NewRuleBuilder()
-	checkBootJars := rule.Command().BuiltTool(ctx, "check_boot_jars").
+	rule := android.NewRuleBuilder(pctx, ctx)
+	checkBootJars := rule.Command().BuiltTool("check_boot_jars").
 		Input(ctx.Config().HostToolPath(ctx, "dexdump")).
 		Input(android.PathForSource(ctx, "build/soong/scripts/check_boot_jars/package_allowed_list.txt"))
 
@@ -109,7 +109,7 @@
 	}
 
 	checkBootJars.Text("&& touch").Output(timestamp)
-	rule.Build(pctx, ctx, "boot_jars_package_check", "check boot jar packages")
+	rule.Build("boot_jars_package_check", "check boot jar packages")
 
 	// The check-boot-jars phony target depends on the timestamp created if the check succeeds.
 	ctx.Phony("check-boot-jars", timestamp)
diff --git a/java/builder.go b/java/builder.go
index 3043e46..995160d 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -42,7 +42,7 @@
 	// TODO(b/143658984): goma can't handle the --system argument to javac.
 	javac, javacRE = remoteexec.MultiCommandStaticRules(pctx, "javac",
 		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
+			Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" "$out" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
 				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
 				`(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` +
 				`${config.SoongJavacWrapper} $javaTemplate${config.JavacCmd} ` +
@@ -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 d8b617e..4914d74 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -163,12 +163,12 @@
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
+func (d *DeviceHostConverter) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (d *DeviceHostConverter) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (d *DeviceHostConverter) SrcJarArgs() ([]string, android.Paths) {
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index a21fb76..67738d4 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -216,7 +216,7 @@
 		return dexJarFile
 	}
 
-	dexpreoptRule.Build(pctx, ctx, "dexpreopt", "dexpreopt")
+	dexpreoptRule.Build("dexpreopt", "dexpreopt")
 
 	d.builtInstalled = dexpreoptRule.Installs().String()
 
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 629d34f..062005b 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -25,11 +25,177 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// This comment describes:
+//   1. ART boot images in general (their types, structure, file layout, etc.)
+//   2. build system support for boot images
+//
+// 1. ART boot images
+// ------------------
+//
+// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot
+// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a
+// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is
+// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled
+// against it (compilation may take place either on host, known as "dexpreopt", or on device, known
+// as "dexopt").
+//
+// A boot image is not a single file, but a collection of interrelated files. Each boot image has a
+// number of components that correspond to the Java libraries that constitute it. For each component
+// there are multiple files:
+//   - *.oat or *.odex file with native code (architecture-specific, one per instruction set)
+//   - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set)
+//   - *.vdex file with verification metadata for the DEX bytecode (architecture independent)
+//
+// *.vdex files for the boot images do not contain the DEX bytecode itself, because the
+// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot
+// image is not self-contained and cannot be used without its DEX files. To simplify the management
+// of boot image files, ART uses a certain naming scheme and associates the following metadata with
+// each boot image:
+//   - A stem, which is a symbolic name that is prepended to boot image file names.
+//   - A location (on-device path to the boot image files).
+//   - A list of boot image locations (on-device paths to dependency boot images).
+//   - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used
+//     to compile the boot image).
+//
+// There are two kinds of boot images:
+//   - primary boot images
+//   - boot image extensions
+//
+// 1.1. Primary boot images
+// ------------------------
+//
+// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not
+// depend on any other images, and other boot images may depend on it.
+//
+// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/,
+// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets
+// (32 and 64 bits), it will have three components with the following files:
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex}
+//
+// The files of the first component are special: they do not have the component name appended after
+// the stem. This naming convention dates back to the times when the boot image was not split into
+// components, and there were just boot.oat and boot.art. The decision to split was motivated by
+// licensing reasons for one of the bootclasspath libraries.
+//
+// As of November 2020 the only primary boot image in Android is the image in the ART APEX
+// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART
+// module. When the ART module gets updated, the primary boot image will be updated with it, and all
+// dependent images will get invalidated (the checksum of the primary image stored in dependent
+// images will not match), unless they are updated in sync with the ART module.
+//
+// 1.2. Boot image extensions
+// --------------------------
+//
+// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular,
+// this subset does not include the Core bootclasspath libraries that go into the primary boot
+// image). A boot image extension depends on the primary boot image and optionally some other boot
+// image extensions. Other images may depend on it. In other words, boot image extensions can form
+// acyclic dependency graphs.
+//
+// The motivation for boot image extensions comes from the Mainline project. Consider a situation
+// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android
+// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java
+// code for C might have changed compared to the code that was used to compile the boot image.
+// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B
+// that does not depend on C is up to date). To avoid this, the original monolithic boot image is
+// split in two parts: the primary boot image that contains A B, and the boot image extension that
+// contains C and depends on the primary boot image (extends it).
+//
+// For example, assuming that the stem is "boot", the location is /system/framework, the set of
+// bootclasspath libraries is D E (where D is part of the platform and is located in
+// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in
+// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits),
+// it will have two components with the following files:
+//   - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex}
+//   - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex}
+//
+// As of November 2020 the only boot image extension in Android is the Framework boot image
+// extension. It extends the primary ART boot image and contains Framework libraries and other
+// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the
+// ART image. The Framework boot image extension is updated together with the platform. In the
+// future other boot image extensions may be added for some updatable modules.
+//
+//
+// 2. Build system support for boot images
+// ---------------------------------------
+//
+// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX
+// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat
+// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the
+// Core libraries.
+//
+// 2.1. Libraries that go in the boot images
+// -----------------------------------------
+//
+// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX
+// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The
+// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and
+// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries,
+// but more product-specific libraries can be added in the product makefiles.
+//
+// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is either a simple
+// name (if the library is a part of the Platform), or a colon-separated pair <apex, name> (if the
+// library is a part of a non-updatable APEX).
+//
+// A related variable PRODUCT_UPDATABLE_BOOT_JARS contains bootclasspath libraries that are in
+// updatable APEXes. They are not included in the boot image.
+//
+// One exception to the above rules are "coverage" builds (a special build flavor which requires
+// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in
+// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent)
+// needs to be added to the list of bootclasspath DEX jars.
+//
+// In general, there is a requirement that the source code for a boot image library must be
+// available at build time (e.g. it cannot be a stub that has a separate implementation library).
+//
+// 2.2. Static configs
+// -------------------
+//
+// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must
+// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image
+// configs are constructed very early during the build, before build rule generation. The configs
+// provide predefined paths to boot image files (these paths depend only on static build
+// configuration, such as PRODUCT variables, and use hard-coded directory names).
+//
+// 2.3. Singleton
+// --------------
+//
+// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no
+// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules.
+// Soong loops through all modules and compares each module against a list of bootclasspath library
+// names. Then it generates build rules that copy DEX jars from their intermediate module-specific
+// locations to the hard-coded locations predefined in the boot image configs.
+//
+// It would be possible to use a module with proper dependencies instead, but that would require
+// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method
+// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile,
+// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables
+// for each module, and is included later.
+//
+// 2.4. Install rules
+// ------------------
+//
+// The primary boot image and the Framework extension are installed in different ways. The primary
+// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged
+// together with other APEX contents, extracted and mounted on device. The Framework boot image
+// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong
+// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
+// paths and so on.
+//
+// 2.5. JIT-Zygote configuration
+// -----------------------------
+//
+// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling
+// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
+//
+
 func init() {
 	RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
 }
 
-// Target-independent description of pre-compiled boot image.
+// Target-independent description of a boot image.
 type bootImageConfig struct {
 	// If this image is an extension, the image that it extends.
 	extends *bootImageConfig
@@ -66,7 +232,7 @@
 	variants []*bootImageVariant
 }
 
-// Target-dependent description of pre-compiled boot image.
+// Target-dependent description of a boot image.
 type bootImageVariant struct {
 	*bootImageConfig
 
@@ -90,6 +256,7 @@
 	unstrippedInstalls android.RuleBuilderInstalls
 }
 
+// Get target-specific boot image variant for the given boot image config and target.
 func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant {
 	for _, variant := range image.variants {
 		if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType {
@@ -99,7 +266,7 @@
 	return nil
 }
 
-// Return any (the first) variant which is for the device (as opposed to for the host)
+// Return any (the first) variant which is for the device (as opposed to for the host).
 func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
 	for _, variant := range image.variants {
 		if variant.target.Os == android.Android {
@@ -109,10 +276,12 @@
 	return nil
 }
 
+// Return the name of a boot image module given a boot image config and a component (module) index.
+// A module name is a combination of the Java library name, and the boot image stem (that is stored
+// in the config).
 func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string {
-	// Dexpreopt on the boot class path produces multiple files. The first dex file
-	// is converted into 'name'.art (to match the legacy assumption that 'name'.art
-	// exists), and the rest are converted to 'name'-<jar>.art.
+	// The first module of the primary boot image is special: its module name has only the stem, but
+	// not the library name. All other module names are of the form <stem>-<library name>
 	m := image.modules.Jar(idx)
 	name := image.stem
 	if idx != 0 || image.extends != nil {
@@ -121,6 +290,7 @@
 	return name
 }
 
+// Return the name of the first boot image module, or stem if the list of modules is empty.
 func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string {
 	if image.modules.Len() > 0 {
 		return image.moduleName(ctx, 0)
@@ -129,6 +299,8 @@
 	}
 }
 
+// Return filenames for the given boot image component, given the output directory and a list of
+// extensions.
 func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
 	ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts))
 	for i := 0; i < image.modules.Len(); i++ {
@@ -140,17 +312,26 @@
 	return ret
 }
 
+// Return boot image locations (as a list of symbolic paths).
+//
 // The image "location" is a symbolic path that, with multiarchitecture support, doesn't really
 // exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the
 // same for all supported architectures on the device. The concrete architecture specific files
 // actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64.
 //
-// For example a physical file
-// "/apex/com.android.art/javalib/x86/boot.art" has "image location"
-// "/apex/com.android.art/javalib/boot.art" (which is not an actual file).
+// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location"
+// /apex/com.android.art/javalib/boot.art (which is not an actual file).
+//
+// For a primary boot image the list of locations has a single element.
+//
+// For a boot image extension the list of locations contains a location for all dependency images
+// (including the primary image) and the location of the extension itself. For example, for the
+// Framework boot image extension that depends on the primary ART boot image the list contains two
+// elements.
 //
 // The location is passed as an argument to the ART tools like dex2oat instead of the real path.
 // ART tools will then reconstruct the architecture-specific real path.
+//
 func (image *bootImageVariant) imageLocations() (imageLocations []string) {
 	if image.extends != nil {
 		imageLocations = image.extends.getVariant(image.target).imageLocations()
@@ -158,18 +339,6 @@
 	return append(imageLocations, dexpreopt.PathToLocation(image.images, image.target.Arch.ArchType))
 }
 
-func concat(lists ...[]string) []string {
-	var size int
-	for _, l := range lists {
-		size += len(l)
-	}
-	ret := make([]string, 0, size)
-	for _, l := range lists {
-		ret = append(ret, l...)
-	}
-	return ret
-}
-
 func dexpreoptBootJarsFactory() android.Singleton {
 	return &dexpreoptBootJars{}
 }
@@ -182,10 +351,21 @@
 	return dexpreopt.GetGlobalConfig(ctx).DisablePreopt
 }
 
+// Singleton for generating boot image build rules.
 type dexpreoptBootJars struct {
+	// Default boot image config (currently always the Framework boot image extension). It should be
+	// noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension,
+	// but the switch is handled not here, but in the makefiles (triggered with
+	// DEXPREOPT_USE_ART_IMAGE=true).
 	defaultBootImage *bootImageConfig
-	otherImages      []*bootImageConfig
 
+	// Other boot image configs (currently the list contains only the primary ART APEX image. It
+	// used to contain an experimental JIT-Zygote image (now replaced with the ART APEX image). In
+	// the future other boot image extensions may be added.
+	otherImages []*bootImageConfig
+
+	// Build path to a config file that Soong writes for Make (to be used in makefiles that install
+	// the default boot image).
 	dexpreoptConfigForMake android.WritablePath
 }
 
@@ -205,7 +385,7 @@
 	return files
 }
 
-// dexpreoptBoot singleton rules
+// Generate build rules for boot images.
 func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) {
 	if skipDexpreoptBootJars(ctx) {
 		return
@@ -238,43 +418,53 @@
 	dumpOatRules(ctx, d.defaultBootImage)
 }
 
-func isHostdex(module android.Module) bool {
-	if lib, ok := module.(*Library); ok {
-		return Bool(lib.deviceProperties.Hostdex)
-	}
-	return false
-}
-
 // Inspect this module to see if it contains a bootclasspath dex jar.
 // Note that the same jar may occur in multiple modules.
 // This logic is tested in the apex package to avoid import cycle apex <-> java.
 func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, module android.Module) (int, android.Path) {
-	// All apex Java libraries have non-installable platform variants, skip them.
-	if module.IsSkipInstall() {
-		return -1, nil
-	}
-
-	jar, hasJar := module.(interface{ DexJarBuildPath() android.Path })
-	if !hasJar {
-		return -1, nil
-	}
-
+	// Ignore any module that is not listed in the boot image configuration.
 	name := ctx.ModuleName(module)
 	index := image.modules.IndexOfJar(name)
 	if index == -1 {
 		return -1, nil
 	}
 
-	// Check that this module satisfies constraints for a particular boot image.
-	_, isApexModule := module.(android.ApexModule)
+	// It is an error if a module configured in the boot image does not support
+	// accessing the dex jar. This is safe because every module that has the same
+	// name has to have the same module type.
+	jar, hasJar := module.(interface{ DexJarBuildPath() android.Path })
+	if !hasJar {
+		ctx.Errorf("module %q configured in boot image %q does not support accessing dex jar", module, image.name)
+		return -1, nil
+	}
+
+	// It is also an error if the module is not an ApexModule.
+	if _, ok := module.(android.ApexModule); !ok {
+		ctx.Errorf("module %q configured in boot image %q does not support being added to an apex", module, image.name)
+		return -1, nil
+	}
+
 	apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
-	fromUpdatableApex := isApexModule && apexInfo.Updatable
-	if image.name == artBootImageName {
-		if isApexModule && len(apexInfo.InApexes) > 0 && allHavePrefix(apexInfo.InApexes, "com.android.art") {
-			// ok: found the jar in the ART apex
-		} else if isApexModule && apexInfo.IsForPlatform() && isHostdex(module) {
-			// exception (skip and continue): special "hostdex" platform variant
+
+	// Now match the apex part of the boot image configuration.
+	requiredApex := image.modules.Apex(index)
+	if requiredApex == "platform" {
+		if len(apexInfo.InApexes) != 0 {
+			// A platform variant is required but this is for an apex so ignore it.
 			return -1, nil
+		}
+	} else if !android.InList(requiredApex, apexInfo.InApexes) {
+		// An apex variant for a specific apex is required but this is the wrong apex.
+		return -1, nil
+	}
+
+	// Check that this module satisfies any boot image specific constraints.
+	fromUpdatableApex := apexInfo.Updatable
+
+	switch image.name {
+	case artBootImageName:
+		if len(apexInfo.InApexes) > 0 && allHavePrefix(apexInfo.InApexes, "com.android.art") {
+			// ok: found the jar in the ART apex
 		} else if name == "jacocoagent" && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
 			// exception (skip and continue): Jacoco platform variant for a coverage build
 			return -1, nil
@@ -285,14 +475,15 @@
 			// error: this jar is part of the platform or a non-updatable apex
 			ctx.Errorf("module %q is not allowed in the ART boot image", name)
 		}
-	} else if image.name == frameworkBootImageName {
+
+	case frameworkBootImageName:
 		if !fromUpdatableApex {
 			// ok: this jar is part of the platform or a non-updatable apex
 		} else {
 			// error: this jar is part of an updatable apex
 			ctx.Errorf("module %q from updatable apexes %q is not allowed in the framework boot image", name, apexInfo.InApexes)
 		}
-	} else {
+	default:
 		panic("unknown boot image: " + image.name)
 	}
 
@@ -315,6 +506,12 @@
 	bootDexJars := make(android.Paths, image.modules.Len())
 	ctx.VisitAllModules(func(module android.Module) {
 		if i, j := getBootImageJar(ctx, image, module); i != -1 {
+			if existing := bootDexJars[i]; existing != nil {
+				ctx.Errorf("Multiple dex jars found for %s:%s - %s and %s",
+					image.modules.Apex(i), image.modules.Jar(i), existing, j)
+				return
+			}
+
 			bootDexJars[i] = j
 		}
 	})
@@ -326,7 +523,7 @@
 			m := image.modules.Jar(i)
 			if ctx.Config().AllowMissingDependencies() {
 				missingDeps = append(missingDeps, m)
-				bootDexJars[i] = android.PathForOutput(ctx, "missing")
+				bootDexJars[i] = android.PathForOutput(ctx, "missing/module", m, "from/apex", image.modules.Apex(i))
 			} else {
 				ctx.Errorf("failed to find a dex jar path for module '%s'"+
 					", note that some jars may be filtered out by module constraints", m)
@@ -334,9 +531,10 @@
 		}
 	}
 
-	// The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before
-	// the bootclasspath modules have been compiled.  Copy the dex jars there so the module rules that have
-	// already been set up can find them.
+	// The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
+	// time, before the boot images are built (these paths are used in dexpreopt rule generation for
+	// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
+	// paths.
 	for i := range bootDexJars {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
@@ -358,19 +556,20 @@
 	}
 
 	if image.zip != nil {
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
-			BuiltTool(ctx, "soong_zip").
+			BuiltTool("soong_zip").
 			FlagWithOutput("-o ", image.zip).
 			FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()).
 			FlagWithInputList("-f ", zipFiles, " -f ")
 
-		rule.Build(pctx, ctx, "zip_"+image.name, "zip "+image.name+" image")
+		rule.Build("zip_"+image.name, "zip "+image.name+" image")
 	}
 
 	return image
 }
 
+// Generate boot image build rules for a specific target.
 func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant,
 	profile android.Path, missingDeps []string) android.WritablePaths {
 
@@ -386,7 +585,7 @@
 	oatLocation := dexpreopt.PathToLocation(outputPath, arch)
 	imagePath := outputPath.ReplaceExtension(ctx, "art")
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.MissingDeps(missingDeps)
 
 	rule.Command().Text("mkdir").Flag("-p").Flag(symbolsDir.String())
@@ -428,12 +627,15 @@
 	}
 
 	if image.extends != nil {
+		// It is a boot image extension, so it needs the boot image it depends on (in this case the
+		// primary ART APEX image).
 		artImage := image.primaryImages
 		cmd.
 			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
 			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
 	} else {
+		// It is a primary image, so it needs a base address.
 		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
 	}
 
@@ -504,7 +706,7 @@
 			android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())})
 	}
 
-	rule.Build(pctx, ctx, image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String())
+	rule.Build(image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String())
 
 	// save output and installed files for makevars
 	image.installs = rule.Installs()
@@ -528,7 +730,7 @@
 	profile := ctx.Config().Once(bootImageProfileRuleKey, func() interface{} {
 		defaultProfile := "frameworks/base/config/boot-image-profile.txt"
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.MissingDeps(missingDeps)
 
 		var bootImageProfile android.Path
@@ -559,7 +761,7 @@
 
 		rule.Install(profile, "/system/etc/boot-image.prof")
 
-		rule.Build(pctx, ctx, "bootJarsProfile", "profile boot jars")
+		rule.Build("bootJarsProfile", "profile boot jars")
 
 		image.profileInstalls = rule.Installs()
 
@@ -581,7 +783,7 @@
 		return nil
 	}
 	return ctx.Config().Once(bootFrameworkProfileRuleKey, func() interface{} {
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.MissingDeps(missingDeps)
 
 		// Some branches like master-art-host don't have frameworks/base, so manually
@@ -594,7 +796,7 @@
 			bootFrameworkProfile = path.Path()
 		} else {
 			missingDeps = append(missingDeps, defaultProfile)
-			bootFrameworkProfile = android.PathForOutput(ctx, "missing")
+			bootFrameworkProfile = android.PathForOutput(ctx, "missing", defaultProfile)
 		}
 
 		profile := image.dir.Join(ctx, "boot.bprof")
@@ -609,7 +811,7 @@
 			FlagWithOutput("--reference-profile-file=", profile)
 
 		rule.Install(profile, "/system/etc/boot-image.bprof")
-		rule.Build(pctx, ctx, "bootFrameworkProfile", "profile boot framework jars")
+		rule.Build("bootFrameworkProfile", "profile boot framework jars")
 		image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
 
 		return profile
@@ -651,16 +853,10 @@
 		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 := android.NewRuleBuilder(pctx, ctx)
 		rule.MissingDeps(missingDeps)
 		rule.Install(updatableBcpPackages, "/system/etc/"+updatableBcpPackagesName)
 		// TODO: Rename `profileInstalls` to `extraInstalls`?
@@ -684,25 +880,25 @@
 		}
 		// Create a rule to call oatdump.
 		output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt")
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			// TODO: for now, use the debug version for better error reporting
-			BuiltTool(ctx, "oatdumpd").
+			BuiltTool("oatdumpd").
 			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":").
 			FlagWithArg("--image=", strings.Join(image.imageLocations(), ":")).Implicits(image.imagesDeps.Paths()).
 			FlagWithOutput("--output=", output).
 			FlagWithArg("--instruction-set=", arch.String())
-		rule.Build(pctx, ctx, "dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
+		rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
 
 		// Create a phony rule that depends on the output file and prints the path.
 		phony := android.PathForPhony(ctx, "dump-oat-boot-"+suffix)
-		rule = android.NewRuleBuilder()
+		rule = android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			Implicit(output).
 			ImplicitOutput(phony).
 			Text("echo").FlagWithArg("Output in ", output.String())
-		rule.Build(pctx, ctx, "phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
+		rule.Build("phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
 
 		allPhonies = append(allPhonies, phony)
 	}
@@ -720,16 +916,12 @@
 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
+// Define Make variables for boot image names, paths, etc. These variables are used in makefiles
+// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the
+// correct output directories.
 func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
 	if d.dexpreoptConfigForMake != nil {
 		ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String())
@@ -743,6 +935,11 @@
 		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " "))
 
 		var imageNames []string
+		// TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
+		// different way as a part of the ART APEX. However, there is a special JIT-Zygote build
+		// configuration which uses the primary ART image instead of the Framework boot image
+		// extension, and it relies on the ART image being exposed to Make. To fix this, it is
+		// necessary to rework the logic in makefiles.
 		for _, current := range append(d.otherImages, image) {
 			imageNames = append(imageNames, current.name)
 			for _, variant := range current.variants {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index c7a27c2..9c88a3c 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -671,7 +671,7 @@
 
 	j.stubsSrcJar = nil
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	rule.Command().Text("rm -rf").Text(outDir.String())
 	rule.Command().Text("mkdir -p").Text(outDir.String())
@@ -689,7 +689,7 @@
 		Flag("-Xdoclint:none")
 
 	rule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
 		Flag("-d").
 		FlagWithOutput("-o ", j.docZip).
@@ -700,7 +700,7 @@
 
 	zipSyncCleanupCmd(rule, srcJarDir)
 
-	rule.Build(pctx, ctx, "javadoc", "javadoc")
+	rule.Build("javadoc", "javadoc")
 }
 
 //
@@ -845,7 +845,7 @@
 	outDir, srcJarDir, srcJarList android.Path, sourcepaths android.Paths) *android.RuleBuilderCommand {
 
 	cmd := rule.Command().
-		BuiltTool(ctx, "soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
+		BuiltTool("soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
 		Flag(config.JavacVmFlags).
 		FlagWithArg("-encoding ", "UTF-8").
 		FlagWithRspFileInputList("@", srcs).
@@ -914,7 +914,7 @@
 	dokkaClasspath := append(bootclasspath.Paths(), classpath.Paths()...)
 
 	return rule.Command().
-		BuiltTool(ctx, "dokka").
+		BuiltTool("dokka").
 		Flag(config.JavacVmFlags).
 		Flag(srcJarDir.String()).
 		FlagWithInputList("-classpath ", dokkaClasspath, ":").
@@ -934,7 +934,7 @@
 	outDir := android.PathForModuleOut(ctx, "out")
 	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
 
@@ -968,7 +968,7 @@
 	}
 
 	rule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
 		Flag("-d").
 		FlagWithOutput("-o ", d.docZip).
@@ -979,7 +979,7 @@
 
 	zipSyncCleanupCmd(rule, srcJarDir)
 
-	rule.Build(pctx, ctx, "javadoc", desc)
+	rule.Build("javadoc", desc)
 }
 
 //
@@ -1050,7 +1050,8 @@
 		return android.Paths{d.stubsSrcJar}, nil
 	case ".docs.zip":
 		return android.Paths{d.docZip}, nil
-	case ".api.txt":
+	case ".api.txt", android.DefaultDistTag:
+		// This is the default dist path for dist properties that have no tag property.
 		return android.Paths{d.apiFilePath}, nil
 	case ".removed-api.txt":
 		return android.Paths{d.removedApiFilePath}, nil
@@ -1277,7 +1278,7 @@
 		}).NoVarTemplate(ctx.Config()))
 	}
 
-	cmd.BuiltTool(ctx, "metalava").
+	cmd.BuiltTool("metalava").
 		Flag(config.JavacVmFlags).
 		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
 		FlagWithArg("-encoding ", "UTF-8").
@@ -1332,7 +1333,7 @@
 
 	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	if BoolDefault(d.properties.High_mem, false) {
 		// This metalava run uses lots of memory, restrict the number of metalava jobs that can run in parallel.
@@ -1479,19 +1480,19 @@
 		cmd.FlagWithArg("--error-message:compatibility:released ", msg)
 	}
 
-	impRule := android.NewRuleBuilder()
+	impRule := android.NewRuleBuilder(pctx, ctx)
 	impCmd := impRule.Command()
 	// An action that copies the ninja generated rsp file to a new location. This allows us to
 	// add a large number of inputs to a file without exceeding bash command length limits (which
 	// would happen if we use the WriteFile rule). The cp is needed because RuleBuilder sets the
 	// rsp file to be ${output}.rsp.
 	impCmd.Text("cp").FlagWithRspFileInputList("", cmd.GetImplicits()).Output(implicitsRsp)
-	impRule.Build(pctx, ctx, "implicitsGen", "implicits generation")
+	impRule.Build("implicitsGen", "implicits generation")
 	cmd.Implicit(implicitsRsp)
 
 	if generateStubs {
 		rule.Command().
-			BuiltTool(ctx, "soong_zip").
+			BuiltTool("soong_zip").
 			Flag("-write_if_changed").
 			Flag("-jar").
 			FlagWithOutput("-o ", d.Javadoc.stubsSrcJar).
@@ -1502,7 +1503,7 @@
 	if Bool(d.properties.Write_sdk_values) {
 		d.metadataZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-metadata.zip")
 		rule.Command().
-			BuiltTool(ctx, "soong_zip").
+			BuiltTool("soong_zip").
 			Flag("-write_if_changed").
 			Flag("-d").
 			FlagWithOutput("-o ", d.metadataZip).
@@ -1523,7 +1524,7 @@
 
 	zipSyncCleanupCmd(rule, srcJarDir)
 
-	rule.Build(pctx, ctx, "metalava", "metalava merged")
+	rule.Build("metalava", "metalava merged")
 
 	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
 
@@ -1541,7 +1542,7 @@
 
 		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 
 		// Diff command line.
 		// -F matches the closest "opening" line, such as "package android {"
@@ -1563,7 +1564,7 @@
 			`   1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+
 			`      to the new methods, etc. shown in the above diff.\n\n`+
 			`   2. You can update current.txt and/or removed.txt by executing the following command:\n`+
-			`         make %s-update-current-api\n\n`+
+			`         m %s-update-current-api\n\n`+
 			`      To submit the revised current.txt to the main Android repository,\n`+
 			`      you will need approval.\n`+
 			`******************************\n`, ctx.ModuleName())
@@ -1575,12 +1576,12 @@
 			Text("; exit 38").
 			Text(")")
 
-		rule.Build(pctx, ctx, "metalavaCurrentApiCheck", "check current API")
+		rule.Build("metalavaCurrentApiCheck", "check current API")
 
 		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
 
 		// update API rule
-		rule = android.NewRuleBuilder()
+		rule = android.NewRuleBuilder(pctx, ctx)
 
 		rule.Command().Text("( true")
 
@@ -1601,7 +1602,7 @@
 			Text("; exit 38").
 			Text(")")
 
-		rule.Build(pctx, ctx, "metalavaCurrentApiUpdate", "update current API")
+		rule.Build("metalavaCurrentApiUpdate", "update current API")
 	}
 
 	if String(d.properties.Check_nullability_warnings) != "" {
@@ -1624,7 +1625,7 @@
 			`       and submitting the updated file as part of your change.`,
 			d.nullabilityWarningsFile, checkNullabilityWarnings)
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 
 		rule.Command().
 			Text("(").
@@ -1636,7 +1637,7 @@
 			Text("; exit 38").
 			Text(")")
 
-		rule.Build(pctx, ctx, "nullabilityWarningsCheck", "nullability warnings check")
+		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
 	}
 }
 
@@ -1721,7 +1722,7 @@
 
 	rule.Temporary(srcJarList)
 
-	rule.Command().BuiltTool(ctx, "zipsync").
+	rule.Command().BuiltTool("zipsync").
 		FlagWithArg("-d ", srcJarDir.String()).
 		FlagWithOutput("-l ", srcJarList).
 		FlagWithArg("-f ", `"*.java"`).
@@ -1748,8 +1749,6 @@
 
 	properties PrebuiltStubsSourcesProperties
 
-	// The source directories containing stubs source files.
-	srcDirs     android.Paths
 	stubsSrcJar android.ModuleOutPath
 }
 
@@ -1769,25 +1768,33 @@
 func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
 
-	p.srcDirs = android.PathsForModuleSrc(ctx, p.properties.Srcs)
+	if len(p.properties.Srcs) != 1 {
+		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
+		return
+	}
 
-	rule := android.NewRuleBuilder()
-	command := rule.Command().
-		BuiltTool(ctx, "soong_zip").
+	localSrcDir := p.properties.Srcs[0]
+	// Although PathForModuleSrc can return nil if either the path doesn't exist or
+	// the path components are invalid it won't in this case because no components
+	// are specified and the module directory must exist in order to get this far.
+	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
+
+	// Glob the contents of the directory just in case the directory does not exist.
+	srcGlob := localSrcDir + "/**/*"
+	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
 		Flag("-jar").
-		FlagWithOutput("-o ", p.stubsSrcJar)
-
-	for _, d := range p.srcDirs {
-		dir := d.String()
-		command.
-			FlagWithArg("-C ", dir).
-			FlagWithInput("-D ", d)
-	}
+		FlagWithOutput("-o ", p.stubsSrcJar).
+		FlagWithArg("-C ", srcDir.String()).
+		FlagWithRspFileInputList("-r ", srcPaths)
 
 	rule.Restat()
 
-	rule.Build(pctx, ctx, "zip src", "Create srcjar from prebuilt source")
+	rule.Build("zip src", "Create srcjar from prebuilt source")
 }
 
 func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
diff --git a/java/gen.go b/java/gen.go
index d50a665..5766a94 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -57,7 +57,7 @@
 
 		outDir := srcJarFile.ReplaceExtension(ctx, "tmp")
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 
 		rule.Command().Text("rm -rf").Flag(outDir.String())
 		rule.Command().Text("mkdir -p").Flag(outDir.String())
@@ -98,7 +98,7 @@
 			ruleDesc += " " + strconv.Itoa(i)
 		}
 
-		rule.Build(pctx, ctx, ruleName, ruleDesc)
+		rule.Build(ruleName, ruleDesc)
 	}
 
 	return srcJarFiles
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 63b801a..71f1e57 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -135,12 +135,12 @@
 	})
 	h.metadataCSVPath = metadataCSV
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
-		BuiltTool(ctx, "merge_csv").
+		BuiltTool("merge_csv").
 		FlagWithInput("--zip_input=", classesJar).
 		FlagWithOutput("--output=", indexCSV)
-	rule.Build(pctx, ctx, "merged-hiddenapi-index", "Merged Hidden API index")
+	rule.Build("merged-hiddenapi-index", "Merged Hidden API index")
 	h.indexCSVPath = indexCSV
 }
 
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 1f80e77..419dc34 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -177,19 +177,20 @@
 	for moduleList, pathList := range moduleListToPathList {
 		for i := range pathList {
 			if pathList[i] == nil {
-				pathList[i] = android.PathForOutput(ctx, "missing")
+				moduleName := (*moduleList)[i]
+				pathList[i] = android.PathForOutput(ctx, "missing/module", moduleName)
 				if ctx.Config().AllowMissingDependencies() {
-					missingDeps = append(missingDeps, (*moduleList)[i])
+					missingDeps = append(missingDeps, moduleName)
 				} else {
 					ctx.Errorf("failed to find dex jar path for module %q",
-						(*moduleList)[i])
+						moduleName)
 				}
 			}
 		}
 	}
 
 	// Singleton rule which applies hiddenapi on all boot class path dex files.
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	outputPath := hiddenAPISingletonPaths(ctx).stubFlags
 	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
@@ -208,7 +209,7 @@
 
 	commitChangeForRestat(rule, tempPath, outputPath)
 
-	rule.Build(pctx, ctx, "hiddenAPIStubFlagsFile", "hiddenapi stub flags")
+	rule.Build("hiddenAPIStubFlagsFile", "hiddenapi stub flags")
 }
 
 // flagsRule creates a rule to build hiddenapi-flags.csv out of flags.csv files generated for boot image modules and
@@ -236,7 +237,7 @@
 		ctx.Errorf("Failed to find combined-removed-dex.")
 	}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	outputPath := hiddenAPISingletonPaths(ctx).flags
 	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
@@ -255,18 +256,18 @@
 		FlagWithInput("--max-target-p ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-p.txt")).
 		FlagWithInput("--max-target-o ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").
+			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--blocked ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
 		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).
+			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--unsupported ", android.PathForSource(
 			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
 
 	commitChangeForRestat(rule, tempPath, outputPath)
 
-	rule.Build(pctx, ctx, "hiddenAPIFlagsFile", "hiddenapi flags")
+	rule.Build("hiddenAPIFlagsFile", "hiddenapi flags")
 
 	return outputPath
 }
@@ -274,14 +275,14 @@
 // emptyFlagsRule creates a rule to build an empty hiddenapi-flags.csv, which is needed by master-art-host builds that
 // have a partial manifest without frameworks/base but still need to build a boot image.
 func emptyFlagsRule(ctx android.SingletonContext) android.Path {
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	outputPath := hiddenAPISingletonPaths(ctx).flags
 
 	rule.Command().Text("rm").Flag("-f").Output(outputPath)
 	rule.Command().Text("touch").Output(outputPath)
 
-	rule.Build(pctx, ctx, "emptyHiddenAPIFlagsFile", "empty hiddenapi flags")
+	rule.Build("emptyHiddenAPIFlagsFile", "empty hiddenapi flags")
 
 	return outputPath
 }
@@ -299,16 +300,16 @@
 		}
 	})
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	outputPath := hiddenAPISingletonPaths(ctx).metadata
 
 	rule.Command().
-		BuiltTool(ctx, "merge_csv").
+		BuiltTool("merge_csv").
 		FlagWithOutput("--output=", outputPath).
 		Inputs(metadataCSV)
 
-	rule.Build(pctx, ctx, "hiddenAPIGreylistMetadataFile", "hiddenapi greylist metadata")
+	rule.Build("hiddenAPIGreylistMetadataFile", "hiddenapi greylist metadata")
 
 	return outputPath
 }
@@ -399,13 +400,13 @@
 		}
 	})
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
-		BuiltTool(ctx, "merge_csv").
+		BuiltTool("merge_csv").
 		FlagWithArg("--header=", "signature,file,startline,startcol,endline,endcol,properties").
 		FlagWithOutput("--output=", hiddenAPISingletonPaths(ctx).index).
 		Inputs(indexes)
-	rule.Build(pctx, ctx, "singleton-merged-hiddenapi-index", "Singleton merged Hidden API index")
+	rule.Build("singleton-merged-hiddenapi-index", "Singleton merged Hidden API index")
 
 	h.index = hiddenAPISingletonPaths(ctx).index
 }
diff --git a/java/java.go b/java/java.go
index d6dc148..d44719e 100644
--- a/java/java.go
+++ b/java/java.go
@@ -201,7 +201,10 @@
 	// List of modules to use as annotation processors
 	Plugins []string
 
-	// List of modules to export to libraries that directly depend on this library as annotation processors
+	// List of modules to export to libraries that directly depend on this library as annotation
+	// processors.  Note that if the plugins set generates_api: true this will disable the turbine
+	// optimization on modules that depend on this module, which will reduce parallelism and cause
+	// more recompilation.
 	Exported_plugins []string
 
 	// The number of Java source entries each Javac instance can process
@@ -248,6 +251,9 @@
 	Errorprone struct {
 		// List of javac flags that should only be used when running errorprone.
 		Javacflags []string
+
+		// List of java_plugin modules that provide extra errorprone checks.
+		Extra_check_modules []string
 	}
 
 	Proto struct {
@@ -417,7 +423,7 @@
 	overrideManifest android.OptionalPath
 
 	// map of SDK version to class loader context
-	exportedSdkLibs dexpreopt.ClassLoaderContextMap
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	// list of plugins that this java module is exporting
 	exportedPluginJars android.Paths
@@ -425,6 +431,9 @@
 	// list of plugins that this java module is exporting
 	exportedPluginClasses []string
 
+	// if true, the exported plugins generate API and require disabling turbine.
+	exportedDisableTurbine bool
+
 	// list of source files, collected from srcFiles with unique java and all kt files,
 	// will be used by android.IDEInfo struct
 	expandIDEInfoCompiledSrcs []string
@@ -447,8 +456,6 @@
 	// list of the xref extraction files
 	kytheFiles android.Paths
 
-	distFiles android.TaggedDistFiles
-
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
 
@@ -477,6 +484,8 @@
 	switch tag {
 	case "":
 		return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil
+	case android.DefaultDistTag:
+		return android.Paths{j.outputFile}, nil
 	case ".jar":
 		return android.Paths{j.implementationAndResourcesJar}, nil
 	case ".proguard_map":
@@ -509,8 +518,8 @@
 	ImplementationJars() android.Paths
 	ResourceJars() android.Paths
 	AidlIncludeDirs() android.Paths
-	ExportedSdkLibs() dexpreopt.ClassLoaderContextMap
-	ExportedPlugins() (android.Paths, []string)
+	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
+	ExportedPlugins() (android.Paths, []string, bool)
 	SrcJarArgs() ([]string, android.Paths)
 	BaseModuleName() string
 	JacocoReportClassesFile() android.Path
@@ -547,6 +556,14 @@
 	name string
 }
 
+// installDependencyTag is a dependency tag that is annotated to cause the installed files of the
+// dependency to be installed when the parent module is installed.
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 type usesLibraryDependencyTag struct {
 	dependencyTag
 	sdkVersion int // SDK version in which the library appared as a standalone library.
@@ -569,6 +586,7 @@
 	libTag                = dependencyTag{name: "javalib"}
 	java9LibTag           = dependencyTag{name: "java9lib"}
 	pluginTag             = dependencyTag{name: "plugin"}
+	errorpronePluginTag   = dependencyTag{name: "errorprone-plugin"}
 	exportedPluginTag     = dependencyTag{name: "exported-plugin"}
 	bootClasspathTag      = dependencyTag{name: "bootclasspath"}
 	systemModulesTag      = dependencyTag{name: "system modules"}
@@ -580,6 +598,8 @@
 	instrumentationForTag = dependencyTag{name: "instrumentation_for"}
 	extraLintCheckTag     = dependencyTag{name: "extra-lint-check"}
 	jniLibTag             = dependencyTag{name: "jnilib"}
+	jniInstallTag         = installDependencyTag{name: "jni install"}
+	binaryInstallTag      = installDependencyTag{name: "binary install"}
 	usesLibTag            = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
 	usesLibCompat28Tag    = makeUsesLibraryDependencyTag(28)
 	usesLibCompat29Tag    = makeUsesLibraryDependencyTag(29)
@@ -752,6 +772,37 @@
 	libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...)
 	ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...)
 
+	if ctx.DeviceConfig().VndkVersion() != "" && ctx.Config().EnforceInterPartitionJavaSdkLibrary() {
+		// Require java_sdk_library at inter-partition java dependency to ensure stable
+		// interface between partitions. If inter-partition java_library dependency is detected,
+		// raise build error because java_library doesn't have a stable interface.
+		//
+		// Inputs:
+		//    PRODUCT_ENFORCE_INTER_PARTITION_JAVA_SDK_LIBRARY
+		//      if true, enable enforcement
+		//    PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST
+		//      exception list of java_library names to allow inter-partition dependency
+		for idx, lib := range j.properties.Libs {
+			if libDeps[idx] == nil {
+				continue
+			}
+
+			if _, ok := syspropPublicStubs[lib]; ok {
+				continue
+			}
+
+			if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok {
+				// java_sdk_library is always allowed at inter-partition dependency.
+				// So, skip check.
+				if _, ok := javaDep.(*SdkLibrary); ok {
+					continue
+				}
+
+				j.checkPartitionsForJavaDependency(ctx, "libs", javaDep)
+			}
+		}
+	}
+
 	// For library dependencies that are component libraries (like stubs), add the implementation
 	// as a dependency (dexpreopt needs to be against the implementation library, not stubs).
 	for _, dep := range libDeps {
@@ -765,6 +816,7 @@
 	}
 
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), pluginTag, j.properties.Plugins...)
+	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), errorpronePluginTag, j.properties.Errorprone.Extra_check_modules...)
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), exportedPluginTag, j.properties.Exported_plugins...)
 
 	android.ProtoDeps(ctx, &j.protoProperties)
@@ -852,21 +904,22 @@
 }
 
 type deps struct {
-	classpath          classpath
-	java9Classpath     classpath
-	bootClasspath      classpath
-	processorPath      classpath
-	processorClasses   []string
-	staticJars         android.Paths
-	staticHeaderJars   android.Paths
-	staticResourceJars android.Paths
-	aidlIncludeDirs    android.Paths
-	srcs               android.Paths
-	srcJars            android.Paths
-	systemModules      *systemModules
-	aidlPreprocess     android.OptionalPath
-	kotlinStdlib       android.Paths
-	kotlinAnnotations  android.Paths
+	classpath               classpath
+	java9Classpath          classpath
+	bootClasspath           classpath
+	processorPath           classpath
+	errorProneProcessorPath classpath
+	processorClasses        []string
+	staticJars              android.Paths
+	staticHeaderJars        android.Paths
+	staticResourceJars      android.Paths
+	aidlIncludeDirs         android.Paths
+	srcs                    android.Paths
+	srcJars                 android.Paths
+	systemModules           *systemModules
+	aidlPreprocess          android.OptionalPath
+	kotlinStdlib            android.Paths
+	kotlinAnnotations       android.Paths
 
 	disableTurbine bool
 }
@@ -952,7 +1005,9 @@
 		return
 	}
 	otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to))
-	commonMessage := "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source."
+	commonMessage := " In order to fix this, consider adjusting sdk_version: OR platform_apis: " +
+		"property of the source or target module so that target module is built with the same " +
+		"or smaller API set when compared to the source."
 
 	switch myLinkType {
 	case javaCore:
@@ -1026,8 +1081,7 @@
 			switch tag {
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
-				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
+				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
 					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
@@ -1038,11 +1092,11 @@
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars()...)
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
-				pluginJars, pluginClasses := dep.ExportedPlugins()
+				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
+				deps.disableTurbine = deps.disableTurbine || disableTurbine
 			case java9LibTag:
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars()...)
 			case staticLibTag:
@@ -1050,11 +1104,13 @@
 				deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...)
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
 				deps.staticResourceJars = append(deps.staticResourceJars, dep.ResourceJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
-				pluginJars, pluginClasses := dep.ExportedPlugins()
+				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
+				// Turbine doesn't run annotation processors, so any module that uses an
+				// annotation processor that generates API is incompatible with the turbine
+				// optimization.
+				deps.disableTurbine = deps.disableTurbine || disableTurbine
 			case pluginTag:
 				if plugin, ok := dep.(*Plugin); ok {
 					if plugin.pluginProperties.Processor_class != nil {
@@ -1062,19 +1118,29 @@
 					} else {
 						addPlugins(&deps, plugin.ImplementationAndResourcesJars())
 					}
+					// Turbine doesn't run annotation processors, so any module that uses an
+					// annotation processor that generates API is incompatible with the turbine
+					// optimization.
 					deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api)
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
+			case errorpronePluginTag:
+				if plugin, ok := dep.(*Plugin); ok {
+					deps.errorProneProcessorPath = append(deps.errorProneProcessorPath, plugin.ImplementationAndResourcesJars()...)
+				} else {
+					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
+				}
 			case exportedPluginTag:
 				if plugin, ok := dep.(*Plugin); ok {
-					if plugin.pluginProperties.Generates_api != nil && *plugin.pluginProperties.Generates_api {
-						ctx.PropertyErrorf("exported_plugins", "Cannot export plugins with generates_api = true, found %v", otherName)
-					}
 					j.exportedPluginJars = append(j.exportedPluginJars, plugin.ImplementationAndResourcesJars()...)
 					if plugin.pluginProperties.Processor_class != nil {
 						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.pluginProperties.Processor_class)
 					}
+					// Turbine doesn't run annotation processors, so any module that uses an
+					// annotation processor that generates API is incompatible with the turbine
+					// optimization.
+					j.exportedDisableTurbine = Bool(plugin.pluginProperties.Generates_api)
 				} else {
 					ctx.PropertyErrorf("exported_plugins", "%q is not a java_plugin module", otherName)
 				}
@@ -1112,6 +1178,9 @@
 				deps.systemModules = &systemModules{outputDir, outputDeps}
 			}
 		}
+
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, tag, otherName, j.classLoaderContexts)
 	})
 
 	return deps
@@ -1189,7 +1258,7 @@
 	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
 
 	if ctx.Config().RunErrorProne() {
-		if config.ErrorProneClasspath == nil {
+		if config.ErrorProneClasspath == nil && ctx.Config().TestProductVariables == nil {
 			ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
 		}
 
@@ -1209,6 +1278,7 @@
 	flags.classpath = append(flags.classpath, deps.classpath...)
 	flags.java9Classpath = append(flags.java9Classpath, deps.java9Classpath...)
 	flags.processorPath = append(flags.processorPath, deps.processorPath...)
+	flags.errorProneProcessorPath = append(flags.errorProneProcessorPath, deps.errorProneProcessorPath...)
 
 	flags.processors = append(flags.processors, deps.processorClasses...)
 	flags.processors = android.FirstUniqueStrings(flags.processors)
@@ -1375,6 +1445,9 @@
 		kotlincFlags := j.properties.Kotlincflags
 		CheckKotlincFlags(ctx, kotlincFlags)
 
+		// Dogfood the JVM_IR backend.
+		kotlincFlags = append(kotlincFlags, "-Xuse-ir")
+
 		// If there are kotlin files, compile them first but pass all the kotlin and java files
 		// kotlinc will use the java files to resolve types referenced by the kotlin files, but
 		// won't emit any classes for them.
@@ -1903,12 +1976,15 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Module) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
-	return j.exportedSdkLibs
+func (j *Module) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
-func (j *Module) ExportedPlugins() (android.Paths, []string) {
-	return j.exportedPluginJars, j.exportedPluginClasses
+// ExportedPlugins returns the list of jars needed to run the exported plugins, the list of
+// classes for the plugins, and a boolean for whether turbine needs to be disabled due to plugins
+// that generate APIs.
+func (j *Module) ExportedPlugins() (android.Paths, []string, bool) {
+	return j.exportedPluginJars, j.exportedPluginClasses, j.exportedDisableTurbine
 }
 
 func (j *Module) SrcJarArgs() ([]string, android.Paths) {
@@ -2042,7 +2118,7 @@
 		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
 	}
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -2062,15 +2138,13 @@
 	// 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.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		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.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
+		j.classLoaderContexts.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
 	}
-
-	j.distFiles = j.GenerateTaggedDistFiles(ctx)
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -2226,6 +2300,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 {
@@ -2322,7 +2399,7 @@
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
-		j.testProperties.Test_suites, j.testProperties.Auto_gen_config)
+		j.testProperties.Test_suites, j.testProperties.Auto_gen_config, j.testProperties.Test_options.Unit_test)
 
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
@@ -2341,7 +2418,7 @@
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.prebuiltTestProperties.Test_config, nil,
-		j.prebuiltTestProperties.Test_suites, nil)
+		j.prebuiltTestProperties.Test_suites, nil, nil)
 
 	j.Import.GenerateAndroidBuildActions(ctx)
 }
@@ -2550,9 +2627,12 @@
 	if ctx.Arch().ArchType == android.Common {
 		j.deps(ctx)
 	} else {
-		// This dependency ensures the host installation rules will install the jni libraries
-		// when the wrapper is installed.
-		ctx.AddVariationDependencies(nil, jniLibTag, j.binaryProperties.Jni_libs...)
+		// These dependencies ensure the host installation rules will install the jar file and
+		// the jni libraries when the wrapper is installed.
+		ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
+		ctx.AddVariationDependencies(
+			[]blueprint.Variation{{Mutator: "arch", Variation: android.CommonArch.String()}},
+			binaryInstallTag, ctx.ModuleName())
 	}
 }
 
@@ -2646,7 +2726,7 @@
 	dexJarFile android.Path
 
 	combinedClasspathFile android.Path
-	exportedSdkLibs       dexpreopt.ClassLoaderContextMap
+	classLoaderContexts   dexpreopt.ClassLoaderContextMap
 	exportAidlIncludeDirs android.Paths
 
 	hideApexVariantFromMake bool
@@ -2721,7 +2801,7 @@
 		TransformJetifier(ctx, outputFile, inputFile)
 	}
 	j.combinedClasspathFile = outputFile
-	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
 
@@ -2734,8 +2814,6 @@
 			switch tag {
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2743,11 +2821,12 @@
 			switch tag {
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
-				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.AddContext(ctx, otherName,
-					dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.classLoaderContexts.AddContext(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
+
+		// Merge dep's CLC after processing the dep itself (which may add its own <uses-library>).
+		maybeAddCLCFromDep(module, tag, otherName, j.classLoaderContexts)
 	})
 
 	var installFile android.Path
@@ -2760,7 +2839,7 @@
 	// 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.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		outputFile, installFile)
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
@@ -2843,12 +2922,12 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Import) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
-	return j.exportedSdkLibs
+func (j *Import) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
-func (j *Import) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (j *Import) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (j *Import) SrcJarArgs() ([]string, android.Paths) {
@@ -3003,21 +3082,21 @@
 	dexOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".jar")
 
 	if j.dexpreopter.uncompressedDex {
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 
 		temporary := android.PathForModuleOut(ctx, ctx.ModuleName()+".jar.unaligned")
 		rule.Temporary(temporary)
 
 		// use zip2zip to uncompress classes*.dex files
 		rule.Command().
-			BuiltTool(ctx, "zip2zip").
+			BuiltTool("zip2zip").
 			FlagWithInput("-i ", inputJar).
 			FlagWithOutput("-o ", temporary).
 			FlagWithArg("-0 ", "'classes*.dex'")
 
 		// use zipalign to align uncompressed classes*.dex files
 		rule.Command().
-			BuiltTool(ctx, "zipalign").
+			BuiltTool("zipalign").
 			Flag("-f").
 			Text("4").
 			Input(temporary).
@@ -3025,7 +3104,7 @@
 
 		rule.DeleteTemporaryFiles()
 
-		rule.Build(pctx, ctx, "uncompress_dex", "uncompress dex")
+		rule.Build("uncompress_dex", "uncompress dex")
 	} else {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
@@ -3168,3 +3247,31 @@
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
 var inList = android.InList
+
+// Add class loader context of a given dependency to the given class loader context, provided that
+// all the necessary conditions are met.
+func maybeAddCLCFromDep(depModule android.Module, depTag blueprint.DependencyTag,
+	depName string, clcMap dexpreopt.ClassLoaderContextMap) {
+
+	if dep, ok := depModule.(Dependency); ok {
+		if depTag == libTag {
+			// Ok, propagate <uses-library> through non-static library dependencies.
+		} else if depTag == staticLibTag {
+			// Propagate <uses-library> through static library dependencies, unless it is a
+			// component library (such as stubs). Component libraries have a dependency on their
+			// SDK library, which should not be pulled just because of a static component library.
+			if comp, isComp := depModule.(SdkLibraryComponentDependency); isComp {
+				if compName := comp.OptionalImplicitSdkLibrary(); compName != nil {
+					dep = nil
+				}
+			}
+		} else {
+			// Don't propagate <uses-library> for other dependency tags.
+			dep = nil
+		}
+
+		if dep != nil {
+			clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
+		}
+	}
+}
diff --git a/java/java_test.go b/java/java_test.go
index 4594b81..f7cf03f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -29,7 +30,6 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
-	"android/soong/genrule"
 	"android/soong/python"
 )
 
@@ -80,7 +80,6 @@
 	RegisterSystemModulesBuildComponents(ctx)
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
 	ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
 	RegisterDocsBuildComponents(ctx)
 	RegisterStubsBuildComponents(ctx)
@@ -92,8 +91,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)
 
@@ -202,7 +201,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -246,7 +245,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -314,8 +313,9 @@
 
 func TestExportedPlugins(t *testing.T) {
 	type Result struct {
-		library    string
-		processors string
+		library        string
+		processors     string
+		disableTurbine bool
 	}
 	var tests = []struct {
 		name    string
@@ -374,6 +374,18 @@
 				{library: "foo", processors: "-processor com.android.TestPlugin,com.android.TestPlugin2"},
 			},
 		},
+		{
+			name: "Exports plugin to with generates_api to dependee",
+			extra: `
+				java_library{name: "exports", exported_plugins: ["plugin_generates_api"]}
+				java_library{name: "foo", srcs: ["a.java"], libs: ["exports"]}
+				java_library{name: "bar", srcs: ["a.java"], static_libs: ["exports"]}
+			`,
+			results: []Result{
+				{library: "foo", processors: "-processor com.android.TestPlugin", disableTurbine: true},
+				{library: "bar", processors: "-processor com.android.TestPlugin", disableTurbine: true},
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -383,6 +395,11 @@
 					name: "plugin",
 					processor_class: "com.android.TestPlugin",
 				}
+				java_plugin {
+					name: "plugin_generates_api",
+					generates_api: true,
+					processor_class: "com.android.TestPlugin",
+				}
 			`+test.extra)
 
 			for _, want := range test.results {
@@ -390,6 +407,11 @@
 				if javac.Args["processor"] != want.processors {
 					t.Errorf("For library %v, expected %v, found %v", want.library, want.processors, javac.Args["processor"])
 				}
+				turbine := ctx.ModuleForTests(want.library, "android_common").MaybeRule("turbine")
+				disableTurbine := turbine.BuildParams.Rule == nil
+				if disableTurbine != want.disableTurbine {
+					t.Errorf("For library %v, expected disableTurbine %v, found %v", want.library, want.disableTurbine, disableTurbine)
+				}
 			}
 		})
 	}
@@ -622,6 +644,35 @@
 	}
 }
 
+func TestPrebuiltStubsSources(t *testing.T) {
+	test := func(t *testing.T, sourcesPath string, expectedInputs []string) {
+		ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
+prebuilt_stubs_sources {
+  name: "stubs-source",
+	srcs: ["%s"],
+}`, sourcesPath), map[string][]byte{
+			"stubs/sources/pkg/A.java": nil,
+			"stubs/sources/pkg/B.java": nil,
+		})
+
+		zipSrc := ctx.ModuleForTests("stubs-source", "android_common").Rule("zip_src")
+		if expected, actual := expectedInputs, zipSrc.Inputs.Strings(); !reflect.DeepEqual(expected, actual) {
+			t.Errorf("mismatch of inputs to soong_zip: expected %q, actual %q", expected, actual)
+		}
+	}
+
+	t.Run("empty/missing directory", func(t *testing.T) {
+		test(t, "empty-directory", []string{})
+	})
+
+	t.Run("non-empty set of sources", func(t *testing.T) {
+		test(t, "stubs/sources", []string{
+			"stubs/sources/pkg/A.java",
+			"stubs/sources/pkg/B.java",
+		})
+	})
+}
+
 func TestJavaSdkLibraryImport(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
@@ -754,6 +805,165 @@
 	})
 }
 
+func TestJavaSdkLibraryEnforce(t *testing.T) {
+	partitionToBpOption := func(partition string) string {
+		switch partition {
+		case "system":
+			return ""
+		case "vendor":
+			return "soc_specific: true,"
+		case "product":
+			return "product_specific: true,"
+		default:
+			panic("Invalid partition group name: " + partition)
+		}
+	}
+
+	type testConfigInfo struct {
+		libraryType                string
+		fromPartition              string
+		toPartition                string
+		enforceVendorInterface     bool
+		enforceProductInterface    bool
+		enforceJavaSdkLibraryCheck bool
+		allowList                  []string
+	}
+
+	createTestConfig := func(info testConfigInfo) android.Config {
+		bpFileTemplate := `
+			java_library {
+				name: "foo",
+				srcs: ["foo.java"],
+				libs: ["bar"],
+				sdk_version: "current",
+				%s
+			}
+
+			%s {
+				name: "bar",
+				srcs: ["bar.java"],
+				sdk_version: "current",
+				%s
+			}
+		`
+
+		bpFile := fmt.Sprintf(bpFileTemplate,
+			partitionToBpOption(info.fromPartition),
+			info.libraryType,
+			partitionToBpOption(info.toPartition))
+
+		config := testConfig(nil, bpFile, nil)
+		configVariables := config.TestProductVariables
+
+		configVariables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+		if info.enforceVendorInterface {
+			configVariables.DeviceVndkVersion = proptools.StringPtr("current")
+		}
+		configVariables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+		configVariables.InterPartitionJavaLibraryAllowList = info.allowList
+
+		return config
+	}
+
+	isValidDependency := func(configInfo testConfigInfo) bool {
+		if configInfo.enforceVendorInterface == false {
+			return true
+		}
+
+		if configInfo.enforceJavaSdkLibraryCheck == false {
+			return true
+		}
+
+		if inList("bar", configInfo.allowList) {
+			return true
+		}
+
+		if configInfo.libraryType == "java_library" {
+			if configInfo.fromPartition != configInfo.toPartition {
+				if !configInfo.enforceProductInterface &&
+					((configInfo.fromPartition == "system" && configInfo.toPartition == "product") ||
+						(configInfo.fromPartition == "product" && configInfo.toPartition == "system")) {
+					return true
+				}
+				return false
+			}
+		}
+
+		return true
+	}
+
+	errorMessage := "is not allowed across the partitions"
+
+	allPartitionCombinations := func() [][2]string {
+		var result [][2]string
+		partitions := []string{"system", "vendor", "product"}
+
+		for _, fromPartition := range partitions {
+			for _, toPartition := range partitions {
+				result = append(result, [2]string{fromPartition, toPartition})
+			}
+		}
+
+		return result
+	}
+
+	allFlagCombinations := func() [][3]bool {
+		var result [][3]bool
+		flagValues := [2]bool{false, true}
+
+		for _, vendorInterface := range flagValues {
+			for _, productInterface := range flagValues {
+				for _, enableEnforce := range flagValues {
+					result = append(result, [3]bool{vendorInterface, productInterface, enableEnforce})
+				}
+			}
+		}
+
+		return result
+	}
+
+	for _, libraryType := range []string{"java_library", "java_sdk_library"} {
+		for _, partitionValues := range allPartitionCombinations() {
+			for _, flagValues := range allFlagCombinations() {
+				testInfo := testConfigInfo{
+					libraryType:                libraryType,
+					fromPartition:              partitionValues[0],
+					toPartition:                partitionValues[1],
+					enforceVendorInterface:     flagValues[0],
+					enforceProductInterface:    flagValues[1],
+					enforceJavaSdkLibraryCheck: flagValues[2],
+				}
+
+				if isValidDependency(testInfo) {
+					testJavaWithConfig(t, createTestConfig(testInfo))
+				} else {
+					testJavaErrorWithConfig(t, errorMessage, createTestConfig(testInfo))
+				}
+			}
+		}
+	}
+
+	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"bar"},
+	}))
+
+	testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"foo"},
+	}))
+}
+
 func TestDefaults(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_defaults {
@@ -1379,8 +1589,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()) {
@@ -1593,7 +1803,7 @@
 	// 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 := quxLib.ExportedSdkLibs().UsesLibs()
+		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/kotlin.go b/java/kotlin.go
index e8c030a..8067ad5 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -63,9 +63,9 @@
 		// we can't use the rsp file because it is already being used for srcs.
 		// Insert a second rule to write out the list of resources to a file.
 		commonSrcsList := android.PathForModuleOut(ctx, "kotlinc_common_srcs.list")
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().Text("cp").FlagWithRspFileInputList("", commonSrcFiles).Output(commonSrcsList)
-		rule.Build(pctx, ctx, "kotlin_common_srcs_list", "kotlin common_srcs list")
+		rule.Build("kotlin_common_srcs_list", "kotlin common_srcs list")
 		return android.OptionalPathForPath(commonSrcsList)
 	}
 	return android.OptionalPath{}
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 60ca1c4..77ef294 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -84,11 +84,14 @@
 }
 
 func TestKapt(t *testing.T) {
-	ctx, _ := testJava(t, `
+	bp := `
 		java_library {
 			name: "foo",
 			srcs: ["a.java", "b.kt"],
 			plugins: ["bar", "baz"],
+			errorprone: {
+				extra_check_modules: ["my_check"],
+			},
 		}
 
 		java_plugin {
@@ -102,64 +105,119 @@
 			processor_class: "com.baz",
 			srcs: ["b.java"],
 		}
-		`)
 
-	buildOS := android.BuildOs.String()
+		java_plugin {
+			name: "my_check",
+			srcs: ["b.java"],
+		}
+	`
+	t.Run("", func(t *testing.T) {
+		ctx, _ := testJava(t, bp)
 
-	kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
-	kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+		buildOS := android.BuildOs.String()
 
-	bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
-	baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
+		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+		kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+		javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 
-	// Test that the kotlin and java sources are passed to kapt and kotlinc
-	if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
-		t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
-	}
-	if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
-		t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
-	}
+		bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
+		baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
 
-	// Test that only the java sources are passed to javac
-	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
-		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
-	}
+		// Test that the kotlin and java sources are passed to kapt and kotlinc
+		if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
+		}
+		if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
+		}
 
-	// Test that the kapt srcjar is a dependency of kotlinc and javac rules
-	if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
-		t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
-	}
-	if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
-		t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
-	}
+		// Test that only the java sources are passed to javac
+		if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
+			t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+		}
 
-	// Test that the kapt srcjar is extracted by the kotlinc and javac rules
-	if kotlinc.Args["srcJars"] != kapt.Output.String() {
-		t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
-	}
-	if javac.Args["srcJars"] != kapt.Output.String() {
-		t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
-	}
+		// Test that the kapt srcjar is a dependency of kotlinc and javac rules
+		if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
+			t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
+		}
+		if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
+			t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
+		}
 
-	// Test that the processors are passed to kapt
-	expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
-		" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
-	if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
-		t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
-	}
-	expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
-	if kapt.Args["kaptProcessor"] != expectedProcessor {
-		t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
-	}
+		// Test that the kapt srcjar is extracted by the kotlinc and javac rules
+		if kotlinc.Args["srcJars"] != kapt.Output.String() {
+			t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		}
+		if javac.Args["srcJars"] != kapt.Output.String() {
+			t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		}
 
-	// Test that the processors are not passed to javac
-	if javac.Args["processorPath"] != "" {
-		t.Errorf("expected processorPath '', got %q", javac.Args["processorPath"])
-	}
-	if javac.Args["processor"] != "-proc:none" {
-		t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
-	}
+		// Test that the processors are passed to kapt
+		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
+			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
+		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		}
+		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
+		if kapt.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		}
+
+		// Test that the processors are not passed to javac
+		if javac.Args["processorpath"] != "" {
+			t.Errorf("expected processorPath '', got %q", javac.Args["processorpath"])
+		}
+		if javac.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
+		}
+	})
+
+	t.Run("errorprone", func(t *testing.T) {
+		env := map[string]string{
+			"RUN_ERROR_PRONE": "true",
+		}
+		config := testConfig(env, bp, nil)
+		ctx, _ := testJavaWithConfig(t, config)
+
+		buildOS := android.BuildOs.String()
+
+		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+		//kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+		javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+		errorprone := ctx.ModuleForTests("foo", "android_common").Description("errorprone")
+
+		bar := ctx.ModuleForTests("bar", buildOS+"_common").Description("javac").Output.String()
+		baz := ctx.ModuleForTests("baz", buildOS+"_common").Description("javac").Output.String()
+		myCheck := ctx.ModuleForTests("my_check", buildOS+"_common").Description("javac").Output.String()
+
+		// Test that the errorprone plugins are not passed to kapt
+		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
+			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
+		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		}
+		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
+		if kapt.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		}
+
+		// Test that the errorprone plugins are not passed to javac
+		if javac.Args["processorpath"] != "" {
+			t.Errorf("expected processorPath '', got %q", javac.Args["processorpath"])
+		}
+		if javac.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
+		}
+
+		// Test that the errorprone plugins are passed to errorprone
+		expectedProcessorPath = "-processorpath " + myCheck
+		if errorprone.Args["processorpath"] != expectedProcessorPath {
+			t.Errorf("expected processorpath %q, got %q", expectedProcessorPath, errorprone.Args["processorpath"])
+		}
+		if errorprone.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", errorprone.Args["processor"])
+		}
+	})
 }
 
 func TestKaptEncodeFlags(t *testing.T) {
diff --git a/java/lint.go b/java/lint.go
index 3df582f..cd2a904 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -176,9 +176,9 @@
 		// we can't use the rsp file because it is already being used for srcs.
 		// Insert a second rule to write out the list of resources to a file.
 		resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
-		resListRule := android.NewRuleBuilder()
+		resListRule := android.NewRuleBuilder(pctx, ctx)
 		resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
-		resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
+		resListRule.Build("lint_resources_list", "lint resources list")
 		deps = append(deps, l.resources...)
 	}
 
@@ -192,7 +192,7 @@
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
 
 	cmd := rule.Command().
-		BuiltTool(ctx, "lint-project-xml").
+		BuiltTool("lint-project-xml").
 		FlagWithOutput("--project_out ", projectXMLPath).
 		FlagWithOutput("--config_out ", configXMLPath).
 		FlagWithArg("--name ", ctx.ModuleName())
@@ -284,7 +284,7 @@
 		}
 	}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	if l.manifest == nil {
 		manifest := l.generateManifest(ctx, rule)
@@ -347,7 +347,7 @@
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 
-	rule.Build(pctx, ctx, "lint", "lint")
+	rule.Build("lint", "lint")
 
 	l.outputs = lintOutputs{
 		html: html,
@@ -447,7 +447,7 @@
 	var outputs []*lintOutputs
 	var dirs []string
 	ctx.VisitAllModules(func(m android.Module) {
-		if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
+		if ctx.Config().KatiEnabled() && !m.ExportedToMake() {
 			return
 		}
 
@@ -511,12 +511,12 @@
 		return paths[i].String() < paths[j].String()
 	})
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
-	rule.Command().BuiltTool(ctx, "soong_zip").
+	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputPath).
 		FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
 		FlagWithRspFileInputList("-r ", paths)
 
-	rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
+	rule.Build(outputPath.Base(), outputPath.Base())
 }
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index cb8e684..9bc821d 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -86,15 +86,15 @@
 		return
 	}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	outputPath := platformCompatConfigPath(ctx)
 
 	rule.Command().
-		BuiltTool(ctx, "process-compat-config").
+		BuiltTool("process-compat-config").
 		FlagForEachInput("--xml ", compatConfigMetadata).
 		FlagWithOutput("--merged-config ", outputPath)
 
-	rule.Build(pctx, ctx, "merged-compat-config", "Merge compat config")
+	rule.Build("merged-compat-config", "Merge compat config")
 
 	p.metadata = outputPath
 }
@@ -106,7 +106,7 @@
 }
 
 func (p *platformCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	configFileName := p.Name() + ".xml"
 	metadataFileName := p.Name() + "_meta.xml"
@@ -115,13 +115,13 @@
 	path := android.PathForModuleSrc(ctx, String(p.properties.Src))
 
 	rule.Command().
-		BuiltTool(ctx, "process-compat-config").
+		BuiltTool("process-compat-config").
 		FlagWithInput("--jar ", path).
 		FlagWithOutput("--device-config ", p.configFile).
 		FlagWithOutput("--merged-config ", p.metadataFile)
 
 	p.installDirPath = android.PathForModuleInstall(ctx, "etc", "compatconfig")
-	rule.Build(pctx, ctx, configFileName, "Extract compat/compat_config.xml and install it")
+	rule.Build(configFileName, "Extract compat/compat_config.xml and install it")
 
 }
 
diff --git a/java/proto.go b/java/proto.go
index 4d735eb..cc9abbe 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -34,7 +34,7 @@
 
 		outDir := srcJarFile.ReplaceExtension(ctx, "tmp")
 
-		rule := android.NewRuleBuilder()
+		rule := android.NewRuleBuilder(pctx, ctx)
 
 		rule.Command().Text("rm -rf").Flag(outDir.String())
 		rule.Command().Text("mkdir -p").Flag(outDir.String())
@@ -42,13 +42,13 @@
 		for _, protoFile := range shard {
 			depFile := srcJarFile.InSameDir(ctx, protoFile.String()+".d")
 			rule.Command().Text("mkdir -p").Flag(filepath.Dir(depFile.String()))
-			android.ProtoRule(ctx, rule, protoFile, flags, flags.Deps, outDir, depFile, nil)
+			android.ProtoRule(rule, protoFile, flags, flags.Deps, outDir, depFile, nil)
 		}
 
 		// Proto generated java files have an unknown package name in the path, so package the entire output directory
 		// into a srcjar.
 		rule.Command().
-			BuiltTool(ctx, "soong_zip").
+			BuiltTool("soong_zip").
 			Flag("-jar").
 			Flag("-write_if_changed").
 			FlagWithOutput("-o ", srcJarFile).
@@ -66,7 +66,7 @@
 			ruleDesc += " " + strconv.Itoa(i)
 		}
 
-		rule.Build(pctx, ctx, ruleName, ruleDesc)
+		rule.Build(ruleName, ruleDesc)
 	}
 
 	return srcJarFiles
diff --git a/java/robolectric.go b/java/robolectric.go
index 04fc117..419efda 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -199,7 +199,7 @@
 
 func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath,
 	instrumentedApp *AndroidApp) {
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	manifest := instrumentedApp.mergedManifestFile
 	resourceApk := instrumentedApp.outputFile
@@ -213,11 +213,11 @@
 		Implicit(manifest).
 		Implicit(resourceApk)
 
-	rule.Build(pctx, ctx, "generate_test_config", "generate test_config.properties")
+	rule.Build("generate_test_config", "generate test_config.properties")
 }
 
 func generateSameDirRoboTestConfigJar(ctx android.ModuleContext, outputFile android.ModuleOutPath) {
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	outputDir := outputFile.InSameDir(ctx)
 	configFile := outputDir.Join(ctx, "com/android/tools/test_config.properties")
@@ -230,12 +230,12 @@
 		Textf(`echo "android_resource_apk=%s.apk"`, ctx.ModuleName()).
 		Text(") >>").Output(configFile)
 	rule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		FlagWithArg("-C ", outputDir.String()).
 		FlagWithInput("-f ", configFile).
 		FlagWithOutput("-o ", outputFile)
 
-	rule.Build(pctx, ctx, "generate_test_config_samedir", "generate test_config.properties")
+	rule.Build("generate_test_config_samedir", "generate test_config.properties")
 }
 
 func (r *robolectricTest) generateRoboSrcJar(ctx android.ModuleContext, outputFile android.WritablePath,
@@ -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.go b/java/sdk.go
index 971791f..32a4b5a 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -544,7 +544,7 @@
 
 	commitChangeForRestat(rule, tempPath, combinedAidl)
 
-	rule.Build(pctx, ctx, "framework_aidl", "generate framework.aidl")
+	rule.Build("framework_aidl", "generate framework.aidl")
 }
 
 // Creates a version of framework.aidl for the non-updatable part of the platform.
@@ -558,7 +558,7 @@
 
 	commitChangeForRestat(rule, tempPath, combinedAidl)
 
-	rule.Build(pctx, ctx, "framework_non_updatable_aidl", "generate framework_non_updatable.aidl")
+	rule.Build("framework_non_updatable_aidl", "generate framework_non_updatable.aidl")
 }
 
 func createFrameworkAidl(stubsModules []string, path android.OutputPath, ctx android.SingletonContext) *android.RuleBuilder {
@@ -586,7 +586,7 @@
 		}
 	}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.MissingDeps(missingDeps)
 
 	var aidls android.Paths
@@ -597,7 +597,7 @@
 			rule.Command().
 				Text("rm -f").Output(aidl)
 			rule.Command().
-				BuiltTool(ctx, "sdkparcelables").
+				BuiltTool("sdkparcelables").
 				Input(jar).
 				Output(aidl)
 
@@ -632,7 +632,7 @@
 func createAPIFingerprint(ctx android.SingletonContext) {
 	out := ApiFingerprintPath(ctx)
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Text("rm -f").Output(out)
@@ -659,7 +659,7 @@
 			Output(out)
 	}
 
-	rule.Build(pctx, ctx, "api_fingerprint", "generate api_fingerprint.txt")
+	rule.Build("api_fingerprint", "generate api_fingerprint.txt")
 }
 
 func ApiFingerprintPath(ctx android.PathContext) android.OutputPath {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 21c03cd..4e33d74 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -435,8 +435,23 @@
 	// If set to true, the path of dist files is apistubs/core. Defaults to false.
 	Core_lib *bool
 
-	// don't create dist rules.
-	No_dist *bool `blueprint:"mutated"`
+	// If set to true then don't create dist rules.
+	No_dist *bool
+
+	// The stem for the artifacts that are copied to the dist, if not specified
+	// then defaults to the base module name.
+	//
+	// For each scope the following artifacts are copied to the apistubs/<scope>
+	// directory in the dist.
+	// * stubs impl jar -> <dist-stem>.jar
+	// * API specification file -> api/<dist-stem>.txt
+	// * Removed API specification file -> api/<dist-stem>-removed.txt
+	//
+	// Also used to construct the name of the filegroup (created by prebuilt_apis)
+	// that references the latest released API and remove API specification files.
+	// * API specification filegroup -> <dist-stem>.api.<scope>.latest
+	// * Removed API specification filegroup -> <dist-stem>-removed.api.<scope>.latest
+	Dist_stem *string
 
 	// indicates whether system and test apis should be generated.
 	Generate_system_and_test_apis bool `blueprint:"mutated"`
@@ -1109,12 +1124,16 @@
 	}
 }
 
+func (module *SdkLibrary) distStem() string {
+	return proptools.StringDefault(module.sdkLibraryProperties.Dist_stem, module.BaseModuleName())
+}
+
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.BaseModuleName() + ".api." + apiScope.name + ".latest"
+	return ":" + module.distStem() + ".api." + apiScope.name + ".latest"
 }
 
 func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.BaseModuleName() + "-removed.api." + apiScope.name + ".latest"
+	return ":" + module.distStem() + "-removed.api." + apiScope.name + ".latest"
 }
 
 func childModuleVisibility(childVisibility []string) []string {
@@ -1220,7 +1239,7 @@
 	// Dist the class jar artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
 		props.Dist.Targets = []string{"sdk", "win_sdk"}
-		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.jar", module.BaseModuleName()))
+		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.jar", module.distStem()))
 		props.Dist.Dir = proptools.StringPtr(module.apiDistPath(apiScope))
 		props.Dist.Tag = proptools.StringPtr(".jar")
 	}
@@ -1262,11 +1281,7 @@
 			Include_dirs       []string
 			Local_include_dirs []string
 		}
-		Dist struct {
-			Targets []string
-			Dest    *string
-			Dir     *string
-		}
+		Dists []android.Dist
 	}{}
 
 	// The stubs source processing uses the same compile time classpath when extracting the
@@ -1366,11 +1381,23 @@
 		}
 	}
 
-	// Dist the api txt artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
-		props.Dist.Targets = []string{"sdk", "win_sdk"}
-		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName()))
-		props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		// Dist the api txt and removed api txt artifacts for sdk builds.
+		distDir := proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		for _, p := range []struct {
+			tag     string
+			pattern string
+		}{
+			{tag: ".api.txt", pattern: "%s.txt"},
+			{tag: ".removed-api.txt", pattern: "%s-removed.txt"},
+		} {
+			props.Dists = append(props.Dists, android.Dist{
+				Targets: []string{"sdk", "win_sdk"},
+				Dir:     distDir,
+				Dest:    proptools.StringPtr(fmt.Sprintf(p.pattern, module.distStem())),
+				Tag:     proptools.StringPtr(p.tag),
+			})
+		}
 	}
 
 	mctx.CreateModule(DroidstubsFactory, &props)
@@ -1474,10 +1501,6 @@
 	return module.sdkJars(ctx, sdkVersion, false /*headerJars*/)
 }
 
-func (module *SdkLibrary) SetNoDist() {
-	module.sdkLibraryProperties.No_dist = proptools.BoolPtr(true)
-}
-
 var javaSdkLibrariesKey = android.NewOnceKey("javaSdkLibraries")
 
 func javaSdkLibraries(config android.Config) *[]string {
@@ -1505,12 +1528,10 @@
 	}
 
 	// If this builds against standard libraries (i.e. is not part of the core libraries)
-	// then assume it provides both system and test apis. Otherwise, assume it does not and
-	// also assume it does not contribute to the dist build.
+	// then assume it provides both system and test apis.
 	sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
 	hasSystemAndTestApis := sdkDep.hasStandardLibs()
 	module.sdkLibraryProperties.Generate_system_and_test_apis = hasSystemAndTestApis
-	module.sdkLibraryProperties.No_dist = proptools.BoolPtr(!hasSystemAndTestApis)
 
 	missing_current_api := false
 
@@ -2155,12 +2176,12 @@
 	xmlContent := fmt.Sprintf(permissionsTemplate, libName, module.implPath(ctx))
 
 	module.outputFilePath = android.PathForModuleOut(ctx, libName+".xml").OutputPath
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		Text("/bin/bash -c \"echo -e '" + xmlContent + "'\" > ").
 		Output(module.outputFilePath)
 
-	rule.Build(pctx, ctx, "java_sdk_xml", "Permission XML")
+	rule.Build("java_sdk_xml", "Permission XML")
 
 	module.installDirPath = android.PathForModuleInstall(ctx, "etc", module.SubDir())
 }
@@ -2292,11 +2313,10 @@
 			scopeSet.AddProperty("jars", jars)
 
 			// Merge the stubs source jar into the snapshot zip so that when it is unpacked
-			// the source files are also unpacked. Use a glob so that if the directory is missing
-			// (because there are no stubs sources for this scope) it will not fail.
+			// the source files are also unpacked.
 			snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources")
 			ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir)
-			scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir + "/**/*.java"})
+			scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir})
 
 			if properties.CurrentApiFile != nil {
 				currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt")
diff --git a/java/sdk_library_external.go b/java/sdk_library_external.go
new file mode 100644
index 0000000..2934936
--- /dev/null
+++ b/java/sdk_library_external.go
@@ -0,0 +1,109 @@
+// 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 java
+
+import (
+	"android/soong/android"
+)
+
+type partitionGroup int
+
+// Representation of partition group for checking inter-partition library dependencies.
+// Between system and system_ext, there are no restrictions of dependencies,
+// so we can treat these partitions as the same in terms of inter-partition dependency.
+// Same policy is applied between vendor and odm partiton.
+const (
+	partitionGroupNone partitionGroup = iota
+	// group for system, and system_ext partition
+	partitionGroupSystem
+	// group for vendor and odm partition
+	partitionGroupVendor
+	// product partition
+	partitionGroupProduct
+)
+
+func (g partitionGroup) String() string {
+	switch g {
+	case partitionGroupSystem:
+		return "system"
+	case partitionGroupVendor:
+		return "vendor"
+	case partitionGroupProduct:
+		return "product"
+	}
+
+	return ""
+}
+
+// Get partition group of java module that can be used at inter-partition dependency check.
+// We currently have three groups
+//   (system, system_ext) => system partition group
+//   (vendor, odm) => vendor partition group
+//   (product) => product partition group
+func (j *Module) partitionGroup(ctx android.EarlyModuleContext) partitionGroup {
+	// system and system_ext partition can be treated as the same in terms of inter-partition dependency.
+	if j.Platform() || j.SystemExtSpecific() {
+		return partitionGroupSystem
+	}
+
+	// vendor and odm partition can be treated as the same in terms of inter-partition dependency.
+	if j.SocSpecific() || j.DeviceSpecific() {
+		return partitionGroupVendor
+	}
+
+	// product partition is independent.
+	if j.ProductSpecific() {
+		return partitionGroupProduct
+	}
+
+	panic("Cannot determine partition type")
+}
+
+func (j *Module) allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool {
+	return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList())
+}
+
+type javaSdkLibraryEnforceContext interface {
+	Name() string
+	allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool
+	partitionGroup(ctx android.EarlyModuleContext) partitionGroup
+}
+
+var _ javaSdkLibraryEnforceContext = (*Module)(nil)
+
+func (j *Module) checkPartitionsForJavaDependency(ctx android.EarlyModuleContext, propName string, dep javaSdkLibraryEnforceContext) {
+	if dep.allowListedInterPartitionJavaLibrary(ctx) {
+		return
+	}
+
+	// If product interface is not enforced, skip check between system and product partition.
+	// But still need to check between product and vendor partition because product interface flag
+	// just represents enforcement between product and system, and vendor interface enforcement
+	// that is enforced here by precondition is representing enforcement between vendor and other partitions.
+	if !ctx.Config().EnforceProductPartitionInterface() {
+		productToSystem := j.partitionGroup(ctx) == partitionGroupProduct && dep.partitionGroup(ctx) == partitionGroupSystem
+		systemToProduct := j.partitionGroup(ctx) == partitionGroupSystem && dep.partitionGroup(ctx) == partitionGroupProduct
+
+		if productToSystem || systemToProduct {
+			return
+		}
+	}
+
+	// If module and dependency library is inter-partition
+	if j.partitionGroup(ctx) != dep.partitionGroup(ctx) {
+		errorFormat := "dependency on java_library (%q) is not allowed across the partitions (%s -> %s), use java_sdk_library instead"
+		ctx.PropertyErrorf(propName, errorFormat, dep.Name(), j.partitionGroup(ctx), dep.partitionGroup(ctx))
+	}
+}
diff --git a/java/sysprop.go b/java/sysprop.go
index 1a70499..e41aef6 100644
--- a/java/sysprop.go
+++ b/java/sysprop.go
@@ -14,6 +14,10 @@
 
 package java
 
+// This file contains a map to redirect dependencies towards sysprop_library. If a sysprop_library
+// is owned by Platform, and the client module links against system API, the public stub of the
+// sysprop_library should be used. The map will contain public stub names of sysprop_libraries.
+
 import (
 	"sync"
 
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 1c44c74..d538ce4 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -68,13 +68,13 @@
 	inputFile := android.PathForModuleSrc(ctx, android.String(l.properties.Src))
 	l.outputFilePath = android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
 	l.installDirPath = android.PathForModuleInstall(ctx, "etc")
-	linkerConfigRule := android.NewRuleBuilder()
+	linkerConfigRule := android.NewRuleBuilder(pctx, ctx)
 	linkerConfigRule.Command().
-		BuiltTool(ctx, "conv_linker_config").
+		BuiltTool("conv_linker_config").
 		Flag("proto").
 		FlagWithInput("-s ", inputFile).
 		FlagWithOutput("-o ", l.outputFilePath)
-	linkerConfigRule.Build(pctx, ctx, "conv_linker_config",
+	linkerConfigRule.Build("conv_linker_config",
 		"Generate linker config protobuf "+l.outputFilePath.String())
 
 	if proptools.BoolDefault(l.properties.Installable, true) {
@@ -102,6 +102,7 @@
 				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
+				entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
 			},
 		},
 	}}
diff --git a/linkerconfig/proto/Android.bp b/linkerconfig/proto/Android.bp
index c04887e..4d97128 100644
--- a/linkerconfig/proto/Android.bp
+++ b/linkerconfig/proto/Android.bp
@@ -7,6 +7,10 @@
         type: "lite",
     },
     srcs: ["linker_config.proto"],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 }
 
 python_library_host {
diff --git a/python/androidmk.go b/python/androidmk.go
index 8ad5889..60637d3 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -15,8 +15,6 @@
 package python
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
 	"strings"
 
@@ -24,82 +22,72 @@
 )
 
 type subAndroidMkProvider interface {
-	AndroidMk(*Module, *android.AndroidMkData)
+	AndroidMk(*Module, *android.AndroidMkEntries)
 }
 
-func (p *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (p *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
 	if p.subAndroidMkOnce == nil {
 		p.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
 	}
 	if androidmk, ok := obj.(subAndroidMkProvider); ok {
 		if !p.subAndroidMkOnce[androidmk] {
 			p.subAndroidMkOnce[androidmk] = true
-			androidmk.AndroidMk(p, data)
+			androidmk.AndroidMk(p, entries)
 		}
 	}
 }
 
-func (p *Module) AndroidMk() android.AndroidMkData {
-	ret := android.AndroidMkData{OutputFile: p.installSource}
+func (p *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	entries := android.AndroidMkEntries{OutputFile: p.installSource}
 
-	p.subAndroidMk(&ret, p.installer)
+	p.subAndroidMk(&entries, p.installer)
 
-	return ret
+	return []android.AndroidMkEntries{entries}
 }
 
-func (p *binaryDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "EXECUTABLES"
+func (p *binaryDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "EXECUTABLES"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
 	})
-	base.subAndroidMk(ret, p.pythonInstaller)
+	base.subAndroidMk(entries, p.pythonInstaller)
 }
 
-func (p *testDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "NATIVE_TESTS"
+func (p *testDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "NATIVE_TESTS"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryDecorator.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryDecorator.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(p.binaryDecorator.binaryProperties.Test_suites...)
 		if p.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=",
-				p.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
 		}
 
-		if !BoolDefault(p.binaryProperties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
 
-		if len(p.data) > 0 {
-			fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
-				strings.Join(android.AndroidMkDataPaths(p.data), " "))
-		}
+		entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
+
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(p.testProperties.Test_options.Unit_test))
 	})
-	base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
+	base.subAndroidMk(entries, p.binaryDecorator.pythonInstaller)
 }
 
-func (installer *pythonInstaller) AndroidMk(base *Module, ret *android.AndroidMkData) {
+func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
 	// Soong installation is only supported for host modules. Have Make
 	// installation trigger Soong installation.
 	if base.Target().Os.Class == android.Host {
-		ret.OutputFile = android.OptionalPathForPath(installer.path)
+		entries.OutputFile = android.OptionalPathForPath(installer.path)
 	}
 
-	ret.Required = append(ret.Required, "libc++")
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	entries.Required = append(entries.Required, "libc++")
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		path, file := filepath.Split(installer.path.ToMakePath().String())
 		stem := strings.TrimSuffix(file, filepath.Ext(file))
 
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+filepath.Ext(file))
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(installer.androidMkSharedLibs, " "))
-		fmt.Fprintln(w, "LOCAL_CHECK_ELF_FILES := false")
+		entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.AddStrings("LOCAL_SHARED_LIBRARIES", installer.androidMkSharedLibs...)
+		entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
 	})
 }
diff --git a/python/binary.go b/python/binary.go
index 1d2400e..416a7ee 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -81,7 +81,7 @@
 func PythonBinaryHostFactory() android.Module {
 	module, _ := NewBinary(android.HostSupported)
 
-	return module.Init()
+	return module.init()
 }
 
 func (binary *binaryDecorator) autorun() bool {
diff --git a/python/library.go b/python/library.go
index 0c8d613..b724d2b 100644
--- a/python/library.go
+++ b/python/library.go
@@ -28,11 +28,11 @@
 func PythonLibraryHostFactory() android.Module {
 	module := newModule(android.HostSupported, android.MultilibFirst)
 
-	return module.Init()
+	return module.init()
 }
 
 func PythonLibraryFactory() android.Module {
 	module := newModule(android.HostAndDeviceSupported, android.MultilibBoth)
 
-	return module.Init()
+	return module.init()
 }
diff --git a/python/proto.go b/python/proto.go
index b71e047..53ebb58 100644
--- a/python/proto.go
+++ b/python/proto.go
@@ -24,17 +24,17 @@
 	outDir := srcsZipFile.ReplaceExtension(ctx, "tmp")
 	depFile := srcsZipFile.ReplaceExtension(ctx, "srcszip.d")
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	rule.Command().Text("rm -rf").Flag(outDir.String())
 	rule.Command().Text("mkdir -p").Flag(outDir.String())
 
-	android.ProtoRule(ctx, rule, protoFile, flags, flags.Deps, outDir, depFile, nil)
+	android.ProtoRule(rule, protoFile, flags, flags.Deps, outDir, depFile, nil)
 
 	// Proto generated python files have an unknown package name in the path, so package the entire output directory
 	// into a srcszip.
 	zipCmd := rule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", srcsZipFile)
 	if pkgPath != "" {
 		zipCmd.FlagWithArg("-P ", pkgPath)
@@ -44,7 +44,7 @@
 
 	rule.Command().Text("rm -rf").Flag(outDir.String())
 
-	rule.Build(pctx, ctx, "protoc_"+protoFile.Rel(), "protoc "+protoFile.Rel())
+	rule.Build("protoc_"+protoFile.Rel(), "protoc "+protoFile.Rel())
 
 	return srcsZipFile
 }
diff --git a/python/python.go b/python/python.go
index 945e264..b3e3d13 100644
--- a/python/python.go
+++ b/python/python.go
@@ -20,7 +20,6 @@
 	"fmt"
 	"path/filepath"
 	"regexp"
-	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -33,33 +32,34 @@
 	android.PreDepsMutators(RegisterPythonPreDepsMutators)
 }
 
+// Exported to support other packages using Python modules in tests.
 func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
+	ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
 }
 
-// the version properties that apply to python libraries and binaries.
+// the version-specific properties that apply to python modules.
 type VersionProperties struct {
-	// true, if the module is required to be built with this version.
+	// whether the module is required to be built with this version.
+	// Defaults to true for Python 3, and false otherwise.
 	Enabled *bool `android:"arch_variant"`
 
-	// non-empty list of .py files under this strict Python version.
-	// srcs may reference the outputs of other modules that produce source files like genrule
-	// or filegroup using the syntax ":module".
+	// list of source files specific to this Python version.
+	// Using the syntax ":module", srcs may reference the outputs of other modules that produce source files,
+	// e.g. genrule or filegroup.
 	Srcs []string `android:"path,arch_variant"`
 
-	// list of source files that should not be used to build the Python module.
-	// This is most useful in the arch/multilib variants to remove non-common files
+	// list of source files that should not be used to build the Python module for this version.
+	// This is most useful to remove files that are not common to all Python versions.
 	Exclude_srcs []string `android:"path,arch_variant"`
 
-	// list of the Python libraries under this Python version.
+	// list of the Python libraries used only for this Python version.
 	Libs []string `android:"arch_variant"`
 
-	// true, if the binary is required to be built with embedded launcher.
-	// TODO(nanzhang): Remove this flag when embedded Python3 is supported later.
-	Embedded_launcher *bool `android:"arch_variant"`
+	// whether the binary is required to be built with embedded launcher for this version, defaults to false.
+	Embedded_launcher *bool `android:"arch_variant"` // TODO(b/174041232): Remove this property
 }
 
-// properties that apply to python libraries and binaries.
+// properties that apply to all python modules
 type BaseProperties struct {
 	// the package path prefix within the output artifact at which to place the source/data
 	// files of the current module.
@@ -86,14 +86,19 @@
 	// the test. the file extension can be arbitrary except for (.py).
 	Data []string `android:"path,arch_variant"`
 
+	// list of java modules that provide data that should be installed alongside the test.
+	Java_data []string
+
 	// list of the Python libraries compatible both with Python2 and Python3.
 	Libs []string `android:"arch_variant"`
 
 	Version struct {
-		// all the "srcs" or Python dependencies that are to be used only for Python2.
+		// Python2-specific properties, including whether Python2 is supported for this module
+		// and version-specific sources, exclusions and dependencies.
 		Py2 VersionProperties `android:"arch_variant"`
 
-		// all the "srcs" or Python dependencies that are to be used only for Python3.
+		// Python3-specific properties, including whether Python3 is supported for this module
+		// and version-specific sources, exclusions and dependencies.
 		Py3 VersionProperties `android:"arch_variant"`
 	} `android:"arch_variant"`
 
@@ -102,14 +107,16 @@
 	// runtime.
 	Actual_version string `blueprint:"mutated"`
 
-	// true, if the module is required to be built with actual_version.
+	// whether the module is required to be built with actual_version.
+	// this is set by the python version mutator based on version-specific properties
 	Enabled *bool `blueprint:"mutated"`
 
-	// true, if the binary is required to be built with embedded launcher.
-	// TODO(nanzhang): Remove this flag when embedded Python3 is supported later.
+	// whether the binary is required to be built with embedded launcher for this actual_version.
+	// this is set by the python version mutator based on version-specific properties
 	Embedded_launcher *bool `blueprint:"mutated"`
 }
 
+// Used to store files of current module after expanding dependencies
 type pathMapping struct {
 	dest string
 	src  android.Path
@@ -126,11 +133,14 @@
 	hod      android.HostOrDeviceSupported
 	multilib android.Multilib
 
-	// the bootstrapper is used to bootstrap .par executable.
-	// bootstrapper might be nil (Python library module).
+	// interface used to bootstrap .par executable when embedded_launcher is true
+	// this should be set by Python modules which are runnable, e.g. binaries and tests
+	// bootstrapper might be nil (e.g. Python library module).
 	bootstrapper bootstrapper
 
-	// the installer might be nil.
+	// interface that implements functions required for installation
+	// this should be set by Python modules which are runnable, e.g. binaries and tests
+	// installer might be nil (e.g. Python library module).
 	installer installer
 
 	// the Python files of current module after expanding source dependencies.
@@ -150,9 +160,11 @@
 	// (.intermediate) module output path as installation source.
 	installSource android.OptionalPath
 
+	// Map to ensure sub-part of the AndroidMk for this module is only added once
 	subAndroidMkOnce map[subAndroidMkProvider]bool
 }
 
+// newModule generates new Python base module
 func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	return &Module{
 		hod:      hod,
@@ -160,6 +172,7 @@
 	}
 }
 
+// bootstrapper interface should be implemented for runnable modules, e.g. binary and test
 type bootstrapper interface {
 	bootstrapperProps() []interface{}
 	bootstrap(ctx android.ModuleContext, ActualVersion string, embeddedLauncher bool,
@@ -169,36 +182,45 @@
 	autorun() bool
 }
 
+// installer interface should be implemented for installable modules, e.g. binary and test
 type installer interface {
 	install(ctx android.ModuleContext, path android.Path)
 	setAndroidMkSharedLibs(sharedLibs []string)
 }
 
-type PythonDependency interface {
-	GetSrcsPathMappings() []pathMapping
-	GetDataPathMappings() []pathMapping
-	GetSrcsZip() android.Path
+// interface implemented by Python modules to provide source and data mappings and zip to python
+// modules that depend on it
+type pythonDependency interface {
+	getSrcsPathMappings() []pathMapping
+	getDataPathMappings() []pathMapping
+	getSrcsZip() android.Path
 }
 
-func (p *Module) GetSrcsPathMappings() []pathMapping {
+// getSrcsPathMappings gets this module's path mapping of src source path : runfiles destination
+func (p *Module) getSrcsPathMappings() []pathMapping {
 	return p.srcsPathMappings
 }
 
-func (p *Module) GetDataPathMappings() []pathMapping {
+// getSrcsPathMappings gets this module's path mapping of data source path : runfiles destination
+func (p *Module) getDataPathMappings() []pathMapping {
 	return p.dataPathMappings
 }
 
-func (p *Module) GetSrcsZip() android.Path {
+// getSrcsZip returns the filepath where the current module's source/data files are zipped.
+func (p *Module) getSrcsZip() android.Path {
 	return p.srcsZip
 }
 
-var _ PythonDependency = (*Module)(nil)
+var _ pythonDependency = (*Module)(nil)
 
-var _ android.AndroidMkDataProvider = (*Module)(nil)
+var _ android.AndroidMkEntriesProvider = (*Module)(nil)
 
-func (p *Module) Init() android.Module {
-
+func (p *Module) init(additionalProps ...interface{}) android.Module {
 	p.AddProperties(&p.properties, &p.protoProperties)
+
+	// Add additional properties for bootstrapping/installation
+	// This is currently tied to the bootstrapper interface;
+	// however, these are a combination of properties for the installation and bootstrapping of a module
 	if p.bootstrapper != nil {
 		p.AddProperties(p.bootstrapper.bootstrapperProps()...)
 	}
@@ -209,16 +231,29 @@
 	return p
 }
 
+// Python-specific tag to transfer information on the purpose of a dependency.
+// This is used when adding a dependency on a module, which can later be accessed when visiting
+// dependencies.
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
 }
 
+// Python-specific tag that indicates that installed files of this module should depend on installed
+// files of the dependency
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	// embedding this struct provides the installation dependency requirement
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 var (
 	pythonLibTag         = dependencyTag{name: "pythonLib"}
+	javaDataTag          = dependencyTag{name: "javaData"}
 	launcherTag          = dependencyTag{name: "launcher"}
-	launcherSharedLibTag = dependencyTag{name: "launcherSharedLib"}
-	pyIdentifierRegexp   = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
+	launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
+	pathComponentRegexp  = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
 	pyExt                = ".py"
 	protoExt             = ".proto"
 	pyVersion2           = "PY2"
@@ -227,35 +262,37 @@
 	mainFileName         = "__main__.py"
 	entryPointFile       = "entry_point.txt"
 	parFileExt           = ".zip"
-	internal             = "internal"
+	internalPath         = "internal"
 )
 
-// create version variants for modules.
+// versionSplitMutator creates version variants for modules and appends the version-specific
+// properties for a given variant to the properties in the variant module
 func versionSplitMutator() func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
 		if base, ok := mctx.Module().(*Module); ok {
 			versionNames := []string{}
+			// collect version specific properties, so that we can merge version-specific properties
+			// into the module's overall properties
 			versionProps := []VersionProperties{}
 			// PY3 is first so that we alias the PY3 variant rather than PY2 if both
 			// are available
-			if !(base.properties.Version.Py3.Enabled != nil &&
-				*(base.properties.Version.Py3.Enabled) == false) {
+			if proptools.BoolDefault(base.properties.Version.Py3.Enabled, true) {
 				versionNames = append(versionNames, pyVersion3)
 				versionProps = append(versionProps, base.properties.Version.Py3)
 			}
-			if base.properties.Version.Py2.Enabled != nil &&
-				*(base.properties.Version.Py2.Enabled) == true {
+			if proptools.BoolDefault(base.properties.Version.Py2.Enabled, false) {
 				versionNames = append(versionNames, pyVersion2)
 				versionProps = append(versionProps, base.properties.Version.Py2)
 			}
-			modules := mctx.CreateVariations(versionNames...)
+			modules := mctx.CreateLocalVariations(versionNames...)
+			// Alias module to the first variant
 			if len(versionNames) > 0 {
 				mctx.AliasVariation(versionNames[0])
 			}
 			for i, v := range versionNames {
 				// set the actual version for Python module.
 				modules[i].(*Module).properties.Actual_version = v
-				// append versioned properties for the Python module
+				// append versioned properties for the Python module to the overall properties
 				err := proptools.AppendMatchingProperties([]interface{}{&modules[i].(*Module).properties}, &versionProps[i], nil)
 				if err != nil {
 					panic(err)
@@ -265,14 +302,19 @@
 	}
 }
 
+// HostToolPath returns a path if appropriate such that this module can be used as a host tool,
+// fulfilling HostToolProvider interface.
 func (p *Module) HostToolPath() android.OptionalPath {
 	if p.installer == nil {
 		// python_library is just meta module, and doesn't have any installer.
 		return android.OptionalPath{}
 	}
+	// TODO: This should only be set when building host binaries -- tests built for device would be
+	// setting this incorrectly.
 	return android.OptionalPathForPath(p.installer.(*binaryDecorator).path)
 }
 
+// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
 func (p *Module) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
 	case "":
@@ -286,12 +328,12 @@
 }
 
 func (p *Module) isEmbeddedLauncherEnabled() bool {
-	return Bool(p.properties.Embedded_launcher)
+	return p.installer != nil && Bool(p.properties.Embedded_launcher)
 }
 
-func hasSrcExt(srcs []string, ext string) bool {
-	for _, src := range srcs {
-		if filepath.Ext(src) == ext {
+func anyHasExt(paths []string, ext string) bool {
+	for _, p := range paths {
+		if filepath.Ext(p) == ext {
 			return true
 		}
 	}
@@ -299,89 +341,104 @@
 	return false
 }
 
-func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool {
-	return hasSrcExt(p.properties.Srcs, protoExt)
+func (p *Module) anySrcHasExt(ctx android.BottomUpMutatorContext, ext string) bool {
+	return anyHasExt(p.properties.Srcs, ext)
 }
 
+// DepsMutator mutates dependencies for this module:
+//  * handles proto dependencies,
+//  * if required, specifies launcher and adds launcher dependencies,
+//  * applies python version mutations to Python dependencies
 func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
 	android.ProtoDeps(ctx, &p.protoProperties)
 
-	if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
-		ctx.AddVariationDependencies(nil, pythonLibTag, "libprotobuf-python")
+	versionVariation := []blueprint.Variation{
+		{"python_version", p.properties.Actual_version},
 	}
-	ctx.AddVariationDependencies(nil, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
-	switch p.properties.Actual_version {
-	case pyVersion2:
+	// If sources contain a proto file, add dependency on libprotobuf-python
+	if p.anySrcHasExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
+		ctx.AddVariationDependencies(versionVariation, pythonLibTag, "libprotobuf-python")
+	}
 
-		if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled() {
-			ctx.AddVariationDependencies(nil, pythonLibTag, "py2-stdlib")
+	// Add python library dependencies for this python version variation
+	ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
-			launcherModule := "py2-launcher"
+	// If this module will be installed and has an embedded launcher, we need to add dependencies for:
+	//   * standard library
+	//   * launcher
+	//   * shared dependencies of the launcher
+	if p.installer != nil && p.isEmbeddedLauncherEnabled() {
+		var stdLib string
+		var launcherModule string
+		// Add launcher shared lib dependencies. Ideally, these should be
+		// derived from the `shared_libs` property of the launcher. However, we
+		// cannot read the property at this stage and it will be too late to add
+		// dependencies later.
+		launcherSharedLibDeps := []string{
+			"libsqlite",
+		}
+		// Add launcher-specific dependencies for bionic
+		if ctx.Target().Os.Bionic() {
+			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
+		}
+
+		switch p.properties.Actual_version {
+		case pyVersion2:
+			stdLib = "py2-stdlib"
+
+			launcherModule = "py2-launcher"
 			if p.bootstrapper.autorun() {
 				launcherModule = "py2-launcher-autorun"
 			}
-			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherTag, launcherModule)
+			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
 
-			// Add py2-launcher shared lib dependencies. Ideally, these should be
-			// derived from the `shared_libs` property of "py2-launcher". However, we
-			// cannot read the property at this stage and it will be too late to add
-			// dependencies later.
-			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libsqlite")
+		case pyVersion3:
+			stdLib = "py3-stdlib"
 
-			if ctx.Target().Os.Bionic() {
-				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
-					"libc", "libdl", "libm")
-			}
-		}
-
-	case pyVersion3:
-
-		if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled() {
-			ctx.AddVariationDependencies(nil, pythonLibTag, "py3-stdlib")
-
-			launcherModule := "py3-launcher"
+			launcherModule = "py3-launcher"
 			if p.bootstrapper.autorun() {
 				launcherModule = "py3-launcher-autorun"
 			}
-			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherTag, launcherModule)
-
-			// Add py3-launcher shared lib dependencies. Ideally, these should be
-			// derived from the `shared_libs` property of "py3-launcher". However, we
-			// cannot read the property at this stage and it will be too late to add
-			// dependencies later.
-			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libsqlite")
 
 			if ctx.Device() {
-				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
-					"liblog")
+				launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
 			}
-
-			if ctx.Target().Os.Bionic() {
-				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
-					"libc", "libdl", "libm")
-			}
+		default:
+			panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
+				p.properties.Actual_version, ctx.ModuleName()))
 		}
-	default:
-		panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
-			p.properties.Actual_version, ctx.ModuleName()))
+		ctx.AddVariationDependencies(versionVariation, pythonLibTag, stdLib)
+		ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherTag, launcherModule)
+		ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, launcherSharedLibDeps...)
 	}
+
+	// Emulate the data property for java_data but with the arch variation overridden to "common"
+	// so that it can point to java modules.
+	javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
+	ctx.AddVariationDependencies(javaDataVariation, javaDataTag, p.properties.Java_data...)
 }
 
 func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.GeneratePythonBuildActions(ctx)
+	p.generatePythonBuildActions(ctx)
 
-	// Only Python binaries and test has non-empty bootstrapper.
+	// Only Python binary and test modules have non-empty bootstrapper.
 	if p.bootstrapper != nil {
-		p.walkTransitiveDeps(ctx)
-		embeddedLauncher := false
-		embeddedLauncher = p.isEmbeddedLauncherEnabled()
+		// if the module is being installed, we need to collect all transitive dependencies to embed in
+		// the final par
+		p.collectPathsFromTransitiveDeps(ctx)
+		// bootstrap the module, including resolving main file, getting launcher path, and
+		// registering actions to build the par file
+		// bootstrap returns the binary output path
 		p.installSource = p.bootstrapper.bootstrap(ctx, p.properties.Actual_version,
-			embeddedLauncher, p.srcsPathMappings, p.srcsZip, p.depsSrcsZips)
+			p.isEmbeddedLauncherEnabled(), p.srcsPathMappings, p.srcsZip, p.depsSrcsZips)
 	}
 
+	// Only Python binary and test modules have non-empty installer.
 	if p.installer != nil {
 		var sharedLibs []string
+		// if embedded launcher is enabled, we need to collect the shared library depenendencies of the
+		// launcher
 		ctx.VisitDirectDeps(func(dep android.Module) {
 			if ctx.OtherModuleDependencyTag(dep) == launcherSharedLibTag {
 				sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
@@ -389,18 +446,16 @@
 		})
 		p.installer.setAndroidMkSharedLibs(sharedLibs)
 
+		// Install the par file from installSource
 		if p.installSource.Valid() {
 			p.installer.install(ctx, p.installSource.Path())
 		}
 	}
-
 }
 
-func (p *Module) GeneratePythonBuildActions(ctx android.ModuleContext) {
-	// expand python files from "srcs" property.
-	srcs := p.properties.Srcs
-	exclude_srcs := p.properties.Exclude_srcs
-	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, srcs, exclude_srcs)
+// generatePythonBuildActions performs build actions common to all Python modules
+func (p *Module) generatePythonBuildActions(ctx android.ModuleContext) {
+	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
 	requiresSrcs := true
 	if p.bootstrapper != nil && !p.bootstrapper.autorun() {
 		requiresSrcs = false
@@ -412,9 +467,15 @@
 	// expand data files from "data" property.
 	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
 
-	// sanitize pkg_path.
+	// Emulate the data property for java_data dependencies.
+	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
+	}
+
+	// Validate pkg_path property
 	pkgPath := String(p.properties.Pkg_path)
 	if pkgPath != "" {
+		// TODO: export validation from android/paths.go handling to replace this duplicated functionality
 		pkgPath = filepath.Clean(String(p.properties.Pkg_path))
 		if pkgPath == ".." || strings.HasPrefix(pkgPath, "../") ||
 			strings.HasPrefix(pkgPath, "/") {
@@ -423,22 +484,35 @@
 				String(p.properties.Pkg_path))
 			return
 		}
-		if p.properties.Is_internal != nil && *p.properties.Is_internal {
-			pkgPath = filepath.Join(internal, pkgPath)
-		}
-	} else {
-		if p.properties.Is_internal != nil && *p.properties.Is_internal {
-			pkgPath = internal
-		}
+	}
+	// If property Is_internal is set, prepend pkgPath with internalPath
+	if proptools.BoolDefault(p.properties.Is_internal, false) {
+		pkgPath = filepath.Join(internalPath, pkgPath)
 	}
 
+	// generate src:destination path mappings for this module
 	p.genModulePathMappings(ctx, pkgPath, expandedSrcs, expandedData)
 
+	// generate the zipfile of all source and data files
 	p.srcsZip = p.createSrcsZip(ctx, pkgPath)
 }
 
-// generate current module unique pathMappings: <dest: runfiles_path, src: source_path>
-// for python/data files.
+func isValidPythonPath(path string) error {
+	identifiers := strings.Split(strings.TrimSuffix(path, filepath.Ext(path)), "/")
+	for _, token := range identifiers {
+		if !pathComponentRegexp.MatchString(token) {
+			return fmt.Errorf("the path %q contains invalid subpath %q. "+
+				"Subpaths must be at least one character long. "+
+				"The first character must an underscore or letter. "+
+				"Following characters may be any of: letter, digit, underscore, hyphen.",
+				path, token)
+		}
+	}
+	return nil
+}
+
+// For this module, generate unique pathMappings: <dest: runfiles_path, src: source_path>
+// for python/data files expanded from properties.
 func (p *Module) genModulePathMappings(ctx android.ModuleContext, pkgPath string,
 	expandedSrcs, expandedData android.Paths) {
 	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
@@ -452,17 +526,11 @@
 			continue
 		}
 		runfilesPath := filepath.Join(pkgPath, s.Rel())
-		identifiers := strings.Split(strings.TrimSuffix(runfilesPath,
-			filepath.Ext(runfilesPath)), "/")
-		for _, token := range identifiers {
-			if !pyIdentifierRegexp.MatchString(token) {
-				ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.",
-					runfilesPath, token)
-			}
+		if err := isValidPythonPath(runfilesPath); err != nil {
+			ctx.PropertyErrorf("srcs", err.Error())
 		}
-		if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
-			p.srcsPathMappings = append(p.srcsPathMappings,
-				pathMapping{dest: runfilesPath, src: s})
+		if !checkForDuplicateOutputPath(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
+			p.srcsPathMappings = append(p.srcsPathMappings, pathMapping{dest: runfilesPath, src: s})
 		}
 	}
 
@@ -472,22 +540,23 @@
 			continue
 		}
 		runfilesPath := filepath.Join(pkgPath, d.Rel())
-		if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
+		if !checkForDuplicateOutputPath(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
 			p.dataPathMappings = append(p.dataPathMappings,
 				pathMapping{dest: runfilesPath, src: d})
 		}
 	}
 }
 
-// register build actions to zip current module's sources.
+// createSrcsZip registers build actions to zip current module's sources and data.
 func (p *Module) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
 	relativeRootMap := make(map[string]android.Paths)
 	pathMappings := append(p.srcsPathMappings, p.dataPathMappings...)
 
 	var protoSrcs android.Paths
-	// "srcs" or "data" properties may have filegroup so it might happen that
-	// the relative root for each source path is different.
+	// "srcs" or "data" properties may contain filegroup so it might happen that
+	// the root directory for each source path is different.
 	for _, path := range pathMappings {
+		// handle proto sources separately
 		if path.src.Ext() == protoExt {
 			protoSrcs = append(protoSrcs, path.src)
 		} else {
@@ -512,24 +581,21 @@
 	}
 
 	if len(relativeRootMap) > 0 {
-		var keys []string
-
 		// in order to keep stable order of soong_zip params, we sort the keys here.
-		for k := range relativeRootMap {
-			keys = append(keys, k)
-		}
-		sort.Strings(keys)
+		roots := android.SortedStringKeys(relativeRootMap)
 
 		parArgs := []string{}
 		if pkgPath != "" {
+			// use package path as path prefix
 			parArgs = append(parArgs, `-P `+pkgPath)
 		}
-		implicits := android.Paths{}
-		for _, k := range keys {
-			parArgs = append(parArgs, `-C `+k)
-			for _, path := range relativeRootMap[k] {
+		paths := android.Paths{}
+		for _, root := range roots {
+			// specify relative root of file in following -f arguments
+			parArgs = append(parArgs, `-C `+root)
+			for _, path := range relativeRootMap[root] {
 				parArgs = append(parArgs, `-f `+path.String())
-				implicits = append(implicits, path)
+				paths = append(paths, path)
 			}
 		}
 
@@ -538,13 +604,15 @@
 			Rule:        zip,
 			Description: "python library archive",
 			Output:      origSrcsZip,
-			Implicits:   implicits,
+			// as zip rule does not use $in, there is no real need to distinguish between Inputs and Implicits
+			Implicits: paths,
 			Args: map[string]string{
 				"args": strings.Join(parArgs, " "),
 			},
 		})
 		zips = append(zips, origSrcsZip)
 	}
+	// we may have multiple zips due to separate handling of proto source files
 	if len(zips) == 1 {
 		return zips[0]
 	} else {
@@ -559,25 +627,27 @@
 	}
 }
 
+// isPythonLibModule returns whether the given module is a Python library Module or not
+// This is distinguished by the fact that Python libraries are not installable, while other Python
+// modules are.
 func isPythonLibModule(module blueprint.Module) bool {
 	if m, ok := module.(*Module); ok {
-		// Python library has no bootstrapper or installer.
-		if m.bootstrapper != nil || m.installer != nil {
-			return false
+		// Python library has no bootstrapper or installer
+		if m.bootstrapper == nil && m.installer == nil {
+			return true
 		}
-		return true
 	}
 	return false
 }
 
-// check Python source/data files duplicates for whole runfiles tree since Python binary/test
-// need collect and zip all srcs of whole transitive dependencies to a final par file.
-func (p *Module) walkTransitiveDeps(ctx android.ModuleContext) {
+// collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
+// for module and its transitive dependencies and collects list of data/source file
+// zips for transitive dependencies.
+func (p *Module) collectPathsFromTransitiveDeps(ctx android.ModuleContext) {
 	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
 	// check duplicates.
 	destToPySrcs := make(map[string]string)
 	destToPyData := make(map[string]string)
-
 	for _, path := range p.srcsPathMappings {
 		destToPySrcs[path.dest] = path.src.String()
 	}
@@ -589,6 +659,7 @@
 
 	// visit all its dependencies in depth first.
 	ctx.WalkDeps(func(child, parent android.Module) bool {
+		// we only collect dependencies tagged as python library deps
 		if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
 			return false
 		}
@@ -598,44 +669,46 @@
 		seen[child] = true
 		// Python modules only can depend on Python libraries.
 		if !isPythonLibModule(child) {
-			panic(fmt.Errorf(
+			ctx.PropertyErrorf("libs",
 				"the dependency %q of module %q is not Python library!",
-				ctx.ModuleName(), ctx.OtherModuleName(child)))
+				ctx.ModuleName(), ctx.OtherModuleName(child))
 		}
-		if dep, ok := child.(PythonDependency); ok {
-			srcs := dep.GetSrcsPathMappings()
+		// collect source and data paths, checking that there are no duplicate output file conflicts
+		if dep, ok := child.(pythonDependency); ok {
+			srcs := dep.getSrcsPathMappings()
 			for _, path := range srcs {
-				if !fillInMap(ctx, destToPySrcs,
-					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child)) {
-					continue
-				}
-			}
-			data := dep.GetDataPathMappings()
-			for _, path := range data {
-				fillInMap(ctx, destToPyData,
+				checkForDuplicateOutputPath(ctx, destToPySrcs,
 					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
 			}
-			p.depsSrcsZips = append(p.depsSrcsZips, dep.GetSrcsZip())
+			data := dep.getDataPathMappings()
+			for _, path := range data {
+				checkForDuplicateOutputPath(ctx, destToPyData,
+					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
+			}
+			p.depsSrcsZips = append(p.depsSrcsZips, dep.getSrcsZip())
 		}
 		return true
 	})
 }
 
-func fillInMap(ctx android.ModuleContext, m map[string]string,
-	key, value, curModule, otherModule string) bool {
-	if oldValue, found := m[key]; found {
+// chckForDuplicateOutputPath checks whether outputPath has already been included in map m, which
+// would result in two files being placed in the same location.
+// If there is a duplicate path, an error is thrown and true is returned
+// Otherwise, outputPath: srcPath is added to m and returns false
+func checkForDuplicateOutputPath(ctx android.ModuleContext, m map[string]string, outputPath, srcPath, curModule, otherModule string) bool {
+	if oldSrcPath, found := m[outputPath]; found {
 		ctx.ModuleErrorf("found two files to be placed at the same location within zip %q."+
 			" First file: in module %s at path %q."+
 			" Second file: in module %s at path %q.",
-			key, curModule, oldValue, otherModule, value)
-		return false
-	} else {
-		m[key] = value
+			outputPath, curModule, oldSrcPath, otherModule, srcPath)
+		return true
 	}
+	m[outputPath] = srcPath
 
-	return true
+	return false
 }
 
+// InstallInData returns true as Python is not supported in the system partition
 func (p *Module) InstallInData() bool {
 	return true
 }
diff --git a/python/python_test.go b/python/python_test.go
index 64bc4f6..5c4efa7 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -44,7 +44,7 @@
 	pkgPathErrTemplate       = moduleVariantErrTemplate +
 		"pkg_path: %q must be a relative path contained in par file."
 	badIdentifierErrTemplate = moduleVariantErrTemplate +
-		"srcs: the path %q contains invalid token %q."
+		"srcs: the path %q contains invalid subpath %q."
 	dupRunfileErrTemplate = moduleVariantErrTemplate +
 		"found two files to be placed at the same location within zip %q." +
 		" First file: in module %s at path %q." +
@@ -370,7 +370,7 @@
 	} else {
 		sort.Strings(expErrs)
 		for i, v := range actErrStrs {
-			if v != expErrs[i] {
+			if !strings.Contains(v, expErrs[i]) {
 				testErrs = append(testErrs, errors.New(v))
 			}
 		}
diff --git a/python/test.go b/python/test.go
index 434e71a..b7cd475 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,12 @@
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
 	Data []string `android:"path,arch_variant"`
+
+	// list of java modules that provide data that should be installed alongside the test.
+	Java_data []string
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type testDecorator struct {
@@ -71,6 +83,13 @@
 	for _, dataSrcPath := range dataSrcPaths {
 		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
 	}
+
+	// Emulate the data property for java_data dependencies.
+	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+		for _, javaDataSrcPath := range android.OutputFilesForModule(ctx, javaData, "") {
+			test.data = append(test.data, android.DataPath{SrcPath: javaDataSrcPath})
+		}
+	}
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *Module {
@@ -89,12 +108,12 @@
 func PythonTestHostFactory() android.Module {
 	module := NewTest(android.HostSupportedNoCross)
 
-	return module.Init()
+	return module.init()
 }
 
 func PythonTestFactory() android.Module {
 	module := NewTest(android.HostAndDeviceSupported)
 	module.multilib = android.MultilibBoth
 
-	return module.Init()
+	return module.init()
 }
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 29e4bd7..c181d67 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -15,10 +15,7 @@
 package rust
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
-	"strings"
 
 	"android/soong/android"
 )
@@ -26,14 +23,14 @@
 type AndroidMkContext interface {
 	Name() string
 	Target() android.Target
-	SubAndroidMk(*android.AndroidMkData, interface{})
+	SubAndroidMk(*android.AndroidMkEntries, interface{})
 }
 
 type SubAndroidMkProvider interface {
-	AndroidMk(AndroidMkContext, *android.AndroidMkData)
+	AndroidMk(AndroidMkContext, *android.AndroidMkEntries)
 }
 
-func (mod *Module) SubAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (mod *Module) SubAndroidMk(data *android.AndroidMkEntries, obj interface{}) {
 	if mod.subAndroidMkOnce == nil {
 		mod.subAndroidMkOnce = make(map[SubAndroidMkProvider]bool)
 	}
@@ -45,33 +42,22 @@
 	}
 }
 
-func (mod *Module) AndroidMk() android.AndroidMkData {
-	if mod.Properties.HideFromMake {
-		return android.AndroidMkData{
-			Disabled: true,
-		}
+func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	if mod.Properties.HideFromMake || mod.hideApexVariantFromMake {
+
+		return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}}
 	}
 
-	ret := android.AndroidMkData{
+	ret := android.AndroidMkEntries{
 		OutputFile: mod.outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				if len(mod.Properties.AndroidMkRlibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_RLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkRlibs, " "))
-				}
-				if len(mod.Properties.AndroidMkDylibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_DYLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkDylibs, " "))
-				}
-				if len(mod.Properties.AndroidMkProcMacroLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_PROC_MACRO_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkProcMacroLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkSharedLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkSharedLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkStaticLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_STATIC_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkStaticLibs, " "))
-				}
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
+				entries.AddStrings("LOCAL_DYLIB_LIBRARIES", mod.Properties.AndroidMkDylibs...)
+				entries.AddStrings("LOCAL_PROC_MACRO_LIBRARIES", mod.Properties.AndroidMkProcMacroLibs...)
+				entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.Properties.AndroidMkSharedLibs...)
+				entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
 			},
 		},
 	}
@@ -84,10 +70,10 @@
 	}
 	ret.SubName += mod.Properties.SubName
 
-	return ret
+	return []android.AndroidMkEntries{ret}
 }
 
-func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, binary.baseCompiler)
 
 	if binary.distFile.Valid() {
@@ -95,32 +81,26 @@
 	}
 
 	ret.Class = "EXECUTABLES"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if binary.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+binary.coverageOutputZipFile.String())
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputZipFile)
 	})
 }
 
-func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	test.binaryDecorator.AndroidMk(ctx, ret)
 	ret.Class = "NATIVE_TESTS"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(test.Properties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(test.Properties.Test_suites, " "))
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
 		if test.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", test.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
 		}
-		if !BoolDefault(test.Properties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
 	})
 	// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
 }
 
-func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, library.baseCompiler)
 
 	if library.rlib() {
@@ -137,15 +117,12 @@
 		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
 	}
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if library.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+library.coverageOutputZipFile.String())
-		}
-
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputZipFile)
 	})
 }
 
-func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, procMacro.baseCompiler)
 
 	ret.Class = "PROC_MACRO_LIBRARIES"
@@ -155,29 +132,29 @@
 
 }
 
-func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	outFile := sourceProvider.OutputFiles[0]
 	ret.Class = "ETC"
 	ret.OutputFile = android.OptionalPathForPath(outFile)
 	ret.SubName += sourceProvider.subName
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		_, file := filepath.Split(outFile.String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
 	})
 }
 
-func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, bindgen.BaseSourceProvider)
 }
 
-func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, proto.BaseSourceProvider)
 }
 
-func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	if compiler.path == (android.InstallPath{}) {
 		return
 	}
@@ -191,14 +168,12 @@
 		unstrippedOutputFile = ret.OutputFile
 		ret.OutputFile = compiler.strippedOutputFile
 	}
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if compiler.strippedOutputFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", unstrippedOutputFile)
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
 		path, file := filepath.Split(compiler.path.ToMakePath().String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
 	})
 }
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 a09b1d1..6079e30 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -200,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 {
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index 132b7b8..e24f666 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -18,6 +18,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 func TestClippy(t *testing.T) {
@@ -45,6 +46,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index a6dba9e..2b40727 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 // Test that feature flags are being correctly generated.
@@ -132,6 +133,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/config/Android.bp b/rust/config/Android.bp
index e0cc4ce..1f0109f 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -13,6 +13,7 @@
         "toolchain.go",
         "allowed_list.go",
         "x86_darwin_host.go",
+        "x86_linux_bionic_host.go",
         "x86_linux_host.go",
         "x86_device.go",
         "x86_64_device.go",
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 3ad32fa..df31d60 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -6,12 +6,14 @@
 	// for an example.
 	// TODO(b/160223496): enable rustfmt globally.
 	RustAllowedPaths = []string{
+		"device/google/cuttlefish",
 		"external/adhd",
 		"external/crosvm",
 		"external/minijail",
 		"external/rust",
 		"external/vm_tools/p9",
 		"frameworks/native/libs/binder/rust",
+		"packages/modules/DnsResolver",
 		"packages/modules/Virtualization",
 		"prebuilts/rust",
 		"system/bt",
diff --git a/rust/config/global.go b/rust/config/global.go
index 6e268a0..22d9567 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.47.0"
+	RustDefaultVersion = "1.48.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/config/x86_linux_bionic_host.go b/rust/config/x86_linux_bionic_host.go
new file mode 100644
index 0000000..b1a2c17
--- /dev/null
+++ b/rust/config/x86_linux_bionic_host.go
@@ -0,0 +1,73 @@
+// Copyright 2020 The Android Open Source Project
+//
+// 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 config
+
+import (
+	"strings"
+
+	"android/soong/android"
+)
+
+var (
+	LinuxBionicRustFlags     = []string{}
+	LinuxBionicRustLinkFlags = []string{
+		"-B${cc_config.ClangBin}",
+		"-fuse-ld=lld",
+		"-Wl,--undefined-version",
+		"-nostdlib",
+	}
+)
+
+func init() {
+	registerToolchainFactory(android.LinuxBionic, android.X86_64, linuxBionicX8664ToolchainFactory)
+
+	pctx.StaticVariable("LinuxBionicToolchainRustFlags", strings.Join(LinuxBionicRustFlags, " "))
+	pctx.StaticVariable("LinuxBionicToolchainLinkFlags", strings.Join(LinuxBionicRustLinkFlags, " "))
+}
+
+type toolchainLinuxBionicX8664 struct {
+	toolchain64Bit
+}
+
+func (toolchainLinuxBionicX8664) Supported() bool {
+	return true
+}
+
+func (toolchainLinuxBionicX8664) Bionic() bool {
+	return true
+}
+
+func (t *toolchainLinuxBionicX8664) Name() string {
+	return "x86_64"
+}
+
+func (t *toolchainLinuxBionicX8664) RustTriple() string {
+	return "x86_64-linux-android"
+}
+
+func (t *toolchainLinuxBionicX8664) ToolchainLinkFlags() string {
+	// Prepend the lld flags from cc_config so we stay in sync with cc
+	return "${cc_config.LinuxBionicLldflags} ${config.LinuxBionicToolchainLinkFlags}"
+}
+
+func (t *toolchainLinuxBionicX8664) ToolchainRustFlags() string {
+	return "${config.LinuxBionicToolchainRustFlags}"
+}
+
+func linuxBionicX8664ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxBionicX8664Singleton
+}
+
+var toolchainLinuxBionicX8664Singleton Toolchain = &toolchainLinuxBionicX8664{}
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/library.go b/rust/library.go
index ae33f0f..971588d 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -428,6 +428,7 @@
 	var srcPath android.Path
 
 	if library.sourceProvider != nil {
+		// Assume the first source from the source provider is the library entry point.
 		srcPath = library.sourceProvider.Srcs()[0]
 	} else {
 		srcPath, _ = srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
diff --git a/rust/project_json.go b/rust/project_json.go
index 5697408..32ce6f4 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -45,10 +45,11 @@
 }
 
 type rustProjectCrate struct {
-	RootModule string           `json:"root_module"`
-	Edition    string           `json:"edition,omitempty"`
-	Deps       []rustProjectDep `json:"deps"`
-	Cfgs       []string         `json:"cfgs"`
+	DisplayName string           `json:"display_name"`
+	RootModule  string           `json:"root_module"`
+	Edition     string           `json:"edition,omitempty"`
+	Deps        []rustProjectDep `json:"deps"`
+	Cfgs        []string         `json:"cfgs"`
 }
 
 type rustProjectJson struct {
@@ -58,13 +59,13 @@
 
 // crateInfo is used during the processing to keep track of the known crates.
 type crateInfo struct {
-	ID   int
-	Deps map[string]int
+	Idx  int            // Index of the crate in rustProjectJson.Crates slice.
+	Deps map[string]int // The keys are the module names and not the crate names.
 }
 
 type projectGeneratorSingleton struct {
 	project     rustProjectJson
-	knownCrates map[string]crateInfo
+	knownCrates map[string]crateInfo // Keys are module names.
 }
 
 func rustProjectGeneratorSingleton() android.Singleton {
@@ -75,90 +76,190 @@
 	android.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
 }
 
-// librarySource finds the main source file (.rs) for a crate.
-func librarySource(ctx android.SingletonContext, rModule *Module, rustLib *libraryDecorator) (string, bool) {
-	srcs := rustLib.baseCompiler.Properties.Srcs
+// sourceProviderVariantSource returns the path to the source file if this
+// module variant should be used as a priority.
+//
+// SourceProvider modules may have multiple variants considered as source
+// (e.g., x86_64 and armv8). For a module available on device, use the source
+// generated for the target. For a host-only module, use the source generated
+// for the host.
+func sourceProviderVariantSource(ctx android.SingletonContext, rModule *Module) (string, bool) {
+	rustLib, ok := rModule.compiler.(*libraryDecorator)
+	if !ok {
+		return "", false
+	}
+	if rustLib.source() {
+		switch rModule.hod {
+		case android.HostSupported, android.HostSupportedNoCross:
+			if rModule.Target().String() == ctx.Config().BuildOSTarget.String() {
+				src := rustLib.sourceProvider.Srcs()[0]
+				return src.String(), true
+			}
+		default:
+			if rModule.Target().String() == ctx.Config().AndroidFirstDeviceTarget.String() {
+				src := rustLib.sourceProvider.Srcs()[0]
+				return src.String(), true
+			}
+		}
+	}
+	return "", false
+}
+
+// sourceProviderSource finds the main source file of a source-provider crate.
+func sourceProviderSource(ctx android.SingletonContext, rModule *Module) (string, bool) {
+	rustLib, ok := rModule.compiler.(*libraryDecorator)
+	if !ok {
+		return "", false
+	}
+	if rustLib.source() {
+		// This is a source-variant, check if we are the right variant
+		// depending on the module configuration.
+		if src, ok := sourceProviderVariantSource(ctx, rModule); ok {
+			return src, true
+		}
+	}
+	foundSource := false
+	sourceSrc := ""
+	// Find the variant with the source and return its.
+	ctx.VisitAllModuleVariants(rModule, func(variant android.Module) {
+		if foundSource {
+			return
+		}
+		// All variants of a source provider library are libraries.
+		rVariant, _ := variant.(*Module)
+		variantLib, _ := rVariant.compiler.(*libraryDecorator)
+		if variantLib.source() {
+			sourceSrc, ok = sourceProviderVariantSource(ctx, rVariant)
+			if ok {
+				foundSource = true
+			}
+		}
+	})
+	if !foundSource {
+		fmt.Errorf("No valid source for source provider found: %v\n", rModule)
+	}
+	return sourceSrc, foundSource
+}
+
+// crateSource finds the main source file (.rs) for a crate.
+func crateSource(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (string, bool) {
+	// Basic libraries, executables and tests.
+	srcs := comp.Properties.Srcs
 	if len(srcs) != 0 {
 		return path.Join(ctx.ModuleDir(rModule), srcs[0]), true
 	}
-	if !rustLib.source() {
-		return "", false
+	// SourceProvider libraries.
+	if rModule.sourceProvider != nil {
+		return sourceProviderSource(ctx, rModule)
 	}
-	// It is a SourceProvider module. If this module is host only, uses the variation for the host.
-	// Otherwise, use the variation for the primary target.
-	switch rModule.hod {
-	case android.HostSupported:
-	case android.HostSupportedNoCross:
-		if rModule.Target().String() != ctx.Config().BuildOSTarget.String() {
-			return "", false
-		}
-	default:
-		if rModule.Target().String() != ctx.Config().AndroidFirstDeviceTarget.String() {
-			return "", false
-		}
-	}
-	src := rustLib.sourceProvider.Srcs()[0]
-	return src.String(), true
+	return "", false
 }
 
+// mergeDependencies visits all the dependencies for module and updates crate and deps
+// with any new dependency.
 func (singleton *projectGeneratorSingleton) mergeDependencies(ctx android.SingletonContext,
-	module android.Module, crate *rustProjectCrate, deps map[string]int) {
+	module *Module, crate *rustProjectCrate, deps map[string]int) {
 
 	ctx.VisitDirectDeps(module, func(child android.Module) {
-		childId, childCrateName, ok := singleton.appendLibraryAndDeps(ctx, child)
+		// Skip intra-module dependencies (i.e., generated-source library depending on the source variant).
+		if module.Name() == child.Name() {
+			return
+		}
+		// Skip unsupported modules.
+		rChild, compChild, ok := isModuleSupported(ctx, child)
 		if !ok {
 			return
 		}
-		if _, ok = deps[ctx.ModuleName(child)]; ok {
+		// For unknown dependency, add it first.
+		var childId int
+		cInfo, known := singleton.knownCrates[rChild.Name()]
+		if !known {
+			childId, ok = singleton.addCrate(ctx, rChild, compChild)
+			if !ok {
+				return
+			}
+		} else {
+			childId = cInfo.Idx
+		}
+		// Is this dependency known already?
+		if _, ok = deps[child.Name()]; ok {
 			return
 		}
-		crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: childCrateName})
-		deps[ctx.ModuleName(child)] = childId
+		crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: rChild.CrateName()})
+		deps[child.Name()] = childId
 	})
 }
 
-// appendLibraryAndDeps creates a rustProjectCrate for the module argument and appends it to singleton.project.
-// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the
-// current module is already in singleton.knownCrates, its dependencies are merged. Returns a tuple (id, crate_name, ok).
-func (singleton *projectGeneratorSingleton) appendLibraryAndDeps(ctx android.SingletonContext, module android.Module) (int, string, bool) {
+// isModuleSupported returns the RustModule and baseCompiler if the module
+// should be considered for inclusion in rust-project.json.
+func isModuleSupported(ctx android.SingletonContext, module android.Module) (*Module, *baseCompiler, bool) {
 	rModule, ok := module.(*Module)
 	if !ok {
-		return 0, "", false
+		return nil, nil, false
 	}
 	if rModule.compiler == nil {
-		return 0, "", false
+		return nil, nil, false
 	}
-	rustLib, ok := rModule.compiler.(*libraryDecorator)
+	var comp *baseCompiler
+	switch c := rModule.compiler.(type) {
+	case *libraryDecorator:
+		comp = c.baseCompiler
+	case *binaryDecorator:
+		comp = c.baseCompiler
+	case *testDecorator:
+		comp = c.binaryDecorator.baseCompiler
+	default:
+		return nil, nil, false
+	}
+	return rModule, comp, true
+}
+
+// addCrate adds a crate to singleton.project.Crates ensuring that required
+// dependencies are also added. It returns the index of the new crate in
+// singleton.project.Crates
+func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (int, bool) {
+	rootModule, ok := crateSource(ctx, rModule, comp)
 	if !ok {
-		return 0, "", false
+		fmt.Errorf("Unable to find source for valid module: %v", rModule)
+		return 0, false
 	}
-	moduleName := ctx.ModuleName(module)
-	crateName := rModule.CrateName()
-	if cInfo, ok := singleton.knownCrates[moduleName]; ok {
-		// We have seen this crate already; merge any new dependencies.
-		crate := singleton.project.Crates[cInfo.ID]
-		singleton.mergeDependencies(ctx, module, &crate, cInfo.Deps)
-		singleton.project.Crates[cInfo.ID] = crate
-		return cInfo.ID, crateName, true
+
+	crate := rustProjectCrate{
+		DisplayName: rModule.Name(),
+		RootModule:  rootModule,
+		Edition:     comp.edition(),
+		Deps:        make([]rustProjectDep, 0),
+		Cfgs:        make([]string, 0),
 	}
-	crate := rustProjectCrate{Deps: make([]rustProjectDep, 0), Cfgs: make([]string, 0)}
-	rootModule, ok := librarySource(ctx, rModule, rustLib)
-	if !ok {
-		return 0, "", false
-	}
-	crate.RootModule = rootModule
-	crate.Edition = rustLib.baseCompiler.edition()
 
 	deps := make(map[string]int)
-	singleton.mergeDependencies(ctx, module, &crate, deps)
+	singleton.mergeDependencies(ctx, rModule, &crate, deps)
 
-	id := len(singleton.project.Crates)
-	singleton.knownCrates[moduleName] = crateInfo{ID: id, Deps: deps}
+	idx := len(singleton.project.Crates)
+	singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps}
 	singleton.project.Crates = append(singleton.project.Crates, crate)
 	// rust-analyzer requires that all crates belong to at least one root:
 	// https://github.com/rust-analyzer/rust-analyzer/issues/4735.
 	singleton.project.Roots = append(singleton.project.Roots, path.Dir(crate.RootModule))
-	return id, crateName, true
+	return idx, true
+}
+
+// appendCrateAndDependencies creates a rustProjectCrate for the module argument and appends it to singleton.project.
+// It visits the dependencies of the module depth-first so the dependency ID can be added to the current module. If the
+// current module is already in singleton.knownCrates, its dependencies are merged.
+func (singleton *projectGeneratorSingleton) appendCrateAndDependencies(ctx android.SingletonContext, module android.Module) {
+	rModule, comp, ok := isModuleSupported(ctx, module)
+	if !ok {
+		return
+	}
+	// If we have seen this crate already; merge any new dependencies.
+	if cInfo, ok := singleton.knownCrates[module.Name()]; ok {
+		crate := singleton.project.Crates[cInfo.Idx]
+		singleton.mergeDependencies(ctx, rModule, &crate, cInfo.Deps)
+		singleton.project.Crates[cInfo.Idx] = crate
+		return
+	}
+	singleton.addCrate(ctx, rModule, comp)
 }
 
 func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
@@ -168,7 +269,7 @@
 
 	singleton.knownCrates = make(map[string]crateInfo)
 	ctx.VisitAllModules(func(module android.Module) {
-		singleton.appendLibraryAndDeps(ctx, module)
+		singleton.appendCrateAndDependencies(ctx, module)
 	})
 
 	path := android.PathForOutput(ctx, rustProjectJsonFileName)
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 69288fc..ba66215 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -67,6 +67,37 @@
 	return crates
 }
 
+// validateCrate ensures that a crate can be parsed as a map.
+func validateCrate(t *testing.T, crate interface{}) map[string]interface{} {
+	c, ok := crate.(map[string]interface{})
+	if !ok {
+		t.Fatalf("Unexpected type for crate: %v", c)
+	}
+	return c
+}
+
+// validateDependencies parses the dependencies for a crate. It returns a list
+// of the dependencies name.
+func validateDependencies(t *testing.T, crate map[string]interface{}) []string {
+	var dependencies []string
+	deps, ok := crate["deps"].([]interface{})
+	if !ok {
+		t.Errorf("Unexpected format for deps: %v", crate["deps"])
+	}
+	for _, dep := range deps {
+		d, ok := dep.(map[string]interface{})
+		if !ok {
+			t.Errorf("Unexpected format for dependency: %v", dep)
+		}
+		name, ok := d["name"].(string)
+		if !ok {
+			t.Errorf("Dependency is missing the name key: %v", d)
+		}
+		dependencies = append(dependencies, name)
+	}
+	return dependencies
+}
+
 func TestProjectJsonDep(t *testing.T) {
 	bp := `
 	rust_library {
@@ -85,13 +116,36 @@
 	validateJsonCrates(t, jsonContent)
 }
 
+func TestProjectJsonBinary(t *testing.T) {
+	bp := `
+	rust_binary {
+		name: "libz",
+		srcs: ["z/src/lib.rs"],
+		crate_name: "z"
+	}
+	`
+	jsonContent := testProjectJson(t, bp)
+	crates := validateJsonCrates(t, jsonContent)
+	for _, c := range crates {
+		crate := validateCrate(t, c)
+		rootModule, ok := crate["root_module"].(string)
+		if !ok {
+			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
+		}
+		if rootModule == "z/src/lib.rs" {
+			return
+		}
+	}
+	t.Errorf("Entry for binary %q not found: %s", "a", jsonContent)
+}
+
 func TestProjectJsonBindGen(t *testing.T) {
 	bp := `
 	rust_library {
-		name: "liba",
-		srcs: ["src/lib.rs"],
+		name: "libd",
+		srcs: ["d/src/lib.rs"],
 		rlibs: ["libbindings1"],
-		crate_name: "a"
+		crate_name: "d"
 	}
 	rust_bindgen {
 		name: "libbindings1",
@@ -101,10 +155,10 @@
 		wrapper_src: "src/any.h",
 	}
 	rust_library_host {
-		name: "libb",
-		srcs: ["src/lib.rs"],
+		name: "libe",
+		srcs: ["e/src/lib.rs"],
 		rustlibs: ["libbindings2"],
-		crate_name: "b"
+		crate_name: "e"
 	}
 	rust_bindgen_host {
 		name: "libbindings2",
@@ -116,10 +170,7 @@
 	jsonContent := testProjectJson(t, bp)
 	crates := validateJsonCrates(t, jsonContent)
 	for _, c := range crates {
-		crate, ok := c.(map[string]interface{})
-		if !ok {
-			t.Fatalf("Unexpected type for crate: %v", c)
-		}
+		crate := validateCrate(t, c)
 		rootModule, ok := crate["root_module"].(string)
 		if !ok {
 			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
@@ -131,6 +182,27 @@
 			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
 				rootModule, android.BuildOs.String())
 		}
+		// Check that libbindings1 does not depend on itself.
+		if strings.Contains(rootModule, "libbindings1") {
+			for _, depName := range validateDependencies(t, crate) {
+				if depName == "bindings1" {
+					t.Errorf("libbindings1 depends on itself")
+				}
+			}
+		}
+		// Check that liba depends on libbindings1
+		if strings.Contains(rootModule, "d/src/lib.rs") {
+			found := false
+			for _, depName := range validateDependencies(t, crate) {
+				if depName == "bindings1" {
+					found = true
+					break
+				}
+			}
+			if !found {
+				t.Errorf("liba does not depend on libbindings1: %v", crate)
+			}
+		}
 	}
 }
 
@@ -155,20 +227,18 @@
 	`
 	jsonContent := testProjectJson(t, bp)
 	crates := validateJsonCrates(t, jsonContent)
-	for _, crate := range crates {
-		c := crate.(map[string]interface{})
-		if c["root_module"] == "b/src/lib.rs" {
-			deps, ok := c["deps"].([]interface{})
-			if !ok {
-				t.Errorf("Unexpected format for deps: %v", c["deps"])
-			}
+	for _, c := range crates {
+		crate := validateCrate(t, c)
+		rootModule, ok := crate["root_module"].(string)
+		if !ok {
+			t.Fatalf("Unexpected type for root_module: %v", crate["root_module"])
+		}
+		// Make sure that b has 2 different dependencies.
+		if rootModule == "b/src/lib.rs" {
 			aCount := 0
-			for _, dep := range deps {
-				d, ok := dep.(map[string]interface{})
-				if !ok {
-					t.Errorf("Unexpected format for dep: %v", dep)
-				}
-				if d["name"] == "a" {
+			deps := validateDependencies(t, crate)
+			for _, depName := range deps {
+				if depName == "a" {
 					aCount++
 				}
 			}
diff --git a/rust/protobuf.go b/rust/protobuf.go
index 76fed30..0e79089 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -46,14 +46,14 @@
 var _ SourceProvider = (*protobufDecorator)(nil)
 
 type ProtobufProperties struct {
-	// Path to the proto file that will be used to generate the source
-	Proto *string `android:"path,arch_variant"`
+	// List of realtive paths to proto files that will be used to generate the source
+	Protos []string `android:"path,arch_variant"`
 
 	// 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"`
+	Header_libs []string `android:"arch_variant,variant_prepend"`
 }
 
 type protobufDecorator struct {
@@ -66,6 +66,7 @@
 func (proto *protobufDecorator) GenerateSource(ctx ModuleContext, deps PathDeps) android.Path {
 	var protoFlags android.ProtoFlags
 	var pluginPaths android.Paths
+	var protoNames []string
 
 	protoFlags.OutTypeFlag = "--rust_out"
 	outDir := android.PathForModuleOut(ctx)
@@ -77,10 +78,7 @@
 
 	protoFlags.Deps = append(protoFlags.Deps, pluginPaths...)
 
-	protoFile := android.OptionalPathForModuleSrc(ctx, proto.Properties.Proto)
-	if !protoFile.Valid() {
-		ctx.PropertyErrorf("proto", "invalid path to proto file")
-	}
+	protoFiles := android.PathsForModuleSrc(ctx, proto.Properties.Protos)
 
 	// Add exported dependency include paths
 	for _, include := range deps.depIncludePaths {
@@ -88,35 +86,58 @@
 	}
 
 	stem := proto.BaseSourceProvider.getStem(ctx)
-	// rust protobuf-codegen output <stem>.rs
-	stemFile := android.PathForModuleOut(ctx, stem+".rs")
-	// add mod_<stem>.rs to import <stem>.rs
-	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"))
+
+	// The mod_stem.rs file is used to avoid collisions if this is not included as a crate.
+	stemFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
+
+	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
+	outputs := android.WritablePaths{stemFile}
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	for _, protoFile := range protoFiles {
+		protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
+		protoNames = append(protoNames, protoName)
+
+		protoOut := android.PathForModuleOut(ctx, protoName+".rs")
+		ruleOutputs := android.WritablePaths{android.WritablePath(protoOut)}
+
+		if proto.plugin == Grpc {
+			grpcOut := android.PathForModuleOut(ctx, protoName+grpcSuffix+".rs")
+			ruleOutputs = append(ruleOutputs, android.WritablePath(grpcOut))
+		}
+
+		depFile := android.PathForModuleOut(ctx, protoName+".d")
+
+		android.ProtoRule(rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
+		outputs = append(outputs, ruleOutputs...)
 	}
-	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 '" + proto.getModFileContents(ctx) + "' >").Output(modFile)
-	rule.Build(pctx, ctx, "protoc_"+protoFile.Path().Rel(), "protoc "+protoFile.Path().Rel())
+	rule.Command().
+		Implicits(outputs.Paths()).
+		Text("printf '" + proto.genModFileContents(ctx, protoNames) + "' >").
+		Output(stemFile)
 
-	proto.BaseSourceProvider.OutputFiles = android.Paths{modFile, stemFile}
-	return modFile
+	rule.Build("protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
+
+	proto.BaseSourceProvider.OutputFiles = outputs.Paths()
+
+	// mod_stem.rs is the entry-point for our library modules, so this is what we return.
+	return stemFile
 }
 
-func (proto *protobufDecorator) getModFileContents(ctx ModuleContext) string {
-	stem := proto.BaseSourceProvider.getStem(ctx)
+func (proto *protobufDecorator) genModFileContents(ctx ModuleContext, protoNames []string) string {
 	lines := []string{
-		"// @generated",
-		fmt.Sprintf("pub mod %s;", stem),
+		"// @Soong generated Source",
+	}
+	for _, protoName := range protoNames {
+		lines = append(lines, fmt.Sprintf("pub mod %s;", protoName))
+
+		if proto.plugin == Grpc {
+			lines = append(lines, fmt.Sprintf("pub mod %s%s;", protoName, grpcSuffix))
+		}
 	}
 
 	if proto.plugin == Grpc {
-		lines = append(lines, fmt.Sprintf("pub mod %s%s;", stem, grpcSuffix))
 		lines = append(
 			lines,
 			"pub mod empty {",
diff --git a/rust/protobuf_test.go b/rust/protobuf_test.go
index 845911f..608a4e8 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -25,7 +25,7 @@
 	ctx := testRust(t, `
 		rust_protobuf {
 			name: "librust_proto",
-			proto: "buf.proto",
+			protos: ["buf.proto", "proto.proto"],
 			crate_name: "rust_proto",
 			source_stem: "buf",
 			shared_libs: ["libfoo_shared"],
@@ -60,13 +60,20 @@
 	if w := "-Istatic_include"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
 	}
+
+	// Check proto.rs, the second protobuf, is listed as an output
+	librust_proto_outputs := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").AllOutputs()
+	if android.InList("proto.rs", librust_proto_outputs) {
+		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto.rs' in list, got: %#v ",
+			librust_proto_outputs)
+	}
 }
 
 func TestRustGrpcio(t *testing.T) {
 	ctx := testRust(t, `
 		rust_grpcio {
 			name: "librust_grpcio",
-			proto: "buf.proto",
+			protos: ["buf.proto", "proto.proto"],
 			crate_name: "rust_grpcio",
 			source_stem: "buf",
 			shared_libs: ["libfoo_shared"],
@@ -117,4 +124,11 @@
 	if w := "-Ilibprotobuf-cpp-full-includes"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
 	}
+
+	// Check proto.rs, the second protobuf, is listed as an output
+	librust_grpcio_outputs := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").AllOutputs()
+	if android.InList("proto_grpc.rs", librust_grpcio_outputs) {
+		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto_grpc.rs' in list, got: %#v ",
+			librust_grpcio_outputs)
+	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index 5b94045..38caad3 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -74,6 +74,7 @@
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.ApexModuleBase
 
 	Properties BaseProperties
 
@@ -88,6 +89,8 @@
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
 	outputFile android.OptionalPath
+
+	hideApexVariantFromMake bool
 }
 
 func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
@@ -508,6 +511,7 @@
 	}
 
 	android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
+	android.InitApexModule(mod)
 
 	android.InitDefaultableModule(mod)
 	return mod
@@ -605,6 +609,11 @@
 		ModuleContext: actx,
 	}
 
+	apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		mod.hideApexVariantFromMake = true
+	}
+
 	toolchain := mod.toolchain(ctx)
 
 	if !toolchain.Supported() {
@@ -685,6 +694,14 @@
 	proc_macro bool
 }
 
+// InstallDepNeeded returns true for rlibs, dylibs, and proc macros so that they or their transitive
+// dependencies (especially C/C++ shared libs) are installed as dependencies of a rust binary.
+func (d dependencyTag) InstallDepNeeded() bool {
+	return d.library || d.proc_macro
+}
+
+var _ android.InstallNeededDependencyTag = dependencyTag{}
+
 var (
 	customBindgenDepTag = dependencyTag{name: "customBindgenTag"}
 	rlibDepTag          = dependencyTag{name: "rlibTag", library: true}
@@ -694,6 +711,11 @@
 	sourceDepTag        = dependencyTag{name: "source"}
 )
 
+func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
+	tag, ok := depTag.(dependencyTag)
+	return ok && tag == dylibDepTag
+}
+
 type autoDep struct {
 	variation string
 	depTag    dependencyTag
@@ -1052,6 +1074,58 @@
 	return android.OptionalPath{}
 }
 
+var _ android.ApexModule = (*Module)(nil)
+
+func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+	return nil
+}
+
+func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	depTag := ctx.OtherModuleDependencyTag(dep)
+
+	if ccm, ok := dep.(*cc.Module); ok {
+		if ccm.HasStubsVariants() {
+			if cc.IsSharedDepTag(depTag) {
+				// dynamic dep to a stubs lib crosses APEX boundary
+				return false
+			}
+			if cc.IsRuntimeDepTag(depTag) {
+				// runtime dep to a stubs lib also crosses APEX boundary
+				return false
+			}
+
+			if cc.IsHeaderDepTag(depTag) {
+				return false
+			}
+		}
+		if mod.Static() && cc.IsSharedDepTag(depTag) {
+			// shared_lib dependency from a static lib is considered as crossing
+			// the APEX boundary because the dependency doesn't actually is
+			// linked; the dependency is used only during the compilation phase.
+			return false
+		}
+	}
+
+	if depTag == procMacroDepTag {
+		return false
+	}
+
+	return true
+}
+
+// Overrides ApexModule.IsInstallabeToApex()
+func (mod *Module) IsInstallableToApex() bool {
+	if mod.compiler != nil {
+		if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+			return true
+		}
+		if _, ok := mod.compiler.(*binaryDecorator); ok {
+			return true
+		}
+	}
+	return false
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 14bbd0b..4edc6cd 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -106,13 +106,14 @@
 // useMockedFs setup a default mocked filesystem for the test environment.
 func (tctx *testRustCtx) useMockedFs() {
 	tctx.fs = map[string][]byte{
-		"foo.rs":     nil,
-		"foo.c":      nil,
-		"src/bar.rs": nil,
-		"src/any.h":  nil,
-		"buf.proto":  nil,
-		"liby.so":    nil,
-		"libz.so":    nil,
+		"foo.rs":      nil,
+		"foo.c":       nil,
+		"src/bar.rs":  nil,
+		"src/any.h":   nil,
+		"proto.proto": nil,
+		"buf.proto":   nil,
+		"liby.so":     nil,
+		"libz.so":     nil,
 	}
 }
 
@@ -120,6 +121,7 @@
 // attributes of the testRustCtx.
 func (tctx *testRustCtx) generateConfig() {
 	tctx.bp = tctx.bp + GatherRequiredDepsForTest()
+	tctx.bp = tctx.bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 	cc.GatherRequiredFilesForTest(tctx.fs)
 	config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
 	tctx.config = &config
@@ -286,6 +288,12 @@
 			srcs: ["src/any.h"],
 			out: ["src/any.rs"],
 		}
+		rust_binary_host {
+			name: "any_rust_binary",
+			srcs: [
+				"foo.rs",
+			],
+		}
 		rust_bindgen {
 			name: "libbindings",
 			crate_name: "bindings",
diff --git a/rust/source_provider.go b/rust/source_provider.go
index 436518c..7719611 100644
--- a/rust/source_provider.go
+++ b/rust/source_provider.go
@@ -30,6 +30,7 @@
 type BaseSourceProvider struct {
 	Properties SourceProviderProperties
 
+	// The first file in OutputFiles must be the library entry point.
 	OutputFiles      android.Paths
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 	subName          string
diff --git a/rust/strip.go b/rust/strip.go
index d1bbba6..110e3cc 100644
--- a/rust/strip.go
+++ b/rust/strip.go
@@ -19,11 +19,14 @@
 	"android/soong/cc"
 )
 
-// Stripper encapsulates cc.Stripper.
+// Stripper defines the stripping actions and properties for a module. The Rust
+// implementation reuses the C++ implementation.
 type Stripper struct {
 	cc.Stripper
 }
 
+// StripExecutableOrSharedLib strips a binary or shared library from its debug
+// symbols and other debug information.
 func (s *Stripper) StripExecutableOrSharedLib(ctx ModuleContext, in android.Path, out android.ModuleOutPath) {
 	ccFlags := cc.StripFlags{Toolchain: ctx.RustModule().ccToolchain(ctx)}
 	s.Stripper.StripExecutableOrSharedLib(ctx, in, out, ccFlags)
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 66877a9..a8496d9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -17,7 +17,6 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
-	"android/soong/genrule"
 )
 
 func GatherRequiredDepsForTest() string {
@@ -78,6 +77,7 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -94,6 +94,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libtest",
@@ -103,6 +104,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libprotobuf",
@@ -123,16 +125,11 @@
 			host_supported: true,
 		}
 
-` + cc.GatherRequiredDepsForTest(android.NoOsType)
+`
 	return bp
 }
 
-func CreateTestContext(config android.Config) *android.TestContext {
-	ctx := android.NewTestArchContext(config)
-	android.RegisterPrebuiltMutators(ctx)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -166,6 +163,14 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+}
+
+func CreateTestContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
+	android.RegisterPrebuiltMutators(ctx)
+	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	RegisterRequiredBuildComponentsForTest(ctx)
 
 	return ctx
 }
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index 89fb1a5..0c868ea 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -12,7 +12,7 @@
 
 export OUT_DIR=${OUT_DIR:-out}
 
-if [ -e ${OUT_DIR}/soong/.soong.in_make ]; then
+if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
   # If ${OUT_DIR} has been created without --skip-make, Soong will create an
   # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
   # expected to be supplied by the .mk files, and that might cause errors in
@@ -32,8 +32,8 @@
 
 my_get_build_var() {
   # get_build_var will run Soong in normal in-make mode where it creates
-  # .soong.in_make. That would clobber our real out directory, so we need to
-  # run it in a different one.
+  # .soong.kati_enabled. That would clobber our real out directory, so we need
+  # to run it in a different one.
   OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
 }
 
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index 63fc9a9..c271211 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -10,6 +10,7 @@
 import re
 import subprocess
 import sys
+import xml.etree.ElementTree
 
 
 # The compiled allow list RE.
@@ -38,46 +39,40 @@
     return False
   return True
 
-# Pattern that matches the class descriptor in a "Class descriptor" line output
-# by dexdump and extracts the class name - with / instead of .
-CLASS_DESCRIPTOR_RE = re.compile("'L([^;]+);'")
-
 def CheckDexJar(dexdump_path, allow_list_path, jar):
   """Check a dex jar file.
   """
-  # Get the class descriptor lines in the dexdump output. This filters out lines
-  # that do not contain class descriptors to reduce the size of the data read by
-  # this script.
-  p = subprocess.Popen(args='%s %s | grep "Class descriptor "' % (dexdump_path, 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
-  # Split the output into lines
-  lines = stdout.split('\n')
-  classes = 0
-  for line in lines:
-    # The last line will be empty
-    if line == '':
-      continue
-    # Try and find the descriptor on the line. Fail immediately if it cannot be found
-    # as the dexdump output has probably changed.
-    found = CLASS_DESCRIPTOR_RE.search(line)
-    if not found:
-      print >> sys.stderr, ('Could not find class descriptor in line `%s`' % line)
-      return False
-    # Extract the class name (using / instead of .) from the class descriptor line
-    f = found.group(1)
-    classes += 1
-    package_name = os.path.dirname(f)
-    package_name = package_name.replace('/', '.')
+
+  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, f, package_name, allow_list_path))
+                            % (jar, class_name, package_name, allow_list_path))
       return False
-  if classes == 0:
-    print >> sys.stderr, ('Error: %s does not contain any class files.' % jar)
+  if packages == 0:
+    print >> sys.stderr, ('Error: %s does not contain any packages.' % jar)
     return False
   return True
 
diff --git a/scripts/conv_linker_config.py b/scripts/conv_linker_config.py
index 81425fb..22fe9f6 100644
--- a/scripts/conv_linker_config.py
+++ b/scripts/conv_linker_config.py
@@ -18,8 +18,10 @@
 import argparse
 import collections
 import json
+import os
 
 import linker_config_pb2
+from google.protobuf.descriptor import FieldDescriptor
 from google.protobuf.json_format import ParseDict
 from google.protobuf.text_format import MessageToString
 
@@ -43,6 +45,40 @@
   print(MessageToString(pb))
 
 
+def SystemProvide(args):
+  pb = linker_config_pb2.LinkerConfig()
+  with open(args.source, 'rb') as f:
+    pb.ParseFromString(f.read())
+  libraries = args.value.split()
+
+  def IsInLibPath(lib_name):
+    lib_path = os.path.join(args.system, 'lib', lib_name)
+    lib64_path = os.path.join(args.system, 'lib64', lib_name)
+    return os.path.exists(lib_path) or os.path.islink(lib_path) or os.path.exists(lib64_path) or os.path.islink(lib64_path)
+
+  installed_libraries = list(filter(IsInLibPath, libraries))
+  for item in installed_libraries:
+    if item not in getattr(pb, 'provideLibs'):
+      getattr(pb, 'provideLibs').append(item)
+  with open(args.output, 'wb') as f:
+    f.write(pb.SerializeToString())
+
+
+def Append(args):
+  pb = linker_config_pb2.LinkerConfig()
+  with open(args.source, 'rb') as f:
+    pb.ParseFromString(f.read())
+
+  if getattr(type(pb), args.key).DESCRIPTOR.label == FieldDescriptor.LABEL_REPEATED:
+    for value in args.value.split():
+      getattr(pb, args.key).append(value)
+  else:
+    setattr(pb, args.key, args.value)
+
+  with open(args.output, 'wb') as f:
+    f.write(pb.SerializeToString())
+
+
 def GetArgParser():
   parser = argparse.ArgumentParser()
   subparsers = parser.add_subparsers()
@@ -73,6 +109,58 @@
       help='Source linker configuration file in protobuf.')
   print_proto.set_defaults(func=Print)
 
+  system_provide_libs = subparsers.add_parser(
+      'systemprovide', help='Append system provide libraries into the configuration.')
+  system_provide_libs.add_argument(
+      '-s',
+      '--source',
+      required=True,
+      type=str,
+      help='Source linker configuration file in protobuf.')
+  system_provide_libs.add_argument(
+      '-o',
+      '--output',
+      required=True,
+      type=str,
+      help='Target linker configuration file to write in protobuf.')
+  system_provide_libs.add_argument(
+      '--value',
+      required=True,
+      type=str,
+      help='Values of the libraries to append. If there are more than one it should be separated by empty space')
+  system_provide_libs.add_argument(
+      '--system',
+      required=True,
+      type=str,
+      help='Path of the system image.')
+  system_provide_libs.set_defaults(func=SystemProvide)
+
+  append = subparsers.add_parser(
+      'append', help='Append value(s) to given key.')
+  append.add_argument(
+      '-s',
+      '--source',
+      required=True,
+      type=str,
+      help='Source linker configuration file in protobuf.')
+  append.add_argument(
+      '-o',
+      '--output',
+      required=True,
+      type=str,
+      help='Target linker configuration file to write in protobuf.')
+  append.add_argument(
+      '--key',
+      required=True,
+      type=str,
+      help='.')
+  append.add_argument(
+      '--value',
+      required=True,
+      type=str,
+      help='Values of the libraries to append. If there are more than one it should be separated by empty space')
+  append.set_defaults(func=Append)
+
   return parser
 
 
diff --git a/scripts/gen_ndk_usedby_apex.sh b/scripts/gen_ndk_usedby_apex.sh
new file mode 100755
index 0000000..f143161
--- /dev/null
+++ b/scripts/gen_ndk_usedby_apex.sh
@@ -0,0 +1,72 @@
+#!/bin/bash -e
+
+# 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.
+
+# Generates NDK API txt file used by Mainline modules. NDK APIs would have value
+# "UND" in Ndx column and have suffix "@LIB_NAME" in Name column.
+# For example, current line llvm-readelf output is:
+# 1: 00000000     0     FUNC      GLOBAL  DEFAULT   UND   dlopen@LIBC
+# After the parse function below "dlopen" would be write to the output file.
+printHelp() {
+    echo "**************************** Usage Instructions ****************************"
+    echo "This script is used to generate the Mainline modules used-by NDK symbols."
+    echo ""
+    echo "To run this script use: ./ndk_usedby_module.sh \$BINARY_IMAGE_DIRECTORY \$BINARY_LLVM_PATH \$OUTPUT_FILE_PATH"
+    echo "For example: If all the module image files that you would like to run is under directory '/myModule' and output write to /myModule.txt then the command would be:"
+    echo "./ndk_usedby_module.sh /myModule \$BINARY_LLVM_PATH /myModule.txt"
+}
+
+parseReadelfOutput() {
+  while IFS= read -r line
+  do
+      if [[ $line = *FUNC*GLOBAL*UND*@* ]] ;
+      then
+          echo "$line" | sed -r 's/.*UND (.*)@.*/\1/g' >> "$2"
+      fi
+  done < "$1"
+  echo "" >> "$2"
+}
+
+unzipJarAndApk() {
+  tmpUnzippedDir="$1"/tmpUnzipped
+  [[ -e "$tmpUnzippedDir" ]] && rm -rf "$tmpUnzippedDir"
+  mkdir -p "$tmpUnzippedDir"
+  find "$1" -name "*.jar" -exec unzip -o {} -d "$tmpUnzippedDir" \;
+  find "$1" -name "*.apk" -exec unzip -o {} -d "$tmpUnzippedDir" \;
+  find "$tmpUnzippedDir" -name "*.MF" -exec rm {} \;
+}
+
+lookForExecFile() {
+  dir="$1"
+  readelf="$2"
+  find "$dir" -type f -name "*.so"  -exec "$2" --dyn-symbols {} >> "$dir"/../tmpReadelf.txt \;
+  find "$dir" -type f -perm /111 ! -name "*.so"  -exec "$2" --dyn-symbols {} >> "$dir"/../tmpReadelf.txt \;
+}
+
+if [[ "$1" == "help" ]]
+then
+  printHelp
+elif [[ "$#" -ne 3 ]]
+then
+  echo "Wrong argument length. Expecting 3 argument representing image file directory, llvm-readelf tool path, output path."
+else
+  unzipJarAndApk "$1"
+  lookForExecFile "$1" "$2"
+  tmpReadelfOutput="$1/../tmpReadelf.txt"
+  [[ -e "$3" ]] && rm "$3"
+  parseReadelfOutput "$tmpReadelfOutput" "$3"
+  [[ -e "$tmpReadelfOutput" ]] && rm "$tmpReadelfOutput"
+  rm -rf "$1/tmpUnzipped"
+fi
\ No newline at end of file
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..b44f66e 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: {
@@ -948,21 +976,21 @@
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     test: {
         jars: ["sdk_library/test/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/test/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/test/myjavalib_stub_sources"],
         current_api: "sdk_library/test/myjavalib.txt",
         removed_api: "sdk_library/test/myjavalib-removed.txt",
         sdk_version: "test_current",
@@ -977,21 +1005,21 @@
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     test: {
         jars: ["sdk_library/test/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/test/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/test/myjavalib_stub_sources"],
         current_api: "sdk_library/test/myjavalib.txt",
         removed_api: "sdk_library/test/myjavalib-removed.txt",
         sdk_version: "test_current",
@@ -1045,10 +1073,11 @@
     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"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "none",
@@ -1059,10 +1088,11 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "none",
@@ -1112,10 +1142,11 @@
     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"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1126,10 +1157,11 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1186,14 +1218,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
@@ -1208,14 +1240,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
@@ -1279,21 +1311,21 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     module_lib: {
         jars: ["sdk_library/module-lib/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources"],
         current_api: "sdk_library/module-lib/myjavalib.txt",
         removed_api: "sdk_library/module-lib/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1308,21 +1340,21 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     module_lib: {
         jars: ["sdk_library/module-lib/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources"],
         current_api: "sdk_library/module-lib/myjavalib.txt",
         removed_api: "sdk_library/module-lib/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1387,14 +1419,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system_server: {
         jars: ["sdk_library/system-server/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources"],
         current_api: "sdk_library/system-server/myjavalib.txt",
         removed_api: "sdk_library/system-server/myjavalib-removed.txt",
         sdk_version: "system_server_current",
@@ -1409,14 +1441,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system_server: {
         jars: ["sdk_library/system-server/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources"],
         current_api: "sdk_library/system-server/myjavalib.txt",
         removed_api: "sdk_library/system-server/myjavalib-removed.txt",
         sdk_version: "system_server_current",
@@ -1476,7 +1508,7 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1492,7 +1524,7 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1547,11 +1579,12 @@
     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: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1562,11 +1595,12 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     doctag_files: ["doctags/docs/known_doctags"],
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
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 5f520e5..91aa879 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -87,7 +87,7 @@
 	// * Configure that we are inside make
 	// * Add CommonOS to ensure that androidmk processing works.
 	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetInMakeForTests(config)
+	android.SetKatiEnabledForTests(config)
 	config.Targets[android.CommonOS] = []android.Target{
 		{android.CommonOS, android.Arch{ArchType: android.Common}, android.NativeBridgeDisabled, "", "", true},
 	}
diff --git a/sdk/update.go b/sdk/update.go
index 7bf5dea..377aaae 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -92,7 +92,7 @@
 }
 
 func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
-	rb := android.NewRuleBuilder()
+	rb := android.NewRuleBuilder(pctx, ctx)
 
 	content := gf.content.String()
 
@@ -108,7 +108,7 @@
 		Text("| sed 's/\\\\n/\\n/g' >").Output(gf.path)
 	rb.Command().
 		Text("chmod a+x").Output(gf.path)
-	rb.Build(pctx, ctx, gf.path.Base(), "Build "+gf.path.Base())
+	rb.Build(gf.path.Base(), "Build "+gf.path.Base())
 }
 
 // Collect all the members.
@@ -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.go b/sh/sh_binary.go
index 7e5c344..f86e1fd 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -398,7 +398,7 @@
 			func(entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
 				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
-				entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", s.testProperties.Test_suites...)
+				entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
 				}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 1740ba8..6a53414 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// sysprop package defines a module named sysprop_library that can implement sysprop as API
+// See https://source.android.com/devices/architecture/sysprops-apis for details
 package sysprop
 
 import (
@@ -71,6 +73,8 @@
 	})
 }
 
+// syspropJavaGenRule module generates srcjar containing generated java APIs.
+// It also depends on check api rule, so api check has to pass to use sysprop_library.
 func (g *syspropJavaGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	var checkApiFileTimeStamp android.WritablePath
 
@@ -170,6 +174,7 @@
 	syspropLibrariesLock sync.Mutex
 )
 
+// List of sysprop_library used by property_contexts to perform type check.
 func syspropLibraries(config android.Config) *[]string {
 	return config.Once(syspropLibrariesKey, func() interface{} {
 		return &[]string{}
@@ -192,7 +197,7 @@
 	return m.properties.Property_owner
 }
 
-func (m *syspropLibrary) CcModuleName() string {
+func (m *syspropLibrary) CcImplementationModuleName() string {
 	return "lib" + m.BaseModuleName()
 }
 
@@ -223,6 +228,8 @@
 	return m.currentApiFile
 }
 
+// GenerateAndroidBuildActions of sysprop_library handles API dump and API check.
+// generated java_library will depend on these API files.
 func (m *syspropLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	baseModuleName := m.BaseModuleName()
 
@@ -240,18 +247,19 @@
 	m.latestApiFile = android.PathForSource(ctx, ctx.ModuleDir(), "api", baseModuleName+"-latest.txt")
 
 	// dump API rule
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	m.dumpedApiFile = android.PathForModuleOut(ctx, "api-dump.txt")
 	rule.Command().
-		BuiltTool(ctx, "sysprop_api_dump").
+		BuiltTool("sysprop_api_dump").
 		Output(m.dumpedApiFile).
 		Inputs(android.PathsForModuleSrc(ctx, m.properties.Srcs))
-	rule.Build(pctx, ctx, baseModuleName+"_api_dump", baseModuleName+" api dump")
+	rule.Build(baseModuleName+"_api_dump", baseModuleName+" api dump")
 
 	// check API rule
-	rule = android.NewRuleBuilder()
+	rule = android.NewRuleBuilder(pctx, ctx)
 
-	// 1. current.txt <-> api_dump.txt
+	// 1. compares current.txt to api-dump.txt
+	// current.txt should be identical to api-dump.txt.
 	msg := fmt.Sprintf(`\n******************************\n`+
 		`API of sysprop_library %s doesn't match with current.txt\n`+
 		`Please update current.txt by:\n`+
@@ -267,7 +275,8 @@
 		Flag(`"` + msg + `"`).
 		Text("; exit 38) )")
 
-	// 2. current.txt <-> latest.txt
+	// 2. compares current.txt to latest.txt (frozen API)
+	// current.txt should be compatible with latest.txt
 	msg = fmt.Sprintf(`\n******************************\n`+
 		`API of sysprop_library %s doesn't match with latest version\n`+
 		`Please fix the breakage and rebuild.\n`+
@@ -275,7 +284,7 @@
 
 	rule.Command().
 		Text("( ").
-		BuiltTool(ctx, "sysprop_api_checker").
+		BuiltTool("sysprop_api_checker").
 		Input(m.latestApiFile).
 		Input(m.currentApiFile).
 		Text(" || ( echo").Flag("-e").
@@ -288,7 +297,7 @@
 		Text("touch").
 		Output(m.checkApiFileTimeStamp)
 
-	rule.Build(pctx, ctx, baseModuleName+"_check_api", baseModuleName+" check api")
+	rule.Build(baseModuleName+"_check_api", baseModuleName+" check api")
 }
 
 func (m *syspropLibrary) AndroidMk() android.AndroidMkData {
@@ -410,14 +419,16 @@
 	installedInVendorOrOdm := ctx.SocSpecific() || ctx.DeviceSpecific()
 	installedInProduct := ctx.ProductSpecific()
 	isOwnerPlatform := false
-	var stub string
+	var javaSyspropStub string
 
+	// javaSyspropStub contains stub libraries used by generated APIs, instead of framework stub.
+	// This is to make sysprop_library link against core_current.
 	if installedInVendorOrOdm {
-		stub = "sysprop-library-stub-vendor"
+		javaSyspropStub = "sysprop-library-stub-vendor"
 	} else if installedInProduct {
-		stub = "sysprop-library-stub-product"
+		javaSyspropStub = "sysprop-library-stub-product"
 	} else {
-		stub = "sysprop-library-stub-platform"
+		javaSyspropStub = "sysprop-library-stub-platform"
 	}
 
 	switch m.Owner() {
@@ -441,8 +452,10 @@
 			"Unknown value %s: must be one of Platform, Vendor or Odm", m.Owner())
 	}
 
+	// Generate a C++ implementation library.
+	// cc_library can receive *.sysprop files as their srcs, generating sources itself.
 	ccProps := ccLibraryProperties{}
-	ccProps.Name = proptools.StringPtr(m.CcModuleName())
+	ccProps.Name = proptools.StringPtr(m.CcImplementationModuleName())
 	ccProps.Srcs = m.properties.Srcs
 	ccProps.Soc_specific = proptools.BoolPtr(ctx.SocSpecific())
 	ccProps.Device_specific = proptools.BoolPtr(ctx.DeviceSpecific())
@@ -463,15 +476,17 @@
 
 	// We need to only use public version, if the partition where sysprop_library will be installed
 	// is different from owner.
-
 	if ctx.ProductSpecific() {
-		// Currently product partition can't own any sysprop_library.
+		// Currently product partition can't own any sysprop_library. So product always uses public.
 		scope = "public"
 	} else if isOwnerPlatform && installedInVendorOrOdm {
 		// Vendor or Odm should use public version of Platform's sysprop_library.
 		scope = "public"
 	}
 
+	// Generate a Java implementation library.
+	// Contrast to C++, syspropJavaGenRule module will generate srcjar and the srcjar will be fed
+	// to Java implementation library.
 	ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{
 		Srcs:  m.properties.Srcs,
 		Scope: scope,
@@ -486,7 +501,7 @@
 		Product_specific: proptools.BoolPtr(ctx.ProductSpecific()),
 		Installable:      m.properties.Installable,
 		Sdk_version:      proptools.StringPtr("core_current"),
-		Libs:             []string{stub},
+		Libs:             []string{javaSyspropStub},
 	})
 
 	// if platform sysprop_library is installed in /system or /system-ext, we regard it as an API
@@ -505,11 +520,13 @@
 			Srcs:        []string{":" + m.javaGenPublicStubName()},
 			Installable: proptools.BoolPtr(false),
 			Sdk_version: proptools.StringPtr("core_current"),
-			Libs:        []string{stub},
+			Libs:        []string{javaSyspropStub},
 			Stem:        proptools.StringPtr(m.BaseModuleName()),
 		})
 	}
 
+	// syspropLibraries will be used by property_contexts to check types.
+	// Record absolute paths of sysprop_library to prevent soong_namespace problem.
 	if m.ExportedToMake() {
 		syspropLibrariesLock.Lock()
 		defer syspropLibrariesLock.Unlock()
@@ -519,6 +536,8 @@
 	}
 }
 
+// syspropDepsMutator adds dependencies from java implementation library to sysprop library.
+// java implementation library then depends on check API rule of sysprop library.
 func syspropDepsMutator(ctx android.BottomUpMutatorContext) {
 	if m, ok := ctx.Module().(*syspropLibrary); ok {
 		ctx.AddReverseDependency(m, nil, m.javaGenModuleName())
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index b35f831..27d71e8 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -188,7 +188,7 @@
 }
 
 func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
-	testSuites []string, autoGenConfig *bool) android.Path {
+	testSuites []string, autoGenConfig *bool, unitTest *bool) android.Path {
 	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
@@ -198,7 +198,11 @@
 			if ctx.Device() {
 				autogenTemplate(ctx, autogenPath, "${JavaTestConfigTemplate}", nil, "")
 			} else {
-				autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", nil, "")
+				if Bool(unitTest) {
+					autogenTemplate(ctx, autogenPath, "${JavaHostUnitTestConfigTemplate}", nil, "")
+				} else {
+					autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", nil, "")
+				}
 			}
 		}
 		return autogenPath
diff --git a/tradefed/config.go b/tradefed/config.go
index f7e8349..f3566a8 100644
--- a/tradefed/config.go
+++ b/tradefed/config.go
@@ -27,6 +27,7 @@
 	pctx.SourcePathVariable("InstrumentationTestConfigTemplate", "build/make/core/instrumentation_test_config_template.xml")
 	pctx.SourcePathVariable("JavaTestConfigTemplate", "build/make/core/java_test_config_template.xml")
 	pctx.SourcePathVariable("JavaHostTestConfigTemplate", "build/make/core/java_host_test_config_template.xml")
+	pctx.SourcePathVariable("JavaHostUnitTestConfigTemplate", "build/make/core/java_host_unit_test_config_template.xml")
 	pctx.SourcePathVariable("NativeBenchmarkTestConfigTemplate", "build/make/core/native_benchmark_test_config_template.xml")
 	pctx.SourcePathVariable("NativeHostTestConfigTemplate", "build/make/core/native_host_test_config_template.xml")
 	pctx.SourcePathVariable("NativeTestConfigTemplate", "build/make/core/native_test_config_template.xml")
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index 4b03bc1..cc29aaf 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -15,6 +15,8 @@
 package build
 
 import (
+	"bytes"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -24,6 +26,31 @@
 	"android/soong/ui/metrics"
 )
 
+func getBazelInfo(ctx Context, config Config, bazelExecutable string, bazelEnv map[string]string, query string) string {
+	infoCmd := Command(ctx, config, "bazel", bazelExecutable)
+
+	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",
+		query,
+	)
+
+	for k, v := range bazelEnv {
+		infoCmd.Environment.Set(k, v)
+	}
+
+	infoCmd.Dir = filepath.Join(config.OutDir(), "..")
+
+	queryResult := strings.TrimSpace(string(infoCmd.OutputOrFatal()))
+	return queryResult
+}
+
+// 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()
@@ -39,63 +66,130 @@
 		outputGroups = strings.Join(config.ninjaArgs, ",")
 	}
 
-	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())
+	// Environment variables are the primary mechanism to pass information from
+	// soong_ui configuration or context to Bazel.
+	bazelEnv := make(map[string]string)
 
+	// 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.
+	bazelEnv["COMBINED_NINJA"] = config.CombinedNinjaFile()
+	bazelEnv["KATI_NINJA"] = config.KatiBuildNinjaFile()
+	bazelEnv["PACKAGE_NINJA"] = config.KatiPackageNinjaFile()
+	bazelEnv["SOONG_NINJA"] = config.SoongNinjaFile()
+
+	bazelEnv["DIST_DIR"] = config.DistDir()
+	bazelEnv["SHELL"] = "/bin/bash"
+
+	// `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,
 		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)...)
+	if config.UseRBE() {
+		for _, envVar := range []string{
+			// RBE client
+			"RBE_compare",
+			"RBE_exec_strategy",
+			"RBE_invocation_id",
+			"RBE_log_dir",
+			"RBE_platform",
+			"RBE_remote_accept_cache",
+			"RBE_remote_update_cache",
+			"RBE_server_address",
+			// TODO: remove old FLAG_ variables.
+			"FLAG_compare",
+			"FLAG_exec_root",
+			"FLAG_exec_strategy",
+			"FLAG_invocation_id",
+			"FLAG_log_dir",
+			"FLAG_platform",
+			"FLAG_remote_accept_cache",
+			"FLAG_remote_update_cache",
+			"FLAG_server_address",
+		} {
+			cmd.Args = append(cmd.Args,
+				"--action_env="+envVar)
+		}
+
+		// We need to calculate --RBE_exec_root ourselves
+		ctx.Println("Getting Bazel execution_root...")
+		cmd.Args = append(cmd.Args, "--action_env=RBE_exec_root="+getBazelInfo(ctx, config, bazelExecutable, bazelEnv, "execution_root"))
 	}
 
+	// 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)
+	}
+
+	// Append custom build flags to the Bazel command. Changes to these flags
+	// may invalidate Bazel's analysis cache.
+	// These should be appended as the final args, so that they take precedence.
+	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(),
 	)
 
-	cmd.Environment.Set("DIST_DIR", config.DistDir())
-	cmd.Environment.Set("SHELL", "/bin/bash")
-
-	ctx.Println(cmd.Cmd)
+	// Execute the command at the root of the directory.
 	cmd.Dir = filepath.Join(config.OutDir(), "..")
-	ctx.Status.Status("Starting Bazel..")
-	cmd.RunAndStreamOrFatal()
 
-	// 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)...)
+	for k, v := range bazelEnv {
+		cmd.Environment.Set(k, v)
 	}
 
-	infoCmd.Args = append(infoCmd.Args,
-		"info",
-		"output_path",
-	)
+	// Make a human-readable version of the bazelEnv map
+	bazelEnvStringBuffer := new(bytes.Buffer)
+	for k, v := range bazelEnv {
+		fmt.Fprintf(bazelEnvStringBuffer, "%s=%s ", k, v)
+	}
 
-	infoCmd.Environment.Set("DIST_DIR", config.DistDir())
-	infoCmd.Environment.Set("SHELL", "/bin/bash")
-	infoCmd.Dir = filepath.Join(config.OutDir(), "..")
-	ctx.Status.Status("Getting Bazel Info..")
-	outputBasePath := string(infoCmd.OutputOrFatal())
+	// Print the implicit command line
+	ctx.Println("Bazel implicit command line: " + strings.Join(cmd.Environment.Environ(), " ") + " " + cmd.Cmd.String() + "\n")
+
+	// Print the explicit command line too
+	ctx.Println("Bazel explicit command line: " + bazelEnvStringBuffer.String() + cmd.Cmd.String() + "\n")
+
+	// 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.
+
+	ctx.Println("Getting Bazel output_path...")
+	outputBasePath := getBazelInfo(ctx, config, bazelExecutable, bazelEnv, "output_path")
 	// TODO: Don't hardcode out/ as the bazel output directory. This is
 	// currently hardcoded as ninja_build.output_root.
 	bazelNinjaBuildOutputRoot := filepath.Join(outputBasePath, "..", "out")
 
+	ctx.Println("Creating output symlinks..")
 	symlinkOutdir(ctx, config, bazelNinjaBuildOutputRoot, ".")
 }
 
@@ -108,27 +202,39 @@
 	if err != nil {
 		ctx.Fatal(err)
 	}
+
 	for _, f := range files {
+		// The original Bazel file path
 		destPath := filepath.Join(destDir, f.Name())
+
+		// The desired Soong file path
 		srcPath := filepath.Join(config.OutDir(), relativePath, f.Name())
-		if statResult, err := os.Stat(srcPath); err == nil {
-			if statResult.Mode().IsDir() && f.IsDir() {
-				// Directory under OutDir already exists, so recurse on its contents.
+
+		destLstatResult, destLstatErr := os.Lstat(destPath)
+		if destLstatErr != nil {
+			ctx.Fatalf("Unable to Lstat dest %s: %s", destPath, destLstatErr)
+		}
+
+		srcLstatResult, srcLstatErr := os.Lstat(srcPath)
+
+		if srcLstatErr == nil {
+			if srcLstatResult.IsDir() && destLstatResult.IsDir() {
+				// src and dest are both existing dirs - recurse on the dest dir contents...
 				symlinkOutdir(ctx, config, rootPath, filepath.Join(relativePath, f.Name()))
-			} else if !statResult.Mode().IsDir() && !f.IsDir() {
-				// File exists both in source and destination, and it's not a directory
-				// in either location. Do nothing.
-				// This can arise for files which are generated under OutDir outside of
-				// soong_build, such as .bootstrap files.
 			} else {
-				// File is a directory in one location but not the other. Raise an error.
-				ctx.Fatalf("Could not link %s to %s due to conflict", srcPath, destPath)
+				// Ignore other pre-existing src files (could be pre-existing files, directories, symlinks, ...)
+				// This can arise for files which are generated under OutDir outside of soong_build, such as .bootstrap files.
+				// FIXME: This might cause a problem later e.g. if a symlink in the build graph changes...
 			}
-		} else if os.IsNotExist(err) {
-			// Create symlink srcPath -> fullDestPath.
-			os.Symlink(destPath, srcPath)
 		} else {
-			ctx.Fatalf("Unable to stat %s: %s", srcPath, err)
+			if !os.IsNotExist(srcLstatErr) {
+				ctx.Fatalf("Unable to Lstat src %s: %s", srcPath, srcLstatErr)
+			}
+
+			// src does not exist, so try to create a src -> dest symlink (i.e. a Soong path -> Bazel path symlink)
+			if symlinkErr := os.Symlink(destPath, srcPath); symlinkErr != nil {
+				ctx.Fatalf("Unable to create symlink %s -> %s due to error %s", srcPath, destPath, symlinkErr)
+			}
 		}
 	}
 }
diff --git a/ui/build/build.go b/ui/build/build.go
index 1cf2023..e8f0fc4 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -23,13 +23,20 @@
 	"android/soong/ui/metrics"
 )
 
-// Ensures the out directory exists, and has the proper files to prevent kati
-// from recursing into it.
+// SetupOutDir ensures the out directory exists, and has the proper files to
+// prevent kati from recursing into it.
 func SetupOutDir(ctx Context, config Config) {
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
-	if !config.SkipMake() {
-		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.in_make"))
+	if !config.SkipKati() {
+		// Run soong_build with Kati for a hybrid build, e.g. running the
+		// AndroidMk singleton and postinstall commands. Communicate this to
+		// soong_build by writing an empty .soong.kati_enabled marker file in the
+		// soong_build output directory for the soong_build primary builder to
+		// know if the user wants to run Kati after.
+		//
+		// This does not preclude running Kati for *product configuration purposes*.
+		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.kati_enabled"))
 	}
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
@@ -37,7 +44,7 @@
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
 
 	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
-		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0777)
+		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
 		if err != nil {
 			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
 		}
@@ -60,8 +67,8 @@
 `))
 
 func createCombinedBuildNinjaFile(ctx Context, config Config) {
-	// If we're in SkipMake mode, skip creating this file if it already exists
-	if config.SkipMake() {
+	// If we're in SkipKati mode, skip creating this file if it already exists
+	if config.SkipKati() {
 		if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
 			return
 		}
@@ -78,6 +85,7 @@
 	}
 }
 
+// These are bitmasks which can be used to check whether various flags are set e.g. whether to use Bazel.
 const (
 	BuildNone          = iota
 	BuildProductConfig = 1 << iota
@@ -90,6 +98,7 @@
 	BuildAllWithBazel  = BuildProductConfig | BuildSoong | BuildKati | BuildBazel
 )
 
+// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
 func checkProblematicFiles(ctx Context) {
 	files := []string{"Android.mk", "CleanSpec.mk"}
 	for _, file := range files {
@@ -101,6 +110,7 @@
 	}
 }
 
+// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
 func checkCaseSensitivity(ctx Context, config Config) {
 	outDir := config.OutDir()
 	lowerCase := filepath.Join(outDir, "casecheck.txt")
@@ -108,13 +118,11 @@
 	lowerData := "a"
 	upperData := "B"
 
-	err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777)
-	if err != nil {
+	if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw
 		ctx.Fatalln("Failed to check case sensitivity:", err)
 	}
 
-	err = ioutil.WriteFile(upperCase, []byte(upperData), 0777)
-	if err != nil {
+	if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw
 		ctx.Fatalln("Failed to check case sensitivity:", err)
 	}
 
@@ -132,18 +140,15 @@
 	}
 }
 
-func help(ctx Context, config Config, what int) {
+// help prints a help/usage message, via the build/make/help.sh script.
+func help(ctx Context, config Config) {
 	cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
 	cmd.Sandbox = dumpvarsSandbox
 	cmd.RunAndPrintOrFatal()
 }
 
-// Build the tree. The 'what' argument can be used to chose which components of
-// the build to run.
-func Build(ctx Context, config Config, what int) {
-	ctx.Verboseln("Starting build with args:", config.Arguments())
-	ctx.Verboseln("Environment:", config.Environment().Environ())
-
+// checkRAM warns if there probably isn't enough RAM to complete a build.
+func checkRAM(ctx Context, config Config) {
 	if totalRAM := config.TotalRAM(); totalRAM != 0 {
 		ram := float32(totalRAM) / (1024 * 1024 * 1024)
 		ctx.Verbosef("Total RAM: %.3vGB", ram)
@@ -159,24 +164,24 @@
 			ctx.Println("-j value.")
 			ctx.Println("************************************************************")
 		} else if ram <= float32(config.Parallel()) {
+			// Want at least 1GB of RAM per job.
 			ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
 			ctx.Println("If you run into segfaults or other errors, try a lower -j value")
 		}
 	}
+}
+
+// Build the tree. The 'what' argument can be used to chose which components of
+// the build to run, via checking various bitmasks.
+func Build(ctx Context, config Config, what int) {
+	ctx.Verboseln("Starting build with args:", config.Arguments())
+	ctx.Verboseln("Environment:", config.Environment().Environ())
 
 	ctx.BeginTrace(metrics.Total, "total")
 	defer ctx.EndTrace()
 
-	if config.SkipMake() {
-		ctx.Verboseln("Skipping Make/Kati as requested")
-		what = what & (BuildSoong | BuildNinja)
-	}
-
 	if inList("help", config.Arguments()) {
-		help(ctx, config, what)
-		return
-	} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
-		clean(ctx, config, what)
+		help(ctx, config)
 		return
 	}
 
@@ -184,16 +189,35 @@
 	buildLock := BecomeSingletonOrFail(ctx, config)
 	defer buildLock.Unlock()
 
+	if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
+		clean(ctx, config)
+		return
+	}
+
+	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
 	checkProblematicFiles(ctx)
 
+	checkRAM(ctx, config)
+
 	SetupOutDir(ctx, config)
 
+	// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
 	checkCaseSensitivity(ctx, config)
 
 	ensureEmptyDirectoriesExist(ctx, config.TempDir())
 
 	SetupPath(ctx, config)
 
+	if config.SkipConfig() {
+		ctx.Verboseln("Skipping Config as requested")
+		what = what &^ BuildProductConfig
+	}
+
+	if config.SkipKati() {
+		ctx.Verboseln("Skipping Kati as requested")
+		what = what &^ BuildKati
+	}
+
 	if config.StartGoma() {
 		// Ensure start Goma compiler_proxy
 		startGoma(ctx, config)
@@ -209,14 +233,18 @@
 		runMakeProductConfig(ctx, config)
 	}
 
+	// Everything below here depends on product config.
+
 	if inList("installclean", config.Arguments()) ||
 		inList("install-clean", config.Arguments()) {
-		installClean(ctx, config, what)
+		installClean(ctx, config)
 		ctx.Println("Deleted images and staging directories.")
 		return
-	} else if inList("dataclean", config.Arguments()) ||
+	}
+
+	if inList("dataclean", config.Arguments()) ||
 		inList("data-clean", config.Arguments()) {
-		dataClean(ctx, config, what)
+		dataClean(ctx, config)
 		ctx.Println("Deleted data files.")
 		return
 	}
@@ -233,7 +261,7 @@
 		runKatiBuild(ctx, config)
 		runKatiPackage(ctx, config)
 
-		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
+		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
 	} else {
 		// Load last Kati Suffix if it exists
 		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
@@ -252,7 +280,7 @@
 	}
 
 	if what&BuildNinja != 0 {
-		if !config.SkipMake() {
+		if what&BuildKati != 0 {
 			installCleanIfNecessary(ctx, config)
 		}
 
@@ -260,6 +288,7 @@
 		runNinja(ctx, config)
 	}
 
+	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
 	if what&BuildBazel != 0 {
 		runBazel(ctx, config)
 	}
@@ -275,14 +304,11 @@
 	subDir := filepath.Join(subDirs...)
 	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
 
-	err := os.MkdirAll(destDir, 0777)
-	if err != nil {
+	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
 	}
 
-	err = gzipFileToDir(src, destDir)
-	if err != nil {
+	if err := gzipFileToDir(src, destDir); err != nil {
 		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
 	}
 }
@@ -297,14 +323,11 @@
 	subDir := filepath.Join(subDirs...)
 	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
 
-	err := os.MkdirAll(destDir, 0777)
-	if err != nil {
+	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
 	}
 
-	_, err = copyFile(src, filepath.Join(destDir, filepath.Base(src)))
-	if err != nil {
+	if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
 		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
 	}
 }
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index 03e884a..06f6c63 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -26,8 +26,11 @@
 	"android/soong/ui/metrics"
 )
 
+// Given a series of glob patterns, remove matching files and directories from the filesystem.
+// For example, "malware*" would remove all files and directories in the current directory that begin with "malware".
 func removeGlobs(ctx Context, globs ...string) {
 	for _, glob := range globs {
+		// Find files and directories that match this glob pattern.
 		files, err := filepath.Glob(glob)
 		if err != nil {
 			// Only possible error is ErrBadPattern
@@ -45,13 +48,15 @@
 
 // Remove everything under the out directory. Don't remove the out directory
 // itself in case it's a symlink.
-func clean(ctx Context, config Config, what int) {
+func clean(ctx Context, config Config) {
 	removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
 	ctx.Println("Entire build directory removed.")
 }
 
-func dataClean(ctx Context, config Config, what int) {
+// Remove everything in the data directory.
+func dataClean(ctx Context, config Config) {
 	removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*"))
+	ctx.Println("Entire data directory removed.")
 }
 
 // installClean deletes all of the installed files -- the intent is to remove
@@ -61,8 +66,8 @@
 //
 // This is faster than a full clean, since we're not deleting the
 // intermediates.  Instead of recompiling, we can just copy the results.
-func installClean(ctx Context, config Config, what int) {
-	dataClean(ctx, config, what)
+func installClean(ctx Context, config Config) {
+	dataClean(ctx, config)
 
 	if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" {
 		hostCrossOut := func(path string) string {
@@ -145,85 +150,95 @@
 // Since products and build variants (unfortunately) shared the same
 // PRODUCT_OUT staging directory, things can get out of sync if different
 // build configurations are built in the same tree. This function will
-// notice when the configuration has changed and call installclean to
+// notice when the configuration has changed and call installClean to
 // remove the files necessary to keep things consistent.
 func installCleanIfNecessary(ctx Context, config Config) {
 	configFile := config.DevicePreviousProductConfig()
 	prefix := "PREVIOUS_BUILD_CONFIG := "
 	suffix := "\n"
-	currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
+	currentConfig := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
 
 	ensureDirectoriesExist(ctx, filepath.Dir(configFile))
 
 	writeConfig := func() {
-		err := ioutil.WriteFile(configFile, []byte(currentProduct), 0666)
+		err := ioutil.WriteFile(configFile, []byte(currentConfig), 0666) // a+rw
 		if err != nil {
 			ctx.Fatalln("Failed to write product config:", err)
 		}
 	}
 
-	prev, err := ioutil.ReadFile(configFile)
+	previousConfigBytes, err := ioutil.ReadFile(configFile)
 	if err != nil {
 		if os.IsNotExist(err) {
+			// Just write the new config file, no old config file to worry about.
 			writeConfig()
 			return
 		} else {
 			ctx.Fatalln("Failed to read previous product config:", err)
 		}
-	} else if string(prev) == currentProduct {
+	}
+
+	previousConfig := string(previousConfigBytes)
+	if previousConfig == currentConfig {
+		// Same config as before - nothing to clean.
 		return
 	}
 
-	if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" {
-		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.")
+	if config.Environment().IsEnvTrue("DISABLE_AUTO_INSTALLCLEAN") {
+		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set and true; skipping auto-clean. Your tree may be in an inconsistent state.")
 		return
 	}
 
 	ctx.BeginTrace(metrics.PrimaryNinja, "installclean")
 	defer ctx.EndTrace()
 
-	prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix)
-	currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix)
+	previousProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(previousConfig, suffix), prefix)
+	currentProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(currentConfig, suffix), prefix)
 
-	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig)
+	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", previousProductAndVariant, currentProductAndVariant)
 
-	installClean(ctx, config, 0)
+	installClean(ctx, config)
 
 	writeConfig()
 }
 
 // cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
 // the filesystem if they were removed from the input file since the last execution.
-func cleanOldFiles(ctx Context, basePath, file string) {
-	file = filepath.Join(basePath, file)
-	oldFile := file + ".previous"
+func cleanOldFiles(ctx Context, basePath, newFile string) {
+	newFile = filepath.Join(basePath, newFile)
+	oldFile := newFile + ".previous"
 
-	if _, err := os.Stat(file); err != nil {
-		ctx.Fatalf("Expected %q to be readable", file)
+	if _, err := os.Stat(newFile); err != nil {
+		ctx.Fatalf("Expected %q to be readable", newFile)
 	}
 
 	if _, err := os.Stat(oldFile); os.IsNotExist(err) {
-		if err := os.Rename(file, oldFile); err != nil {
-			ctx.Fatalf("Failed to rename file list (%q->%q): %v", file, oldFile, err)
+		if err := os.Rename(newFile, oldFile); err != nil {
+			ctx.Fatalf("Failed to rename file list (%q->%q): %v", newFile, oldFile, err)
 		}
 		return
 	}
 
-	var newPaths, oldPaths []string
-	if newData, err := ioutil.ReadFile(file); err == nil {
-		if oldData, err := ioutil.ReadFile(oldFile); err == nil {
-			// Common case: nothing has changed
-			if bytes.Equal(newData, oldData) {
-				return
-			}
-			newPaths = strings.Fields(string(newData))
-			oldPaths = strings.Fields(string(oldData))
-		} else {
-			ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
-		}
+	var newData, oldData []byte
+	if data, err := ioutil.ReadFile(newFile); err == nil {
+		newData = data
 	} else {
-		ctx.Fatalf("Failed to read list of installable files (%q): %v", file, err)
+		ctx.Fatalf("Failed to read list of installable files (%q): %v", newFile, err)
 	}
+	if data, err := ioutil.ReadFile(oldFile); err == nil {
+		oldData = data
+	} else {
+		ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
+	}
+
+	// Common case: nothing has changed
+	if bytes.Equal(newData, oldData) {
+		return
+	}
+
+	var newPaths, oldPaths []string
+	newPaths = strings.Fields(string(newData))
+	oldPaths = strings.Fields(string(oldData))
 
 	// These should be mostly sorted by make already, but better make sure Go concurs
 	sort.Strings(newPaths)
@@ -242,42 +257,55 @@
 				continue
 			}
 		}
+
 		// File only exists in the old list; remove if it exists
-		old := filepath.Join(basePath, oldPaths[0])
+		oldPath := filepath.Join(basePath, oldPaths[0])
 		oldPaths = oldPaths[1:]
-		if fi, err := os.Stat(old); err == nil {
-			if fi.IsDir() {
-				if err := os.Remove(old); err == nil {
-					ctx.Println("Removed directory that is no longer installed: ", old)
-					cleanEmptyDirs(ctx, filepath.Dir(old))
+
+		if oldFile, err := os.Stat(oldPath); err == nil {
+			if oldFile.IsDir() {
+				if err := os.Remove(oldPath); err == nil {
+					ctx.Println("Removed directory that is no longer installed: ", oldPath)
+					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
 				} else {
-					ctx.Println("Failed to remove directory that is no longer installed (%q): %v", old, err)
+					ctx.Println("Failed to remove directory that is no longer installed (%q): %v", oldPath, err)
 					ctx.Println("It's recommended to run `m installclean`")
 				}
 			} else {
-				if err := os.Remove(old); err == nil {
-					ctx.Println("Removed file that is no longer installed: ", old)
-					cleanEmptyDirs(ctx, filepath.Dir(old))
+				// Removing a file, not a directory.
+				if err := os.Remove(oldPath); err == nil {
+					ctx.Println("Removed file that is no longer installed: ", oldPath)
+					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
 				} else if !os.IsNotExist(err) {
-					ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", old, err)
+					ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", oldPath, err)
 				}
 			}
 		}
 	}
 
 	// Use the new list as the base for the next build
-	os.Rename(file, oldFile)
+	os.Rename(newFile, oldFile)
 }
 
+// cleanEmptyDirs will delete a directory if it contains no files.
+// If a deletion occurs, then it also recurses upwards to try and delete empty parent directories.
 func cleanEmptyDirs(ctx Context, dir string) {
 	files, err := ioutil.ReadDir(dir)
-	if err != nil || len(files) > 0 {
+	if err != nil {
+		ctx.Println("Could not read directory while trying to clean empty dirs: ", dir)
 		return
 	}
-	if err := os.Remove(dir); err == nil {
-		ctx.Println("Removed directory that is no longer installed: ", dir)
-	} else {
-		ctx.Fatalf("Failed to remove directory that is no longer installed (%q): %v", dir, err)
+	if len(files) > 0 {
+		// Directory is not empty.
+		return
 	}
+
+	if err := os.Remove(dir); err == nil {
+		ctx.Println("Removed empty directory (may no longer be installed?): ", dir)
+	} else {
+		ctx.Fatalf("Failed to remove empty directory (which may no longer be installed?) %q: (%v)", dir, err)
+	}
+
+	// Try and delete empty parent directories too.
 	cleanEmptyDirs(ctx, filepath.Dir(dir))
 }
diff --git a/ui/build/config.go b/ui/build/config.go
index 641aae6..c9911f3 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -46,7 +46,8 @@
 	verbose        bool
 	checkbuild     bool
 	dist           bool
-	skipMake       bool
+	skipConfig     bool
+	skipKati       bool
 	skipSoongTests bool
 
 	// From the product config
@@ -536,7 +537,10 @@
 		} else if arg == "showcommands" {
 			c.verbose = true
 		} else if arg == "--skip-make" {
-			c.skipMake = true
+			c.skipConfig = true
+			c.skipKati = true
+		} else if arg == "--skip-kati" {
+			c.skipKati = true
 		} else if arg == "--skip-soong-tests" {
 			c.skipSoongTests = true
 		} else if len(arg) > 0 && arg[0] == '-' {
@@ -697,12 +701,16 @@
 }
 
 func (c *configImpl) NinjaArgs() []string {
-	if c.skipMake {
+	if c.skipKati {
 		return c.arguments
 	}
 	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")
 }
@@ -736,8 +744,12 @@
 	return c.verbose
 }
 
-func (c *configImpl) SkipMake() bool {
-	return c.skipMake
+func (c *configImpl) SkipKati() bool {
+	return c.skipKati
+}
+
+func (c *configImpl) SkipConfig() bool {
+	return c.skipConfig
 }
 
 func (c *configImpl) TargetProduct() string {
diff --git a/ui/build/environment.go b/ui/build/environment.go
index 9bca7c0..6d8a28f 100644
--- a/ui/build/environment.go
+++ b/ui/build/environment.go
@@ -37,8 +37,8 @@
 // It's equivalent to the os.LookupEnv function, but with this copy of the
 // Environment.
 func (e *Environment) Get(key string) (string, bool) {
-	for _, env := range *e {
-		if k, v, ok := decodeKeyValue(env); ok && k == key {
+	for _, envVar := range *e {
+		if k, v, ok := decodeKeyValue(envVar); ok && k == key {
 			return v, true
 		}
 	}
@@ -65,37 +65,40 @@
 
 // Unset removes the specified keys from the Environment.
 func (e *Environment) Unset(keys ...string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+			// Delete this key.
 			continue
 		}
-		out = append(out, env)
+		newEnv = append(newEnv, envVar)
 	}
-	*e = out
+	*e = newEnv
 }
 
 // UnsetWithPrefix removes all keys that start with prefix.
 func (e *Environment) UnsetWithPrefix(prefix string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && strings.HasPrefix(key, prefix) {
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && strings.HasPrefix(key, prefix) {
+			// Delete this key.
 			continue
 		}
-		out = append(out, env)
+		newEnv = append(newEnv, envVar)
 	}
-	*e = out
+	*e = newEnv
 }
 
 // Allow removes all keys that are not present in the input list
 func (e *Environment) Allow(keys ...string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
-			out = append(out, env)
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+			// Keep this key.
+			newEnv = append(newEnv, envVar)
 		}
 	}
-	*e = out
+	*e = newEnv
 }
 
 // Environ returns the []string required for exec.Cmd.Env
@@ -105,11 +108,11 @@
 
 // Copy returns a copy of the Environment so that independent changes may be made.
 func (e *Environment) Copy() *Environment {
-	ret := Environment(make([]string, len(*e)))
-	for i, v := range *e {
-		ret[i] = v
+	envCopy := Environment(make([]string, len(*e)))
+	for i, envVar := range *e {
+		envCopy[i] = envVar
 	}
-	return &ret
+	return &envCopy
 }
 
 // IsTrue returns whether an environment variable is set to a positive value (1,y,yes,on,true)
@@ -140,15 +143,20 @@
 	return e.appendFromKati(file)
 }
 
+// Helper function for AppendFromKati. Accepts an io.Reader to make testing easier.
 func (e *Environment) appendFromKati(reader io.Reader) error {
 	scanner := bufio.NewScanner(reader)
 	for scanner.Scan() {
 		text := strings.TrimSpace(scanner.Text())
 
 		if len(text) == 0 || text[0] == '#' {
+			// Skip blank lines and comments.
 			continue
 		}
 
+		// We expect two space-delimited strings, like:
+		// unset 'HOME'
+		// export 'BEST_PIZZA_CITY'='NYC'
 		cmd := strings.SplitN(text, " ", 2)
 		if len(cmd) != 2 {
 			return fmt.Errorf("Unknown kati environment line: %q", text)
@@ -159,6 +167,8 @@
 			if !ok {
 				return fmt.Errorf("Failed to unquote kati line: %q", text)
 			}
+
+			// Actually unset it.
 			e.Unset(str)
 		} else if cmd[0] == "export" {
 			key, value, ok := decodeKeyValue(cmd[1])
@@ -175,6 +185,7 @@
 				return fmt.Errorf("Failed to unquote kati line: %q", text)
 			}
 
+			// Actually set it.
 			e.Set(key, value)
 		} else {
 			return fmt.Errorf("Unknown kati environment command: %q", text)
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/kati.go b/ui/build/kati.go
index f6c0f52..06ec646 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -33,12 +33,18 @@
 const katiCleanspecSuffix = "-cleanspec"
 const katiPackageSuffix = "-package"
 
-// genKatiSuffix creates a suffix for kati-generated files so that we can cache
-// them based on their inputs. So this should encode all common changes to Kati
-// inputs. Currently that includes the TARGET_PRODUCT, kati-processed command
-// line arguments, and the directories specified by mm/mmm.
+// genKatiSuffix creates a filename suffix for kati-generated files so that we
+// can cache them based on their inputs. Such files include the generated Ninja
+// files and env.sh environment variable setup files.
+//
+// The filename suffix should encode all common changes to Kati inputs.
+// Currently that includes the TARGET_PRODUCT and kati-processed command line
+// arguments.
 func genKatiSuffix(ctx Context, config Config) {
+	// Construct the base suffix.
 	katiSuffix := "-" + config.TargetProduct()
+
+	// Append kati arguments to the suffix.
 	if args := config.KatiArgs(); len(args) > 0 {
 		katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_"))
 	}
@@ -60,51 +66,101 @@
 	}
 }
 
+// Base function to construct and run the Kati command line with additional
+// arguments, and a custom function closure to mutate the environment Kati runs
+// in.
 func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {
 	executable := config.PrebuiltBuildTool("ckati")
+	// cKati arguments.
 	args = append([]string{
+		// Instead of executing commands directly, generate a Ninja file.
 		"--ninja",
+		// Generate Ninja files in the output directory.
 		"--ninja_dir=" + config.OutDir(),
+		// Filename suffix of the generated Ninja file.
 		"--ninja_suffix=" + config.KatiSuffix() + extraSuffix,
+		// Remove common parts at the beginning of a Ninja file, like build_dir,
+		// local_pool and _kati_always_build_. Allows Kati to be run multiple
+		// times, with generated Ninja files combined in a single invocation
+		// using 'include'.
 		"--no_ninja_prelude",
+		// Support declaring phony outputs in AOSP Ninja.
 		"--use_ninja_phony_output",
+		// Support declaring symlink outputs in AOSP Ninja.
 		"--use_ninja_symlink_outputs",
+		// Regenerate the Ninja file if environment inputs have changed. e.g.
+		// CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some
+		// $(shell ..) results.
 		"--regen",
+		// Skip '-include' directives starting with the specified path. Used to
+		// ignore generated .mk files.
 		"--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
+		// Detect the use of $(shell echo ...).
 		"--detect_android_echo",
+		// Colorful ANSI-based warning and error messages.
 		"--color_warnings",
+		// Generate all targets, not just the top level requested ones.
 		"--gen_all_targets",
+		// Use the built-in emulator of GNU find for better file finding
+		// performance. Used with $(shell find ...).
 		"--use_find_emulator",
+		// Fail when the find emulator encounters problems.
 		"--werror_find_emulator",
+		// Do not provide any built-in rules.
 		"--no_builtin_rules",
+		// Fail when suffix rules are used.
 		"--werror_suffix_rules",
-		"--warn_real_to_phony",
-		"--warn_phony_looks_real",
+		// Fail when a real target depends on a phony target.
 		"--werror_real_to_phony",
-		"--werror_phony_looks_real",
-		"--werror_writable",
+		// Makes real_to_phony checks assume that any top-level or leaf
+		// dependencies that does *not* have a '/' in it is a phony target.
 		"--top_level_phony",
+		// Fail when a phony target contains slashes.
+		"--werror_phony_looks_real",
+		// Fail when writing to a read-only directory.
+		"--werror_writable",
+		// Print Kati's internal statistics, such as the number of variables,
+		// implicit/explicit/suffix rules, and so on.
 		"--kati_stats",
 	}, args...)
 
+	// Generate a minimal Ninja file.
+	//
+	// Used for build_test and multiproduct_kati, which runs Kati several
+	// hundred times for different configurations to test file generation logic.
+	// These can result in generating Ninja files reaching ~1GB or more,
+	// resulting in ~hundreds of GBs of writes.
+	//
+	// Since we don't care about executing the Ninja files in these test cases,
+	// generating the Ninja file content wastes time, so skip writing any
+	// information out with --empty_ninja_file.
+	//
+	// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
 	if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {
 		args = append(args, "--empty_ninja_file")
 	}
 
+	// Apply 'local_pool' to to all rules that don't specify a pool.
 	if config.UseRemoteBuild() {
 		args = append(args, "--default_pool=local_pool")
 	}
 
 	cmd := Command(ctx, config, "ckati", executable, args...)
+
+	// Set up the nsjail sandbox.
 	cmd.Sandbox = katiSandbox
+
+	// Set up stdout and stderr.
 	pipe, err := cmd.StdoutPipe()
 	if err != nil {
 		ctx.Fatalln("Error getting output pipe for ckati:", err)
 	}
 	cmd.Stderr = cmd.Stdout
 
+	// Apply the caller's function closure to mutate the environment variables.
 	envFunc(cmd.Environment)
 
+	// Pass on various build environment metadata to Kati.
 	if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {
 		username := "unknown"
 		if u, err := user.Current(); err == nil {
@@ -125,6 +181,8 @@
 	}
 
 	cmd.StartOrFatal()
+	// Set up the ToolStatus command line reader for Kati for a consistent UI
+	// for the user.
 	status.KatiReader(ctx.Status.StartTool(), pipe)
 	cmd.WaitOrFatal()
 }
@@ -134,35 +192,57 @@
 	defer ctx.EndTrace()
 
 	args := []string{
+		// Mark the output directory as writable.
 		"--writable", config.OutDir() + "/",
+		// Fail when encountering implicit rules. e.g.
+		// %.foo: %.bar
+		//   cp $< $@
 		"--werror_implicit_rules",
+		// Entry point for the Kati Ninja file generation.
 		"-f", "build/make/core/main.mk",
 	}
 
 	if !config.BuildBrokenDupRules() {
+		// Fail when redefining / duplicating a target.
 		args = append(args, "--werror_overriding_commands")
 	}
 
 	args = append(args, config.KatiArgs()...)
 
 	args = append(args,
+		// Location of the Make vars .mk file generated by Soong.
 		"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
+		// Location of the Android.mk file generated by Soong. This
+		// file contains Soong modules represented as Kati modules,
+		// allowing Kati modules to depend on Soong modules.
 		"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
+		// Directory containing outputs for the target device.
 		"TARGET_DEVICE_DIR="+config.TargetDeviceDir(),
+		// Directory containing .mk files for packaging purposes, such as
+		// the dist.mk file, containing dist-for-goals data.
 		"KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())
 
 	runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
 
+	// compress and dist the main build ninja file.
 	distGzipFile(ctx, config, config.KatiBuildNinjaFile())
 
+	// Cleanup steps.
 	cleanCopyHeaders(ctx, config)
 	cleanOldInstalledFiles(ctx, config)
 }
 
+// Clean out obsolete header files on the disk that were *not copied* during the
+// build with BUILD_COPY_HEADERS and LOCAL_COPY_HEADERS.
+//
+// These should be increasingly uncommon, as it's a deprecated feature and there
+// isn't an equivalent feature in Soong.
 func cleanCopyHeaders(ctx Context, config Config) {
 	ctx.BeginTrace("clean", "clean copy headers")
 	defer ctx.EndTrace()
 
+	// Read and parse the list of copied headers from a file in the product
+	// output directory.
 	data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list"))
 	if err != nil {
 		if os.IsNotExist(err) {
@@ -178,6 +258,8 @@
 	headerDir := headers[0]
 	headers = headers[1:]
 
+	// Walk the tree and remove any headers that are not in the list of copied
+	// headers in the current build.
 	filepath.Walk(headerDir,
 		func(path string, info os.FileInfo, err error) error {
 			if err != nil {
@@ -196,6 +278,8 @@
 		})
 }
 
+// Clean out any previously installed files from the disk that are not installed
+// in the current build.
 func cleanOldInstalledFiles(ctx Context, config Config) {
 	ctx.BeginTrace("clean", "clean old installed files")
 	defer ctx.EndTrace()
@@ -213,18 +297,27 @@
 	cleanOldFiles(ctx, config.HostOut(), ".installable_test_files")
 }
 
+// Generate the Ninja file containing the packaging command lines for the dist
+// dir.
 func runKatiPackage(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunKati, "kati package")
 	defer ctx.EndTrace()
 
 	args := []string{
+		// Mark the dist dir as writable.
 		"--writable", config.DistDir() + "/",
+		// Fail when encountering implicit rules. e.g.
 		"--werror_implicit_rules",
+		// Fail when redefining / duplicating a target.
 		"--werror_overriding_commands",
+		// Entry point.
 		"-f", "build/make/packaging/main.mk",
+		// Directory containing .mk files for packaging purposes, such as
+		// the dist.mk file, containing dist-for-goals data.
 		"KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(),
 	}
 
+	// Run Kati against a restricted set of environment variables.
 	runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) {
 		env.Allow([]string{
 			// Some generic basics
@@ -251,16 +344,21 @@
 		}
 	})
 
+	// Compress and dist the packaging Ninja file.
 	distGzipFile(ctx, config, config.KatiPackageNinjaFile())
 }
 
+// Run Kati on the cleanspec files to clean the build.
 func runKatiCleanSpec(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunKati, "kati cleanspec")
 	defer ctx.EndTrace()
 
 	runKati(ctx, config, katiCleanspecSuffix, []string{
+		// Fail when encountering implicit rules. e.g.
 		"--werror_implicit_rules",
+		// Fail when redefining / duplicating a target.
 		"--werror_overriding_commands",
+		// Entry point.
 		"-f", "build/make/core/cleanbuild.mk",
 		"SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(),
 		"TARGET_DEVICE_DIR=" + config.TargetDeviceDir(),
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index fa44cb1..7799766 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -27,10 +27,16 @@
 	"android/soong/ui/status"
 )
 
+// Constructs and runs the Ninja command line with a restricted set of
+// environment variables. It's important to restrict the environment Ninja runs
+// for hermeticity reasons, and to avoid spurious rebuilds.
 func runNinja(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
 	defer ctx.EndTrace()
 
+	// Sets up the FIFO status updater that reads the Ninja protobuf output, and
+	// translates it to the soong_ui status output, displaying real-time
+	// progress of the build.
 	fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
 	nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
 	defer nr.Close()
@@ -64,8 +70,12 @@
 		"-w", "missingdepfile=err")
 
 	cmd := Command(ctx, config, "ninja", executable, args...)
+
+	// Set up the nsjail sandbox Ninja runs in.
 	cmd.Sandbox = ninjaSandbox
 	if config.HasKatiSuffix() {
+		// Reads and executes a shell script from Kati that sets/unsets the
+		// environment Ninja runs in.
 		cmd.Environment.AppendFromKati(config.KatiEnvFile())
 	}
 
@@ -78,8 +88,8 @@
 		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
 	}
 
-	logPath := filepath.Join(config.OutDir(), ".ninja_log")
 	ninjaHeartbeatDuration := time.Minute * 5
+	// Get the ninja heartbeat interval from the environment before it's filtered away later.
 	if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
 		// For example, "1m"
 		overrideDuration, err := time.ParseDuration(overrideText)
@@ -88,18 +98,22 @@
 		}
 	}
 
-	// Filter the environment, as ninja does not rebuild files when environment variables change.
+	// Filter the environment, as ninja does not rebuild files when environment
+	// variables change.
 	//
-	// Anything listed here must not change the output of rules/actions when the value changes,
-	// otherwise incremental builds may be unsafe. Vars explicitly set to stable values
-	// elsewhere in soong_ui are fine.
+	// Anything listed here must not change the output of rules/actions when the
+	// value changes, otherwise incremental builds may be unsafe. Vars
+	// explicitly set to stable values elsewhere in soong_ui are fine.
 	//
-	// For the majority of cases, either Soong or the makefiles should be replicating any
-	// necessary environment variables in the command line of each action that needs it.
+	// For the majority of cases, either Soong or the makefiles should be
+	// replicating any necessary environment variables in the command line of
+	// each action that needs it.
 	if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") {
 		ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.")
 	} else {
 		cmd.Environment.Allow(append([]string{
+			// Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based
+			// tools can symbolize crashes.
 			"ASAN_SYMBOLIZER_PATH",
 			"HOME",
 			"JAVA_HOME",
@@ -108,27 +122,23 @@
 			"OUT_DIR",
 			"PATH",
 			"PWD",
+			// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
 			"PYTHONDONTWRITEBYTECODE",
 			"TMPDIR",
 			"USER",
 
 			// TODO: remove these carefully
+			// Options for the address sanitizer.
 			"ASAN_OPTIONS",
+			// The list of Android app modules to be built in an unbundled manner.
 			"TARGET_BUILD_APPS",
+			// The variant of the product being built. e.g. eng, userdebug, debug.
 			"TARGET_BUILD_VARIANT",
+			// The product name of the product being built, e.g. aosp_arm, aosp_flame.
 			"TARGET_PRODUCT",
 			// b/147197813 - used by art-check-debug-apex-gen
 			"EMMA_INSTRUMENT_FRAMEWORK",
 
-			// Goma -- gomacc may not need all of these
-			"GOMA_DIR",
-			"GOMA_DISABLED",
-			"GOMA_FAIL_FAST",
-			"GOMA_FALLBACK",
-			"GOMA_GCE_SERVICE_ACCOUNT",
-			"GOMA_TMP_DIR",
-			"GOMA_USE_LOCAL",
-
 			// RBE client
 			"RBE_compare",
 			"RBE_exec_root",
@@ -162,6 +172,7 @@
 	cmd.Environment.Set("DIST_DIR", config.DistDir())
 	cmd.Environment.Set("SHELL", "/bin/bash")
 
+	// Print the environment variables that Ninja is operating in.
 	ctx.Verboseln("Ninja environment: ")
 	envVars := cmd.Environment.Environ()
 	sort.Strings(envVars)
@@ -169,17 +180,21 @@
 		ctx.Verbosef("  %s", envVar)
 	}
 
-	// Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
+	// Poll the Ninja log for updates regularly based on the heartbeat
+	// frequency. If it isn't updated enough, then we want to surface the
+	// possibility that Ninja is stuck, to the user.
 	done := make(chan struct{})
 	defer close(done)
 	ticker := time.NewTicker(ninjaHeartbeatDuration)
 	defer ticker.Stop()
-	checker := &statusChecker{}
+	ninjaChecker := &ninjaStucknessChecker{
+		logPath: filepath.Join(config.OutDir(), ".ninja_log"),
+	}
 	go func() {
 		for {
 			select {
 			case <-ticker.C:
-				checker.check(ctx, config, logPath)
+				ninjaChecker.check(ctx, config)
 			case <-done:
 				return
 			}
@@ -190,37 +205,36 @@
 	cmd.RunAndStreamOrFatal()
 }
 
-type statusChecker struct {
-	prevTime time.Time
+// A simple struct for checking if Ninja gets stuck, using timestamps.
+type ninjaStucknessChecker struct {
+	logPath     string
+	prevModTime time.Time
 }
 
-func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) {
-	info, err := os.Stat(pathToCheck)
-	var newTime time.Time
+// Check that a file has been modified since the last time it was checked. If
+// the mod time hasn't changed, then assume that Ninja got stuck, and print
+// diagnostics for debugging.
+func (c *ninjaStucknessChecker) check(ctx Context, config Config) {
+	info, err := os.Stat(c.logPath)
+	var newModTime time.Time
 	if err == nil {
-		newTime = info.ModTime()
+		newModTime = info.ModTime()
 	}
-	if newTime == c.prevTime {
-		// ninja may be stuck
-		dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime)
+	if newModTime == c.prevModTime {
+		// The Ninja file hasn't been modified since the last time it was
+		// checked, so Ninja could be stuck. Output some diagnostics.
+		ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime)
+
+		// The "pstree" command doesn't exist on Mac, but "pstree" on Linux
+		// gives more convenient output than "ps" So, we try pstree first, and
+		// ps second
+		commandText := fmt.Sprintf("pstree -pal %v || ps -ef", os.Getpid())
+
+		cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
+		output := cmd.CombinedOutputOrFatal()
+		ctx.Verbose(string(output))
+
+		ctx.Verbosef("done\n")
 	}
-	c.prevTime = newTime
-}
-
-// dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics
-func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) {
-
-	ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated)
-
-	// The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps"
-	// So, we try pstree first, and ps second
-	pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid())
-	psCommandText := "ps -ef"
-	commandText := pstreeCommandText + " || " + psCommandText
-
-	cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
-	output := cmd.CombinedOutputOrFatal()
-	ctx.Verbose(string(output))
-
-	ctx.Verbosef("done\n")
+	c.prevModTime = newModTime
 }
diff --git a/ui/build/path.go b/ui/build/path.go
index 6f5cf78..86e61c0 100644
--- a/ui/build/path.go
+++ b/ui/build/path.go
@@ -29,6 +29,9 @@
 	"android/soong/ui/metrics"
 )
 
+// parsePathDir returns the list of filenames of readable files in a directory.
+// This does not recurse into subdirectories, and does not contain subdirectory
+// names in the list.
 func parsePathDir(dir string) []string {
 	f, err := os.Open(dir)
 	if err != nil {
@@ -54,10 +57,12 @@
 	return ret
 }
 
-// A "lite" version of SetupPath used for dumpvars, or other places that need
-// minimal overhead (but at the expense of logging). If tmpDir is empty, the
-// default TMPDIR is used from config.
+// SetupLitePath is the "lite" version of SetupPath used for dumpvars, or other
+// places that does not need the full logging capabilities of path_interposer,
+// wants the minimal performance overhead, and still get the benefits of $PATH
+// hermeticity.
 func SetupLitePath(ctx Context, config Config, tmpDir string) {
+	// Don't replace the path twice.
 	if config.pathReplaced {
 		return
 	}
@@ -67,6 +72,7 @@
 
 	origPath, _ := config.Environment().Get("PATH")
 
+	// If tmpDir is empty, the default TMPDIR is used from config.
 	if tmpDir == "" {
 		tmpDir, _ = config.Environment().Get("TMPDIR")
 	}
@@ -74,8 +80,10 @@
 	ensureEmptyDirectoriesExist(ctx, myPath)
 
 	os.Setenv("PATH", origPath)
+	// Iterate over the ACL configuration of host tools for this build.
 	for name, pathConfig := range paths.Configuration {
 		if !pathConfig.Symlink {
+			// Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs.
 			continue
 		}
 
@@ -88,6 +96,7 @@
 			continue
 		}
 
+		// Symlink allowed host tools into a directory for hermeticity.
 		err = os.Symlink(origExec, filepath.Join(myPath, name))
 		if err != nil {
 			ctx.Fatalln("Failed to create symlink:", err)
@@ -96,14 +105,26 @@
 
 	myPath, _ = filepath.Abs(myPath)
 
+	// Set up the checked-in prebuilts path directory for the current host OS.
 	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
 	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
 
+	// Set $PATH to be the directories containing the host tool symlinks, and
+	// the prebuilts directory for the current host OS.
 	config.Environment().Set("PATH", myPath)
 	config.pathReplaced = true
 }
 
+// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
+// communicates with the interposer to validate allowed $PATH binaries at
+// runtime, using logs as a medium.
+//
+// This results in hermetic directories in $PATH containing only allowed host
+// tools for the build, and replaces $PATH to contain *only* these directories,
+// and enables an incremental restriction of tools allowed in the $PATH without
+// breaking existing use cases.
 func SetupPath(ctx Context, config Config) {
+	// Don't replace $PATH twice.
 	if config.pathReplaced {
 		return
 	}
@@ -112,9 +133,11 @@
 	defer ctx.EndTrace()
 
 	origPath, _ := config.Environment().Get("PATH")
+	// The directory containing symlinks from binaries in $PATH to the interposer.
 	myPath := filepath.Join(config.OutDir(), ".path")
 	interposer := myPath + "_interposer"
 
+	// Bootstrap the path_interposer Go binary with microfactory.
 	var cfg microfactory.Config
 	cfg.Map("android/soong", "build/soong")
 	cfg.TrimPath, _ = filepath.Abs(".")
@@ -122,15 +145,20 @@
 		ctx.Fatalln("Failed to build path interposer:", err)
 	}
 
+	// Save the original $PATH in a file.
 	if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
 		ctx.Fatalln("Failed to write original path:", err)
 	}
 
+	// Communication with the path interposer works over log entries. Set up the
+	// listener channel for the log entries here.
 	entries, err := paths.LogListener(ctx.Context, interposer+"_log")
 	if err != nil {
 		ctx.Fatalln("Failed to listen for path logs:", err)
 	}
 
+	// Loop over all log entry listener channels to validate usage of only
+	// allowed PATH tools at runtime.
 	go func() {
 		for log := range entries {
 			curPid := os.Getpid()
@@ -140,6 +168,8 @@
 					break
 				}
 			}
+			// Compute the error message along with the process tree, including
+			// parents, for this log line.
 			procPrints := []string{
 				"See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
 			}
@@ -150,6 +180,7 @@
 				}
 			}
 
+			// Validate usage against disallowed or missing PATH tools.
 			config := paths.GetConfig(log.Basename)
 			if config.Error {
 				ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
@@ -165,8 +196,10 @@
 		}
 	}()
 
+	// Create the .path directory.
 	ensureEmptyDirectoriesExist(ctx, myPath)
 
+	// Compute the full list of binaries available in the original $PATH.
 	var execs []string
 	for _, pathEntry := range filepath.SplitList(origPath) {
 		if pathEntry == "" {
@@ -185,8 +218,14 @@
 		ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
 	}
 
+	// Create symlinks from the path_interposer binary to all binaries for each
+	// directory in the original $PATH. This ensures that during the build,
+	// every call to a binary that's expected to be in the $PATH will be
+	// intercepted by the path_interposer binary, and validated with the
+	// LogEntry listener above at build time.
 	for _, name := range execs {
 		if !paths.GetConfig(name).Symlink {
+			// Ignore host tools that shouldn't be symlinked.
 			continue
 		}
 
@@ -200,11 +239,13 @@
 
 	myPath, _ = filepath.Abs(myPath)
 
-	// We put some prebuilts in $PATH, since it's infeasible to add dependencies for all of
-	// them.
+	// We put some prebuilts in $PATH, since it's infeasible to add dependencies
+	// for all of them.
 	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
 	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
 
+	// Replace the $PATH variable with the path_interposer symlinks, and
+	// checked-in prebuilts.
 	config.Environment().Set("PATH", myPath)
 	config.pathReplaced = true
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index b20237c..08c2ee1 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -30,16 +30,33 @@
 	"android/soong/ui/status"
 )
 
+// This uses Android.bp files and various tools to generate <builddir>/build.ninja.
+//
+// However, the execution of <builddir>/build.ninja happens later in build/soong/ui/build/build.go#Build()
+//
+// We want to rely on as few prebuilts as possible, so there is some bootstrapping here.
+//
+// "Microfactory" is a tool for compiling Go code. We use it to build two other tools:
+// - minibp, used to generate build.ninja files. This is really build/blueprint/bootstrap/command.go#Main()
+// - bpglob, used during incremental builds to identify files in a glob that have changed
+//
+// In reality, several build.ninja files are generated and/or used during the bootstrapping and build process.
+// See build/blueprint/bootstrap/doc.go for more information.
+//
 func runSoong(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSoong, "soong")
 	defer ctx.EndTrace()
 
+	// Use an anonymous inline function for tracing purposes (this pattern is used several times below).
 	func() {
 		ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 		defer ctx.EndTrace()
 
+		// Use validations to depend on tests.
 		args := []string{"-n"}
+
 		if !config.skipSoongTests {
+			// Run tests.
 			args = append(args, "-t")
 		}
 
@@ -133,12 +150,22 @@
 			"-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()
 	}
 
+	// This build generates .bootstrap/build.ninja, which is used in the next step.
 	ninja("minibootstrap", ".minibootstrap/build.ninja")
+
+	// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
 	ninja("bootstrap", ".bootstrap/build.ninja")
 
 	soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
@@ -146,7 +173,7 @@
 
 	distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
 
-	if !config.SkipMake() {
+	if !config.SkipKati() {
 		distGzipFile(ctx, config, config.SoongAndroidMk(), "soong")
 		distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong")
 	}
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 3164680..41acc26 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -68,6 +68,7 @@
 	miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap")
 	modulePathsDir := filepath.Join(outDir, ".module_paths")
 	variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
+
 	// dexpreopt.config is an input to the soong_docs action, which runs the
 	// soong_build primary builder. However, this file is created from $(shell)
 	// invocation at Kati parse time, so it's not an explicit output of any
@@ -75,6 +76,9 @@
 	// treated as an source file.
 	dexpreoptConfigFilePath := filepath.Join(outDir, "soong", "dexpreopt.config")
 
+	// out/build_date.txt is considered a "source file"
+	buildDatetimeFilePath := filepath.Join(outDir, "build_date.txt")
+
 	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
@@ -88,7 +92,8 @@
 			strings.HasPrefix(line, miniBootstrapDir) ||
 			strings.HasPrefix(line, modulePathsDir) ||
 			line == variablesFilePath ||
-			line == dexpreoptConfigFilePath {
+			line == dexpreoptConfigFilePath ||
+			line == buildDatetimeFilePath {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
 			// full build rules in the primary build.ninja file.
 			continue
diff --git a/ui/build/upload.go b/ui/build/upload.go
index a9346e0..4f30136 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -30,32 +30,34 @@
 )
 
 const (
+	// Used to generate a raw protobuf file that contains information
+	// of the list of metrics files from host to destination storage.
 	uploadPbFilename = ".uploader.pb"
 )
 
 var (
-	// For testing purpose
-	getTmpDir = ioutil.TempDir
+	// For testing purpose.
+	tmpDir = ioutil.TempDir
 )
 
 // UploadMetrics uploads a set of metrics files to a server for analysis. An
-// uploader full path is required to be specified in order to upload the set
-// of metrics files. This is accomplished by defining the ANDROID_ENABLE_METRICS_UPLOAD
-// environment variable. The metrics files are copied to a temporary directory
-// and the uploader is then executed in the background to allow the user to continue
-// working.
+// uploader full path is specified in ANDROID_ENABLE_METRICS_UPLOAD environment
+// variable in order to upload the set of metrics files. The metrics files are
+// first copied to a temporary directory and the uploader is then executed in
+// the background to allow the user/system to continue working. Soong communicates
+// to the uploader through the upload_proto raw protobuf file.
 func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, files ...string) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
 
 	uploader := config.MetricsUploaderApp()
-	// No metrics to upload if the path to the uploader was not specified.
 	if uploader == "" {
+		// If the uploader path was not specified, no metrics shall be uploaded.
 		return
 	}
 
-	// Some files may not exist. For example, build errors protobuf file
-	// may not exist since the build was successful.
+	// Some files passed in to this function may not exist. For example,
+	// build errors protobuf file may not exist since the build was successful.
 	var metricsFiles []string
 	for _, f := range files {
 		if _, err := os.Stat(f); err == nil {
@@ -70,7 +72,7 @@
 	// The temporary directory cannot be deleted as the metrics uploader is started
 	// in the background and requires to exist until the operation is done. The
 	// uploader can delete the directory as it is specified in the upload proto.
-	tmpDir, err := getTmpDir("", "upload_metrics")
+	tmpDir, err := tmpDir("", "upload_metrics")
 	if err != nil {
 		ctx.Fatalf("failed to create a temporary directory to store the list of metrics files: %v\n", err)
 	}
@@ -103,7 +105,7 @@
 	}
 
 	// Start the uploader in the background as it takes several milliseconds to start the uploader
-	// and prepare the metrics for upload. This affects small commands like "lunch".
+	// and prepare the metrics for upload. This affects small shell commands like "lunch".
 	cmd := Command(ctx, config, "upload metrics", uploader, "--upload-metrics", pbFile)
 	if simpleOutput {
 		cmd.RunOrFatal()
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index dccf156..768b031 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -62,16 +62,16 @@
 			}
 			defer os.RemoveAll(outDir)
 
-			// Supply our own getTmpDir to delete the temp dir once the test is done.
-			orgGetTmpDir := getTmpDir
-			getTmpDir = func(string, string) (string, error) {
+			// Supply our own tmpDir to delete the temp dir once the test is done.
+			orgTmpDir := tmpDir
+			tmpDir = func(string, string) (string, error) {
 				retDir := filepath.Join(outDir, "tmp_upload_dir")
 				if err := os.Mkdir(retDir, 0755); err != nil {
 					t.Fatalf("failed to create temporary directory %q: %v", retDir, err)
 				}
 				return retDir, nil
 			}
-			defer func() { getTmpDir = orgGetTmpDir }()
+			defer func() { tmpDir = orgTmpDir }()
 
 			metricsUploadDir := filepath.Join(outDir, ".metrics_uploader")
 			if err := os.Mkdir(metricsUploadDir, 0755); err != nil {
@@ -134,11 +134,11 @@
 			}
 			defer os.RemoveAll(outDir)
 
-			orgGetTmpDir := getTmpDir
-			getTmpDir = func(string, string) (string, error) {
+			orgTmpDir := tmpDir
+			tmpDir = func(string, string) (string, error) {
 				return tt.tmpDir, tt.tmpDirErr
 			}
-			defer func() { getTmpDir = orgGetTmpDir }()
+			defer func() { tmpDir = orgTmpDir }()
 
 			metricsFile := filepath.Join(outDir, "metrics_file_1")
 			if err := ioutil.WriteFile(metricsFile, []byte("test file"), 0644); err != nil {
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)
 	}