Merge "Improve comments in cc/library.go and cc/linkable.go."
diff --git a/android/Android.bp b/android/Android.bp
index 4bd272d..f8c1d55 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -23,7 +23,8 @@
         "csuite_config.go",
         "defaults.go",
         "defs.go",
-        "depset.go",
+        "depset_generic.go",
+        "depset_paths.go",
         "deptag.go",
         "expand.go",
         "filegroup.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index a670656..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
@@ -368,6 +434,8 @@
 	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()
@@ -431,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) {
@@ -488,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
@@ -508,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{}
 }
@@ -515,6 +589,7 @@
 type androidMkSingleton struct{}
 
 func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
+	// Skip if Soong wasn't invoked from Make.
 	if !ctx.Config().KatiEnabled() {
 		return
 	}
@@ -525,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])
 	})
@@ -617,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 {
 
@@ -649,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 {
 
@@ -695,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
@@ -742,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/arch.go b/android/arch.go
index df407d4..34f9b29 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1337,17 +1337,6 @@
 		addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 	}
 
-	// An optional host target that uses the Bionic glibc runtime.
-	if Bool(config.Host_bionic) {
-		addTarget(LinuxBionic, "x86_64", nil, nil, nil, NativeBridgeDisabled, nil, nil)
-	}
-
-	// An optional cross-compiled host target that uses the Bionic glibc runtime on an arm64
-	// architecture.
-	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)
@@ -1437,53 +1426,6 @@
 	abi         []string
 }
 
-// getMegaDeviceConfig returns a list of archConfigs for every architecture simultaneously.
-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{
diff --git a/android/config.go b/android/config.go
index 04c9129..453e074 100644
--- a/android/config.go
+++ b/android/config.go
@@ -54,28 +54,9 @@
 	isPreview: true,
 }
 
-// configFileName is the name the file containing FileConfigurableOptions from
-// soong_ui for the soong_build primary builder.
-const configFileName = "soong.config"
-
-// productVariablesFileName contain the product configuration variables from soong_ui for the
-// soong_build primary builder and Kati.
+// 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"`
-}
-
-// SetDefaultConfig resets the receiving FileConfigurableOptions to default
-// values.
-func (f *FileConfigurableOptions) SetDefaultConfig() {
-	*f = FileConfigurableOptions{}
-}
-
 // A Config object represents the entire build configuration for Android.
 type Config struct {
 	*config
@@ -97,12 +78,8 @@
 type VendorConfig soongconfig.SoongConfig
 
 // Definition of general build configuration for soong_build. Some of these
-// configuration values are generated from soong_ui for soong_build,
-// communicated over JSON files like soong.config or soong.variables.
+// product configuration values are read from Kati-generated soong.variables.
 type config struct {
-	// Options configurable with soong.confg
-	FileConfigurableOptions
-
 	// Options configurable with soong.variables
 	productVariables productVariables
 
@@ -113,7 +90,6 @@
 	// purposes.
 	BazelContext BazelContext
 
-	ConfigFileName           string
 	ProductVariablesFileName string
 
 	Targets                  map[OsType][]Target
@@ -170,11 +146,6 @@
 }
 
 func loadConfig(config *config) error {
-	err := loadFromConfigFile(&config.FileConfigurableOptions, absolutePath(config.ConfigFileName))
-	if err != nil {
-		return err
-	}
-
 	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
 
@@ -384,7 +355,6 @@
 func NewConfig(srcDir, buildDir string, moduleListFile string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
-		ConfigFileName:           filepath.Join(buildDir, configFileName),
 		ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName),
 
 		env: originalEnv,
@@ -439,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()
@@ -864,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...)
 }
@@ -1309,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)
 }
@@ -1321,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)
 }
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/defs.go b/android/defs.go
index 631dfe8..f5bd362 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -97,7 +98,7 @@
 	// content to file.
 	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")
@@ -133,9 +134,7 @@
 	shellUnescaper = strings.NewReplacer(`'\''`, `'`)
 )
 
-// WriteFileRule creates a ninja rule to write contents to a file.  The contents will be escaped
-// so that the file contains exactly the contents passed to the function, plus a trailing newline.
-func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
+func buildWriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
 	content = echoEscaper.Replace(content)
 	content = proptools.ShellEscape(content)
 	if content == "" {
@@ -151,6 +150,31 @@
 	})
 }
 
+// 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
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/module.go b/android/module.go
index d680baa..b6729c0 100644
--- a/android/module.go
+++ b/android/module.go
@@ -2399,10 +2399,6 @@
 		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
 			return true
 		}
-
-		if m.Config().SkipMegaDeviceInstall(fullInstallPath.String()) {
-			return true
-		}
 	}
 
 	return false
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/packaging.go b/android/packaging.go
index f168fa6..09432e6 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -40,6 +40,15 @@
 	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
@@ -144,15 +153,9 @@
 
 // See PackageModule.CopyDepsToZip
 func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
-	var supportedArches []string
-	for _, t := range p.getSupportedTargets(ctx) {
-		supportedArches = append(supportedArches, t.Arch.ArchType.String())
-	}
 	m := make(map[string]PackagingSpec)
 	ctx.WalkDeps(func(child Module, parent Module) bool {
-		// Don't track modules with unsupported arch
-		// TODO(jiyong): remove this when aosp/1501613 lands.
-		if !InList(child.Target().Arch.ArchType.String(), supportedArches) {
+		if !IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
 			return false
 		}
 		for _, ps := range child.PackagingSpecs() {
@@ -163,7 +166,7 @@
 		return true
 	})
 
-	builder := NewRuleBuilder()
+	builder := NewRuleBuilder(pctx, ctx)
 
 	dir := PathForModuleOut(ctx, ".zip").OutputPath
 	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
@@ -190,13 +193,13 @@
 	}
 
 	builder.Command().
-		BuiltTool(ctx, "soong_zip").
+		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(pctx, ctx, "zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName()))
+	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
index 2acd15c..7269bfb 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -29,6 +29,12 @@
 	}
 }
 
+// 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)
@@ -37,7 +43,7 @@
 }
 
 func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
 }
 
 func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
@@ -63,7 +69,7 @@
 }
 
 func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
-	m.AddDeps(ctx, struct{ blueprint.BaseDependencyTag }{})
+	m.AddDeps(ctx, installDepTag{})
 }
 
 func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
diff --git a/android/paths.go b/android/paths.go
index b7117a3..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 {
@@ -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{}
 
@@ -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/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 08e47b3..b26f9b9 100644
--- a/android/register.go
+++ b/android/register.go
@@ -90,6 +90,21 @@
 	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, SingletonFactoryAdaptor(ctx, t.factory))
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 3efe9f8..e2d8187 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -37,6 +37,9 @@
 // RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
 // graph.
 type RuleBuilder struct {
+	pctx PackageContext
+	ctx  BuilderContext
+
 	commands         []*RuleBuilderCommand
 	installs         RuleBuilderInstalls
 	temporariesSet   map[WritablePath]bool
@@ -44,14 +47,16 @@
 	sbox             bool
 	highmem          bool
 	remoteable       RemoteRuleSupports
-	sboxOutDir       WritablePath
+	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),
 	}
 }
@@ -130,7 +135,7 @@
 		panic("Sbox() is not compatible with Restat()")
 	}
 	r.sbox = true
-	r.sboxOutDir = outputDir
+	r.outDir = outputDir
 	r.sboxManifestPath = manifestPath
 	return r
 }
@@ -146,8 +151,7 @@
 // race with any call to Build.
 func (r *RuleBuilder) Command() *RuleBuilderCommand {
 	command := &RuleBuilderCommand{
-		sbox:   r.sbox,
-		outDir: r.sboxOutDir,
+		rule: r,
 	}
 	r.commands = append(r.commands, command)
 	return command
@@ -395,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(),
@@ -426,13 +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.  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())
 				}
 			}
 		}
@@ -468,7 +472,7 @@
 		// to the output directory.
 		sboxOutputs := make([]string, len(outputs))
 		for i, output := range outputs {
-			rel := Rel(ctx, r.sboxOutDir.String(), output.String())
+			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)),
@@ -483,23 +487,27 @@
 
 		// 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(ctx, r.sboxOutDir.String(), r.sboxManifestPath.String())
+		_, manifestInOutDir := MaybeRel(r.ctx, r.outDir.String(), r.sboxManifestPath.String())
 		if manifestInOutDir {
-			ReportPathErrorf(ctx, "sbox rule %q manifestPath %q must not be in outputDir %q",
-				name, r.sboxManifestPath.String(), r.sboxOutDir.String())
+			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(ctx, r.sboxManifestPath, proto.MarshalTextString(&manifest))
+		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{}
-		sboxCmd.Text("rm -rf").Output(r.sboxOutDir)
+		sboxCmd := &RuleBuilderCommand{
+			rule: &RuleBuilder{
+				ctx: r.ctx,
+			},
+		}
+		sboxCmd.Text("rm -rf").Output(r.outDir)
 		sboxCmd.Text("&&")
-		sboxCmd.BuiltTool(ctx, "sbox").
-			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
+		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
@@ -528,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,
@@ -564,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
@@ -576,16 +586,11 @@
 
 	// spans [start,end) of the command that should not be ninja escaped
 	unescapedSpans [][2]int
-
-	sbox bool
-	// outDir is the directory that will contain the output files of the rules.  sbox will copy
-	// the output files from the sandbox directory to this directory when it finishes.
-	outDir WritablePath
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
-	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+	if c.rule.sbox {
+		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
 			return filepath.Join(sboxOutDir, rel)
 		}
 	}
@@ -594,8 +599,8 @@
 }
 
 func (c *RuleBuilderCommand) addImplicit(path Path) string {
-	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+	if c.rule.sbox {
+		if rel, isRel, _ := maybeRelErr(c.rule.outDir.String(), path.String()); isRel {
 			return filepath.Join(sboxOutDir, rel)
 		}
 	}
@@ -607,22 +612,19 @@
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
-func (c *RuleBuilderCommand) outputStr(path WritablePath) string {
-	if c.sbox {
-		return SboxPathForOutput(path, c.outDir)
+// 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.rule.outDir.String(), path.String())
+		return filepath.Join(sboxOutDir, rel)
 	}
 	return path.String()
 }
 
-// SboxPathForOutput takes an output path and the out directory passed to RuleBuilder.Sbox(),
-// and returns the corresponding path for the output in the sbox sandbox.  This can be used
-// on the RuleBuilder command line to reference the output.
-func SboxPathForOutput(path WritablePath, outDir WritablePath) string {
-	// Errors will be handled in RuleBuilder.Build where we have a context to report them
-	rel, _, _ := maybeRelErr(outDir.String(), path.String())
-	return filepath.Join(sboxOutDir, rel)
-}
-
 // Text adds the specified raw text to the command line.  The text should not contain input or output paths or the
 // rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
@@ -699,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
@@ -768,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
@@ -783,7 +785,7 @@
 // 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(sboxOutDir)
@@ -794,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
@@ -885,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
@@ -987,3 +989,21 @@
 	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 dc360c3..e676e4a 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -27,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,
@@ -44,9 +44,9 @@
 }
 
 func ExampleRuleBuilder() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "ld")).
@@ -55,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())
@@ -70,9 +70,9 @@
 }
 
 func ExampleRuleBuilder_SymlinkOutputs() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "ln")).
@@ -96,9 +96,9 @@
 }
 
 func ExampleRuleBuilder_Temporary() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "cp")).
@@ -123,9 +123,9 @@
 }
 
 func ExampleRuleBuilder_DeleteTemporaryFiles() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	rule.Command().
 		Tool(PathForSource(ctx, "cp")).
@@ -151,9 +151,9 @@
 }
 
 func ExampleRuleBuilder_Installs() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	out := PathForOutput(ctx, "linked")
 
@@ -171,9 +171,9 @@
 }
 
 func ExampleRuleBuilderCommand() {
-	rule := NewRuleBuilder()
+	ctx := builderContext()
 
-	ctx := pathContext()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	// chained
 	rule.Command().
@@ -194,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:
@@ -219,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:
@@ -228,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:
@@ -237,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:
@@ -246,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:
@@ -255,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:
@@ -264,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())
@@ -274,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())
@@ -283,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())
@@ -305,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().
@@ -355,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{
@@ -389,13 +394,13 @@
 			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)
 
@@ -427,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)
 		}
 	})
@@ -476,7 +481,7 @@
 }
 
 func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
-	rule := NewRuleBuilder()
+	rule := NewRuleBuilder(pctx, ctx)
 
 	if sbox {
 		rule.Sbox(outDir, manifestPath)
@@ -488,7 +493,7 @@
 		rule.Restat()
 	}
 
-	rule.Build(pctx, ctx, "rule", "desc")
+	rule.Build("rule", "desc")
 }
 
 func TestRuleBuilder_Build(t *testing.T) {
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/util.go b/android/util.go
index 8e4c0f4..0f940fa 100644
--- a/android/util.go
+++ b/android/util.go
@@ -401,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 {
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/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 c14910a..5afef65 100644
--- a/androidmk/parser/parser.go
+++ b/androidmk/parser/parser.go
@@ -553,12 +553,14 @@
 	"else",
 	"endef",
 	"endif",
+	"export",
 	"ifdef",
 	"ifeq",
 	"ifndef",
 	"ifneq",
 	"include",
 	"-include",
+	"unexport",
 }
 
 var functions = [...]string{
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 da38c2a..6c76ad3f 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -360,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 f127757..28ae4bd 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -120,6 +120,12 @@
 	// 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.
@@ -353,6 +359,11 @@
 	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.
@@ -1663,7 +1674,7 @@
 							// system libraries.
 							if !am.DirectlyInAnyApex() {
 								// we need a module name for Make
-								name := cc.ImplementationModuleName(ctx)
+								name := cc.ImplementationModuleNameForMake(ctx)
 
 								if !proptools.Bool(a.properties.Use_vendor) {
 									// we don't use subName(.vendor) for a "use_vendor: true" apex
@@ -2013,7 +2024,9 @@
 			// 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") {
+			// 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
 			}
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a94e3b4..a207319 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -346,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, `
@@ -2831,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
@@ -2864,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.
@@ -2888,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) {
@@ -5964,9 +5971,27 @@
 			srcs: ["mylib.cpp"],
 			system_shared_libs: [],
 			stl: "none",
-			shared_libs: ["mylib", "myprivlib"],
+			shared_libs: ["mylib", "myprivlib", "mytestlib"],
 			test_for: ["myapex"]
 		}
+
+		cc_library {
+			name: "mytestlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			shared_libs: ["mylib", "myprivlib"],
+			stl: "none",
+			test_for: ["myapex"],
+		}
+
+		cc_benchmark {
+			name: "mybench",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			shared_libs: ["mylib", "myprivlib"],
+			stl: "none",
+			test_for: ["myapex"],
+		}
 	`)
 
 	// the test 'mytest' is a test for the apex, therefore is linked to the
@@ -5974,6 +5999,16 @@
 	ldFlags := ctx.ModuleForTests("mytest", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
 	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
 	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+
+	// The same should be true for cc_library
+	ldFlags = ctx.ModuleForTests("mytestlib", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
+	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
+	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+
+	// ... and for cc_benchmark
+	ldFlags = ctx.ModuleForTests("mybench", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
+	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
+	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
 }
 
 // TODO(jungjw): Move this to proptools
@@ -6186,6 +6221,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 b858135..9db8e59 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -37,6 +37,7 @@
 
 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"
@@ -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 (
@@ -176,6 +179,12 @@
 		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.
 )
 
@@ -262,7 +271,7 @@
 	}
 
 	output := android.PathForModuleOut(ctx, "file_contexts")
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 
 	switch a.properties.ApexType {
 	case imageApex:
@@ -294,7 +303,7 @@
 		panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
 	}
 
-	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
+	rule.Build("file_contexts."+a.Name(), "Generate file_contexts")
 	return output.OutputPath
 }
 
@@ -329,14 +338,14 @@
 // 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
 }
 
@@ -675,6 +684,22 @@
 			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
@@ -714,7 +739,7 @@
 
 	////////////////////////////////////////////////////////////////////////////////////
 	// Step 4: Sign the APEX using signapk
-	a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+	signedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix)
 
 	pem, key := a.getCertificateAndPrivateKey(ctx)
 	rule := java.Signapk
@@ -726,16 +751,47 @@
 	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() {
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/binary.go b/cc/binary.go
index fbd293e..fa3966f 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -373,7 +373,7 @@
 	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)
 	}
 
@@ -428,13 +428,13 @@
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
 	// Register link action.
-	TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
+	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'
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 ce69668..986bf2b 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -91,6 +91,14 @@
 	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++
@@ -310,6 +332,11 @@
 	// framework module from a snapshot.
 	Exclude_from_vendor_snapshot   *bool
 	Exclude_from_recovery_snapshot *bool
+
+	// List of APEXes that this module has private access to for testing purpose. The module
+	// can depend on libraries that are not exported by the APEXes and use private symbols
+	// from the exported libraries.
+	Test_for []string
 }
 
 type VendorProperties struct {
@@ -358,6 +385,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
@@ -412,6 +443,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
@@ -419,6 +452,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
@@ -430,6 +466,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
@@ -445,15 +484,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)
@@ -584,7 +627,7 @@
 	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"}
@@ -626,7 +669,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
@@ -643,18 +697,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
 
@@ -1045,6 +1104,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)
 }
@@ -1074,7 +1146,7 @@
 
 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
@@ -2868,13 +2940,7 @@
 }
 
 func (c *Module) TestFor() []string {
-	if test, ok := c.linker.(interface {
-		testFor() []string
-	}); ok {
-		return test.testFor()
-	} else {
-		return c.ApexModuleBase.TestFor()
-	}
+	return c.Properties.Test_for
 }
 
 func (c *Module) UniqueApexVariations() bool {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 7c98585..af9b943 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2913,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.
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 04ed80d..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{
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 563ce76..a548452 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -21,7 +21,6 @@
 	"android.hardware.automotive.occupant_awareness-ndk_platform",
 	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-ndk_platform",
-	"android.hardware.keymint-ndk_platform",
 	"android.hardware.keymint-unstable-ndk_platform",
 	"android.hardware.nfc@1.2",
 	"android.hardware.power-ndk_platform",
diff --git a/cc/fuzz.go b/cc/fuzz.go
index dddfe94..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 {
@@ -419,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)
@@ -434,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())
@@ -492,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 {
@@ -504,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
@@ -531,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.
@@ -549,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 5895b31..75b9e49 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -75,10 +75,9 @@
 	cmd := rule.Command()
 
 	// Fix up #line markers to not use the sbox temporary directory
-	// android.SboxPathForOutput(outDir, outDir) returns the sbox placeholder for the out
+	// android.sboxPathForOutput(outDir, outDir) returns the sbox placeholder for the out
 	// directory itself, without any filename appended.
-	// TODO(ccross): make this cmd.PathForOutput(outDir) instead.
-	sboxOutDir := android.SboxPathForOutput(outDir, outDir)
+	sboxOutDir := cmd.PathForOutput(outDir)
 	sedCmd := "sed -i.bak 's#" + sboxOutDir + "#" + outDir.String() + "#'"
 	rule.Command().Text(sedCmd).Input(outFile)
 	rule.Command().Text(sedCmd).Input(headerFile)
@@ -137,7 +136,7 @@
 	}
 
 	cmd := rule.Command()
-	cmd.BuiltTool(ctx, "aidl-cpp").
+	cmd.BuiltTool("aidl-cpp").
 		FlagWithDepFile("-d", depFile).
 		Flag("--ninja").
 		Flag(aidlFlags).
@@ -232,7 +231,7 @@
 	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_
@@ -262,7 +261,7 @@
 			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")
@@ -285,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/library.go b/cc/library.go
index 5eea419..b796aaf 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -934,9 +934,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)
 
@@ -1011,7 +1011,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) {
@@ -1056,7 +1056,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)
 
@@ -1066,7 +1066,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)
 
@@ -1076,7 +1076,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
@@ -1152,7 +1152,7 @@
 		return refAbiDumpTextFile.Path()
 	}
 	if refAbiDumpGzipFile.Valid() {
-		return UnzipRefDump(ctx, refAbiDumpGzipFile.Path(), fileName)
+		return unzipRefDump(ctx, refAbiDumpGzipFile.Path(), fileName)
 	}
 	return nil
 }
@@ -1178,7 +1178,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)
@@ -1187,7 +1187,7 @@
 
 		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.Config()), ctx.isVndkExt())
@@ -1836,13 +1836,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/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/proto.go b/cc/proto.go
index 9c102a2..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
 }
diff --git a/cc/sanitize.go b/cc/sanitize.go
index dbc52a5..22ee25f 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -169,14 +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
diff --git a/cc/strip.go b/cc/strip.go
index e9aec91..b1f34bb 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -23,19 +23,23 @@
 // StripProperties defines the type of stripping applied to the module.
 type StripProperties struct {
 	Strip struct {
-		// whether to disable all stripping.
+		// 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"`
 
-		// whether to strip everything, including the mini debug info.
+		// all forces stripping everything, including the mini debug info.
 		All *bool `android:"arch_variant"`
 
-		// whether to keep the symbols.
+		// keep_symbols enables stripping but keeps all symbols.
 		Keep_symbols *bool `android:"arch_variant"`
 
-		// keeps only the symbols defined here.
+		// 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"`
 
-		// whether to keep the symbols and the debug frames.
+		// 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"`
 }
@@ -47,14 +51,18 @@
 
 // NeedsStrip determines if stripping is required for a module.
 func (stripper *Stripper) NeedsStrip(actx android.ModuleContext) bool {
-	// TODO(ccross): enable host stripping when Kati is enabled? Make never had support for stripping host binaries.
-	return (!actx.Config().KatiEnabled() || 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
@@ -68,7 +76,7 @@
 		if actx.Config().Debuggable() && !flags.StripKeepMiniDebugInfo && !isStaticLib {
 			flags.StripAddGnuDebuglink = true
 		}
-		TransformStrip(actx, in, out, flags)
+		transformStrip(actx, in, out, flags)
 	}
 }
 
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/test.go b/cc/test.go
index 3772691..a9be6f9 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -29,11 +29,6 @@
 
 	// if set, use the isolated gtest runner. Defaults to false.
 	Isolated *bool
-
-	// 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.
-	Test_for []string
 }
 
 // Test option struct.
@@ -241,10 +236,6 @@
 	return BoolDefault(test.Properties.Gtest, true)
 }
 
-func (test *testDecorator) testFor() []string {
-	return test.Properties.Test_for
-}
-
 func (test *testDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	if !test.gtest() {
 		return flags
diff --git a/cc/testing.go b/cc/testing.go
index 95a93a0..85f12b7 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -29,6 +29,7 @@
 
 	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("llndk_library", LlndkLibraryFactory)
+	ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
 	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
@@ -437,6 +438,13 @@
 		ndk_prebuilt_shared_stl {
 			name: "ndk_libc++_shared",
 		}
+
+		cc_library_static {
+			name: "libgoogle-benchmark",
+			sdk_version: "current",
+			stl: "none",
+			system_shared_libs: [],
+		}
 	`
 
 	supportLinuxBionic := false
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 6563f6e..3ef0b62 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -339,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,
@@ -1124,7 +1124,7 @@
 		ctx,
 		snapshotDir,
 		c.name+"-"+ctx.Config().DeviceName()+".zip")
-	zipRule := android.NewRuleBuilder()
+	zipRule := android.NewRuleBuilder(pctx, ctx)
 
 	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
 	snapshotOutputList := android.PathForOutput(
@@ -1140,12 +1140,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(), c.name+" snapshot "+zipPath.String())
+	zipRule.Build(zipPath.String(), c.name+" snapshot "+zipPath.String())
 	zipRule.DeleteTemporaryFiles()
 	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
 }
diff --git a/cc/vndk.go b/cc/vndk.go
index 4a005f3..d57cdf7 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -727,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(" ")
@@ -762,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")
@@ -775,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)
 }
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/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/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_ui/main.go b/cmd/soong_ui/main.go
index 29030d6..4ffe944 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -174,10 +174,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")
-	}
+	logsDir := config.LogsDir()
 
 	buildErrorFile := filepath.Join(logsDir, c.logsPrefix+"build_error")
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 3759217..deaf77f 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -437,7 +437,11 @@
 			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.
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index df68563..be7d4c6 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -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/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/filesystem/filesystem.go b/filesystem/filesystem.go
index ecbfbab..c6181bc 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -29,8 +29,16 @@
 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)
@@ -44,6 +52,10 @@
 	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) {
@@ -51,9 +63,9 @@
 	f.CopyDepsToZip(ctx, zipFile)
 
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	builder := android.NewRuleBuilder()
+	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().
-		BuiltTool(ctx, "zipsync").
+		BuiltTool("zipsync").
 		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
 		Input(zipFile)
 
@@ -68,13 +80,32 @@
 		Text(">").Output(propFile).
 		Implicit(mkuserimg)
 
-	image := android.PathForModuleOut(ctx, "filesystem.img").OutputPath
-	builder.Command().BuiltTool(ctx, "build_image").
+	f.output = android.PathForModuleOut(ctx, "filesystem.img").OutputPath
+	builder.Command().BuiltTool("build_image").
 		Text(rootDir.String()). // input directory
 		Input(propFile).
-		Output(image).
+		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(pctx, ctx, "build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
+	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/genrule.go b/genrule/genrule.go
index e5ee3fc..93938c9 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -342,8 +342,27 @@
 			return
 		}
 
+		// 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{android.SboxPathForOutput(out, task.genDir)})
+			addLocationLabel(out.Rel(), []string{cmd.PathForOutput(out)})
 		}
 
 		referencedDepfile := false
@@ -374,7 +393,7 @@
 			case "out":
 				var sandboxOuts []string
 				for _, out := range task.out {
-					sandboxOuts = append(sandboxOuts, android.SboxPathForOutput(out, task.genDir))
+					sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
 				}
 				return strings.Join(sandboxOuts, " "), nil
 			case "depfile":
@@ -384,7 +403,7 @@
 				}
 				return "__SBOX_DEPFILE__", nil
 			case "genDir":
-				return android.SboxPathForOutput(task.genDir, task.genDir), nil
+				return cmd.PathForOutput(task.genDir), nil
 			default:
 				if strings.HasPrefix(name, "location ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -426,24 +445,6 @@
 		}
 		g.rawCommands = append(g.rawCommands, rawCommand)
 
-		// 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().Sbox(task.genDir, manifestPath)
-		cmd := rule.Command()
 		cmd.Text(rawCommand)
 		cmd.ImplicitOutputs(task.out)
 		cmd.Implicits(task.in)
@@ -454,7 +455,7 @@
 		}
 
 		// Create the rule to run the genrule command inside sbox.
-		rule.Build(pctx, ctx, name, desc)
+		rule.Build(name, desc)
 
 		if len(task.copyTo) > 0 {
 			// If copyTo is set, multiple shards need to be copied into a single directory.
@@ -612,6 +613,10 @@
 			}
 
 			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))
@@ -634,11 +639,11 @@
 					case "in":
 						return in.String(), nil
 					case "out":
-						return android.SboxPathForOutput(outFile, genDir), 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 := android.SboxPathForOutput(outFile.ReplaceExtension(ctx, "d"), genDir)
+						depFile := rule.Command().PathForOutput(outFile.ReplaceExtension(ctx, "d"))
 						commandDepFiles = append(commandDepFiles, depFile)
 						return depFile, nil
 					default:
@@ -703,7 +708,7 @@
 	Shard_size *int64
 }
 
-const defaultShardSize = 100
+const defaultShardSize = 50
 
 func NewGenRule() *Module {
 	properties := &genRuleProperties{}
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 1940d7f..3b6b34e 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -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:
@@ -439,7 +440,6 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				classLoaderContexts.AddContextMap(aarDep.ClassLoaderContexts(), 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 {
-			classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), 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...)
diff --git a/java/app.go b/java/app.go
index 3384bbd..e6d9550 100755
--- a/java/app.go
+++ b/java/app.go
@@ -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) {
@@ -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_builder.go b/java/app_builder.go
index 69e462c..b53c15a 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -32,7 +32,7 @@
 var (
 	Signapk, SignapkRE = remoteexec.StaticRules(pctx, "signapk",
 		blueprint.RuleParams{
-			Command: `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname ${config.SignapkJniLibrary}) ` +
+			Command: `rm -f $out && $reTemplate${config.JavaCmd} ${config.JavaVmFlags} -Djava.library.path=$$(dirname ${config.SignapkJniLibrary}) ` +
 				`-jar ${config.SignapkCmd} $flags $certificates $in $out`,
 			CommandDeps: []string{"${config.SignapkCmd}", "${config.SignapkJniLibrary}"},
 		},
diff --git a/java/app_test.go b/java/app_test.go
index 6efb0dc..e13c6b9 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -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 cd35245..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} ` +
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 5df5845..062005b 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -418,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
@@ -465,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)
 	}
 
@@ -495,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
 		}
 	})
@@ -506,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)
@@ -539,14 +556,14 @@
 	}
 
 	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
@@ -568,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())
@@ -689,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()
@@ -713,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
@@ -744,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()
 
@@ -766,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
@@ -779,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")
@@ -794,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
@@ -839,7 +856,7 @@
 		// 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`?
@@ -863,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)
 	}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index da8489a..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)
 }
 
 //
@@ -1278,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").
@@ -1333,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.
@@ -1480,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).
@@ -1503,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).
@@ -1524,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") {
 
@@ -1542,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 {"
@@ -1564,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())
@@ -1576,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")
 
@@ -1602,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) != "" {
@@ -1625,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("(").
@@ -1637,7 +1637,7 @@
 			Text("; exit 38").
 			Text(")")
 
-		rule.Build(pctx, ctx, "nullabilityWarningsCheck", "nullability warnings check")
+		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
 	}
 }
 
@@ -1722,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"`).
@@ -1783,9 +1783,9 @@
 	srcGlob := localSrcDir + "/**/*"
 	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
-		BuiltTool(ctx, "soong_zip").
+		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
 		Flag("-jar").
 		FlagWithOutput("-o ", p.stubsSrcJar).
@@ -1794,7 +1794,7 @@
 
 	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 8f0e09c..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")
@@ -266,7 +267,7 @@
 
 	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 8738e00..d44719e 100644
--- a/java/java.go
+++ b/java/java.go
@@ -772,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 {
@@ -1050,7 +1081,6 @@
 			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.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
 					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
@@ -1062,7 +1092,6 @@
 				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.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
@@ -1075,8 +1104,6 @@
 				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.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
 				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
@@ -1151,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
@@ -1415,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.
@@ -2781,8 +2814,6 @@
 			switch tag {
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
-				// sdk lib names from dependencies are re-exported
-				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2790,10 +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.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
@@ -3049,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).
@@ -3071,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,
@@ -3214,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 83db443..f7cf03f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -805,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 {
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/lint.go b/java/lint.go
index 11f92e5..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,
@@ -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 62d1d99..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,
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 32bc077..4e33d74 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -2176,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())
 }
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/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/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 7e376c6..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("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.
@@ -93,10 +93,12 @@
 	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"`
 
@@ -105,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
@@ -129,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.
@@ -153,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,
@@ -163,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,
@@ -172,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.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()...)
 	}
@@ -212,13 +231,19 @@
 	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
 }
@@ -228,7 +253,7 @@
 	javaDataTag          = dependencyTag{name: "javaData"}
 	launcherTag          = dependencyTag{name: "launcher"}
 	launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
-	pyIdentifierRegexp   = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
+	pathComponentRegexp  = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
 	pyExt                = ".py"
 	protoExt             = ".proto"
 	pyVersion2           = "PY2"
@@ -237,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.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)
@@ -275,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 "":
@@ -296,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
 		}
 	}
@@ -309,10 +341,14 @@
 	return false
 }
 
-func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool {
-	return hasSrcExt(p.properties.Srcs, ext)
+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)
 
@@ -320,66 +356,61 @@
 		{"python_version", p.properties.Actual_version},
 	}
 
-	if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
+	// 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")
 	}
+
+	// Add python library dependencies for this python version variation
 	ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
-	switch p.properties.Actual_version {
-	case pyVersion2:
+	// 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")
+		}
 
-		if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled() {
-			ctx.AddVariationDependencies(versionVariation, pythonLibTag, "py2-stdlib")
+		switch p.properties.Actual_version {
+		case pyVersion2:
+			stdLib = "py2-stdlib"
 
-			launcherModule := "py2-launcher"
+			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")
-			ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libc++")
+		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(versionVariation, 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"
@@ -389,19 +420,25 @@
 }
 
 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))
@@ -409,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
@@ -437,9 +472,10 @@
 		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
 	}
 
-	// sanitize pkg_path.
+	// 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, "/") {
@@ -448,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
@@ -477,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})
 		}
 	}
 
@@ -497,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 {
@@ -537,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)
 			}
 		}
 
@@ -563,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 {
@@ -584,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()
 	}
@@ -614,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
 		}
@@ -623,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 4df71c1..b7cd475 100644
--- a/python/test.go
+++ b/python/test.go
@@ -108,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/binary.go b/rust/binary.go
index af39d38..c2d97f3 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -24,11 +24,6 @@
 }
 
 type BinaryCompilerProperties struct {
-	// Change the rustlibs linkage to select rlib linkage by default for device targets.
-	// Also link libstd as an rlib as well on device targets.
-	// Note: This is the default behavior for host targets.
-	Prefer_rlib *bool `android:"arch_variant"`
-
 	// Builds this binary as a static binary. Implies prefer_rlib true.
 	//
 	// Static executables currently only support for bionic targets. Non-bionic targets will not produce a fully static
@@ -115,7 +110,7 @@
 }
 
 func (binary *binaryDecorator) preferRlib() bool {
-	return Bool(binary.Properties.Prefer_rlib) || Bool(binary.Properties.Static_executable)
+	return Bool(binary.baseCompiler.Properties.Prefer_rlib) || Bool(binary.Properties.Static_executable)
 }
 
 func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
@@ -156,8 +151,7 @@
 	// Binaries default to dylib dependencies for device, rlib for host.
 	if binary.preferRlib() {
 		return rlibAutoDep
-	}
-	if ctx.Device() {
+	} else if ctx.Device() {
 		return dylibAutoDep
 	} else {
 		return rlibAutoDep
diff --git a/rust/compiler.go b/rust/compiler.go
index 8d2f09c..4312452 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -122,6 +122,17 @@
 
 	// whether to suppress inclusion of standard crates - defaults to false
 	No_stdlibs *bool
+
+	// Change the rustlibs linkage to select rlib linkage by default for device targets.
+	// Also link libstd as an rlib as well on device targets.
+	// Note: This is the default behavior for host targets.
+	//
+	// This is primarily meant for rust_binary and rust_ffi modules where the default
+	// linkage of libstd might need to be overridden in some use cases. This should
+	// generally be avoided with other module types since it may cause collisions at
+	// linkage if all dependencies of the root binary module do not link against libstd\
+	// the same way.
+	Prefer_rlib *bool `android:"arch_variant"`
 }
 
 type baseCompiler struct {
@@ -154,9 +165,15 @@
 	panic("baseCompiler does not implement coverageOutputZipPath()")
 }
 
+func (compiler *baseCompiler) preferRlib() bool {
+	return Bool(compiler.Properties.Prefer_rlib)
+}
+
 func (compiler *baseCompiler) stdLinkage(ctx *depsContext) RustLinkage {
 	// For devices, we always link stdlibs in as dylibs by default.
-	if ctx.Device() {
+	if compiler.preferRlib() {
+		return RlibLinkage
+	} else if ctx.Device() {
 		return DylibLinkage
 	} else {
 		return RlibLinkage
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 e6643f5..df31d60 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -13,6 +13,7 @@
 		"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/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/library.go b/rust/library.go
index 971588d..9d731e6 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -159,14 +159,6 @@
 	return library.MutatedProperties.VariantIsStatic
 }
 
-func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
-	// libraries should only request the RlibLinkage when building a static FFI or when variant is StaticStd
-	if library.static() || library.MutatedProperties.VariantIsStaticStd {
-		return RlibLinkage
-	}
-	return DefaultLinkage
-}
-
 func (library *libraryDecorator) source() bool {
 	return library.MutatedProperties.VariantIsSource
 }
@@ -228,7 +220,9 @@
 }
 
 func (library *libraryDecorator) autoDep(ctx BaseModuleContext) autoDep {
-	if library.rlib() || library.static() {
+	if library.preferRlib() {
+		return rlibAutoDep
+	} else if library.rlib() || library.static() {
 		return rlibAutoDep
 	} else if library.dylib() || library.shared() {
 		return dylibAutoDep
@@ -237,6 +231,15 @@
 	}
 }
 
+func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
+	if library.static() || library.MutatedProperties.VariantIsStaticStd {
+		return RlibLinkage
+	} else if library.baseCompiler.preferRlib() {
+		return RlibLinkage
+	}
+	return DefaultLinkage
+}
+
 var _ compiler = (*libraryDecorator)(nil)
 var _ libraryInterface = (*libraryDecorator)(nil)
 var _ exportedFlagsProducer = (*libraryDecorator)(nil)
diff --git a/rust/library_test.go b/rust/library_test.go
index fec3992..54cd2a5 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -251,6 +251,13 @@
 			srcs: ["foo.rs"],
 			crate_name: "bar",
 			rustlibs: ["libfoo"],
+		}
+		rust_ffi {
+			name: "libbar.prefer_rlib",
+			srcs: ["foo.rs"],
+			crate_name: "bar",
+			rustlibs: ["libfoo"],
+			prefer_rlib: true,
 		}`)
 
 	libfooDylib := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
@@ -260,6 +267,9 @@
 	libbarShared := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module().(*Module)
 	libbarStatic := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Module().(*Module)
 
+	// prefer_rlib works the same for both rust_library and rust_ffi, so a single check is sufficient here.
+	libbarRlibStd := ctx.ModuleForTests("libbar.prefer_rlib", "android_arm64_armv8-a_shared").Module().(*Module)
+
 	if !android.InList("libstd", libfooRlibStatic.Properties.AndroidMkRlibs) {
 		t.Errorf("rlib-std variant for device rust_library_rlib does not link libstd as an rlib")
 	}
@@ -279,4 +289,8 @@
 	if !android.InList("libfoo.rlib-std", libbarStatic.Properties.AndroidMkRlibs) {
 		t.Errorf("Device rust_ffi_static does not link dependent rustlib rlib-std variant")
 	}
+	if !android.InList("libstd", libbarRlibStd.Properties.AndroidMkRlibs) {
+		t.Errorf("rust_ffi with prefer_rlib does not link libstd as an rlib")
+	}
+
 }
diff --git a/rust/project_json.go b/rust/project_json.go
index 8d9e50c..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,94 +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)
-		if !ok {
-			return
-		}
 		// Skip intra-module dependencies (i.e., generated-source library depending on the source variant).
 		if module.Name() == child.Name() {
 			return
 		}
-		if _, ok = deps[ctx.ModuleName(child)]; ok {
+		// Skip unsupported modules.
+		rChild, compChild, ok := isModuleSupported(ctx, child)
+		if !ok {
 			return
 		}
-		crate.Deps = append(crate.Deps, rustProjectDep{Crate: childId, Name: childCrateName})
-		deps[ctx.ModuleName(child)] = childId
+		// 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: 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) {
@@ -172,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 16699c1..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"])
@@ -133,20 +184,25 @@
 		}
 		// Check that libbindings1 does not depend on itself.
 		if strings.Contains(rootModule, "libbindings1") {
-			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 dep: %v", dep)
-				}
-				if d["name"] == "bindings1" {
+			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)
+			}
+		}
 	}
 }
 
@@ -171,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 235b4ad..0e79089 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -93,7 +93,7 @@
 	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
 	outputs := android.WritablePaths{stemFile}
 
-	rule := android.NewRuleBuilder()
+	rule := android.NewRuleBuilder(pctx, ctx)
 	for _, protoFile := range protoFiles {
 		protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
 		protoNames = append(protoNames, protoName)
@@ -108,7 +108,7 @@
 
 		depFile := android.PathForModuleOut(ctx, protoName+".d")
 
-		android.ProtoRule(ctx, rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
+		android.ProtoRule(rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
 		outputs = append(outputs, ruleOutputs...)
 	}
 
@@ -117,7 +117,7 @@
 		Text("printf '" + proto.genModFileContents(ctx, protoNames) + "' >").
 		Output(stemFile)
 
-	rule.Build(pctx, ctx, "protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
+	rule.Build("protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
 
 	proto.BaseSourceProvider.OutputFiles = outputs.Paths()
 
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/update.go b/sdk/update.go
index ba63542..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.
diff --git a/shared/paths.go b/shared/paths.go
index 24ba057..f5dc5dd 100644
--- a/shared/paths.go
+++ b/shared/paths.go
@@ -20,21 +20,23 @@
 	"path/filepath"
 )
 
+// A SharedPaths represents a list of paths that are shared between
+// soong_ui and soong.
+type SharedPaths interface {
+	// BazelMetricsDir returns the path where a set of bazel profile
+	// files are stored for later processed by the metrics pipeline.
+	BazelMetricsDir() string
+}
+
 // Given the out directory, returns the root of the temp directory (to be cleared at the start of each execution of Soong)
 func TempDirForOutDir(outDir string) (tempPath string) {
 	return filepath.Join(outDir, ".temp")
 }
 
-// BazelMetricsDir returns the path where a set of bazel profile
-// files are stored for later processed by the metrics pipeline.
-func BazelMetricsDir(outDir string) string {
-	return filepath.Join(outDir, "bazel_metrics")
-}
-
 // BazelMetricsFilename returns the bazel profile filename based
 // on the action name. This is to help to store a set of bazel
 // profiles since bazel may execute multiple times during a single
 // build.
-func BazelMetricsFilename(outDir, actionName string) string {
-	return filepath.Join(BazelMetricsDir(outDir), actionName+"_bazel_profile.gz")
+func BazelMetricsFilename(s SharedPaths, actionName string) string {
+	return filepath.Join(s.BazelMetricsDir(), actionName+"_bazel_profile.gz")
 }
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 828d1cf..6a53414 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -247,16 +247,16 @@
 	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. compares current.txt to api-dump.txt
 	// current.txt should be identical to api-dump.txt.
@@ -284,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").
@@ -297,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 {
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index 2d36f67..d9c2266 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,29 @@
 	"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) {
@@ -43,14 +68,18 @@
 
 	// 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.
-	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())
+	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.
@@ -72,20 +101,40 @@
 		// ninja_build target.
 		"--output_groups="+outputGroups,
 		// Generate a performance profile
-		"--profile="+filepath.Join(shared.BazelMetricsFilename(config.OutDir(), actionName)),
+		"--profile="+filepath.Join(shared.BazelMetricsFilename(config, actionName)),
 		"--slim_profile=true",
 	)
 
-	// Append custom build flags to the Bazel command. Changes to these flags
-	// may invalidate Bazel's analysis cache.
-	if extraBuildArgs, ok := cmd.Environment.Get("BAZEL_BUILD_ARGS"); ok {
-		cmd.Args = append(cmd.Args, strings.Fields(extraBuildArgs)...)
-	}
+	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)
+		}
 
-	// Append the label of the default ninja_build target.
-	cmd.Args = append(cmd.Args,
-		"//:"+config.TargetProduct()+"-"+config.TargetBuildVariant(),
-	)
+		// 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
@@ -95,15 +144,36 @@
 		cmd.Args = append(cmd.Args, "--action_env=PATH="+pathEnvValue)
 	}
 
-	cmd.Environment.Set("DIST_DIR", config.DistDir())
-	cmd.Environment.Set("SHELL", "/bin/bash")
+	// 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)...)
+	}
 
-	// Print the full command line for debugging purposes.
-	ctx.Println(cmd.Cmd)
+	// Append the label of the default ninja_build target.
+	cmd.Args = append(cmd.Args,
+		"//:"+config.TargetProduct()+"-"+config.TargetBuildVariant(),
+	)
 
 	// Execute the command at the root of the directory.
 	cmd.Dir = filepath.Join(config.OutDir(), "..")
-	ctx.Status.Status("Starting Bazel..")
+
+	for k, v := range bazelEnv {
+		cmd.Environment.Set(k, v)
+	}
+
+	// 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)
+	}
+
+	// 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()
@@ -113,28 +183,13 @@
 	// Ensure that the $OUT_DIR contains the expected set of files by symlinking
 	// the files from the execution root's output direction into $OUT_DIR.
 
-	// Obtain the Bazel output directory for ninja_build.
-	infoCmd := Command(ctx, config, "bazel", bazelExecutable)
-
-	if extraStartupArgs, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
-		infoCmd.Args = append(infoCmd.Args, strings.Fields(extraStartupArgs)...)
-	}
-
-	// Obtain the output directory path in the execution root.
-	infoCmd.Args = append(infoCmd.Args,
-		"info",
-		"output_path",
-	)
-
-	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())
+	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, ".")
 }
 
@@ -147,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 cfd0b83..e8f0fc4 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -28,7 +28,7 @@
 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() {
+	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
@@ -44,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)
 		}
@@ -67,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
 		}
@@ -85,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
@@ -97,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 {
@@ -108,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")
@@ -115,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)
 	}
 
@@ -139,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)
@@ -166,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)
+		help(ctx, config)
 		return
 	}
 
@@ -191,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)
@@ -216,12 +233,16 @@
 		runMakeProductConfig(ctx, config)
 	}
 
+	// Everything below here depends on product config.
+
 	if inList("installclean", config.Arguments()) ||
 		inList("install-clean", config.Arguments()) {
 		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)
 		ctx.Println("Deleted data files.")
@@ -240,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 {
@@ -259,7 +280,7 @@
 	}
 
 	if what&BuildNinja != 0 {
-		if !config.SkipMake() {
+		if what&BuildKati != 0 {
 			installCleanIfNecessary(ctx, config)
 		}
 
@@ -267,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)
 	}
@@ -282,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())
 	}
 }
@@ -304,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/config.go b/ui/build/config.go
index 229bd5c..72ae3fe 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
@@ -274,7 +275,7 @@
 		}
 	}
 
-	bpd := shared.BazelMetricsDir(ret.OutDir())
+	bpd := ret.BazelMetricsDir()
 	if err := os.RemoveAll(bpd); err != nil {
 		ctx.Fatalf("Unable to remove bazel profile directory %q: %v", bpd, err)
 	}
@@ -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,7 +701,7 @@
 }
 
 func (c *configImpl) NinjaArgs() []string {
-	if c.skipMake {
+	if c.skipKati {
 		return c.arguments
 	}
 	return c.ninjaArgs
@@ -740,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 {
@@ -1113,3 +1121,20 @@
 	}
 	return ""
 }
+
+// LogsDir returns the logs directory where build log and metrics
+// files are located. By default, the logs directory is the out
+// directory. If the argument dist is specified, the logs directory
+// is <dist_dir>/logs.
+func (c *configImpl) LogsDir() string {
+	if c.Dist() {
+		return filepath.Join(c.DistDir(), "logs")
+	}
+	return c.OutDir()
+}
+
+// BazelMetricsDir returns the <logs dir>/bazel_metrics directory
+// where the bazel profiles are located.
+func (c *configImpl) BazelMetricsDir() string {
+	return filepath.Join(c.LogsDir(), "bazel_metrics")
+}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index ffd1ab9..7799766 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -139,15 +139,6 @@
 			// 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",
diff --git a/ui/build/soong.go b/ui/build/soong.go
index bb5cbf0..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")
 		}
 
@@ -145,7 +162,10 @@
 		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)
@@ -153,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