Merge "Add and update comments in ui/build/upload.go"
diff --git a/Android.bp b/Android.bp
index e7a5de2..866ed25 100644
--- a/Android.bp
+++ b/Android.bp
@@ -75,6 +75,10 @@
     product_available: true,
     recovery_available: true,
     native_bridge_supported: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 
     arch: {
         arm: {
diff --git a/android/Android.bp b/android/Android.bp
index 8f89a59..4bd272d 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -4,8 +4,10 @@
     deps: [
         "blueprint",
         "blueprint-bootstrap",
+        "sbox_proto",
         "soong",
         "soong-android-soongconfig",
+        "soong-bazel",
         "soong-env",
         "soong-shared",
         "soong-ui-metrics_proto",
@@ -15,6 +17,7 @@
         "apex.go",
         "api_levels.go",
         "arch.go",
+        "arch_list.go",
         "bazel_handler.go",
         "config.go",
         "csuite_config.go",
@@ -33,11 +36,13 @@
         "mutator.go",
         "namespace.go",
         "neverallow.go",
+        "ninja_deps.go",
         "notices.go",
         "onceper.go",
         "override_module.go",
         "package.go",
         "package_ctx.go",
+        "packaging.go",
         "path_properties.go",
         "paths.go",
         "phony.go",
@@ -75,8 +80,10 @@
         "mutator_test.go",
         "namespace_test.go",
         "neverallow_test.go",
+        "ninja_deps_test.go",
         "onceper_test.go",
         "package_test.go",
+        "packaging_test.go",
         "path_properties_test.go",
         "paths_test.go",
         "prebuilt_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index cfd7c91..063830b 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -177,24 +177,84 @@
 	a.EntryMap[name] = append(a.EntryMap[name], value...)
 }
 
-// Compute the list of Make strings to declare phone goals and dist-for-goals
-// calls from the module's dist and dists properties.
-func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+// The contributions to the dist.
+type distContributions struct {
+	// List of goals and the dist copy instructions.
+	copiesForGoals []*copiesForGoals
+}
+
+// getCopiesForGoals returns a copiesForGoals into which copy instructions that
+// must be processed when building one or more of those goals can be added.
+func (d *distContributions) getCopiesForGoals(goals string) *copiesForGoals {
+	copiesForGoals := &copiesForGoals{goals: goals}
+	d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
+	return copiesForGoals
+}
+
+// Associates a list of dist copy instructions with a set of goals for which they
+// should be run.
+type copiesForGoals struct {
+	// goals are a space separated list of build targets that will trigger the
+	// copy instructions.
+	goals string
+
+	// A list of instructions to copy a module's output files to somewhere in the
+	// dist directory.
+	copies []distCopy
+}
+
+// Adds a copy instruction.
+func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
+	d.copies = append(d.copies, distCopy{from, dest})
+}
+
+// Instruction on a path that must be copied into the dist.
+type distCopy struct {
+	// The path to copy from.
+	from Path
+
+	// The destination within the dist directory to copy to.
+	dest string
+}
+
+// Compute the contributions that the module makes to the dist.
+func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
 	amod := mod.(Module).base()
 	name := amod.BaseModuleName()
 
-	var ret []string
+	// Collate the set of associated tag/paths available for copying to the dist.
+	// Start with an empty (nil) set.
 	var availableTaggedDists TaggedDistFiles
 
+	// Then merge in any that are provided explicitly by the module.
 	if a.DistFiles != nil {
-		availableTaggedDists = a.DistFiles
-	} else if a.OutputFile.Valid() {
-		availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
-	} else {
+		// Merge the DistFiles into the set.
+		availableTaggedDists = availableTaggedDists.merge(a.DistFiles)
+	}
+
+	// If no paths have been provided for the DefaultDistTag and the output file is
+	// valid then add that as the default dist path.
+	if _, ok := availableTaggedDists[DefaultDistTag]; !ok && a.OutputFile.Valid() {
+		availableTaggedDists = availableTaggedDists.addPathsForTag(DefaultDistTag, a.OutputFile.Path())
+	}
+
+	// If the distFiles created by GenerateTaggedDistFiles contains paths for the
+	// DefaultDistTag then that takes priority so delete any existing paths.
+	if _, ok := amod.distFiles[DefaultDistTag]; ok {
+		delete(availableTaggedDists, DefaultDistTag)
+	}
+
+	// Finally, merge the distFiles created by GenerateTaggedDistFiles.
+	availableTaggedDists = availableTaggedDists.merge(amod.distFiles)
+
+	if len(availableTaggedDists) == 0 {
 		// Nothing dist-able for this module.
 		return nil
 	}
 
+	// Collate the contributions this module makes to the dist.
+	distContributions := &distContributions{}
+
 	// Iterate over this module's dist structs, merged from the dist and dists properties.
 	for _, dist := range amod.Dists() {
 		// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
@@ -204,7 +264,7 @@
 		var tag string
 		if dist.Tag == nil {
 			// If the dist struct does not specify a tag, use the default output files tag.
-			tag = ""
+			tag = DefaultDistTag
 		} else {
 			tag = *dist.Tag
 		}
@@ -218,15 +278,15 @@
 		}
 
 		if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
-			errorMessage := "Cannot apply dest/suffix for more than one dist " +
-				"file for %s goals in module %s. The list of dist files, " +
+			errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
+				"file for %q goals tag %q in module %s. The list of dist files, " +
 				"which should have a single element, is:\n%s"
-			panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+			panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
 		}
 
-		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+		copiesForGoals := distContributions.getCopiesForGoals(goals)
 
-		// Create dist-for-goals calls for each path in the dist'd files.
+		// Iterate over each path adding a copy instruction to copiesForGoals
 		for _, path := range tagPaths {
 			// It's possible that the Path is nil from errant modules. Be defensive here.
 			if path == nil {
@@ -261,15 +321,41 @@
 				}
 			}
 
+			copiesForGoals.addCopyInstruction(path, dest)
+		}
+	}
+
+	return distContributions
+}
+
+// generateDistContributionsForMake generates make rules that will generate the
+// dist according to the instructions in the supplied distContribution.
+func generateDistContributionsForMake(distContributions *distContributions) []string {
+	var ret []string
+	for _, d := range distContributions.copiesForGoals {
+		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
+		// Create dist-for-goals calls for each of the copy instructions.
+		for _, c := range d.copies {
 			ret = append(
 				ret,
-				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
 		}
 	}
 
 	return ret
 }
 
+// Compute the list of Make strings to declare phony goals and dist-for-goals
+// calls from the module's dist and dists properties.
+func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+	distContributions := a.getDistContributions(mod)
+	if distContributions == nil {
+		return nil
+	}
+
+	return generateDistContributionsForMake(distContributions)
+}
+
 func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
 	a.EntryMap = make(map[string][]string)
 	amod := mod.(Module).base()
@@ -417,7 +503,7 @@
 type androidMkSingleton struct{}
 
 func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
-	if !ctx.Config().EmbeddedInMake() {
+	if !ctx.Config().KatiEnabled() {
 		return
 	}
 
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 10527b9..347b92e 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,17 +18,77 @@
 	"fmt"
 	"io"
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type customModule struct {
 	ModuleBase
-	data      AndroidMkData
-	distFiles TaggedDistFiles
+
+	properties struct {
+		Default_dist_files *string
+		Dist_output_file   *bool
+	}
+
+	data       AndroidMkData
+	distFiles  TaggedDistFiles
+	outputFile OptionalPath
+
+	// The paths that will be used as the default dist paths if no tag is
+	// specified.
+	defaultDistPaths Paths
 }
 
+const (
+	defaultDistFiles_None    = "none"
+	defaultDistFiles_Default = "default"
+	defaultDistFiles_Tagged  = "tagged"
+)
+
 func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	m.distFiles = m.GenerateTaggedDistFiles(ctx)
+
+	// If the dist_output_file: true then create an output file that is stored in
+	// the OutputFile property of the AndroidMkEntry.
+	if proptools.BoolDefault(m.properties.Dist_output_file, true) {
+		path := PathForTesting("dist-output-file.out")
+		m.outputFile = OptionalPathForPath(path)
+
+		// Previous code would prioritize the DistFiles property over the OutputFile
+		// property in AndroidMkEntry when determining the default dist paths.
+		// Setting this first allows it to be overridden based on the
+		// default_dist_files setting replicating that previous behavior.
+		m.defaultDistPaths = Paths{path}
+	}
+
+	// Based on the setting of the default_dist_files property possibly create a
+	// TaggedDistFiles structure that will be stored in the DistFiles property of
+	// the AndroidMkEntry.
+	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
+	switch defaultDistFiles {
+	case defaultDistFiles_None:
+		// Do nothing
+
+	case defaultDistFiles_Default:
+		path := PathForTesting("default-dist.out")
+		m.defaultDistPaths = Paths{path}
+		m.distFiles = MakeDefaultDistFiles(path)
+
+	case defaultDistFiles_Tagged:
+		// Module types that set AndroidMkEntry.DistFiles to the result of calling
+		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
+		// meant that the default dist paths would be whatever was returned by
+		// OutputFiles(""). In order to preserve that behavior when treating no tag
+		// as being equal to DefaultDistTag this ensures that
+		// OutputFiles(DefaultDistTag) will return the same as OutputFiles("").
+		m.defaultDistPaths = PathsForTesting("one.out")
+
+		// This must be called after setting defaultDistPaths/outputFile as
+		// GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those
+		// fields.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+	}
 }
 
 func (m *customModule) AndroidMk() AndroidMkData {
@@ -41,6 +101,12 @@
 
 func (m *customModule) OutputFiles(tag string) (Paths, error) {
 	switch tag {
+	case DefaultDistTag:
+		if m.defaultDistPaths != nil {
+			return m.defaultDistPaths, nil
+		} else {
+			return nil, fmt.Errorf("default dist tag is not available")
+		}
 	case "":
 		return PathsForTesting("one.out"), nil
 	case ".multiple":
@@ -55,18 +121,43 @@
 func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
 	return []AndroidMkEntries{
 		{
-			Class:     "CUSTOM_MODULE",
-			DistFiles: m.distFiles,
+			Class:      "CUSTOM_MODULE",
+			DistFiles:  m.distFiles,
+			OutputFile: m.outputFile,
 		},
 	}
 }
 
 func customModuleFactory() Module {
 	module := &customModule{}
+
+	module.AddProperties(&module.properties)
+
 	InitAndroidModule(module)
 	return module
 }
 
+// buildConfigAndCustomModuleFoo creates a config object, processes the supplied
+// bp module and then returns the config and the custom module called "foo".
+func buildConfigAndCustomModuleFoo(t *testing.T, bp string) (Config, *customModule) {
+	t.Helper()
+	config := TestConfig(buildDir, nil, bp, nil)
+	config.katiEnabled = true // Enable androidmk Singleton
+
+	ctx := NewTestContext(config)
+	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
+	ctx.RegisterModuleType("custom", customModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	return config, module
+}
+
 func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
 	bp := `
 	custom {
@@ -77,20 +168,7 @@
 	}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.inMake = true // Enable androidmk Singleton
-
-	ctx := NewTestContext(config)
-	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-	ctx.RegisterModuleType("custom", customModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	m := ctx.ModuleForTests("foo", "").Module().(*customModule)
+	_, m := buildConfigAndCustomModuleFoo(t, bp)
 
 	assertEqual := func(expected interface{}, actual interface{}) {
 		if !reflect.DeepEqual(expected, actual) {
@@ -102,101 +180,29 @@
 	assertEqual([]string{"qux"}, m.data.Target_required)
 }
 
-func TestGetDistForGoals(t *testing.T) {
-	testCases := []struct {
-		bp                     string
-		expectedAndroidMkLines []string
-	}{
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"]
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
-					tag: ".another-tag",
-				}
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-						tag: ".another-tag",
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,another.out:another.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dists: [
-					{
-						targets: ["my_goal"],
-					},
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-			},
-		},
-		{
-			bp: `
-			custom {
-				name: "foo",
-				dist: {
-					targets: ["my_goal"],
+func TestGenerateDistContributionsForMake(t *testing.T) {
+	dc := &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+					distCopyForTest("two.out", "other.out"),
 				},
-				dists: [
-					{
-						targets: ["my_second_goal", "my_third_goal"],
-					},
-				],
-			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal my_third_goal\n",
-				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
-				".PHONY: my_goal\n",
-				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
 			},
 		},
-		{
-			bp: `
+	}
+
+	makeOutput := generateDistContributionsForMake(dc)
+
+	assertStringEquals(t, `.PHONY: my_goal
+$(call dist-for-goals,my_goal,one.out:one.out)
+$(call dist-for-goals,my_goal,two.out:other.out)
+`, strings.Join(makeOutput, ""))
+}
+
+func TestGetDistForGoals(t *testing.T) {
+	bp := `
 			custom {
 				name: "foo",
 				dist: {
@@ -228,64 +234,522 @@
 					},
 				],
 			}
-			`,
-			expectedAndroidMkLines: []string{
-				".PHONY: my_second_goal\n",
-				"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
-				".PHONY: my_third_goal\n",
-				"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
-				".PHONY: my_fourth_goal\n",
-				"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
-				".PHONY: my_fifth_goal\n",
-				"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
-				".PHONY: my_sixth_goal\n",
-				"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
-				".PHONY: my_goal my_other_goal\n",
-				"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
-				"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
-			},
-		},
+			`
+
+	expectedAndroidMkLines := []string{
+		".PHONY: my_second_goal\n",
+		"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
+		".PHONY: my_third_goal\n",
+		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
+		".PHONY: my_fourth_goal\n",
+		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
+		".PHONY: my_fifth_goal\n",
+		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
+		".PHONY: my_sixth_goal\n",
+		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
+		".PHONY: my_goal my_other_goal\n",
+		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
+		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
 	}
 
-	for _, testCase := range testCases {
-		config := TestConfig(buildDir, nil, testCase.bp, nil)
-		config.inMake = true // Enable androidmk Singleton
+	config, module := buildConfigAndCustomModuleFoo(t, bp)
+	entries := AndroidMkEntriesForTest(t, config, "", module)
+	if len(entries) != 1 {
+		t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+	}
+	androidMkLines := entries[0].GetDistForGoals(module)
 
-		ctx := NewTestContext(config)
-		ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register()
-
-		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-		FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		FailIfErrored(t, errs)
-
-		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
-		entries := AndroidMkEntriesForTest(t, config, "", module)
-		if len(entries) != 1 {
-			t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
-		}
-		androidMkLines := entries[0].GetDistForGoals(module)
-
-		if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
+	if len(androidMkLines) != len(expectedAndroidMkLines) {
+		t.Errorf(
+			"Expected %d AndroidMk lines, got %d:\n%v",
+			len(expectedAndroidMkLines),
+			len(androidMkLines),
+			androidMkLines,
+		)
+	}
+	for idx, line := range androidMkLines {
+		expectedLine := expectedAndroidMkLines[idx]
+		if line != expectedLine {
 			t.Errorf(
-				"Expected %d AndroidMk lines, got %d:\n%v",
-				len(testCase.expectedAndroidMkLines),
-				len(androidMkLines),
-				androidMkLines,
+				"Expected AndroidMk line to be '%s', got '%s'",
+				expectedLine,
+				line,
 			)
 		}
-		for idx, line := range androidMkLines {
-			expectedLine := testCase.expectedAndroidMkLines[idx]
-			if line != expectedLine {
-				t.Errorf(
-					"Expected AndroidMk line to be '%s', got '%s'",
-					line,
-					expectedLine,
-				)
+	}
+}
+
+func distCopyForTest(from, to string) distCopy {
+	return distCopy{PathForTesting(from), to}
+}
+
+func TestGetDistContributions(t *testing.T) {
+	compareContributions := func(d1 *distContributions, d2 *distContributions) error {
+		if d1 == nil || d2 == nil {
+			if d1 != d2 {
+				return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2)
+			} else {
+				return nil
 			}
 		}
+		if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual {
+			return fmt.Errorf("length mismatch, expected %d found %d", expected, actual)
+		}
+
+		for i, copies1 := range d1.copiesForGoals {
+			copies2 := d2.copiesForGoals[i]
+			if expected, actual := copies1.goals, copies2.goals; expected != actual {
+				return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual)
+			}
+
+			if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual {
+				return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual)
+			}
+
+			for j, c1 := range copies1.copies {
+				c2 := copies2.copies[j]
+				if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual {
+					return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+
+				if expected, actual := c1.dest, c2.dest; expected != actual {
+					return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+				}
+			}
+		}
+
+		return nil
 	}
+
+	formatContributions := func(d *distContributions) string {
+		buf := &strings.Builder{}
+		if d == nil {
+			fmt.Fprint(buf, "nil")
+		} else {
+			for _, copiesForGoals := range d.copiesForGoals {
+				fmt.Fprintf(buf, "    Goals: %q {\n", copiesForGoals.goals)
+				for _, c := range copiesForGoals.copies {
+					fmt.Fprintf(buf, "        %s -> %s\n", NormalizePathForTesting(c.from), c.dest)
+				}
+				fmt.Fprint(buf, "    }\n")
+			}
+		}
+		return buf.String()
+	}
+
+	testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) {
+		t.Helper()
+		t.Run(name, func(t *testing.T) {
+			t.Helper()
+
+			config, module := buildConfigAndCustomModuleFoo(t, bp)
+			entries := AndroidMkEntriesForTest(t, config, "", module)
+			if len(entries) != 1 {
+				t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+			}
+			distContributions := entries[0].getDistContributions(module)
+
+			if err := compareContributions(expectedContributions, distContributions); err != nil {
+				t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s",
+					err,
+					formatContributions(expectedContributions),
+					formatContributions(distContributions))
+			}
+		})
+	}
+
+	testHelper(t, "dist-without-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"]
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-with-tag", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+					tag: ".another-tag",
+				}
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dists-with-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+						tag: ".another-tag",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("another.out", "another.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "multiple-dists-with-and-without-tag", `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-without-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+				},
+				dists: [
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+				{
+					goals: "my_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.out"),
+					},
+				},
+			},
+		})
+
+	testHelper(t, "dist-plus-dists-with-tags", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal", "my_other_goal"],
+					tag: ".multiple",
+				},
+				dists: [
+					{
+						targets: ["my_second_goal"],
+						tag: ".multiple",
+					},
+					{
+						targets: ["my_third_goal"],
+						dir: "test/dir",
+					},
+					{
+						targets: ["my_fourth_goal"],
+						suffix: ".suffix",
+					},
+					{
+						targets: ["my_fifth_goal"],
+						dest: "new-name",
+					},
+					{
+						targets: ["my_sixth_goal"],
+						dest: "new-name",
+						dir: "some/dir",
+						suffix: ".suffix",
+					},
+				],
+			}
+`,
+		&distContributions{
+			copiesForGoals: []*copiesForGoals{
+				{
+					goals: "my_second_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+				{
+					goals: "my_third_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "test/dir/one.out"),
+					},
+				},
+				{
+					goals: "my_fourth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "one.suffix.out"),
+					},
+				},
+				{
+					goals: "my_fifth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "new-name"),
+					},
+				},
+				{
+					goals: "my_sixth_goal",
+					copies: []distCopy{
+						distCopyForTest("one.out", "some/dir/new-name.suffix"),
+					},
+				},
+				{
+					goals: "my_goal my_other_goal",
+					copies: []distCopy{
+						distCopyForTest("two.out", "two.out"),
+						distCopyForTest("three/four.out", "four.out"),
+					},
+				},
+			},
+		})
+
+	// The above test the default values of default_dist_files and use_output_file.
+
+	// The following tests explicitly test the different combinations of those settings.
+	testHelper(t, "tagged-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: false,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-no-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: false,
+				dists: [
+					// The following is silently ignored because there is not default file
+					// in either the dist files or the output file.
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "tagged-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "tagged",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "default-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "default",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("default-dist.out", "default-dist.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
+
+	testHelper(t, "no-dist-files-default-output", `
+			custom {
+				name: "foo",
+				default_dist_files: "none",
+				dist_output_file: true,
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_goal"],
+						tag: ".multiple",
+					},
+				],
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("dist-output-file.out", "dist-output-file.out"),
+				},
+			},
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("two.out", "two.out"),
+					distCopyForTest("three/four.out", "four.out"),
+				},
+			},
+		},
+	})
 }
diff --git a/android/apex.go b/android/apex.go
index 276f7a4..a4ff0f9 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -25,32 +25,54 @@
 )
 
 var (
+	// This is the sdk version when APEX was first introduced
 	SdkVersion_Android10 = uncheckedFinalApiLevel(29)
 )
 
-// ApexInfo describes the metadata common to all modules in an apexBundle.
+// ApexInfo describes the metadata about one or more apexBundles that an apex variant of a module is
+// part of.  When an apex variant is created, the variant is associated with one apexBundle. But
+// when multiple apex variants are merged for deduping (see mergeApexVariations), this holds the
+// information about the apexBundles that are merged together.
+// Accessible via `ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)`
 type ApexInfo struct {
-	// Name of the apex variation that this module is mutated into, or "" for
-	// a platform variant.  Note that a module can be included in multiple APEXes,
-	// in which case, the module is mutated into one or more variants, each of
-	// which is for one or more APEXes.
+	// Name of the apex variation that this module (i.e. the apex variant of the module) is
+	// mutated into, or "" for a platform (i.e. non-APEX) variant. Note that a module can be
+	// included in multiple APEXes, in which case, the module is mutated into one or more
+	// variants, each of which is for an APEX. The variants then can later be deduped if they
+	// don't need to be compiled differently. This is an optimization done in
+	// mergeApexVariations.
 	ApexVariationName string
 
-	// Serialized ApiLevel. Use via MinSdkVersion() method. Cannot be stored in
-	// its struct form because this is cloned into properties structs, and
-	// ApiLevel has private members.
+	// Serialized ApiLevel that this module has to support at minimum. Should be accessed via
+	// MinSdkVersion() method. Cannot be stored in its struct form because this is cloned into
+	// properties structs, and ApiLevel has private members.
 	MinSdkVersionStr string
 
-	// True if the module comes from an updatable APEX.
-	Updatable    bool
+	// True if this module comes from an updatable apexBundle.
+	Updatable bool
+
+	// The list of SDK modules that the containing apexBundle depends on.
 	RequiredSdks SdkRefs
 
-	InApexes     []string
+	// List of apexBundles that this apex variant of the module is associated with. Initially,
+	// the size of this list is one because one apex variant is associated with one apexBundle.
+	// When multiple apex variants are merged in mergeApexVariations, ApexInfo struct of the
+	// merged variant holds the list of apexBundles that are merged together.
+	InApexes []string
+
+	// Pointers to the ApexContents struct each of which is for apexBundle modules that this
+	// module is part of. The ApexContents gives information about which modules the apexBundle
+	// has and whether a module became part of the apexBundle via a direct dependency or not.
 	ApexContents []*ApexContents
 }
 
 var ApexInfoProvider = blueprint.NewMutatorProvider(ApexInfo{}, "apex")
 
+// mergedName gives the name of the alias variation that will be used when multiple apex variations
+// of a module can be deduped into one variation. For example, if libfoo is included in both apex.a
+// and apex.b, and if the two APEXes have the same min_sdk_version (say 29), then libfoo doesn't
+// have to be built twice, but only once. In that case, the two apex variations apex.a and apex.b
+// are configured to have the same alias variation named apex29.
 func (i ApexInfo) mergedName(ctx PathContext) string {
 	name := "apex" + strconv.Itoa(i.MinSdkVersion(ctx).FinalOrFutureInt())
 	for _, sdk := range i.RequiredSdks {
@@ -59,14 +81,19 @@
 	return name
 }
 
-func (this *ApexInfo) MinSdkVersion(ctx PathContext) ApiLevel {
-	return ApiLevelOrPanic(ctx, this.MinSdkVersionStr)
+// MinSdkVersion gives the api level that this module has to support at minimum. This is from the
+// min_sdk_version property of the containing apexBundle.
+func (i ApexInfo) MinSdkVersion(ctx PathContext) ApiLevel {
+	return ApiLevelOrPanic(ctx, i.MinSdkVersionStr)
 }
 
+// IsForPlatform tells whether this module is for the platform or not. If false is returned, it
+// means that this apex variant of the module is built for an APEX.
 func (i ApexInfo) IsForPlatform() bool {
 	return i.ApexVariationName == ""
 }
 
+// InApex tells whether this apex variant of the module is part of the given apexBundle or not.
 func (i ApexInfo) InApex(apex string) bool {
 	for _, a := range i.InApexes {
 		if a == apex {
@@ -76,102 +103,111 @@
 	return false
 }
 
-// ApexTestForInfo stores the contents of APEXes for which this module is a test and thus has
-// access to APEX internals.
+// ApexTestForInfo stores the contents of APEXes for which this module is a test - although this
+// module is not part of the APEX - and thus has access to APEX internals.
 type ApexTestForInfo struct {
 	ApexContents []*ApexContents
 }
 
 var ApexTestForInfoProvider = blueprint.NewMutatorProvider(ApexTestForInfo{}, "apex_test_for")
 
-// Extracted from ApexModule to make it easier to define custom subsets of the
-// ApexModule interface and improve code navigation within the IDE.
+// DepIsInSameApex defines an interface that should be used to determine whether a given dependency
+// should be considered as part of the same APEX as the current module or not. Note: this was
+// extracted from ApexModule to make it easier to define custom subsets of the ApexModule interface
+// and improve code navigation within the IDE.
 type DepIsInSameApex interface {
-	// DepIsInSameApex tests if the other module 'dep' is installed to the same
-	// APEX as this module
+	// DepIsInSameApex tests if the other module 'dep' is considered as part of the same APEX as
+	// this module. For example, a static lib dependency usually returns true here, while a
+	// shared lib dependency to a stub library returns false.
 	DepIsInSameApex(ctx BaseModuleContext, dep Module) bool
 }
 
-// ApexModule is the interface that a module type is expected to implement if
-// the module has to be built differently depending on whether the module
-// is destined for an apex or not (installed to one of the regular partitions).
+// ApexModule is the interface that a module type is expected to implement if the module has to be
+// built differently depending on whether the module is destined for an APEX or not (i.e., installed
+// to one of the regular partitions).
 //
-// Native shared libraries are one such module type; when it is built for an
-// APEX, it should depend only on stable interfaces such as NDK, stable AIDL,
-// or C APIs from other APEXs.
+// Native shared libraries are one such module type; when it is built for an APEX, it should depend
+// only on stable interfaces such as NDK, stable AIDL, or C APIs from other APEXes.
 //
-// A module implementing this interface will be mutated into multiple
-// variations by apex.apexMutator if it is directly or indirectly included
-// in one or more APEXs. Specifically, if a module is included in apex.foo and
-// apex.bar then three apex variants are created: platform, apex.foo and
-// apex.bar. The platform variant is for the regular partitions
-// (e.g., /system or /vendor, etc.) while the other two are for the APEXs,
-// respectively.
+// A module implementing this interface will be mutated into multiple variations by apex.apexMutator
+// if it is directly or indirectly included in one or more APEXes. Specifically, if a module is
+// included in apex.foo and apex.bar then three apex variants are created: platform, apex.foo and
+// apex.bar. The platform variant is for the regular partitions (e.g., /system or /vendor, etc.)
+// while the other two are for the APEXs, respectively. The latter two variations can be merged (see
+// mergedName) when the two APEXes have the same min_sdk_version requirement.
 type ApexModule interface {
 	Module
 	DepIsInSameApex
 
 	apexModuleBase() *ApexModuleBase
 
-	// Marks that this module should be built for the specified APEX.
-	// Call this before apex.apexMutator is run.
+	// Marks that this module should be built for the specified APEX. Call this BEFORE
+	// apex.apexMutator is run.
 	BuildForApex(apex ApexInfo)
 
-	// Returns true if this module is present in any APEXes
-	// directly or indirectly.
-	// Call this after apex.apexMutator is run.
+	// Returns true if this module is present in any APEX either directly or indirectly. Call
+	// this after apex.apexMutator is run.
 	InAnyApex() bool
 
-	// Returns true if this module is directly in any APEXes.
-	// Call this after apex.apexMutator is run.
+	// Returns true if this module is directly in any APEX. Call this AFTER apex.apexMutator is
+	// run.
 	DirectlyInAnyApex() bool
 
-	// Returns true if any variant of this module is directly in any APEXes.
-	// Call this after apex.apexMutator is run.
+	// Returns true in the primary variant of a module if _any_ variant of the module is
+	// directly in any apex. This includes host, arch, asan, etc. variants. It is unused in any
+	// variant that is not the primary variant. Ideally this wouldn't be used, as it incorrectly
+	// mixes arch variants if only one arch is in an apex, but a few places depend on it, for
+	// example when an ASAN variant is created before the apexMutator. Call this after
+	// apex.apexMutator is run.
 	AnyVariantDirectlyInAnyApex() bool
 
-	// Tests if this module could have APEX variants. APEX variants are
-	// created only for the modules that returns true here. This is useful
-	// for not creating APEX variants for certain types of shared libraries
-	// such as NDK stubs.
+	// Tests if this module could have APEX variants. Even when a module type implements
+	// ApexModule interface, APEX variants are created only for the module instances that return
+	// true here. This is useful for not creating APEX variants for certain types of shared
+	// libraries such as NDK stubs.
 	CanHaveApexVariants() bool
 
-	// Tests if this module can be installed to APEX as a file. For example,
-	// this would return true for shared libs while return false for static
-	// libs.
+	// Tests if this module can be installed to APEX as a file. For example, this would return
+	// true for shared libs while return false for static libs because static libs are not
+	// installable module (but it can still be mutated for APEX)
 	IsInstallableToApex() bool
 
-	// Tests if this module is available for the specified APEX or ":platform"
+	// Tests if this module is available for the specified APEX or ":platform". This is from the
+	// apex_available property of the module.
 	AvailableFor(what string) bool
 
-	// Return true if this module is not available to platform (i.e. apex_available
-	// property doesn't have "//apex_available:platform"), or shouldn't be available
-	// to platform, which is the case when this module depends on other module that
-	// isn't available to platform.
+	// Returns true if this module is not available to platform (i.e. apex_available property
+	// doesn't have "//apex_available:platform"), or shouldn't be available to platform, which
+	// is the case when this module depends on other module that isn't available to platform.
 	NotAvailableForPlatform() bool
 
-	// Mark that this module is not available to platform. Set by the
+	// Marks that this module is not available to platform. Set by the
 	// check-platform-availability mutator in the apex package.
 	SetNotAvailableForPlatform()
 
-	// List of APEXes that this module tests. The module has access to
-	// the private part of the listed APEXes even when it is not included in the
-	// APEXes.
+	// Returns the list of APEXes that this module is a test for. The module has access to the
+	// private part of the listed APEXes even when it is not included in the APEXes. This by
+	// default returns nil. A module type should override the default implementation. For
+	// example, cc_test module type returns the value of test_for here.
 	TestFor() []string
 
-	// Returns nil if this module supports sdkVersion
-	// Otherwise, returns error with reason
+	// Returns nil (success) if this module should support the given sdk version. Returns an
+	// error if not. No default implementation is provided for this method. A module type
+	// implementing this interface should provide an implementation. A module supports an sdk
+	// version when the module's min_sdk_version is equal to or less than the given sdk version.
 	ShouldSupportSdkVersion(ctx BaseModuleContext, sdkVersion ApiLevel) error
 
-	// Returns true if this module needs a unique variation per apex, for example if
-	// use_apex_name_macro is set.
+	// Returns true if this module needs a unique variation per apex, effectively disabling the
+	// deduping. This is turned on when, for example if use_apex_name_macro is set so that each
+	// apex variant should be built with different macro definitions.
 	UniqueApexVariations() bool
 }
 
+// Properties that are common to all module types implementing ApexModule interface.
 type ApexProperties struct {
-	// Availability of this module in APEXes. Only the listed APEXes can contain
-	// this module. If the module has stubs then other APEXes and the platform may
-	// access it through them (subject to visibility).
+	// Availability of this module in APEXes. Only the listed APEXes can contain this module. If
+	// the module has stubs then other APEXes and the platform may access it through them
+	// (subject to visibility).
 	//
 	// "//apex_available:anyapex" is a pseudo APEX name that matches to any APEX.
 	// "//apex_available:platform" refers to non-APEX partitions like "system.img".
@@ -179,29 +215,23 @@
 	// Default is ["//apex_available:platform"].
 	Apex_available []string
 
-	// AnyVariantDirectlyInAnyApex is true in the primary variant of a module if _any_ variant
-	// of the module is directly in any apex.  This includes host, arch, asan, etc. variants.
-	// It is unused in any variant that is not the primary variant.
-	// Ideally this wouldn't be used, as it incorrectly mixes arch variants if only one arch
-	// is in an apex, but a few places depend on it, for example when an ASAN variant is
-	// created before the apexMutator.
-	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// DirectlyInAnyApex is true if any APEX variant (including the "" variant used for the
-	// platform) of this module is directly in any APEX.
-	DirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// DirectlyInAnyApex is true if any APEX variant (including the "" variant used for the
-	// platform) of this module is directly or indirectly in any APEX.
+	// See ApexModule.InAnyApex()
 	InAnyApex bool `blueprint:"mutated"`
 
+	// See ApexModule.DirectlyInAnyApex()
+	DirectlyInAnyApex bool `blueprint:"mutated"`
+
+	// See ApexModule.AnyVariantDirectlyInAnyApex()
+	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
+
+	// See ApexModule.NotAvailableForPlatform()
 	NotAvailableForPlatform bool `blueprint:"mutated"`
 
+	// See ApexModule.UniqueApexVariants()
 	UniqueApexVariationsForDeps bool `blueprint:"mutated"`
 }
 
-// Marker interface that identifies dependencies that are excluded from APEX
-// contents.
+// Marker interface that identifies dependencies that are excluded from APEX contents.
 type ExcludeFromApexContentsTag interface {
 	blueprint.DependencyTag
 
@@ -209,85 +239,122 @@
 	ExcludeFromApexContents()
 }
 
-// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex
-// state from the parent to the child.  For example, stubs libraries are marked as
-// DirectlyInAnyApex if their implementation is in an apex.
+// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
+// from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
+// their implementation is in an apex.
 type CopyDirectlyInAnyApexTag interface {
 	blueprint.DependencyTag
 
+	// Method that differentiates this interface from others.
 	CopyDirectlyInAnyApex()
 }
 
-// Provides default implementation for the ApexModule interface. APEX-aware
+// ApexModuleBase provides the default implementation for the ApexModule interface. APEX-aware
 // modules are expected to include this struct and call InitApexModule().
 type ApexModuleBase struct {
 	ApexProperties ApexProperties
 
 	canHaveApexVariants bool
 
-	apexVariationsLock sync.Mutex // protects apexVariations during parallel apexDepsMutator
-	apexVariations     []ApexInfo
+	apexInfos     []ApexInfo
+	apexInfosLock sync.Mutex // protects apexInfos during parallel apexDepsMutator
 }
 
+// Initializes ApexModuleBase struct. Not calling this (even when inheriting from ApexModuleBase)
+// prevents the module from being mutated for apexBundle.
+func InitApexModule(m ApexModule) {
+	base := m.apexModuleBase()
+	base.canHaveApexVariants = true
+
+	m.AddProperties(&base.ApexProperties)
+}
+
+// Implements ApexModule
 func (m *ApexModuleBase) apexModuleBase() *ApexModuleBase {
 	return m
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) ApexAvailable() []string {
 	return m.ApexProperties.Apex_available
 }
 
-func (m *ApexModuleBase) TestFor() []string {
-	// To be implemented by concrete types inheriting ApexModuleBase
-	return nil
-}
-
-func (m *ApexModuleBase) UniqueApexVariations() bool {
-	return false
-}
-
+// Implements ApexModule
 func (m *ApexModuleBase) BuildForApex(apex ApexInfo) {
-	m.apexVariationsLock.Lock()
-	defer m.apexVariationsLock.Unlock()
-	for _, v := range m.apexVariations {
+	m.apexInfosLock.Lock()
+	defer m.apexInfosLock.Unlock()
+	for _, v := range m.apexInfos {
 		if v.ApexVariationName == apex.ApexVariationName {
 			return
 		}
 	}
-	m.apexVariations = append(m.apexVariations, apex)
+	m.apexInfos = append(m.apexInfos, apex)
 }
 
-func (m *ApexModuleBase) DirectlyInAnyApex() bool {
-	return m.ApexProperties.DirectlyInAnyApex
-}
-
-func (m *ApexModuleBase) AnyVariantDirectlyInAnyApex() bool {
-	return m.ApexProperties.AnyVariantDirectlyInAnyApex
-}
-
+// Implements ApexModule
 func (m *ApexModuleBase) InAnyApex() bool {
 	return m.ApexProperties.InAnyApex
 }
 
+// Implements ApexModule
+func (m *ApexModuleBase) DirectlyInAnyApex() bool {
+	return m.ApexProperties.DirectlyInAnyApex
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) AnyVariantDirectlyInAnyApex() bool {
+	return m.ApexProperties.AnyVariantDirectlyInAnyApex
+}
+
+// Implements ApexModule
 func (m *ApexModuleBase) CanHaveApexVariants() bool {
 	return m.canHaveApexVariants
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) IsInstallableToApex() bool {
-	// should be overriden if needed
+	// If needed, this will bel overridden by concrete types inheriting
+	// ApexModuleBase
 	return false
 }
 
+// Implements ApexModule
+func (m *ApexModuleBase) TestFor() []string {
+	// If needed, this will be overridden by concrete types inheriting
+	// ApexModuleBase
+	return nil
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) UniqueApexVariations() bool {
+	// If needed, this will bel overridden by concrete types inheriting
+	// ApexModuleBase
+	return false
+}
+
+// Implements ApexModule
+func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
+	// By default, if there is a dependency from A to B, we try to include both in the same
+	// APEX, unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning
+	// true. This is overridden by some module types like apex.ApexBundle, cc.Module,
+	// java.Module, etc.
+	return true
+}
+
 const (
 	AvailableToPlatform = "//apex_available:platform"
 	AvailableToAnyApex  = "//apex_available:anyapex"
 	AvailableToGkiApex  = "com.android.gki.*"
 )
 
+// CheckAvailableForApex provides the default algorithm for checking the apex availability. When the
+// availability is empty, it defaults to ["//apex_available:platform"] which means "available to the
+// platform but not available to any APEX". When the list is not empty, `what` is matched against
+// the list. If there is any matching element in the list, thus function returns true. The special
+// availability "//apex_available:anyapex" matches with anything except for
+// "//apex_available:platform".
 func CheckAvailableForApex(what string, apex_available []string) bool {
 	if len(apex_available) == 0 {
-		// apex_available defaults to ["//apex_available:platform"],
-		// which means 'available to the platform but no apexes'.
 		return what == AvailableToPlatform
 	}
 	return InList(what, apex_available) ||
@@ -295,25 +362,22 @@
 		(strings.HasPrefix(what, "com.android.gki.") && InList(AvailableToGkiApex, apex_available))
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) AvailableFor(what string) bool {
 	return CheckAvailableForApex(what, m.ApexProperties.Apex_available)
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) NotAvailableForPlatform() bool {
 	return m.ApexProperties.NotAvailableForPlatform
 }
 
+// Implements ApexModule
 func (m *ApexModuleBase) SetNotAvailableForPlatform() {
 	m.ApexProperties.NotAvailableForPlatform = true
 }
 
-func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
-	// By default, if there is a dependency from A to B, we try to include both in the same APEX,
-	// unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning true.
-	// This is overridden by some module types like apex.ApexBundle, cc.Module, java.Module, etc.
-	return true
-}
-
+// This function makes sure that the apex_available property is valid
 func (m *ApexModuleBase) checkApexAvailableProperty(mctx BaseModuleContext) {
 	for _, n := range m.ApexProperties.Apex_available {
 		if n == AvailableToPlatform || n == AvailableToAnyApex || n == AvailableToGkiApex {
@@ -331,22 +395,23 @@
 func (a byApexName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a byApexName) Less(i, j int) bool { return a[i].ApexVariationName < a[j].ApexVariationName }
 
-// mergeApexVariations deduplicates APEX variations that would build identically into a common
-// variation.  It returns the reduced list of variations and a list of aliases from the original
+// mergeApexVariations deduplicates apex variations that would build identically into a common
+// variation. It returns the reduced list of variations and a list of aliases from the original
 // variation names to the new variation names.
-func mergeApexVariations(ctx PathContext, apexVariations []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
-	sort.Sort(byApexName(apexVariations))
+func mergeApexVariations(ctx PathContext, apexInfos []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
+	sort.Sort(byApexName(apexInfos))
 	seen := make(map[string]int)
-	for _, apexInfo := range apexVariations {
+	for _, apexInfo := range apexInfos {
 		apexName := apexInfo.ApexVariationName
 		mergedName := apexInfo.mergedName(ctx)
 		if index, exists := seen[mergedName]; exists {
+			// Variants having the same mergedName are deduped
 			merged[index].InApexes = append(merged[index].InApexes, apexName)
 			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
 			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
 		} else {
 			seen[mergedName] = len(merged)
-			apexInfo.ApexVariationName = apexInfo.mergedName(ctx)
+			apexInfo.ApexVariationName = mergedName
 			apexInfo.InApexes = CopyOf(apexInfo.InApexes)
 			apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
 			merged = append(merged, apexInfo)
@@ -356,75 +421,78 @@
 	return merged, aliases
 }
 
+// CreateApexVariations mutates a given module into multiple apex variants each of which is for an
+// apexBundle (and/or the platform) where the module is part of.
 func CreateApexVariations(mctx BottomUpMutatorContext, module ApexModule) []Module {
 	base := module.apexModuleBase()
-	if len(base.apexVariations) > 0 {
-		base.checkApexAvailableProperty(mctx)
 
-		var apexVariations []ApexInfo
-		var aliases [][2]string
-		if !mctx.Module().(ApexModule).UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
-			apexVariations, aliases = mergeApexVariations(mctx, base.apexVariations)
-		} else {
-			apexVariations = base.apexVariations
-		}
-		// base.apexVariations is only needed to propagate the list of apexes from
-		// apexDepsMutator to apexMutator.  It is no longer accurate after
-		// mergeApexVariations, and won't be copied to all but the first created
-		// variant.  Clear it so it doesn't accidentally get used later.
-		base.apexVariations = nil
-
-		sort.Sort(byApexName(apexVariations))
-		variations := []string{}
-		variations = append(variations, "") // Original variation for platform
-		for _, apex := range apexVariations {
-			variations = append(variations, apex.ApexVariationName)
-		}
-
-		defaultVariation := ""
-		mctx.SetDefaultDependencyVariation(&defaultVariation)
-
-		var inApex ApexMembership
-		for _, a := range apexVariations {
-			for _, apexContents := range a.ApexContents {
-				inApex = inApex.merge(apexContents.contents[mctx.ModuleName()])
-			}
-		}
-
-		base.ApexProperties.InAnyApex = true
-		base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
-
-		modules := mctx.CreateVariations(variations...)
-		for i, mod := range modules {
-			platformVariation := i == 0
-			if platformVariation && !mctx.Host() && !mod.(ApexModule).AvailableFor(AvailableToPlatform) {
-				// Do not install the module for platform, but still allow it to output
-				// uninstallable AndroidMk entries in certain cases when they have
-				// side effects.
-				mod.MakeUninstallable()
-			}
-			if !platformVariation {
-				mctx.SetVariationProvider(mod, ApexInfoProvider, apexVariations[i-1])
-			}
-		}
-
-		for _, alias := range aliases {
-			mctx.CreateAliasVariation(alias[0], alias[1])
-		}
-
-		return modules
+	// Shortcut
+	if len(base.apexInfos) == 0 {
+		return nil
 	}
-	return nil
+
+	// Do some validity checks.
+	// TODO(jiyong): is this the right place?
+	base.checkApexAvailableProperty(mctx)
+
+	var apexInfos []ApexInfo
+	var aliases [][2]string
+	if !mctx.Module().(ApexModule).UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
+		apexInfos, aliases = mergeApexVariations(mctx, base.apexInfos)
+	} else {
+		apexInfos = base.apexInfos
+	}
+	// base.apexInfos is only needed to propagate the list of apexes from apexDepsMutator to
+	// apexMutator. It is no longer accurate after mergeApexVariations, and won't be copied to
+	// all but the first created variant. Clear it so it doesn't accidentally get used later.
+	base.apexInfos = nil
+	sort.Sort(byApexName(apexInfos))
+
+	var inApex ApexMembership
+	for _, a := range apexInfos {
+		for _, apexContents := range a.ApexContents {
+			inApex = inApex.merge(apexContents.contents[mctx.ModuleName()])
+		}
+	}
+	base.ApexProperties.InAnyApex = true
+	base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
+
+	defaultVariation := ""
+	mctx.SetDefaultDependencyVariation(&defaultVariation)
+
+	variations := []string{defaultVariation}
+	for _, a := range apexInfos {
+		variations = append(variations, a.ApexVariationName)
+	}
+	modules := mctx.CreateVariations(variations...)
+	for i, mod := range modules {
+		platformVariation := i == 0
+		if platformVariation && !mctx.Host() && !mod.(ApexModule).AvailableFor(AvailableToPlatform) {
+			// Do not install the module for platform, but still allow it to output
+			// uninstallable AndroidMk entries in certain cases when they have side
+			// effects.  TODO(jiyong): move this routine to somewhere else
+			mod.MakeUninstallable()
+		}
+		if !platformVariation {
+			mctx.SetVariationProvider(mod, ApexInfoProvider, apexInfos[i-1])
+		}
+	}
+
+	for _, alias := range aliases {
+		mctx.CreateAliasVariation(alias[0], alias[1])
+	}
+
+	return modules
 }
 
-// UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies
-// that are in the same APEX have unique APEX variations so that the module can link against
-// the right variant.
+// UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies that are
+// in the same APEX have unique APEX variations so that the module can link against the right
+// variant.
 func UpdateUniqueApexVariationsForDeps(mctx BottomUpMutatorContext, am ApexModule) {
-	// anyInSameApex returns true if the two ApexInfo lists contain any values in an InApexes list
-	// in common.  It is used instead of DepIsInSameApex because it needs to determine if the dep
-	// is in the same APEX due to being directly included, not only if it is included _because_ it
-	// is a dependency.
+	// anyInSameApex returns true if the two ApexInfo lists contain any values in an InApexes
+	// list in common. It is used instead of DepIsInSameApex because it needs to determine if
+	// the dep is in the same APEX due to being directly included, not only if it is included
+	// _because_ it is a dependency.
 	anyInSameApex := func(a, b []ApexInfo) bool {
 		collectApexes := func(infos []ApexInfo) []string {
 			var ret []string
@@ -446,9 +514,10 @@
 		return false
 	}
 
+	// If any of the dependencies requires unique apex variations, so does this module.
 	mctx.VisitDirectDeps(func(dep Module) {
 		if depApexModule, ok := dep.(ApexModule); ok {
-			if anyInSameApex(depApexModule.apexModuleBase().apexVariations, am.apexModuleBase().apexVariations) &&
+			if anyInSameApex(depApexModule.apexModuleBase().apexInfos, am.apexModuleBase().apexInfos) &&
 				(depApexModule.UniqueApexVariations() ||
 					depApexModule.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps) {
 				am.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps = true
@@ -457,10 +526,10 @@
 	})
 }
 
-// UpdateDirectlyInAnyApex uses the final module to store if any variant of this
-// module is directly in any APEX, and then copies the final value to all the modules.
-// It also copies the DirectlyInAnyApex value to any direct dependencies with a
-// CopyDirectlyInAnyApexTag dependency tag.
+// UpdateDirectlyInAnyApex uses the final module to store if any variant of this module is directly
+// in any APEX, and then copies the final value to all the modules. It also copies the
+// DirectlyInAnyApex value to any direct dependencies with a CopyDirectlyInAnyApexTag dependency
+// tag.
 func UpdateDirectlyInAnyApex(mctx BottomUpMutatorContext, am ApexModule) {
 	base := am.apexModuleBase()
 	// Copy DirectlyInAnyApex and InAnyApex from any direct dependencies with a
@@ -475,14 +544,13 @@
 
 	if base.ApexProperties.DirectlyInAnyApex {
 		// Variants of a module are always visited sequentially in order, so it is safe to
-		// write to another variant of this module.
-		// For a BottomUpMutator the PrimaryModule() is visited first and FinalModule() is
-		// visited last.
+		// write to another variant of this module. For a BottomUpMutator the
+		// PrimaryModule() is visited first and FinalModule() is visited last.
 		mctx.FinalModule().(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex = true
 	}
 
-	// If this is the FinalModule (last visited module) copy AnyVariantDirectlyInAnyApex to
-	// all the other variants
+	// If this is the FinalModule (last visited module) copy
+	// AnyVariantDirectlyInAnyApex to all the other variants
 	if am == mctx.FinalModule().(ApexModule) {
 		mctx.VisitAllModuleVariants(func(variant Module) {
 			variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
@@ -491,6 +559,7 @@
 	}
 }
 
+// ApexMembership tells how a module became part of an APEX.
 type ApexMembership int
 
 const (
@@ -499,20 +568,21 @@
 	directlyInApex
 )
 
-// Each apexBundle has an apexContents, and modules in that apex have a provider containing the
-// apexContents of each apexBundle they are part of.
+// ApexContents gives an information about member modules of an apexBundle.  Each apexBundle has an
+// apexContents, and modules in that apex have a provider containing the apexContents of each
+// apexBundle they are part of.
 type ApexContents struct {
-	ApexName string
+	// map from a module name to its membership to this apexBUndle
 	contents map[string]ApexMembership
 }
 
-func NewApexContents(name string, contents map[string]ApexMembership) *ApexContents {
+func NewApexContents(contents map[string]ApexMembership) *ApexContents {
 	return &ApexContents{
-		ApexName: name,
 		contents: contents,
 	}
 }
 
+// Updates an existing membership by adding a new direct (or indirect) membership
 func (i ApexMembership) Add(direct bool) ApexMembership {
 	if direct || i == directlyInApex {
 		return directlyInApex
@@ -520,6 +590,10 @@
 	return indirectlyInApex
 }
 
+// Merges two membership into one. Merging is needed because a module can be a part of an apexBundle
+// in many different paths. For example, it could be dependend on by the apexBundle directly, but at
+// the same time, there might be an indirect dependency to the module. In that case, the more
+// specific dependency (the direct one) is chosen.
 func (i ApexMembership) merge(other ApexMembership) ApexMembership {
 	if other == directlyInApex || i == directlyInApex {
 		return directlyInApex
@@ -531,16 +605,19 @@
 	return notInApex
 }
 
-func (ac *ApexContents) DirectlyInApex(name string) bool {
-	return ac.contents[name] == directlyInApex
+// Tests whether a module named moduleName is directly included in the apexBundle where this
+// ApexContents is tagged.
+func (ac *ApexContents) DirectlyInApex(moduleName string) bool {
+	return ac.contents[moduleName] == directlyInApex
 }
 
-func (ac *ApexContents) InApex(name string) bool {
-	return ac.contents[name] != notInApex
+// Tests whether a module named moduleName is included in the apexBundle where this ApexContent is
+// tagged.
+func (ac *ApexContents) InApex(moduleName string) bool {
+	return ac.contents[moduleName] != notInApex
 }
 
-// Tests whether a module named moduleName is directly depended on by all APEXes
-// in an ApexInfo.
+// Tests whether a module named moduleName is directly depended on by all APEXes in an ApexInfo.
 func DirectlyInAllApexes(apexInfo ApexInfo, moduleName string) bool {
 	for _, contents := range apexInfo.ApexContents {
 		if !contents.DirectlyInApex(moduleName) {
@@ -550,12 +627,15 @@
 	return true
 }
 
-func InitApexModule(m ApexModule) {
-	base := m.apexModuleBase()
-	base.canHaveApexVariants = true
-
-	m.AddProperties(&base.ApexProperties)
-}
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//Below are routines for extra safety checks.
+//
+// BuildDepsInfoLists is to flatten the dependency graph for an apexBundle into a text file
+// (actually two in slightly different formats). The files are mostly for debugging, for example to
+// see why a certain module is included in an APEX via which dependency path.
+//
+// CheckMinSdkVersion is to make sure that all modules in an apexBundle satisfy the min_sdk_version
+// requirement of the apexBundle.
 
 // A dependency info for a single ApexModule, either direct or transitive.
 type ApexModuleDepInfo struct {
@@ -705,7 +785,8 @@
 	WalkPayloadDeps(ctx ModuleContext, do PayloadDepsCallback)
 }
 
-// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version accordingly
+// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version
+// accordingly
 func CheckMinSdkVersion(m UpdatableModule, ctx ModuleContext, minSdkVersion ApiLevel) {
 	// do not enforce min_sdk_version for host
 	if ctx.Host() {
@@ -725,8 +806,9 @@
 
 	m.WalkPayloadDeps(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
 		if externalDep {
-			// external deps are outside the payload boundary, which is "stable" interface.
-			// We don't have to check min_sdk_version for external dependencies.
+			// external deps are outside the payload boundary, which is "stable"
+			// interface. We don't have to check min_sdk_version for external
+			// dependencies.
 			return false
 		}
 		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
diff --git a/android/arch.go b/android/arch.go
index 16211f8..df407d4 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -27,28 +27,6 @@
 	"github.com/google/blueprint/proptools"
 )
 
-const COMMON_VARIANT = "common"
-
-var (
-	archTypeList []ArchType
-
-	Arm    = newArch("arm", "lib32")
-	Arm64  = newArch("arm64", "lib64")
-	X86    = newArch("x86", "lib32")
-	X86_64 = newArch("x86_64", "lib64")
-
-	Common = ArchType{
-		Name: COMMON_VARIANT,
-	}
-)
-
-var archTypeMap = map[string]ArchType{
-	"arm":    Arm,
-	"arm64":  Arm64,
-	"x86":    X86,
-	"x86_64": X86_64,
-}
-
 /*
 Example blueprints file containing all variant property groups, with comment listing what type
 of variants get properties in that group:
@@ -111,405 +89,26 @@
 }
 */
 
-var archVariants = map[ArchType][]string{
-	Arm: {
-		"armv7-a",
-		"armv7-a-neon",
-		"armv8-a",
-		"armv8-2a",
-		"cortex-a7",
-		"cortex-a8",
-		"cortex-a9",
-		"cortex-a15",
-		"cortex-a53",
-		"cortex-a53-a57",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"krait",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
-	},
-	Arm64: {
-		"armv8_a",
-		"armv8_2a",
-		"armv8-2a-dotprod",
-		"cortex-a53",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
-	},
-	X86: {
-		"amberlake",
-		"atom",
-		"broadwell",
-		"haswell",
-		"icelake",
-		"ivybridge",
-		"kabylake",
-		"sandybridge",
-		"silvermont",
-		"skylake",
-		"stoneyridge",
-		"tigerlake",
-		"whiskeylake",
-		"x86_64",
-	},
-	X86_64: {
-		"amberlake",
-		"broadwell",
-		"haswell",
-		"icelake",
-		"ivybridge",
-		"kabylake",
-		"sandybridge",
-		"silvermont",
-		"skylake",
-		"stoneyridge",
-		"tigerlake",
-		"whiskeylake",
-	},
-}
-
-var archFeatures = map[ArchType][]string{
-	Arm: {
-		"neon",
-	},
-	Arm64: {
-		"dotprod",
-	},
-	X86: {
-		"ssse3",
-		"sse4",
-		"sse4_1",
-		"sse4_2",
-		"aes_ni",
-		"avx",
-		"avx2",
-		"avx512",
-		"popcnt",
-		"movbe",
-	},
-	X86_64: {
-		"ssse3",
-		"sse4",
-		"sse4_1",
-		"sse4_2",
-		"aes_ni",
-		"avx",
-		"avx2",
-		"avx512",
-		"popcnt",
-	},
-}
-
-var archFeatureMap = map[ArchType]map[string][]string{
-	Arm: {
-		"armv7-a-neon": {
-			"neon",
-		},
-		"armv8-a": {
-			"neon",
-		},
-		"armv8-2a": {
-			"neon",
-		},
-	},
-	Arm64: {
-		"armv8-2a-dotprod": {
-			"dotprod",
-		},
-	},
-	X86: {
-		"amberlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"atom": {
-			"ssse3",
-			"movbe",
-		},
-		"broadwell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"haswell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-			"movbe",
-		},
-		"icelake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"ivybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"kabylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"sandybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-		"silvermont": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"popcnt",
-			"movbe",
-		},
-		"skylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"stoneyridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"popcnt",
-			"movbe",
-		},
-		"tigerlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"whiskeylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"x86_64": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-	},
-	X86_64: {
-		"amberlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"broadwell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"haswell": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"icelake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"ivybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"popcnt",
-		},
-		"kabylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"aes_ni",
-			"popcnt",
-		},
-		"sandybridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"popcnt",
-		},
-		"silvermont": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"popcnt",
-		},
-		"skylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"stoneyridge": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"aes_ni",
-			"avx",
-			"avx2",
-			"popcnt",
-		},
-		"tigerlake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-		"whiskeylake": {
-			"ssse3",
-			"sse4",
-			"sse4_1",
-			"sse4_2",
-			"avx",
-			"avx2",
-			"avx512",
-			"aes_ni",
-			"popcnt",
-		},
-	},
-}
-
-var defaultArchFeatureMap = map[OsType]map[ArchType][]string{}
-
-func RegisterDefaultArchVariantFeatures(os OsType, arch ArchType, features ...string) {
-	checkCalledFromInit()
-
-	for _, feature := range features {
-		if !InList(feature, archFeatures[arch]) {
-			panic(fmt.Errorf("Invalid feature %q for arch %q variant \"\"", feature, arch))
-		}
-	}
-
-	if defaultArchFeatureMap[os] == nil {
-		defaultArchFeatureMap[os] = make(map[ArchType][]string)
-	}
-	defaultArchFeatureMap[os][arch] = features
-}
-
 // An Arch indicates a single CPU architecture.
 type Arch struct {
-	ArchType     ArchType
-	ArchVariant  string
-	CpuVariant   string
-	Abi          []string
+	// The type of the architecture (arm, arm64, x86, or x86_64).
+	ArchType ArchType
+
+	// The variant of the architecture, for example "armv7-a" or "armv7-a-neon" for arm.
+	ArchVariant string
+
+	// The variant of the CPU, for example "cortex-a53" for arm64.
+	CpuVariant string
+
+	// The list of Android app ABIs supported by the CPU architecture, for example "arm64-v8a".
+	Abi []string
+
+	// The list of arch-specific features supported by the CPU architecture, for example "neon".
 	ArchFeatures []string
 }
 
+// String returns the Arch as a string.  The value is used as the name of the variant created
+// by archMutator.
 func (a Arch) String() string {
 	s := a.ArchType.String()
 	if a.ArchVariant != "" {
@@ -521,12 +120,42 @@
 	return s
 }
 
+// ArchType is used to define the 4 supported architecture types (arm, arm64, x86, x86_64), as
+// well as the "common" architecture used for modules that support multiple architectures, for
+// example Java modules.
 type ArchType struct {
-	Name     string
-	Field    string
+	// Name is the name of the architecture type, "arm", "arm64", "x86", or "x86_64".
+	Name string
+
+	// Field is the name of the field used in properties that refer to the architecture, e.g. "Arm64".
+	Field string
+
+	// Multilib is either "lib32" or "lib64" for 32-bit or 64-bit architectures.
 	Multilib string
 }
 
+// String returns the name of the ArchType.
+func (a ArchType) String() string {
+	return a.Name
+}
+
+const COMMON_VARIANT = "common"
+
+var (
+	archTypeList []ArchType
+
+	Arm    = newArch("arm", "lib32")
+	Arm64  = newArch("arm64", "lib64")
+	X86    = newArch("x86", "lib32")
+	X86_64 = newArch("x86_64", "lib64")
+
+	Common = ArchType{
+		Name: COMMON_VARIANT,
+	}
+)
+
+var archTypeMap = map[string]ArchType{}
+
 func newArch(name, multilib string) ArchType {
 	archType := ArchType{
 		Name:     name,
@@ -534,25 +163,25 @@
 		Multilib: multilib,
 	}
 	archTypeList = append(archTypeList, archType)
+	archTypeMap[name] = archType
 	return archType
 }
 
+// ArchTypeList returns the 4 supported ArchTypes for arm, arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
 }
 
-func (a ArchType) String() string {
-	return a.Name
-}
-
-var _ encoding.TextMarshaler = ArchType{}
-
+// MarshalText allows an ArchType to be serialized through any encoder that supports
+// encoding.TextMarshaler.
 func (a ArchType) MarshalText() ([]byte, error) {
 	return []byte(strconv.Quote(a.String())), nil
 }
 
-var _ encoding.TextUnmarshaler = &ArchType{}
+var _ encoding.TextMarshaler = ArchType{}
 
+// UnmarshalText allows an ArchType to be deserialized through any decoder that supports
+// encoding.TextUnmarshaler.
 func (a *ArchType) UnmarshalText(text []byte) error {
 	if u, ok := archTypeMap[string(text)]; ok {
 		*a = u
@@ -562,71 +191,22 @@
 	return fmt.Errorf("unknown ArchType %q", text)
 }
 
-var BuildOs = func() OsType {
-	switch runtime.GOOS {
-	case "linux":
-		return Linux
-	case "darwin":
-		return Darwin
-	default:
-		panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
-	}
-}()
+var _ encoding.TextUnmarshaler = &ArchType{}
 
-var BuildArch = func() ArchType {
-	switch runtime.GOARCH {
-	case "amd64":
-		return X86_64
-	default:
-		panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
-	}
-}()
-
-var (
-	OsTypeList      []OsType
-	commonTargetMap = make(map[string]Target)
-
-	NoOsType    OsType
-	Linux       = NewOsType("linux_glibc", Host, false)
-	Darwin      = NewOsType("darwin", Host, false)
-	LinuxBionic = NewOsType("linux_bionic", Host, false)
-	Windows     = NewOsType("windows", Host, true)
-	Android     = NewOsType("android", Device, false)
-	Fuchsia     = NewOsType("fuchsia", Device, false)
-
-	// A pseudo OSType for a common os variant, which is OSType agnostic and which
-	// has dependencies on all the OS variants.
-	CommonOS = NewOsType("common_os", Generic, false)
-
-	// CommonArch is the Arch for all modules that are os-specific but not arch specific,
-	// for example most Java modules.
-	CommonArch = Arch{ArchType: Common}
-
-	osArchTypeMap = map[OsType][]ArchType{
-		Linux:       []ArchType{X86, X86_64},
-		LinuxBionic: []ArchType{Arm64, X86_64},
-		Darwin:      []ArchType{X86_64},
-		Windows:     []ArchType{X86, X86_64},
-		Android:     []ArchType{Arm, Arm64, X86, X86_64},
-		Fuchsia:     []ArchType{Arm64, X86_64},
-	}
-)
-
-type OsType struct {
-	Name, Field string
-	Class       OsClass
-
-	DefaultDisabled bool
-}
-
+// OsClass is an enum that describes whether a variant of a module runs on the host, on the device,
+// or is generic.
 type OsClass int
 
 const (
+	// Generic is used for variants of modules that are not OS-specific.
 	Generic OsClass = iota
+	// Device is used for variants of modules that run on the device.
 	Device
+	// Host is used for variants of modules that run on the host.
 	Host
 )
 
+// String returns the OsClass as a string.
 func (class OsClass) String() string {
 	switch class {
 	case Generic:
@@ -640,22 +220,48 @@
 	}
 }
 
+// OsType describes an OS variant of a module.
+type OsType struct {
+	// Name is the name of the OS.  It is also used as the name of the property in Android.bp
+	// files.
+	Name string
+
+	// Field is the name of the OS converted to an exported field name, i.e. with the first
+	// character capitalized.
+	Field string
+
+	// Class is the OsClass of the OS.
+	Class OsClass
+
+	// DefaultDisabled is set when the module variants for the OS should not be created unless
+	// the module explicitly requests them.  This is used to limit Windows cross compilation to
+	// only modules that need it.
+	DefaultDisabled bool
+}
+
+// String returns the name of the OsType.
 func (os OsType) String() string {
 	return os.Name
 }
 
+// Bionic returns true if the OS uses the Bionic libc runtime, i.e. if the OS is Android or
+// is Linux with Bionic.
 func (os OsType) Bionic() bool {
 	return os == Android || os == LinuxBionic
 }
 
+// Linux returns true if the OS uses the Linux kernel, i.e. if the OS is Android or is Linux
+// with or without the Bionic libc runtime.
 func (os OsType) Linux() bool {
 	return os == Android || os == Linux || os == LinuxBionic
 }
 
-func NewOsType(name string, class OsClass, defDisabled bool) OsType {
+// newOsType constructs an OsType and adds it to the global lists.
+func newOsType(name string, class OsClass, defDisabled bool, archTypes ...ArchType) OsType {
+	checkCalledFromInit()
 	os := OsType{
 		Name:  name,
-		Field: strings.Title(name),
+		Field: proptools.FieldNameForProperty(name),
 		Class: class,
 
 		DefaultDisabled: defDisabled,
@@ -667,10 +273,12 @@
 	} else {
 		commonTargetMap[name] = Target{Os: os, Arch: CommonArch}
 	}
+	osArchTypeMap[os] = archTypes
 
 	return os
 }
 
+// osByName returns the OsType that has the given name, or NoOsType if none match.
 func osByName(name string) OsType {
 	for _, os := range OsTypeList {
 		if os.Name == name {
@@ -681,18 +289,78 @@
 	return NoOsType
 }
 
-type NativeBridgeSupport bool
+// BuildOs returns the OsType for the OS that the build is running on.
+var BuildOs = func() OsType {
+	switch runtime.GOOS {
+	case "linux":
+		return Linux
+	case "darwin":
+		return Darwin
+	default:
+		panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
+	}
+}()
 
-const (
-	NativeBridgeDisabled NativeBridgeSupport = false
-	NativeBridgeEnabled  NativeBridgeSupport = true
+// BuildArch returns the ArchType for the CPU that the build is running on.
+var BuildArch = func() ArchType {
+	switch runtime.GOARCH {
+	case "amd64":
+		return X86_64
+	default:
+		panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
+	}
+}()
+
+var (
+	// OsTypeList contains a list of all the supported OsTypes, including ones not supported
+	// by the current build host or the target device.
+	OsTypeList []OsType
+	// commonTargetMap maps names of OsTypes to the corresponding common Target, i.e. the
+	// Target with the same OsType and the common ArchType.
+	commonTargetMap = make(map[string]Target)
+	// osArchTypeMap maps OsTypes to the list of supported ArchTypes for that OS.
+	osArchTypeMap = map[OsType][]ArchType{}
+
+	// NoOsType is a placeholder for when no OS is needed.
+	NoOsType OsType
+	// Linux is the OS for the Linux kernel plus the glibc runtime.
+	Linux = newOsType("linux_glibc", Host, false, X86, X86_64)
+	// Darwin is the OS for MacOS/Darwin host machines.
+	Darwin = newOsType("darwin", Host, false, X86_64)
+	// LinuxBionic is the OS for the Linux kernel plus the Bionic libc runtime, but without the
+	// rest of Android.
+	LinuxBionic = newOsType("linux_bionic", Host, false, Arm64, X86_64)
+	// Windows the OS for Windows host machines.
+	Windows = newOsType("windows", Host, true, X86, X86_64)
+	// Android is the OS for target devices that run all of Android, including the Linux kernel
+	// and the Bionic libc runtime.
+	Android = newOsType("android", Device, false, Arm, Arm64, X86, X86_64)
+	// Fuchsia is the OS for target devices that run Fuchsia.
+	Fuchsia = newOsType("fuchsia", Device, false, Arm64, X86_64)
+
+	// CommonOS is a pseudo OSType for a common OS variant, which is OsType agnostic and which
+	// has dependencies on all the OS variants.
+	CommonOS = newOsType("common_os", Generic, false)
+
+	// CommonArch is the Arch for all modules that are os-specific but not arch specific,
+	// for example most Java modules.
+	CommonArch = Arch{ArchType: Common}
 )
 
+// Target specifies the OS and architecture that a module is being compiled for.
 type Target struct {
-	Os                       OsType
-	Arch                     Arch
-	NativeBridge             NativeBridgeSupport
+	// Os the OS that the module is being compiled for (e.g. "linux_glibc", "android").
+	Os OsType
+	// Arch is the architecture that the module is being compiled for.
+	Arch Arch
+	// NativeBridge is NativeBridgeEnabled if the architecture is supported using NativeBridge
+	// (i.e. arm on x86) for this device.
+	NativeBridge NativeBridgeSupport
+	// NativeBridgeHostArchName is the name of the real architecture that is used to implement
+	// the NativeBridge architecture.  For example, for arm on x86 this would be "x86".
 	NativeBridgeHostArchName string
+	// NativeBridgeRelativePath is the name of the subdirectory that will contain NativeBridge
+	// libraries and binaries.
 	NativeBridgeRelativePath string
 
 	// HostCross is true when the target cannot run natively on the current build host.
@@ -701,14 +369,25 @@
 	HostCross bool
 }
 
+// NativeBridgeSupport is an enum that specifies if a Target supports NativeBridge.
+type NativeBridgeSupport bool
+
+const (
+	NativeBridgeDisabled NativeBridgeSupport = false
+	NativeBridgeEnabled  NativeBridgeSupport = true
+)
+
+// String returns the OS and arch variations used for the Target.
 func (target Target) String() string {
 	return target.OsVariation() + "_" + target.ArchVariation()
 }
 
+// OsVariation returns the name of the variation used by the osMutator for the Target.
 func (target Target) OsVariation() string {
 	return target.Os.String()
 }
 
+// ArchVariation returns the name of the variation used by the archMutator for the Target.
 func (target Target) ArchVariation() string {
 	var variation string
 	if target.NativeBridge {
@@ -719,6 +398,8 @@
 	return variation
 }
 
+// Variations returns a list of blueprint.Variations for the osMutator and archMutator for the
+// Target.
 func (target Target) Variations() []blueprint.Variation {
 	return []blueprint.Variation{
 		{Mutator: "os", Variation: target.OsVariation()},
@@ -726,10 +407,16 @@
 	}
 }
 
+// osMutator splits an arch-specific module into a variant for each OS that is enabled for the
+// module.  It uses the HostOrDevice value passed to InitAndroidArchModule and the
+// device_supported and host_supported properties to determine which OsTypes are enabled for this
+// module, then searches through the Targets to determine which have enabled Targets for this
+// module.
 func osMutator(bpctx blueprint.BottomUpMutatorContext) {
 	var module Module
 	var ok bool
 	if module, ok = bpctx.Module().(Module); !ok {
+		// The module is not a Soong module, it is a Blueprint module.
 		if bootstrap.IsBootstrapModule(bpctx.Module()) {
 			// Bootstrap Go modules are always the build OS or linux bionic.
 			config := bpctx.Config().(Config)
@@ -753,35 +440,38 @@
 
 	base := module.base()
 
+	// Nothing to do for modules that are not architecture specific (e.g. a genrule).
 	if !base.ArchSpecific() {
 		return
 	}
 
+	// Collect a list of OSTypes supported by this module based on the HostOrDevice value
+	// passed to InitAndroidArchModule and the device_supported and host_supported properties.
 	var moduleOSList []OsType
-
 	for _, os := range OsTypeList {
 		for _, t := range mctx.Config().Targets[os] {
-			if base.supportsTarget(t, mctx.Config()) {
+			if base.supportsTarget(t) {
 				moduleOSList = append(moduleOSList, os)
 				break
 			}
 		}
 	}
 
+	// If there are no supported OSes then disable the module.
 	if len(moduleOSList) == 0 {
 		base.Disable()
 		return
 	}
 
+	// Convert the list of supported OsTypes to the variation names.
 	osNames := make([]string, len(moduleOSList))
-
 	for i, os := range moduleOSList {
 		osNames[i] = os.String()
 	}
 
 	createCommonOSVariant := base.commonProperties.CreateCommonOSVariant
 	if createCommonOSVariant {
-		// A CommonOS variant was requested so add it to the list of OS's variants to
+		// A CommonOS variant was requested so add it to the list of OS variants to
 		// create. It needs to be added to the end because it needs to depend on the
 		// the other variants in the list returned by CreateVariations(...) and inter
 		// variant dependencies can only be created from a later variant in that list to
@@ -791,6 +481,8 @@
 		moduleOSList = append(moduleOSList, CommonOS)
 	}
 
+	// Create the variations, annotate each one with which OS it was created for, and
+	// squash the appropriate OS-specific properties into the top level properties.
 	modules := mctx.CreateVariations(osNames...)
 	for i, m := range modules {
 		m.base().commonProperties.CompileOS = moduleOSList[i]
@@ -842,7 +534,7 @@
 }
 
 // archMutator splits a module into a variant for each Target requested by the module.  Target selection
-// for a module is in three levels, OsClass, mulitlib, and then Target.
+// for a module is in three levels, OsClass, multilib, and then Target.
 // OsClass selection is determined by:
 //    - The HostOrDeviceSupported value passed in to InitAndroidArchModule by the module type factory, which selects
 //      whether the module type can compile for host, device or both.
@@ -859,7 +551,11 @@
 //        but may be arm for a 32-bit only build.
 //    "32": compile for only a single 32-bit Target supported by the OsClass.
 //    "64": compile for only a single 64-bit Target supported by the OsClass.
-//    "common": compile a for a single Target that will work on all Targets suported by the OsClass (for example Java).
+//    "common": compile a for a single Target that will work on all Targets supported by the OsClass (for example Java).
+//    "common_first": compile a for a Target that will work on all Targets supported by the OsClass
+//        (same as "common"), plus a second Target for the preferred Target supported by the OsClass
+//        (same as "first").  This is used for java_binary that produces a common .jar and a wrapper
+//        executable script.
 //
 // Once the list of Targets is determined, the module is split into a variant for each Target.
 //
@@ -900,8 +596,8 @@
 
 	osTargets := mctx.Config().Targets[os]
 	image := base.commonProperties.ImageVariation
-	// Filter NativeBridge targets unless they are explicitly supported
-	// Skip creating native bridge variants for vendor modules
+	// Filter NativeBridge targets unless they are explicitly supported.
+	// Skip creating native bridge variants for non-core modules.
 	if os == Android &&
 		!(Bool(base.commonProperties.Native_bridge_supported) && image == CoreVariation) {
 
@@ -920,17 +616,24 @@
 		osTargets = []Target{osTargets[0]}
 	}
 
+	// Some modules want compile_multilib: "first" to mean 32-bit, not 64-bit.
+	// This is used for Windows support and for HOST_PREFER_32_BIT=true support for Art modules.
 	prefer32 := false
 	if base.prefer32 != nil {
 		prefer32 = base.prefer32(mctx, base, os)
 	}
 
+	// Determine the multilib selection for this module.
 	multilib, extraMultilib := decodeMultilib(base, os.Class)
+
+	// Convert the multilib selection into a list of Targets.
 	targets, err := decodeMultilibTargets(multilib, osTargets, prefer32)
 	if err != nil {
 		mctx.ModuleErrorf("%s", err.Error())
 	}
 
+	// If the module is using extraMultilib, decode the extraMultilib selection into
+	// a separate list of Targets.
 	var multiTargets []Target
 	if extraMultilib != "" {
 		multiTargets, err = decodeMultilibTargets(extraMultilib, osTargets, prefer32)
@@ -939,23 +642,27 @@
 		}
 	}
 
+	// Recovery is always the primary architecture, filter out any other architectures.
 	if image == RecoveryVariation {
 		primaryArch := mctx.Config().DevicePrimaryArchType()
 		targets = filterToArch(targets, primaryArch)
 		multiTargets = filterToArch(multiTargets, primaryArch)
 	}
 
+	// If there are no supported targets disable the module.
 	if len(targets) == 0 {
 		base.Disable()
 		return
 	}
 
+	// Convert the targets into a list of arch variation names.
 	targetNames := make([]string, len(targets))
-
 	for i, target := range targets {
 		targetNames[i] = target.ArchVariation()
 	}
 
+	// Create the variations, annotate each one with which Target it was created for, and
+	// squash the appropriate arch-specific properties into the top level properties.
 	modules := mctx.CreateVariations(targetNames...)
 	for i, m := range modules {
 		addTargetProperties(m, targets[i], multiTargets, i == 0)
@@ -963,22 +670,34 @@
 	}
 }
 
+// addTargetProperties annotates a variant with the Target is is being compiled for, the list
+// of additional Targets it is supporting (if any), and whether it is the primary Target for
+// the module.
 func addTargetProperties(m Module, target Target, multiTargets []Target, primaryTarget bool) {
 	m.base().commonProperties.CompileTarget = target
 	m.base().commonProperties.CompileMultiTargets = multiTargets
 	m.base().commonProperties.CompilePrimary = primaryTarget
 }
 
+// decodeMultilib returns the appropriate compile_multilib property for the module, or the default
+// multilib from the factory's call to InitAndroidArchModule if none was set.  For modules that
+// called InitAndroidMultiTargetsArchModule it always returns "common" for multilib, and returns
+// the actual multilib in extraMultilib.
 func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib string) {
+	// First check the "android.compile_multilib" or "host.compile_multilib" properties.
 	switch class {
 	case Device:
 		multilib = String(base.commonProperties.Target.Android.Compile_multilib)
 	case Host:
 		multilib = String(base.commonProperties.Target.Host.Compile_multilib)
 	}
+
+	// If those aren't set, try the "compile_multilib" property.
 	if multilib == "" {
 		multilib = String(base.commonProperties.Compile_multilib)
 	}
+
+	// If that wasn't set, use the default multilib set by the factory.
 	if multilib == "" {
 		multilib = base.commonProperties.Default_multilib
 	}
@@ -995,6 +714,8 @@
 	}
 }
 
+// filterToArch takes a list of Targets and an ArchType, and returns a modified list that contains
+// only Targets that have the specified ArchType.
 func filterToArch(targets []Target, arch ArchType) []Target {
 	for i := 0; i < len(targets); i++ {
 		if targets[i].Arch.ArchType != arch {
@@ -1005,17 +726,27 @@
 	return targets
 }
 
-type archPropTypeDesc struct {
-	arch, multilib, target reflect.Type
-}
-
+// archPropRoot is a struct type used as the top level of the arch-specific properties.  It
+// contains the "arch", "multilib", and "target" property structs.  It is used to split up the
+// property structs to limit how much is allocated when a single arch-specific property group is
+// used.  The types are interface{} because they will hold instances of runtime-created types.
 type archPropRoot struct {
 	Arch, Multilib, Target interface{}
 }
 
+// archPropTypeDesc holds the runtime-created types for the property structs to instantiate to
+// create an archPropRoot property struct.
+type archPropTypeDesc struct {
+	arch, multilib, target reflect.Type
+}
+
 // createArchPropTypeDesc takes a reflect.Type that is either a struct or a pointer to a struct, and
 // returns lists of reflect.Types that contains the arch-variant properties inside structs for each
 // arch, multilib and target property.
+//
+// This is a relatively expensive operation, so the results are cached in the global
+// archPropTypeMap.  It is constructed entirely based on compile-time data, so there is no need
+// to isolate the results between multiple tests running in parallel.
 func createArchPropTypeDesc(props reflect.Type) []archPropTypeDesc {
 	// Each property struct shard will be nested many times under the runtime generated arch struct,
 	// which can hit the limit of 64kB for the name of runtime generated structs.  They are nested
@@ -1024,7 +755,12 @@
 	// could cause problems if a single deeply nested property no longer fits in the name.
 	const maxArchTypeNameSize = 500
 
+	// Convert the type to a new set of types that contains only the arch-specific properties
+	// (those that are tagged with `android:"arch_specific"`), and sharded into multiple types
+	// to keep the runtime-generated names under the limit.
 	propShards, _ := proptools.FilterPropertyStructSharded(props, maxArchTypeNameSize, filterArchStruct)
+
+	// If the type has no arch-specific properties there is nothing to do.
 	if len(propShards) == 0 {
 		return nil
 	}
@@ -1032,6 +768,8 @@
 	var ret []archPropTypeDesc
 	for _, props := range propShards {
 
+		// variantFields takes a list of variant property field names and returns a list the
+		// StructFields with the names and the type of the current shard.
 		variantFields := func(names []string) []reflect.StructField {
 			ret := make([]reflect.StructField, len(names))
 
@@ -1043,9 +781,11 @@
 			return ret
 		}
 
+		// Create a type that contains the properties in this shard repeated for each
+		// architecture, architecture variant, and architecture feature.
 		archFields := make([]reflect.StructField, len(archTypeList))
 		for i, arch := range archTypeList {
-			variants := []string{}
+			var variants []string
 
 			for _, archVariant := range archVariants[arch] {
 				archVariant := variantReplacer.Replace(archVariant)
@@ -1056,8 +796,13 @@
 				variants = append(variants, proptools.FieldNameForProperty(feature))
 			}
 
+			// Create the StructFields for each architecture variant architecture feature
+			// (e.g. "arch.arm.cortex-a53" or "arch.arm.neon").
 			fields := variantFields(variants)
 
+			// Create the StructField for the architecture itself (e.g. "arch.arm").  The special
+			// "BlueprintEmbed" name is used by Blueprint to put the properties in the
+			// parent struct.
 			fields = append([]reflect.StructField{{
 				Name:      "BlueprintEmbed",
 				Type:      props,
@@ -1069,10 +814,15 @@
 				Type: reflect.StructOf(fields),
 			}
 		}
+
+		// Create the type of the "arch" property struct for this shard.
 		archType := reflect.StructOf(archFields)
 
+		// Create the type for the "multilib" property struct for this shard, containing the
+		// "multilib.lib32" and "multilib.lib64" property structs.
 		multilibType := reflect.StructOf(variantFields([]string{"Lib32", "Lib64"}))
 
+		// Start with a list of the special targets
 		targets := []string{
 			"Host",
 			"Android64",
@@ -1085,11 +835,14 @@
 			"Native_bridge",
 		}
 		for _, os := range OsTypeList {
+			// Add all the OSes.
 			targets = append(targets, os.Field)
 
+			// Add the OS/Arch combinations, e.g. "android_arm64".
 			for _, archType := range osArchTypeMap[os] {
 				targets = append(targets, os.Field+"_"+archType.Name)
 
+				// Also add the special "linux_<arch>" and "bionic_<arch>" property structs.
 				if os.Linux() {
 					target := "Linux_" + archType.Name
 					if !InList(target, targets) {
@@ -1105,8 +858,10 @@
 			}
 		}
 
+		// Create the type for the "target" property struct for this shard.
 		targetType := reflect.StructOf(variantFields(targets))
 
+		// Return a descriptor of the 3 runtime-created types.
 		ret = append(ret, archPropTypeDesc{
 			arch:     reflect.PtrTo(archType),
 			multilib: reflect.PtrTo(multilibType),
@@ -1116,6 +871,11 @@
 	return ret
 }
 
+// variantReplacer converts architecture variant or architecture feature names into names that
+// are valid for an Android.bp file.
+var variantReplacer = strings.NewReplacer("-", "_", ".", "_")
+
+// filterArchStruct returns true if the given field is an architecture specific property.
 func filterArchStruct(field reflect.StructField, prefix string) (bool, reflect.StructField) {
 	if proptools.HasTag(field, "android", "arch_variant") {
 		// The arch_variant field isn't necessary past this point
@@ -1142,12 +902,17 @@
 	return false, field
 }
 
+// archPropTypeMap contains a cache of the results of createArchPropTypeDesc for each type.  It is
+// shared across all Contexts, but is constructed based only on compile-time information so there
+// is no risk of contaminating one Context with data from another.
 var archPropTypeMap OncePer
 
-func InitArchModule(m Module) {
+// initArchModule adds the architecture-specific property structs to a Module.
+func initArchModule(m Module) {
 
 	base := m.base()
 
+	// Store the original list of top level property structs
 	base.generalProperties = m.GetProperties()
 
 	for _, properties := range base.generalProperties {
@@ -1164,10 +929,13 @@
 				propertiesValue.Interface()))
 		}
 
+		// Get or create the arch-specific property struct types for this property struct type.
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
 			return createArchPropTypeDesc(t)
 		}).([]archPropTypeDesc)
 
+		// Instantiate one of each arch-specific property struct type and add it to the
+		// properties for the Module.
 		var archProperties []interface{}
 		for _, t := range archPropTypes {
 			archProperties = append(archProperties, &archPropRoot{
@@ -1180,14 +948,18 @@
 		m.AddProperties(archProperties...)
 	}
 
+	// Update the list of properties that can be set by a defaults module or a call to
+	// AppendMatchingProperties or PrependMatchingProperties.
 	base.customizableProperties = m.GetProperties()
 }
 
-var variantReplacer = strings.NewReplacer("-", "_", ".", "_")
-
+// appendProperties squashes properties from the given field of the given src property struct
+// into the dst property struct.  Returns the reflect.Value of the field in the src property
+// struct to be used for further appendProperties calls on fields of that property struct.
 func (m *ModuleBase) appendProperties(ctx BottomUpMutatorContext,
 	dst interface{}, src reflect.Value, field, srcPrefix string) reflect.Value {
 
+	// Step into non-nil pointers to structs in the src value.
 	if src.Kind() == reflect.Ptr {
 		if src.IsNil() {
 			return src
@@ -1195,18 +967,25 @@
 		src = src.Elem()
 	}
 
+	// Find the requested field in the src struct.
 	src = src.FieldByName(field)
 	if !src.IsValid() {
 		ctx.ModuleErrorf("field %q does not exist", srcPrefix)
 		return src
 	}
 
+	// Save the value of the field in the src struct to return.
 	ret := src
 
+	// If the value of the field is a struct (as opposed to a pointer to a struct) then step
+	// into the BlueprintEmbed field.
 	if src.Kind() == reflect.Struct {
 		src = src.FieldByName("BlueprintEmbed")
 	}
 
+	// order checks the `android:"variant_prepend"` tag to handle properties where the
+	// arch-specific value needs to come before the generic value, for example for lists of
+	// include directories.
 	order := func(property string,
 		dstField, srcField reflect.StructField,
 		dstValue, srcValue interface{}) (proptools.Order, error) {
@@ -1217,6 +996,7 @@
 		}
 	}
 
+	// Squash the located property struct into the destination property struct.
 	err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, order)
 	if err != nil {
 		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
@@ -1229,7 +1009,8 @@
 	return ret
 }
 
-// Rewrite the module's properties structs to contain os-specific values.
+// Squash the appropriate OS-specific property structs into the matching top level property structs
+// based on the CompileOS value that was annotated on the variant.
 func (m *ModuleBase) setOSProperties(ctx BottomUpMutatorContext) {
 	os := m.commonProperties.CompileOS
 
@@ -1323,7 +1104,8 @@
 	}
 }
 
-// Rewrite the module's properties structs to contain arch-specific values.
+// Squash the appropriate arch-specific property structs into the matching top level property
+// structs based on the CompileTarget value that was annotated on the variant.
 func (m *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) {
 	arch := m.Arch()
 	os := m.Os()
@@ -1475,22 +1257,7 @@
 	}
 }
 
-func forEachInterface(v reflect.Value, f func(reflect.Value)) {
-	switch v.Kind() {
-	case reflect.Interface:
-		f(v)
-	case reflect.Struct:
-		for i := 0; i < v.NumField(); i++ {
-			forEachInterface(v.Field(i), f)
-		}
-	case reflect.Ptr:
-		forEachInterface(v.Elem(), f)
-	default:
-		panic(fmt.Errorf("Unsupported kind %s", v.Kind()))
-	}
-}
-
-// Convert the arch product variables into a list of targets for each os class structs
+// Convert the arch product variables into a list of targets for each OsType.
 func decodeTargetProductVariables(config *config) (map[OsType][]Target, error) {
 	variables := config.productVariables
 
@@ -1562,19 +1329,26 @@
 		return nil, fmt.Errorf("No host primary architecture set")
 	}
 
+	// The primary host target, which must always exist.
 	addTarget(BuildOs, *variables.HostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 
+	// An optional secondary host target.
 	if variables.HostSecondaryArch != nil && *variables.HostSecondaryArch != "" {
 		addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 	}
 
+	// 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)
 		if crossHostOs == NoOsType {
@@ -1585,28 +1359,34 @@
 			return nil, fmt.Errorf("No cross-host primary architecture set")
 		}
 
+		// The primary cross-compiled host target.
 		addTarget(crossHostOs, *variables.CrossHostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 
+		// An optional secondary cross-compiled host target.
 		if variables.CrossHostSecondaryArch != nil && *variables.CrossHostSecondaryArch != "" {
 			addTarget(crossHostOs, *variables.CrossHostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
 		}
 	}
 
+	// Optional device targets
 	if variables.DeviceArch != nil && *variables.DeviceArch != "" {
 		var target = Android
 		if Bool(variables.Fuchsia) {
 			target = Fuchsia
 		}
 
+		// The primary device target.
 		addTarget(target, *variables.DeviceArch, variables.DeviceArchVariant,
 			variables.DeviceCpuVariant, variables.DeviceAbi, NativeBridgeDisabled, nil, nil)
 
+		// An optional secondary device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" {
 			addTarget(Android, *variables.DeviceSecondaryArch,
 				variables.DeviceSecondaryArchVariant, variables.DeviceSecondaryCpuVariant,
 				variables.DeviceSecondaryAbi, NativeBridgeDisabled, nil, nil)
 		}
 
+		// An optional NativeBridge device target.
 		if variables.NativeBridgeArch != nil && *variables.NativeBridgeArch != "" {
 			addTarget(Android, *variables.NativeBridgeArch,
 				variables.NativeBridgeArchVariant, variables.NativeBridgeCpuVariant,
@@ -1614,6 +1394,7 @@
 				variables.NativeBridgeRelativePath)
 		}
 
+		// An optional secondary NativeBridge device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" &&
 			variables.NativeBridgeSecondaryArch != nil && *variables.NativeBridgeSecondaryArch != "" {
 			addTarget(Android, *variables.NativeBridgeSecondaryArch,
@@ -1648,6 +1429,7 @@
 	return false
 }
 
+// archConfig describes a built-in configuration.
 type archConfig struct {
 	arch        string
 	archVariant string
@@ -1655,6 +1437,7 @@
 	abi         []string
 }
 
+// getMegaDeviceConfig returns a list of archConfigs for every architecture simultaneously.
 func getMegaDeviceConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a", "generic", []string{"armeabi-v7a"}},
@@ -1701,6 +1484,7 @@
 	}
 }
 
+// getNdkAbisConfig returns a list of archConfigs for the ABIs supported by the NDK.
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
@@ -1710,6 +1494,7 @@
 	}
 }
 
+// getAmlAbisConfig returns a list of archConfigs for the ABIs supported by mainline modules.
 func getAmlAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a-neon", "", []string{"armeabi-v7a"}},
@@ -1719,6 +1504,7 @@
 	}
 }
 
+// decodeArchSettings converts a list of archConfigs into a list of Targets for the given OsType.
 func decodeArchSettings(os OsType, archConfigs []archConfig) ([]Target, error) {
 	var ret []Target
 
@@ -1738,15 +1524,9 @@
 	return ret, nil
 }
 
-// Convert a set of strings from product variables into a single Arch struct
+// decodeArch converts a set of strings from product variables into an Arch struct.
 func decodeArch(os OsType, arch string, archVariant, cpuVariant *string, abi []string) (Arch, error) {
-	stringPtr := func(p *string) string {
-		if p != nil {
-			return *p
-		}
-		return ""
-	}
-
+	// Verify the arch is valid
 	archType, ok := archTypeMap[arch]
 	if !ok {
 		return Arch{}, fmt.Errorf("unknown arch %q", arch)
@@ -1754,19 +1534,22 @@
 
 	a := Arch{
 		ArchType:    archType,
-		ArchVariant: stringPtr(archVariant),
-		CpuVariant:  stringPtr(cpuVariant),
+		ArchVariant: String(archVariant),
+		CpuVariant:  String(cpuVariant),
 		Abi:         abi,
 	}
 
+	// Convert generic arch variants into the empty string.
 	if a.ArchVariant == a.ArchType.Name || a.ArchVariant == "generic" {
 		a.ArchVariant = ""
 	}
 
+	// Convert generic CPU variants into the empty string.
 	if a.CpuVariant == a.ArchType.Name || a.CpuVariant == "generic" {
 		a.CpuVariant = ""
 	}
 
+	// Filter empty ABIs out of the list.
 	for i := 0; i < len(a.Abi); i++ {
 		if a.Abi[i] == "" {
 			a.Abi = append(a.Abi[:i], a.Abi[i+1:]...)
@@ -1775,10 +1558,12 @@
 	}
 
 	if a.ArchVariant == "" {
+		// Set ArchFeatures from the default arch features.
 		if featureMap, ok := defaultArchFeatureMap[os]; ok {
 			a.ArchFeatures = featureMap[archType]
 		}
 	} else {
+		// Set ArchFeatures from the arch type.
 		if featureMap, ok := archFeatureMap[archType]; ok {
 			a.ArchFeatures = featureMap[a.ArchVariant]
 		}
@@ -1787,6 +1572,8 @@
 	return a, nil
 }
 
+// filterMultilibTargets takes a list of Targets and a multilib value and returns a new list of
+// Targets containing only those that have the given multilib value.
 func filterMultilibTargets(targets []Target, multilib string) []Target {
 	var ret []Target
 	for _, t := range targets {
@@ -1797,8 +1584,8 @@
 	return ret
 }
 
-// Return the set of Os specific common architecture targets for each Os in a list of
-// targets.
+// getCommonTargets returns the set of Os specific common architecture targets for each Os in a list
+// of targets.
 func getCommonTargets(targets []Target) []Target {
 	var ret []Target
 	set := make(map[string]bool)
@@ -1813,6 +1600,9 @@
 	return ret
 }
 
+// firstTarget takes a list of Targets and a list of multilib values and returns a list of Targets
+// that contains zero or one Target for each OsType, selecting the one that matches the earliest
+// filter.
 func firstTarget(targets []Target, filters ...string) []Target {
 	// find the first target from each OS
 	var ret []Target
@@ -1832,9 +1622,10 @@
 	return ret
 }
 
-// Use the module multilib setting to select one or more targets from a target list
+// decodeMultilibTargets uses the module's multilib setting to select one or more targets from a
+// list of Targets.
 func decodeMultilibTargets(multilib string, targets []Target, prefer32 bool) ([]Target, error) {
-	buildTargets := []Target{}
+	var buildTargets []Target
 
 	switch multilib {
 	case "common":
diff --git a/android/arch_list.go b/android/arch_list.go
new file mode 100644
index 0000000..0c33b9d
--- /dev/null
+++ b/android/arch_list.go
@@ -0,0 +1,410 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import "fmt"
+
+var archVariants = map[ArchType][]string{
+	Arm: {
+		"armv7-a",
+		"armv7-a-neon",
+		"armv8-a",
+		"armv8-2a",
+		"cortex-a7",
+		"cortex-a8",
+		"cortex-a9",
+		"cortex-a15",
+		"cortex-a53",
+		"cortex-a53-a57",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"krait",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	Arm64: {
+		"armv8_a",
+		"armv8_2a",
+		"armv8-2a-dotprod",
+		"cortex-a53",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	X86: {
+		"amberlake",
+		"atom",
+		"broadwell",
+		"haswell",
+		"icelake",
+		"ivybridge",
+		"kabylake",
+		"sandybridge",
+		"silvermont",
+		"skylake",
+		"stoneyridge",
+		"tigerlake",
+		"whiskeylake",
+		"x86_64",
+	},
+	X86_64: {
+		"amberlake",
+		"broadwell",
+		"haswell",
+		"icelake",
+		"ivybridge",
+		"kabylake",
+		"sandybridge",
+		"silvermont",
+		"skylake",
+		"stoneyridge",
+		"tigerlake",
+		"whiskeylake",
+	},
+}
+
+var archFeatures = map[ArchType][]string{
+	Arm: {
+		"neon",
+	},
+	Arm64: {
+		"dotprod",
+	},
+	X86: {
+		"ssse3",
+		"sse4",
+		"sse4_1",
+		"sse4_2",
+		"aes_ni",
+		"avx",
+		"avx2",
+		"avx512",
+		"popcnt",
+		"movbe",
+	},
+	X86_64: {
+		"ssse3",
+		"sse4",
+		"sse4_1",
+		"sse4_2",
+		"aes_ni",
+		"avx",
+		"avx2",
+		"avx512",
+		"popcnt",
+	},
+}
+
+var archFeatureMap = map[ArchType]map[string][]string{
+	Arm: {
+		"armv7-a-neon": {
+			"neon",
+		},
+		"armv8-a": {
+			"neon",
+		},
+		"armv8-2a": {
+			"neon",
+		},
+	},
+	Arm64: {
+		"armv8-2a-dotprod": {
+			"dotprod",
+		},
+	},
+	X86: {
+		"amberlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"atom": {
+			"ssse3",
+			"movbe",
+		},
+		"broadwell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"haswell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+			"movbe",
+		},
+		"icelake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"ivybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"kabylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"sandybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+		"silvermont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+			"movbe",
+		},
+		"skylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"stoneyridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"avx2",
+			"popcnt",
+			"movbe",
+		},
+		"tigerlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"whiskeylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"x86_64": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+	},
+	X86_64: {
+		"amberlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"broadwell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"haswell": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"icelake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"ivybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"popcnt",
+		},
+		"kabylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"aes_ni",
+			"popcnt",
+		},
+		"sandybridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
+		"silvermont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+		},
+		"skylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"stoneyridge": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"avx",
+			"avx2",
+			"popcnt",
+		},
+		"tigerlake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+		"whiskeylake": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"avx",
+			"avx2",
+			"avx512",
+			"aes_ni",
+			"popcnt",
+		},
+	},
+}
+
+var defaultArchFeatureMap = map[OsType]map[ArchType][]string{}
+
+// RegisterDefaultArchVariantFeatures is called by files that define Toolchains to specify the
+// arch features that are available for the default arch variant.  It must be called from an
+// init() function.
+func RegisterDefaultArchVariantFeatures(os OsType, arch ArchType, features ...string) {
+	checkCalledFromInit()
+
+	for _, feature := range features {
+		if !InList(feature, archFeatures[arch]) {
+			panic(fmt.Errorf("Invalid feature %q for arch %q variant \"\"", feature, arch))
+		}
+	}
+
+	if defaultArchFeatureMap[os] == nil {
+		defaultArchFeatureMap[os] = make(map[ArchType][]string)
+	}
+	defaultArchFeatureMap[os][arch] = features
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index c87a945..7d8d12f 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -192,9 +192,9 @@
 func (context *bazelContext) issueBazelCommand(command string, labels []string,
 	extraFlags ...string) (string, error) {
 
-	cmdFlags := []string{"--bazelrc=build/bazel/common.bazelrc",
-		"--output_base=" + context.outputBase, command}
+	cmdFlags := []string{"--output_base=" + context.outputBase, command}
 	cmdFlags = append(cmdFlags, labels...)
+	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+context.intermediatesDir())
 	cmdFlags = append(cmdFlags, extraFlags...)
 
 	bazelCmd := exec.Command(context.bazelPath, cmdFlags...)
@@ -211,6 +211,21 @@
 	}
 }
 
+// Returns the string contents of a workspace file that should be output
+// adjacent to the main bzl file and build file.
+// This workspace file allows, via local_repository rule, sourcetree-level
+// BUILD targets to be referenced via @sourceroot.
+func (context *bazelContext) workspaceFileContents() []byte {
+	formatString := `
+# This file is generated by soong_build. Do not edit.
+local_repository(
+    name = "sourceroot",
+    path = "%s",
+)
+`
+	return []byte(fmt.Sprintf(formatString, context.workspaceDir))
+}
+
 func (context *bazelContext) mainBzlFileContents() []byte {
 	contents := `
 # This file is generated by soong_build. Do not edit.
@@ -225,6 +240,18 @@
 	return []byte(contents)
 }
 
+// Returns a "canonicalized" corresponding to the given sourcetree-level label.
+// This abstraction is required because a sourcetree label such as //foo/bar:baz
+// must be referenced via the local repository prefix, such as
+// @sourceroot//foo/bar:baz.
+func canonicalizeLabel(label string) string {
+	if strings.HasPrefix(label, "//") {
+		return "@sourceroot" + label
+	} else {
+		return "@sourceroot//" + label
+	}
+}
+
 func (context *bazelContext) mainBuildFileContents() []byte {
 	formatString := `
 # This file is generated by soong_build. Do not edit.
@@ -236,7 +263,7 @@
 `
 	var buildRootDeps []string = nil
 	for val, _ := range context.requests {
-		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\"", val.label))
+		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label)))
 	}
 	buildRootDepsString := strings.Join(buildRootDeps, ",\n            ")
 
@@ -262,13 +289,19 @@
 	// TODO(cparsons): Sort by request type instead of assuming all requests
 	// are of GetAllFiles type.
 	for val, _ := range context.requests {
-		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\" : True", val.label))
+		buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\" : True", canonicalizeLabel(val.label)))
 	}
 	buildRootDepsString := strings.Join(buildRootDeps, ",\n  ")
 
 	return []byte(fmt.Sprintf(formatString, buildRootDepsString))
 }
 
+// Returns a workspace-relative path containing build-related metadata required
+// for interfacing with Bazel. Example: out/soong/bazel.
+func (context *bazelContext) intermediatesDir() string {
+	return filepath.Join(context.buildDir, "bazel")
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *bazelContext) InvokeBazel() error {
@@ -276,26 +309,38 @@
 
 	var cqueryOutput string
 	var err error
+
+	err = os.Mkdir(absolutePath(context.intermediatesDir()), 0777)
+	if err != nil {
+		return err
+	}
 	err = ioutil.WriteFile(
-		absolutePath(filepath.Join(context.buildDir, "main.bzl")),
+		absolutePath(filepath.Join(context.intermediatesDir(), "main.bzl")),
 		context.mainBzlFileContents(), 0666)
 	if err != nil {
 		return err
 	}
 	err = ioutil.WriteFile(
-		absolutePath(filepath.Join(context.buildDir, "BUILD.bazel")),
+		absolutePath(filepath.Join(context.intermediatesDir(), "BUILD.bazel")),
 		context.mainBuildFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	cquery_file_relpath := filepath.Join(context.buildDir, "buildroot.cquery")
+	cquery_file_relpath := filepath.Join(context.intermediatesDir(), "buildroot.cquery")
 	err = ioutil.WriteFile(
 		absolutePath(cquery_file_relpath),
 		context.cqueryStarlarkFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	buildroot_label := fmt.Sprintf("//%s:buildroot", context.buildDir)
+	workspace_file_relpath := filepath.Join(context.intermediatesDir(), "WORKSPACE.bazel")
+	err = ioutil.WriteFile(
+		absolutePath(workspace_file_relpath),
+		context.workspaceFileContents(), 0666)
+	if err != nil {
+		return err
+	}
+	buildroot_label := "//:buildroot"
 	cqueryOutput, err = context.issueBazelCommand("cquery",
 		[]string{fmt.Sprintf("deps(%s)", buildroot_label)},
 		"--output=starlark",
@@ -314,7 +359,7 @@
 	}
 
 	for val, _ := range context.requests {
-		if cqueryResult, ok := cqueryResults[val.label]; ok {
+		if cqueryResult, ok := cqueryResults[canonicalizeLabel(val.label)]; ok {
 			context.results[val] = string(cqueryResult)
 		} else {
 			return fmt.Errorf("missing result for bazel target %s", val.label)
diff --git a/android/config.go b/android/config.go
index e87a4ac..04c9129 100644
--- a/android/config.go
+++ b/android/config.go
@@ -14,6 +14,9 @@
 
 package android
 
+// This is the primary location to write and read all configuration values and
+// product variables necessary for soong_build's operation.
+
 import (
 	"encoding/json"
 	"fmt"
@@ -32,20 +35,31 @@
 	"android/soong/android/soongconfig"
 )
 
+// Bool re-exports proptools.Bool for the android package.
 var Bool = proptools.Bool
+
+// String re-exports proptools.String for the android package.
 var String = proptools.String
+
+// StringDefault re-exports proptools.StringDefault for the android package.
 var StringDefault = proptools.StringDefault
 
+// FutureApiLevelInt is a placeholder constant for unreleased API levels.
 const FutureApiLevelInt = 10000
 
+// FutureApiLevel represents unreleased API levels.
 var FutureApiLevel = ApiLevel{
 	value:     "current",
 	number:    FutureApiLevelInt,
 	isPreview: true,
 }
 
-// The configuration file name
+// 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.
 const productVariablesFileName = "soong.variables"
 
 // A FileConfigurableOptions contains options which can be configured by the
@@ -56,6 +70,8 @@
 	Host_bionic_arm64 *bool `json:",omitempty"`
 }
 
+// SetDefaultConfig resets the receiving FileConfigurableOptions to default
+// values.
 func (f *FileConfigurableOptions) SetDefaultConfig() {
 	*f = FileConfigurableOptions{}
 }
@@ -65,29 +81,38 @@
 	*config
 }
 
+// BuildDir returns the build output directory for the configuration.
 func (c Config) BuildDir() string {
 	return c.buildDir
 }
 
-// A DeviceConfig object represents the configuration for a particular device being built.  For
-// now there will only be one of these, but in the future there may be multiple devices being
-// built
+// A DeviceConfig object represents the configuration for a particular device
+// being built. For now there will only be one of these, but in the future there
+// may be multiple devices being built.
 type DeviceConfig struct {
 	*deviceConfig
 }
 
+// VendorConfig represents the configuration for vendor-specific behavior.
 type VendorConfig soongconfig.SoongConfig
 
+// Definition of general build configuration for soong_build. Some of these
+// configuration values are generated from soong_ui for soong_build,
+// communicated over JSON files like soong.config or soong.variables.
 type config struct {
+	// Options configurable with soong.confg
 	FileConfigurableOptions
+
+	// Options configurable with soong.variables
 	productVariables productVariables
 
 	// Only available on configs created by TestConfig
 	TestProductVariables *productVariables
 
+	// A specialized context object for Bazel/Soong mixed builds and migration
+	// purposes.
 	BazelContext BazelContext
 
-	PrimaryBuilder           string
 	ConfigFileName           string
 	ProductVariablesFileName string
 
@@ -97,8 +122,8 @@
 	AndroidCommonTarget      Target // the Target for common modules for the Android device
 	AndroidFirstDeviceTarget Target // the first Target for modules for the Android device
 
-	// multilibConflicts for an ArchType is true if there is earlier configured device architecture with the same
-	// multilib value.
+	// multilibConflicts for an ArchType is true if there is earlier configured
+	// device architecture with the same multilib value.
 	multilibConflicts map[ArchType]bool
 
 	deviceConfig *deviceConfig
@@ -112,7 +137,9 @@
 	envDeps   map[string]string
 	envFrozen bool
 
-	inMake bool
+	// Changes behavior based on whether Kati runs after soong_build, or if soong_build
+	// runs standalone.
+	katiEnabled bool
 
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
@@ -126,6 +153,10 @@
 	// in tests when a path doesn't exist.
 	testAllowNonExistentPaths bool
 
+	// The list of files that when changed, must invalidate soong_build to
+	// regenerate build.ninja.
+	ninjaFileDepsSet sync.Map
+
 	OncePer
 }
 
@@ -147,7 +178,8 @@
 	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
 
-// loads configuration options from a JSON file in the cwd.
+// loadFromConfigFile loads and decodes configuration options from a JSON file
+// in the current working directory.
 func loadFromConfigFile(configurable jsonConfigurable, filename string) error {
 	// Try to open the file
 	configFileReader, err := os.Open(filename)
@@ -187,7 +219,7 @@
 
 	f, err := ioutil.TempFile(filepath.Dir(filename), "config")
 	if err != nil {
-		return fmt.Errorf("cannot create empty config file %s: %s\n", filename, err.Error())
+		return fmt.Errorf("cannot create empty config file %s: %s", filename, err.Error())
 	}
 	defer os.Remove(f.Name())
 	defer f.Close()
@@ -219,14 +251,15 @@
 	}
 }
 
-// TestConfig returns a Config object suitable for using for tests
+// TestConfig returns a Config object for testing.
 func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	envCopy := make(map[string]string)
 	for k, v := range env {
 		envCopy[k] = v
 	}
 
-	// Copy the real PATH value to the test environment, it's needed by HostSystemTool() used in x86_darwin_host.go
+	// Copy the real PATH value to the test environment, it's needed by
+	// NonHermeticHostSystemTool() used in x86_darwin_host.go
 	envCopy["PATH"] = originalEnv["PATH"]
 
 	config := &config{
@@ -261,13 +294,12 @@
 
 	config.mockFileSystem(bp, fs)
 
-	if err := config.fromEnv(); err != nil {
-		panic(err)
-	}
-
 	return Config{config}
 }
 
+// TestArchConfigNativeBridge returns a Config object suitable for using
+// for tests that need to run the arch mutator for native bridge supported
+// archs.
 func TestArchConfigNativeBridge(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestArchConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -282,6 +314,8 @@
 	return testConfig
 }
 
+// TestArchConfigFuchsia returns a Config object suitable for using for
+// tests that need to run the arch mutator for the Fuchsia arch.
 func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -298,7 +332,8 @@
 	return testConfig
 }
 
-// TestConfig returns a Config object suitable for using for tests that need to run the arch mutator
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
 func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	testConfig := TestConfig(buildDir, env, bp, fs)
 	config := testConfig.config
@@ -330,10 +365,10 @@
 	return testConfig
 }
 
-// Returns a config object which is "reset" for another bootstrap run.
-// Only per-run data is reset. Data which needs to persist across multiple
-// runs in the same program execution is carried over (such as Bazel context
-// or environment deps).
+// ConfigForAdditionalRun is a config object which is "reset" for another
+// bootstrap run. Only per-run data is reset. Data which needs to persist across
+// multiple runs in the same program execution is carried over (such as Bazel
+// context or environment deps).
 func ConfigForAdditionalRun(c Config) (Config, error) {
 	newConfig, err := NewConfig(c.srcDir, c.buildDir, c.moduleListFile)
 	if err != nil {
@@ -344,10 +379,10 @@
 	return newConfig, nil
 }
 
-// New creates a new Config object.  The srcDir argument specifies the path to
-// the root source directory. It also loads the config file, if found.
+// NewConfig creates a new Config object. The srcDir argument specifies the path
+// to the root source directory. It also loads the config file, if found.
 func NewConfig(srcDir, buildDir string, moduleListFile string) (Config, error) {
-	// Make a config with default options
+	// Make a config with default options.
 	config := &config{
 		ConfigFileName:           filepath.Join(buildDir, configFileName),
 		ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName),
@@ -388,11 +423,13 @@
 		return Config{}, err
 	}
 
-	inMakeFile := filepath.Join(buildDir, ".soong.in_make")
-	if _, err := os.Stat(absolutePath(inMakeFile)); err == nil {
-		config.inMake = true
+	KatiEnabledMarkerFile := filepath.Join(buildDir, ".soong.kati_enabled")
+	if _, err := os.Stat(absolutePath(KatiEnabledMarkerFile)); err == nil {
+		config.katiEnabled = true
 	}
 
+	// Sets up the map of target OSes to the finer grained compilation targets
+	// that are configured from the product variables.
 	targets, err := decodeTargetProductVariables(config)
 	if err != nil {
 		return Config{}, err
@@ -426,18 +463,19 @@
 		multilib[target.Arch.ArchType.Multilib] = true
 	}
 
+	// Map of OS to compilation targets.
 	config.Targets = targets
+
+	// Compilation targets for host tools.
 	config.BuildOSTarget = config.Targets[BuildOs][0]
 	config.BuildOSCommonTarget = getCommonTargets(config.Targets[BuildOs])[0]
+
+	// Compilation targets for Android.
 	if len(config.Targets[Android]) > 0 {
 		config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0]
 		config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	if err := config.fromEnv(); err != nil {
-		return Config{}, err
-	}
-
 	if Bool(config.productVariables.GcovCoverage) && Bool(config.productVariables.ClangCoverage) {
 		return Config{}, fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
 	}
@@ -447,13 +485,9 @@
 			Bool(config.productVariables.ClangCoverage))
 
 	config.BazelContext, err = NewBazelContext(config)
-	if err != nil {
-		return Config{}, err
-	}
-	return Config{config}, nil
-}
 
-var TestConfigOsFs = map[string][]byte{}
+	return Config{config}, err
+}
 
 // mockFileSystem replaces all reads with accesses to the provided map of
 // filenames to contents stored as a byte slice.
@@ -485,27 +519,19 @@
 	c.mockBpList = blueprint.MockModuleListFile
 }
 
-func (c *config) fromEnv() error {
-	switch c.Getenv("EXPERIMENTAL_JAVA_LANGUAGE_LEVEL_9") {
-	case "", "true":
-		// Do nothing
-	default:
-		return fmt.Errorf("The environment variable EXPERIMENTAL_JAVA_LANGUAGE_LEVEL_9 is no longer supported. Java language level 9 is now the global default.")
-	}
-
-	return nil
-}
-
 func (c *config) StopBefore() bootstrap.StopBefore {
 	return c.stopBefore
 }
 
+// SetStopBefore configures soong_build to exit earlier at a specific point.
 func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) {
 	c.stopBefore = stopBefore
 }
 
 var _ bootstrap.ConfigStopBefore = (*config)(nil)
 
+// BlueprintToolLocation returns the directory containing build system tools
+// from Blueprint, like soong_zip and merge_zips.
 func (c *config) BlueprintToolLocation() string {
 	return filepath.Join(c.buildDir, "host", c.PrebuiltOS(), "bin")
 }
@@ -528,9 +554,12 @@
 	return PathForOutput(ctx, "host", c.PrebuiltOS(), "framework", path)
 }
 
-// HostSystemTool looks for non-hermetic tools from the system we're running on.
-// Generally shouldn't be used, but useful to find the XCode SDK, etc.
-func (c *config) HostSystemTool(name string) string {
+// NonHermeticHostSystemTool looks for non-hermetic tools from the system we're
+// running on. These tools are not checked-in to AOSP, and therefore could lead
+// to reproducibility problems. Should not be used for other than finding the
+// XCode SDK (xcrun, sw_vers), etc. See ui/build/paths/config.go for the
+// allowlist of host system tools.
+func (c *config) NonHermeticHostSystemTool(name string) string {
 	for _, dir := range filepath.SplitList(c.Getenv("PATH")) {
 		path := filepath.Join(dir, name)
 		if s, err := os.Stat(path); err != nil {
@@ -539,10 +568,13 @@
 			return path
 		}
 	}
-	return name
+	panic(fmt.Errorf(
+		"Unable to use '%s' as a host system tool for build system "+
+			"hermeticity reasons. See build/soong/ui/build/paths/config.go "+
+			"for the full list of allowed host tools on your system.", name))
 }
 
-// PrebuiltOS returns the name of the host OS used in prebuilts directories
+// PrebuiltOS returns the name of the host OS used in prebuilts directories.
 func (c *config) PrebuiltOS() string {
 	switch runtime.GOOS {
 	case "linux":
@@ -559,10 +591,14 @@
 	return fmt.Sprintf("%s/prebuilts/go/%s", c.srcDir, c.PrebuiltOS())
 }
 
+// PrebuiltBuildTool returns the path to a tool in the prebuilts directory containing
+// checked-in tools, like Kati, Ninja or Toybox, for the current host OS.
 func (c *config) PrebuiltBuildTool(ctx PathContext, tool string) Path {
 	return PathForSource(ctx, "prebuilts/build-tools", c.PrebuiltOS(), "bin", tool)
 }
 
+// CpPreserveSymlinksFlags returns the host-specific flag for the cp(1) command
+// to preserve symlinks.
 func (c *config) CpPreserveSymlinksFlags() string {
 	switch runtime.GOOS {
 	case "darwin":
@@ -610,6 +646,8 @@
 	return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
 }
 
+// EnvDeps returns the environment variables this build depends on. The first
+// call to this function blocks future reads from the environment.
 func (c *config) EnvDeps() map[string]string {
 	c.envLock.Lock()
 	defer c.envLock.Unlock()
@@ -617,19 +655,26 @@
 	return c.envDeps
 }
 
-func (c *config) EmbeddedInMake() bool {
-	return c.inMake
+func (c *config) KatiEnabled() bool {
+	return c.katiEnabled
 }
 
 func (c *config) BuildId() string {
 	return String(c.productVariables.BuildId)
 }
 
+// BuildNumberFile returns the path to a text file containing metadata
+// representing the current build's number.
+//
+// Rules that want to reference the build number should read from this file
+// without depending on it. They will run whenever their other dependencies
+// require them to run and get the current build number. This ensures they don't
+// rebuild on every incremental build when the build number changes.
 func (c *config) BuildNumberFile(ctx PathContext) Path {
 	return PathForOutput(ctx, String(c.productVariables.BuildNumberFile))
 }
 
-// DeviceName returns the name of the current device target
+// DeviceName returns the name of the current device target.
 // TODO: take an AndroidModuleContext to select the device name for multi-device builds
 func (c *config) DeviceName() string {
 	return *c.productVariables.DeviceName
@@ -701,19 +746,20 @@
 	return append(levels, c.PreviewApiLevels()...)
 }
 
+// DefaultAppTargetSdk returns the API level that platform apps are targeting.
+// This converts a codename to the exact ApiLevel it represents.
 func (c *config) DefaultAppTargetSdk(ctx EarlyModuleContext) ApiLevel {
 	if Bool(c.productVariables.Platform_sdk_final) {
 		return c.PlatformSdkVersion()
-	} else {
-		codename := c.PlatformSdkCodename()
-		if codename == "" {
-			return NoneApiLevel
-		}
-		if codename == "REL" {
-			panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
-		}
-		return ApiLevelOrPanic(ctx, codename)
 	}
+	codename := c.PlatformSdkCodename()
+	if codename == "" {
+		return NoneApiLevel
+	}
+	if codename == "REL" {
+		panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
+	}
+	return ApiLevelOrPanic(ctx, codename)
 }
 
 func (c *config) AppsDefaultVersionName() string {
@@ -745,19 +791,17 @@
 	defaultCert := String(c.productVariables.DefaultAppCertificate)
 	if defaultCert != "" {
 		return PathForSource(ctx, filepath.Dir(defaultCert))
-	} else {
-		return PathForSource(ctx, "build/make/target/product/security")
 	}
+	return PathForSource(ctx, "build/make/target/product/security")
 }
 
 func (c *config) DefaultAppCertificate(ctx PathContext) (pem, key SourcePath) {
 	defaultCert := String(c.productVariables.DefaultAppCertificate)
 	if defaultCert != "" {
 		return PathForSource(ctx, defaultCert+".x509.pem"), PathForSource(ctx, defaultCert+".pk8")
-	} else {
-		defaultDir := c.DefaultAppCertificateDir(ctx)
-		return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
 	}
+	defaultDir := c.DefaultAppCertificateDir(ctx)
+	return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
 }
 
 func (c *config) ApexKeyDir(ctx ModuleContext) SourcePath {
@@ -767,12 +811,14 @@
 		// When defaultCert is unset or is set to the testkeys path, use the APEX keys
 		// that is under the module dir
 		return pathForModuleSrc(ctx)
-	} else {
-		// If not, APEX keys are under the specified directory
-		return PathForSource(ctx, filepath.Dir(defaultCert))
 	}
+	// If not, APEX keys are under the specified directory
+	return PathForSource(ctx, filepath.Dir(defaultCert))
 }
 
+// AllowMissingDependencies configures Blueprint/Soong to not fail when modules
+// are configured to depend on non-existent modules. Note that this does not
+// affect missing input dependencies at the Ninja level.
 func (c *config) AllowMissingDependencies() bool {
 	return Bool(c.productVariables.Allow_missing_dependencies)
 }
@@ -842,9 +888,8 @@
 func (c *config) EnableCFI() bool {
 	if c.productVariables.EnableCFI == nil {
 		return true
-	} else {
-		return *c.productVariables.EnableCFI
 	}
+	return *c.productVariables.EnableCFI
 }
 
 func (c *config) DisableScudo() bool {
@@ -889,11 +934,13 @@
 	return c.IsEnvTrue("RUN_ERROR_PRONE")
 }
 
+// XrefCorpusName returns the Kythe cross-reference corpus name.
 func (c *config) XrefCorpusName() string {
 	return c.Getenv("XREF_CORPUS")
 }
 
-// Returns Compilation Unit encoding to use. Can be 'json' (default), 'proto' or 'all'.
+// XrefCuEncoding returns the compilation unit encoding to use for Kythe code
+// xrefs. Can be 'json' (default), 'proto' or 'all'.
 func (c *config) XrefCuEncoding() string {
 	if enc := c.Getenv("KYTHE_KZIP_ENCODING"); enc != "" {
 		return enc
@@ -928,6 +975,10 @@
 	return Bool(c.productVariables.ArtUseReadBarrier)
 }
 
+// Enforce Runtime Resource Overlays for a module. RROs supersede static RROs,
+// but some modules still depend on it.
+//
+// More info: https://source.android.com/devices/architecture/rros
 func (c *config) EnforceRROForModule(name string) bool {
 	enforceList := c.productVariables.EnforceRROTargets
 	// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
@@ -974,6 +1025,9 @@
 	return c.productVariables.ModulesLoadedByPrivilegedModules
 }
 
+// DexpreoptGlobalConfigPath returns the path to the dexpreopt.config file in
+// the output directory, if it was created during the product configuration
+// phase by Kati.
 func (c *config) DexpreoptGlobalConfigPath(ctx PathContext) OptionalPath {
 	if c.productVariables.DexpreoptGlobalConfig == nil {
 		return OptionalPathForPath(nil)
@@ -982,6 +1036,12 @@
 		pathForBuildToolDep(ctx, *c.productVariables.DexpreoptGlobalConfig))
 }
 
+// DexpreoptGlobalConfig returns the raw byte contents of the dexpreopt global
+// configuration. Since the configuration file was created by Kati during
+// product configuration (externally of soong_build), it's not tracked, so we
+// also manually add a Ninja file dependency on the configuration file to the
+// rule that creates the main build.ninja file. This ensures that build.ninja is
+// regenerated correctly if dexpreopt.config changes.
 func (c *config) DexpreoptGlobalConfig(ctx PathContext) ([]byte, error) {
 	path := c.DexpreoptGlobalConfigPath(ctx)
 	if !path.Valid() {
@@ -1340,26 +1400,31 @@
 //   - "system_ext:foo"
 //
 type ConfiguredJarList struct {
-	apexes []string // A list of apex components.
-	jars   []string // A list of jar components.
+	// A list of apex components, which can be an apex name,
+	// or special names like "platform" or "system_ext".
+	apexes []string
+
+	// A list of jar module name components.
+	jars []string
 }
 
-// The length of the list.
+// Len returns the length of the list of jars.
 func (l *ConfiguredJarList) Len() int {
 	return len(l.jars)
 }
 
-// Jar component of idx-th pair on the list.
+// Jar returns the idx-th jar component of (apex, jar) pairs.
 func (l *ConfiguredJarList) Jar(idx int) string {
 	return l.jars[idx]
 }
 
-// Apex component of idx-th pair on the list.
+// Apex returns the idx-th apex component of (apex, jar) pairs.
 func (l *ConfiguredJarList) Apex(idx int) string {
 	return l.apexes[idx]
 }
 
-// If the list contains a pair with the given jar.
+// ContainsJar returns true if the (apex, jar) pairs contains a pair with the
+// given jar module name.
 func (l *ConfiguredJarList) ContainsJar(jar string) bool {
 	return InList(jar, l.jars)
 }
@@ -1374,7 +1439,8 @@
 	return false
 }
 
-// Index of the first pair with the given jar on the list, or -1 if none.
+// IndexOfJar returns the first pair with the given jar name on the list, or -1
+// if not found.
 func (l *ConfiguredJarList) IndexOfJar(jar string) int {
 	return IndexList(jar, l.jars)
 }
@@ -1402,7 +1468,7 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
-// Filter out sublist.
+// RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
 func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
 	jars := make([]string, 0, l.Len())
@@ -1418,12 +1484,14 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
-// A copy of the list of strings containing jar components.
+// CopyOfJars returns a copy of the list of strings containing jar module name
+// components.
 func (l *ConfiguredJarList) CopyOfJars() []string {
 	return CopyOf(l.jars)
 }
 
-// A copy of the list of strings with colon-separated (apex, jar) pairs.
+// CopyOfApexJarPairs returns a copy of the list of strings with colon-separated
+// (apex, jar) pairs.
 func (l *ConfiguredJarList) CopyOfApexJarPairs() []string {
 	pairs := make([]string, 0, l.Len())
 
@@ -1435,7 +1503,7 @@
 	return pairs
 }
 
-// A list of build paths based on the given directory prefix.
+// BuildPaths returns a list of build paths based on the given directory prefix.
 func (l *ConfiguredJarList) BuildPaths(ctx PathContext, dir OutputPath) WritablePaths {
 	paths := make(WritablePaths, l.Len())
 	for i, jar := range l.jars {
@@ -1444,7 +1512,8 @@
 	return paths
 }
 
-// Called when loading configuration from JSON into a configuration structure.
+// UnmarshalJSON converts JSON configuration from raw bytes into a
+// ConfiguredJarList structure.
 func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
 	// Try and unmarshal into a []string each item of which contains a pair
 	// <apex>:<jar>.
@@ -1464,16 +1533,19 @@
 	return nil
 }
 
+// ModuleStem hardcodes the stem of framework-minus-apex to return "framework".
+//
+// TODO(b/139391334): hard coded until we find a good way to query the stem of a
+// module before any other mutators are run.
 func ModuleStem(module string) string {
-	// b/139391334: the stem of framework-minus-apex is framework. This is hard coded here until we
-	// find a good way to query the stem of a module before any other mutators are run.
 	if module == "framework-minus-apex" {
 		return "framework"
 	}
 	return module
 }
 
-// A list of on-device paths.
+// DevicePaths computes the on-device paths for the list of (apex, jar) pairs,
+// based on the operating system.
 func (l *ConfiguredJarList) DevicePaths(cfg Config, ostype OsType) []string {
 	paths := make([]string, l.Len())
 	for i, jar := range l.jars {
@@ -1534,6 +1606,8 @@
 	}
 }
 
+// CreateTestConfiguredJarList is a function to create ConfiguredJarList for
+// tests.
 func CreateTestConfiguredJarList(list []string) ConfiguredJarList {
 	apexes, jars, err := splitListOfPairsIntoPairOfLists(list)
 	if err != nil {
@@ -1543,6 +1617,7 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// EmptyConfiguredJarList returns an empty jar list.
 func EmptyConfiguredJarList() ConfiguredJarList {
 	return ConfiguredJarList{}
 }
@@ -1552,8 +1627,7 @@
 func (c *config) BootJars() []string {
 	return c.Once(earlyBootJarsKey, func() interface{} {
 		list := c.productVariables.BootJars.CopyOfJars()
-		list = append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
-		return list
+		return append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
 	}).([]string)
 }
 
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 15c518a..a5b1533 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -14,11 +14,6 @@
 
 package android
 
-import (
-	"fmt"
-	"io"
-)
-
 func init() {
 	RegisterModuleType("csuite_config", CSuiteConfigFactory)
 }
@@ -38,22 +33,21 @@
 	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
 }
 
-func (me *CSuiteConfig) AndroidMk() AndroidMkData {
-	androidMkData := AndroidMkData{
+func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
+	androidMkEntries := AndroidMkEntries{
 		Class:      "FAKE",
 		Include:    "$(BUILD_SYSTEM)/suite_host_config.mk",
 		OutputFile: OptionalPathForPath(me.OutputFilePath),
 	}
-	androidMkData.Extra = []AndroidMkExtraFunc{
-		func(w io.Writer, outputFile Path) {
+	androidMkEntries.ExtraEntries = []AndroidMkExtraEntriesFunc{
+		func(entries *AndroidMkEntries) {
 			if me.properties.Test_config != nil {
-				fmt.Fprintf(w, "LOCAL_TEST_CONFIG := %s\n",
-					*me.properties.Test_config)
+				entries.SetString("LOCAL_TEST_CONFIG", *me.properties.Test_config)
 			}
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := csuite")
+			entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", "csuite")
 		},
 	}
-	return androidMkData
+	return []AndroidMkEntries{androidMkEntries}
 }
 
 func InitCSuiteConfigModule(me *CSuiteConfig) {
diff --git a/android/defaults.go b/android/defaults.go
index eb013d7..44753ce 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -183,7 +183,7 @@
 
 	initAndroidModuleBase(module)
 	initProductVariableModule(module)
-	InitArchModule(module)
+	initArchModule(module)
 	InitDefaultableModule(module)
 
 	// Add properties that will not have defaults applied to them.
diff --git a/android/filegroup.go b/android/filegroup.go
index 68311e3..9425616 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"android/soong/bazel"
 	"strings"
 )
 
@@ -37,6 +38,9 @@
 	// Create a make variable with the specified name that contains the list of files in the
 	// filegroup, relative to the root of the source tree.
 	Export_to_make_var *string
+
+	// Properties for Bazel migration purposes.
+	bazel.Properties
 }
 
 type fileGroup struct {
diff --git a/android/makevars.go b/android/makevars.go
index 3ca7792..5101436 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -139,15 +139,24 @@
 	MakeVars(ctx MakeVarsContext)
 }
 
-// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to the list of
-// MakeVarsProviders to run.
-func registerSingletonMakeVarsProvider(singleton SingletonMakeVarsProvider) {
-	singletonMakeVarsProviders = append(singletonMakeVarsProviders,
-		makeVarsProvider{pctx, SingletonmakeVarsProviderAdapter(singleton)})
+var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
+
+// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
+// the list of MakeVarsProviders to run.
+func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
+	// Singletons are registered on the Context and may be different between different Contexts,
+	// for example when running multiple tests.  Store the SingletonMakeVarsProviders in the
+	// Config so they are attached to the Context.
+	singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} {
+		return &[]makeVarsProvider{}
+	}).(*[]makeVarsProvider)
+
+	*singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
+		makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
 }
 
-// SingletonmakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
-func SingletonmakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
+// singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
+func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
 	return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
 }
 
@@ -175,9 +184,6 @@
 // Collection of makevars providers that are registered in init() methods.
 var makeVarsInitProviders []makeVarsProvider
 
-// Collection of singleton makevars providers that are not registered as part of init() methods.
-var singletonMakeVarsProviders []makeVarsProvider
-
 type makeVarsContext struct {
 	SingletonContext
 	config  Config
@@ -207,7 +213,7 @@
 }
 
 func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	if !ctx.Config().EmbeddedInMake() {
+	if !ctx.Config().KatiEnabled() {
 		return
 	}
 
@@ -224,7 +230,11 @@
 	var vars []makeVarsVariable
 	var dists []dist
 	var phonies []phony
-	for _, provider := range append(makeVarsInitProviders) {
+
+	providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
+	providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
+
+	for _, provider := range providers {
 		mctx := &makeVarsContext{
 			SingletonContext: ctx,
 			pctx:             provider.pctx,
@@ -237,25 +247,6 @@
 		dists = append(dists, mctx.dists...)
 	}
 
-	for _, provider := range append(singletonMakeVarsProviders) {
-		mctx := &makeVarsContext{
-			SingletonContext: ctx,
-			pctx:             provider.pctx,
-		}
-
-		provider.call(mctx)
-
-		vars = append(vars, mctx.vars...)
-		phonies = append(phonies, mctx.phonies...)
-		dists = append(dists, mctx.dists...)
-	}
-
-	// Clear singleton makevars providers after use. Since these are in-memory
-	// singletons, this ensures state is reset if the build tree is processed
-	// multiple times.
-	// TODO(cparsons): Clean up makeVarsProviders to be part of the context.
-	singletonMakeVarsProviders = nil
-
 	ctx.VisitAllModules(func(m Module) {
 		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
 			mctx := &makeVarsContext{
diff --git a/android/module.go b/android/module.go
index ef1b0bd..d680baa 100644
--- a/android/module.go
+++ b/android/module.go
@@ -440,6 +440,7 @@
 	TargetRequiredModuleNames() []string
 
 	FilesToInstall() InstallPaths
+	PackagingSpecs() []PackagingSpec
 }
 
 // Qualified id for a module
@@ -502,8 +503,12 @@
 	// A suffix to add to the artifact file name (before any extension).
 	Suffix *string `android:"arch_variant"`
 
-	// A string tag to select the OutputFiles associated with the tag. Defaults to the
-	// the empty "" string.
+	// A string tag to select the OutputFiles associated with the tag.
+	//
+	// If no tag is specified then it will select the default dist paths provided
+	// by the module type. If a tag of "" is specified then it will return the
+	// default output files provided by the modules, i.e. the result of calling
+	// OutputFiles("").
 	Tag *string `android:"arch_variant"`
 }
 
@@ -716,7 +721,9 @@
 	DebugMutators   []string `blueprint:"mutated"`
 	DebugVariations []string `blueprint:"mutated"`
 
-	// set by ImageMutator
+	// ImageVariation is set by ImageMutator to specify which image this variation is for,
+	// for example "" for core or "recovery" for recovery.  It will often be set to one of the
+	// constants in image.go, but can also be set to a custom value by individual module types.
 	ImageVariation string `blueprint:"mutated"`
 }
 
@@ -730,9 +737,45 @@
 	Dists []Dist `android:"arch_variant"`
 }
 
+// The key to use in TaggedDistFiles when a Dist structure does not specify a
+// tag property. This intentionally does not use "" as the default because that
+// would mean that an empty tag would have a different meaning when used in a dist
+// structure that when used to reference a specific set of output paths using the
+// :module{tag} syntax, which passes tag to the OutputFiles(tag) method.
+const DefaultDistTag = "<default-dist-tag>"
+
 // A map of OutputFile tag keys to Paths, for disting purposes.
 type TaggedDistFiles map[string]Paths
 
+// addPathsForTag adds a mapping from the tag to the paths. If the map is nil
+// then it will create a map, update it and then return it. If a mapping already
+// exists for the tag then the paths are appended to the end of the current list
+// of paths, ignoring any duplicates.
+func (t TaggedDistFiles) addPathsForTag(tag string, paths ...Path) TaggedDistFiles {
+	if t == nil {
+		t = make(TaggedDistFiles)
+	}
+
+	for _, distFile := range paths {
+		if distFile != nil && !t[tag].containsPath(distFile) {
+			t[tag] = append(t[tag], distFile)
+		}
+	}
+
+	return t
+}
+
+// merge merges the entries from the other TaggedDistFiles object into this one.
+// If the TaggedDistFiles is nil then it will create a new instance, merge the
+// other into it, and then return it.
+func (t TaggedDistFiles) merge(other TaggedDistFiles) TaggedDistFiles {
+	for tag, paths := range other {
+		t = t.addPathsForTag(tag, paths...)
+	}
+
+	return t
+}
+
 func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
 	for _, path := range paths {
 		if path == nil {
@@ -741,7 +784,7 @@
 	}
 
 	// The default OutputFile tag is the empty "" string.
-	return TaggedDistFiles{"": paths}
+	return TaggedDistFiles{DefaultDistTag: paths}
 }
 
 type hostAndDeviceProperties struct {
@@ -765,27 +808,32 @@
 type HostOrDeviceSupported int
 
 const (
-	_ HostOrDeviceSupported = iota
+	hostSupported = 1 << iota
+	hostCrossSupported
+	deviceSupported
+	hostDefault
+	deviceDefault
 
 	// Host and HostCross are built by default. Device is not supported.
-	HostSupported
+	HostSupported = hostSupported | hostCrossSupported | hostDefault
 
 	// Host is built by default. HostCross and Device are not supported.
-	HostSupportedNoCross
+	HostSupportedNoCross = hostSupported | hostDefault
 
 	// Device is built by default. Host and HostCross are not supported.
-	DeviceSupported
+	DeviceSupported = deviceSupported | deviceDefault
 
 	// Device is built by default. Host and HostCross are supported.
-	HostAndDeviceSupported
+	HostAndDeviceSupported = hostSupported | hostCrossSupported | deviceSupported | deviceDefault
 
 	// Host, HostCross, and Device are built by default.
-	HostAndDeviceDefault
+	HostAndDeviceDefault = hostSupported | hostCrossSupported | hostDefault |
+		deviceSupported | deviceDefault
 
 	// Nothing is supported. This is not exposed to the user, but used to mark a
 	// host only module as unsupported when the module type is not supported on
 	// the host OS. E.g. benchmarks are supported on Linux but not Darwin.
-	NeitherHostNorDeviceSupported
+	NeitherHostNorDeviceSupported = 0
 )
 
 type moduleKind int
@@ -819,6 +867,8 @@
 	m.base().module = m
 }
 
+// InitAndroidModule initializes the Module as an Android module that is not architecture-specific.
+// It adds the common properties, for example "name" and "enabled".
 func InitAndroidModule(m Module) {
 	initAndroidModuleBase(m)
 	base := m.base()
@@ -838,6 +888,12 @@
 	setPrimaryVisibilityProperty(m, "visibility", &base.commonProperties.Visibility)
 }
 
+// InitAndroidArchModule initializes the Module as an Android module that is architecture-specific.
+// It adds the common properties, for example "name" and "enabled", as well as runtime generated
+// property structs for architecture-specific versions of generic properties tagged with
+// `android:"arch_variant"`.
+//
+//  InitAndroidModule should not be called if InitAndroidArchModule was called.
 func InitAndroidArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidModule(m)
 
@@ -847,21 +903,37 @@
 	base.commonProperties.ArchSpecific = true
 	base.commonProperties.UseTargetVariants = true
 
-	switch hod {
-	case HostAndDeviceSupported, HostAndDeviceDefault:
+	if hod&hostSupported != 0 && hod&deviceSupported != 0 {
 		m.AddProperties(&base.hostAndDeviceProperties)
 	}
 
-	InitArchModule(m)
+	initArchModule(m)
 }
 
+// InitAndroidMultiTargetsArchModule initializes the Module as an Android module that is
+// architecture-specific, but will only have a single variant per OS that handles all the
+// architectures simultaneously.  The list of Targets that it must handle will be available from
+// ModuleContext.MultiTargets. It adds the common properties, for example "name" and "enabled", as
+// well as runtime generated property structs for architecture-specific versions of generic
+// properties tagged with `android:"arch_variant"`.
+//
+// InitAndroidModule or InitAndroidArchModule should not be called if
+// InitAndroidMultiTargetsArchModule was called.
 func InitAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidArchModule(m, hod, defaultMultilib)
 	m.base().commonProperties.UseTargetVariants = false
 }
 
-// As InitAndroidMultiTargetsArchModule except it creates an additional CommonOS variant that
-// has dependencies on all the OsType specific variants.
+// InitCommonOSAndroidMultiTargetsArchModule initializes the Module as an Android module that is
+// architecture-specific, but will only have a single variant per OS that handles all the
+// architectures simultaneously, and will also have an additional CommonOS variant that has
+// dependencies on all the OS-specific variants.  The list of Targets that it must handle will be
+// available from ModuleContext.MultiTargets.  It adds the common properties, for example "name" and
+// "enabled", as well as runtime generated property structs for architecture-specific versions of
+// generic properties tagged with `android:"arch_variant"`.
+//
+// InitAndroidModule, InitAndroidArchModule or InitAndroidMultiTargetsArchModule should not be
+// called if InitCommonOSAndroidMultiTargetsArchModule was called.
 func InitCommonOSAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib) {
 	InitAndroidArchModule(m, hod, defaultMultilib)
 	m.base().commonProperties.UseTargetVariants = false
@@ -934,9 +1006,13 @@
 	noAddressSanitizer bool
 	installFiles       InstallPaths
 	checkbuildFiles    Paths
+	packagingSpecs     []PackagingSpec
 	noticeFiles        Paths
 	phonies            map[string]Paths
 
+	// The files to copy to the dist as explicitly specified in the .bp file.
+	distFiles TaggedDistFiles
+
 	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
 	// Only set on the final variant of each module
 	installTarget    WritablePath
@@ -1038,23 +1114,30 @@
 }
 
 func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
-	distFiles := make(TaggedDistFiles)
+	var distFiles TaggedDistFiles
 	for _, dist := range m.Dists() {
-		var tag string
-		var distFilesForTag Paths
-		if dist.Tag == nil {
-			tag = ""
-		} else {
-			tag = *dist.Tag
-		}
-		distFilesForTag, err := m.base().module.(OutputFileProducer).OutputFiles(tag)
-		if err != nil {
-			ctx.PropertyErrorf("dist.tag", "%s", err.Error())
-		}
-		for _, distFile := range distFilesForTag {
-			if distFile != nil && !distFiles[tag].containsPath(distFile) {
-				distFiles[tag] = append(distFiles[tag], distFile)
+		// If no tag is specified then it means to use the default dist paths so use
+		// the special tag name which represents that.
+		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
+
+		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
+			// Call the OutputFiles(tag) method to get the paths associated with the tag.
+			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
+
+			// If the tag was not supported and is not DefaultDistTag then it is an error.
+			// Failing to find paths for DefaultDistTag is not an error. It just means
+			// that the module type requires the legacy behavior.
+			if err != nil && tag != DefaultDistTag {
+				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
 			}
+
+			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
+		} else if tag != DefaultDistTag {
+			// If the tag was specified then it is an error if the module does not
+			// implement OutputFileProducer because there is no other way of accessing
+			// the paths for the specified tag.
+			ctx.PropertyErrorf("dist.tag",
+				"tag %s not supported because the module does not implement OutputFileProducer", tag)
 		}
 	}
 
@@ -1098,43 +1181,54 @@
 	return m.commonProperties.CommonOSVariant
 }
 
-func (m *ModuleBase) supportsTarget(target Target, config Config) bool {
-	switch m.commonProperties.HostOrDeviceSupported {
-	case HostSupported:
-		return target.Os.Class == Host
-	case HostSupportedNoCross:
-		return target.Os.Class == Host && !target.HostCross
-	case DeviceSupported:
-		return target.Os.Class == Device
-	case HostAndDeviceSupported, HostAndDeviceDefault:
-		supported := false
-		if Bool(m.hostAndDeviceProperties.Host_supported) ||
-			(m.commonProperties.HostOrDeviceSupported == HostAndDeviceDefault &&
-				m.hostAndDeviceProperties.Host_supported == nil) {
-			supported = supported || target.Os.Class == Host
+// supportsTarget returns true if the given Target is supported by the current module.
+func (m *ModuleBase) supportsTarget(target Target) bool {
+	switch target.Os.Class {
+	case Host:
+		if target.HostCross {
+			return m.HostCrossSupported()
+		} else {
+			return m.HostSupported()
 		}
-		if m.hostAndDeviceProperties.Device_supported == nil ||
-			*m.hostAndDeviceProperties.Device_supported {
-			supported = supported || target.Os.Class == Device
-		}
-		return supported
+	case Device:
+		return m.DeviceSupported()
 	default:
 		return false
 	}
 }
 
+// DeviceSupported returns true if the current module is supported and enabled for device targets,
+// i.e. the factory method set the HostOrDeviceSupported value to include device support and
+// the device support is enabled by default or enabled by the device_supported property.
 func (m *ModuleBase) DeviceSupported() bool {
-	return m.commonProperties.HostOrDeviceSupported == DeviceSupported ||
-		m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported &&
-			(m.hostAndDeviceProperties.Device_supported == nil ||
-				*m.hostAndDeviceProperties.Device_supported)
+	hod := m.commonProperties.HostOrDeviceSupported
+	// deviceEnabled is true if the device_supported property is true or the HostOrDeviceSupported
+	// value has the deviceDefault bit set.
+	deviceEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Device_supported, hod&deviceDefault != 0)
+	return hod&deviceSupported != 0 && deviceEnabled
 }
 
+// HostSupported returns true if the current module is supported and enabled for host targets,
+// i.e. the factory method set the HostOrDeviceSupported value to include host support and
+// the host support is enabled by default or enabled by the host_supported property.
 func (m *ModuleBase) HostSupported() bool {
-	return m.commonProperties.HostOrDeviceSupported == HostSupported ||
-		m.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported &&
-			(m.hostAndDeviceProperties.Host_supported != nil &&
-				*m.hostAndDeviceProperties.Host_supported)
+	hod := m.commonProperties.HostOrDeviceSupported
+	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
+	// value has the hostDefault bit set.
+	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
+	return hod&hostSupported != 0 && hostEnabled
+}
+
+// HostCrossSupported returns true if the current module is supported and enabled for host cross
+// targets, i.e. the factory method set the HostOrDeviceSupported value to include host cross
+// support and the host cross support is enabled by default or enabled by the
+// host_supported property.
+func (m *ModuleBase) HostCrossSupported() bool {
+	hod := m.commonProperties.HostOrDeviceSupported
+	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
+	// value has the hostDefault bit set.
+	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
+	return hod&hostCrossSupported != 0 && hostEnabled
 }
 
 func (m *ModuleBase) Platform() bool {
@@ -1263,6 +1357,10 @@
 	return m.installFiles
 }
 
+func (m *ModuleBase) PackagingSpecs() []PackagingSpec {
+	return m.packagingSpecs
+}
+
 func (m *ModuleBase) NoAddressSanitizer() bool {
 	return m.noAddressSanitizer
 }
@@ -1396,7 +1494,7 @@
 
 	if len(deps) > 0 {
 		suffix := ""
-		if ctx.Config().EmbeddedInMake() {
+		if ctx.Config().KatiEnabled() {
 			suffix = "-soong"
 		}
 
@@ -1531,22 +1629,9 @@
 	ctx.Variable(pctx, "moduleDescSuffix", s)
 
 	// Some common property checks for properties that will be used later in androidmk.go
-	if m.distProperties.Dist.Dest != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dest)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dest", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Dir != nil {
-		_, err := validateSafePath(*m.distProperties.Dist.Dir)
-		if err != nil {
-			ctx.PropertyErrorf("dist.dir", "%s", err.Error())
-		}
-	}
-	if m.distProperties.Dist.Suffix != nil {
-		if strings.Contains(*m.distProperties.Dist.Suffix, "/") {
-			ctx.PropertyErrorf("dist.suffix", "Suffix may not contain a '/' character.")
-		}
+	checkDistProperties(ctx, "dist", &m.distProperties.Dist)
+	for i, _ := range m.distProperties.Dists {
+		checkDistProperties(ctx, fmt.Sprintf("dists[%d]", i), &m.distProperties.Dists[i])
 	}
 
 	if m.Enabled() {
@@ -1583,8 +1668,18 @@
 			return
 		}
 
+		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
+		// as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
+		// output paths being set which must be done before or during
+		// GenerateAndroidBuildActions.
+		m.distFiles = m.GenerateTaggedDistFiles(ctx)
+		if ctx.Failed() {
+			return
+		}
+
 		m.installFiles = append(m.installFiles, ctx.installFiles...)
 		m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
+		m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
 		m.initRcPaths = PathsForModuleSrc(ctx, m.commonProperties.Init_rc)
 		m.vintfFragmentsPaths = PathsForModuleSrc(ctx, m.commonProperties.Vintf_fragments)
 		for k, v := range ctx.phonies {
@@ -1610,6 +1705,32 @@
 	m.variables = ctx.variables
 }
 
+// Check the supplied dist structure to make sure that it is valid.
+//
+// property - the base property, e.g. dist or dists[1], which is combined with the
+// name of the nested property to produce the full property, e.g. dist.dest or
+// dists[1].dir.
+func checkDistProperties(ctx *moduleContext, property string, dist *Dist) {
+	if dist.Dest != nil {
+		_, err := validateSafePath(*dist.Dest)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dest", "%s", err.Error())
+		}
+	}
+	if dist.Dir != nil {
+		_, err := validateSafePath(*dist.Dir)
+		if err != nil {
+			ctx.PropertyErrorf(property+".dir", "%s", err.Error())
+		}
+	}
+	if dist.Suffix != nil {
+		if strings.Contains(*dist.Suffix, "/") {
+			ctx.PropertyErrorf(property+".suffix", "Suffix may not contain a '/' character.")
+		}
+	}
+
+}
+
 type earlyModuleContext struct {
 	blueprint.EarlyModuleContext
 
@@ -1752,6 +1873,7 @@
 type moduleContext struct {
 	bp blueprint.ModuleContext
 	baseModuleContext
+	packagingSpecs  []PackagingSpec
 	installDeps     InstallPaths
 	installFiles    InstallPaths
 	checkbuildFiles Paths
@@ -2274,7 +2396,7 @@
 	}
 
 	if m.Device() {
-		if m.Config().EmbeddedInMake() && !m.InstallBypassMake() {
+		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
 			return true
 		}
 
@@ -2288,16 +2410,15 @@
 
 func (m *moduleContext) InstallFile(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, Cp, deps)
+	return m.installFile(installPath, name, srcPath, deps, false)
 }
 
 func (m *moduleContext) InstallExecutable(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, CpExecutable, deps)
+	return m.installFile(installPath, name, srcPath, deps, true)
 }
 
-func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path,
-	rule blueprint.Rule, deps []Path) InstallPath {
+func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path, executable bool) InstallPath {
 
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
@@ -2316,6 +2437,11 @@
 			orderOnlyDeps = deps
 		}
 
+		rule := Cp
+		if executable {
+			rule = CpExecutable
+		}
+
 		m.Build(pctx, BuildParams{
 			Rule:        rule,
 			Description: "install " + fullInstallPath.Base(),
@@ -2323,11 +2449,19 @@
 			Input:       srcPath,
 			Implicits:   implicitDeps,
 			OrderOnly:   orderOnlyDeps,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 		})
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          srcPath,
+		symlinkTarget:    "",
+		executable:       executable,
+	})
+
 	m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 	return fullInstallPath
 }
@@ -2336,18 +2470,18 @@
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, true)
 
+	relPath, err := filepath.Rel(path.Dir(fullInstallPath.String()), srcPath.String())
+	if err != nil {
+		panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err))
+	}
 	if !m.skipInstall(fullInstallPath) {
 
-		relPath, err := filepath.Rel(path.Dir(fullInstallPath.String()), srcPath.String())
-		if err != nil {
-			panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err))
-		}
 		m.Build(pctx, BuildParams{
 			Rule:        Symlink,
 			Description: "install symlink " + fullInstallPath.Base(),
 			Output:      fullInstallPath,
 			Input:       srcPath,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 			Args: map[string]string{
 				"fromPath": relPath,
 			},
@@ -2356,6 +2490,14 @@
 		m.installFiles = append(m.installFiles, fullInstallPath)
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          nil,
+		symlinkTarget:    relPath,
+		executable:       false,
+	})
+
 	return fullInstallPath
 }
 
@@ -2370,7 +2512,7 @@
 			Rule:        Symlink,
 			Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
 			Output:      fullInstallPath,
-			Default:     !m.Config().EmbeddedInMake(),
+			Default:     !m.Config().KatiEnabled(),
 			Args: map[string]string{
 				"fromPath": absPath,
 			},
@@ -2378,6 +2520,14 @@
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
+
+	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
+		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:          nil,
+		symlinkTarget:    absPath,
+		executable:       false,
+	})
+
 	return fullInstallPath
 }
 
@@ -2504,6 +2654,15 @@
 			return nil, fmt.Errorf("failed to get output files from module %q", pathContextName(ctx, module))
 		}
 		return paths, nil
+	} else if sourceFileProducer, ok := module.(SourceFileProducer); ok {
+		if tag != "" {
+			return nil, fmt.Errorf("module %q is a SourceFileProducer, not an OutputFileProducer, and so does not support tag %q", pathContextName(ctx, module), tag)
+		}
+		paths := sourceFileProducer.Srcs()
+		if len(paths) == 0 {
+			return nil, fmt.Errorf("failed to get output files from module %q", pathContextName(ctx, module))
+		}
+		return paths, nil
 	} else {
 		return nil, fmt.Errorf("module %q is not an OutputFileProducer", pathContextName(ctx, module))
 	}
@@ -2591,7 +2750,7 @@
 	})
 
 	suffix := ""
-	if ctx.Config().EmbeddedInMake() {
+	if ctx.Config().KatiEnabled() {
 		suffix = "-soong"
 	}
 
@@ -2599,7 +2758,7 @@
 	ctx.Phony("checkbuild"+suffix, checkbuildDeps...)
 
 	// Make will generate the MODULES-IN-* targets
-	if ctx.Config().EmbeddedInMake() {
+	if ctx.Config().KatiEnabled() {
 		return
 	}
 
diff --git a/android/module_test.go b/android/module_test.go
index 6cc1813..e3cc613 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -232,3 +232,51 @@
 		t.Errorf("Expected build params to fail validation: %+v", bparams)
 	}
 }
+
+func TestDistErrorChecking(t *testing.T) {
+	bp := `
+		deps {
+			name: "foo",
+      dist: {
+        dest: "../invalid-dest",
+        dir: "../invalid-dir",
+        suffix: "invalid/suffix",
+      },
+      dists: [
+        {
+          dest: "../invalid-dest0",
+          dir: "../invalid-dir0",
+          suffix: "invalid/suffix0",
+        },
+        {
+          dest: "../invalid-dest1",
+          dir: "../invalid-dir1",
+          suffix: "invalid/suffix1",
+        },
+      ],
+ 		}
+	`
+
+	config := TestConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("deps", depsModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+
+	expectedErrs := []string{
+		"\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E",
+		"\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E",
+		"\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E",
+		"\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E",
+		"\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E",
+		"\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E",
+		"\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E",
+		"\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E",
+	}
+	CheckErrorsAgainstExpectations(t, errs, expectedErrs)
+}
diff --git a/android/ninja_deps.go b/android/ninja_deps.go
new file mode 100644
index 0000000..2f442d5
--- /dev/null
+++ b/android/ninja_deps.go
@@ -0,0 +1,43 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import "sort"
+
+func (c *config) addNinjaFileDeps(deps ...string) {
+	for _, dep := range deps {
+		c.ninjaFileDepsSet.Store(dep, true)
+	}
+}
+
+func (c *config) ninjaFileDeps() []string {
+	var deps []string
+	c.ninjaFileDepsSet.Range(func(key, value interface{}) bool {
+		deps = append(deps, key.(string))
+		return true
+	})
+	sort.Strings(deps)
+	return deps
+}
+
+func ninjaDepsSingletonFactory() Singleton {
+	return &ninjaDepsSingleton{}
+}
+
+type ninjaDepsSingleton struct{}
+
+func (ninjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	ctx.AddNinjaFileDeps(ctx.Config().ninjaFileDeps()...)
+}
diff --git a/android/ninja_deps_test.go b/android/ninja_deps_test.go
new file mode 100644
index 0000000..d3775ed
--- /dev/null
+++ b/android/ninja_deps_test.go
@@ -0,0 +1,75 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"testing"
+)
+
+func init() {
+	// This variable uses ExistentPathForSource on a PackageVarContext, which is a PathContext
+	// that is not a PathGlobContext.  That requires the deps to be stored in the Config.
+	pctx.VariableFunc("test_ninja_deps_variable", func(ctx PackageVarContext) string {
+		// Using ExistentPathForSource to look for a file that does not exist in a directory that
+		// does exist (test_ninja_deps) from a PackageVarContext adds a dependency from build.ninja
+		// to the directory.
+		if ExistentPathForSource(ctx, "test_ninja_deps/does_not_exist").Valid() {
+			return "true"
+		} else {
+			return "false"
+		}
+	})
+}
+
+func testNinjaDepsSingletonFactory() Singleton {
+	return testNinjaDepsSingleton{}
+}
+
+type testNinjaDepsSingleton struct{}
+
+func (testNinjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	// Reference the test_ninja_deps_variable in a build statement so Blueprint is forced to
+	// evaluate it.
+	ctx.Build(pctx, BuildParams{
+		Rule:   Cp,
+		Input:  PathForTesting("foo"),
+		Output: PathForOutput(ctx, "test_ninja_deps_out"),
+		Args: map[string]string{
+			"cpFlags": "${test_ninja_deps_variable}",
+		},
+	})
+}
+
+func TestNinjaDeps(t *testing.T) {
+	fs := map[string][]byte{
+		"test_ninja_deps/exists": nil,
+	}
+	config := TestConfig(buildDir, nil, "", fs)
+
+	ctx := NewTestContext(config)
+	ctx.RegisterSingletonType("test_ninja_deps_singleton", testNinjaDepsSingletonFactory)
+	ctx.RegisterSingletonType("ninja_deps_singleton", ninjaDepsSingletonFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	ninjaDeps, errs := ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	// Verify that the ninja file has a dependency on the test_ninja_deps directory.
+	if g, w := ninjaDeps, "test_ninja_deps"; !InList(w, g) {
+		t.Errorf("expected %q in %q", w, g)
+	}
+}
diff --git a/android/package_ctx.go b/android/package_ctx.go
index 0de356e..6d0fcb3 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -56,7 +56,7 @@
 	e.errors = append(e.errors, fmt.Errorf(format, args...))
 }
 func (e *configErrorWrapper) AddNinjaFileDeps(deps ...string) {
-	e.pctx.AddNinjaFileDeps(deps...)
+	e.config.addNinjaFileDeps(deps...)
 }
 
 type PackageVarContext interface {
diff --git a/android/packaging.go b/android/packaging.go
new file mode 100644
index 0000000..512e4ba
--- /dev/null
+++ b/android/packaging.go
@@ -0,0 +1,205 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License")
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"github.com/google/blueprint"
+)
+
+// PackagingSpec abstracts a request to place a built artifact at a certain path in a package.
+// A package can be the traditional <partition>.img, but isn't limited to those. Other examples could
+// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running
+// on a VM), or a zip archive for some of the host tools.
+type PackagingSpec struct {
+	// Path relative to the root of the package
+	relPathInPackage string
+
+	// The path to the built artifact
+	srcPath Path
+
+	// If this is not empty, then relPathInPackage should be a symlink to this target. (Then
+	// srcPath is of course ignored.)
+	symlinkTarget string
+
+	// Whether relPathInPackage should be marked as executable or not
+	executable bool
+}
+
+type PackageModule interface {
+	Module
+	packagingBase() *PackagingBase
+
+	// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
+	AddDeps(ctx BottomUpMutatorContext)
+
+	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
+	// returns zip entries in it.  This is expected to be called in GenerateAndroidBuildActions,
+	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
+	// etc.) from the extracted files
+	CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string
+}
+
+// PackagingBase provides basic functionality for packaging dependencies. A module is expected to
+// include this struct and call InitPackageModule.
+type PackagingBase struct {
+	properties PackagingProperties
+
+	// Allows this module to skip missing dependencies. In most cases, this
+	// is not required, but for rare cases like when there's a dependency
+	// to a module which exists in certain repo checkouts, this is needed.
+	IgnoreMissingDependencies bool
+}
+
+type depsProperty struct {
+	// Modules to include in this package
+	Deps []string `android:"arch_variant"`
+}
+
+type packagingMultilibProperties struct {
+	First  depsProperty `android:"arch_variant"`
+	Common depsProperty `android:"arch_variant"`
+	Lib32  depsProperty `android:"arch_variant"`
+	Lib64  depsProperty `android:"arch_variant"`
+}
+
+type PackagingProperties struct {
+	Deps     []string                    `android:"arch_variant"`
+	Multilib packagingMultilibProperties `android:"arch_variant"`
+}
+
+type packagingDependencyTag struct{ blueprint.BaseDependencyTag }
+
+var depTag = packagingDependencyTag{}
+
+func InitPackageModule(p PackageModule) {
+	base := p.packagingBase()
+	p.AddProperties(&base.properties)
+}
+
+func (p *PackagingBase) packagingBase() *PackagingBase {
+	return p
+}
+
+// From deps and multilib.*.deps, select the dependencies that are for the given arch
+// deps is for the current archicture when this module is not configured for multi target.
+// When configured for multi target, deps is selected for each of the targets and is NOT
+// selected for the current architecture which would be Common.
+func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
+	var ret []string
+	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
+		ret = append(ret, p.properties.Deps...)
+	} else if arch.Multilib == "lib32" {
+		ret = append(ret, p.properties.Multilib.Lib32.Deps...)
+	} else if arch.Multilib == "lib64" {
+		ret = append(ret, p.properties.Multilib.Lib64.Deps...)
+	} else if arch == Common {
+		ret = append(ret, p.properties.Multilib.Common.Deps...)
+	}
+	for i, t := range ctx.MultiTargets() {
+		if t.Arch.ArchType == arch {
+			ret = append(ret, p.properties.Deps...)
+			if i == 0 {
+				ret = append(ret, p.properties.Multilib.First.Deps...)
+			}
+		}
+	}
+	return FirstUniqueStrings(ret)
+}
+
+func (p *PackagingBase) getSupportedTargets(ctx BaseModuleContext) []Target {
+	var ret []Target
+	// The current and the common OS targets are always supported
+	ret = append(ret, ctx.Target())
+	if ctx.Arch().ArchType != Common {
+		ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}})
+	}
+	// If this module is configured for multi targets, those should be supported as well
+	ret = append(ret, ctx.MultiTargets()...)
+	return ret
+}
+
+// See PackageModule.AddDeps
+func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext) {
+	for _, t := range p.getSupportedTargets(ctx) {
+		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
+			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
+				continue
+			}
+			ctx.AddFarVariationDependencies(t.Variations(), depTag, dep)
+		}
+	}
+}
+
+// See PackageModule.CopyDepsToZip
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
+	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) {
+			return false
+		}
+		for _, ps := range child.PackagingSpecs() {
+			if _, ok := m[ps.relPathInPackage]; !ok {
+				m[ps.relPathInPackage] = ps
+			}
+		}
+		return true
+	})
+
+	builder := NewRuleBuilder()
+
+	dir := PathForModuleOut(ctx, ".zip").OutputPath
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
+
+	seenDir := make(map[string]bool)
+	for _, k := range SortedStringKeys(m) {
+		ps := m[k]
+		destPath := dir.Join(ctx, ps.relPathInPackage).String()
+		destDir := filepath.Dir(destPath)
+		entries = append(entries, ps.relPathInPackage)
+		if _, ok := seenDir[destDir]; !ok {
+			seenDir[destDir] = true
+			builder.Command().Text("mkdir").Flag("-p").Text(destDir)
+		}
+		if ps.symlinkTarget == "" {
+			builder.Command().Text("cp").Input(ps.srcPath).Text(destPath)
+		} else {
+			builder.Command().Text("ln").Flag("-sf").Text(ps.symlinkTarget).Text(destPath)
+		}
+		if ps.executable {
+			builder.Command().Text("chmod").Flag("a+x").Text(destPath)
+		}
+	}
+
+	builder.Command().
+		BuiltTool(ctx, "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()))
+	return entries
+}
diff --git a/android/packaging_test.go b/android/packaging_test.go
new file mode 100644
index 0000000..7710c7f
--- /dev/null
+++ b/android/packaging_test.go
@@ -0,0 +1,188 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"reflect"
+	"testing"
+)
+
+// Module to be packaged
+type componentTestModule struct {
+	ModuleBase
+	props struct {
+		Deps []string
+	}
+}
+
+func componentTestModuleFactory() Module {
+	m := &componentTestModule{}
+	m.AddProperties(&m.props)
+	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibBoth)
+	return m
+}
+
+func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+}
+
+func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	builtFile := PathForModuleOut(ctx, m.Name())
+	dir := ctx.Target().Arch.ArchType.Multilib
+	installDir := PathForModuleInstall(ctx, dir)
+	ctx.InstallFile(installDir, m.Name(), builtFile)
+}
+
+// Module that itself is a package
+type packageTestModule struct {
+	ModuleBase
+	PackagingBase
+
+	entries []string
+}
+
+func packageTestModuleFactory() Module {
+	module := &packageTestModule{}
+	InitPackageModule(module)
+	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
+
+func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
+	m.AddDeps(ctx)
+}
+
+func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	zipFile := PathForModuleOut(ctx, "myzip.zip").OutputPath
+	m.entries = m.CopyDepsToZip(ctx, zipFile)
+}
+
+func runPackagingTest(t *testing.T, bp string, expected []string) {
+	t.Helper()
+
+	config := TestArchConfig(buildDir, nil, bp, nil)
+
+	ctx := NewTestArchContext(config)
+	ctx.RegisterModuleType("component", componentTestModuleFactory)
+	ctx.RegisterModuleType("package_module", packageTestModuleFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	p := ctx.ModuleForTests("package", "android_common").Module().(*packageTestModule)
+	actual := p.entries
+	actual = SortedUniqueStrings(actual)
+	expected = SortedUniqueStrings(expected)
+	if !reflect.DeepEqual(actual, expected) {
+		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
+	}
+}
+
+func TestPackagingBase(t *testing.T) {
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+			compile_multilib: "32",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				lib32: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})
+
+	runPackagingTest(t,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				first: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
+}
diff --git a/android/path_properties.go b/android/path_properties.go
index 6b1cdb3..4bb706a 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -21,27 +21,31 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// This file implements support for automatically adding dependencies on any module referenced
+// with the ":module" module reference syntax in a property that is annotated with `android:"path"`.
+// The dependency is used by android.PathForModuleSrc to convert the module reference into the path
+// to the output file of the referenced module.
+
 func registerPathDepsMutator(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("pathdeps", pathDepsMutator).Parallel()
 }
 
-// The pathDepsMutator automatically adds dependencies on any module that is listed with ":module" syntax in a
-// property that is tagged with android:"path".
+// The pathDepsMutator automatically adds dependencies on any module that is listed with the
+// ":module" module reference syntax in a property that is tagged with `android:"path"`.
 func pathDepsMutator(ctx BottomUpMutatorContext) {
-	m := ctx.Module().(Module)
-	if m == nil {
-		return
-	}
+	props := ctx.Module().base().generalProperties
 
-	props := m.base().generalProperties
-
+	// Iterate through each property struct of the module extracting the contents of all properties
+	// tagged with `android:"path"`.
 	var pathProperties []string
 	for _, ps := range props {
-		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ctx, ps)...)
+		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ps)...)
 	}
 
+	// Remove duplicates to avoid multiple dependencies.
 	pathProperties = FirstUniqueStrings(pathProperties)
 
+	// Add dependencies to anything that is a module reference.
 	for _, s := range pathProperties {
 		if m, t := SrcIsModuleWithTag(s); m != "" {
 			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
@@ -49,34 +53,45 @@
 	}
 }
 
-// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with android:"path" to extract
-// all their values from a property struct, returning them as a single slice of strings..
-func pathPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}) []string {
+// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with
+// android:"path" to extract all their values from a property struct, returning them as a single
+// slice of strings.
+func pathPropertiesForPropertyStruct(ps interface{}) []string {
 	v := reflect.ValueOf(ps)
 	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
 		panic(fmt.Errorf("type %s is not a pointer to a struct", v.Type()))
 	}
+
+	// If the property struct is a nil pointer it can't have any paths set in it.
 	if v.IsNil() {
 		return nil
 	}
+
+	// v is now the reflect.Value for the concrete property struct.
 	v = v.Elem()
 
+	// Get or create the list of indexes of properties that are tagged with `android:"path"`.
 	pathPropertyIndexes := pathPropertyIndexesForPropertyStruct(ps)
 
 	var ret []string
 
 	for _, i := range pathPropertyIndexes {
+		// Turn an index into a field.
 		sv := fieldByIndex(v, i)
 		if !sv.IsValid() {
+			// Skip properties inside a nil pointer.
 			continue
 		}
 
+		// If the field is a non-nil pointer step into it.
 		if sv.Kind() == reflect.Ptr {
 			if sv.IsNil() {
 				continue
 			}
 			sv = sv.Elem()
 		}
+
+		// Collect paths from all strings and slices of strings.
 		switch sv.Kind() {
 		case reflect.String:
 			ret = append(ret, sv.String())
@@ -91,8 +106,8 @@
 	return ret
 }
 
-// fieldByIndex is like reflect.Value.FieldByIndex, but returns an invalid reflect.Value when traversing a nil pointer
-// to a struct.
+// fieldByIndex is like reflect.Value.FieldByIndex, but returns an invalid reflect.Value when
+// traversing a nil pointer to a struct.
 func fieldByIndex(v reflect.Value, index []int) reflect.Value {
 	if len(index) == 1 {
 		return v.Field(index[0])
@@ -111,9 +126,9 @@
 
 var pathPropertyIndexesCache OncePer
 
-// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in property struct type that
-// are tagged with android:"path".  Each index is a []int suitable for passing to reflect.Value.FieldByIndex.  The value
-// is cached in a global cache by type.
+// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in
+// property struct type that are tagged with `android:"path"`.  Each index is a []int suitable for
+// passing to reflect.Value.FieldByIndex.  The value is cached in a global cache by type.
 func pathPropertyIndexesForPropertyStruct(ps interface{}) [][]int {
 	key := NewCustomOnceKey(reflect.TypeOf(ps))
 	return pathPropertyIndexesCache.Once(key, func() interface{} {
diff --git a/android/paths.go b/android/paths.go
index b13979d..b7117a3 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -665,7 +665,7 @@
 	return Paths(ret)
 }
 
-// WritablePaths is a slice of WritablePaths, used for multiple outputs.
+// WritablePaths is a slice of WritablePath, used for multiple outputs.
 type WritablePaths []WritablePath
 
 // Strings returns the string forms of the writable paths.
@@ -1312,7 +1312,7 @@
 
 	ret := pathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
 
-	if ctx.InstallBypassMake() && ctx.Config().EmbeddedInMake() {
+	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
 		ret = ret.ToMakePath()
 	}
 
diff --git a/android/phony.go b/android/phony.go
index f8e5a44..0adbb55 100644
--- a/android/phony.go
+++ b/android/phony.go
@@ -53,7 +53,7 @@
 		p.phonyMap[phony] = SortedUniquePaths(p.phonyMap[phony])
 	}
 
-	if !ctx.Config().EmbeddedInMake() {
+	if !ctx.Config().KatiEnabled() {
 		for _, phony := range p.phonyList {
 			ctx.Build(pctx, BuildParams{
 				Rule:      blueprint.Phony,
diff --git a/android/register.go b/android/register.go
index bd824c9..08e47b3 100644
--- a/android/register.go
+++ b/android/register.go
@@ -29,7 +29,7 @@
 
 type singleton struct {
 	name    string
-	factory blueprint.SingletonFactory
+	factory SingletonFactory
 }
 
 var singletons []singleton
@@ -57,11 +57,11 @@
 
 // SingletonFactoryAdaptor wraps a SingletonFactory into a blueprint.SingletonFactory by converting
 // a Singleton into a blueprint.Singleton
-func SingletonFactoryAdaptor(factory SingletonFactory) blueprint.SingletonFactory {
+func SingletonFactoryAdaptor(ctx *Context, factory SingletonFactory) blueprint.SingletonFactory {
 	return func() blueprint.Singleton {
 		singleton := factory()
 		if makevars, ok := singleton.(SingletonMakeVarsProvider); ok {
-			registerSingletonMakeVarsProvider(makevars)
+			registerSingletonMakeVarsProvider(ctx.config, makevars)
 		}
 		return &singletonAdaptor{Singleton: singleton}
 	}
@@ -72,11 +72,11 @@
 }
 
 func RegisterSingletonType(name string, factory SingletonFactory) {
-	singletons = append(singletons, singleton{name, SingletonFactoryAdaptor(factory)})
+	singletons = append(singletons, singleton{name, factory})
 }
 
 func RegisterPreSingletonType(name string, factory SingletonFactory) {
-	preSingletons = append(preSingletons, singleton{name, SingletonFactoryAdaptor(factory)})
+	preSingletons = append(preSingletons, singleton{name, factory})
 }
 
 type Context struct {
@@ -92,7 +92,7 @@
 
 func (ctx *Context) Register() {
 	for _, t := range preSingletons {
-		ctx.RegisterPreSingletonType(t.name, t.factory)
+		ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
 	}
 
 	for _, t := range moduleTypes {
@@ -100,21 +100,23 @@
 	}
 
 	for _, t := range singletons {
-		ctx.RegisterSingletonType(t.name, t.factory)
+		ctx.RegisterSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
 	}
 
 	registerMutators(ctx.Context, preArch, preDeps, postDeps, finalDeps)
 
-	ctx.RegisterSingletonType("bazeldeps", SingletonFactoryAdaptor(BazelSingleton))
+	ctx.RegisterSingletonType("bazeldeps", SingletonFactoryAdaptor(ctx, BazelSingleton))
 
 	// Register phony just before makevars so it can write out its phony rules as Make rules
-	ctx.RegisterSingletonType("phony", SingletonFactoryAdaptor(phonySingletonFactory))
+	ctx.RegisterSingletonType("phony", SingletonFactoryAdaptor(ctx, phonySingletonFactory))
 
 	// Register makevars after other singletons so they can export values through makevars
-	ctx.RegisterSingletonType("makevars", SingletonFactoryAdaptor(makeVarsSingletonFunc))
+	ctx.RegisterSingletonType("makevars", SingletonFactoryAdaptor(ctx, makeVarsSingletonFunc))
 
-	// Register env last so that it can track all used environment variables
-	ctx.RegisterSingletonType("env", SingletonFactoryAdaptor(EnvSingleton))
+	// Register env and ninjadeps last so that they can track all used environment variables and
+	// Ninja file dependencies stored in the config.
+	ctx.RegisterSingletonType("env", SingletonFactoryAdaptor(ctx, EnvSingleton))
+	ctx.RegisterSingletonType("ninjadeps", SingletonFactoryAdaptor(ctx, ninjaDepsSingletonFactory))
 }
 
 func ModuleTypeFactories() map[string]ModuleFactory {
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 8dc9d6a..3efe9f8 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -15,28 +15,38 @@
 package android
 
 import (
+	"crypto/sha256"
 	"fmt"
+	"path/filepath"
 	"sort"
 	"strings"
+	"testing"
 
+	"github.com/golang/protobuf/proto"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
+	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/shared"
 )
 
+const sboxSandboxBaseDir = "__SBOX_SANDBOX_DIR__"
+const sboxOutSubDir = "out"
+const sboxOutDir = sboxSandboxBaseDir + "/" + sboxOutSubDir
+
 // RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
 // graph.
 type RuleBuilder struct {
-	commands       []*RuleBuilderCommand
-	installs       RuleBuilderInstalls
-	temporariesSet map[WritablePath]bool
-	restat         bool
-	sbox           bool
-	highmem        bool
-	remoteable     RemoteRuleSupports
-	sboxOutDir     WritablePath
-	missingDeps    []string
+	commands         []*RuleBuilderCommand
+	installs         RuleBuilderInstalls
+	temporariesSet   map[WritablePath]bool
+	restat           bool
+	sbox             bool
+	highmem          bool
+	remoteable       RemoteRuleSupports
+	sboxOutDir       WritablePath
+	sboxManifestPath WritablePath
+	missingDeps      []string
 }
 
 // NewRuleBuilder returns a newly created RuleBuilder.
@@ -102,12 +112,14 @@
 	return r
 }
 
-// Sbox marks the rule as needing to be wrapped by sbox. The WritablePath should point to the output
-// directory that sbox will wipe. It should not be written to by any other rule. sbox will ensure
-// that all outputs have been written, and will discard any output files that were not specified.
+// Sbox marks the rule as needing to be wrapped by sbox. The outputDir should point to the output
+// directory that sbox will wipe. It should not be written to by any other rule. manifestPath should
+// point to a location where sbox's manifest will be written and must be outside outputDir. sbox
+// will ensure that all outputs have been written, and will discard any output files that were not
+// specified.
 //
 // Sbox is not compatible with Restat()
-func (r *RuleBuilder) Sbox(outputDir WritablePath) *RuleBuilder {
+func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
 	if r.sbox {
 		panic("Sbox() may not be called more than once")
 	}
@@ -119,6 +131,7 @@
 	}
 	r.sbox = true
 	r.sboxOutDir = outputDir
+	r.sboxManifestPath = manifestPath
 	return r
 }
 
@@ -133,8 +146,8 @@
 // race with any call to Build.
 func (r *RuleBuilder) Command() *RuleBuilderCommand {
 	command := &RuleBuilderCommand{
-		sbox:       r.sbox,
-		sboxOutDir: r.sboxOutDir,
+		sbox:   r.sbox,
+		outDir: r.sboxOutDir,
 	}
 	r.commands = append(r.commands, command)
 	return command
@@ -163,7 +176,7 @@
 }
 
 // Inputs returns the list of paths that were passed to the RuleBuilderCommand methods that take
-// input paths, such as RuleBuilderCommand.Input, RuleBuilderComand.Implicit, or
+// input paths, such as RuleBuilderCommand.Input, RuleBuilderCommand.Implicit, or
 // RuleBuilderCommand.FlagWithInput.  Inputs to a command that are also outputs of another command
 // in the same RuleBuilder are filtered out.  The list is sorted and duplicates removed.
 func (r *RuleBuilder) Inputs() Paths {
@@ -362,7 +375,7 @@
 	return commands
 }
 
-// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to
+// NinjaEscapedCommands returns a slice containing the built command line after ninja escaping for each call to
 // RuleBuilder.Command.
 func (r *RuleBuilder) NinjaEscapedCommands() []string {
 	var commands []string
@@ -416,7 +429,8 @@
 			r.depFileMergerCmd(ctx, depFiles)
 
 			if r.sbox {
-				// Check for Rel() errors, as all depfiles should be in the output dir
+				// Check for Rel() errors, as all depfiles should be in the output dir.  Errors
+				// will be reported to the ctx.
 				for _, path := range depFiles[1:] {
 					Rel(ctx, r.sboxOutDir.String(), path.String())
 				}
@@ -427,6 +441,7 @@
 	tools := r.Tools()
 	commands := r.NinjaEscapedCommands()
 	outputs := r.Outputs()
+	inputs := r.Inputs()
 
 	if len(commands) == 0 {
 		return
@@ -438,30 +453,65 @@
 	commandString := strings.Join(commands, " && ")
 
 	if r.sbox {
-		sboxOutputs := make([]string, len(outputs))
-		for i, output := range outputs {
-			sboxOutputs[i] = "__SBOX_OUT_DIR__/" + Rel(ctx, r.sboxOutDir.String(), output.String())
-		}
-
-		commandString = proptools.ShellEscape(commandString)
-		if !strings.HasPrefix(commandString, `'`) {
-			commandString = `'` + commandString + `'`
-		}
-
-		sboxCmd := &RuleBuilderCommand{}
-		sboxCmd.BuiltTool(ctx, "sbox").
-			Flag("-c").Text(commandString).
-			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
-			Flag("--output-root").Text(r.sboxOutDir.String())
+		// If running the command inside sbox, write the rule data out to an sbox
+		// manifest.textproto.
+		manifest := sbox_proto.Manifest{}
+		command := sbox_proto.Command{}
+		manifest.Commands = append(manifest.Commands, &command)
+		command.Command = proto.String(commandString)
 
 		if depFile != nil {
-			sboxCmd.Flag("--depfile-out").Text(depFile.String())
+			manifest.OutputDepfile = proto.String(depFile.String())
 		}
 
-		sboxCmd.Flags(sboxOutputs)
+		// Add copy rules to the manifest to copy each output file from the sbox directory.
+		// to the output directory.
+		sboxOutputs := make([]string, len(outputs))
+		for i, output := range outputs {
+			rel := Rel(ctx, r.sboxOutDir.String(), output.String())
+			sboxOutputs[i] = filepath.Join(sboxOutDir, rel)
+			command.CopyAfter = append(command.CopyAfter, &sbox_proto.Copy{
+				From: proto.String(filepath.Join(sboxOutSubDir, rel)),
+				To:   proto.String(output.String()),
+			})
+		}
 
+		// Add a hash of the list of input files to the manifest so that the textproto file
+		// changes when the list of input files changes and causes the sbox rule that
+		// depends on it to rerun.
+		command.InputHash = proto.String(hashSrcFiles(inputs))
+
+		// Verify that the manifest textproto is not inside the sbox output directory, otherwise
+		// it will get deleted when the sbox rule clears its output directory.
+		_, manifestInOutDir := MaybeRel(ctx, r.sboxOutDir.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())
+		}
+
+		// Create a rule to write the manifest as a the textproto.
+		WriteFileRule(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.Text("&&")
+		sboxCmd.BuiltTool(ctx, "sbox").
+			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
+			Flag("--manifest").Input(r.sboxManifestPath)
+
+		// Replace the command string, and add the sbox tool and manifest textproto to the
+		// dependencies of the final sbox rule.
 		commandString = sboxCmd.buf.String()
 		tools = append(tools, sboxCmd.tools...)
+		inputs = append(inputs, sboxCmd.inputs...)
+	} else {
+		// If not using sbox the rule will run the command directly, put the hash of the
+		// list of input files in a comment at the end of the command line to ensure ninja
+		// reruns the rule when the list of input files changes.
+		commandString += " # hash of input list: " + hashSrcFiles(inputs)
 	}
 
 	// Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
@@ -499,7 +549,7 @@
 			Pool:           pool,
 		}),
 		Inputs:          rspFileInputs,
-		Implicits:       r.Inputs(),
+		Implicits:       inputs,
 		Output:          output,
 		ImplicitOutputs: implicitOutputs,
 		SymlinkOutputs:  r.SymlinkOutputs(),
@@ -527,14 +577,16 @@
 	// spans [start,end) of the command that should not be ninja escaped
 	unescapedSpans [][2]int
 
-	sbox       bool
-	sboxOutDir WritablePath
+	sbox bool
+	// outDir is the directory that will contain the output files of the rules.  sbox will copy
+	// the output files from the sandbox directory to this directory when it finishes.
+	outDir WritablePath
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
 	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
-			return "__SBOX_OUT_DIR__/" + rel
+		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.inputs = append(c.inputs, path)
@@ -543,8 +595,8 @@
 
 func (c *RuleBuilderCommand) addImplicit(path Path) string {
 	if c.sbox {
-		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
-			return "__SBOX_OUT_DIR__/" + rel
+		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
+			return filepath.Join(sboxOutDir, rel)
 		}
 	}
 	c.implicits = append(c.implicits, path)
@@ -555,15 +607,22 @@
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
-func (c *RuleBuilderCommand) outputStr(path Path) string {
+func (c *RuleBuilderCommand) outputStr(path WritablePath) string {
 	if c.sbox {
-		// Errors will be handled in RuleBuilder.Build where we have a context to report them
-		rel, _, _ := maybeRelErr(c.sboxOutDir.String(), path.String())
-		return "__SBOX_OUT_DIR__/" + rel
+		return SboxPathForOutput(path, c.outDir)
 	}
 	return path.String()
 }
 
+// SboxPathForOutput takes an output path and the out directory passed to RuleBuilder.Sbox(),
+// and returns the corresponding path for the output in the sbox sandbox.  This can be used
+// on the RuleBuilder command line to reference the output.
+func SboxPathForOutput(path WritablePath, outDir WritablePath) string {
+	// Errors will be handled in RuleBuilder.Build where we have a context to report them
+	rel, _, _ := maybeRelErr(outDir.String(), path.String())
+	return filepath.Join(sboxOutDir, rel)
+}
+
 // Text adds the specified raw text to the command line.  The text should not contain input or output paths or the
 // rule will not have them listed in its dependencies or outputs.
 func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
@@ -727,7 +786,7 @@
 	if !c.sbox {
 		panic("OutputDir only valid with Sbox")
 	}
-	return c.Text("__SBOX_OUT_DIR__")
+	return c.Text(sboxOutDir)
 }
 
 // DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
@@ -867,6 +926,19 @@
 	return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
 }
 
+// RuleBuilderSboxProtoForTests takes the BuildParams for the manifest passed to RuleBuilder.Sbox()
+// and returns sbox testproto generated by the RuleBuilder.
+func RuleBuilderSboxProtoForTests(t *testing.T, params TestingBuildParams) *sbox_proto.Manifest {
+	t.Helper()
+	content := ContentFromFileRuleForTests(t, params)
+	manifest := sbox_proto.Manifest{}
+	err := proto.UnmarshalText(content, &manifest)
+	if err != nil {
+		t.Fatalf("failed to unmarshal manifest: %s", err.Error())
+	}
+	return &manifest
+}
+
 func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
 	if len(spans) == 0 {
 		return proptools.NinjaEscape(s)
@@ -906,3 +978,12 @@
 	}
 	return s
 }
+
+// hashSrcFiles returns a hash of the list of source files.  It is used to ensure the command line
+// or the sbox textproto manifest change even if the input files are not listed on the command line.
+func hashSrcFiles(srcFiles Paths) string {
+	h := sha256.New()
+	srcFileList := strings.Join(srcFiles.Strings(), "\n")
+	h.Write([]byte(srcFileList))
+	return fmt.Sprintf("%x", h.Sum(nil))
+}
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index ca6359d..dc360c3 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -394,16 +395,17 @@
 	})
 
 	t.Run("sbox", func(t *testing.T) {
-		rule := NewRuleBuilder().Sbox(PathForOutput(ctx))
+		rule := NewRuleBuilder().Sbox(PathForOutput(ctx, ""),
+			PathForOutput(ctx, "sbox.textproto"))
 		addCommands(rule)
 
 		wantCommands := []string{
-			"__SBOX_OUT_DIR__/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_OUT_DIR__/depfile FlagWithInput=input FlagWithOutput=__SBOX_OUT_DIR__/output Input __SBOX_OUT_DIR__/Output __SBOX_OUT_DIR__/SymlinkOutput Text Tool after command2 old cmd",
-			"command2 __SBOX_OUT_DIR__/depfile2 input2 __SBOX_OUT_DIR__/output2 tool2",
-			"command3 input3 __SBOX_OUT_DIR__/output2 __SBOX_OUT_DIR__/output3",
+			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output Input __SBOX_SANDBOX_DIR__/out/Output __SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
+			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
+			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3",
 		}
 
-		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_OUT_DIR__/DepFile __SBOX_OUT_DIR__/depfile __SBOX_OUT_DIR__/ImplicitDepFile __SBOX_OUT_DIR__/depfile2"
+		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
 			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
@@ -441,7 +443,7 @@
 type testRuleBuilderModule struct {
 	ModuleBase
 	properties struct {
-		Src string
+		Srcs []string
 
 		Restat bool
 		Sbox   bool
@@ -449,12 +451,13 @@
 }
 
 func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	in := PathForSource(ctx, t.properties.Src)
-	out := PathForModuleOut(ctx, ctx.ModuleName())
-	outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
-	outDir := PathForModuleOut(ctx)
+	in := PathsForSource(ctx, t.properties.Srcs)
+	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
+	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
+	outDir := PathForModuleOut(ctx, "gen")
+	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
 
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, t.properties.Restat, t.properties.Sbox)
+	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
 }
 
 type testRuleBuilderSingleton struct{}
@@ -465,20 +468,21 @@
 
 func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
 	in := PathForSource(ctx, "bar")
-	out := PathForOutput(ctx, "baz")
-	outDep := PathForOutput(ctx, "baz.d")
-	outDir := PathForOutput(ctx)
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, true, false)
+	out := PathForOutput(ctx, "singleton/gen/baz")
+	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
+	outDir := PathForOutput(ctx, "singleton/gen")
+	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
+	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir WritablePath, restat, sbox bool) {
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
 	rule := NewRuleBuilder()
 
 	if sbox {
-		rule.Sbox(outDir)
+		rule.Sbox(outDir, manifestPath)
 	}
 
-	rule.Command().Tool(PathForSource(ctx, "cp")).Input(in).Output(out).ImplicitDepFile(outDep)
+	rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
 
 	if restat {
 		rule.Restat()
@@ -496,12 +500,12 @@
 	bp := `
 		rule_builder_test {
 			name: "foo",
-			src: "bar",
+			srcs: ["bar"],
 			restat: true,
 		}
 		rule_builder_test {
 			name: "foo_sbox",
-			src: "bar",
+			srcs: ["bar"],
 			sbox: true,
 		}
 	`
@@ -517,9 +521,12 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	FailIfErrored(t, errs)
 
-	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
+	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
 		t.Helper()
-		if params.RuleParams.Command != wantCommand {
+		command := params.RuleParams.Command
+		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
+		command = re.ReplaceAllLiteralString(command, "")
+		if command != wantCommand {
 			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
 		}
 
@@ -532,7 +539,8 @@
 			t.Errorf("want RuleParams.Restat = %v, got %v", wantRestat, params.RuleParams.Restat)
 		}
 
-		if len(params.Implicits) != 1 || params.Implicits[0].String() != "bar" {
+		wantImplicits := append([]string{"bar"}, extraImplicits...)
+		if !reflect.DeepEqual(params.Implicits.Strings(), wantImplicits) {
 			t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
 		}
 
@@ -554,27 +562,29 @@
 	}
 
 	t.Run("module", func(t *testing.T) {
-		outFile := filepath.Join(buildDir, ".intermediates", "foo", "foo")
+		outFile := filepath.Join(buildDir, ".intermediates", "foo", "gen", "foo")
 		check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
 			"cp bar "+outFile,
-			outFile, outFile+".d", true, nil)
+			outFile, outFile+".d", true, nil, nil)
 	})
 	t.Run("sbox", func(t *testing.T) {
 		outDir := filepath.Join(buildDir, ".intermediates", "foo_sbox")
-		outFile := filepath.Join(outDir, "foo_sbox")
-		depFile := filepath.Join(outDir, "foo_sbox.d")
+		outFile := filepath.Join(outDir, "gen/foo_sbox")
+		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join(buildDir, "host", config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir(buildDir)
 
-		cmd := sbox + ` -c 'cp bar __SBOX_OUT_DIR__/foo_sbox' --sandbox-path ` + sandboxPath + " --output-root " + outDir + " --depfile-out " + depFile + " __SBOX_OUT_DIR__/foo_sbox"
+		cmd := `rm -rf ` + outDir + `/gen && ` +
+			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
 
-		check(t, ctx.ModuleForTests("foo_sbox", "").Rule("rule"),
-			cmd, outFile, depFile, false, []string{sbox})
+		check(t, ctx.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
+			cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("singleton", func(t *testing.T) {
-		outFile := filepath.Join(buildDir, "baz")
+		outFile := filepath.Join(buildDir, "singleton/gen/baz")
 		check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
-			"cp bar "+outFile, outFile, outFile+".d", true, nil)
+			"cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
 	})
 }
 
@@ -651,3 +661,80 @@
 		})
 	}
 }
+
+func TestRuleBuilderHashInputs(t *testing.T) {
+	// The basic idea here is to verify that the command (in the case of a
+	// non-sbox rule) or the sbox textproto manifest contain a hash of the
+	// inputs.
+
+	// By including a hash of the inputs, we cause the rule to re-run if
+	// the list of inputs changes because the command line or a dependency
+	// changes.
+
+	bp := `
+			rule_builder_test {
+				name: "hash0",
+				srcs: ["in1.txt", "in2.txt"],
+			}
+			rule_builder_test {
+				name: "hash0_sbox",
+				srcs: ["in1.txt", "in2.txt"],
+				sbox: true,
+			}
+			rule_builder_test {
+				name: "hash1",
+				srcs: ["in1.txt", "in2.txt", "in3.txt"],
+			}
+			rule_builder_test {
+				name: "hash1_sbox",
+				srcs: ["in1.txt", "in2.txt", "in3.txt"],
+				sbox: true,
+			}
+		`
+	testcases := []struct {
+		name         string
+		expectedHash string
+	}{
+		{
+			name: "hash0",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
+			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
+		},
+		{
+			name: "hash1",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
+		},
+	}
+
+	config := TestConfig(buildDir, nil, bp, nil)
+	ctx := NewTestContext(config)
+	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
+	ctx.Register()
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	FailIfErrored(t, errs)
+
+	for _, test := range testcases {
+		t.Run(test.name, func(t *testing.T) {
+			t.Run("sbox", func(t *testing.T) {
+				gen := ctx.ModuleForTests(test.name+"_sbox", "")
+				manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto"))
+				hash := manifest.Commands[0].GetInputHash()
+
+				if g, w := hash, test.expectedHash; g != w {
+					t.Errorf("Expected has %q, got %q", w, g)
+				}
+			})
+			t.Run("", func(t *testing.T) {
+				gen := ctx.ModuleForTests(test.name+"", "")
+				command := gen.Output("gen/" + test.name).RuleParams.Command
+				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
+					t.Errorf("Expected command line to end with %q, got %q", w, g)
+				}
+			})
+		})
+	}
+}
diff --git a/android/testing.go b/android/testing.go
index d83cecc..6539063 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -104,7 +104,7 @@
 }
 
 func (ctx *TestContext) RegisterSingletonType(name string, factory SingletonFactory) {
-	ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(factory))
+	ctx.Context.RegisterSingletonType(name, SingletonFactoryAdaptor(ctx.Context, factory))
 }
 
 func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
@@ -424,8 +424,8 @@
 
 }
 
-func SetInMakeForTests(config Config) {
-	config.inMake = true
+func SetKatiEnabledForTests(config Config) {
+	config.katiEnabled = true
 }
 
 func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) []AndroidMkEntries {
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 739a965..4540a1f 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -214,6 +214,8 @@
 
 			"LOCAL_PRIVATE_PLATFORM_APIS": "platform_apis",
 			"LOCAL_JETIFIER_ENABLED":      "jetifier",
+
+			"LOCAL_IS_UNIT_TEST": "unit_test",
 		})
 }
 
diff --git a/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index 03cf74d..f4e5fa0 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -137,7 +137,14 @@
 
 		switch x := node.(type) {
 		case *mkparser.Comment:
-			file.insertComment("//" + x.Comment)
+			// Split the comment on escaped newlines and then
+			// add each chunk separately.
+			chunks := strings.Split(x.Comment, "\\\n")
+			file.insertComment("//" + chunks[0])
+			for i := 1; i < len(chunks); i++ {
+				file.bpPos.Line++
+				file.insertComment("//" + chunks[i])
+			}
 		case *mkparser.Assignment:
 			handleAssignment(file, x, assignmentCond)
 		case *mkparser.Directive:
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 16cb138..f32ff2a 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -1260,10 +1260,10 @@
 		desc: "comment with ESC",
 		in: `
 # Comment line 1 \
-# Comment line 2
+ Comment line 2
 `,
 		expected: `
-// Comment line 1 \
+// Comment line 1
 // Comment line 2
 `,
 	},
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
index 86dabf9..c14910a 100644
--- a/androidmk/parser/parser.go
+++ b/androidmk/parser/parser.go
@@ -212,8 +212,21 @@
 	expression := SimpleMakeString("", pos)
 
 	switch d {
-	case "endif", "endef", "else":
+	case "endif", "endef":
 		// Nothing
+	case "else":
+		p.ignoreSpaces()
+		if p.tok != '\n' {
+			d = p.scanner.TokenText()
+			p.accept(scanner.Ident)
+			if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
+				d = "el" + d
+				p.ignoreSpaces()
+				expression = p.parseExpression()
+			} else {
+				p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
+			}
+		}
 	case "define":
 		expression, endPos = p.parseDefine()
 	default:
@@ -484,12 +497,6 @@
 		switch p.tok {
 		case '\\':
 			p.parseEscape()
-			if p.tok == '\n' {
-				// Special case: '\' does not "escape" newline in comment (b/127521510)
-				comment += "\\"
-				p.accept(p.tok)
-				break loop
-			}
 			comment += "\\" + p.scanner.TokenText()
 			p.accept(p.tok)
 		case '\n':
diff --git a/apex/Android.bp b/apex/Android.bp
index 1a5f683..9e8c30d 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,7 @@
         "soong-cc",
         "soong-java",
         "soong-python",
+        "soong-rust",
         "soong-sh",
     ],
     srcs: [
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 993260c..da38c2a 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -36,6 +36,33 @@
 	return a.androidMkForType()
 }
 
+// nameInMake converts apexFileClass into the corresponding class name in Make.
+func (class apexFileClass) nameInMake() string {
+	switch class {
+	case etc:
+		return "ETC"
+	case nativeSharedLib:
+		return "SHARED_LIBRARIES"
+	case nativeExecutable, shBinary, pyBinary, goBinary:
+		return "EXECUTABLES"
+	case javaSharedLib:
+		return "JAVA_LIBRARIES"
+	case nativeTest:
+		return "NATIVE_TESTS"
+	case app, appSet:
+		// b/142537672 Why isn't this APP? We want to have full control over
+		// the paths and file names of the apk file under the flattend APEX.
+		// If this is set to APP, then the paths and file names are modified
+		// by the Make build system. For example, it is installed to
+		// /system/apex/<apexname>/app/<Appname>/<apexname>.<Appname>/ instead of
+		// /system/apex/<apexname>/app/<Appname> because the build system automatically
+		// appends module name (which is <apexname>.<Appname> to the path.
+		return "ETC"
+	default:
+		panic(fmt.Errorf("unknown class %d", class))
+	}
+}
+
 func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, apexName, moduleDir string,
 	apexAndroidMkData android.AndroidMkData) []string {
 
@@ -68,10 +95,10 @@
 
 	var postInstallCommands []string
 	for _, fi := range a.filesInfo {
-		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
 			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			linkTarget := filepath.Join("/system", fi.Path())
-			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.Path())
+			linkTarget := filepath.Join("/system", fi.path())
+			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.path())
 			mkdirCmd := "mkdir -p " + filepath.Dir(linkPath)
 			linkCmd := "ln -sfn " + linkTarget + " " + linkPath
 			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
@@ -85,7 +112,7 @@
 			continue
 		}
 
-		linkToSystemLib := a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform()
+		linkToSystemLib := a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform()
 
 		var moduleName string
 		if linkToSystemLib {
@@ -151,7 +178,7 @@
 			fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
 		}
 		fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", fi.builtFile.String())
-		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.NameInMake())
+		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.nameInMake())
 		if fi.module != nil {
 			archStr := fi.module.Target().Arch.ArchType.String()
 			host := false
@@ -189,7 +216,7 @@
 			// soong_java_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .jar  Therefore
 			// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 			// we will have foo.jar.jar
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.Stem(), ".jar"))
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.stem(), ".jar"))
 			if javaModule, ok := fi.module.(java.ApexDependency); ok {
 				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", javaModule.ImplementationAndResourcesJars()[0].String())
 				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", javaModule.HeaderJars()[0].String())
@@ -205,7 +232,7 @@
 			// soong_app_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .apk  Therefore
 			// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 			// we will have foo.apk.apk
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.Stem(), ".apk"))
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.stem(), ".apk"))
 			if app, ok := fi.module.(*java.AndroidApp); ok {
 				if jniCoverageOutputs := app.JniCoverageOutputs(); len(jniCoverageOutputs) > 0 {
 					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", strings.Join(jniCoverageOutputs.Strings(), " "))
@@ -224,7 +251,7 @@
 			fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String())
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk")
 		case nativeSharedLib, nativeExecutable, nativeTest:
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.Stem())
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if ccMod, ok := fi.module.(*cc.Module); ok {
 				if ccMod.UnstrippedOutputFile() != nil {
 					fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", ccMod.UnstrippedOutputFile().String())
@@ -236,7 +263,7 @@
 			}
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_prebuilt.mk")
 		default:
-			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.Stem())
+			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if fi.builtFile == a.manifestPbOut && apexType == flattenedApex {
 				if a.primaryApexType {
 					// To install companion files (init_rc, vintf_fragments)
@@ -307,7 +334,6 @@
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
 	return android.AndroidMkData{
-		DistFiles: a.distFiles,
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
 			moduleNames := []string{}
 			apexType := a.properties.ApexType
diff --git a/apex/apex.go b/apex/apex.go
index 91770f4..f127757 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// package apex implements build rules for creating the APEX files which are container for
+// lower-level system components. See https://source.android.com/devices/tech/ota/apex
 package apex
 
 import (
@@ -30,14 +32,945 @@
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
 	"android/soong/python"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
+func init() {
+	android.RegisterModuleType("apex", BundleFactory)
+	android.RegisterModuleType("apex_test", testApexBundleFactory)
+	android.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
+	android.RegisterModuleType("apex_defaults", defaultsFactory)
+	android.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
+	android.RegisterModuleType("override_apex", overrideApexFactory)
+	android.RegisterModuleType("apex_set", apexSetFactory)
+
+	android.PreDepsMutators(RegisterPreDepsMutators)
+	android.PostDepsMutators(RegisterPostDepsMutators)
+}
+
+func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
+	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
+}
+
+func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.TopDown("apex_deps", apexDepsMutator).Parallel()
+	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
+	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
+	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
+	ctx.BottomUp("apex", apexMutator).Parallel()
+	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
+	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
+	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
+}
+
+type apexBundleProperties struct {
+	// Json manifest file describing meta info of this APEX bundle. Refer to
+	// system/apex/proto/apex_manifest.proto for the schema. Default: "apex_manifest.json"
+	Manifest *string `android:"path"`
+
+	// AndroidManifest.xml file used for the zip container of this APEX bundle. If unspecified,
+	// a default one is automatically generated.
+	AndroidManifest *string `android:"path"`
+
+	// Canonical name of this APEX bundle. Used to determine the path to the activated APEX on
+	// device (/apex/<apex_name>). If unspecified, follows the name property.
+	Apex_name *string
+
+	// Determines the file contexts file for setting the security contexts to files in this APEX
+	// bundle. For platform APEXes, this should points to a file under /system/sepolicy Default:
+	// /system/sepolicy/apex/<module_name>_file_contexts.
+	File_contexts *string `android:"path"`
+
+	ApexNativeDependencies
+
+	Multilib apexMultilibProperties
+
+	// List of java libraries that are embedded inside this APEX bundle.
+	Java_libs []string
+
+	// List of prebuilt files that are embedded inside this APEX bundle.
+	Prebuilts []string
+
+	// List of BPF programs inside this APEX bundle.
+	Bpfs []string
+
+	// Name of the apex_key module that provides the private key to sign this APEX bundle.
+	Key *string
+
+	// Specifies the certificate and the private key to sign the zip container of this APEX. If
+	// this is "foo", foo.x509.pem and foo.pk8 under PRODUCT_DEFAULT_DEV_CERTIFICATE are used
+	// as the certificate and the private key, respectively. If this is ":module", then the
+	// certificate and the private key are provided from the android_app_certificate module
+	// named "module".
+	Certificate *string
+
+	// The minimum SDK version that this APEX must support at minimum. This is usually set to
+	// the SDK version that the APEX was first introduced.
+	Min_sdk_version *string
+
+	// Whether this APEX is considered updatable or not. When set to true, this will enforce
+	// additional rules for making sure that the APEX is truly updatable. To be updatable,
+	// min_sdk_version should be set as well. This will also disable the size optimizations like
+	// symlinking to the system libs. Default is false.
+	Updatable *bool
+
+	// Whether this APEX is installable to one of the partitions like system, vendor, etc.
+	// Default: true.
+	Installable *bool
+
+	// For native libraries and binaries, use the vendor variant instead of the core (platform)
+	// variant. Default is false. DO NOT use this for APEXes that are installed to the system or
+	// system_ext partition.
+	Use_vendor *bool
+
+	// If set true, VNDK libs are considered as stable libs and are not included in this APEX.
+	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
+	Use_vndk_as_stable *bool
+
+	// List of SDKs that are used to build this APEX. A reference to an SDK should be either
+	// `name#version` or `name` which is an alias for `name#current`. If left empty,
+	// `platform#current` is implied. This value affects all modules included in this APEX. In
+	// other words, they are also built with the SDKs specified here.
+	Uses_sdks []string
+
+	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
+	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
+	// container. When set to zip, contents are stored in a zip container directly. This type is
+	// mostly for host-side debugging. When set to both, the two types are both built. Default
+	// is 'image'.
+	Payload_type *string
+
+	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4' or 'f2fs'.
+	// Default 'ext4'.
+	Payload_fs_type *string
+
+	// For telling the APEX to ignore special handling for system libraries such as bionic.
+	// Default is false.
+	Ignore_system_library_special_case *bool
+
+	// Whenever apex_payload.img of the APEX should include dm-verity hashtree. Should be only
+	// used in tests.
+	Test_only_no_hashtree *bool
+
+	// Whenever apex_payload.img of the APEX should not be dm-verity signed. Should be only
+	// used in tests.
+	Test_only_unsigned_payload *bool
+
+	IsCoverageVariant bool `blueprint:"mutated"`
+
+	// List of sanitizer names that this APEX is enabled for
+	SanitizerNames []string `blueprint:"mutated"`
+
+	PreventInstall bool `blueprint:"mutated"`
+
+	HideFromMake bool `blueprint:"mutated"`
+
+	// Internal package method for this APEX. When payload_type is image, this can be either
+	// imageApex or flattenedApex depending on Config.FlattenApex(). When payload_type is zip,
+	// this becomes zipApex.
+	ApexType apexPackaging `blueprint:"mutated"`
+}
+
+type ApexNativeDependencies struct {
+	// List of native libraries that are embedded inside this APEX.
+	Native_shared_libs []string
+
+	// List of JNI libraries that are embedded inside this APEX.
+	Jni_libs []string
+
+	// List of rust dyn libraries
+	Rust_dyn_libs []string
+
+	// List of native executables that are embedded inside this APEX.
+	Binaries []string
+
+	// List of native tests that are embedded inside this APEX.
+	Tests []string
+}
+
+type apexMultilibProperties struct {
+	// Native dependencies whose compile_multilib is "first"
+	First ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "both"
+	Both ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "prefer32"
+	Prefer32 ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "32"
+	Lib32 ApexNativeDependencies
+
+	// Native dependencies whose compile_multilib is "64"
+	Lib64 ApexNativeDependencies
+}
+
+type apexTargetBundleProperties struct {
+	Target struct {
+		// Multilib properties only for android.
+		Android struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host.
+		Host struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host linux_bionic.
+		Linux_bionic struct {
+			Multilib apexMultilibProperties
+		}
+
+		// Multilib properties only for host linux_glibc.
+		Linux_glibc struct {
+			Multilib apexMultilibProperties
+		}
+	}
+}
+
+// These properties can be used in override_apex to override the corresponding properties in the
+// base apex.
+type overridableProperties struct {
+	// List of APKs that are embedded inside this APEX.
+	Apps []string
+
+	// List of runtime resource overlays (RROs) that are embedded inside this APEX.
+	Rros []string
+
+	// Names of modules to be overridden. Listed modules can only be other binaries (in Make or
+	// Soong). This does not completely prevent installation of the overridden binaries, but if
+	// both binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will
+	// be removed from PRODUCT_PACKAGES.
+	Overrides []string
+
+	// Logging parent value.
+	Logging_parent string
+
+	// Apex Container package name. Override value for attribute package:name in
+	// AndroidManifest.xml
+	Package_name string
+
+	// A txt file containing list of files that are allowed to be included in this APEX.
+	Allowed_files *string `android:"path"`
+}
+
+type apexBundle struct {
+	// Inherited structs
+	android.ModuleBase
+	android.DefaultableModuleBase
+	android.OverridableModuleBase
+	android.SdkBase
+
+	// Properties
+	properties            apexBundleProperties
+	targetProperties      apexTargetBundleProperties
+	overridableProperties overridableProperties
+	vndkProperties        apexVndkProperties // only for apex_vndk modules
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Inputs
+
+	// Keys for apex_paylaod.img
+	public_key_file  android.Path
+	private_key_file android.Path
+
+	// Cert/priv-key for the zip container
+	container_certificate_file android.Path
+	container_private_key_file android.Path
+
+	// Flags for special variants of APEX
+	testApex bool
+	vndkApex bool
+	artApex  bool
+
+	// Tells whether this variant of the APEX bundle is the primary one or not. Only the primary
+	// one gets installed to the device.
+	primaryApexType bool
+
+	// Suffix of module name in Android.mk ".flattened", ".apex", ".zipapex", or ""
+	suffix string
+
+	// File system type of apex_payload.img
+	payloadFsType fsType
+
+	// Whether to create symlink to the system file instead of having a file inside the apex or
+	// not
+	linkToSystemLib bool
+
+	// List of files to be included in this APEX. This is filled in the first part of
+	// GenerateAndroidBuildActions.
+	filesInfo []apexFile
+
+	// List of other module names that should be installed when this APEX gets installed.
+	requiredDeps []string
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Outputs (final and intermediates)
+
+	// Processed apex manifest in JSONson format (for Q)
+	manifestJsonOut android.WritablePath
+
+	// Processed apex manifest in PB format (for R+)
+	manifestPbOut android.WritablePath
+
+	// Processed file_contexts files
+	fileContexts android.WritablePath
+
+	// Struct holding the merged notice file paths in different formats
+	mergedNotices android.NoticeOutputs
+
+	// The built APEX file. This is the main product.
+	outputFile android.WritablePath
+
+	// The built APEX file in app bundle format. This file is not directly installed to the
+	// device. For an APEX, multiple app bundles are created each of which is for a specific ABI
+	// like arm, arm64, x86, etc. Then they are processed again (outside of the Android build
+	// system) to be merged into a single app bundle file that Play accepts. See
+	// vendor/google/build/build_unbundled_mainline_module.sh for more detail.
+	bundleModuleFile android.WritablePath
+
+	// Target path to install this APEX. Usually out/target/product/<device>/<partition>/apex.
+	installDir android.InstallPath
+
+	// List of commands to create symlinks for backward compatibility. These commands will be
+	// attached as LOCAL_POST_INSTALL_CMD to apex package itself (for unflattened build) or
+	// apex_manifest (for flattened build) so that compat symlinks are always installed
+	// regardless of TARGET_FLATTEN_APEX setting.
+	compatSymlinks []string
+
+	// Text file having the list of individual files that are included in this APEX. Used for
+	// debugging purpose.
+	installedFilesFile android.WritablePath
+
+	// List of module names that this APEX is including (to be shown via *-deps-info target).
+	// Used for debugging purpose.
+	android.ApexBundleDepsInfo
+
+	// Optional list of lint report zip files for apexes that contain java or app modules
+	lintReports android.Paths
+
+	prebuiltFileToDelete string
+}
+
+// apexFileClass represents a type of file that can be included in APEX.
+type apexFileClass int
+
 const (
+	app apexFileClass = iota
+	appSet
+	etc
+	goBinary
+	javaSharedLib
+	nativeExecutable
+	nativeSharedLib
+	nativeTest
+	pyBinary
+	shBinary
+)
+
+// apexFile represents a file in an APEX bundle. This is created during the first half of
+// GenerateAndroidBuildActions by traversing the dependencies of the APEX. Then in the second half
+// of the function, this is used to create commands that copies the files into a staging directory,
+// where they are packaged into the APEX file. This struct is also used for creating Make modules
+// for each of the files in case when the APEX is flattened.
+type apexFile struct {
+	// buildFile is put in the installDir inside the APEX.
+	builtFile   android.Path
+	noticeFiles android.Paths
+	installDir  string
+	customStem  string
+	symlinks    []string // additional symlinks
+
+	// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
+	// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
+	// suffix>]
+	androidMkModuleName       string             // becomes LOCAL_MODULE
+	class                     apexFileClass      // becomes LOCAL_MODULE_CLASS
+	moduleDir                 string             // becomes LOCAL_PATH
+	requiredModuleNames       []string           // becomes LOCAL_REQUIRED_MODULES
+	targetRequiredModuleNames []string           // becomes LOCAL_TARGET_REQUIRED_MODULES
+	hostRequiredModuleNames   []string           // becomes LOCAL_HOST_REQUIRED_MODULES
+	dataPaths                 []android.DataPath // becomes LOCAL_TEST_DATA
+
+	jacocoReportClassesFile android.Path     // only for javalibs and apps
+	lintDepSets             java.LintDepSets // only for javalibs and apps
+	certificate             java.Certificate // only for apps
+	overriddenPackageName   string           // only for apps
+
+	transitiveDep bool
+	isJniLib      bool
+
+	// TODO(jiyong): remove this
+	module android.Module
+}
+
+// TODO(jiyong): shorten the arglist using an option struct
+func newApexFile(ctx android.BaseModuleContext, builtFile android.Path, androidMkModuleName string, installDir string, class apexFileClass, module android.Module) apexFile {
+	ret := apexFile{
+		builtFile:           builtFile,
+		installDir:          installDir,
+		androidMkModuleName: androidMkModuleName,
+		class:               class,
+		module:              module,
+	}
+	if module != nil {
+		ret.noticeFiles = module.NoticeFiles()
+		ret.moduleDir = ctx.OtherModuleDir(module)
+		ret.requiredModuleNames = module.RequiredModuleNames()
+		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
+		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
+	}
+	return ret
+}
+
+func (af *apexFile) ok() bool {
+	return af.builtFile != nil && af.builtFile.String() != ""
+}
+
+// apexRelativePath returns the relative path of the given path from the install directory of this
+// apexFile.
+// TODO(jiyong): rename this
+func (af *apexFile) apexRelativePath(path string) string {
+	return filepath.Join(af.installDir, path)
+}
+
+// path returns path of this apex file relative to the APEX root
+func (af *apexFile) path() string {
+	return af.apexRelativePath(af.stem())
+}
+
+// stem returns the base filename of this apex file
+func (af *apexFile) stem() string {
+	if af.customStem != "" {
+		return af.customStem
+	}
+	return af.builtFile.Base()
+}
+
+// symlinkPaths returns paths of the symlinks (if any) relative to the APEX root
+func (af *apexFile) symlinkPaths() []string {
+	var ret []string
+	for _, symlink := range af.symlinks {
+		ret = append(ret, af.apexRelativePath(symlink))
+	}
+	return ret
+}
+
+// availableToPlatform tests whether this apexFile is from a module that can be installed to the
+// platform.
+func (af *apexFile) availableToPlatform() bool {
+	if af.module == nil {
+		return false
+	}
+	if am, ok := af.module.(android.ApexModule); ok {
+		return am.AvailableFor(android.AvailableToPlatform)
+	}
+	return false
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Mutators
+//
+// Brief description about mutators for APEX. The following three mutators are the most important
+// ones.
+//
+// 1) DepsMutator: from the properties like native_shared_libs, java_libs, etc., modules are added
+// to the (direct) dependencies of this APEX bundle.
+//
+// 2) apexDepsMutator: this is a post-deps mutator, so runs after DepsMutator. Its goal is to
+// collect modules that are direct and transitive dependencies of each APEX bundle. The collected
+// modules are marked as being included in the APEX via BuildForApex().
+//
+// 3) apexMutator: this is a post-deps mutator that runs after apexDepsMutator. For each module that
+// are marked by the apexDepsMutator, apex variations are created using CreateApexVariations().
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+
+	// Determines if the dependent will be part of the APEX payload. Can be false for the
+	// dependencies to the signing key module, etc.
+	payload bool
+}
+
+var (
+	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
+	bpfTag         = dependencyTag{name: "bpf", payload: true}
+	certificateTag = dependencyTag{name: "certificate"}
+	executableTag  = dependencyTag{name: "executable", payload: true}
+	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
+	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
+	keyTag         = dependencyTag{name: "key"}
+	prebuiltTag    = dependencyTag{name: "prebuilt", payload: true}
+	rroTag         = dependencyTag{name: "rro", payload: true}
+	sharedLibTag   = dependencyTag{name: "sharedLib", payload: true}
+	testForTag     = dependencyTag{name: "test for"}
+	testTag        = dependencyTag{name: "test", payload: true}
+)
+
+// TODO(jiyong): shorten this function signature
+func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
+	binVariations := target.Variations()
+	libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+	rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
+
+	if ctx.Device() {
+		binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
+		libVariations = append(libVariations,
+			blueprint.Variation{Mutator: "image", Variation: imageVariation},
+			blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
+		rustLibVariations = append(rustLibVariations,
+			blueprint.Variation{Mutator: "image", Variation: imageVariation})
+	}
+
+	// Use *FarVariation* to be able to depend on modules having conflicting variations with
+	// this module. This is required since arch variant of an APEX bundle is 'common' but it is
+	// 'arm' or 'arm64' for native shared libs.
+	ctx.AddFarVariationDependencies(binVariations, executableTag, nativeModules.Binaries...)
+	ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
+	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
+	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
+	ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
+}
+
+func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
+	if ctx.Device() {
+		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Android.Multilib, nil)
+	} else {
+		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Host.Multilib, nil)
+		if ctx.Os().Bionic() {
+			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_bionic.Multilib, nil)
+		} else {
+			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_glibc.Multilib, nil)
+		}
+	}
+}
+
+// getImageVariation returns the image variant name for this apexBundle. In most cases, it's simply
+// android.CoreVariation, but gets complicated for the vendor APEXes and the VNDK APEX.
+func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
+	deviceConfig := ctx.DeviceConfig()
+	if a.vndkApex {
+		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
+	}
+
+	var prefix string
+	var vndkVersion string
+	if deviceConfig.VndkVersion() != "" {
+		if proptools.Bool(a.properties.Use_vendor) {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.PlatformVndkVersion()
+		} else if a.SocSpecific() || a.DeviceSpecific() {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.VndkVersion()
+		} else if a.ProductSpecific() {
+			prefix = cc.ProductVariationPrefix
+			vndkVersion = deviceConfig.ProductVndkVersion()
+		}
+	}
+	if vndkVersion == "current" {
+		vndkVersion = deviceConfig.PlatformVndkVersion()
+	}
+	if vndkVersion != "" {
+		return prefix + vndkVersion
+	}
+
+	return android.CoreVariation // The usual case
+}
+
+func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// TODO(jiyong): move this kind of checks to GenerateAndroidBuildActions?
+	checkUseVendorProperty(ctx, a)
+
+	// apexBundle is a multi-arch targets module. Arch variant of apexBundle is set to 'common'.
+	// arch-specific targets are enabled by the compile_multilib setting of the apex bundle. For
+	// each target os/architectures, appropriate dependencies are selected by their
+	// target.<os>.multilib.<type> groups and are added as (direct) dependencies.
+	targets := ctx.MultiTargets()
+	config := ctx.DeviceConfig()
+	imageVariation := a.getImageVariation(ctx)
+
+	a.combineProperties(ctx)
+
+	has32BitTarget := false
+	for _, target := range targets {
+		if target.Arch.ArchType.Multilib == "lib32" {
+			has32BitTarget = true
+		}
+	}
+	for i, target := range targets {
+		// Don't include artifacts for the host cross targets because there is no way for us
+		// to run those artifacts natively on host
+		if target.HostCross {
+			continue
+		}
+
+		var depsList []ApexNativeDependencies
+
+		// Add native modules targeting both ABIs. When multilib.* is omitted for
+		// native_shared_libs/jni_libs/tests, it implies multilib.both
+		depsList = append(depsList, a.properties.Multilib.Both)
+		depsList = append(depsList, ApexNativeDependencies{
+			Native_shared_libs: a.properties.Native_shared_libs,
+			Tests:              a.properties.Tests,
+			Jni_libs:           a.properties.Jni_libs,
+			Binaries:           nil,
+		})
+
+		// Add native modules targeting the first ABI When multilib.* is omitted for
+		// binaries, it implies multilib.first
+		isPrimaryAbi := i == 0
+		if isPrimaryAbi {
+			depsList = append(depsList, a.properties.Multilib.First)
+			depsList = append(depsList, ApexNativeDependencies{
+				Native_shared_libs: nil,
+				Tests:              nil,
+				Jni_libs:           nil,
+				Binaries:           a.properties.Binaries,
+			})
+		}
+
+		// Add native modules targeting either 32-bit or 64-bit ABI
+		switch target.Arch.ArchType.Multilib {
+		case "lib32":
+			depsList = append(depsList, a.properties.Multilib.Lib32)
+			depsList = append(depsList, a.properties.Multilib.Prefer32)
+		case "lib64":
+			depsList = append(depsList, a.properties.Multilib.Lib64)
+			if !has32BitTarget {
+				depsList = append(depsList, a.properties.Multilib.Prefer32)
+			}
+		}
+
+		for _, d := range depsList {
+			addDependenciesForNativeModules(ctx, d, target, imageVariation)
+		}
+	}
+
+	// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
+	// regardless of the TARGET_PREFER_* setting. See b/144532908
+	archForPrebuiltEtc := config.Arches()[0]
+	for _, arch := range config.Arches() {
+		// Prefer 64-bit arch if there is any
+		if arch.ArchType.Multilib == "lib64" {
+			archForPrebuiltEtc = arch
+			break
+		}
+	}
+	ctx.AddFarVariationDependencies([]blueprint.Variation{
+		{Mutator: "os", Variation: ctx.Os().String()},
+		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
+	}, prebuiltTag, a.properties.Prebuilts...)
+
+	// Common-arch dependencies come next
+	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
+	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
+	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
+
+	// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
+	if a.artApex && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
+		ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
+	}
+
+	// Dependencies for signing
+	if String(a.properties.Key) == "" {
+		ctx.PropertyErrorf("key", "missing")
+		return
+	}
+	ctx.AddDependency(ctx.Module(), keyTag, String(a.properties.Key))
+
+	cert := android.SrcIsModule(a.getCertString(ctx))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+		// empty cert is not an error. Cert and private keys will be directly found under
+		// PRODUCT_DEFAULT_DEV_CERTIFICATE
+	}
+
+	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
+	// This field currently isn't used.
+	// TODO(jiyong): consider dropping this feature
+	// TODO(jiyong): ensure that all apexes are with non-empty uses_sdks
+	if len(a.properties.Uses_sdks) > 0 {
+		sdkRefs := []android.SdkRef{}
+		for _, str := range a.properties.Uses_sdks {
+			parsed := android.ParseSdkRef(ctx, str, "uses_sdks")
+			sdkRefs = append(sdkRefs, parsed)
+		}
+		a.BuildWithSdks(sdkRefs)
+	}
+}
+
+// DepsMutator for the overridden properties.
+func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
+	if a.overridableProperties.Allowed_files != nil {
+		android.ExtractSourceDeps(ctx, a.overridableProperties.Allowed_files)
+	}
+
+	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
+	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps...)
+	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.overridableProperties.Rros...)
+}
+
+type ApexBundleInfo struct {
+	Contents *android.ApexContents
+}
+
+var ApexBundleInfoProvider = blueprint.NewMutatorProvider(ApexBundleInfo{}, "apex_deps")
+
+// apexDepsMutator is responsible for collecting modules that need to have apex variants. They are
+// identified by doing a graph walk starting from an apexBundle. Basically, all the (direct and
+// indirect) dependencies are collected. But a few types of modules that shouldn't be included in
+// the apexBundle (e.g. stub libraries) are not collected. Note that a single module can be depended
+// on by multiple apexBundles. In that case, the module is collected for all of the apexBundles.
+func apexDepsMutator(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+
+	a, ok := mctx.Module().(*apexBundle)
+	if !ok {
+		return
+	}
+
+	// The VNDK APEX is special. For the APEX, the membership is described in a very different
+	// way. There is no dependency from the VNDK APEX to the VNDK libraries. Instead, VNDK
+	// libraries are self-identified by their vndk.enabled properties. There is no need to run
+	// this mutator for the APEX as nothing will be collected. So, let's return fast.
+	if a.vndkApex {
+		return
+	}
+
+	// Special casing for APEXes on non-system (e.g., vendor, odm, etc.) partitions. They are
+	// provided with a property named use_vndk_as_stable, which when set to true doesn't collect
+	// VNDK libraries as transitive dependencies. This option is useful for reducing the size of
+	// the non-system APEXes because the VNDK libraries won't be included (and duped) in the
+	// APEX, but shared across APEXes via the VNDK APEX.
+	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
+	excludeVndkLibs := useVndk && proptools.Bool(a.properties.Use_vndk_as_stable)
+	if !useVndk && proptools.Bool(a.properties.Use_vndk_as_stable) {
+		mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
+		return
+	}
+
+	continueApexDepsWalk := func(child, parent android.Module) bool {
+		am, ok := child.(android.ApexModule)
+		if !ok || !am.CanHaveApexVariants() {
+			return false
+		}
+		if !parent.(android.DepIsInSameApex).DepIsInSameApex(mctx, child) {
+			return false
+		}
+		if excludeVndkLibs {
+			if c, ok := child.(*cc.Module); ok && c.IsVndk() {
+				return false
+			}
+		}
+		// By default, all the transitive dependencies are collected, unless filtered out
+		// above.
+		return true
+	}
+
+	// Records whether a certain module is included in this apexBundle via direct dependency or
+	// inndirect dependency.
+	contents := make(map[string]android.ApexMembership)
+	mctx.WalkDeps(func(child, parent android.Module) bool {
+		if !continueApexDepsWalk(child, parent) {
+			return false
+		}
+		// If the parent is apexBundle, this child is directly depended.
+		_, directDep := parent.(*apexBundle)
+		depName := mctx.OtherModuleName(child)
+		contents[depName] = contents[depName].Add(directDep)
+		return true
+	})
+
+	// The membership information is saved for later access
+	apexContents := android.NewApexContents(contents)
+	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
+		Contents: apexContents,
+	})
+
+	// This is the main part of this mutator. Mark the collected dependencies that they need to
+	// be built for this apexBundle.
+	apexInfo := android.ApexInfo{
+		ApexVariationName: mctx.ModuleName(),
+		MinSdkVersionStr:  a.minSdkVersion(mctx).String(),
+		RequiredSdks:      a.RequiredSdks(),
+		Updatable:         a.Updatable(),
+		InApexes:          []string{mctx.ModuleName()},
+		ApexContents:      []*android.ApexContents{apexContents},
+	}
+	mctx.WalkDeps(func(child, parent android.Module) bool {
+		if !continueApexDepsWalk(child, parent) {
+			return false
+		}
+		child.(android.ApexModule).BuildForApex(apexInfo) // leave a mark!
+		return true
+	})
+}
+
+// apexUniqueVariationsMutator checks if any dependencies use unique apex variations. If so, use
+// unique apex variations for this module. See android/apex.go for more about unique apex variant.
+// TODO(jiyong): move this to android/apex.go?
+func apexUniqueVariationsMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		android.UpdateUniqueApexVariationsForDeps(mctx, am)
+	}
+}
+
+// apexTestForDepsMutator checks if this module is a test for an apex. If so, add a dependency on
+// the apex in order to retrieve its contents later.
+// TODO(jiyong): move this to android/apex.go?
+func apexTestForDepsMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		if testFor := am.TestFor(); len(testFor) > 0 {
+			mctx.AddFarVariationDependencies([]blueprint.Variation{
+				{Mutator: "os", Variation: am.Target().OsVariation()},
+				{"arch", "common"},
+			}, testForTag, testFor...)
+		}
+	}
+}
+
+// TODO(jiyong): move this to android/apex.go?
+func apexTestForMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if _, ok := mctx.Module().(android.ApexModule); ok {
+		var contents []*android.ApexContents
+		for _, testFor := range mctx.GetDirectDepsWithTag(testForTag) {
+			abInfo := mctx.OtherModuleProvider(testFor, ApexBundleInfoProvider).(ApexBundleInfo)
+			contents = append(contents, abInfo.Contents)
+		}
+		mctx.SetProvider(android.ApexTestForInfoProvider, android.ApexTestForInfo{
+			ApexContents: contents,
+		})
+	}
+}
+
+// markPlatformAvailability marks whether or not a module can be available to platform. A module
+// cannot be available to platform if 1) it is explicitly marked as not available (i.e.
+// "//apex_available:platform" is absent) or 2) it depends on another module that isn't (or can't
+// be) available to platform
+// TODO(jiyong): move this to android/apex.go?
+func markPlatformAvailability(mctx android.BottomUpMutatorContext) {
+	// Host and recovery are not considered as platform
+	if mctx.Host() || mctx.Module().InstallInRecovery() {
+		return
+	}
+
+	am, ok := mctx.Module().(android.ApexModule)
+	if !ok {
+		return
+	}
+
+	availableToPlatform := am.AvailableFor(android.AvailableToPlatform)
+
+	// If any of the dep is not available to platform, this module is also considered as being
+	// not available to platform even if it has "//apex_available:platform"
+	mctx.VisitDirectDeps(func(child android.Module) {
+		if !am.DepIsInSameApex(mctx, child) {
+			// if the dependency crosses apex boundary, don't consider it
+			return
+		}
+		if dep, ok := child.(android.ApexModule); ok && dep.NotAvailableForPlatform() {
+			availableToPlatform = false
+			// TODO(b/154889534) trigger an error when 'am' has
+			// "//apex_available:platform"
+		}
+	})
+
+	// Exception 1: stub libraries and native bridge libraries are always available to platform
+	if cc, ok := mctx.Module().(*cc.Module); ok &&
+		(cc.IsStubs() || cc.Target().NativeBridge == android.NativeBridgeEnabled) {
+		availableToPlatform = true
+	}
+
+	// Exception 2: bootstrap bionic libraries are also always available to platform
+	if cc.InstallToBootstrap(mctx.ModuleName(), mctx.Config()) {
+		availableToPlatform = true
+	}
+
+	if !availableToPlatform {
+		am.SetNotAvailableForPlatform()
+	}
+}
+
+// apexMutator visits each module and creates apex variations if the module was marked in the
+// previous run of apexDepsMutator.
+func apexMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+
+	// This is the usual path.
+	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
+		android.CreateApexVariations(mctx, am)
+		return
+	}
+
+	// apexBundle itself is mutated so that it and its dependencies have the same apex variant.
+	// TODO(jiyong): document the reason why the VNDK APEX is an exception here.
+	if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
+		apexBundleName := mctx.ModuleName()
+		mctx.CreateVariations(apexBundleName)
+	} else if o, ok := mctx.Module().(*OverrideApex); ok {
+		apexBundleName := o.GetOverriddenModuleName()
+		if apexBundleName == "" {
+			mctx.ModuleErrorf("base property is not set")
+			return
+		}
+		mctx.CreateVariations(apexBundleName)
+	}
+}
+
+// See android.UpdateDirectlyInAnyApex
+// TODO(jiyong): move this to android/apex.go?
+func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		android.UpdateDirectlyInAnyApex(mctx, am)
+	}
+}
+
+// apexPackaging represents a specific packaging method for an APEX.
+type apexPackaging int
+
+const (
+	// imageApex is a packaging method where contents are included in a filesystem image which
+	// is then included in a zip container. This is the most typical way of packaging.
+	imageApex apexPackaging = iota
+
+	// zipApex is a packaging method where contents are directly included in the zip container.
+	// This is used for host-side testing - because the contents are easily accessible by
+	// unzipping the container.
+	zipApex
+
+	// flattendApex is a packaging method where contents are not included in the APEX file, but
+	// installed to /apex/<apexname> directory on the device. This packaging method is used for
+	// old devices where the filesystem-based APEX file can't be supported.
+	flattenedApex
+)
+
+const (
+	// File extensions of an APEX for different packaging methods
 	imageApexSuffix = ".apex"
 	zipApexSuffix   = ".zipapex"
 	flattenedSuffix = ".flattened"
 
+	// variant names each of which is for a packaging method
 	imageApexType     = "image"
 	zipApexType       = "zip"
 	flattenedApexType = "flattened"
@@ -46,33 +979,1166 @@
 	f2fsFsType = "f2fs"
 )
 
-type dependencyTag struct {
-	blueprint.BaseDependencyTag
-	name string
+// The suffix for the output "file", not the module
+func (a apexPackaging) suffix() string {
+	switch a {
+	case imageApex:
+		return imageApexSuffix
+	case zipApex:
+		return zipApexSuffix
+	default:
+		panic(fmt.Errorf("unknown APEX type %d", a))
+	}
+}
 
-	// determines if the dependent will be part of the APEX payload
-	payload bool
+func (a apexPackaging) name() string {
+	switch a {
+	case imageApex:
+		return imageApexType
+	case zipApex:
+		return zipApexType
+	default:
+		panic(fmt.Errorf("unknown APEX type %d", a))
+	}
+}
+
+// apexFlattenedMutator creates one or more variations each of which is for a packaging method.
+// TODO(jiyong): give a better name to this mutator
+func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if ab, ok := mctx.Module().(*apexBundle); ok {
+		var variants []string
+		switch proptools.StringDefault(ab.properties.Payload_type, "image") {
+		case "image":
+			// This is the normal case. Note that both image and flattend APEXes are
+			// created. The image type is installed to the system partition, while the
+			// flattened APEX is (optionally) installed to the system_ext partition.
+			// This is mostly for GSI which has to support wide range of devices. If GSI
+			// is installed on a newer (APEX-capable) device, the image APEX in the
+			// system will be used. However, if the same GSI is installed on an old
+			// device which can't support image APEX, the flattened APEX in the
+			// system_ext partion (which still is part of GSI) is used instead.
+			variants = append(variants, imageApexType, flattenedApexType)
+		case "zip":
+			variants = append(variants, zipApexType)
+		case "both":
+			variants = append(variants, imageApexType, zipApexType, flattenedApexType)
+		default:
+			mctx.PropertyErrorf("payload_type", "%q is not one of \"image\", \"zip\", or \"both\".", *ab.properties.Payload_type)
+			return
+		}
+
+		modules := mctx.CreateLocalVariations(variants...)
+
+		for i, v := range variants {
+			switch v {
+			case imageApexType:
+				modules[i].(*apexBundle).properties.ApexType = imageApex
+			case zipApexType:
+				modules[i].(*apexBundle).properties.ApexType = zipApex
+			case flattenedApexType:
+				modules[i].(*apexBundle).properties.ApexType = flattenedApex
+				// See the comment above for why system_ext.
+				if !mctx.Config().FlattenApex() && ab.Platform() {
+					modules[i].(*apexBundle).MakeAsSystemExt()
+				}
+			}
+		}
+	} else if _, ok := mctx.Module().(*OverrideApex); ok {
+		// payload_type is forcibly overridden to "image"
+		// TODO(jiyong): is this the right decision?
+		mctx.CreateVariations(imageApexType, flattenedApexType)
+	}
+}
+
+// checkUseVendorProperty checks if the use of `use_vendor` property is allowed for the given APEX.
+// When use_vendor is used, native modules are built with __ANDROID_VNDK__ and __ANDROID_APEX__,
+// which may cause compatibility issues. (e.g. libbinder) Even though libbinder restricts its
+// availability via 'apex_available' property and relies on yet another macro
+// __ANDROID_APEX_<NAME>__, we restrict usage of "use_vendor:" from other APEX modules to avoid
+// similar problems.
+func checkUseVendorProperty(ctx android.BottomUpMutatorContext, a *apexBundle) {
+	if proptools.Bool(a.properties.Use_vendor) && !android.InList(a.Name(), useVendorAllowList(ctx.Config())) {
+		ctx.PropertyErrorf("use_vendor", "not allowed to set use_vendor: true")
+	}
 }
 
 var (
-	sharedLibTag   = dependencyTag{name: "sharedLib", payload: true}
-	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
-	executableTag  = dependencyTag{name: "executable", payload: true}
-	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
-	prebuiltTag    = dependencyTag{name: "prebuilt", payload: true}
-	testTag        = dependencyTag{name: "test", payload: true}
-	keyTag         = dependencyTag{name: "key"}
-	certificateTag = dependencyTag{name: "certificate"}
-	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
-	rroTag         = dependencyTag{name: "rro", payload: true}
-	bpfTag         = dependencyTag{name: "bpf", payload: true}
-	testForTag     = dependencyTag{name: "test for"}
+	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
+)
 
-	apexAvailBaseline = makeApexAvailableBaseline()
+func useVendorAllowList(config android.Config) []string {
+	return config.Once(useVendorAllowListKey, func() interface{} {
+		return []string{
+			// swcodec uses "vendor" variants for smaller size
+			"com.android.media.swcodec",
+			"test_com.android.media.swcodec",
+		}
+	}).([]string)
+}
 
+// setUseVendorAllowListForTest overrides useVendorAllowList and must be called before the first
+// call to useVendorAllowList()
+func setUseVendorAllowListForTest(config android.Config, allowList []string) {
+	config.Once(useVendorAllowListKey, func() interface{} {
+		return allowList
+	})
+}
+
+var _ android.DepIsInSameApex = (*apexBundle)(nil)
+
+// Implements android.DepInInSameApex
+func (a *apexBundle) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	// direct deps of an APEX bundle are all part of the APEX bundle
+	// TODO(jiyong): shouldn't we look into the payload field of the dependencyTag?
+	return true
+}
+
+var _ android.OutputFileProducer = (*apexBundle)(nil)
+
+// Implements android.OutputFileProducer
+func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "", android.DefaultDistTag:
+		// This is the default dist path.
+		return android.Paths{a.outputFile}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+var _ cc.Coverage = (*apexBundle)(nil)
+
+// Implements cc.Coverage
+func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
+	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) PreventInstall() {
+	a.properties.PreventInstall = true
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) HideFromMake() {
+	a.properties.HideFromMake = true
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) MarkAsCoverageVariant(coverage bool) {
+	a.properties.IsCoverageVariant = coverage
+}
+
+// Implements cc.Coverage
+func (a *apexBundle) EnableCoverageIfNeeded() {}
+
+var _ android.ApexBundleDepsInfoIntf = (*apexBundle)(nil)
+
+// Implements android.ApexBudleDepsInfoIntf
+func (a *apexBundle) Updatable() bool {
+	return proptools.Bool(a.properties.Updatable)
+}
+
+// getCertString returns the name of the cert that should be used to sign this APEX. This is
+// basically from the "certificate" property, but could be overridden by the device config.
+func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
+	moduleName := ctx.ModuleName()
+	// VNDK APEXes share the same certificate. To avoid adding a new VNDK version to the
+	// OVERRIDE_* list, we check with the pseudo module name to see if its certificate is
+	// overridden.
+	if a.vndkApex {
+		moduleName = vndkApexName
+	}
+	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
+	if overridden {
+		return ":" + certificate
+	}
+	return String(a.properties.Certificate)
+}
+
+// See the installable property
+func (a *apexBundle) installable() bool {
+	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
+}
+
+// See the test_only_no_hashtree property
+func (a *apexBundle) testOnlyShouldSkipHashtreeGeneration() bool {
+	return proptools.Bool(a.properties.Test_only_no_hashtree)
+}
+
+// See the test_only_unsigned_payload property
+func (a *apexBundle) testOnlyShouldSkipPayloadSign() bool {
+	return proptools.Bool(a.properties.Test_only_unsigned_payload)
+}
+
+// These functions are interfacing with cc/sanitizer.go. The entire APEX (along with all of its
+// members) can be sanitized, either forcibly, or by the global configuration. For some of the
+// sanitizers, extra dependencies can be forcibly added as well.
+
+func (a *apexBundle) EnableSanitizer(sanitizerName string) {
+	if !android.InList(sanitizerName, a.properties.SanitizerNames) {
+		a.properties.SanitizerNames = append(a.properties.SanitizerNames, sanitizerName)
+	}
+}
+
+func (a *apexBundle) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
+	if android.InList(sanitizerName, a.properties.SanitizerNames) {
+		return true
+	}
+
+	// Then follow the global setting
+	globalSanitizerNames := []string{}
+	if a.Host() {
+		globalSanitizerNames = ctx.Config().SanitizeHost()
+	} else {
+		arches := ctx.Config().SanitizeDeviceArch()
+		if len(arches) == 0 || android.InList(a.Arch().ArchType.Name, arches) {
+			globalSanitizerNames = ctx.Config().SanitizeDevice()
+		}
+	}
+	return android.InList(sanitizerName, globalSanitizerNames)
+}
+
+func (a *apexBundle) AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string) {
+	// TODO(jiyong): move this info (the sanitizer name, the lib name, etc.) to cc/sanitize.go
+	// Keep only the mechanism here.
+	if ctx.Device() && sanitizerName == "hwaddress" && strings.HasPrefix(a.Name(), "com.android.runtime") {
+		imageVariation := a.getImageVariation(ctx)
+		for _, target := range ctx.MultiTargets() {
+			if target.Arch.ArchType.Multilib == "lib64" {
+				addDependenciesForNativeModules(ctx, ApexNativeDependencies{
+					Native_shared_libs: []string{"libclang_rt.hwasan-aarch64-android"},
+					Tests:              nil,
+					Jni_libs:           nil,
+					Binaries:           nil,
+				}, target, imageVariation)
+				break
+			}
+		}
+	}
+}
+
+// apexFileFor<Type> functions below create an apexFile struct for a given Soong module. The
+// returned apexFile saves information about the Soong module that will be used for creating the
+// build rules.
+func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile {
+	// Decide the APEX-local directory by the multilib of the library In the future, we may
+	// query this to the module.
+	// TODO(jiyong): use the new PackagingSpec
+	var dirInApex string
+	switch ccMod.Arch().ArchType.Multilib {
+	case "lib32":
+		dirInApex = "lib"
+	case "lib64":
+		dirInApex = "lib64"
+	}
+	if ccMod.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, ccMod.Target().NativeBridgeRelativePath)
+	}
+	dirInApex = filepath.Join(dirInApex, ccMod.RelativeInstallPath())
+	if handleSpecialLibs && cc.InstallToBootstrap(ccMod.BaseModuleName(), ctx.Config()) {
+		// Special case for Bionic libs and other libs installed with them. This is to
+		// prevent those libs from being included in the search path
+		// /apex/com.android.runtime/${LIB}. This exclusion is required because those libs
+		// in the Runtime APEX are available via the legacy paths in /system/lib/. By the
+		// init process, the libs in the APEX are bind-mounted to the legacy paths and thus
+		// will be loaded into the default linker namespace (aka "platform" namespace). If
+		// the libs are directly in /apex/com.android.runtime/${LIB} then the same libs will
+		// be loaded again into the runtime linker namespace, which will result in double
+		// loading of them, which isn't supported.
+		dirInApex = filepath.Join(dirInApex, "bionic")
+	}
+
+	fileToCopy := ccMod.OutputFile().Path()
+	androidMkModuleName := ccMod.BaseModuleName() + ccMod.Properties.SubName
+	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, ccMod)
+}
+
+func apexFileForExecutable(ctx android.BaseModuleContext, cc *cc.Module) apexFile {
+	dirInApex := "bin"
+	if cc.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
+	}
+	dirInApex = filepath.Join(dirInApex, cc.RelativeInstallPath())
+	fileToCopy := cc.OutputFile().Path()
+	androidMkModuleName := cc.BaseModuleName() + cc.Properties.SubName
+	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, cc)
+	af.symlinks = cc.Symlinks()
+	af.dataPaths = cc.DataPaths()
+	return af
+}
+
+func apexFileForRustExecutable(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	dirInApex := "bin"
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, rustm)
+	return af
+}
+
+func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+	// Decide the APEX-local directory by the multilib of the library
+	// In the future, we may query this to the module.
+	var dirInApex string
+	switch rustm.Arch().ArchType.Multilib {
+	case "lib32":
+		dirInApex = "lib"
+	case "lib64":
+		dirInApex = "lib64"
+	}
+	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+	}
+	fileToCopy := rustm.OutputFile().Path()
+	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
+}
+
+func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
+	dirInApex := "bin"
+	fileToCopy := py.HostToolPath().Path()
+	return newApexFile(ctx, fileToCopy, py.BaseModuleName(), dirInApex, pyBinary, py)
+}
+
+func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
+	dirInApex := "bin"
+	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
+	if err != nil {
+		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
+		return apexFile{}
+	}
+	fileToCopy := android.PathForOutput(ctx, s)
+	// NB: Since go binaries are static we don't need the module for anything here, which is
+	// good since the go tool is a blueprint.Module not an android.Module like we would
+	// normally use.
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, goBinary, nil)
+}
+
+func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
+	dirInApex := filepath.Join("bin", sh.SubDir())
+	fileToCopy := sh.OutputFile()
+	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
+	af.symlinks = sh.Symlinks()
+	return af
+}
+
+func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, depName string) apexFile {
+	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
+	fileToCopy := prebuilt.OutputFile()
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, prebuilt)
+}
+
+func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
+	dirInApex := filepath.Join("etc", config.SubDir())
+	fileToCopy := config.CompatConfig()
+	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, config)
+}
+
+// javaModule is an interface to handle all Java modules (java_library, dex_import, etc) in the same
+// way.
+type javaModule interface {
+	android.Module
+	BaseModuleName() string
+	DexJarBuildPath() android.Path
+	JacocoReportClassesFile() android.Path
+	LintDepSets() java.LintDepSets
+	Stem() string
+}
+
+var _ javaModule = (*java.Library)(nil)
+var _ javaModule = (*java.SdkLibrary)(nil)
+var _ javaModule = (*java.DexImport)(nil)
+var _ javaModule = (*java.SdkLibraryImport)(nil)
+
+func apexFileForJavaModule(ctx android.BaseModuleContext, module javaModule) apexFile {
+	dirInApex := "javalib"
+	fileToCopy := module.DexJarBuildPath()
+	af := newApexFile(ctx, fileToCopy, module.BaseModuleName(), dirInApex, javaSharedLib, module)
+	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
+	af.lintDepSets = module.LintDepSets()
+	af.customStem = module.Stem() + ".jar"
+	return af
+}
+
+// androidApp is an interface to handle all app modules (android_app, android_app_import, etc.) in
+// the same way.
+type androidApp interface {
+	android.Module
+	Privileged() bool
+	InstallApkName() string
+	OutputFile() android.Path
+	JacocoReportClassesFile() android.Path
+	Certificate() java.Certificate
+	BaseModuleName() string
+}
+
+var _ androidApp = (*java.AndroidApp)(nil)
+var _ androidApp = (*java.AndroidAppImport)(nil)
+
+func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp androidApp) apexFile {
+	appDir := "app"
+	if aapp.Privileged() {
+		appDir = "priv-app"
+	}
+	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
+	fileToCopy := aapp.OutputFile()
+	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
+	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
+	af.certificate = aapp.Certificate()
+
+	if app, ok := aapp.(interface {
+		OverriddenManifestPackageName() string
+	}); ok {
+		af.overriddenPackageName = app.OverriddenManifestPackageName()
+	}
+	return af
+}
+
+func apexFileForRuntimeResourceOverlay(ctx android.BaseModuleContext, rro java.RuntimeResourceOverlayModule) apexFile {
+	rroDir := "overlay"
+	dirInApex := filepath.Join(rroDir, rro.Theme())
+	fileToCopy := rro.OutputFile()
+	af := newApexFile(ctx, fileToCopy, rro.Name(), dirInApex, app, rro)
+	af.certificate = rro.Certificate()
+
+	if a, ok := rro.(interface {
+		OverriddenManifestPackageName() string
+	}); ok {
+		af.overriddenPackageName = a.OverriddenManifestPackageName()
+	}
+	return af
+}
+
+func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile {
+	dirInApex := filepath.Join("etc", "bpf")
+	return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram)
+}
+
+// WalyPayloadDeps visits dependencies that contributes to the payload of this APEX. For each of the
+// visited module, the `do` callback is executed. Returning true in the callback continues the visit
+// to the child modules. Returning false makes the visit to continue in the sibling or the parent
+// modules. This is used in check* functions below.
+func (a *apexBundle) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		am, ok := child.(android.ApexModule)
+		if !ok || !am.CanHaveApexVariants() {
+			return false
+		}
+
+		// Filter-out unwanted depedendencies
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+		if dt, ok := depTag.(dependencyTag); ok && !dt.payload {
+			return false
+		}
+
+		ai := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
+		externalDep := !android.InList(ctx.ModuleName(), ai.InApexes)
+
+		// Visit actually
+		return do(ctx, parent, am, externalDep)
+	})
+}
+
+// filesystem type of the apex_payload.img inside the APEX. Currently, ext4 and f2fs are supported.
+type fsType int
+
+const (
+	ext4 fsType = iota
+	f2fs
+)
+
+func (f fsType) string() string {
+	switch f {
+	case ext4:
+		return ext4FsType
+	case f2fs:
+		return f2fsFsType
+	default:
+		panic(fmt.Errorf("unknown APEX payload type %d", f))
+	}
+}
+
+// Creates build rules for an APEX. It consists of the following major steps:
+//
+// 1) do some validity checks such as apex_available, min_sdk_version, etc.
+// 2) traverse the dependency tree to collect apexFile structs from them.
+// 3) some fields in apexBundle struct are configured
+// 4) generate the build rules to create the APEX. This is mostly done in builder.go.
+func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 1) do some validity checks such as apex_available, min_sdk_version, etc.
+	a.checkApexAvailability(ctx)
+	a.checkUpdatable(ctx)
+	a.checkMinSdkVersion(ctx)
+	a.checkStaticLinkingToStubLibraries(ctx)
+	if len(a.properties.Tests) > 0 && !a.testApex {
+		ctx.PropertyErrorf("tests", "property allowed only in apex_test module type")
+		return
+	}
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 2) traverse the dependency tree to collect apexFile structs from them.
+
+	// all the files that will be included in this APEX
+	var filesInfo []apexFile
+
+	// native lib dependencies
+	var provideNativeLibs []string
+	var requireNativeLibs []string
+
+	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
+
+	// TODO(jiyong): do this using WalkPayloadDeps
+	// TODO(jiyong): make this clean!!!
+	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+		depName := ctx.OtherModuleName(child)
+		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
+			switch depTag {
+			case sharedLibTag, jniLibTag:
+				isJniLib := depTag == jniLibTag
+				if c, ok := child.(*cc.Module); ok {
+					fi := apexFileForNativeLibrary(ctx, c, handleSpecialLibs)
+					fi.isJniLib = isJniLib
+					filesInfo = append(filesInfo, fi)
+					// Collect the list of stub-providing libs except:
+					// - VNDK libs are only for vendors
+					// - bootstrap bionic libs are treated as provided by system
+					if c.HasStubsVariants() && !a.vndkApex && !cc.InstallToBootstrap(c.BaseModuleName(), ctx.Config()) {
+						provideNativeLibs = append(provideNativeLibs, fi.stem())
+					}
+					return true // track transitive dependencies
+				} else {
+					propertyName := "native_shared_libs"
+					if isJniLib {
+						propertyName = "jni_libs"
+					}
+					ctx.PropertyErrorf(propertyName, "%q is not a cc_library or cc_library_shared module", depName)
+				}
+			case executableTag:
+				if cc, ok := child.(*cc.Module); ok {
+					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
+					return true // track transitive dependencies
+				} else if sh, ok := child.(*sh.ShBinary); ok {
+					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
+				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
+					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
+				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
+					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
+				} else if rust, ok := child.(*rust.Module); ok {
+					filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
+					return true // track transitive dependencies
+				} else {
+					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+				}
+			case javaLibTag:
+				switch child.(type) {
+				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport:
+					af := apexFileForJavaModule(ctx, child.(javaModule))
+					if !af.ok() {
+						ctx.PropertyErrorf("java_libs", "%q is not configured to be compiled into dex", depName)
+						return false
+					}
+					filesInfo = append(filesInfo, af)
+					return true // track transitive dependencies
+				default:
+					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
+				}
+			case androidAppTag:
+				if ap, ok := child.(*java.AndroidApp); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+					return true // track transitive dependencies
+				} else if ap, ok := child.(*java.AndroidAppImport); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+				} else if ap, ok := child.(*java.AndroidTestHelperApp); ok {
+					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
+				} else if ap, ok := child.(*java.AndroidAppSet); ok {
+					appDir := "app"
+					if ap.Privileged() {
+						appDir = "priv-app"
+					}
+					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(),
+						filepath.Join(appDir, ap.BaseModuleName()), appSet, ap)
+					af.certificate = java.PresignedCertificate
+					filesInfo = append(filesInfo, af)
+				} else {
+					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
+				}
+			case rroTag:
+				if rro, ok := child.(java.RuntimeResourceOverlayModule); ok {
+					filesInfo = append(filesInfo, apexFileForRuntimeResourceOverlay(ctx, rro))
+				} else {
+					ctx.PropertyErrorf("rros", "%q is not an runtime_resource_overlay module", depName)
+				}
+			case bpfTag:
+				if bpfProgram, ok := child.(bpf.BpfModule); ok {
+					filesToCopy, _ := bpfProgram.OutputFiles("")
+					for _, bpfFile := range filesToCopy {
+						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram))
+					}
+				} else {
+					ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName)
+				}
+			case prebuiltTag:
+				if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
+					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
+				} else if prebuilt, ok := child.(java.PlatformCompatConfigIntf); ok {
+					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, prebuilt, depName))
+				} else {
+					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc and not a platform_compat_config module", depName)
+				}
+			case testTag:
+				if ccTest, ok := child.(*cc.Module); ok {
+					if ccTest.IsTestPerSrcAllTestsVariation() {
+						// Multiple-output test module (where `test_per_src: true`).
+						//
+						// `ccTest` is the "" ("all tests") variation of a `test_per_src` module.
+						// We do not add this variation to `filesInfo`, as it has no output;
+						// however, we do add the other variations of this module as indirect
+						// dependencies (see below).
+					} else {
+						// Single-output test module (where `test_per_src: false`).
+						af := apexFileForExecutable(ctx, ccTest)
+						af.class = nativeTest
+						filesInfo = append(filesInfo, af)
+					}
+					return true // track transitive dependencies
+				} else {
+					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
+				}
+			case keyTag:
+				if key, ok := child.(*apexKey); ok {
+					a.private_key_file = key.private_key_file
+					a.public_key_file = key.public_key_file
+				} else {
+					ctx.PropertyErrorf("key", "%q is not an apex_key module", depName)
+				}
+				return false
+			case certificateTag:
+				if dep, ok := child.(*java.AndroidAppCertificate); ok {
+					a.container_certificate_file = dep.Certificate.Pem
+					a.container_private_key_file = dep.Certificate.Key
+				} else {
+					ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
+				}
+			case android.PrebuiltDepTag:
+				// If the prebuilt is force disabled, remember to delete the prebuilt file
+				// that might have been installed in the previous builds
+				if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
+					a.prebuiltFileToDelete = prebuilt.InstallFilename()
+				}
+			}
+		} else if !a.vndkApex {
+			// indirect dependencies
+			if am, ok := child.(android.ApexModule); ok {
+				// We cannot use a switch statement on `depTag` here as the checked
+				// tags used below are private (e.g. `cc.sharedDepTag`).
+				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
+					if cc, ok := child.(*cc.Module); ok {
+						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
+							requireNativeLibs = append(requireNativeLibs, ":vndk")
+							return false
+						}
+						af := apexFileForNativeLibrary(ctx, cc, handleSpecialLibs)
+						af.transitiveDep = true
+						abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
+						if !a.Host() && !abInfo.Contents.DirectlyInApex(depName) && (cc.IsStubs() || cc.HasStubsVariants()) {
+							// If the dependency is a stubs lib, don't include it in this APEX,
+							// but make sure that the lib is installed on the device.
+							// In case no APEX is having the lib, the lib is installed to the system
+							// partition.
+							//
+							// Always include if we are a host-apex however since those won't have any
+							// system libraries.
+							if !am.DirectlyInAnyApex() {
+								// we need a module name for Make
+								name := cc.ImplementationModuleName(ctx)
+
+								if !proptools.Bool(a.properties.Use_vendor) {
+									// we don't use subName(.vendor) for a "use_vendor: true" apex
+									// which is supposed to be installed in /system
+									name += cc.Properties.SubName
+								}
+								if !android.InList(name, a.requiredDeps) {
+									a.requiredDeps = append(a.requiredDeps, name)
+								}
+							}
+							requireNativeLibs = append(requireNativeLibs, af.stem())
+							// Don't track further
+							return false
+						}
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if cc.IsTestPerSrcDepTag(depTag) {
+					if cc, ok := child.(*cc.Module); ok {
+						af := apexFileForExecutable(ctx, cc)
+						// Handle modules created as `test_per_src` variations of a single test module:
+						// use the name of the generated test binary (`fileToCopy`) instead of the name
+						// of the original test module (`depName`, shared by all `test_per_src`
+						// variations of that module).
+						af.androidMkModuleName = filepath.Base(af.builtFile.String())
+						// these are not considered transitive dep
+						af.transitiveDep = false
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if java.IsJniDepTag(depTag) {
+					// Because APK-in-APEX embeds jni_libs transitively, we don't need to track transitive deps
+					return false
+				} else if java.IsXmlPermissionsFileDepTag(depTag) {
+					if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
+						filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
+					}
+				} else if rust.IsDylibDepTag(depTag) {
+					if rustm, ok := child.(*rust.Module); ok && rustm.IsInstallableToApex() {
+						af := apexFileForRustLibrary(ctx, rustm)
+						af.transitiveDep = true
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					}
+				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
+					// nothing
+				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
+					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
+				}
+			}
+		}
+		return false
+	})
+	if a.private_key_file == nil {
+		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.properties.Key))
+		return
+	}
+
+	// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries. Build rules are
+	// generated by the dexpreopt singleton, and here we access build artifacts via the global
+	// boot image config.
+	if a.artApex {
+		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
+			dirInApex := filepath.Join("javalib", arch.String())
+			for _, f := range files {
+				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
+				af := newApexFile(ctx, f, localModule, dirInApex, etc, nil)
+				filesInfo = append(filesInfo, af)
+			}
+		}
+	}
+
+	// Remove duplicates in filesInfo
+	removeDup := func(filesInfo []apexFile) []apexFile {
+		encountered := make(map[string]apexFile)
+		for _, f := range filesInfo {
+			dest := filepath.Join(f.installDir, f.builtFile.Base())
+			if e, ok := encountered[dest]; !ok {
+				encountered[dest] = f
+			} else {
+				// If a module is directly included and also transitively depended on
+				// consider it as directly included.
+				e.transitiveDep = e.transitiveDep && f.transitiveDep
+				encountered[dest] = e
+			}
+		}
+		var result []apexFile
+		for _, v := range encountered {
+			result = append(result, v)
+		}
+		return result
+	}
+	filesInfo = removeDup(filesInfo)
+
+	// Sort to have consistent build rules
+	sort.Slice(filesInfo, func(i, j int) bool {
+		return filesInfo[i].builtFile.String() < filesInfo[j].builtFile.String()
+	})
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 3) some fields in apexBundle struct are configured
+	a.installDir = android.PathForModuleInstall(ctx, "apex")
+	a.filesInfo = filesInfo
+
+	// Set suffix and primaryApexType depending on the ApexType
+	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
+	switch a.properties.ApexType {
+	case imageApex:
+		if buildFlattenedAsDefault {
+			a.suffix = imageApexSuffix
+		} else {
+			a.suffix = ""
+			a.primaryApexType = true
+
+			if ctx.Config().InstallExtraFlattenedApexes() {
+				a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix)
+			}
+		}
+	case zipApex:
+		if proptools.String(a.properties.Payload_type) == "zip" {
+			a.suffix = ""
+			a.primaryApexType = true
+		} else {
+			a.suffix = zipApexSuffix
+		}
+	case flattenedApex:
+		if buildFlattenedAsDefault {
+			a.suffix = ""
+			a.primaryApexType = true
+		} else {
+			a.suffix = flattenedSuffix
+		}
+	}
+
+	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
+	case ext4FsType:
+		a.payloadFsType = ext4
+	case f2fsFsType:
+		a.payloadFsType = f2fs
+	default:
+		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
+	}
+
+	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
+	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
+	// the same library in the system partition, thus effectively sharing the same libraries
+	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
+	// in the APEX.
+	a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable() && !proptools.Bool(a.properties.Use_vendor)
+
+	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
+	// So we can't link them to /system/lib libs which are core variants.
+	if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
+		a.linkToSystemLib = false
+	}
+
+	// We don't need the optimization for updatable APEXes, as it might give false signal
+	// to the system health when the APEXes are still bundled (b/149805758)
+	if a.Updatable() && a.properties.ApexType == imageApex {
+		a.linkToSystemLib = false
+	}
+
+	// We also don't want the optimization for host APEXes, because it doesn't make sense.
+	if ctx.Host() {
+		a.linkToSystemLib = false
+	}
+
+	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// 4) generate the build rules to create the APEX. This is done in builder.go.
+	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
+	if a.properties.ApexType == flattenedApex {
+		a.buildFlattenedApex(ctx)
+	} else {
+		a.buildUnflattenedApex(ctx)
+	}
+	a.buildApexDependencyInfo(ctx)
+	a.buildLintReports(ctx)
+
+	// Append meta-files to the filesInfo list so that they are reflected in Android.mk as well.
+	if a.installable() {
+		// For flattened APEX, make sure that APEX manifest and apex_pubkey are also copied
+		// along with other ordinary files. (Note that this is done by apexer for
+		// non-flattened APEXes)
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
+
+		// Place the public key as apex_pubkey. This is also done by apexer for
+		// non-flattened APEXes case.
+		// TODO(jiyong): Why do we need this CP rule?
+		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  a.public_key_file,
+			Output: copiedPubkey,
+		})
+		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Factory functions
+//
+
+func newApexBundle() *apexBundle {
+	module := &apexBundle{}
+
+	module.AddProperties(&module.properties)
+	module.AddProperties(&module.targetProperties)
+	module.AddProperties(&module.overridableProperties)
+
+	android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitSdkAwareModule(module)
+	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
+	return module
+}
+
+func ApexBundleFactory(testApex bool, artApex bool) android.Module {
+	bundle := newApexBundle()
+	bundle.testApex = testApex
+	bundle.artApex = artApex
+	return bundle
+}
+
+// apex_test is an APEX for testing. The difference from the ordinary apex module type is that
+// certain compatibility checks such as apex_available are not done for apex_test.
+func testApexBundleFactory() android.Module {
+	bundle := newApexBundle()
+	bundle.testApex = true
+	return bundle
+}
+
+// apex packages other modules into an APEX file which is a packaging format for system-level
+// components like binaries, shared libraries, etc.
+func BundleFactory() android.Module {
+	return newApexBundle()
+}
+
+type Defaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+// apex_defaults provides defaultable properties to other apex modules.
+func defaultsFactory() android.Module {
+	return DefaultsFactory()
+}
+
+func DefaultsFactory(props ...interface{}) android.Module {
+	module := &Defaults{}
+
+	module.AddProperties(props...)
+	module.AddProperties(
+		&apexBundleProperties{},
+		&apexTargetBundleProperties{},
+		&overridableProperties{},
+	)
+
+	android.InitDefaultsModule(module)
+	return module
+}
+
+type OverrideApex struct {
+	android.ModuleBase
+	android.OverrideModuleBase
+}
+
+func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// All the overrides happen in the base module.
+}
+
+// override_apex is used to create an apex module based on another apex module by overriding some of
+// its properties.
+func overrideApexFactory() android.Module {
+	m := &OverrideApex{}
+
+	m.AddProperties(&overridableProperties{})
+
+	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	android.InitOverrideModule(m)
+	return m
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Vality check routines
+//
+// These are called in at the very beginning of GenerateAndroidBuildActions to flag an error when
+// certain conditions are not met.
+//
+// TODO(jiyong): move these checks to a separate go file.
+
+// Entures that min_sdk_version of the included modules are equal or less than the min_sdk_version
+// of this apexBundle.
+func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
+	if a.testApex || a.vndkApex {
+		return
+	}
+	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
+	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
+		return
+	}
+	// apexBundle::minSdkVersion reports its own errors.
+	minSdkVersion := a.minSdkVersion(ctx)
+	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+}
+
+func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
+	ver := proptools.String(a.properties.Min_sdk_version)
+	if ver == "" {
+		return android.FutureApiLevel
+	}
+	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
+	if err != nil {
+		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
+		return android.NoneApiLevel
+	}
+	if apiLevel.IsPreview() {
+		// All codenames should build against "current".
+		return android.FutureApiLevel
+	}
+	return apiLevel
+}
+
+// Ensures that a lib providing stub isn't statically linked
+func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext) {
+	// Practically, we only care about regular APEXes on the device.
+	if ctx.Host() || a.testApex || a.vndkApex {
+		return
+	}
+
+	abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
+
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+		if ccm, ok := to.(*cc.Module); ok {
+			apexName := ctx.ModuleName()
+			fromName := ctx.OtherModuleName(from)
+			toName := ctx.OtherModuleName(to)
+
+			// If `to` is not actually in the same APEX as `from` then it does not need
+			// apex_available and neither do any of its dependencies.
+			if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+				// As soon as the dependency graph crosses the APEX boundary, don't go further.
+				return false
+			}
+
+			// The dynamic linker and crash_dump tool in the runtime APEX is the only
+			// exception to this rule. It can't make the static dependencies dynamic
+			// because it can't do the dynamic linking for itself.
+			if apexName == "com.android.runtime" && (fromName == "linker" || fromName == "crash_dump") {
+				return false
+			}
+
+			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
+			if isStubLibraryFromOtherApex && !externalDep {
+				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
+					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
+			}
+
+		}
+		return true
+	})
+}
+
+// Enforce that Java deps of the apex are using stable SDKs to compile
+func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
+	if a.Updatable() {
+		if String(a.properties.Min_sdk_version) == "" {
+			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
+		}
+		a.checkJavaStableSdkVersion(ctx)
+	}
+}
+
+func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
+	// Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs,
+	// java's checkLinkType guarantees correct usage for transitive deps
+	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		switch tag {
+		case javaLibTag, androidAppTag:
+			if m, ok := module.(interface{ CheckStableSdkVersion() error }); ok {
+				if err := m.CheckStableSdkVersion(); err != nil {
+					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
+				}
+			}
+		}
+	})
+}
+
+// Ensures that the all the dependencies are marked as available for this APEX
+func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) {
+	// Let's be practical. Availability for test, host, and the VNDK apex isn't important
+	if ctx.Host() || a.testApex || a.vndkApex {
+		return
+	}
+
+	// Because APEXes targeting other than system/system_ext partitions can't set
+	// apex_available, we skip checks for these APEXes
+	if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
+		return
+	}
+
+	// Coverage build adds additional dependencies for the coverage-only runtime libraries.
+	// Requiring them and their transitive depencies with apex_available is not right
+	// because they just add noise.
+	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || a.IsNativeCoverageNeeded(ctx) {
+		return
+	}
+
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+		// As soon as the dependency graph crosses the APEX boundary, don't go further.
+		if externalDep {
+			return false
+		}
+
+		apexName := ctx.ModuleName()
+		fromName := ctx.OtherModuleName(from)
+		toName := ctx.OtherModuleName(to)
+
+		// If `to` is not actually in the same APEX as `from` then it does not need
+		// apex_available and neither do any of its dependencies.
+		if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+			// As soon as the dependency graph crosses the APEX boundary, don't go
+			// further.
+			return false
+		}
+
+		if to.AvailableFor(apexName) || baselineApexAvailable(apexName, toName) {
+			return true
+		}
+		ctx.ModuleErrorf("%q requires %q that doesn't list the APEX under 'apex_available'. Dependency path:%s",
+			fromName, toName, ctx.GetPathString(true))
+		// Visit this module's dependencies to check and report any issues with their availability.
+		return true
+	})
+}
+
+var (
+	apexAvailBaseline        = makeApexAvailableBaseline()
 	inverseApexAvailBaseline = invertApexBaseline(apexAvailBaseline)
 )
 
+func baselineApexAvailable(apex, moduleName string) bool {
+	key := apex
+	moduleName = normalizeModuleName(moduleName)
+
+	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
+		return true
+	}
+
+	key = android.AvailableToAnyApex
+	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
+		return true
+	}
+
+	return false
+}
+
+func normalizeModuleName(moduleName string) string {
+	// Prebuilt modules (e.g. java_import, etc.) have "prebuilt_" prefix added by the build
+	// system. Trim the prefix for the check since they are confusing
+	moduleName = strings.TrimPrefix(moduleName, "prebuilt_")
+	if strings.HasPrefix(moduleName, "libclang_rt.") {
+		// This module has many arch variants that depend on the product being built.
+		// We don't want to list them all
+		moduleName = "libclang_rt"
+	}
+	if strings.HasPrefix(moduleName, "androidx.") {
+		// TODO(b/156996905) Set apex_available/min_sdk_version for androidx support libraries
+		moduleName = "androidx"
+	}
+	return moduleName
+}
+
 // Transform the map of apex -> modules to module -> apexes.
 func invertApexBaseline(m map[string][]string) map[string][]string {
 	r := make(map[string][]string)
@@ -89,9 +2155,8 @@
 	return inverseApexAvailBaseline[normalizeModuleName(moduleName)]
 }
 
-// This is a map from apex to modules, which overrides the
-// apex_available setting for that particular module to make
-// it available for the apex regardless of its setting.
+// This is a map from apex to modules, which overrides the apex_available setting for that
+// particular module to make it available for the apex regardless of its setting.
 // TODO(b/147364041): remove this
 func makeApexAvailableBaseline() map[string][]string {
 	// The "Module separator"s below are employed to minimize merge conflicts.
@@ -670,6 +2735,26 @@
 	return m
 }
 
+func init() {
+	android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
+	android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
+}
+
+func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
+	rules := make([]android.Rule, 0, len(modules_packages))
+	for module_name, module_packages := range modules_packages {
+		permitted_packages_rule := android.NeverAllow().
+			BootclasspathJar().
+			With("apex_available", module_name).
+			WithMatcher("permitted_packages", android.NotInList(module_packages)).
+			Because("jars that are part of the " + module_name +
+				" module may only allow these packages: " + strings.Join(module_packages, ",") +
+				". Please jarjar or move code around.")
+		rules = append(rules, permitted_packages_rule)
+	}
+	return rules
+}
+
 // DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func qModulesPackages() map[string][]string {
@@ -718,1900 +2803,3 @@
 		},
 	}
 }
-
-func init() {
-	android.RegisterModuleType("apex", BundleFactory)
-	android.RegisterModuleType("apex_test", testApexBundleFactory)
-	android.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
-	android.RegisterModuleType("apex_defaults", defaultsFactory)
-	android.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	android.RegisterModuleType("override_apex", overrideApexFactory)
-	android.RegisterModuleType("apex_set", apexSetFactory)
-
-	android.PreDepsMutators(RegisterPreDepsMutators)
-	android.PostDepsMutators(RegisterPostDepsMutators)
-
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
-}
-
-func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
-	rules := make([]android.Rule, 0, len(modules_packages))
-	for module_name, module_packages := range modules_packages {
-		permitted_packages_rule := android.NeverAllow().
-			BootclasspathJar().
-			With("apex_available", module_name).
-			WithMatcher("permitted_packages", android.NotInList(module_packages)).
-			Because("jars that are part of the " + module_name +
-				" module may only allow these packages: " + strings.Join(module_packages, ",") +
-				". Please jarjar or move code around.")
-		rules = append(rules, permitted_packages_rule)
-	}
-	return rules
-}
-
-func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
-	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
-}
-
-func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.TopDown("apex_deps", apexDepsMutator).Parallel()
-	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
-	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
-	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
-	ctx.BottomUp("apex", apexMutator).Parallel()
-	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
-	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
-	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
-}
-
-// Mark the direct and transitive dependencies of apex bundles so that they
-// can be built for the apex bundles.
-func apexDepsMutator(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	a, ok := mctx.Module().(*apexBundle)
-	if !ok || a.vndkApex {
-		return
-	}
-
-	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
-	excludeVndkLibs := useVndk && proptools.Bool(a.properties.Use_vndk_as_stable)
-	if !useVndk && proptools.Bool(a.properties.Use_vndk_as_stable) {
-		mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
-		return
-	}
-
-	contents := make(map[string]android.ApexMembership)
-
-	continueApexDepsWalk := func(child, parent android.Module) bool {
-		am, ok := child.(android.ApexModule)
-		if !ok || !am.CanHaveApexVariants() {
-			return false
-		}
-		if !parent.(android.DepIsInSameApex).DepIsInSameApex(mctx, child) {
-			return false
-		}
-		if excludeVndkLibs {
-			if c, ok := child.(*cc.Module); ok && c.IsVndk() {
-				return false
-			}
-		}
-		return true
-	}
-
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-
-		depName := mctx.OtherModuleName(child)
-		// If the parent is apexBundle, this child is directly depended.
-		_, directDep := parent.(*apexBundle)
-		contents[depName] = contents[depName].Add(directDep)
-		return true
-	})
-
-	apexContents := android.NewApexContents(mctx.ModuleName(), contents)
-	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
-		Contents: apexContents,
-	})
-
-	apexInfo := android.ApexInfo{
-		ApexVariationName: mctx.ModuleName(),
-		MinSdkVersionStr:  a.minSdkVersion(mctx).String(),
-		RequiredSdks:      a.RequiredSdks(),
-		Updatable:         a.Updatable(),
-		InApexes:          []string{mctx.ModuleName()},
-		ApexContents:      []*android.ApexContents{apexContents},
-	}
-
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-
-		child.(android.ApexModule).BuildForApex(apexInfo)
-		return true
-	})
-}
-
-func apexUniqueVariationsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		// Check if any dependencies use unique apex variations.  If so, use unique apex variations
-		// for this module.
-		android.UpdateUniqueApexVariationsForDeps(mctx, am)
-	}
-}
-
-func apexTestForDepsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	// Check if this module is a test for an apex.  If so, add a dependency on the apex
-	// in order to retrieve its contents later.
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		if testFor := am.TestFor(); len(testFor) > 0 {
-			mctx.AddFarVariationDependencies([]blueprint.Variation{
-				{Mutator: "os", Variation: am.Target().OsVariation()},
-				{"arch", "common"},
-			}, testForTag, testFor...)
-		}
-	}
-}
-
-func apexTestForMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-
-	if _, ok := mctx.Module().(android.ApexModule); ok {
-		var contents []*android.ApexContents
-		for _, testFor := range mctx.GetDirectDepsWithTag(testForTag) {
-			abInfo := mctx.OtherModuleProvider(testFor, ApexBundleInfoProvider).(ApexBundleInfo)
-			contents = append(contents, abInfo.Contents)
-		}
-		mctx.SetProvider(android.ApexTestForInfoProvider, android.ApexTestForInfo{
-			ApexContents: contents,
-		})
-	}
-}
-
-// mark if a module cannot be available to platform. A module cannot be available
-// to platform if 1) it is explicitly marked as not available (i.e. "//apex_available:platform"
-// is absent) or 2) it depends on another module that isn't (or can't be) available to platform
-func markPlatformAvailability(mctx android.BottomUpMutatorContext) {
-	// Host and recovery are not considered as platform
-	if mctx.Host() || mctx.Module().InstallInRecovery() {
-		return
-	}
-
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		availableToPlatform := am.AvailableFor(android.AvailableToPlatform)
-
-		// If any of the dep is not available to platform, this module is also considered
-		// as being not available to platform even if it has "//apex_available:platform"
-		mctx.VisitDirectDeps(func(child android.Module) {
-			if !am.DepIsInSameApex(mctx, child) {
-				// if the dependency crosses apex boundary, don't consider it
-				return
-			}
-			if dep, ok := child.(android.ApexModule); ok && dep.NotAvailableForPlatform() {
-				availableToPlatform = false
-				// TODO(b/154889534) trigger an error when 'am' has "//apex_available:platform"
-			}
-		})
-
-		// Exception 1: stub libraries and native bridge libraries are always available to platform
-		if cc, ok := mctx.Module().(*cc.Module); ok &&
-			(cc.IsStubs() || cc.Target().NativeBridge == android.NativeBridgeEnabled) {
-			availableToPlatform = true
-		}
-
-		// Exception 2: bootstrap bionic libraries are also always available to platform
-		if cc.InstallToBootstrap(mctx.ModuleName(), mctx.Config()) {
-			availableToPlatform = true
-		}
-
-		if !availableToPlatform {
-			am.SetNotAvailableForPlatform()
-		}
-	}
-}
-
-// If a module in an APEX depends on a module from an SDK then it needs an APEX
-// specific variant created for it. Refer to sdk.sdkDepsReplaceMutator.
-func inAnySdk(module android.Module) bool {
-	if sa, ok := module.(android.SdkAware); ok {
-		return sa.IsInAnySdk()
-	}
-
-	return false
-}
-
-// Create apex variations if a module is included in APEX(s).
-func apexMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-
-	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
-		android.CreateApexVariations(mctx, am)
-	} else if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
-		// apex bundle itself is mutated so that it and its modules have same
-		// apex variant.
-		apexBundleName := mctx.ModuleName()
-		mctx.CreateVariations(apexBundleName)
-	} else if o, ok := mctx.Module().(*OverrideApex); ok {
-		apexBundleName := o.GetOverriddenModuleName()
-		if apexBundleName == "" {
-			mctx.ModuleErrorf("base property is not set")
-			return
-		}
-		mctx.CreateVariations(apexBundleName)
-	}
-
-}
-
-func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		android.UpdateDirectlyInAnyApex(mctx, am)
-	}
-}
-
-func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
-	if ab, ok := mctx.Module().(*apexBundle); ok {
-		var variants []string
-		switch proptools.StringDefault(ab.properties.Payload_type, "image") {
-		case "image":
-			variants = append(variants, imageApexType, flattenedApexType)
-		case "zip":
-			variants = append(variants, zipApexType)
-		case "both":
-			variants = append(variants, imageApexType, zipApexType, flattenedApexType)
-		default:
-			mctx.PropertyErrorf("type", "%q is not one of \"image\", \"zip\", or \"both\".", *ab.properties.Payload_type)
-			return
-		}
-
-		modules := mctx.CreateLocalVariations(variants...)
-
-		for i, v := range variants {
-			switch v {
-			case imageApexType:
-				modules[i].(*apexBundle).properties.ApexType = imageApex
-			case zipApexType:
-				modules[i].(*apexBundle).properties.ApexType = zipApex
-			case flattenedApexType:
-				modules[i].(*apexBundle).properties.ApexType = flattenedApex
-				if !mctx.Config().FlattenApex() && ab.Platform() {
-					modules[i].(*apexBundle).MakeAsSystemExt()
-				}
-			}
-		}
-	} else if _, ok := mctx.Module().(*OverrideApex); ok {
-		mctx.CreateVariations(imageApexType, flattenedApexType)
-	}
-}
-
-var (
-	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
-)
-
-// useVendorAllowList returns the list of APEXes which are allowed to use_vendor.
-// When use_vendor is used, native modules are built with __ANDROID_VNDK__ and __ANDROID_APEX__,
-// which may cause compatibility issues. (e.g. libbinder)
-// Even though libbinder restricts its availability via 'apex_available' property and relies on
-// yet another macro __ANDROID_APEX_<NAME>__, we restrict usage of "use_vendor:" from other APEX modules
-// to avoid similar problems.
-func useVendorAllowList(config android.Config) []string {
-	return config.Once(useVendorAllowListKey, func() interface{} {
-		return []string{
-			// swcodec uses "vendor" variants for smaller size
-			"com.android.media.swcodec",
-			"test_com.android.media.swcodec",
-		}
-	}).([]string)
-}
-
-// setUseVendorAllowListForTest overrides useVendorAllowList and must be
-// called before the first call to useVendorAllowList()
-func setUseVendorAllowListForTest(config android.Config, allowList []string) {
-	config.Once(useVendorAllowListKey, func() interface{} {
-		return allowList
-	})
-}
-
-type ApexNativeDependencies struct {
-	// List of native libraries
-	Native_shared_libs []string
-
-	// List of JNI libraries
-	Jni_libs []string
-
-	// List of native executables
-	Binaries []string
-
-	// List of native tests
-	Tests []string
-}
-
-type apexMultilibProperties struct {
-	// Native dependencies whose compile_multilib is "first"
-	First ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "both"
-	Both ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "prefer32"
-	Prefer32 ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "32"
-	Lib32 ApexNativeDependencies
-
-	// Native dependencies whose compile_multilib is "64"
-	Lib64 ApexNativeDependencies
-}
-
-type apexBundleProperties struct {
-	// Json manifest file describing meta info of this APEX bundle. Default:
-	// "apex_manifest.json"
-	Manifest *string `android:"path"`
-
-	// AndroidManifest.xml file used for the zip container of this APEX bundle.
-	// If unspecified, a default one is automatically generated.
-	AndroidManifest *string `android:"path"`
-
-	// Canonical name of the APEX bundle. Used to determine the path to the activated APEX on
-	// device (/apex/<apex_name>).
-	// If unspecified, defaults to the value of name.
-	Apex_name *string
-
-	// Determines the file contexts file for setting security context to each file in this APEX bundle.
-	// For platform APEXes, this should points to a file under /system/sepolicy
-	// Default: /system/sepolicy/apex/<module_name>_file_contexts.
-	File_contexts *string `android:"path"`
-
-	ApexNativeDependencies
-
-	// List of java libraries that are embedded inside this APEX bundle
-	Java_libs []string
-
-	// List of prebuilt files that are embedded inside this APEX bundle
-	Prebuilts []string
-
-	// List of BPF programs inside APEX
-	Bpfs []string
-
-	// Name of the apex_key module that provides the private key to sign APEX
-	Key *string
-
-	// The type of APEX to build. Controls what the APEX payload is. Either
-	// 'image', 'zip' or 'both'. Default: 'image'.
-	Payload_type *string
-
-	// The name of a certificate in the default certificate directory, blank to use the default product certificate,
-	// or an android_app_certificate module name in the form ":module".
-	Certificate *string
-
-	// Whether this APEX is installable to one of the partitions. Default: true.
-	Installable *bool
-
-	// For native libraries and binaries, use the vendor variant instead of the core (platform) variant.
-	// Default is false.
-	Use_vendor *bool
-
-	// For telling the apex to ignore special handling for system libraries such as bionic. Default is false.
-	Ignore_system_library_special_case *bool
-
-	Multilib apexMultilibProperties
-
-	// List of sanitizer names that this APEX is enabled for
-	SanitizerNames []string `blueprint:"mutated"`
-
-	PreventInstall bool `blueprint:"mutated"`
-
-	HideFromMake bool `blueprint:"mutated"`
-
-	// package format of this apex variant; could be non-flattened, flattened, or zip.
-	// imageApex, zipApex or flattened
-	ApexType apexPackaging `blueprint:"mutated"`
-
-	// List of SDKs that are used to build this APEX. A reference to an SDK should be either
-	// `name#version` or `name` which is an alias for `name#current`. If left empty, `platform#current`
-	// is implied. This value affects all modules included in this APEX. In other words, they are
-	// also built with the SDKs specified here.
-	Uses_sdks []string
-
-	// Whenever apex_payload.img of the APEX should include dm-verity hashtree.
-	// Should be only used in tests#.
-	Test_only_no_hashtree *bool
-
-	// Whenever apex_payload.img of the APEX should not be dm-verity signed.
-	// Should be only used in tests#.
-	Test_only_unsigned_payload *bool
-
-	IsCoverageVariant bool `blueprint:"mutated"`
-
-	// Whether this APEX is considered updatable or not. When set to true, this will enforce additional
-	// rules for making sure that the APEX is truly updatable.
-	// - To be updatable, min_sdk_version should be set as well
-	// This will also disable the size optimizations like symlinking to the system libs.
-	// Default is false.
-	Updatable *bool
-
-	// The minimum SDK version that this apex must be compatibile with.
-	Min_sdk_version *string
-
-	// If set true, VNDK libs are considered as stable libs and are not included in this apex.
-	// Should be only used in non-system apexes (e.g. vendor: true).
-	// Default is false.
-	Use_vndk_as_stable *bool
-
-	// The type of filesystem to use for an image apex. Either 'ext4' or 'f2fs'.
-	// Default 'ext4'.
-	Payload_fs_type *string
-}
-
-type ApexBundleInfo struct {
-	Contents *android.ApexContents
-}
-
-var ApexBundleInfoProvider = blueprint.NewMutatorProvider(ApexBundleInfo{}, "apex_deps")
-
-type apexTargetBundleProperties struct {
-	Target struct {
-		// Multilib properties only for android.
-		Android struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host.
-		Host struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host linux_bionic.
-		Linux_bionic struct {
-			Multilib apexMultilibProperties
-		}
-
-		// Multilib properties only for host linux_glibc.
-		Linux_glibc struct {
-			Multilib apexMultilibProperties
-		}
-	}
-}
-
-type overridableProperties struct {
-	// List of APKs to package inside APEX
-	Apps []string
-
-	// List of runtime resource overlays (RROs) inside APEX
-	Rros []string
-
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-
-	// Logging Parent value
-	Logging_parent string
-
-	// Apex Container Package Name.
-	// Override value for attribute package:name in AndroidManifest.xml
-	Package_name string
-
-	// A txt file containing list of files that are allowed to be included in this APEX.
-	Allowed_files *string `android:"path"`
-}
-
-type apexPackaging int
-
-const (
-	imageApex apexPackaging = iota
-	zipApex
-	flattenedApex
-)
-
-// The suffix for the output "file", not the module
-func (a apexPackaging) suffix() string {
-	switch a {
-	case imageApex:
-		return imageApexSuffix
-	case zipApex:
-		return zipApexSuffix
-	default:
-		panic(fmt.Errorf("unknown APEX type %d", a))
-	}
-}
-
-func (a apexPackaging) name() string {
-	switch a {
-	case imageApex:
-		return imageApexType
-	case zipApex:
-		return zipApexType
-	default:
-		panic(fmt.Errorf("unknown APEX type %d", a))
-	}
-}
-
-type apexFileClass int
-
-const (
-	etc apexFileClass = iota
-	nativeSharedLib
-	nativeExecutable
-	shBinary
-	pyBinary
-	goBinary
-	javaSharedLib
-	nativeTest
-	app
-	appSet
-)
-
-func (class apexFileClass) NameInMake() string {
-	switch class {
-	case etc:
-		return "ETC"
-	case nativeSharedLib:
-		return "SHARED_LIBRARIES"
-	case nativeExecutable, shBinary, pyBinary, goBinary:
-		return "EXECUTABLES"
-	case javaSharedLib:
-		return "JAVA_LIBRARIES"
-	case nativeTest:
-		return "NATIVE_TESTS"
-	case app, appSet:
-		// b/142537672 Why isn't this APP? We want to have full control over
-		// the paths and file names of the apk file under the flattend APEX.
-		// If this is set to APP, then the paths and file names are modified
-		// by the Make build system. For example, it is installed to
-		// /system/apex/<apexname>/app/<Appname>/<apexname>.<Appname>/ instead of
-		// /system/apex/<apexname>/app/<Appname> because the build system automatically
-		// appends module name (which is <apexname>.<Appname> to the path.
-		return "ETC"
-	default:
-		panic(fmt.Errorf("unknown class %d", class))
-	}
-}
-
-// apexFile represents a file in an APEX bundle
-type apexFile struct {
-	builtFile android.Path
-	stem      string
-	// Module name of `module` in AndroidMk. Note the generated AndroidMk module for
-	// apexFile is named something like <AndroidMk module name>.<apex name>[<apex suffix>]
-	androidMkModuleName string
-	installDir          string
-	class               apexFileClass
-	module              android.Module
-	// list of symlinks that will be created in installDir that point to this apexFile
-	symlinks      []string
-	dataPaths     []android.DataPath
-	transitiveDep bool
-	moduleDir     string
-
-	requiredModuleNames       []string
-	targetRequiredModuleNames []string
-	hostRequiredModuleNames   []string
-
-	jacocoReportClassesFile android.Path     // only for javalibs and apps
-	lintDepSets             java.LintDepSets // only for javalibs and apps
-	certificate             java.Certificate // only for apps
-	overriddenPackageName   string           // only for apps
-
-	isJniLib bool
-
-	noticeFiles android.Paths
-}
-
-func newApexFile(ctx android.BaseModuleContext, builtFile android.Path, androidMkModuleName string, installDir string, class apexFileClass, module android.Module) apexFile {
-	ret := apexFile{
-		builtFile:           builtFile,
-		androidMkModuleName: androidMkModuleName,
-		installDir:          installDir,
-		class:               class,
-		module:              module,
-	}
-	if module != nil {
-		ret.moduleDir = ctx.OtherModuleDir(module)
-		ret.requiredModuleNames = module.RequiredModuleNames()
-		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
-		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
-		ret.noticeFiles = module.NoticeFiles()
-	}
-	return ret
-}
-
-func (af *apexFile) Ok() bool {
-	return af.builtFile != nil && af.builtFile.String() != ""
-}
-
-func (af *apexFile) apexRelativePath(path string) string {
-	return filepath.Join(af.installDir, path)
-}
-
-// Path() returns path of this apex file relative to the APEX root
-func (af *apexFile) Path() string {
-	return af.apexRelativePath(af.Stem())
-}
-
-func (af *apexFile) Stem() string {
-	if af.stem != "" {
-		return af.stem
-	}
-	return af.builtFile.Base()
-}
-
-// SymlinkPaths() returns paths of the symlinks (if any) relative to the APEX root
-func (af *apexFile) SymlinkPaths() []string {
-	var ret []string
-	for _, symlink := range af.symlinks {
-		ret = append(ret, af.apexRelativePath(symlink))
-	}
-	return ret
-}
-
-func (af *apexFile) AvailableToPlatform() bool {
-	if af.module == nil {
-		return false
-	}
-	if am, ok := af.module.(android.ApexModule); ok {
-		return am.AvailableFor(android.AvailableToPlatform)
-	}
-	return false
-}
-
-type fsType int
-
-const (
-	ext4 fsType = iota
-	f2fs
-)
-
-func (f fsType) string() string {
-	switch f {
-	case ext4:
-		return ext4FsType
-	case f2fs:
-		return f2fsFsType
-	default:
-		panic(fmt.Errorf("unknown APEX payload type %d", f))
-	}
-}
-
-type apexBundle struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.OverridableModuleBase
-	android.SdkBase
-
-	properties            apexBundleProperties
-	targetProperties      apexTargetBundleProperties
-	overridableProperties overridableProperties
-
-	// specific to apex_vndk modules
-	vndkProperties apexVndkProperties
-
-	bundleModuleFile android.WritablePath
-	outputFile       android.WritablePath
-	installDir       android.InstallPath
-
-	prebuiltFileToDelete string
-
-	public_key_file  android.Path
-	private_key_file android.Path
-
-	container_certificate_file android.Path
-	container_private_key_file android.Path
-
-	fileContexts android.WritablePath
-
-	// list of files to be included in this apex
-	filesInfo []apexFile
-
-	// list of module names that should be installed along with this APEX
-	requiredDeps []string
-
-	// list of module names that this APEX is including (to be shown via *-deps-info target)
-	android.ApexBundleDepsInfo
-
-	testApex        bool
-	vndkApex        bool
-	artApex         bool
-	primaryApexType bool
-
-	manifestJsonOut android.WritablePath
-	manifestPbOut   android.WritablePath
-
-	// list of commands to create symlinks for backward compatibility.
-	// these commands will be attached as LOCAL_POST_INSTALL_CMD to
-	// apex package itself(for unflattened build) or apex_manifest(for flattened build)
-	// so that compat symlinks are always installed regardless of TARGET_FLATTEN_APEX setting.
-	compatSymlinks []string
-
-	// Suffix of module name in Android.mk
-	// ".flattened", ".apex", ".zipapex", or ""
-	suffix string
-
-	installedFilesFile android.WritablePath
-
-	// Whether to create symlink to the system file instead of having a file
-	// inside the apex or not
-	linkToSystemLib bool
-
-	// Struct holding the merged notice file paths in different formats
-	mergedNotices android.NoticeOutputs
-
-	// Optional list of lint report zip files for apexes that contain java or app modules
-	lintReports android.Paths
-
-	payloadFsType fsType
-
-	distFiles android.TaggedDistFiles
-}
-
-func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
-	nativeModules ApexNativeDependencies,
-	target android.Target, imageVariation string) {
-	// Use *FarVariation* to be able to depend on modules having
-	// conflicting variations with this module. This is required since
-	// arch variant of an APEX bundle is 'common' but it is 'arm' or 'arm64'
-	// for native shared libs.
-
-	binVariations := target.Variations()
-	libVariations := append(target.Variations(),
-		blueprint.Variation{Mutator: "link", Variation: "shared"})
-
-	if ctx.Device() {
-		binVariations = append(binVariations,
-			blueprint.Variation{Mutator: "image", Variation: imageVariation})
-		libVariations = append(libVariations,
-			blueprint.Variation{Mutator: "image", Variation: imageVariation},
-			blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
-	}
-
-	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
-
-	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
-
-	ctx.AddFarVariationDependencies(binVariations, executableTag, nativeModules.Binaries...)
-
-	ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
-}
-
-func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
-	if ctx.Os().Class == android.Device {
-		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Android.Multilib, nil)
-	} else {
-		proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Host.Multilib, nil)
-		if ctx.Os().Bionic() {
-			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_bionic.Multilib, nil)
-		} else {
-			proptools.AppendProperties(&a.properties.Multilib, &a.targetProperties.Target.Linux_glibc.Multilib, nil)
-		}
-	}
-}
-
-func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
-	if proptools.Bool(a.properties.Use_vendor) && !android.InList(a.Name(), useVendorAllowList(ctx.Config())) {
-		ctx.PropertyErrorf("use_vendor", "not allowed to set use_vendor: true")
-	}
-
-	targets := ctx.MultiTargets()
-	config := ctx.DeviceConfig()
-	imageVariation := a.getImageVariation(ctx)
-
-	a.combineProperties(ctx)
-
-	has32BitTarget := false
-	for _, target := range targets {
-		if target.Arch.ArchType.Multilib == "lib32" {
-			has32BitTarget = true
-		}
-	}
-	for i, target := range targets {
-		if target.HostCross {
-			// Don't include artifats for the host cross targets because there is no way
-			// for us to run those artifacts natively on host
-			continue
-		}
-
-		// When multilib.* is omitted for native_shared_libs/jni_libs/tests, it implies
-		// multilib.both
-		addDependenciesForNativeModules(ctx,
-			ApexNativeDependencies{
-				Native_shared_libs: a.properties.Native_shared_libs,
-				Tests:              a.properties.Tests,
-				Jni_libs:           a.properties.Jni_libs,
-				Binaries:           nil,
-			},
-			target, imageVariation)
-
-		// Add native modules targetting both ABIs
-		addDependenciesForNativeModules(ctx,
-			a.properties.Multilib.Both,
-			target,
-			imageVariation)
-
-		isPrimaryAbi := i == 0
-		if isPrimaryAbi {
-			// When multilib.* is omitted for binaries, it implies
-			// multilib.first
-			addDependenciesForNativeModules(ctx,
-				ApexNativeDependencies{
-					Native_shared_libs: nil,
-					Tests:              nil,
-					Jni_libs:           nil,
-					Binaries:           a.properties.Binaries,
-				},
-				target, imageVariation)
-
-			// Add native modules targetting the first ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.First,
-				target,
-				imageVariation)
-		}
-
-		switch target.Arch.ArchType.Multilib {
-		case "lib32":
-			// Add native modules targetting 32-bit ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Lib32,
-				target,
-				imageVariation)
-
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Prefer32,
-				target,
-				imageVariation)
-		case "lib64":
-			// Add native modules targetting 64-bit ABI
-			addDependenciesForNativeModules(ctx,
-				a.properties.Multilib.Lib64,
-				target,
-				imageVariation)
-
-			if !has32BitTarget {
-				addDependenciesForNativeModules(ctx,
-					a.properties.Multilib.Prefer32,
-					target,
-					imageVariation)
-			}
-		}
-	}
-
-	// For prebuilt_etc, use the first variant (64 on 64/32bit device,
-	// 32 on 32bit device) regardless of the TARGET_PREFER_* setting.
-	// b/144532908
-	archForPrebuiltEtc := config.Arches()[0]
-	for _, arch := range config.Arches() {
-		// Prefer 64-bit arch if there is any
-		if arch.ArchType.Multilib == "lib64" {
-			archForPrebuiltEtc = arch
-			break
-		}
-	}
-	ctx.AddFarVariationDependencies([]blueprint.Variation{
-		{Mutator: "os", Variation: ctx.Os().String()},
-		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
-	}, prebuiltTag, a.properties.Prebuilts...)
-
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		javaLibTag, a.properties.Java_libs...)
-
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		bpfTag, a.properties.Bpfs...)
-
-	// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
-	if a.artApex && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-		ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-			javaLibTag, "jacocoagent")
-	}
-
-	if String(a.properties.Key) == "" {
-		ctx.ModuleErrorf("key is missing")
-		return
-	}
-	ctx.AddDependency(ctx.Module(), keyTag, String(a.properties.Key))
-
-	cert := android.SrcIsModule(a.getCertString(ctx))
-	if cert != "" {
-		ctx.AddDependency(ctx.Module(), certificateTag, cert)
-	}
-
-	// TODO(jiyong): ensure that all apexes are with non-empty uses_sdks
-	if len(a.properties.Uses_sdks) > 0 {
-		sdkRefs := []android.SdkRef{}
-		for _, str := range a.properties.Uses_sdks {
-			parsed := android.ParseSdkRef(ctx, str, "uses_sdks")
-			sdkRefs = append(sdkRefs, parsed)
-		}
-		a.BuildWithSdks(sdkRefs)
-	}
-}
-
-func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
-	if a.overridableProperties.Allowed_files != nil {
-		android.ExtractSourceDeps(ctx, a.overridableProperties.Allowed_files)
-	}
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		androidAppTag, a.overridableProperties.Apps...)
-	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
-		rroTag, a.overridableProperties.Rros...)
-}
-
-func (a *apexBundle) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	// direct deps of an APEX bundle are all part of the APEX bundle
-	return true
-}
-
-func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
-	moduleName := ctx.ModuleName()
-	// VNDK APEXes share the same certificate. To avoid adding a new VNDK version to the OVERRIDE_* list,
-	// we check with the pseudo module name to see if its certificate is overridden.
-	if a.vndkApex {
-		moduleName = vndkApexName
-	}
-	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
-	if overridden {
-		return ":" + certificate
-	}
-	return String(a.properties.Certificate)
-}
-
-func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{a.outputFile}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (a *apexBundle) installable() bool {
-	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
-}
-
-func (a *apexBundle) testOnlyShouldSkipHashtreeGeneration() bool {
-	return proptools.Bool(a.properties.Test_only_no_hashtree)
-}
-
-func (a *apexBundle) testOnlyShouldSkipPayloadSign() bool {
-	return proptools.Bool(a.properties.Test_only_unsigned_payload)
-}
-
-func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
-	deviceConfig := ctx.DeviceConfig()
-	if a.vndkApex {
-		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
-	}
-
-	var prefix string
-	var vndkVersion string
-	if deviceConfig.VndkVersion() != "" {
-		if proptools.Bool(a.properties.Use_vendor) {
-			prefix = cc.VendorVariationPrefix
-			vndkVersion = deviceConfig.PlatformVndkVersion()
-		} else if a.SocSpecific() || a.DeviceSpecific() {
-			prefix = cc.VendorVariationPrefix
-			vndkVersion = deviceConfig.VndkVersion()
-		} else if a.ProductSpecific() {
-			prefix = cc.ProductVariationPrefix
-			vndkVersion = deviceConfig.ProductVndkVersion()
-		}
-	}
-	if vndkVersion == "current" {
-		vndkVersion = deviceConfig.PlatformVndkVersion()
-	}
-	if vndkVersion != "" {
-		return prefix + vndkVersion
-	}
-	return android.CoreVariation
-}
-
-func (a *apexBundle) EnableSanitizer(sanitizerName string) {
-	if !android.InList(sanitizerName, a.properties.SanitizerNames) {
-		a.properties.SanitizerNames = append(a.properties.SanitizerNames, sanitizerName)
-	}
-}
-
-func (a *apexBundle) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
-	if android.InList(sanitizerName, a.properties.SanitizerNames) {
-		return true
-	}
-
-	// Then follow the global setting
-	globalSanitizerNames := []string{}
-	if a.Host() {
-		globalSanitizerNames = ctx.Config().SanitizeHost()
-	} else {
-		arches := ctx.Config().SanitizeDeviceArch()
-		if len(arches) == 0 || android.InList(a.Arch().ArchType.Name, arches) {
-			globalSanitizerNames = ctx.Config().SanitizeDevice()
-		}
-	}
-	return android.InList(sanitizerName, globalSanitizerNames)
-}
-
-func (a *apexBundle) AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string) {
-	if ctx.Device() && sanitizerName == "hwaddress" && strings.HasPrefix(a.Name(), "com.android.runtime") {
-		for _, target := range ctx.MultiTargets() {
-			if target.Arch.ArchType.Multilib == "lib64" {
-				ctx.AddFarVariationDependencies(append(target.Variations(), []blueprint.Variation{
-					{Mutator: "image", Variation: a.getImageVariation(ctx)},
-					{Mutator: "link", Variation: "shared"},
-					{Mutator: "version", Variation: ""}, // "" is the non-stub variant
-				}...), sharedLibTag, "libclang_rt.hwasan-aarch64-android")
-				break
-			}
-		}
-	}
-}
-
-var _ cc.Coverage = (*apexBundle)(nil)
-
-func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
-	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
-}
-
-func (a *apexBundle) PreventInstall() {
-	a.properties.PreventInstall = true
-}
-
-func (a *apexBundle) HideFromMake() {
-	a.properties.HideFromMake = true
-}
-
-func (a *apexBundle) MarkAsCoverageVariant(coverage bool) {
-	a.properties.IsCoverageVariant = coverage
-}
-
-func (a *apexBundle) EnableCoverageIfNeeded() {}
-
-// TODO(jiyong) move apexFileFor* close to the apexFile type definition
-func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile {
-	// Decide the APEX-local directory by the multilib of the library
-	// In the future, we may query this to the module.
-	var dirInApex string
-	switch ccMod.Arch().ArchType.Multilib {
-	case "lib32":
-		dirInApex = "lib"
-	case "lib64":
-		dirInApex = "lib64"
-	}
-	if ccMod.Target().NativeBridge == android.NativeBridgeEnabled {
-		dirInApex = filepath.Join(dirInApex, ccMod.Target().NativeBridgeRelativePath)
-	}
-	dirInApex = filepath.Join(dirInApex, ccMod.RelativeInstallPath())
-	if handleSpecialLibs && cc.InstallToBootstrap(ccMod.BaseModuleName(), ctx.Config()) {
-		// Special case for Bionic libs and other libs installed with them. This is
-		// to prevent those libs from being included in the search path
-		// /apex/com.android.runtime/${LIB}. This exclusion is required because
-		// those libs in the Runtime APEX are available via the legacy paths in
-		// /system/lib/. By the init process, the libs in the APEX are bind-mounted
-		// to the legacy paths and thus will be loaded into the default linker
-		// namespace (aka "platform" namespace). If the libs are directly in
-		// /apex/com.android.runtime/${LIB} then the same libs will be loaded again
-		// into the runtime linker namespace, which will result in double loading of
-		// them, which isn't supported.
-		dirInApex = filepath.Join(dirInApex, "bionic")
-	}
-
-	fileToCopy := ccMod.OutputFile().Path()
-	androidMkModuleName := ccMod.BaseModuleName() + ccMod.Properties.SubName
-	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, ccMod)
-}
-
-func apexFileForExecutable(ctx android.BaseModuleContext, cc *cc.Module) apexFile {
-	dirInApex := "bin"
-	if cc.Target().NativeBridge == android.NativeBridgeEnabled {
-		dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
-	}
-	dirInApex = filepath.Join(dirInApex, cc.RelativeInstallPath())
-	fileToCopy := cc.OutputFile().Path()
-	androidMkModuleName := cc.BaseModuleName() + cc.Properties.SubName
-	af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, cc)
-	af.symlinks = cc.Symlinks()
-	af.dataPaths = cc.DataPaths()
-	return af
-}
-
-func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
-	dirInApex := "bin"
-	fileToCopy := py.HostToolPath().Path()
-	return newApexFile(ctx, fileToCopy, py.BaseModuleName(), dirInApex, pyBinary, py)
-}
-func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
-	dirInApex := "bin"
-	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
-	if err != nil {
-		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
-		return apexFile{}
-	}
-	fileToCopy := android.PathForOutput(ctx, s)
-	// NB: Since go binaries are static we don't need the module for anything here, which is
-	// good since the go tool is a blueprint.Module not an android.Module like we would
-	// normally use.
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, goBinary, nil)
-}
-
-func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
-	dirInApex := filepath.Join("bin", sh.SubDir())
-	fileToCopy := sh.OutputFile()
-	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
-	af.symlinks = sh.Symlinks()
-	return af
-}
-
-type javaModule interface {
-	android.Module
-	BaseModuleName() string
-	DexJarBuildPath() android.Path
-	JacocoReportClassesFile() android.Path
-	LintDepSets() java.LintDepSets
-
-	Stem() string
-}
-
-var _ javaModule = (*java.Library)(nil)
-var _ javaModule = (*java.SdkLibrary)(nil)
-var _ javaModule = (*java.DexImport)(nil)
-var _ javaModule = (*java.SdkLibraryImport)(nil)
-
-func apexFileForJavaLibrary(ctx android.BaseModuleContext, module javaModule) apexFile {
-	dirInApex := "javalib"
-	fileToCopy := module.DexJarBuildPath()
-	af := newApexFile(ctx, fileToCopy, module.BaseModuleName(), dirInApex, javaSharedLib, module)
-	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
-	af.lintDepSets = module.LintDepSets()
-	af.stem = module.Stem() + ".jar"
-	return af
-}
-
-func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, depName string) apexFile {
-	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
-	fileToCopy := prebuilt.OutputFile()
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, prebuilt)
-}
-
-func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
-	dirInApex := filepath.Join("etc", config.SubDir())
-	fileToCopy := config.CompatConfig()
-	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, config)
-}
-
-func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp interface {
-	android.Module
-	Privileged() bool
-	InstallApkName() string
-	OutputFile() android.Path
-	JacocoReportClassesFile() android.Path
-	Certificate() java.Certificate
-	BaseModuleName() string
-}) apexFile {
-	appDir := "app"
-	if aapp.Privileged() {
-		appDir = "priv-app"
-	}
-	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
-	fileToCopy := aapp.OutputFile()
-	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
-	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
-	af.certificate = aapp.Certificate()
-
-	if app, ok := aapp.(interface {
-		OverriddenManifestPackageName() string
-	}); ok {
-		af.overriddenPackageName = app.OverriddenManifestPackageName()
-	}
-	return af
-}
-
-func apexFileForRuntimeResourceOverlay(ctx android.BaseModuleContext, rro java.RuntimeResourceOverlayModule) apexFile {
-	rroDir := "overlay"
-	dirInApex := filepath.Join(rroDir, rro.Theme())
-	fileToCopy := rro.OutputFile()
-	af := newApexFile(ctx, fileToCopy, rro.Name(), dirInApex, app, rro)
-	af.certificate = rro.Certificate()
-
-	if a, ok := rro.(interface {
-		OverriddenManifestPackageName() string
-	}); ok {
-		af.overriddenPackageName = a.OverriddenManifestPackageName()
-	}
-	return af
-}
-
-func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile {
-	dirInApex := filepath.Join("etc", "bpf")
-	return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram)
-}
-
-// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
-type flattenedApexContext struct {
-	android.ModuleContext
-}
-
-func (c *flattenedApexContext) InstallBypassMake() bool {
-	return true
-}
-
-// Visit dependencies that contributes to the payload of this APEX
-func (a *apexBundle) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		am, ok := child.(android.ApexModule)
-		if !ok || !am.CanHaveApexVariants() {
-			return false
-		}
-
-		childApexInfo := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
-
-		dt := ctx.OtherModuleDependencyTag(child)
-
-		if _, ok := dt.(android.ExcludeFromApexContentsTag); ok {
-			return false
-		}
-
-		// Check for the direct dependencies that contribute to the payload
-		if adt, ok := dt.(dependencyTag); ok {
-			if adt.payload {
-				return do(ctx, parent, am, false /* externalDep */)
-			}
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		// Check for the indirect dependencies if it is considered as part of the APEX
-		if android.InList(ctx.ModuleName(), childApexInfo.InApexes) {
-			return do(ctx, parent, am, false /* externalDep */)
-		}
-
-		return do(ctx, parent, am, true /* externalDep */)
-	})
-}
-
-func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
-	ver := proptools.String(a.properties.Min_sdk_version)
-	if ver == "" {
-		return android.FutureApiLevel
-	}
-	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
-	if err != nil {
-		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
-		return android.NoneApiLevel
-	}
-	if apiLevel.IsPreview() {
-		// All codenames should build against "current".
-		return android.FutureApiLevel
-	}
-	return apiLevel
-}
-
-func (a *apexBundle) Updatable() bool {
-	return proptools.Bool(a.properties.Updatable)
-}
-
-var _ android.ApexBundleDepsInfoIntf = (*apexBundle)(nil)
-
-// Ensures that the dependencies are marked as available for this APEX
-func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) {
-	// Let's be practical. Availability for test, host, and the VNDK apex isn't important
-	if ctx.Host() || a.testApex || a.vndkApex {
-		return
-	}
-
-	// Because APEXes targeting other than system/system_ext partitions
-	// can't set apex_available, we skip checks for these APEXes
-	if a.SocSpecific() || a.DeviceSpecific() ||
-		(a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
-		return
-	}
-
-	// Coverage build adds additional dependencies for the coverage-only runtime libraries.
-	// Requiring them and their transitive depencies with apex_available is not right
-	// because they just add noise.
-	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || a.IsNativeCoverageNeeded(ctx) {
-		return
-	}
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if externalDep {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		apexName := ctx.ModuleName()
-		fromName := ctx.OtherModuleName(from)
-		toName := ctx.OtherModuleName(to)
-
-		// If `to` is not actually in the same APEX as `from` then it does not need apex_available and neither
-		// do any of its dependencies.
-		if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-
-		if to.AvailableFor(apexName) || baselineApexAvailable(apexName, toName) {
-			return true
-		}
-		ctx.ModuleErrorf("%q requires %q that doesn't list the APEX under 'apex_available'. Dependency path:%s", fromName, toName, ctx.GetPathString(true))
-		// Visit this module's dependencies to check and report any issues with their availability.
-		return true
-	})
-}
-
-func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
-	if a.Updatable() {
-		if String(a.properties.Min_sdk_version) == "" {
-			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
-		}
-
-		a.checkJavaStableSdkVersion(ctx)
-	}
-}
-
-func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
-	if a.testApex || a.vndkApex {
-		return
-	}
-	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
-	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
-		return
-	}
-	// apexBundle::minSdkVersion reports its own errors.
-	minSdkVersion := a.minSdkVersion(ctx)
-	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
-}
-
-// Ensures that a lib providing stub isn't statically linked
-func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext) {
-	// Practically, we only care about regular APEXes on the device.
-	if ctx.Host() || a.testApex || a.vndkApex {
-		return
-	}
-
-	abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if ccm, ok := to.(*cc.Module); ok {
-			apexName := ctx.ModuleName()
-			fromName := ctx.OtherModuleName(from)
-			toName := ctx.OtherModuleName(to)
-
-			// If `to` is not actually in the same APEX as `from` then it does not need apex_available and neither
-			// do any of its dependencies.
-			if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
-				// As soon as the dependency graph crosses the APEX boundary, don't go further.
-				return false
-			}
-
-			// The dynamic linker and crash_dump tool in the runtime APEX is the only exception to this rule.
-			// It can't make the static dependencies dynamic because it can't
-			// do the dynamic linking for itself.
-			if apexName == "com.android.runtime" && (fromName == "linker" || fromName == "crash_dump") {
-				return false
-			}
-
-			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
-			if isStubLibraryFromOtherApex && !externalDep {
-				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
-					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
-			}
-
-		}
-		return true
-	})
-}
-
-func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
-	switch a.properties.ApexType {
-	case imageApex:
-		if buildFlattenedAsDefault {
-			a.suffix = imageApexSuffix
-		} else {
-			a.suffix = ""
-			a.primaryApexType = true
-
-			if ctx.Config().InstallExtraFlattenedApexes() {
-				a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix)
-			}
-		}
-	case zipApex:
-		if proptools.String(a.properties.Payload_type) == "zip" {
-			a.suffix = ""
-			a.primaryApexType = true
-		} else {
-			a.suffix = zipApexSuffix
-		}
-	case flattenedApex:
-		if buildFlattenedAsDefault {
-			a.suffix = ""
-			a.primaryApexType = true
-		} else {
-			a.suffix = flattenedSuffix
-		}
-	}
-
-	if len(a.properties.Tests) > 0 && !a.testApex {
-		ctx.PropertyErrorf("tests", "property not allowed in apex module type")
-		return
-	}
-
-	a.checkApexAvailability(ctx)
-	a.checkUpdatable(ctx)
-	a.checkMinSdkVersion(ctx)
-	a.checkStaticLinkingToStubLibraries(ctx)
-
-	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
-
-	// native lib dependencies
-	var provideNativeLibs []string
-	var requireNativeLibs []string
-
-	var filesInfo []apexFile
-	// TODO(jiyong) do this using WalkPayloadDeps
-	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
-		depTag := ctx.OtherModuleDependencyTag(child)
-		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
-			return false
-		}
-		depName := ctx.OtherModuleName(child)
-		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
-			switch depTag {
-			case sharedLibTag, jniLibTag:
-				isJniLib := depTag == jniLibTag
-				if c, ok := child.(*cc.Module); ok {
-					fi := apexFileForNativeLibrary(ctx, c, handleSpecialLibs)
-					fi.isJniLib = isJniLib
-					filesInfo = append(filesInfo, fi)
-					// Collect the list of stub-providing libs except:
-					// - VNDK libs are only for vendors
-					// - bootstrap bionic libs are treated as provided by system
-					if c.HasStubsVariants() && !a.vndkApex && !cc.InstallToBootstrap(c.BaseModuleName(), ctx.Config()) {
-						provideNativeLibs = append(provideNativeLibs, fi.Stem())
-					}
-					return true // track transitive dependencies
-				} else {
-					propertyName := "native_shared_libs"
-					if isJniLib {
-						propertyName = "jni_libs"
-					}
-					ctx.PropertyErrorf(propertyName, "%q is not a cc_library or cc_library_shared module", depName)
-				}
-			case executableTag:
-				if cc, ok := child.(*cc.Module); ok {
-					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
-					return true // track transitive dependencies
-				} else if sh, ok := child.(*sh.ShBinary); ok {
-					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
-				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
-					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
-				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
-					filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
-				} else {
-					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
-				}
-			case javaLibTag:
-				switch child.(type) {
-				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport:
-					af := apexFileForJavaLibrary(ctx, child.(javaModule))
-					if !af.Ok() {
-						ctx.PropertyErrorf("java_libs", "%q is not configured to be compiled into dex", depName)
-						return false
-					}
-					filesInfo = append(filesInfo, af)
-					return true // track transitive dependencies
-				default:
-					ctx.PropertyErrorf("java_libs", "%q of type %q is not supported", depName, ctx.OtherModuleType(child))
-				}
-			case androidAppTag:
-				if ap, ok := child.(*java.AndroidApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-					return true // track transitive dependencies
-				} else if ap, ok := child.(*java.AndroidAppImport); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-				} else if ap, ok := child.(*java.AndroidTestHelperApp); ok {
-					filesInfo = append(filesInfo, apexFileForAndroidApp(ctx, ap))
-				} else if ap, ok := child.(*java.AndroidAppSet); ok {
-					appDir := "app"
-					if ap.Privileged() {
-						appDir = "priv-app"
-					}
-					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(),
-						filepath.Join(appDir, ap.BaseModuleName()), appSet, ap)
-					af.certificate = java.PresignedCertificate
-					filesInfo = append(filesInfo, af)
-				} else {
-					ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
-				}
-			case rroTag:
-				if rro, ok := child.(java.RuntimeResourceOverlayModule); ok {
-					filesInfo = append(filesInfo, apexFileForRuntimeResourceOverlay(ctx, rro))
-				} else {
-					ctx.PropertyErrorf("rros", "%q is not an runtime_resource_overlay module", depName)
-				}
-			case bpfTag:
-				if bpfProgram, ok := child.(bpf.BpfModule); ok {
-					filesToCopy, _ := bpfProgram.OutputFiles("")
-					for _, bpfFile := range filesToCopy {
-						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram))
-					}
-				} else {
-					ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName)
-				}
-			case prebuiltTag:
-				if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
-				} else if prebuilt, ok := child.(java.PlatformCompatConfigIntf); ok {
-					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, prebuilt, depName))
-				} else {
-					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc and not a platform_compat_config module", depName)
-				}
-			case testTag:
-				if ccTest, ok := child.(*cc.Module); ok {
-					if ccTest.IsTestPerSrcAllTestsVariation() {
-						// Multiple-output test module (where `test_per_src: true`).
-						//
-						// `ccTest` is the "" ("all tests") variation of a `test_per_src` module.
-						// We do not add this variation to `filesInfo`, as it has no output;
-						// however, we do add the other variations of this module as indirect
-						// dependencies (see below).
-					} else {
-						// Single-output test module (where `test_per_src: false`).
-						af := apexFileForExecutable(ctx, ccTest)
-						af.class = nativeTest
-						filesInfo = append(filesInfo, af)
-					}
-					return true // track transitive dependencies
-				} else {
-					ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
-				}
-			case keyTag:
-				if key, ok := child.(*apexKey); ok {
-					a.private_key_file = key.private_key_file
-					a.public_key_file = key.public_key_file
-				} else {
-					ctx.PropertyErrorf("key", "%q is not an apex_key module", depName)
-				}
-				return false
-			case certificateTag:
-				if dep, ok := child.(*java.AndroidAppCertificate); ok {
-					a.container_certificate_file = dep.Certificate.Pem
-					a.container_private_key_file = dep.Certificate.Key
-				} else {
-					ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
-				}
-			case android.PrebuiltDepTag:
-				// If the prebuilt is force disabled, remember to delete the prebuilt file
-				// that might have been installed in the previous builds
-				if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
-					a.prebuiltFileToDelete = prebuilt.InstallFilename()
-				}
-			}
-		} else if !a.vndkApex {
-			// indirect dependencies
-			if am, ok := child.(android.ApexModule); ok {
-				// We cannot use a switch statement on `depTag` here as the checked
-				// tags used below are private (e.g. `cc.sharedDepTag`).
-				if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
-					if cc, ok := child.(*cc.Module); ok {
-						if cc.UseVndk() && proptools.Bool(a.properties.Use_vndk_as_stable) && cc.IsVndk() {
-							requireNativeLibs = append(requireNativeLibs, ":vndk")
-							return false
-						}
-						af := apexFileForNativeLibrary(ctx, cc, handleSpecialLibs)
-						af.transitiveDep = true
-						abInfo := ctx.Provider(ApexBundleInfoProvider).(ApexBundleInfo)
-						if !a.Host() && !abInfo.Contents.DirectlyInApex(depName) && (cc.IsStubs() || cc.HasStubsVariants()) {
-							// If the dependency is a stubs lib, don't include it in this APEX,
-							// but make sure that the lib is installed on the device.
-							// In case no APEX is having the lib, the lib is installed to the system
-							// partition.
-							//
-							// Always include if we are a host-apex however since those won't have any
-							// system libraries.
-							if !am.DirectlyInAnyApex() {
-								// we need a module name for Make
-								name := cc.ImplementationModuleName(ctx)
-
-								if !proptools.Bool(a.properties.Use_vendor) {
-									// we don't use subName(.vendor) for a "use_vendor: true" apex
-									// which is supposed to be installed in /system
-									name += cc.Properties.SubName
-								}
-								if !android.InList(name, a.requiredDeps) {
-									a.requiredDeps = append(a.requiredDeps, name)
-								}
-							}
-							requireNativeLibs = append(requireNativeLibs, af.Stem())
-							// Don't track further
-							return false
-						}
-						filesInfo = append(filesInfo, af)
-						return true // track transitive dependencies
-					}
-				} else if cc.IsTestPerSrcDepTag(depTag) {
-					if cc, ok := child.(*cc.Module); ok {
-						af := apexFileForExecutable(ctx, cc)
-						// Handle modules created as `test_per_src` variations of a single test module:
-						// use the name of the generated test binary (`fileToCopy`) instead of the name
-						// of the original test module (`depName`, shared by all `test_per_src`
-						// variations of that module).
-						af.androidMkModuleName = filepath.Base(af.builtFile.String())
-						// these are not considered transitive dep
-						af.transitiveDep = false
-						filesInfo = append(filesInfo, af)
-						return true // track transitive dependencies
-					}
-				} else if java.IsJniDepTag(depTag) {
-					// Because APK-in-APEX embeds jni_libs transitively, we don't need to track transitive deps
-					return false
-				} else if java.IsXmlPermissionsFileDepTag(depTag) {
-					if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-						filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
-					}
-				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
-					// nothing
-				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
-					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
-				}
-			}
-		}
-		return false
-	})
-
-	// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries.
-	// Build rules are generated by the dexpreopt singleton, and here we access build artifacts
-	// via the global boot image config.
-	if a.artApex {
-		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
-			dirInApex := filepath.Join("javalib", arch.String())
-			for _, f := range files {
-				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-				af := newApexFile(ctx, f, localModule, dirInApex, etc, nil)
-				filesInfo = append(filesInfo, af)
-			}
-		}
-	}
-
-	if a.private_key_file == nil {
-		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.properties.Key))
-		return
-	}
-
-	// remove duplicates in filesInfo
-	removeDup := func(filesInfo []apexFile) []apexFile {
-		encountered := make(map[string]apexFile)
-		for _, f := range filesInfo {
-			dest := filepath.Join(f.installDir, f.builtFile.Base())
-			if e, ok := encountered[dest]; !ok {
-				encountered[dest] = f
-			} else {
-				// If a module is directly included and also transitively depended on
-				// consider it as directly included.
-				e.transitiveDep = e.transitiveDep && f.transitiveDep
-				encountered[dest] = e
-			}
-		}
-		var result []apexFile
-		for _, v := range encountered {
-			result = append(result, v)
-		}
-		return result
-	}
-	filesInfo = removeDup(filesInfo)
-
-	// to have consistent build rules
-	sort.Slice(filesInfo, func(i, j int) bool {
-		return filesInfo[i].builtFile.String() < filesInfo[j].builtFile.String()
-	})
-
-	a.installDir = android.PathForModuleInstall(ctx, "apex")
-	a.filesInfo = filesInfo
-
-	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
-	case ext4FsType:
-		a.payloadFsType = ext4
-	case f2fsFsType:
-		a.payloadFsType = f2fs
-	default:
-		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
-	}
-
-	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
-	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
-	// the same library in the system partition, thus effectively sharing the same libraries
-	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
-	// in the APEX.
-	a.linkToSystemLib = !ctx.Config().UnbundledBuild() &&
-		a.installable() &&
-		!proptools.Bool(a.properties.Use_vendor)
-
-	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
-	// So we can't link them to /system/lib libs which are core variants.
-	if a.SocSpecific() || a.DeviceSpecific() ||
-		(a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
-		a.linkToSystemLib = false
-	}
-
-	// We don't need the optimization for updatable APEXes, as it might give false signal
-	// to the system health when the APEXes are still bundled (b/149805758)
-	if a.Updatable() && a.properties.ApexType == imageApex {
-		a.linkToSystemLib = false
-	}
-
-	// We also don't want the optimization for host APEXes, because it doesn't make sense.
-	if ctx.Host() {
-		a.linkToSystemLib = false
-	}
-
-	// prepare apex_manifest.json
-	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
-
-	a.buildFileContexts(ctx)
-
-	a.setCertificateAndPrivateKey(ctx)
-	if a.properties.ApexType == flattenedApex {
-		a.buildFlattenedApex(ctx)
-	} else {
-		a.buildUnflattenedApex(ctx)
-	}
-
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
-
-	a.buildApexDependencyInfo(ctx)
-
-	a.buildLintReports(ctx)
-
-	a.distFiles = a.GenerateTaggedDistFiles(ctx)
-}
-
-// Enforce that Java deps of the apex are using stable SDKs to compile
-func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
-	// Visit direct deps only. As long as we guarantee top-level deps are using
-	// stable SDKs, java's checkLinkType guarantees correct usage for transitive deps
-	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		switch tag {
-		case javaLibTag, androidAppTag:
-			if m, ok := module.(interface{ CheckStableSdkVersion() error }); ok {
-				if err := m.CheckStableSdkVersion(); err != nil {
-					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
-				}
-			}
-		}
-	})
-}
-
-func baselineApexAvailable(apex, moduleName string) bool {
-	key := apex
-	moduleName = normalizeModuleName(moduleName)
-
-	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
-		return true
-	}
-
-	key = android.AvailableToAnyApex
-	if val, ok := apexAvailBaseline[key]; ok && android.InList(moduleName, val) {
-		return true
-	}
-
-	return false
-}
-
-func normalizeModuleName(moduleName string) string {
-	// Prebuilt modules (e.g. java_import, etc.) have "prebuilt_" prefix added by the build
-	// system. Trim the prefix for the check since they are confusing
-	moduleName = strings.TrimPrefix(moduleName, "prebuilt_")
-	if strings.HasPrefix(moduleName, "libclang_rt.") {
-		// This module has many arch variants that depend on the product being built.
-		// We don't want to list them all
-		moduleName = "libclang_rt"
-	}
-	if strings.HasPrefix(moduleName, "androidx.") {
-		// TODO(b/156996905) Set apex_available/min_sdk_version for androidx support libraries
-		moduleName = "androidx"
-	}
-	return moduleName
-}
-
-func newApexBundle() *apexBundle {
-	module := &apexBundle{}
-	module.AddProperties(&module.properties)
-	module.AddProperties(&module.targetProperties)
-	module.AddProperties(&module.overridableProperties)
-	android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-	android.InitSdkAwareModule(module)
-	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
-	return module
-}
-
-func ApexBundleFactory(testApex bool, artApex bool) android.Module {
-	bundle := newApexBundle()
-	bundle.testApex = testApex
-	bundle.artApex = artApex
-	return bundle
-}
-
-// apex_test is an APEX for testing. The difference from the ordinary apex module type is that
-// certain compatibility checks such as apex_available are not done for apex_test.
-func testApexBundleFactory() android.Module {
-	bundle := newApexBundle()
-	bundle.testApex = true
-	return bundle
-}
-
-// apex packages other modules into an APEX file which is a packaging format for system-level
-// components like binaries, shared libraries, etc.
-func BundleFactory() android.Module {
-	return newApexBundle()
-}
-
-//
-// Defaults
-//
-type Defaults struct {
-	android.ModuleBase
-	android.DefaultsModuleBase
-}
-
-func defaultsFactory() android.Module {
-	return DefaultsFactory()
-}
-
-func DefaultsFactory(props ...interface{}) android.Module {
-	module := &Defaults{}
-
-	module.AddProperties(props...)
-	module.AddProperties(
-		&apexBundleProperties{},
-		&apexTargetBundleProperties{},
-		&overridableProperties{},
-	)
-
-	android.InitDefaultsModule(module)
-	return module
-}
-
-//
-// OverrideApex
-//
-type OverrideApex struct {
-	android.ModuleBase
-	android.OverrideModuleBase
-}
-
-func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// All the overrides happen in the base module.
-}
-
-// override_apex is used to create an apex module based on another apex module
-// by overriding some of its properties.
-func overrideApexFactory() android.Module {
-	m := &OverrideApex{}
-	m.AddProperties(&overridableProperties{})
-
-	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
-	android.InitOverrideModule(m)
-	return m
-}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 33e5077..a94e3b4 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -32,6 +32,7 @@
 	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/java"
+	"android/soong/rust"
 	"android/soong/sh"
 )
 
@@ -136,6 +137,8 @@
 
 	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
 
+	bp = bp + rust.GatherRequiredDepsForTest()
+
 	bp = bp + java.GatherRequiredDepsForTest()
 
 	fs := map[string][]byte{
@@ -185,6 +188,7 @@
 		"bar/baz":                                    nil,
 		"testdata/baz":                               nil,
 		"AppSet.apks":                                nil,
+		"foo.rs":                                     nil,
 	}
 
 	cc.GatherRequiredFilesForTest(fs)
@@ -241,6 +245,7 @@
 	ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
 
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	rust.RegisterRequiredBuildComponentsForTest(ctx)
 
 	ctx.RegisterModuleType("cc_test", cc.TestFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@@ -349,10 +354,12 @@
 			manifest: ":myapex.manifest",
 			androidManifest: ":myapex.androidmanifest",
 			key: "myapex.key",
+			binaries: ["foo.rust"],
 			native_shared_libs: ["mylib"],
+			rust_dyn_libs: ["libfoo.dylib.rust"],
 			multilib: {
 				both: {
-					binaries: ["foo",],
+					binaries: ["foo"],
 				}
 			},
 			java_libs: [
@@ -415,6 +422,28 @@
 			apex_available: [ "myapex", "com.android.gki.*" ],
 		}
 
+		rust_binary {
+		        name: "foo.rust",
+			srcs: ["foo.rs"],
+			rlibs: ["libfoo.rlib.rust"],
+			dylibs: ["libfoo.dylib.rust"],
+			apex_available: ["myapex"],
+		}
+
+		rust_library_rlib {
+		        name: "libfoo.rlib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
+		rust_library_dylib {
+		        name: "libfoo.dylib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
 		apex {
 			name: "com.android.gki.fake",
 			binaries: ["foo"],
@@ -529,16 +558,20 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("foo.rust"), "android_arm64_armv8-a_apex10000")
 
 	// Ensure that apex variant is created for the indirect dep
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
 
 	// Ensure that both direct and indirect deps are copied into apex
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
+	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
 	// .. but not for java libs
 	ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
 	ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")
diff --git a/apex/builder.go b/apex/builder.go
index acfb8c5..b858135 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -40,7 +40,7 @@
 	pctx.Import("android/soong/java")
 	pctx.HostBinToolVariable("apexer", "apexer")
 	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
-	// projects, and hence cannot built 'aapt2'. Use the SDK prebuilt instead.
+	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
 	hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
 		pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
 			if !ctx.Config().FrameworksBaseDirExists(ctx) {
@@ -175,37 +175,43 @@
 			`exit 1); touch ${out}`,
 		Description: "Diff ${image_content_file} and ${allowed_files_file}",
 	}, "image_content_file", "allowed_files_file", "apex_module_name")
+
+	// Don't add more rules here. Consider using android.NewRuleBuilder instead.
 )
 
+// buildManifest creates buile rules to modify the input apex_manifest.json to add information
+// gathered by the build system such as provided/required native libraries. Two output files having
+// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and
+// a.manifest.PbOut is protobuf format for R+ devices.
+// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle
 func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) {
-	manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
+	src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
 
-	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
-
-	// put dependency({provide|require}NativeLibs) in apex_manifest.json
+	// Put dependency({provide|require}NativeLibs) in apex_manifest.json
 	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
 	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
 
-	// apex name can be overridden
+	// APEX name can be overridden
 	optCommands := []string{}
 	if a.properties.Apex_name != nil {
 		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
 	}
 
-	// collect jniLibs. Notice that a.filesInfo is already sorted
+	// Collect jniLibs. Notice that a.filesInfo is already sorted
 	var jniLibs []string
 	for _, fi := range a.filesInfo {
-		if fi.isJniLib && !android.InList(fi.Stem(), jniLibs) {
-			jniLibs = append(jniLibs, fi.Stem())
+		if fi.isJniLib && !android.InList(fi.stem(), jniLibs) {
+			jniLibs = append(jniLibs, fi.stem())
 		}
 	}
 	if len(jniLibs) > 0 {
 		optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
 	}
 
+	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexManifestRule,
-		Input:  manifestSrc,
+		Input:  src,
 		Output: manifestJsonFullOut,
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
@@ -214,10 +220,10 @@
 		},
 	})
 
+	// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare
+	// stripped-down version so that APEX modules built from R+ can be installed to Q
 	minSdkVersion := a.minSdkVersion(ctx)
 	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json
-		// prepare stripped-down version so that APEX modules built from R+ can be installed to Q
 		a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   stripApexManifestRule,
@@ -226,7 +232,7 @@
 		})
 	}
 
-	// from R+, protobuf binary format (.pb) is the standard format for apex_manifest
+	// From R+, protobuf binary format (.pb) is the standard format for apex_manifest
 	a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   pbApexManifestRule,
@@ -235,10 +241,11 @@
 	})
 }
 
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) {
-	if a.properties.ApexType == zipApex {
-		return
-	}
+// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts
+// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
+// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
+// labeled as system_file.
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
 	var fileContexts android.Path
 	if a.properties.File_contexts == nil {
 		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
@@ -248,18 +255,17 @@
 	if a.Platform() {
 		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
 			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
-			return
 		}
 	}
 	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
-		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
-		return
+		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String())
 	}
 
 	output := android.PathForModuleOut(ctx, "file_contexts")
 	rule := android.NewRuleBuilder()
 
-	if a.properties.ApexType == imageApex {
+	switch a.properties.ApexType {
+	case imageApex:
 		// remove old file
 		rule.Command().Text("rm").FlagWithOutput("-f ", output)
 		// copy file_contexts
@@ -269,7 +275,7 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
 		rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
-	} else {
+	case flattenedApex:
 		// For flattened apexes, install path should be prepended.
 		// File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS
 		// so that it can be merged into file_contexts.bin
@@ -284,13 +290,16 @@
 		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 		rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output)
 		rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output)
+	default:
+		panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
 	}
 
 	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
-
-	a.fileContexts = output.OutputPath
+	return output.OutputPath
 }
 
+// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
+// contributes to this APEX. The notice files are merged into a big notice file.
 func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
 	var noticeFiles android.Paths
 
@@ -299,13 +308,11 @@
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
 			return false
 		}
-
-		notices := to.NoticeFiles()
-		noticeFiles = append(noticeFiles, notices...)
-
+		noticeFiles = append(noticeFiles, to.NoticeFiles()...)
 		return true
 	})
 
+	// TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
 	for _, fi := range a.filesInfo {
 		noticeFiles = append(noticeFiles, fi.noticeFiles...)
 	}
@@ -317,6 +324,9 @@
 	return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
 }
 
+// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
+// files included in this APEX is shown. The text file is dist'ed so that people can see what's
+// included in the APEX without actually downloading and extracting it.
 func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "installed-files.txt")
 	rule := android.NewRuleBuilder()
@@ -330,6 +340,8 @@
 	return output.OutputPath
 }
 
+// buildBundleConfig creates a build rule for the bundle config file that will control the bundle
+// creation process.
 func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
 	output := android.PathForModuleOut(ctx, "bundle_config.json")
 
@@ -351,8 +363,8 @@
 		"apex_manifest.*",
 	}
 
-	// collect the manifest names and paths of android apps
-	// if their manifest names are overridden
+	// Collect the manifest names and paths of android apps if their manifest names are
+	// overridden.
 	for _, fi := range a.filesInfo {
 		if fi.class != app && fi.class != appSet {
 			continue
@@ -363,7 +375,7 @@
 				config.Apex_config.Apex_embedded_apk_config,
 				ApkConfig{
 					Package_name: packageName,
-					Apk_path:     fi.Path(),
+					Apk_path:     fi.path(),
 				})
 		}
 	}
@@ -378,33 +390,34 @@
 	return output.OutputPath
 }
 
+// buildUnflattendApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
-	var abis []string
-	for _, target := range ctx.MultiTargets() {
-		if len(target.Arch.Abi) > 0 {
-			abis = append(abis, target.Arch.Abi[0])
-		}
-	}
-
-	abis = android.FirstUniqueStrings(abis)
-
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
-	var implicitInputs []android.Path
-	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 
-	// TODO(jiyong): construct the copy rules using RuleBuilder
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1: copy built files to appropriate directories under the image directory
+
+	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
+
+	// TODO(jiyong): use the RuleBuilder
 	var copyCommands []string
+	var implicitInputs []android.Path
 	for _, fi := range a.filesInfo {
-		destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.Path()).String()
+		destPath := imageDir.Join(ctx, fi.path()).String()
+
+		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
 			copyCommands = append(copyCommands, "rm -rf "+destPathDir)
 		}
 		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
-		if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() {
+
+		// Copy the built file to the directory. But if the symlink optimization is turned
+		// on, place a symlink to the corresponding file in /system partition instead.
+		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
 			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			pathOnDevice := filepath.Join("/system", fi.Path())
+			pathOnDevice := filepath.Join("/system", fi.path())
 			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
 		} else {
 			if fi.class == appSet {
@@ -415,11 +428,15 @@
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
 		}
-		// create additional symlinks pointing the file inside the APEX
-		for _, symlinkPath := range fi.SymlinkPaths() {
-			symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String()
+
+		// Create additional symlinks pointing the file inside the APEX (if any). Note that
+		// this is independent from the symlink optimization.
+		for _, symlinkPath := range fi.symlinkPaths() {
+			symlinkDest := imageDir.Join(ctx, symlinkPath).String()
 			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
 		}
+
+		// Copy the test files (if any)
 		for _, d := range fi.dataPaths {
 			// TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible
 			relPath := d.SrcPath.Rel()
@@ -428,28 +445,37 @@
 				panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath))
 			}
 
-			dataDest := android.PathForModuleOut(ctx, "image"+suffix, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
+			dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
 
 			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
 			implicitInputs = append(implicitInputs, d.SrcPath)
 		}
 	}
-
-	// TODO(jiyong): use RuleBuilder
-	var emitCommands []string
-	imageContentFile := android.PathForModuleOut(ctx, "content.txt")
-	emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
-	minSdkVersion := a.minSdkVersion(ctx)
-	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
-		emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
-	}
-	for _, fi := range a.filesInfo {
-		emitCommands = append(emitCommands, "echo './"+fi.Path()+"' >> "+imageContentFile.String())
-	}
-	emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
+	////////////////////////////////////////////////////////////////////////////////////////////
+	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
+	// the allowed list given via the allowed_files property. Build fails when the two lists
+	// differ.
+	//
+	// TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems
+	// to be using this at this moment. Furthermore, this looks very similar to what
+	// buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't
+	// hurt readability.
+	// TODO(jiyong): use RuleBuilder
 	if a.overridableProperties.Allowed_files != nil {
+		// Build content.txt
+		var emitCommands []string
+		imageContentFile := android.PathForModuleOut(ctx, "content.txt")
+		emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
+		minSdkVersion := a.minSdkVersion(ctx)
+		if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
+			emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
+		}
+		for _, fi := range a.filesInfo {
+			emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
+		}
+		emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        emitApexContentRule,
 			Implicits:   implicitInputs,
@@ -460,8 +486,9 @@
 			},
 		})
 		implicitInputs = append(implicitInputs, imageContentFile)
-		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 
+		// Compare content.txt against allowed_files.
+		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 		phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        diffApexContentRule,
@@ -474,22 +501,25 @@
 				"apex_module_name":   a.Name(),
 			},
 		})
-
 		implicitInputs = append(implicitInputs, phonyOutput)
 	}
 
+	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
 	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
-	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 	if apexType == imageApex {
-		// files and dirs that will be created in APEX
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
+		// in this APEX. The file will be used by apexer in later steps.
+		// TODO(jiyong): make this as a function
+		// TODO(jiyong): use the RuleBuilder
 		var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
 		var executablePaths []string // this also includes dirs
 		var extractedAppSetPaths android.Paths
 		var extractedAppSetDirs []string
 		for _, f := range a.filesInfo {
-			pathInApex := f.Path()
+			pathInApex := f.path()
 			if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
 				executablePaths = append(executablePaths, pathInApex)
 				for _, d := range f.dataPaths {
@@ -528,11 +558,17 @@
 				"apk_paths":  strings.Join(extractedAppSetDirs, " "),
 			},
 		})
+		implicitInputs = append(implicitInputs, cannedFsConfig)
 
+		////////////////////////////////////////////////////////////////////////////////////
+		// Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX.
+		// TODO(jiyong): use the RuleBuilder
 		optFlags := []string{}
 
-		// Additional implicit inputs.
-		implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file)
+		fileContexts := a.buildFileContexts(ctx)
+		implicitInputs = append(implicitInputs, fileContexts)
+
+		implicitInputs = append(implicitInputs, a.private_key_file, a.public_key_file)
 		optFlags = append(optFlags, "--pubkey "+a.public_key_file.String())
 
 		manifestPackageName := a.getOverrideManifestPackageName(ctx)
@@ -546,15 +582,18 @@
 			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
 		}
 
+		// Determine target/min sdk version from the context
+		// TODO(jiyong): make this as a function
 		moduleMinSdkVersion := a.minSdkVersion(ctx)
 		minSdkVersion := moduleMinSdkVersion.String()
 
-		// bundletool doesn't understand what "current" is. We need to transform it to codename
+		// bundletool doesn't understand what "current" is. We need to transform it to
+		// codename
 		if moduleMinSdkVersion.IsCurrent() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 		}
-		// apex module doesn't have a concept of target_sdk_version, hence for the time being
-		// targetSdkVersion == default targetSdkVersion of the branch.
+		// apex module doesn't have a concept of target_sdk_version, hence for the time
+		// being targetSdkVersion == default targetSdkVersion of the branch.
 		targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
 
 		if java.UseApiFingerprint(ctx) {
@@ -595,8 +634,8 @@
 		}
 
 		if a.properties.Apex_name != nil {
-			// If apex_name is set, apexer can skip checking if key name matches with apex name.
-			// Note that apex_manifest is also mended.
+			// If apex_name is set, apexer can skip checking if key name matches with
+			// apex name.  Note that apex_manifest is also mended.
 			optFlags = append(optFlags, "--do_not_check_keyname")
 		}
 
@@ -617,13 +656,14 @@
 				"image_dir":        imageDir.String(),
 				"copy_commands":    strings.Join(copyCommands, " && "),
 				"manifest":         a.manifestPbOut.String(),
-				"file_contexts":    a.fileContexts.String(),
+				"file_contexts":    fileContexts.String(),
 				"canned_fs_config": cannedFsConfig.String(),
 				"key":              a.private_key_file.String(),
 				"opt_flags":        strings.Join(optFlags, " "),
 			},
 		})
 
+		// TODO(jiyong): make the two rules below as separate functions
 		apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix)
 		bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip")
 		a.bundleModuleFile = bundleModuleFile
@@ -637,6 +677,15 @@
 
 		bundleConfig := a.buildBundleConfig(ctx)
 
+		var abis []string
+		for _, target := range ctx.MultiTargets() {
+			if len(target.Arch.Abi) > 0 {
+				abis = append(abis, target.Arch.Abi[0])
+			}
+		}
+
+		abis = android.FirstUniqueStrings(abis)
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        apexBundleRule,
 			Input:       apexProtoFile,
@@ -648,7 +697,7 @@
 				"config": bundleConfig.String(),
 			},
 		})
-	} else {
+	} else { // zipApex
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        zipApexRule,
 			Implicits:   implicitInputs,
@@ -663,16 +712,17 @@
 		})
 	}
 
+	////////////////////////////////////////////////////////////////////////////////////
+	// Step 4: Sign the APEX using signapk
 	a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+
+	pem, key := a.getCertificateAndPrivateKey(ctx)
 	rule := java.Signapk
 	args := map[string]string{
-		"certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(),
+		"certificates": pem.String() + " " + key.String(),
 		"flags":        "-a 4096", //alignment
 	}
-	implicits := android.Paths{
-		a.container_certificate_file,
-		a.container_private_key_file,
-	}
+	implicits := android.Paths{pem, key}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
 		rule = java.SignapkRE
 		args["implicits"] = strings.Join(implicits.Strings(), ",")
@@ -691,64 +741,64 @@
 	if a.installable() {
 		ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
 	}
-	a.buildFilesInfo(ctx)
 
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
 }
 
+// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
+type flattenedApexContext struct {
+	android.ModuleContext
+}
+
+func (c *flattenedApexContext) InstallBypassMake() bool {
+	return true
+}
+
+// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
+// single output file. It is a phony target for all the files under /system/apex/<name> directory.
+// This function creates the installation rules for the files.
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
-	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it
-	// reply true to `InstallBypassMake()` (thus making the call
-	// `android.PathForModuleInstall` below use `android.pathForInstallInMakeDir`
-	// instead of `android.PathForOutput`) to return the correct path to the flattened
-	// APEX (as its contents is installed by Make, not Soong).
-	factx := flattenedApexContext{ctx}
-	a.outputFile = android.PathForModuleInstall(&factx, "apex", a.Name())
-	a.buildFilesInfo(ctx)
-}
-
-func (a *apexBundle) setCertificateAndPrivateKey(ctx android.ModuleContext) {
-	if a.container_certificate_file == nil {
-		cert := String(a.properties.Certificate)
-		if cert == "" {
-			pem, key := ctx.Config().DefaultAppCertificate(ctx)
-			a.container_certificate_file = pem
-			a.container_private_key_file = key
-		} else {
-			defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
-			a.container_certificate_file = defaultDir.Join(ctx, cert+".x509.pem")
-			a.container_private_key_file = defaultDir.Join(ctx, cert+".pk8")
-		}
-	}
-}
-
-func (a *apexBundle) buildFilesInfo(ctx android.ModuleContext) {
+	bundleName := a.Name()
 	if a.installable() {
-		// For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along
-		// with other ordinary files.
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
-
-		// rename to apex_pubkey
-		copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:   android.Cp,
-			Input:  a.public_key_file,
-			Output: copiedPubkey,
-		})
-		a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
-
-		if a.properties.ApexType == flattenedApex {
-			apexBundleName := a.Name()
-			for _, fi := range a.filesInfo {
-				dir := filepath.Join("apex", apexBundleName, fi.installDir)
-				target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.Stem(), fi.builtFile)
-				for _, sym := range fi.symlinks {
-					ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
-				}
+		for _, fi := range a.filesInfo {
+			dir := filepath.Join("apex", bundleName, fi.installDir)
+			target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
+			for _, sym := range fi.symlinks {
+				ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
 			}
 		}
 	}
+
+	a.fileContexts = a.buildFileContexts(ctx)
+
+	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
+	// to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
+	// `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
+	// correct path to the flattened APEX (as its contents is installed by Make, not Soong).
+	// TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
+	// it and it actually points to a path that can never be built. Remove this.
+	factx := flattenedApexContext{ctx}
+	a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+}
+
+// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
+// the zip container of this APEX. See the description of the 'certificate' property for how
+// the cert and the private key are found.
+func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) {
+	if a.container_certificate_file != nil {
+		return a.container_certificate_file, a.container_private_key_file
+	}
+
+	cert := String(a.properties.Certificate)
+	if cert == "" {
+		return ctx.Config().DefaultAppCertificate(ctx)
+	}
+
+	defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+	pem = defaultDir.Join(ctx, cert+".x509.pem")
+	key = defaultDir.Join(ctx, cert+".pk8")
+	return pem, key
 }
 
 func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string {
diff --git a/apex/key.go b/apex/key.go
index 43764da..d9e3c10 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -127,13 +127,14 @@
 	apexKeyMap := make(map[string]apexKeyEntry)
 	ctx.VisitAllModules(func(module android.Module) {
 		if m, ok := module.(*apexBundle); ok && m.Enabled() && m.installable() {
+			pem, key := m.getCertificateAndPrivateKey(ctx)
 			apexKeyMap[m.Name()] = apexKeyEntry{
 				name:                  m.Name() + ".apex",
 				presigned:             false,
 				public_key:            m.public_key_file.String(),
 				private_key:           m.private_key_file.String(),
-				container_certificate: m.container_certificate_file.String(),
-				container_private_key: m.container_private_key_file.String(),
+				container_certificate: pem.String(),
+				container_private_key: key.String(),
 				partition:             m.PartitionTag(ctx.DeviceConfig()),
 			}
 		}
diff --git a/bazel/Android.bp b/bazel/Android.bp
new file mode 100644
index 0000000..0113726
--- /dev/null
+++ b/bazel/Android.bp
@@ -0,0 +1,10 @@
+bootstrap_go_package {
+    name: "soong-bazel",
+    pkgPath: "android/soong/bazel",
+    srcs: [
+        "properties.go",
+    ],
+    pluginFor: [
+        "soong_build",
+    ],
+}
diff --git a/bazel/bazelenv.sh b/bazel/bazelenv.sh
deleted file mode 100755
index fcf71f1..0000000
--- a/bazel/bazelenv.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Helper script for setting environment variables required for Bazel/Soong
-# mixed builds prototype. For development use only.
-#
-# Usage:
-#   export BAZEL_PATH=[some_bazel_path] && source bazelenv.sh
-#
-# If BAZEL_PATH is not set, `which bazel` will be used
-# to locate the appropriate bazel to use.
-
-
-# Function to find top of the source tree (if $TOP isn't set) by walking up the
-# tree.
-function gettop
-{
-    local TOPFILE=build/soong/root.bp
-    if [ -n "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
-        # The following circumlocution ensures we remove symlinks from TOP.
-        (cd $TOP; PWD= /bin/pwd)
-    else
-        if [ -f $TOPFILE ] ; then
-            # The following circumlocution (repeated below as well) ensures
-            # that we record the true directory name and not one that is
-            # faked up with symlink names.
-            PWD= /bin/pwd
-        else
-            local HERE=$PWD
-            T=
-            while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
-                \cd ..
-                T=`PWD= /bin/pwd -P`
-            done
-            \cd $HERE
-            if [ -f "$T/$TOPFILE" ]; then
-                echo $T
-            fi
-        fi
-    fi
-}
-
-BASE_DIR="$(mktemp -d)"
-
-if [ -z "$BAZEL_PATH" ] ; then
-    export BAZEL_PATH="$(which bazel)"
-fi
-
-# TODO(cparsons): Use USE_BAZEL=1 instead once "mixed Soong/Bazel builds" are
-# production ready.
-export USE_BAZEL_ANALYSIS=1
-# TODO(cparsons): Retrieve this information in either envsetup.sh or 
-# bazel.sh.
-export BAZEL_HOME="$BASE_DIR/bazelhome"
-export BAZEL_OUTPUT_BASE="$BASE_DIR/output"
-export BAZEL_WORKSPACE="$(gettop)"
-
-echo "USE_BAZEL_ANALYSIS=${USE_BAZEL_ANALYSIS}"
-echo "BAZEL_PATH=${BAZEL_PATH}"
-echo "BAZEL_HOME=${BAZEL_HOME}"
-echo "BAZEL_OUTPUT_BASE=${BAZEL_OUTPUT_BASE}"
-echo "BAZEL_WORKSPACE=${BAZEL_WORKSPACE}"
-
-mkdir -p $BAZEL_HOME
-mkdir -p $BAZEL_OUTPUT_BASE
diff --git a/bazel/master.WORKSPACE.bazel b/bazel/master.WORKSPACE.bazel
deleted file mode 100644
index e69de29..0000000
--- a/bazel/master.WORKSPACE.bazel
+++ /dev/null
diff --git a/bazel/properties.go b/bazel/properties.go
new file mode 100644
index 0000000..8bb1956
--- /dev/null
+++ b/bazel/properties.go
@@ -0,0 +1,27 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bazel
+
+type bazelModuleProperties struct {
+	// The label of the Bazel target replacing this Soong module.
+	Label string
+}
+
+// Properties contains common module properties for migration purposes.
+type Properties struct {
+	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
+	// this Soong module.
+	Bazel_module bazelModuleProperties
+}
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 38269cb..d32e4de 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -370,6 +370,9 @@
 			entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
 		}
 		entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", test.Properties.Test_mainline_modules...)
+		if Bool(test.Properties.Test_options.Unit_test) {
+			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+		}
 	})
 
 	androidMkWriteTestData(test.data, ctx, entries)
@@ -505,7 +508,7 @@
 	})
 }
 
-func (c *vendorSnapshotLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	// Each vendor snapshot is exported to androidMk only when BOARD_VNDK_VERSION != current
 	// and the version of the prebuilt is same as BOARD_VNDK_VERSION.
 	if c.shared() {
@@ -549,7 +552,7 @@
 	})
 }
 
-func (c *vendorSnapshotBinaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotBinaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.Class = "EXECUTABLES"
 
 	if c.androidMkVendorSuffix {
@@ -563,7 +566,7 @@
 	})
 }
 
-func (c *vendorSnapshotObjectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *snapshotObjectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.Class = "STATIC_LIBRARIES"
 
 	if c.androidMkVendorSuffix {
diff --git a/cc/binary.go b/cc/binary.go
index da29412..fbd293e 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -42,6 +42,7 @@
 	// extension (if any) appended
 	Symlinks []string `android:"arch_variant"`
 
+	// override the dynamic linker
 	DynamicLinker string `blueprint:"mutated"`
 
 	// Names of modules to be overridden. Listed modules can only be other binaries
@@ -80,6 +81,7 @@
 // Executables
 //
 
+// binaryDecorator is a decorator containing information for C++ binary modules.
 type binaryDecorator struct {
 	*baseLinker
 	*baseInstaller
@@ -105,11 +107,15 @@
 	// Location of the files that should be copied to dist dir when requested
 	distFiles android.TaggedDistFiles
 
+	// Action command lines to run directly after the binary is installed. For example,
+	// may be used to symlink runtime dependencies (such as bionic) alongside installation.
 	post_install_cmds []string
 }
 
 var _ linker = (*binaryDecorator)(nil)
 
+// linkerProps returns the list of individual properties objects relevant
+// for this binary.
 func (binary *binaryDecorator) linkerProps() []interface{} {
 	return append(binary.baseLinker.linkerProps(),
 		&binary.Properties,
@@ -117,6 +123,10 @@
 
 }
 
+// getStemWithoutSuffix returns the main section of the name to use for the symlink of
+// the main output file of this binary module. This may be derived from the module name
+// or other property overrides.
+// For the full symlink name, the `Suffix` property of a binary module must be appended.
 func (binary *binaryDecorator) getStemWithoutSuffix(ctx BaseModuleContext) string {
 	stem := ctx.baseModuleName()
 	if String(binary.Properties.Stem) != "" {
@@ -126,10 +136,14 @@
 	return stem
 }
 
+// getStem returns the full name to use for the symlink of the main output file of this binary
+// module. This may be derived from the module name and/or other property overrides.
 func (binary *binaryDecorator) getStem(ctx BaseModuleContext) string {
 	return binary.getStemWithoutSuffix(ctx) + String(binary.Properties.Suffix)
 }
 
+// linkerDeps augments and returns the given `deps` to contain dependencies on
+// modules common to most binaries, such as bionic libraries.
 func (binary *binaryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = binary.baseLinker.linkerDeps(ctx, deps)
 	if ctx.toolchain().Bionic() {
@@ -155,6 +169,13 @@
 			deps.LateStaticLibs = append(groupLibs, deps.LateStaticLibs...)
 		}
 
+		// Embed the linker into host bionic binaries. This is needed to support host bionic,
+		// as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
+		// either an absolute path, or relative from CWD. To work around this, we extract
+		// the load sections from the runtime linker ELF binary and embed them into each host
+		// bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
+		// binary, and then we use a special entry point to fix up the arguments passed by
+		// the kernel before jumping to the embedded linker.
 		if ctx.Os() == android.LinuxBionic && !binary.static() {
 			deps.DynamicLinker = "linker"
 			deps.LinkerFlagsFile = "host_bionic_linker_flags"
@@ -170,9 +191,13 @@
 }
 
 func (binary *binaryDecorator) isDependencyRoot() bool {
+	// Binaries are always the dependency root.
 	return true
 }
 
+// NewBinary builds and returns a new Module corresponding to a C++ binary.
+// Individual module implementations which comprise a C++ binary should call this function,
+// set some fields on the result, and then call the Init function.
 func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
 	module := newModule(hod, android.MultilibFirst)
 	binary := &binaryDecorator{
@@ -190,11 +215,15 @@
 	return module, binary
 }
 
+// linkerInit initializes dynamic properties of the linker (such as runpath) based
+// on properties of this binary.
 func (binary *binaryDecorator) linkerInit(ctx BaseModuleContext) {
 	binary.baseLinker.linkerInit(ctx)
 
 	if !ctx.toolchain().Bionic() {
 		if ctx.Os() == android.Linux {
+			// Unless explicitly specified otherwise, host static binaries are built with -static
+			// if HostStaticBinaries is true for the product configuration.
 			if binary.Properties.Static_executable == nil && ctx.Config().HostStaticBinaries() {
 				binary.Properties.Static_executable = BoolPtr(true)
 			}
@@ -217,9 +246,13 @@
 	return true
 }
 
+// linkerFlags returns a Flags object containing linker flags that are defined
+// by this binary, or that are implied by attributes of this binary. These flags are
+// combined with the given flags.
 func (binary *binaryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = binary.baseLinker.linkerFlags(ctx, flags)
 
+	// Passing -pie to clang for Windows binaries causes a warning that -pie is unused.
 	if ctx.Host() && !ctx.Windows() && !binary.static() {
 		if !ctx.Config().IsEnvTrue("DISABLE_HOST_PIE") {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-pie")
@@ -248,7 +281,7 @@
 				"-Bstatic",
 				"-Wl,--gc-sections",
 			)
-		} else {
+		} else { // not static
 			if flags.DynamicLinker == "" {
 				if binary.Properties.DynamicLinker != "" {
 					flags.DynamicLinker = binary.Properties.DynamicLinker
@@ -288,7 +321,7 @@
 				"-Wl,-z,nocopyreloc",
 			)
 		}
-	} else {
+	} else { // not bionic
 		if binary.static() {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-static")
 		}
@@ -300,6 +333,9 @@
 	return flags
 }
 
+// link registers actions to link this binary, and sets various fields
+// on this binary to reflect information that should be exported up the build
+// tree (for example, exported flags and include paths).
 func (binary *binaryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
@@ -309,6 +345,7 @@
 
 	var linkerDeps android.Paths
 
+	// Add flags from linker flags file.
 	if deps.LinkerFlagsFile.Valid() {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "$$(cat "+deps.LinkerFlagsFile.String()+")")
 		linkerDeps = append(linkerDeps, deps.LinkerFlagsFile.Path())
@@ -342,12 +379,15 @@
 
 	outputFile = maybeInjectBoringSSLHash(ctx, outputFile, binary.Properties.Inject_bssl_hash, fileName)
 
+	// If use_version_lib is true, make an android::build::GetBuildNumber() function available.
 	if Bool(binary.baseLinker.Properties.Use_version_lib) {
 		if ctx.Host() {
 			versionedOutputFile := outputFile
 			outputFile = android.PathForModuleOut(ctx, "unversioned", fileName)
 			binary.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		} else {
+			// When dist'ing a library or binary that has use_version_lib set, always
+			// distribute the stamped version, even for the device.
 			versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
 			binary.distFiles = android.MakeDefaultDistFiles(versionedOutputFile)
 
@@ -361,6 +401,7 @@
 		}
 	}
 
+	// Handle host bionic linker symbols.
 	if ctx.Os() == android.LinuxBionic && !binary.static() {
 		injectedOutputFile := outputFile
 		outputFile = android.PathForModuleOut(ctx, "prelinker", fileName)
@@ -386,6 +427,7 @@
 	linkerDeps = append(linkerDeps, objs.tidyFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
+	// Register link action.
 	TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
 		builderFlags, outputFile, nil)
@@ -406,6 +448,7 @@
 			ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix")
 		}
 		if ctx.TargetPrimary() {
+			// Install a symlink to the preferred architecture
 			symlinkName := binary.getStemWithoutSuffix(ctx)
 			binary.symlinks = append(binary.symlinks, symlinkName)
 			binary.preferredArchSymlink = symlinkName
diff --git a/cc/cc.go b/cc/cc.go
index 0724a76..6deb1b4 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -88,7 +88,7 @@
 		ctx.TopDown("double_loadable", checkDoubleLoadableLibraries).Parallel()
 	})
 
-	android.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
+	ctx.RegisterSingletonType("kythe_extract_all", kytheExtractAllFactory)
 }
 
 type Deps struct {
@@ -305,10 +305,11 @@
 
 	// Normally Soong uses the directory structure to decide which modules
 	// should be included (framework) or excluded (non-framework) from the
-	// vendor snapshot, but this property allows a partner to exclude a
-	// module normally thought of as a framework module from the vendor
-	// snapshot.
-	Exclude_from_vendor_snapshot *bool
+	// different snapshots (vendor, recovery, etc.), but these properties
+	// allow a partner to exclude a module normally thought of as a
+	// framework module from a snapshot.
+	Exclude_from_vendor_snapshot   *bool
+	Exclude_from_recovery_snapshot *bool
 }
 
 type VendorProperties struct {
@@ -368,7 +369,7 @@
 	useSdk() bool
 	sdkVersion() string
 	useVndk() bool
-	isNdk() bool
+	isNdk(config android.Config) bool
 	isLlndk(config android.Config) bool
 	isLlndkPublic(config android.Config) bool
 	isVndkPrivate(config android.Config) bool
@@ -954,8 +955,8 @@
 	return c.coverage.Properties.IsCoverageVariant
 }
 
-func (c *Module) IsNdk() bool {
-	return inList(c.BaseModuleName(), ndkKnownLibs)
+func (c *Module) IsNdk(config android.Config) bool {
+	return inList(c.BaseModuleName(), *getNDKKnownLibs(config))
 }
 
 func (c *Module) isLlndk(config android.Config) bool {
@@ -1067,6 +1068,10 @@
 	return Bool(c.Properties.Exclude_from_vendor_snapshot)
 }
 
+func (c *Module) ExcludeFromRecoverySnapshot() bool {
+	return Bool(c.Properties.Exclude_from_recovery_snapshot)
+}
+
 func isBionic(name string) bool {
 	switch name {
 	case "libc", "libm", "libdl", "libdl_android", "linker":
@@ -1156,8 +1161,8 @@
 	return ctx.mod.UseVndk()
 }
 
-func (ctx *moduleContextImpl) isNdk() bool {
-	return ctx.mod.IsNdk()
+func (ctx *moduleContextImpl) isNdk(config android.Config) bool {
+	return ctx.mod.IsNdk(config)
 }
 
 func (ctx *moduleContextImpl) isLlndk(config android.Config) bool {
@@ -1777,7 +1782,7 @@
 			for _, entry := range list {
 				// strip #version suffix out
 				name, _ := StubsLibNameAndVersion(entry)
-				if ctx.useSdk() && inList(name, ndkKnownLibs) {
+				if ctx.useSdk() && inList(name, *getNDKKnownLibs(ctx.Config())) {
 					variantLibs = append(variantLibs, name+ndkLibrarySuffix)
 				} else if ctx.useVndk() {
 					nonvariantLibs = append(nonvariantLibs, rewriteVendorLibs(entry))
@@ -1852,6 +1857,11 @@
 		return
 	}
 
+	// sysprop_library has to support both C++ and Java. So sysprop_library internally creates one
+	// C++ implementation library and one Java implementation library. When a module links against
+	// sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a
+	// map from sysprop_library to implementation library; it will be used in whole_static_libs,
+	// static_libs, and shared_libs.
 	syspropImplLibraries := syspropImplLibraries(actx.Config())
 	vendorSnapshotStaticLibs := vendorSnapshotStaticLibs(actx.Config())
 
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 7c60686..7c98585 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -1551,6 +1551,8 @@
 	android.CheckErrorsAgainstExpectations(t, errs, []string{
 		`module "libvendor\{.+,image:vendor.+,arch:arm64_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
 		`module "libvendor\{.+,image:vendor.+,arch:arm_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
+		`module "libvendor\{.+,image:vendor.+,arch:arm64_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
+		`module "libvendor\{.+,image:vendor.+,arch:arm_.+\}" in vendor proprietary path "device" may not use "exclude_from_vendor_snapshot: true"`,
 	})
 }
 
@@ -1597,6 +1599,132 @@
 	})
 }
 
+func TestRecoverySnapshotCapture(t *testing.T) {
+	bp := `
+	cc_library {
+		name: "libvndk",
+		vendor_available: true,
+		recovery_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "librecovery",
+		recovery: true,
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "librecovery_available",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	cc_library_headers {
+		name: "librecovery_headers",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	cc_binary {
+		name: "recovery_bin",
+		recovery: true,
+		nocrt: true,
+	}
+
+	cc_binary {
+		name: "recovery_available_bin",
+		recovery_available: true,
+		nocrt: true,
+	}
+
+	toolchain_library {
+		name: "libb",
+		recovery_available: true,
+		src: "libb.a",
+	}
+
+	cc_object {
+		name: "obj",
+		recovery_available: true,
+	}
+`
+	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	ctx := testCcWithConfig(t, config)
+
+	// Check Recovery snapshot output.
+
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var jsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, only recovery_available modules are captured.
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "libvndk.so.json"),
+			filepath.Join(sharedDir, "librecovery.so.json"),
+			filepath.Join(sharedDir, "librecovery_available.so.json"))
+
+		// For static libraries, all recovery:true and recovery_available modules are captured.
+		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libb.a.json"),
+			filepath.Join(staticDir, "librecovery.a.json"),
+			filepath.Join(staticDir, "librecovery_available.a.json"))
+
+		// For binary executables, all recovery:true and recovery_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			checkSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			checkSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "recovery_bin.json"),
+				filepath.Join(binaryDir, "recovery_available_bin.json"))
+		}
+
+		// For header libraries, all vendor:true and vendor_available modules are captured.
+		headerDir := filepath.Join(snapshotVariantPath, archDir, "header")
+		jsonFiles = append(jsonFiles, filepath.Join(headerDir, "librecovery_headers.json"))
+
+		// For object modules, all vendor:true and vendor_available modules are captured.
+		objectVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
+		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found", jsonFile)
+		}
+	}
+}
+
 func TestDoubleLoadableDepError(t *testing.T) {
 	// Check whether an error is emitted when a LLNDK depends on a non-double_loadable VNDK lib.
 	testCcError(t, "module \".*\" variant \".*\": link.* \".*\" which is not LL-NDK, VNDK-SP, .*double_loadable", `
diff --git a/cc/compiler.go b/cc/compiler.go
index 3c86d20..04ed80d 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -687,6 +687,9 @@
 	// list of shared libraries that provide headers for this binding.
 	Shared_libs []string `android:"arch_variant"`
 
+	// List of libraries which export include paths required for this module
+	Header_libs []string `android:"arch_variant,variant_prepend"`
+
 	// list of clang flags required to correctly interpret the headers.
 	Cflags []string `android:"arch_variant"`
 
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index d18ae25..563ce76 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -21,6 +21,8 @@
 	"android.hardware.automotive.occupant_awareness-ndk_platform",
 	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-ndk_platform",
+	"android.hardware.keymint-ndk_platform",
+	"android.hardware.keymint-unstable-ndk_platform",
 	"android.hardware.nfc@1.2",
 	"android.hardware.power-ndk_platform",
 	"android.hardware.rebootescrow-ndk_platform",
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index 81c907d..d7ff580 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -135,7 +135,7 @@
 
 func getMacTools(ctx android.PackageVarContext) *macPlatformTools {
 	macTools.once.Do(func() {
-		xcrunTool := ctx.Config().HostSystemTool("xcrun")
+		xcrunTool := ctx.Config().NonHermeticHostSystemTool("xcrun")
 
 		xcrun := func(args ...string) string {
 			if macTools.err != nil {
diff --git a/cc/gen.go b/cc/gen.go
index ccc3d0e..5895b31 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -75,7 +75,11 @@
 	cmd := rule.Command()
 
 	// Fix up #line markers to not use the sbox temporary directory
-	sedCmd := "sed -i.bak 's#__SBOX_OUT_DIR__#" + outDir.String() + "#'"
+	// android.SboxPathForOutput(outDir, outDir) returns the sbox placeholder for the out
+	// directory itself, without any filename appended.
+	// TODO(ccross): make this cmd.PathForOutput(outDir) instead.
+	sboxOutDir := android.SboxPathForOutput(outDir, outDir)
+	sedCmd := "sed -i.bak 's#" + sboxOutDir + "#" + outDir.String() + "#'"
 	rule.Command().Text(sedCmd).Input(outFile)
 	rule.Command().Text(sedCmd).Input(headerFile)
 
@@ -228,7 +232,8 @@
 	var yaccRule_ *android.RuleBuilder
 	yaccRule := func() *android.RuleBuilder {
 		if yaccRule_ == nil {
-			yaccRule_ = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "yacc"))
+			yaccRule_ = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "yacc"),
+				android.PathForModuleGen(ctx, "yacc.sbox.textproto"))
 		}
 		return yaccRule_
 	}
@@ -257,7 +262,8 @@
 			deps = append(deps, headerFile)
 		case ".aidl":
 			if aidlRule == nil {
-				aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl"))
+				aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl"),
+					android.PathForModuleGen(ctx, "aidl.sbox.textproto"))
 			}
 			cppFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp")
 			depFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp.d")
diff --git a/cc/gen_test.go b/cc/gen_test.go
index 4b9a36e..41ef95c 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -18,6 +18,8 @@
 	"path/filepath"
 	"strings"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestGen(t *testing.T) {
@@ -56,13 +58,14 @@
 		}`)
 
 		aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
+		aidlManifest := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
 		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
 		if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.Local.CommonFlags) {
 			t.Errorf("missing aidl includes in global flags")
 		}
 
-		aidlCommand := aidl.RuleParams.Command
+		aidlCommand := android.RuleBuilderSboxProtoForTests(t, aidlManifest).Commands[0].GetCommand()
 		if !strings.Contains(aidlCommand, "-Isub") {
 			t.Errorf("aidl command for c.aidl should contain \"-Isub\", but was %q", aidlCommand)
 		}
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index 0c7952b..fa0c6f2 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -66,14 +66,14 @@
 
 	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out_arm")
 	expected := []string{"foo"}
-	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
-		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Inputs.Strings())
+	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
+		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
 	}
 
 	gen = ctx.ModuleForTests("gen", "android_arm64_armv8-a").Output("out_arm64")
 	expected = []string{"bar"}
-	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
-		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Inputs.Strings())
+	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
+		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Implicits.Strings())
 	}
 }
 
@@ -108,10 +108,10 @@
 	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out")
 	expected := []string{"libboth.so", "libshared.so", "libstatic.a"}
 	var got []string
-	for _, input := range gen.Inputs {
+	for _, input := range gen.Implicits {
 		got = append(got, input.Base())
 	}
-	if !reflect.DeepEqual(expected, got) {
+	if !reflect.DeepEqual(expected, got[:len(expected)]) {
 		t.Errorf(`want inputs %v, got %v`, expected, got)
 	}
 }
diff --git a/cc/library.go b/cc/library.go
index eeddd90..7ae75f2 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -567,7 +567,7 @@
 		return ""
 	}
 	// Return NDK if the library is both NDK and LLNDK.
-	if ctx.isNdk() {
+	if ctx.isNdk(ctx.Config()) {
 		return "NDK"
 	}
 	if ctx.isLlndkPublic(ctx.Config()) {
@@ -1099,7 +1099,7 @@
 
 func getRefAbiDumpFile(ctx ModuleContext, vndkVersion, fileName string) android.Path {
 	// The logic must be consistent with classifySourceAbiDump.
-	isNdk := ctx.isNdk()
+	isNdk := ctx.isNdk(ctx.Config())
 	isLlndkOrVndk := ctx.isLlndkPublic(ctx.Config()) || (ctx.useVndk() && ctx.isVndk())
 
 	refAbiDumpTextFile := android.PathForVndkRefAbiDump(ctx, vndkVersion, fileName, isNdk, isLlndkOrVndk, false)
@@ -1153,7 +1153,7 @@
 			library.sAbiDiff = SourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
 				refAbiDumpFile, fileName, exportedHeaderFlags,
 				Bool(library.Properties.Header_abi_checker.Check_all_apis),
-				ctx.isLlndk(ctx.Config()), ctx.isNdk(), ctx.isVndkExt())
+				ctx.isLlndk(ctx.Config()), ctx.isNdk(ctx.Config()), ctx.isVndkExt())
 		}
 	}
 }
@@ -1202,15 +1202,21 @@
 		}
 	}
 
+	// If the library is sysprop_library, expose either public or internal header selectively.
 	if library.baseCompiler.hasSrcExt(".sysprop") {
 		dir := android.PathForModuleGen(ctx, "sysprop", "include")
 		if library.Properties.Sysprop.Platform != nil {
-			isProduct := ctx.ProductSpecific() && !ctx.useVndk()
-			isVendor := ctx.useVndk()
+			isClientProduct := ctx.ProductSpecific() && !ctx.useVndk()
+			isClientVendor := ctx.useVndk()
 			isOwnerPlatform := Bool(library.Properties.Sysprop.Platform)
 
+			// If the owner is different from the user, expose public header. That is,
+			// 1) if the user is product (as owner can only be platform / vendor)
+			// 2) if one is platform and the other is vendor
+			// Exceptions are ramdisk and recovery. They are not enforced at all. So
+			// they always use internal header.
 			if !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() &&
-				(isProduct || (isOwnerPlatform == isVendor)) {
+				(isClientProduct || (isOwnerPlatform == isClientVendor)) {
 				dir = android.PathForModuleGen(ctx, "sysprop/public", "include")
 			}
 		}
diff --git a/cc/linker.go b/cc/linker.go
index cbf8898..9d4a643 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -211,6 +211,7 @@
 	linker.Properties.Ldflags = append(linker.Properties.Ldflags, flags...)
 }
 
+// linkerInit initializes dynamic properties of the linker (such as runpath).
 func (linker *baseLinker) linkerInit(ctx BaseModuleContext) {
 	if ctx.toolchain().Is64Bit() {
 		linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, "../lib64", "lib64")
diff --git a/cc/makevars.go b/cc/makevars.go
index dcfd6d8..bd8aab5 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -171,6 +171,7 @@
 	ctx.StrictRaw("SRC_HEADERS", strings.Join(includes, " "))
 	ctx.StrictRaw("SRC_SYSTEM_HEADERS", strings.Join(systemIncludes, " "))
 
+	ndkKnownLibs := *getNDKKnownLibs(ctx.Config())
 	sort.Strings(ndkKnownLibs)
 	ctx.Strict("NDK_KNOWN_LIBS", strings.Join(ndkKnownLibs, " "))
 
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 9097e7b..a5c43fe 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -45,8 +45,7 @@
 
 	ndkLibrarySuffix = ".ndk"
 
-	// Added as a variation dependency via depsMutator.
-	ndkKnownLibs = []string{}
+	ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
 	// protects ndkKnownLibs writes during parallel BeginMutator.
 	ndkKnownLibsLock sync.Mutex
 )
@@ -158,6 +157,12 @@
 	return true
 }
 
+func getNDKKnownLibs(config android.Config) *[]string {
+	return config.Once(ndkKnownLibsKey, func() interface{} {
+		return &[]string{}
+	}).(*[]string)
+}
+
 func (c *stubDecorator) compilerInit(ctx BaseModuleContext) {
 	c.baseCompiler.compilerInit(ctx)
 
@@ -168,12 +173,13 @@
 
 	ndkKnownLibsLock.Lock()
 	defer ndkKnownLibsLock.Unlock()
-	for _, lib := range ndkKnownLibs {
+	ndkKnownLibs := getNDKKnownLibs(ctx.Config())
+	for _, lib := range *ndkKnownLibs {
 		if lib == name {
 			return
 		}
 	}
-	ndkKnownLibs = append(ndkKnownLibs, name)
+	*ndkKnownLibs = append(*ndkKnownLibs, name)
 }
 
 func addStubLibraryCompilerFlags(flags Flags) Flags {
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 5bf334e..ee4de6e 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -32,7 +32,7 @@
 	// * Configure that we are inside make
 	// * Add CommonOS to ensure that androidmk processing works.
 	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetInMakeForTests(config)
+	android.SetKatiEnabledForTests(config)
 
 	for _, handler := range handlers {
 		handler(config)
diff --git a/cc/proto.go b/cc/proto.go
index ae988ec..9c102a2 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -130,6 +130,8 @@
 			flags.protoC = true
 			flags.protoOptionsFile = true
 			flags.proto.OutTypeFlag = "--nanopb_out"
+			// Disable nanopb timestamps to support remote caching.
+			flags.proto.OutParams = append(flags.proto.OutParams, "-T")
 			plugin = "protoc-gen-nanopb"
 		case "full":
 			flags.proto.OutTypeFlag = "--cpp_out"
diff --git a/cc/sanitize.go b/cc/sanitize.go
index b1326d9..dbc52a5 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -172,6 +172,12 @@
 		No_recover       []string
 	}
 
+	// Sanitizers to run with flag configuration specified
+	Config struct {
+		// Enables CFI support flags for assembly-heavy libraries
+		Cfi_assembly_support *bool `android:"arch_variant"`
+	}
+
 	// value to pass to -fsanitize-recover=
 	Recover []string
 
@@ -543,6 +549,9 @@
 
 		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
+		if Bool(sanitize.Properties.Sanitize.Config.Cfi_assembly_support) {
+			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-cfi-canonical-jump-tables")
+		}
 		// Only append the default visibility flag if -fvisibility has not already been set
 		// to hidden.
 		if !inList("-fvisibility=hidden", flags.Local.CFlags) {
@@ -847,7 +856,7 @@
 				return true
 			}
 
-			if p, ok := d.linker.(*vendorSnapshotLibraryDecorator); ok {
+			if p, ok := d.linker.(*snapshotLibraryDecorator); ok {
 				if Bool(p.properties.Sanitize_minimal_dep) {
 					c.sanitize.Properties.MinimalRuntimeDep = true
 				}
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index 05c06ac..a3d52e6 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -60,7 +60,8 @@
 func isSnapshotAware(ctx android.ModuleContext, m *Module, apexInfo android.ApexInfo) bool {
 	if _, _, ok := isVndkSnapshotLibrary(ctx.DeviceConfig(), m, apexInfo); ok {
 		return ctx.Config().VndkSnapshotBuildArtifacts()
-	} else if isVendorSnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) {
+	} else if isVendorSnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) ||
+		isRecoverySnapshotModule(m, isVendorProprietaryPath(ctx.ModuleDir()), apexInfo) {
 		return true
 	}
 	return false
diff --git a/cc/strip.go b/cc/strip.go
index 18150dc..e9aec91 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -20,23 +20,35 @@
 	"android/soong/android"
 )
 
+// StripProperties defines the type of stripping applied to the module.
 type StripProperties struct {
 	Strip struct {
-		None                         *bool    `android:"arch_variant"`
-		All                          *bool    `android:"arch_variant"`
-		Keep_symbols                 *bool    `android:"arch_variant"`
-		Keep_symbols_list            []string `android:"arch_variant"`
-		Keep_symbols_and_debug_frame *bool    `android:"arch_variant"`
+		// whether to disable all stripping.
+		None *bool `android:"arch_variant"`
+
+		// whether to strip everything, including the mini debug info.
+		All *bool `android:"arch_variant"`
+
+		// whether to keep the symbols.
+		Keep_symbols *bool `android:"arch_variant"`
+
+		// keeps only the symbols defined here.
+		Keep_symbols_list []string `android:"arch_variant"`
+
+		// whether to keep the symbols and the debug frames.
+		Keep_symbols_and_debug_frame *bool `android:"arch_variant"`
 	} `android:"arch_variant"`
 }
 
+// Stripper defines the stripping actions and properties for a module.
 type Stripper struct {
 	StripProperties StripProperties
 }
 
+// NeedsStrip determines if stripping is required for a module.
 func (stripper *Stripper) NeedsStrip(actx android.ModuleContext) bool {
-	// TODO(ccross): enable host stripping when embedded in make?  Make never had support for stripping host binaries.
-	return (!actx.Config().EmbeddedInMake() || actx.Device()) && !Bool(stripper.StripProperties.Strip.None)
+	// 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)
 }
 
 func (stripper *Stripper) strip(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
@@ -60,11 +72,17 @@
 	}
 }
 
+// StripExecutableOrSharedLib strips a binary or shared library from its debug
+// symbols and other debugging information. The helper function
+// flagsToStripFlags may be used to generate the flags argument.
 func (stripper *Stripper) StripExecutableOrSharedLib(actx android.ModuleContext, in android.Path,
 	out android.ModuleOutPath, flags StripFlags) {
 	stripper.strip(actx, in, out, flags, false)
 }
 
+// StripStaticLib strips a static library from its debug symbols and other
+// debugging information. The helper function flagsToStripFlags may be used to
+// generate the flags argument.
 func (stripper *Stripper) StripStaticLib(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
 	flags StripFlags) {
 	stripper.strip(actx, in, out, flags, true)
diff --git a/cc/sysprop.go b/cc/sysprop.go
index 6cac7fb..f578b50 100644
--- a/cc/sysprop.go
+++ b/cc/sysprop.go
@@ -14,6 +14,25 @@
 
 package cc
 
+// This file contains a map to redirect dependencies towards sysprop_library.
+// As sysprop_library has to support both Java and C++, sysprop_library internally
+// generates cc_library and java_library. For example, the following sysprop_library
+//
+//     sysprop_library {
+//         name: "foo",
+//     }
+//
+// will internally generate with prefix "lib"
+//
+//     cc_library {
+//         name: "libfoo",
+//     }
+//
+// When a cc module links against "foo", build system will redirect the
+// dependency to "libfoo". To do that, SyspropMutator gathers all sysprop_library,
+// records their cc implementation library names to a map. The map will be used in
+// cc.Module.DepsMutator.
+
 import (
 	"sync"
 
@@ -22,7 +41,7 @@
 
 type syspropLibraryInterface interface {
 	BaseModuleName() string
-	CcModuleName() string
+	CcImplementationModuleName() string
 }
 
 var (
@@ -43,6 +62,8 @@
 		syspropImplLibrariesLock.Lock()
 		defer syspropImplLibrariesLock.Unlock()
 
-		syspropImplLibraries[m.BaseModuleName()] = m.CcModuleName()
+		// BaseModuleName is the name of sysprop_library
+		// CcImplementationModuleName is the name of cc_library generated by sysprop_library
+		syspropImplLibraries[m.BaseModuleName()] = m.CcImplementationModuleName()
 	}
 }
diff --git a/cc/test.go b/cc/test.go
index 619dc4d..3772691 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -46,6 +46,9 @@
 
 	// a list of extra test configuration files that should be installed with the module.
 	Extra_test_configs []string `android:"path,arch_variant"`
+
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
 }
 
 type TestBinaryProperties struct {
diff --git a/cc/testing.go b/cc/testing.go
index a3235e9..95a93a0 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -166,6 +166,10 @@
 			product_available: true,
 			recovery_available: true,
 			src: "",
+			apex_available: [
+				"//apex_available:platform",
+				"//apex_available:anyapex",
+			],
 		}
 
 		toolchain_library {
@@ -565,6 +569,7 @@
 	RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
+	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
 
 	return ctx
 }
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 78bde38..6563f6e 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -25,6 +25,115 @@
 	"android/soong/android"
 )
 
+// Defines the specifics of different images to which the snapshot process is
+// applicable, e.g., vendor, recovery, ramdisk.
+type image interface {
+	// Used to register callbacks with the build system.
+	init()
+
+	// Function that returns true if the module is included in this image.
+	// Using a function return instead of a value to prevent early
+	// evalution of a function that may be not be defined.
+	inImage(m *Module) func() bool
+
+	// Returns the value of the "available" property for a given module for
+	// and snapshot, e.g., "vendor_available", "recovery_available", etc.
+	// or nil if the property is not defined.
+	available(m *Module) *bool
+
+	// Returns true if a dir under source tree is an SoC-owned proprietary
+	// directory, such as device/, vendor/, etc.
+	//
+	// For a given snapshot (e.g., vendor, recovery, etc.) if
+	// isProprietaryPath(dir) returns true, then the module in dir will be
+	// built from sources.
+	isProprietaryPath(dir string) bool
+
+	// Whether to include VNDK in the snapshot for this image.
+	includeVndk() bool
+
+	// Whether a given module has been explicitly excluded from the
+	// snapshot, e.g., using the exclude_from_vendor_snapshot or
+	// exclude_from_recovery_snapshot properties.
+	excludeFromSnapshot(m *Module) bool
+}
+
+type vendorImage struct{}
+type recoveryImage struct{}
+
+func (vendorImage) init() {
+	android.RegisterSingletonType(
+		"vendor-snapshot", VendorSnapshotSingleton)
+	android.RegisterModuleType(
+		"vendor_snapshot_shared", VendorSnapshotSharedFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_static", VendorSnapshotStaticFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_header", VendorSnapshotHeaderFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_binary", VendorSnapshotBinaryFactory)
+	android.RegisterModuleType(
+		"vendor_snapshot_object", VendorSnapshotObjectFactory)
+}
+
+func (vendorImage) inImage(m *Module) func() bool {
+	return m.inVendor
+}
+
+func (vendorImage) available(m *Module) *bool {
+	return m.VendorProperties.Vendor_available
+}
+
+func (vendorImage) isProprietaryPath(dir string) bool {
+	return isVendorProprietaryPath(dir)
+}
+
+func (vendorImage) includeVndk() bool {
+	return true
+}
+
+func (vendorImage) excludeFromSnapshot(m *Module) bool {
+	return m.ExcludeFromVendorSnapshot()
+}
+
+func (recoveryImage) init() {
+	android.RegisterSingletonType(
+		"recovery-snapshot", RecoverySnapshotSingleton)
+	android.RegisterModuleType(
+		"recovery_snapshot_shared", RecoverySnapshotSharedFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_static", RecoverySnapshotStaticFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_header", RecoverySnapshotHeaderFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_binary", RecoverySnapshotBinaryFactory)
+	android.RegisterModuleType(
+		"recovery_snapshot_object", RecoverySnapshotObjectFactory)
+}
+
+func (recoveryImage) inImage(m *Module) func() bool {
+	return m.InRecovery
+}
+
+func (recoveryImage) available(m *Module) *bool {
+	return m.Properties.Recovery_available
+}
+
+func (recoveryImage) isProprietaryPath(dir string) bool {
+	return isRecoveryProprietaryPath(dir)
+}
+
+func (recoveryImage) includeVndk() bool {
+	return false
+}
+
+func (recoveryImage) excludeFromSnapshot(m *Module) bool {
+	return m.ExcludeFromRecoverySnapshot()
+}
+
+var vendorImageSingleton vendorImage
+var recoveryImageSingleton recoveryImage
+
 const (
 	vendorSnapshotHeaderSuffix = ".vendor_header."
 	vendorSnapshotSharedSuffix = ".vendor_shared."
@@ -33,6 +142,14 @@
 	vendorSnapshotObjectSuffix = ".vendor_object."
 )
 
+const (
+	recoverySnapshotHeaderSuffix = ".recovery_header."
+	recoverySnapshotSharedSuffix = ".recovery_shared."
+	recoverySnapshotStaticSuffix = ".recovery_static."
+	recoverySnapshotBinarySuffix = ".recovery_binary."
+	recoverySnapshotObjectSuffix = ".recovery_object."
+)
+
 var (
 	vendorSnapshotsLock         sync.Mutex
 	vendorSuffixModulesKey      = android.NewOnceKey("vendorSuffixModules")
@@ -136,7 +253,7 @@
 	}
 }
 
-type vendorSnapshotLibraryProperties struct {
+type snapshotLibraryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 
@@ -161,25 +278,25 @@
 	setSanitizerVariation(t sanitizerType, enabled bool)
 }
 
-type vendorSnapshotLibraryDecorator struct {
+type snapshotLibraryDecorator struct {
 	vendorSnapshotModuleBase
 	*libraryDecorator
-	properties          vendorSnapshotLibraryProperties
+	properties          snapshotLibraryProperties
 	sanitizerProperties struct {
 		CfiEnabled bool `blueprint:"mutated"`
 
 		// Library flags for cfi variant.
-		Cfi vendorSnapshotLibraryProperties `android:"arch_variant"`
+		Cfi snapshotLibraryProperties `android:"arch_variant"`
 	}
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotLibraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+func (p *snapshotLibraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	p.libraryDecorator.libName = strings.TrimSuffix(ctx.ModuleName(), p.NameSuffix())
 	return p.libraryDecorator.linkerFlags(ctx, flags)
 }
 
-func (p *vendorSnapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
 	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
 		return false
@@ -190,7 +307,7 @@
 	return true
 }
 
-func (p *vendorSnapshotLibraryDecorator) link(ctx ModuleContext,
+func (p *snapshotLibraryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	m := ctx.Module().(*Module)
 	p.androidMkVendorSuffix = vendorSuffixModules(ctx.Config())[m.BaseModuleName()]
@@ -246,17 +363,17 @@
 	return in
 }
 
-func (p *vendorSnapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
+func (p *snapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
 	if p.matchesWithDevice(ctx.DeviceConfig()) && (p.shared() || p.static()) {
 		p.baseInstaller.install(ctx, file)
 	}
 }
 
-func (p *vendorSnapshotLibraryDecorator) nativeCoverage() bool {
+func (p *snapshotLibraryDecorator) nativeCoverage() bool {
 	return false
 }
 
-func (p *vendorSnapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
+func (p *snapshotLibraryDecorator) isSanitizerEnabled(t sanitizerType) bool {
 	switch t {
 	case cfi:
 		return p.sanitizerProperties.Cfi.Src != nil
@@ -265,7 +382,7 @@
 	}
 }
 
-func (p *vendorSnapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
+func (p *snapshotLibraryDecorator) setSanitizerVariation(t sanitizerType, enabled bool) {
 	if !enabled {
 		return
 	}
@@ -277,14 +394,14 @@
 	}
 }
 
-func vendorSnapshotLibrary(suffix string) (*Module, *vendorSnapshotLibraryDecorator) {
+func snapshotLibrary(suffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
 	module.sanitize = nil
 	library.disableStripping()
 
-	prebuilt := &vendorSnapshotLibraryDecorator{
+	prebuilt := &snapshotLibraryDecorator{
 		libraryDecorator: library,
 	}
 
@@ -310,38 +427,56 @@
 }
 
 func VendorSnapshotSharedFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotSharedSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotSharedSuffix)
+	prebuilt.libraryDecorator.BuildOnlyShared()
+	return module.Init()
+}
+
+func RecoverySnapshotSharedFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
 
 func VendorSnapshotStaticFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotStaticSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotStaticSuffix)
+	prebuilt.libraryDecorator.BuildOnlyStatic()
+	return module.Init()
+}
+
+func RecoverySnapshotStaticFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
 
 func VendorSnapshotHeaderFactory() android.Module {
-	module, prebuilt := vendorSnapshotLibrary(vendorSnapshotHeaderSuffix)
+	module, prebuilt := snapshotLibrary(vendorSnapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
 
-var _ snapshotSanitizer = (*vendorSnapshotLibraryDecorator)(nil)
+func RecoverySnapshotHeaderFactory() android.Module {
+	module, prebuilt := snapshotLibrary(recoverySnapshotHeaderSuffix)
+	prebuilt.libraryDecorator.HeaderOnly()
+	return module.Init()
+}
 
-type vendorSnapshotBinaryProperties struct {
+var _ snapshotSanitizer = (*snapshotLibraryDecorator)(nil)
+
+type snapshotBinaryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 }
 
-type vendorSnapshotBinaryDecorator struct {
+type snapshotBinaryDecorator struct {
 	vendorSnapshotModuleBase
 	*binaryDecorator
-	properties            vendorSnapshotBinaryProperties
+	properties            snapshotBinaryProperties
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
 	if config.DeviceArch() != p.arch() {
 		return false
 	}
@@ -351,7 +486,7 @@
 	return true
 }
 
-func (p *vendorSnapshotBinaryDecorator) link(ctx ModuleContext,
+func (p *snapshotBinaryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
@@ -382,11 +517,19 @@
 	return outputFile
 }
 
-func (p *vendorSnapshotBinaryDecorator) nativeCoverage() bool {
+func (p *snapshotBinaryDecorator) nativeCoverage() bool {
 	return false
 }
 
 func VendorSnapshotBinaryFactory() android.Module {
+	return snapshotBinaryFactory(vendorSnapshotBinarySuffix)
+}
+
+func RecoverySnapshotBinaryFactory() android.Module {
+	return snapshotBinaryFactory(recoverySnapshotBinarySuffix)
+}
+
+func snapshotBinaryFactory(suffix string) android.Module {
 	module, binary := NewBinary(android.DeviceSupported)
 	binary.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	binary.baseLinker.Properties.Nocrt = BoolPtr(true)
@@ -396,7 +539,7 @@
 		binary.baseLinker.Properties.System_shared_libs = []string{}
 	}
 
-	prebuilt := &vendorSnapshotBinaryDecorator{
+	prebuilt := &snapshotBinaryDecorator{
 		binaryDecorator: binary,
 	}
 
@@ -405,7 +548,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotBinarySuffix)
+	prebuilt.init(module, suffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -415,14 +558,14 @@
 	Src *string `android:"arch_variant"`
 }
 
-type vendorSnapshotObjectLinker struct {
+type snapshotObjectLinker struct {
 	vendorSnapshotModuleBase
 	objectLinker
 	properties            vendorSnapshotObjectProperties
 	androidMkVendorSuffix bool
 }
 
-func (p *vendorSnapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
 	if config.DeviceArch() != p.arch() {
 		return false
 	}
@@ -432,7 +575,7 @@
 	return true
 }
 
-func (p *vendorSnapshotObjectLinker) link(ctx ModuleContext,
+func (p *snapshotObjectLinker) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
@@ -444,14 +587,14 @@
 	return android.PathForModuleSrc(ctx, *p.properties.Src)
 }
 
-func (p *vendorSnapshotObjectLinker) nativeCoverage() bool {
+func (p *snapshotObjectLinker) nativeCoverage() bool {
 	return false
 }
 
 func VendorSnapshotObjectFactory() android.Module {
 	module := newObject()
 
-	prebuilt := &vendorSnapshotObjectLinker{
+	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
 			baseLinker: NewBaseLinker(nil),
 		},
@@ -463,21 +606,68 @@
 	return module.Init()
 }
 
+func RecoverySnapshotObjectFactory() android.Module {
+	module := newObject()
+
+	prebuilt := &snapshotObjectLinker{
+		objectLinker: objectLinker{
+			baseLinker: NewBaseLinker(nil),
+		},
+	}
+	module.linker = prebuilt
+
+	prebuilt.init(module, recoverySnapshotObjectSuffix)
+	module.AddProperties(&prebuilt.properties)
+	return module.Init()
+}
+
 func init() {
-	android.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
-	android.RegisterModuleType("vendor_snapshot_shared", VendorSnapshotSharedFactory)
-	android.RegisterModuleType("vendor_snapshot_static", VendorSnapshotStaticFactory)
-	android.RegisterModuleType("vendor_snapshot_header", VendorSnapshotHeaderFactory)
-	android.RegisterModuleType("vendor_snapshot_binary", VendorSnapshotBinaryFactory)
-	android.RegisterModuleType("vendor_snapshot_object", VendorSnapshotObjectFactory)
+	vendorImageSingleton.init()
+	recoveryImageSingleton.init()
+}
+
+var vendorSnapshotSingleton = snapshotSingleton{
+	"vendor",
+	"SOONG_VENDOR_SNAPSHOT_ZIP",
+	android.OptionalPath{},
+	true,
+	vendorImageSingleton,
+}
+
+var recoverySnapshotSingleton = snapshotSingleton{
+	"recovery",
+	"SOONG_RECOVERY_SNAPSHOT_ZIP",
+	android.OptionalPath{},
+	false,
+	recoveryImageSingleton,
 }
 
 func VendorSnapshotSingleton() android.Singleton {
-	return &vendorSnapshotSingleton{}
+	return &vendorSnapshotSingleton
 }
 
-type vendorSnapshotSingleton struct {
-	vendorSnapshotZipFile android.OptionalPath
+func RecoverySnapshotSingleton() android.Singleton {
+	return &recoverySnapshotSingleton
+}
+
+type snapshotSingleton struct {
+	// Name, e.g., "vendor", "recovery", "ramdisk".
+	name string
+
+	// Make variable that points to the snapshot file, e.g.,
+	// "SOONG_RECOVERY_SNAPSHOT_ZIP".
+	makeVar string
+
+	// Path to the snapshot zip file.
+	snapshotZipFile android.OptionalPath
+
+	// Whether the image supports VNDK extension modules.
+	supportsVndkExt bool
+
+	// Implementation of the image interface specific to the image
+	// associated with this snapshot (e.g., specific to the vendor image,
+	// recovery image, etc.).
+	image image
 }
 
 var (
@@ -491,6 +681,17 @@
 		"hardware",
 	}
 
+	// Modules under following directories are ignored. They are OEM's and vendor's
+	// proprietary modules(device/, kernel/, vendor/, and hardware/).
+	// TODO(b/65377115): Clean up these with more maintainable way
+	recoveryProprietaryDirs = []string{
+		"bootable/recovery",
+		"device",
+		"hardware",
+		"kernel",
+		"vendor",
+	}
+
 	// Modules under following directories are included as they are in AOSP,
 	// although hardware/ and kernel/ are normally for vendor's own.
 	// TODO(b/65377115): Clean up these with more maintainable way
@@ -508,7 +709,17 @@
 // Determine if a dir under source tree is an SoC-owned proprietary directory, such as
 // device/, vendor/, etc.
 func isVendorProprietaryPath(dir string) bool {
-	for _, p := range vendorProprietaryDirs {
+	return isProprietaryPath(dir, vendorProprietaryDirs)
+}
+
+func isRecoveryProprietaryPath(dir string) bool {
+	return isProprietaryPath(dir, recoveryProprietaryDirs)
+}
+
+// Determine if a dir under source tree is an SoC-owned proprietary directory, such as
+// device/, vendor/, etc.
+func isProprietaryPath(dir string, proprietaryDirs []string) bool {
+	for _, p := range proprietaryDirs {
 		if strings.HasPrefix(dir, p) {
 			// filter out AOSP defined directories, e.g. hardware/interfaces/
 			aosp := false
@@ -556,6 +767,14 @@
 // depend on newer VNDK) So they are captured as vendor snapshot To build older vendor
 // image and newer system image altogether.
 func isVendorSnapshotModule(m *Module, inVendorProprietaryPath bool, apexInfo android.ApexInfo) bool {
+	return isSnapshotModule(m, inVendorProprietaryPath, apexInfo, vendorImageSingleton)
+}
+
+func isRecoverySnapshotModule(m *Module, inRecoveryProprietaryPath bool, apexInfo android.ApexInfo) bool {
+	return isSnapshotModule(m, inRecoveryProprietaryPath, apexInfo, recoveryImageSingleton)
+}
+
+func isSnapshotModule(m *Module, inProprietaryPath bool, apexInfo android.ApexInfo, image image) bool {
 	if !m.Enabled() || m.Properties.HideFromMake {
 		return false
 	}
@@ -564,8 +783,9 @@
 	if m.IsSkipInstall() {
 		return false
 	}
-	// skip proprietary modules, but include all VNDK (static)
-	if inVendorProprietaryPath && !m.IsVndk() {
+	// skip proprietary modules, but (for the vendor snapshot only)
+	// include all VNDK (static)
+	if inProprietaryPath && (!image.includeVndk() || !m.IsVndk()) {
 		return false
 	}
 	// If the module would be included based on its path, check to see if
@@ -580,7 +800,7 @@
 		return false
 	}
 	// the module must be installed in /vendor
-	if !apexInfo.IsForPlatform() || m.isSnapshotPrebuilt() || !m.inVendor() {
+	if !apexInfo.IsForPlatform() || m.isSnapshotPrebuilt() || !image.inImage(m)() {
 		return false
 	}
 	// skip kernel_headers which always depend on vendor
@@ -612,29 +832,31 @@
 			}
 		}
 		if l.static() {
-			return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
+			return m.outputFile.Valid() && proptools.BoolDefault(image.available(m), true)
 		}
 		if l.shared() {
 			if !m.outputFile.Valid() {
 				return false
 			}
-			if !m.IsVndk() {
-				return true
+			if image.includeVndk() {
+				if !m.IsVndk() {
+					return true
+				}
+				return m.isVndkExt()
 			}
-			return m.isVndkExt()
 		}
 		return true
 	}
 
 	// Binaries and Objects
 	if m.binary() || m.object() {
-		return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
+		return m.outputFile.Valid() && proptools.BoolDefault(image.available(m), true)
 	}
 
 	return false
 }
 
-func (c *vendorSnapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+func (c *snapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	// BOARD_VNDK_VERSION must be set to 'current' in order to generate a vendor snapshot.
 	if ctx.DeviceConfig().VndkVersion() != "current" {
 		return
@@ -675,7 +897,7 @@
 				(header files of same directory structure with source tree)
 	*/
 
-	snapshotDir := "vendor-snapshot"
+	snapshotDir := c.name + "-snapshot"
 	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
 
 	includeDir := filepath.Join(snapshotArchDir, "include")
@@ -722,7 +944,7 @@
 
 		// Common properties among snapshots.
 		prop.ModuleName = ctx.ModuleName(m)
-		if m.isVndkExt() {
+		if c.supportsVndkExt && m.isVndkExt() {
 			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
 			if m.isVndkSp() {
 				prop.RelativeInstallPath = "vndk-sp"
@@ -843,26 +1065,30 @@
 		}
 
 		moduleDir := ctx.ModuleDir(module)
-		inVendorProprietaryPath := isVendorProprietaryPath(moduleDir)
+		inProprietaryPath := c.image.isProprietaryPath(moduleDir)
 		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
 		if m.ExcludeFromVendorSnapshot() {
-			if inVendorProprietaryPath {
+			if inProprietaryPath {
 				// Error: exclude_from_vendor_snapshot applies
 				// to framework-path modules only.
 				ctx.Errorf("module %q in vendor proprietary path %q may not use \"exclude_from_vendor_snapshot: true\"", m.String(), moduleDir)
 				return
 			}
-			if Bool(m.VendorProperties.Vendor_available) {
+			if Bool(c.image.available(m)) {
 				// Error: may not combine "vendor_available:
 				// true" with "exclude_from_vendor_snapshot:
 				// true".
-				ctx.Errorf("module %q may not use both \"vendor_available: true\" and \"exclude_from_vendor_snapshot: true\"", m.String())
+				ctx.Errorf(
+					"module %q may not use both \""+
+						c.name+
+						"_available: true\" and \"exclude_from_vendor_snapshot: true\"",
+					m.String())
 				return
 			}
 		}
 
-		if !isVendorSnapshotModule(m, inVendorProprietaryPath, apexInfo) {
+		if !isSnapshotModule(m, inProprietaryPath, apexInfo, c.image) {
 			return
 		}
 
@@ -894,11 +1120,17 @@
 		return snapshotOutputs[i].String() < snapshotOutputs[j].String()
 	})
 
-	zipPath := android.PathForOutput(ctx, snapshotDir, "vendor-"+ctx.Config().DeviceName()+".zip")
+	zipPath := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+".zip")
 	zipRule := android.NewRuleBuilder()
 
 	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
-	snapshotOutputList := android.PathForOutput(ctx, snapshotDir, "vendor-"+ctx.Config().DeviceName()+"_list")
+	snapshotOutputList := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+"_list")
 	zipRule.Command().
 		Text("tr").
 		FlagWithArg("-d ", "\\'").
@@ -913,13 +1145,15 @@
 		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
 		FlagWithInput("-l ", snapshotOutputList)
 
-	zipRule.Build(pctx, ctx, zipPath.String(), "vendor snapshot "+zipPath.String())
+	zipRule.Build(pctx, ctx, zipPath.String(), c.name+" snapshot "+zipPath.String())
 	zipRule.DeleteTemporaryFiles()
-	c.vendorSnapshotZipFile = android.OptionalPathForPath(zipPath)
+	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
 }
 
-func (c *vendorSnapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.Strict("SOONG_VENDOR_SNAPSHOT_ZIP", c.vendorSnapshotZipFile.String())
+func (c *snapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict(
+		c.makeVar,
+		c.snapshotZipFile.String())
 }
 
 type snapshotInterface interface {
@@ -927,9 +1161,9 @@
 }
 
 var _ snapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotLibraryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotBinaryDecorator)(nil)
-var _ snapshotInterface = (*vendorSnapshotObjectLinker)(nil)
+var _ snapshotInterface = (*snapshotLibraryDecorator)(nil)
+var _ snapshotInterface = (*snapshotBinaryDecorator)(nil)
+var _ snapshotInterface = (*snapshotObjectLinker)(nil)
 
 // gathers all snapshot modules for vendor, and disable unnecessary snapshots
 // TODO(b/145966707): remove mutator and utilize android.Prebuilt to override source modules
@@ -970,9 +1204,9 @@
 			// header
 			snapshotMap = vendorSnapshotHeaderLibs(ctx.Config())
 		}
-	} else if _, ok := module.linker.(*vendorSnapshotBinaryDecorator); ok {
+	} else if _, ok := module.linker.(*snapshotBinaryDecorator); ok {
 		snapshotMap = vendorSnapshotBinaries(ctx.Config())
-	} else if _, ok := module.linker.(*vendorSnapshotObjectLinker); ok {
+	} else if _, ok := module.linker.(*snapshotObjectLinker); ok {
 		snapshotMap = vendorSnapshotObjects(ctx.Config())
 	} else {
 		return
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index 6fa304e..f5e87c0 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -14,8 +14,20 @@
 
 blueprint_go_binary {
     name: "sbox",
-    deps: ["soong-makedeps"],
+    deps: [
+        "sbox_proto",
+        "soong-makedeps",
+    ],
     srcs: [
         "sbox.go",
     ],
 }
+
+bootstrap_go_package {
+    name: "sbox_proto",
+    pkgPath: "android/soong/cmd/sbox/sbox_proto",
+    deps: ["golang-protobuf-proto"],
+    srcs: [
+        "sbox_proto/sbox.pb.go",
+    ],
+}
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 65a34fd..633c6b2 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -19,41 +19,39 @@
 	"errors"
 	"flag"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 
+	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/makedeps"
+
+	"github.com/golang/protobuf/proto"
 )
 
 var (
 	sandboxesRoot string
-	rawCommand    string
-	outputRoot    string
+	manifestFile  string
 	keepOutDir    bool
-	depfileOut    string
-	inputHash     string
+)
+
+const (
+	depFilePlaceholder    = "__SBOX_DEPFILE__"
+	sandboxDirPlaceholder = "__SBOX_SANDBOX_DIR__"
 )
 
 func init() {
 	flag.StringVar(&sandboxesRoot, "sandbox-path", "",
 		"root of temp directory to put the sandbox into")
-	flag.StringVar(&rawCommand, "c", "",
-		"command to run")
-	flag.StringVar(&outputRoot, "output-root", "",
-		"root of directory to copy outputs into")
+	flag.StringVar(&manifestFile, "manifest", "",
+		"textproto manifest describing the sandboxed command(s)")
 	flag.BoolVar(&keepOutDir, "keep-out-dir", false,
 		"whether to keep the sandbox directory when done")
-
-	flag.StringVar(&depfileOut, "depfile-out", "",
-		"file path of the depfile to generate. This value will replace '__SBOX_DEPFILE__' in the command and will be treated as an output but won't be added to __SBOX_OUT_FILES__")
-
-	flag.StringVar(&inputHash, "input-hash", "",
-		"This option is ignored. Typical usage is to supply a hash of the list of input names so that the module will be rebuilt if the list (and thus the hash) changes.")
 }
 
 func usageViolation(violation string) {
@@ -62,11 +60,7 @@
 	}
 
 	fmt.Fprintf(os.Stderr,
-		"Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> [--depfile-out depFile] [--input-hash hash] <outputFile> [<outputFile>...]\n"+
-			"\n"+
-			"Deletes <outputRoot>,"+
-			"runs <commandToRun>,"+
-			"and moves each <outputFile> out of <sandboxPath> and into <outputRoot>\n")
+		"Usage: sbox --manifest <manifest> --sandbox-path <sandboxPath>\n")
 
 	flag.PrintDefaults()
 
@@ -103,8 +97,8 @@
 }
 
 func run() error {
-	if rawCommand == "" {
-		usageViolation("-c <commandToRun> is required and must be non-empty")
+	if manifestFile == "" {
+		usageViolation("--manifest <manifest> is required and must be non-empty")
 	}
 	if sandboxesRoot == "" {
 		// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
@@ -114,61 +108,28 @@
 		// and by passing it as a parameter we don't need to duplicate its value
 		usageViolation("--sandbox-path <sandboxPath> is required and must be non-empty")
 	}
-	if len(outputRoot) == 0 {
-		usageViolation("--output-root <outputRoot> is required and must be non-empty")
+
+	manifest, err := readManifest(manifestFile)
+
+	if len(manifest.Commands) == 0 {
+		return fmt.Errorf("at least one commands entry is required in %q", manifestFile)
 	}
 
-	// the contents of the __SBOX_OUT_FILES__ variable
-	outputsVarEntries := flag.Args()
-	if len(outputsVarEntries) == 0 {
-		usageViolation("at least one output file must be given")
-	}
-
-	// all outputs
-	var allOutputs []string
-
-	// setup directories
-	err := os.MkdirAll(sandboxesRoot, 0777)
+	// setup sandbox directory
+	err = os.MkdirAll(sandboxesRoot, 0777)
 	if err != nil {
-		return err
-	}
-	err = os.RemoveAll(outputRoot)
-	if err != nil {
-		return err
-	}
-	err = os.MkdirAll(outputRoot, 0777)
-	if err != nil {
-		return err
+		return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
 	}
 
 	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
-
-	for i, filePath := range outputsVarEntries {
-		if !strings.HasPrefix(filePath, "__SBOX_OUT_DIR__/") {
-			return fmt.Errorf("output files must start with `__SBOX_OUT_DIR__/`")
-		}
-		outputsVarEntries[i] = strings.TrimPrefix(filePath, "__SBOX_OUT_DIR__/")
-	}
-
-	allOutputs = append([]string(nil), outputsVarEntries...)
-
-	if depfileOut != "" {
-		sandboxedDepfile, err := filepath.Rel(outputRoot, depfileOut)
-		if err != nil {
-			return err
-		}
-		allOutputs = append(allOutputs, sandboxedDepfile)
-		rawCommand = strings.Replace(rawCommand, "__SBOX_DEPFILE__", filepath.Join(tempDir, sandboxedDepfile), -1)
-
-	}
-
 	if err != nil {
-		return fmt.Errorf("Failed to create temp dir: %s", err)
+		return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
 	}
 
 	// In the common case, the following line of code is what removes the sandbox
 	// If a fatal error occurs (such as if our Go process is killed unexpectedly),
-	// then at the beginning of the next build, Soong will retry the cleanup
+	// then at the beginning of the next build, Soong will wipe the temporary
+	// directory.
 	defer func() {
 		// in some cases we decline to remove the temp dir, to facilitate debugging
 		if !keepOutDir {
@@ -176,27 +137,95 @@
 		}
 	}()
 
-	if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
-		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
-	}
+	// If there is more than one command in the manifest use a separate directory for each one.
+	useSubDir := len(manifest.Commands) > 1
+	var commandDepFiles []string
 
-	if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
-		// expands into a space-separated list of output files to be generated into the sandbox directory
-		tempOutPaths := []string{}
-		for _, outputPath := range outputsVarEntries {
-			tempOutPath := path.Join(tempDir, outputPath)
-			tempOutPaths = append(tempOutPaths, tempOutPath)
+	for i, command := range manifest.Commands {
+		localTempDir := tempDir
+		if useSubDir {
+			localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
 		}
-		pathsText := strings.Join(tempOutPaths, " ")
-		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
-	}
-
-	for _, filePath := range allOutputs {
-		dir := path.Join(tempDir, filepath.Dir(filePath))
-		err = os.MkdirAll(dir, 0777)
+		depFile, err := runCommand(command, localTempDir)
 		if err != nil {
+			// Running the command failed, keep the temporary output directory around in
+			// case a user wants to inspect it for debugging purposes.  Soong will delete
+			// it at the beginning of the next build anyway.
+			keepOutDir = true
 			return err
 		}
+		if depFile != "" {
+			commandDepFiles = append(commandDepFiles, depFile)
+		}
+	}
+
+	outputDepFile := manifest.GetOutputDepfile()
+	if len(commandDepFiles) > 0 && outputDepFile == "" {
+		return fmt.Errorf("Sandboxed commands used %s but output depfile is not set in manifest file",
+			depFilePlaceholder)
+	}
+
+	if outputDepFile != "" {
+		// Merge the depfiles from each command in the manifest to a single output depfile.
+		err = rewriteDepFiles(commandDepFiles, outputDepFile)
+		if err != nil {
+			return fmt.Errorf("failed merging depfiles: %w", err)
+		}
+	}
+
+	return nil
+}
+
+// readManifest reads an sbox manifest from a textproto file.
+func readManifest(file string) (*sbox_proto.Manifest, error) {
+	manifestData, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, fmt.Errorf("error reading manifest %q: %w", file, err)
+	}
+
+	manifest := sbox_proto.Manifest{}
+
+	err = proto.UnmarshalText(string(manifestData), &manifest)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
+	}
+
+	return &manifest, nil
+}
+
+// runCommand runs a single command from a manifest.  If the command references the
+// __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
+func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
+	rawCommand := command.GetCommand()
+	if rawCommand == "" {
+		return "", fmt.Errorf("command is required")
+	}
+
+	err = os.MkdirAll(tempDir, 0777)
+	if err != nil {
+		return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
+	}
+
+	// Copy in any files specified by the manifest.
+	err = copyFiles(command.CopyBefore, "", tempDir)
+	if err != nil {
+		return "", err
+	}
+
+	if strings.Contains(rawCommand, depFilePlaceholder) {
+		depFile = filepath.Join(tempDir, "deps.d")
+		rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
+	}
+
+	if strings.Contains(rawCommand, sandboxDirPlaceholder) {
+		rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, tempDir, -1)
+	}
+
+	// Emulate ninja's behavior of creating the directories for any output files before
+	// running the command.
+	err = makeOutputDirs(command.CopyAfter, tempDir)
+	if err != nil {
+		return "", err
 	}
 
 	commandDescription := rawCommand
@@ -205,27 +234,20 @@
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
+
+	if command.GetChdir() {
+		cmd.Dir = tempDir
+	}
 	err = cmd.Run()
 
 	if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
-		return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
+		return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
 	} else if err != nil {
-		return err
+		return "", err
 	}
 
-	// validate that all files are created properly
-	var missingOutputErrors []string
-	for _, filePath := range allOutputs {
-		tempPath := filepath.Join(tempDir, filePath)
-		fileInfo, err := os.Stat(tempPath)
-		if err != nil {
-			missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: does not exist", filePath))
-			continue
-		}
-		if fileInfo.IsDir() {
-			missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: not a file", filePath))
-		}
-	}
+	missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
+
 	if len(missingOutputErrors) > 0 {
 		// find all created files for making a more informative error message
 		createdFiles := findAllFilesUnder(tempDir)
@@ -236,7 +258,7 @@
 		errorMessage += "in sandbox " + tempDir + ",\n"
 		errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
 		for _, missingOutputError := range missingOutputErrors {
-			errorMessage += "  " + missingOutputError + "\n"
+			errorMessage += "  " + missingOutputError.Error() + "\n"
 		}
 		if len(createdFiles) < 1 {
 			errorMessage += "created 0 files."
@@ -253,19 +275,120 @@
 			}
 		}
 
-		// Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
-		// Soong will delete it later anyway.
-		keepOutDir = true
-		return errors.New(errorMessage)
+		return "", errors.New(errorMessage)
 	}
 	// the created files match the declared files; now move them
-	for _, filePath := range allOutputs {
-		tempPath := filepath.Join(tempDir, filePath)
-		destPath := filePath
-		if len(outputRoot) != 0 {
-			destPath = filepath.Join(outputRoot, filePath)
+	err = moveFiles(command.CopyAfter, tempDir, "")
+
+	return depFile, nil
+}
+
+// makeOutputDirs creates directories in the sandbox dir for every file that has a rule to be copied
+// out of the sandbox.  This emulate's Ninja's behavior of creating directories for output files
+// so that the tools don't have to.
+func makeOutputDirs(copies []*sbox_proto.Copy, sandboxDir string) error {
+	for _, copyPair := range copies {
+		dir := joinPath(sandboxDir, filepath.Dir(copyPair.GetFrom()))
+		err := os.MkdirAll(dir, 0777)
+		if err != nil {
+			return err
 		}
-		err := os.MkdirAll(filepath.Dir(destPath), 0777)
+	}
+	return nil
+}
+
+// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
+// were created by the command.
+func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
+	var missingOutputErrors []error
+	for _, copyPair := range copies {
+		fromPath := joinPath(sandboxDir, copyPair.GetFrom())
+		fileInfo, err := os.Stat(fromPath)
+		if err != nil {
+			missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: does not exist", fromPath))
+			continue
+		}
+		if fileInfo.IsDir() {
+			missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
+		}
+	}
+	return missingOutputErrors
+}
+
+// copyFiles copies files in or out of the sandbox.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+	for _, copyPair := range copies {
+		fromPath := joinPath(fromDir, copyPair.GetFrom())
+		toPath := joinPath(toDir, copyPair.GetTo())
+		err := copyOneFile(fromPath, toPath)
+		if err != nil {
+			return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
+		}
+	}
+	return nil
+}
+
+// copyOneFile copies a file.
+func copyOneFile(from string, to string) error {
+	err := os.MkdirAll(filepath.Dir(to), 0777)
+	if err != nil {
+		return err
+	}
+
+	stat, err := os.Stat(from)
+	if err != nil {
+		return err
+	}
+
+	perm := stat.Mode()
+
+	in, err := os.Open(from)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	out, err := os.Create(to)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		out.Close()
+		if err != nil {
+			os.Remove(to)
+		}
+	}()
+
+	_, err = io.Copy(out, in)
+	if err != nil {
+		return err
+	}
+
+	if err = out.Close(); err != nil {
+		return err
+	}
+
+	if err = os.Chmod(to, perm); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// moveFiles moves files specified by a set of copy rules.  It uses os.Rename, so it is restricted
+// to moving files where the source and destination are in the same filesystem.  This is OK for
+// sbox because the temporary directory is inside the out directory.  It updates the timestamp
+// of the new file.
+func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+	for _, copyPair := range copies {
+		fromPath := joinPath(fromDir, copyPair.GetFrom())
+		toPath := joinPath(toDir, copyPair.GetTo())
+		err := os.MkdirAll(filepath.Dir(toPath), 0777)
+		if err != nil {
+			return err
+		}
+
+		err = os.Rename(fromPath, toPath)
 		if err != nil {
 			return err
 		}
@@ -273,37 +396,53 @@
 		// Update the timestamp of the output file in case the tool wrote an old timestamp (for example, tar can extract
 		// files with old timestamps).
 		now := time.Now()
-		err = os.Chtimes(tempPath, now, now)
-		if err != nil {
-			return err
-		}
-
-		err = os.Rename(tempPath, destPath)
+		err = os.Chtimes(toPath, now, now)
 		if err != nil {
 			return err
 		}
 	}
-
-	// Rewrite the depfile so that it doesn't include the (randomized) sandbox directory
-	if depfileOut != "" {
-		in, err := ioutil.ReadFile(depfileOut)
-		if err != nil {
-			return err
-		}
-
-		deps, err := makedeps.Parse(depfileOut, bytes.NewBuffer(in))
-		if err != nil {
-			return err
-		}
-
-		deps.Output = "outputfile"
-
-		err = ioutil.WriteFile(depfileOut, deps.Print(), 0666)
-		if err != nil {
-			return err
-		}
-	}
-
-	// TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
 	return nil
 }
+
+// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
+// to an output file.
+func rewriteDepFiles(ins []string, out string) error {
+	var mergedDeps []string
+	for _, in := range ins {
+		data, err := ioutil.ReadFile(in)
+		if err != nil {
+			return err
+		}
+
+		deps, err := makedeps.Parse(in, bytes.NewBuffer(data))
+		if err != nil {
+			return err
+		}
+		mergedDeps = append(mergedDeps, deps.Inputs...)
+	}
+
+	deps := makedeps.Deps{
+		// Ninja doesn't care what the output file is, so we can use any string here.
+		Output: "outputfile",
+		Inputs: mergedDeps,
+	}
+
+	// Make the directory for the output depfile in case it is in a different directory
+	// than any of the output files.
+	outDir := filepath.Dir(out)
+	err := os.MkdirAll(outDir, 0777)
+	if err != nil {
+		return fmt.Errorf("failed to create %q: %w", outDir, err)
+	}
+
+	return ioutil.WriteFile(out, deps.Print(), 0666)
+}
+
+// joinPath wraps filepath.Join but returns file without appending to dir if file is
+// absolute.
+func joinPath(dir, file string) string {
+	if filepath.IsAbs(file) {
+		return file
+	}
+	return filepath.Join(dir, file)
+}
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
new file mode 100644
index 0000000..6584bdf
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -0,0 +1,233 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: sbox.proto
+
+package sbox_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// A set of commands to run in a sandbox.
+type Manifest struct {
+	// A list of commands to run in the sandbox.
+	Commands []*Command `protobuf:"bytes,1,rep,name=commands" json:"commands,omitempty"`
+	// If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+	// merged into the given output file relative to the $PWD when sbox was started.
+	OutputDepfile        *string  `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Manifest) Reset()         { *m = Manifest{} }
+func (m *Manifest) String() string { return proto.CompactTextString(m) }
+func (*Manifest) ProtoMessage()    {}
+func (*Manifest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{0}
+}
+
+func (m *Manifest) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Manifest.Unmarshal(m, b)
+}
+func (m *Manifest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Manifest.Marshal(b, m, deterministic)
+}
+func (m *Manifest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Manifest.Merge(m, src)
+}
+func (m *Manifest) XXX_Size() int {
+	return xxx_messageInfo_Manifest.Size(m)
+}
+func (m *Manifest) XXX_DiscardUnknown() {
+	xxx_messageInfo_Manifest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Manifest proto.InternalMessageInfo
+
+func (m *Manifest) GetCommands() []*Command {
+	if m != nil {
+		return m.Commands
+	}
+	return nil
+}
+
+func (m *Manifest) GetOutputDepfile() string {
+	if m != nil && m.OutputDepfile != nil {
+		return *m.OutputDepfile
+	}
+	return ""
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+type Command struct {
+	// A list of copy rules to run before the sandboxed command.  The from field is relative to the
+	// $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+	CopyBefore []*Copy `protobuf:"bytes,1,rep,name=copy_before,json=copyBefore" json:"copy_before,omitempty"`
+	// If true, change the working directory to the top of the temporary sandbox directory before
+	// running the command.  If false, leave the working directory where it was when sbox was started.
+	Chdir *bool `protobuf:"varint,2,opt,name=chdir" json:"chdir,omitempty"`
+	// The command to run.
+	Command *string `protobuf:"bytes,3,req,name=command" json:"command,omitempty"`
+	// A list of copy rules to run after the sandboxed command.  The from field is relative to the
+	// top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+	CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
+	// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+	// when the lists of inputs changes, even if the inputs are not on the command line.
+	InputHash            *string  `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Command) Reset()         { *m = Command{} }
+func (m *Command) String() string { return proto.CompactTextString(m) }
+func (*Command) ProtoMessage()    {}
+func (*Command) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{1}
+}
+
+func (m *Command) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Command.Unmarshal(m, b)
+}
+func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Command.Marshal(b, m, deterministic)
+}
+func (m *Command) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Command.Merge(m, src)
+}
+func (m *Command) XXX_Size() int {
+	return xxx_messageInfo_Command.Size(m)
+}
+func (m *Command) XXX_DiscardUnknown() {
+	xxx_messageInfo_Command.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Command proto.InternalMessageInfo
+
+func (m *Command) GetCopyBefore() []*Copy {
+	if m != nil {
+		return m.CopyBefore
+	}
+	return nil
+}
+
+func (m *Command) GetChdir() bool {
+	if m != nil && m.Chdir != nil {
+		return *m.Chdir
+	}
+	return false
+}
+
+func (m *Command) GetCommand() string {
+	if m != nil && m.Command != nil {
+		return *m.Command
+	}
+	return ""
+}
+
+func (m *Command) GetCopyAfter() []*Copy {
+	if m != nil {
+		return m.CopyAfter
+	}
+	return nil
+}
+
+func (m *Command) GetInputHash() string {
+	if m != nil && m.InputHash != nil {
+		return *m.InputHash
+	}
+	return ""
+}
+
+// Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+type Copy struct {
+	From                 *string  `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+	To                   *string  `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Copy) Reset()         { *m = Copy{} }
+func (m *Copy) String() string { return proto.CompactTextString(m) }
+func (*Copy) ProtoMessage()    {}
+func (*Copy) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{2}
+}
+
+func (m *Copy) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Copy.Unmarshal(m, b)
+}
+func (m *Copy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Copy.Marshal(b, m, deterministic)
+}
+func (m *Copy) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Copy.Merge(m, src)
+}
+func (m *Copy) XXX_Size() int {
+	return xxx_messageInfo_Copy.Size(m)
+}
+func (m *Copy) XXX_DiscardUnknown() {
+	xxx_messageInfo_Copy.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Copy proto.InternalMessageInfo
+
+func (m *Copy) GetFrom() string {
+	if m != nil && m.From != nil {
+		return *m.From
+	}
+	return ""
+}
+
+func (m *Copy) GetTo() string {
+	if m != nil && m.To != nil {
+		return *m.To
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
+	proto.RegisterType((*Command)(nil), "sbox.Command")
+	proto.RegisterType((*Copy)(nil), "sbox.Copy")
+}
+
+func init() {
+	proto.RegisterFile("sbox.proto", fileDescriptor_9d0425bf0de86ed1)
+}
+
+var fileDescriptor_9d0425bf0de86ed1 = []byte{
+	// 252 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x40,
+	0x10, 0x85, 0x49, 0x9a, 0xd2, 0x66, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x08, 0x01, 0x21, 0x55,
+	0xe8, 0xc1, 0x7f, 0x60, 0xf5, 0xe0, 0xc5, 0xcb, 0x1e, 0x45, 0x08, 0xdb, 0x64, 0x97, 0x04, 0x4c,
+	0x66, 0xd9, 0xdd, 0x82, 0xfd, 0x57, 0xfe, 0x44, 0xd9, 0x49, 0xea, 0xc5, 0xdb, 0xcc, 0xfb, 0x78,
+	0xf3, 0x1e, 0x03, 0xe0, 0x4f, 0xf4, 0x7d, 0xb0, 0x8e, 0x02, 0x61, 0x16, 0xe7, 0xf2, 0x13, 0xd6,
+	0xef, 0x6a, 0xec, 0x8d, 0xf6, 0x01, 0xf7, 0xb0, 0x6e, 0x68, 0x18, 0xd4, 0xd8, 0x7a, 0x91, 0x14,
+	0x8b, 0x6a, 0xf3, 0xb4, 0x3d, 0xb0, 0xe1, 0x65, 0x52, 0xe5, 0x1f, 0xc6, 0x7b, 0xd8, 0xd1, 0x39,
+	0xd8, 0x73, 0xa8, 0x5b, 0x6d, 0x4d, 0xff, 0xa5, 0x45, 0x5a, 0x24, 0x55, 0x2e, 0xb7, 0x93, 0xfa,
+	0x3a, 0x89, 0xe5, 0x4f, 0x02, 0xab, 0xd9, 0x8c, 0x8f, 0xb0, 0x69, 0xc8, 0x5e, 0xea, 0x93, 0x36,
+	0xe4, 0xf4, 0x1c, 0x00, 0xd7, 0x00, 0x7b, 0x91, 0x10, 0xf1, 0x91, 0x29, 0xde, 0xc2, 0xb2, 0xe9,
+	0xda, 0xde, 0xf1, 0xd9, 0xb5, 0x9c, 0x16, 0x14, 0xb0, 0x9a, 0x1b, 0x88, 0x45, 0x91, 0x56, 0xb9,
+	0xbc, 0xae, 0xb8, 0x07, 0x76, 0xd7, 0xca, 0x04, 0xed, 0x44, 0xf6, 0xef, 0x76, 0x1e, 0xe9, 0x73,
+	0x84, 0x78, 0x07, 0xd0, 0x8f, 0xb1, 0x79, 0xa7, 0x7c, 0x27, 0x96, 0x5c, 0x3b, 0x67, 0xe5, 0x4d,
+	0xf9, 0xae, 0x7c, 0x80, 0x2c, 0x3a, 0x10, 0x21, 0x33, 0x8e, 0x06, 0x91, 0x70, 0x10, 0xcf, 0xb8,
+	0x83, 0x34, 0x90, 0x48, 0x59, 0x49, 0x03, 0x1d, 0x6f, 0x3e, 0xf8, 0xa1, 0x35, 0x3f, 0xf4, 0x37,
+	0x00, 0x00, 0xff, 0xff, 0x95, 0x4d, 0xee, 0x7d, 0x5d, 0x01, 0x00, 0x00,
+}
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
new file mode 100644
index 0000000..ab95545
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -0,0 +1,58 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package sbox;
+option go_package = "sbox_proto";
+
+// A set of commands to run in a sandbox.
+message Manifest {
+  // A list of commands to run in the sandbox.
+  repeated Command commands = 1;
+
+  // If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+  // merged into the given output file relative to the $PWD when sbox was started.
+  optional string output_depfile = 2;
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+message Command {
+  // A list of copy rules to run before the sandboxed command.  The from field is relative to the
+  // $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+  repeated Copy copy_before = 1;
+
+  // If true, change the working directory to the top of the temporary sandbox directory before
+  // running the command.  If false, leave the working directory where it was when sbox was started.
+  optional bool chdir = 2;
+
+  // The command to run.
+  required string command = 3;
+
+  // A list of copy rules to run after the sandboxed command.  The from field is relative to the
+  // top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+  repeated Copy copy_after = 4;
+
+  // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+  // when the lists of inputs changes, even if the inputs are not on the command line.
+  optional string input_hash = 5;
+}
+
+// Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+message Copy {
+  required string from = 1;
+  required string to = 2;
+}
\ No newline at end of file
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 680c00a..441ea0d 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -27,6 +27,7 @@
         "main.go",
         "writedocs.go",
         "queryview.go",
+        "queryview_templates.go",
     ],
     testSrcs: [
         "queryview_test.go",
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 27856b5..f5aa685 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -28,115 +28,6 @@
 	"github.com/google/blueprint/proptools"
 )
 
-const (
-	// The default `load` preamble for every generated BUILD file.
-	soongModuleLoad = `package(default_visibility = ["//visibility:public"])
-load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
-
-`
-
-	// A macro call in the BUILD file representing a Soong module, with space
-	// for expanding more attributes.
-	soongModuleTarget = `soong_module(
-    name = "%s",
-    module_name = "%s",
-    module_type = "%s",
-    module_variant = "%s",
-    module_deps = %s,
-%s)`
-
-	// A simple provider to mark and differentiate Soong module rule shims from
-	// regular Bazel rules. Every Soong module rule shim returns a
-	// SoongModuleInfo provider, and can only depend on rules returning
-	// SoongModuleInfo in the `module_deps` attribute.
-	providersBzl = `SoongModuleInfo = provider(
-    fields = {
-        "name": "Name of module",
-        "type": "Type of module",
-        "variant": "Variant of module",
-    },
-)
-`
-
-	// The soong_module rule implementation in a .bzl file.
-	soongModuleBzl = `
-%s
-
-load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
-
-def _generic_soong_module_impl(ctx):
-    return [
-        SoongModuleInfo(
-            name = ctx.attr.module_name,
-            type = ctx.attr.module_type,
-            variant = ctx.attr.module_variant,
-        ),
-    ]
-
-generic_soong_module = rule(
-    implementation = _generic_soong_module_impl,
-    attrs = {
-        "module_name": attr.string(mandatory = True),
-        "module_type": attr.string(mandatory = True),
-        "module_variant": attr.string(),
-        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
-    },
-)
-
-soong_module_rule_map = {
-%s}
-
-_SUPPORTED_TYPES = ["bool", "int", "string"]
-
-def _is_supported_type(value):
-    if type(value) in _SUPPORTED_TYPES:
-        return True
-    elif type(value) == "list":
-        supported = True
-        for v in value:
-            supported = supported and type(v) in _SUPPORTED_TYPES
-        return supported
-    else:
-        return False
-
-# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
-# expand to the right underlying shim.
-def soong_module(name, module_type, **kwargs):
-    soong_module_rule = soong_module_rule_map.get(module_type)
-
-    if soong_module_rule == None:
-        # This module type does not have an existing rule to map to, so use the
-        # generic_soong_module rule instead.
-        generic_soong_module(
-            name = name,
-            module_type = module_type,
-            module_name = kwargs.pop("module_name", ""),
-            module_variant = kwargs.pop("module_variant", ""),
-            module_deps = kwargs.pop("module_deps", []),
-        )
-    else:
-        supported_kwargs = dict()
-        for key, value in kwargs.items():
-            if _is_supported_type(value):
-                supported_kwargs[key] = value
-        soong_module_rule(
-            name = name,
-            **supported_kwargs,
-        )
-`
-
-	// A rule shim for representing a Soong module type and its properties.
-	moduleRuleShim = `
-def _%[1]s_impl(ctx):
-    return [SoongModuleInfo()]
-
-%[1]s = rule(
-    implementation = _%[1]s_impl,
-    attrs = %[2]s
-)
-`
-)
-
 var (
 	// An allowlist of prop types that are surfaced from module props to rule
 	// attributes. (nested) dictionaries are notably absent here, because while
diff --git a/cmd/soong_build/queryview_templates.go b/cmd/soong_build/queryview_templates.go
new file mode 100644
index 0000000..359c0d8
--- /dev/null
+++ b/cmd/soong_build/queryview_templates.go
@@ -0,0 +1,124 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+const (
+	// The default `load` preamble for every generated BUILD file.
+	soongModuleLoad = `package(default_visibility = ["//visibility:public"])
+load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
+
+`
+
+	// A macro call in the BUILD file representing a Soong module, with space
+	// for expanding more attributes.
+	soongModuleTarget = `soong_module(
+    name = "%s",
+    module_name = "%s",
+    module_type = "%s",
+    module_variant = "%s",
+    module_deps = %s,
+%s)`
+
+	// A simple provider to mark and differentiate Soong module rule shims from
+	// regular Bazel rules. Every Soong module rule shim returns a
+	// SoongModuleInfo provider, and can only depend on rules returning
+	// SoongModuleInfo in the `module_deps` attribute.
+	providersBzl = `SoongModuleInfo = provider(
+    fields = {
+        "name": "Name of module",
+        "type": "Type of module",
+        "variant": "Variant of module",
+    },
+)
+`
+
+	// The soong_module rule implementation in a .bzl file.
+	soongModuleBzl = `
+%s
+
+load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
+
+def _generic_soong_module_impl(ctx):
+    return [
+        SoongModuleInfo(
+            name = ctx.attr.module_name,
+            type = ctx.attr.module_type,
+            variant = ctx.attr.module_variant,
+        ),
+    ]
+
+generic_soong_module = rule(
+    implementation = _generic_soong_module_impl,
+    attrs = {
+        "module_name": attr.string(mandatory = True),
+        "module_type": attr.string(mandatory = True),
+        "module_variant": attr.string(),
+        "module_deps": attr.label_list(providers = [SoongModuleInfo]),
+    },
+)
+
+soong_module_rule_map = {
+%s}
+
+_SUPPORTED_TYPES = ["bool", "int", "string"]
+
+def _is_supported_type(value):
+    if type(value) in _SUPPORTED_TYPES:
+        return True
+    elif type(value) == "list":
+        supported = True
+        for v in value:
+            supported = supported and type(v) in _SUPPORTED_TYPES
+        return supported
+    else:
+        return False
+
+# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
+# expand to the right underlying shim.
+def soong_module(name, module_type, **kwargs):
+    soong_module_rule = soong_module_rule_map.get(module_type)
+
+    if soong_module_rule == None:
+        # This module type does not have an existing rule to map to, so use the
+        # generic_soong_module rule instead.
+        generic_soong_module(
+            name = name,
+            module_type = module_type,
+            module_name = kwargs.pop("module_name", ""),
+            module_variant = kwargs.pop("module_variant", ""),
+            module_deps = kwargs.pop("module_deps", []),
+        )
+    else:
+        supported_kwargs = dict()
+        for key, value in kwargs.items():
+            if _is_supported_type(value):
+                supported_kwargs[key] = value
+        soong_module_rule(
+            name = name,
+            **supported_kwargs,
+        )
+`
+
+	// A rule shim for representing a Soong module type and its properties.
+	moduleRuleShim = `
+def _%[1]s_impl(ctx):
+    return [SoongModuleInfo()]
+
+%[1]s = rule(
+    implementation = _%[1]s_impl,
+    attrs = %[2]s
+)
+`
+)
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
index 525802a..9471a91 100644
--- a/cmd/soong_build/queryview_test.go
+++ b/cmd/soong_build/queryview_test.go
@@ -53,6 +53,12 @@
 	android.ModuleBase
 }
 
+// OutputFiles is needed because some instances of this module use dist with a
+// tag property which requires the module implements OutputFileProducer.
+func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
+	return android.PathsForTesting("path" + tag), nil
+}
+
 func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// nothing for now.
 }
diff --git a/cmd/soong_env/soong_env.go b/cmd/soong_env/soong_env.go
index d305d83..8020b17 100644
--- a/cmd/soong_env/soong_env.go
+++ b/cmd/soong_env/soong_env.go
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// soong_glob is the command line tool that checks if the list of files matching a glob has
-// changed, and only updates the output file list if it has changed.  It is used to optimize
-// out build.ninja regenerations when non-matching files are added.  See
-// android/soong/android/glob.go for a longer description.
+// soong_env determines if the given soong environment file (usually ".soong.environment") is stale
+// by comparing its contents to the current corresponding environment variable values.
+// It fails if the file cannot be opened or corrupted, or its contents differ from the current
+// values.
+
 package main
 
 import (
@@ -34,6 +35,7 @@
 	os.Exit(2)
 }
 
+// This is a simple executable packaging, and the real work happens in env.StaleEnvFile.
 func main() {
 	flag.Parse()
 
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index aee8e5a..29030d6 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -125,27 +125,35 @@
 		os.Exit(1)
 	}
 
+	// Create a terminal output that mimics Ninja's.
 	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput,
 		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
 
+	// Attach a new logger instance to the terminal output.
 	log := logger.New(output)
 	defer log.Cleanup()
 
+	// Create a context to simplify the program termination process.
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
+	// Create a new trace file writer, making it log events to the log instance.
 	trace := tracer.New(log)
 	defer trace.Close()
 
+	// Create and start a new metric record.
 	met := metrics.New()
 	met.SetBuildDateTime(buildStarted)
 	met.SetBuildCommand(os.Args)
 
+	// Create a new Status instance, which manages action counts and event output channels.
 	stat := &status.Status{}
 	defer stat.Finish()
+	// Hook up the terminal output and tracer to Status.
 	stat.AddOutput(output)
 	stat.AddOutput(trace.StatusTracer())
 
+	// Set up a cleanup procedure in case the normal termination process doesn't work.
 	build.SetupSignals(log, cancel, func() {
 		trace.Close()
 		log.Cleanup()
@@ -165,6 +173,7 @@
 
 	build.SetupOutDir(buildCtx, config)
 
+	// Set up files to be outputted in the log directory.
 	logsDir := config.OutDir()
 	if config.Dist() {
 		logsDir = filepath.Join(config.DistDir(), "logs")
@@ -192,7 +201,10 @@
 	defer met.Dump(soongMetricsFile)
 	defer build.DumpRBEMetrics(buildCtx, config, rbeMetricsFile)
 
+	// Read the time at the starting point.
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
+		// soong_ui.bash uses the date command's %N (nanosec) flag when getting the start time,
+		// which Darwin doesn't support. Check if it was executed properly before parsing the value.
 		if !strings.HasSuffix(start, "N") {
 			if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
 				log.Verbosef("Took %dms to start up.",
@@ -211,6 +223,7 @@
 	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
 	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
 
+	// Create a source finder.
 	f := build.NewSourceFinder(buildCtx, config)
 	defer f.Shutdown()
 	build.FindSources(buildCtx, config, f)
@@ -354,6 +367,8 @@
 	return terminal.StdioImpl{}
 }
 
+// dumpvar and dumpvars use stdout to output variable values, so use stderr instead of stdout when
+// reporting events to keep stdout clean from noise.
 func customStdio() terminal.StdioInterface {
 	return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
 }
@@ -493,16 +508,6 @@
 		if c.flag == args[1] {
 			return &c, args[2:], nil
 		}
-
-		// special case for --make-mode: if soong_ui was called from
-		// build/make/core/main.mk, the makeparallel with --ninja
-		// option specified puts the -j<num> before --make-mode.
-		// TODO: Remove this hack once it has been fixed.
-		if c.flag == makeModeFlagName {
-			if inList(makeModeFlagName, args) {
-				return &c, args[1:], nil
-			}
-		}
 	}
 
 	// command not found
diff --git a/cmd/zipsync/zipsync.go b/cmd/zipsync/zipsync.go
index 294e5ef..aecdc3d 100644
--- a/cmd/zipsync/zipsync.go
+++ b/cmd/zipsync/zipsync.go
@@ -53,6 +53,16 @@
 	return out.Close()
 }
 
+func writeSymlink(filename string, in io.Reader) error {
+	b, err := ioutil.ReadAll(in)
+	if err != nil {
+		return err
+	}
+	dest := string(b)
+	err = os.Symlink(dest, filename)
+	return err
+}
+
 func main() {
 	flag.Usage = func() {
 		fmt.Fprintln(os.Stderr, "usage: zipsync -d <output dir> [-l <output file>] [-f <pattern>] [zip]...")
@@ -122,7 +132,11 @@
 				if err != nil {
 					log.Fatal(err)
 				}
-				must(writeFile(filename, in, f.FileInfo().Mode()))
+				if f.FileInfo().Mode()&os.ModeSymlink != 0 {
+					must(writeSymlink(filename, in))
+				} else {
+					must(writeFile(filename, in, f.FileInfo().Mode()))
+				}
 				in.Close()
 				files = append(files, filename)
 			}
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 77d6ee9..22f712c 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -143,7 +143,6 @@
 	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
 	if err != nil {
 		ctx.ModuleErrorf(err.Error())
-		android.ReportPathErrorf(ctx, err.Error())
 	}
 }
 
@@ -204,20 +203,16 @@
 	}
 }
 
-// List of libraries in the unconditional class loader context, excluding dependencies of shared libraries.
+// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
+// included). This is the list of libraries that should be in the <uses-library> tags in the
+// manifest. Some of them may be present in the source manifest, others are added by manifest_fixer.
 func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
 	if clcMap != nil {
-		// compatibility libraries (those in conditional context) are not added to <uses-library> tags
-		ulibs = usesLibsRec(clcMap[AnySdkVersion])
-		ulibs = android.FirstUniqueStrings(ulibs)
-	}
-	return ulibs
-}
-
-func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
-	for _, clc := range clcs {
-		ulibs = append(ulibs, clc.Name)
-		ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
+		clcs := clcMap[AnySdkVersion]
+		ulibs = make([]string, 0, len(clcs))
+		for _, clc := range clcs {
+			ulibs = append(ulibs, clc.Name)
+		}
 	}
 	return ulibs
 }
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index e0a75bf..df68563 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -153,7 +153,7 @@
 
 	// Test for libraries that are added by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		wantUsesLibs := []string{"a", "b", "c", "d", "a2", "b2", "c2", "a1", "b1", "f", "a3", "b3"}
+		wantUsesLibs := []string{"a", "b", "c", "d", "f", "a3", "b3"}
 		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
 			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
 		}
diff --git a/env/env.go b/env/env.go
index a98e1f6..735a38a 100644
--- a/env/env.go
+++ b/env/env.go
@@ -27,6 +27,15 @@
 type envFileEntry struct{ Key, Value string }
 type envFileData []envFileEntry
 
+// Serializes the given environment variable name/value map into JSON formatted bytes by converting
+// to envFileEntry values and marshaling them.
+//
+// e.g. OUT_DIR = "out"
+// is converted to:
+// {
+//     "Key": "OUT_DIR",
+//     "Value": "out",
+// },
 func EnvFileContents(envDeps map[string]string) ([]byte, error) {
 	contents := make(envFileData, 0, len(envDeps))
 	for key, value := range envDeps {
@@ -45,8 +54,11 @@
 	return data, nil
 }
 
-func StaleEnvFile(filename string) (bool, error) {
-	data, err := ioutil.ReadFile(filename)
+// Reads and deserializes a Soong environment file located at the given file path to determine its
+// staleness. If any environment variable values have changed, it prints them out and returns true.
+// Failing to read or parse the file also causes it to return true.
+func StaleEnvFile(filepath string) (bool, error) {
+	data, err := ioutil.ReadFile(filepath)
 	if err != nil {
 		return true, err
 	}
@@ -79,6 +91,7 @@
 	return false, nil
 }
 
+// Implements sort.Interface so that we can use sort.Sort on envFileData arrays.
 func (e envFileData) Len() int {
 	return len(e)
 }
@@ -90,3 +103,5 @@
 func (e envFileData) Swap(i, j int) {
 	e[i], e[j] = e[j], e[i]
 }
+
+var _ sort.Interface = envFileData{}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 44b8149..850c8f9 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -14,9 +14,20 @@
 
 package etc
 
-import (
-	"strconv"
+// This file implements module types that install prebuilt artifacts.
+//
+// There exist two classes of prebuilt modules in the Android tree. The first class are the ones
+// based on `android.Prebuilt`, such as `cc_prebuilt_library` and `java_import`. This kind of
+// modules may exist both as prebuilts and source at the same time, though only one would be
+// installed and the other would be marked disabled. The `prebuilt_postdeps` mutator would select
+// the actual modules to be installed. More details in android/prebuilt.go.
+//
+// The second class is described in this file. Unlike `android.Prebuilt` based module types,
+// `prebuilt_etc` exist only as prebuilts and cannot have a same-named source module counterpart.
+// This makes the logic of `prebuilt_etc` to be much simpler as they don't need to go through the
+// various `prebuilt_*` mutators.
 
+import (
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -42,19 +53,22 @@
 }
 
 type prebuiltEtcProperties struct {
-	// Source file of this prebuilt.
+	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
 	Src *string `android:"path,arch_variant"`
 
-	// optional subdirectory under which this file is installed into, cannot be specified with relative_install_path, prefer relative_install_path
+	// Optional subdirectory under which this file is installed into, cannot be specified with
+	// relative_install_path, prefer relative_install_path.
 	Sub_dir *string `android:"arch_variant"`
 
-	// optional subdirectory under which this file is installed into, cannot be specified with sub_dir
+	// Optional subdirectory under which this file is installed into, cannot be specified with
+	// sub_dir.
 	Relative_install_path *string `android:"arch_variant"`
 
-	// optional name for the installed file. If unspecified, name of the module is used as the file name
+	// Optional name for the installed file. If unspecified, name of the module is used as the file
+	// name.
 	Filename *string `android:"arch_variant"`
 
-	// when set to true, and filename property is not set, the name for the installed file
+	// When set to true, and filename property is not set, the name for the installed file
 	// is the same as the file name of the source file.
 	Filename_from_src *bool `android:"arch_variant"`
 
@@ -84,8 +98,15 @@
 
 type PrebuiltEtcModule interface {
 	android.Module
+
+	// Returns the base install directory, such as "etc", "usr/share".
 	BaseDir() string
+
+	// Returns the sub install directory relative to BaseDir().
 	SubDir() string
+
+	// Returns an android.OutputPath to the intermeidate file, which is the renamed prebuilt source
+	// file.
 	OutputFile() android.OutputPath
 }
 
@@ -98,7 +119,8 @@
 	outputFilePath android.OutputPath
 	// The base install location, e.g. "etc" for prebuilt_etc, "usr/share" for prebuilt_usr_share.
 	installDirBase string
-	// The base install location when soc_specific property is set to true, e.g. "firmware" for prebuilt_firmware.
+	// The base install location when soc_specific property is set to true, e.g. "firmware" for
+	// prebuilt_firmware.
 	socInstallDirBase      string
 	installDirPath         android.InstallPath
 	additionalDependencies *android.Paths
@@ -168,14 +190,8 @@
 func (p *PrebuiltEtc) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
 }
 
-func (p *PrebuiltEtc) DepsMutator(ctx android.BottomUpMutatorContext) {
-	if p.properties.Src == nil {
-		ctx.PropertyErrorf("src", "missing prebuilt source file")
-	}
-}
-
 func (p *PrebuiltEtc) SourceFilePath(ctx android.ModuleContext) android.Path {
-	return android.PathForModuleSrc(ctx, android.String(p.properties.Src))
+	return android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
 }
 
 func (p *PrebuiltEtc) InstallDirPath() android.InstallPath {
@@ -204,31 +220,41 @@
 }
 
 func (p *PrebuiltEtc) Installable() bool {
-	return p.properties.Installable == nil || android.Bool(p.properties.Installable)
+	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
 }
 
 func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.sourceFilePath = android.PathForModuleSrc(ctx, android.String(p.properties.Src))
-	filename := android.String(p.properties.Filename)
-	filename_from_src := android.Bool(p.properties.Filename_from_src)
-	if filename == "" {
-		if filename_from_src {
-			filename = p.sourceFilePath.Base()
-		} else {
-			filename = ctx.ModuleName()
-		}
-	} else if filename_from_src {
-		ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
+	if p.properties.Src == nil {
+		ctx.PropertyErrorf("src", "missing prebuilt source file")
 		return
 	}
+	p.sourceFilePath = android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
+
+	// Determine the output file basename.
+	// If Filename is set, use the name specified by the property.
+	// If Filename_from_src is set, use the source file name.
+	// Otherwise use the module name.
+	filename := proptools.String(p.properties.Filename)
+	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
+	if filename != "" {
+		if filenameFromSrc {
+			ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
+			return
+		}
+	} else if filenameFromSrc {
+		filename = p.sourceFilePath.Base()
+	} else {
+		filename = ctx.ModuleName()
+	}
 	p.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
 
+	// Check that `sub_dir` and `relative_install_path` are not set at the same time.
 	if p.properties.Sub_dir != nil && p.properties.Relative_install_path != nil {
 		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
 	}
 
-	// If soc install dir was specified and SOC specific is set, set the installDirPath to the specified
-	// socInstallDirBase.
+	// If soc install dir was specified and SOC specific is set, set the installDirPath to the
+	// specified socInstallDirBase.
 	installBaseDir := p.installDirBase
 	if p.SocSpecific() && p.socInstallDirBase != "" {
 		installBaseDir = p.socInstallDirBase
@@ -274,11 +300,9 @@
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
 				}
-				entries.SetString("LOCAL_UNINSTALLABLE_MODULE", strconv.FormatBool(!p.Installable()))
+				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.Installable())
 				if p.additionalDependencies != nil {
-					for _, path := range *p.additionalDependencies {
-						entries.AddStrings("LOCAL_ADDITIONAL_DEPENDENCIES", path.String())
-					}
+					entries.AddStrings("LOCAL_ADDITIONAL_DEPENDENCIES", p.additionalDependencies.Strings()...)
 				}
 			},
 		},
@@ -339,9 +363,10 @@
 	return module
 }
 
-// prebuilt_firmware installs a firmware file to <partition>/etc/firmware directory for system image.
-// If soc_specific property is set to true, the firmware file is installed to the vendor <partition>/firmware
-// directory for vendor image.
+// prebuilt_firmware installs a firmware file to <partition>/etc/firmware directory for system
+// image.
+// If soc_specific property is set to true, the firmware file is installed to the
+// vendor <partition>/firmware directory for vendor image.
 func PrebuiltFirmwareFactory() android.Module {
 	module := &PrebuiltEtc{}
 	module.socInstallDirBase = "firmware"
@@ -352,8 +377,8 @@
 }
 
 // prebuilt_dsp installs a DSP related file to <partition>/etc/dsp directory for system image.
-// If soc_specific property is set to true, the DSP related file is installed to the vendor <partition>/dsp
-// directory for vendor image.
+// If soc_specific property is set to true, the DSP related file is installed to the
+// vendor <partition>/dsp directory for vendor image.
 func PrebuiltDSPFactory() android.Module {
 	module := &PrebuiltEtc{}
 	module.socInstallDirBase = "dsp"
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
new file mode 100644
index 0000000..926df6e
--- /dev/null
+++ b/filesystem/Android.bp
@@ -0,0 +1,15 @@
+bootstrap_go_package {
+    name: "soong-filesystem",
+    pkgPath: "android/soong/filesystem",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+    ],
+    srcs: [
+        "filesystem.go",
+    ],
+    testSrcs: [
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
new file mode 100644
index 0000000..a1605b4
--- /dev/null
+++ b/filesystem/filesystem.go
@@ -0,0 +1,76 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"fmt"
+
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterModuleType("android_filesystem", filesystemFactory)
+}
+
+type filesystem struct {
+	android.ModuleBase
+	android.PackagingBase
+}
+
+func filesystemFactory() android.Module {
+	module := &filesystem{}
+	android.InitPackageModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
+	f.AddDeps(ctx)
+}
+
+var pctx = android.NewPackageContext("android/soong/filesystem")
+
+func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
+	f.CopyDepsToZip(ctx, zipFile)
+
+	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
+	builder := android.NewRuleBuilder()
+	builder.Command().
+		BuiltTool(ctx, "zipsync").
+		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
+		Input(zipFile)
+
+	mkuserimg := ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs")
+	propFile := android.PathForModuleOut(ctx, "prop").OutputPath
+	// TODO(jiyong): support more filesystem types other than ext4
+	propsText := fmt.Sprintf(`mount_point=system\n`+
+		`fs_type=ext4\n`+
+		`use_dynamic_partition_size=true\n`+
+		`ext_mkuserimg=%s\n`, mkuserimg.String())
+	builder.Command().Text("echo").Flag("-e").Flag(`"` + propsText + `"`).
+		Text(">").Output(propFile).
+		Implicit(mkuserimg)
+
+	image := android.PathForModuleOut(ctx, "filesystem.img").OutputPath
+	builder.Command().BuiltTool(ctx, "build_image").
+		Text(rootDir.String()). // input directory
+		Input(propFile).
+		Output(image).
+		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()))
+}
diff --git a/genrule/Android.bp b/genrule/Android.bp
index ff543a6..0e27d4e 100644
--- a/genrule/Android.bp
+++ b/genrule/Android.bp
@@ -4,8 +4,10 @@
     deps: [
         "blueprint",
         "blueprint-pathtools",
+        "sbox_proto",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-shared",
     ],
     srcs: [
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 3067846..e5ee3fc 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -12,11 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// A genrule module takes a list of source files ("srcs" property), an optional
+// list of tools ("tools" property), and a command line ("cmd" property), to
+// generate output files ("out" property).
+
 package genrule
 
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
@@ -25,9 +30,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/shared"
-	"crypto/sha256"
-	"path/filepath"
+	"android/soong/bazel"
 )
 
 func init() {
@@ -48,6 +51,8 @@
 var (
 	pctx = android.NewPackageContext("android/soong/genrule")
 
+	// Used by gensrcs when there is more than 1 shard to merge the outputs
+	// of each shard into a zip file.
 	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
 		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
 		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
@@ -80,13 +85,6 @@
 	blueprint.BaseDependencyTag
 	label string
 }
-
-// TODO(cparsons): Move to a common location when there is more than just
-// genrule with a bazel_module property.
-type bazelModuleProperties struct {
-	Label string
-}
-
 type generatorProperties struct {
 	// The command to run on one or more input files. Cmd supports substitution of a few variables
 	//
@@ -120,9 +118,10 @@
 	// input files to exclude
 	Exclude_srcs []string `android:"path,arch_variant"`
 
-	// in bazel-enabled mode, the bazel label to evaluate instead of this module
-	Bazel_module bazelModuleProperties
+	// Properties for Bazel migration purposes.
+	bazel.Properties
 }
+
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -135,6 +134,9 @@
 
 	properties generatorProperties
 
+	// For the different tasks that genrule and gensrc generate. genrule will
+	// generate 1 task, and gensrc will generate 1 or more tasks based on the
+	// number of shards the input files are sharded into.
 	taskGenerator taskFunc
 
 	deps        android.Paths
@@ -156,14 +158,17 @@
 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
 
 type generateTask struct {
-	in          android.Paths
-	out         android.WritablePaths
-	copyTo      android.WritablePaths
-	genDir      android.WritablePath
-	sandboxOuts []string
-	cmd         string
-	shard       int
-	shards      int
+	in         android.Paths
+	out        android.WritablePaths
+	depFile    android.WritablePath
+	copyTo     android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
+	genDir     android.WritablePath
+	extraTools android.Paths // dependencies on tools used by the generator
+
+	cmd string
+	// For gensrsc sharding.
+	shard  int
+	shards int
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -208,6 +213,7 @@
 	}
 	return ok
 }
+
 func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	g.subName = ctx.ModuleSubDir()
 
@@ -329,20 +335,25 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	// Generate tasks, either from genrule or gensrcs.
 	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
-		for _, out := range task.out {
-			addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
+		if len(task.out) == 0 {
+			ctx.ModuleErrorf("must have at least one output file")
+			return
 		}
 
-		referencedIn := false
+		for _, out := range task.out {
+			addLocationLabel(out.Rel(), []string{android.SboxPathForOutput(out, task.genDir)})
+		}
+
 		referencedDepfile := false
 
-		rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, error) {
+		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
 			// report the error directly without returning an error to android.Expand to catch multiple errors in a
 			// single run
-			reportError := func(fmt string, args ...interface{}) (string, bool, error) {
+			reportError := func(fmt string, args ...interface{}) (string, error) {
 				ctx.PropertyErrorf("cmd", fmt, args...)
-				return "SOONG_ERROR", false, nil
+				return "SOONG_ERROR", nil
 			}
 
 			switch name {
@@ -357,20 +368,23 @@
 					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
 						firstLabel, firstLabel)
 				}
-				return locationLabels[firstLabel][0], false, nil
+				return locationLabels[firstLabel][0], nil
 			case "in":
-				referencedIn = true
-				return "${in}", true, nil
+				return strings.Join(srcFiles.Strings(), " "), nil
 			case "out":
-				return "__SBOX_OUT_FILES__", false, nil
+				var sandboxOuts []string
+				for _, out := range task.out {
+					sandboxOuts = append(sandboxOuts, android.SboxPathForOutput(out, task.genDir))
+				}
+				return strings.Join(sandboxOuts, " "), nil
 			case "depfile":
 				referencedDepfile = true
 				if !Bool(g.properties.Depfile) {
 					return reportError("$(depfile) used without depfile property")
 				}
-				return "__SBOX_DEPFILE__", false, nil
+				return "__SBOX_DEPFILE__", nil
 			case "genDir":
-				return "__SBOX_OUT_DIR__", false, nil
+				return android.SboxPathForOutput(task.genDir, task.genDir), nil
 			default:
 				if strings.HasPrefix(name, "location ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -381,7 +395,7 @@
 							return reportError("label %q has multiple files, use $(locations %s) to reference it",
 								label, label)
 						}
-						return paths[0], false, nil
+						return paths[0], nil
 					} else {
 						return reportError("unknown location label %q", label)
 					}
@@ -391,7 +405,7 @@
 						if len(paths) == 0 {
 							return reportError("label %q has no files", label)
 						}
-						return strings.Join(paths, " "), false, nil
+						return strings.Join(paths, " "), nil
 					} else {
 						return reportError("unknown locations label %q", label)
 					}
@@ -410,50 +424,45 @@
 			ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
 			return
 		}
-
-		// tell the sbox command which directory to use as its sandbox root
-		buildDir := android.PathForOutput(ctx).String()
-		sandboxPath := shared.TempDirForOutDir(buildDir)
-
-		// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
-		// to be replaced later by ninja_strings.go
-		depfilePlaceholder := ""
-		if Bool(g.properties.Depfile) {
-			depfilePlaceholder = "$depfileArgs"
-		}
-
-		// Escape the command for the shell
-		rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'"
 		g.rawCommands = append(g.rawCommands, rawCommand)
 
-		sandboxCommand := fmt.Sprintf("rm -rf %s && $sboxCmd --sandbox-path %s --output-root %s",
-			task.genDir, sandboxPath, task.genDir)
-
-		if !referencedIn {
-			sandboxCommand = sandboxCommand + hashSrcFiles(srcFiles)
-		}
-
-		sandboxCommand = sandboxCommand + fmt.Sprintf(" -c %s %s $allouts",
-			rawCommand, depfilePlaceholder)
-
-		ruleParams := blueprint.RuleParams{
-			Command:     sandboxCommand,
-			CommandDeps: []string{"$sboxCmd"},
-		}
-		args := []string{"allouts"}
-		if Bool(g.properties.Depfile) {
-			ruleParams.Deps = blueprint.DepsGCC
-			args = append(args, "depfileArgs")
-		}
+		// Pick a unique 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 > 1 {
+		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()
 		}
-		rule := ctx.Rule(pctx, name, ruleParams, args...)
 
-		g.generateSourceFile(ctx, task, rule)
+		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)
+		cmd.Implicits(g.deps)
+		cmd.Implicits(task.extraTools)
+		if Bool(g.properties.Depfile) {
+			cmd.ImplicitDepFile(task.depFile)
+		}
+
+		// Create the rule to run the genrule command inside sbox.
+		rule.Build(pctx, ctx, name, desc)
 
 		if len(task.copyTo) > 0 {
+			// If copyTo is set, multiple shards need to be copied into a single directory.
+			// task.out contains the per-shard paths, and copyTo contains the corresponding
+			// final path.  The files need to be copied into the final directory by a
+			// single rule so it can remove the directory before it starts to ensure no
+			// old files remain.  zipsync already does this, so build up zipArgs that
+			// zip all the per-shard directories into a single zip.
 			outputFiles = append(outputFiles, task.copyTo...)
 			copyFrom = append(copyFrom, task.out.Paths()...)
 			zipArgs.WriteString(" -C " + task.genDir.String())
@@ -464,10 +473,13 @@
 	}
 
 	if len(copyFrom) > 0 {
+		// Create a rule that zips all the per-shard directories into a single zip and then
+		// uses zipsync to unzip it into the final directory.
 		ctx.Build(pctx, android.BuildParams{
-			Rule:      gensrcsMerge,
-			Implicits: copyFrom,
-			Outputs:   outputFiles,
+			Rule:        gensrcsMerge,
+			Implicits:   copyFrom,
+			Outputs:     outputFiles,
+			Description: "merge shards",
 			Args: map[string]string{
 				"zipArgs": zipArgs.String(),
 				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
@@ -501,51 +513,6 @@
 		}
 	}
 }
-func hashSrcFiles(srcFiles android.Paths) string {
-	h := sha256.New()
-	for _, src := range srcFiles {
-		h.Write([]byte(src.String()))
-	}
-	return fmt.Sprintf(" --input-hash %x", h.Sum(nil))
-}
-
-func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask, rule blueprint.Rule) {
-	desc := "generate"
-	if len(task.out) == 0 {
-		ctx.ModuleErrorf("must have at least one output file")
-		return
-	}
-	if len(task.out) == 1 {
-		desc += " " + task.out[0].Base()
-	}
-
-	var depFile android.ModuleGenPath
-	if Bool(g.properties.Depfile) {
-		depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
-	}
-
-	if task.shards > 1 {
-		desc += " " + strconv.Itoa(task.shard)
-	}
-
-	params := android.BuildParams{
-		Rule:            rule,
-		Description:     desc,
-		Output:          task.out[0],
-		ImplicitOutputs: task.out[1:],
-		Inputs:          task.in,
-		Implicits:       g.deps,
-		Args: map[string]string{
-			"allouts": strings.Join(task.sandboxOuts, " "),
-		},
-	}
-	if Bool(g.properties.Depfile) {
-		params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
-		params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
-	}
-
-	ctx.Build(pctx, params)
-}
 
 // Collect information for opening IDE project files in java/jdeps.go.
 func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
@@ -610,61 +577,70 @@
 func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
 }
 
-// replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>"
-func pathToSandboxOut(path android.Path, genDir android.Path) string {
-	relOut, err := filepath.Rel(genDir.String(), path.String())
-	if err != nil {
-		panic(fmt.Sprintf("Could not make ${out} relative: %v", err))
-	}
-	return filepath.Join("__SBOX_OUT_DIR__", relOut)
-
-}
-
 func NewGenSrcs() *Module {
 	properties := &genSrcsProperties{}
 
+	// finalSubDir is the name of the subdirectory that output files will be generated into.
+	// It is used so that per-shard directories can be placed alongside it an then finally
+	// merged into it.
+	const finalSubDir = "gensrcs"
+
 	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
-		genDir := android.PathForModuleGen(ctx, "gensrcs")
 		shardSize := defaultShardSize
 		if s := properties.Shard_size; s != nil {
 			shardSize = int(*s)
 		}
 
+		// gensrcs rules can easily hit command line limits by repeating the command for
+		// every input file.  Shard the input files into groups.
 		shards := android.ShardPaths(srcFiles, shardSize)
 		var generateTasks []generateTask
 
 		for i, shard := range shards {
 			var commands []string
 			var outFiles android.WritablePaths
+			var commandDepFiles []string
 			var copyTo android.WritablePaths
-			var shardDir android.WritablePath
-			var sandboxOuts []string
 
+			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
+			// shard will be write to their own directories and then be merged together
+			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
+			// the sbox rule will write directly to finalSubDir.
+			genSubDir := finalSubDir
 			if len(shards) > 1 {
-				shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
-			} else {
-				shardDir = genDir
+				genSubDir = strconv.Itoa(i)
 			}
 
-			for _, in := range shard {
-				outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
-				sandboxOutfile := pathToSandboxOut(outFile, genDir)
+			genDir := android.PathForModuleGen(ctx, genSubDir)
 
+			for _, in := range shard {
+				outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
+
+				// If sharding is enabled, then outFile is the path to the output file in
+				// the shard directory, and copyTo is the path to the output file in the
+				// final directory.
 				if len(shards) > 1 {
-					shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
+					shardFile := android.GenPathWithExt(ctx, genSubDir, in, String(properties.Output_extension))
 					copyTo = append(copyTo, outFile)
 					outFile = shardFile
 				}
 
 				outFiles = append(outFiles, outFile)
-				sandboxOuts = append(sandboxOuts, sandboxOutfile)
 
+				// pre-expand the command line to replace $in and $out with references to
+				// a single input and output file.
 				command, err := android.Expand(rawCommand, func(name string) (string, error) {
 					switch name {
 					case "in":
 						return in.String(), nil
 					case "out":
-						return sandboxOutfile, nil
+						return android.SboxPathForOutput(outFile, genDir), 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)
+						commandDepFiles = append(commandDepFiles, depFile)
+						return depFile, nil
 					default:
 						return "$(" + name + ")", nil
 					}
@@ -679,15 +655,29 @@
 			}
 			fullCommand := strings.Join(commands, " && ")
 
+			var outputDepfile android.WritablePath
+			var extraTools android.Paths
+			if len(commandDepFiles) > 0 {
+				// Each command wrote to a depfile, but ninja can only handle one
+				// depfile per rule.  Use the dep_fixer tool at the end of the
+				// command to combine all the depfiles into a single output depfile.
+				outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
+				depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
+				fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
+					depFixerTool.String(), strings.Join(commandDepFiles, " "))
+				extraTools = append(extraTools, depFixerTool)
+			}
+
 			generateTasks = append(generateTasks, generateTask{
-				in:          shard,
-				out:         outFiles,
-				copyTo:      copyTo,
-				genDir:      shardDir,
-				sandboxOuts: sandboxOuts,
-				cmd:         fullCommand,
-				shard:       i,
-				shards:      len(shards),
+				in:         shard,
+				out:        outFiles,
+				depFile:    outputDepfile,
+				copyTo:     copyTo,
+				genDir:     genDir,
+				cmd:        fullCommand,
+				shard:      i,
+				shards:     len(shards),
+				extraTools: extraTools,
 			})
 		}
 
@@ -695,7 +685,7 @@
 	}
 
 	g := generatorFactory(taskGenerator, properties)
-	g.subDir = "gensrcs"
+	g.subDir = finalSubDir
 	return g
 }
 
@@ -720,18 +710,20 @@
 
 	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
 		outs := make(android.WritablePaths, len(properties.Out))
-		sandboxOuts := make([]string, len(properties.Out))
-		genDir := android.PathForModuleGen(ctx)
+		var depFile android.WritablePath
 		for i, out := range properties.Out {
-			outs[i] = android.PathForModuleGen(ctx, out)
-			sandboxOuts[i] = pathToSandboxOut(outs[i], genDir)
+			outPath := android.PathForModuleGen(ctx, out)
+			if i == 0 {
+				depFile = outPath.ReplaceExtension(ctx, "d")
+			}
+			outs[i] = outPath
 		}
 		return []generateTask{{
-			in:          srcFiles,
-			out:         outs,
-			genDir:      android.PathForModuleGen(ctx),
-			sandboxOuts: sandboxOuts,
-			cmd:         rawCommand,
+			in:      srcFiles,
+			out:     outs,
+			depFile: depFile,
+			genDir:  android.PathForModuleGen(ctx),
+			cmd:     rawCommand,
 		}}
 	}
 
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index c3c0b97..8d3cfcb 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -141,7 +141,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "empty location tool2",
@@ -150,7 +150,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "empty location tool file",
@@ -159,7 +159,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "empty location tool file fg",
@@ -168,7 +168,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "empty location tool and tool file",
@@ -178,7 +178,7 @@
 				out: ["out"],
 				cmd: "$(location) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool",
@@ -187,7 +187,7 @@
 				out: ["out"],
 				cmd: "$(location tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool2",
@@ -196,7 +196,7 @@
 				out: ["out"],
 				cmd: "$(location :tool) > $(out)",
 			`,
-			expect: "out/tool > __SBOX_OUT_FILES__",
+			expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool file",
@@ -205,7 +205,7 @@
 				out: ["out"],
 				cmd: "$(location tool_file1) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool file fg",
@@ -214,7 +214,7 @@
 				out: ["out"],
 				cmd: "$(location :1tool_file) > $(out)",
 			`,
-			expect: "tool_file1 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool files",
@@ -223,7 +223,7 @@
 				out: ["out"],
 				cmd: "$(locations :tool_files) > $(out)",
 			`,
-			expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__",
+			expect: "tool_file1 tool_file2 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "in1",
@@ -232,7 +232,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "in1 fg",
@@ -241,7 +241,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "ins",
@@ -250,7 +250,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "ins fg",
@@ -259,7 +259,7 @@
 				out: ["out"],
 				cmd: "cat $(in) > $(out)",
 			`,
-			expect: "cat ${in} > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "location in1",
@@ -268,7 +268,7 @@
 				out: ["out"],
 				cmd: "cat $(location in1) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "location in1 fg",
@@ -277,7 +277,7 @@
 				out: ["out"],
 				cmd: "cat $(location :1in) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "location ins",
@@ -286,7 +286,7 @@
 				out: ["out"],
 				cmd: "cat $(location in1) > $(out)",
 			`,
-			expect: "cat in1 > __SBOX_OUT_FILES__",
+			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "location ins fg",
@@ -295,7 +295,7 @@
 				out: ["out"],
 				cmd: "cat $(locations :ins) > $(out)",
 			`,
-			expect: "cat in1 in2 > __SBOX_OUT_FILES__",
+			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "outs",
@@ -303,7 +303,7 @@
 				out: ["out", "out2"],
 				cmd: "echo foo > $(out)",
 			`,
-			expect: "echo foo > __SBOX_OUT_FILES__",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2",
 		},
 		{
 			name: "location out",
@@ -311,7 +311,7 @@
 				out: ["out", "out2"],
 				cmd: "echo foo > $(location out2)",
 			`,
-			expect: "echo foo > __SBOX_OUT_DIR__/out2",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2",
 		},
 		{
 			name: "depfile",
@@ -320,7 +320,7 @@
 				depfile: true,
 				cmd: "echo foo > $(out) && touch $(depfile)",
 			`,
-			expect: "echo foo > __SBOX_OUT_FILES__ && touch __SBOX_DEPFILE__",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out && touch __SBOX_DEPFILE__",
 		},
 		{
 			name: "gendir",
@@ -328,7 +328,7 @@
 				out: ["out"],
 				cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)",
 			`,
-			expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_FILES__",
+			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out",
 		},
 
 		{
@@ -443,7 +443,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__",
+			expect: "cat ***missing srcs :missing*** > __SBOX_SANDBOX_DIR__/out/out",
 		},
 		{
 			name: "tool allow missing dependencies",
@@ -455,7 +455,7 @@
 
 			allowMissingDependencies: true,
 
-			expect: "***missing tool :missing*** > __SBOX_OUT_FILES__",
+			expect: "***missing tool :missing*** > __SBOX_SANDBOX_DIR__/out/out",
 		},
 	}
 
@@ -495,7 +495,7 @@
 			}
 
 			gen := ctx.ModuleForTests("gen", "").Module().(*Module)
-			if g, w := gen.rawCommands[0], "'"+test.expect+"'"; w != g {
+			if g, w := gen.rawCommands[0], test.expect; w != g {
 				t.Errorf("want %q, got %q", w, g)
 			}
 		})
@@ -542,18 +542,18 @@
 	}{
 		{
 			name: "hash0",
-			// sha256 value obtained from: echo -n 'in1.txtin2.txt' | sha256sum
-			expectedHash: "031097e11e0a8c822c960eb9742474f46336360a515744000d086d94335a9cb9",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
+			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
 		},
 		{
 			name: "hash1",
-			// sha256 value obtained from: echo -n 'in1.txtin2.txtin3.txt' | sha256sum
-			expectedHash: "de5d22a4a7ab50d250cc59fcdf7a7e0775790d270bfca3a7a9e1f18a70dd996c",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
 		},
 		{
 			name: "hash2",
-			// $(in) is present, option should not appear
-			expectedHash: "",
+			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
+			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
 		},
 	}
 
@@ -570,20 +570,11 @@
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
 			gen := ctx.ModuleForTests(test.name, "")
-			command := gen.Rule("generator").RuleParams.Command
+			manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+			hash := manifest.Commands[0].GetInputHash()
 
-			if len(test.expectedHash) > 0 {
-				// We add spaces before and after to make sure that
-				// this option doesn't abutt another sbox option.
-				expectedInputHashOption := " --input-hash " + test.expectedHash + " "
-
-				if !strings.Contains(command, expectedInputHashOption) {
-					t.Errorf("Expected command \"%s\" to contain \"%s\"", command, expectedInputHashOption)
-				}
-			} else {
-				if strings.Contains(command, "--input-hash") {
-					t.Errorf("Unexpected \"--input-hash\" found in command: \"%s\"", command)
-				}
+			if g, w := hash, test.expectedHash; g != w {
+				t.Errorf("Expected has %q, got %q", w, g)
 			}
 		})
 	}
@@ -609,7 +600,7 @@
 				cmd: "$(location) $(in) > $(out)",
 			`,
 			cmds: []string{
-				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''",
+				"bash -c 'out/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
 			},
 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
@@ -623,8 +614,8 @@
 				shard_size: 2,
 			`,
 			cmds: []string{
-				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''",
-				"'bash -c '\\''out/tool in3.txt > __SBOX_OUT_DIR__/in3.h'\\'''",
+				"bash -c 'out/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
+				"bash -c 'out/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'",
 			},
 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
@@ -710,7 +701,7 @@
 	}
 	gen := ctx.ModuleForTests("gen", "").Module().(*Module)
 
-	expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'"
+	expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
 	if gen.rawCommands[0] != expectedCmd {
 		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0])
 	}
diff --git a/java/aar.go b/java/aar.go
index 7c3840b..1940d7f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -259,16 +259,16 @@
 	})
 
 func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext,
-	sdkLibraries dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
 
 	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
-		aaptLibs(ctx, sdkContext, sdkLibraries)
+		aaptLibs(ctx, sdkContext, classLoaderContexts)
 
 	// App manifest file
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
 
-	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, sdkLibraries,
+	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, classLoaderContexts,
 		a.isLibrary, a.useEmbeddedNativeLibs, a.usesNonSdkApis, a.useEmbeddedDex, a.hasNoCode,
 		a.LoggingParent)
 
@@ -389,15 +389,15 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, sdkLibraries dexpreopt.ClassLoaderContextMap) (
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, classLoaderContexts dexpreopt.ClassLoaderContextMap) (
 	transitiveStaticLibs, transitiveStaticLibManifests android.Paths, staticRRODirs []rroDir, assets, deps android.Paths, flags []string) {
 
 	var sharedLibs android.Paths
 
-	if sdkLibraries == nil {
+	if classLoaderContexts == nil {
 		// Not all callers need to compute class loader context, those who don't just pass nil.
 		// Create a temporary class loader context here (it will be computed, but not used).
-		sdkLibraries = make(dexpreopt.ClassLoaderContextMap)
+		classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 	}
 
 	sdkDep := decodeSdkDep(ctx, sdkContext)
@@ -426,7 +426,7 @@
 			// (including the java_sdk_library) itself then append any implicit sdk library
 			// names to the list of sdk libraries to be added to the manifest.
 			if component, ok := module.(SdkLibraryComponentDependency); ok {
-				sdkLibraries.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
+				classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
 					component.DexJarBuildPath(), component.DexJarInstallPath())
 			}
 
@@ -439,7 +439,7 @@
 				transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
 				transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
 				transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...)
-				sdkLibraries.AddContextMap(aarDep.ExportedSdkLibs(), depName)
+				classLoaderContexts.AddContextMap(aarDep.ClassLoaderContexts(), depName)
 				if aarDep.ExportedAssets().Valid() {
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
@@ -461,7 +461,7 @@
 		// Add nested dependencies after processing the direct dependency: if it is a <uses-library>,
 		// nested context is added as its subcontext, and should not be re-added at the top-level.
 		if dep, ok := module.(Dependency); ok {
-			sdkLibraries.AddContextMap(dep.ExportedSdkLibs(), depName)
+			classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), depName)
 		}
 	})
 
@@ -514,8 +514,8 @@
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
-	a.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
-	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs)
+	a.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
+	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -832,12 +832,12 @@
 	return nil
 }
 
-func (a *AARImport) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
+func (a *AARImport) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
-func (d *AARImport) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (d *AARImport) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (a *AARImport) SrcJarArgs() ([]string, android.Paths) {
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 6b39c35..c76bb2f 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -44,7 +44,7 @@
 
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
 func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
-	sdkLibraries dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
 	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
 
 	var args []string
@@ -71,7 +71,7 @@
 		args = append(args, "--use-embedded-dex")
 	}
 
-	for _, usesLib := range sdkLibraries.UsesLibs() {
+	for _, usesLib := range classLoaderContexts.UsesLibs() {
 		if inList(usesLib, dexpreopt.OptionalCompatUsesLibs) {
 			args = append(args, "--optional-uses-library", usesLib)
 		} else {
diff --git a/java/androidmk.go b/java/androidmk.go
index c606245..9ad3f9b 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -85,7 +85,6 @@
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
-			DistFiles:  library.distFiles,
 			OutputFile: android.OptionalPathForPath(library.outputFile),
 			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
@@ -115,7 +114,7 @@
 						entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 					}
 
-					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.exportedSdkLibs.UsesLibs()...)
+					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.classLoaderContexts.UsesLibs()...)
 
 					if len(library.additionalCheckedModules) != 0 {
 						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
@@ -160,6 +159,9 @@
 			entries.SetString("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", "true")
 		}
 		entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", j.testProperties.Test_mainline_modules...)
+		if Bool(j.testProperties.Test_options.Unit_test) {
+			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+		}
 	})
 
 	return entriesList
@@ -521,17 +523,12 @@
 	// Note that dstubs.apiFile can be also be nil if WITHOUT_CHECKS_API is true.
 	// TODO(b/146727827): Revert when we do not need to generate stubs and API separately.
 
-	var distFiles android.TaggedDistFiles
-	if dstubs.apiFile != nil {
-		distFiles = android.MakeDefaultDistFiles(dstubs.apiFile)
-	}
 	outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar)
 	if !outputFile.Valid() {
 		outputFile = android.OptionalPathForPath(dstubs.apiFile)
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		DistFiles:  distFiles,
 		OutputFile: outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 075b7aa..233e9d5 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"reflect"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -135,182 +134,6 @@
 	}
 }
 
-func TestDistWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-			},
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["hi"],
-				tag: ".jar",
-			},
-		}
-	`)
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-	if len(withTagEntries[0].DistFiles[".jar"]) != 1 ||
-		!strings.Contains(withTagEntries[0].DistFiles[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected DistFiles to contain classes.jar, got %v", withTagEntries[0].DistFiles)
-	}
-	if len(withoutTagEntries[0].DistFiles[".jar"]) > 0 {
-		t.Errorf("did not expect explicit DistFile for .jar tag, got %v", withoutTagEntries[0].DistFiles[".jar"])
-	}
-}
-
-func TestDistWithDest(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["my_goal"],
-				dest: "my/custom/dest/dir",
-			},
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 2 {
-		t.Errorf("Expected 2 entries for dist: PHONY and dist-for-goals, but got %q", distStrings)
-	}
-
-	if distStrings[0] != ".PHONY: my_goal\n" {
-		t.Errorf("Expected .PHONY entry to declare my_goal, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,my_goal") ||
-		!strings.Contains(distStrings[1], ".intermediates/foo/android_common/dex/foo.jar:my/custom/dest/dir") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_goal and new dest dir, but got: %s", distStrings[1])
-	}
-}
-
-func TestDistsWithAllProperties(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dist: {
-				targets: ["baz"],
-			},
-			dists: [
-				{
-					targets: ["bar"],
-					tag: ".jar",
-					dest: "bar.jar",
-					dir: "bar/dir",
-					suffix: ".qux",
-				},
-			]
-		}
-	`)
-
-	module := ctx.ModuleForTests("foo", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, config, "", module)
-	if len(entries) != 2 {
-		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
-	}
-
-	distStrings := entries[0].GetDistForGoals(module)
-
-	if len(distStrings) != 4 {
-		t.Errorf("Expected 4 entries for dist: PHONY and dist-for-goals, but got %d", len(distStrings))
-	}
-
-	if distStrings[0] != ".PHONY: bar\n" {
-		t.Errorf("Expected .PHONY entry to declare bar, but got: %s", distStrings[0])
-	}
-
-	if !strings.Contains(distStrings[1], "$(call dist-for-goals,bar") ||
-		!strings.Contains(
-			distStrings[1],
-			".intermediates/foo/android_common/javac/foo.jar:bar/dir/bar.qux.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain bar and new dest dir, but got: %s", distStrings[1])
-	}
-
-	if distStrings[2] != ".PHONY: baz\n" {
-		t.Errorf("Expected .PHONY entry to declare baz, but got: %s", distStrings[2])
-	}
-
-	if !strings.Contains(distStrings[3], "$(call dist-for-goals,baz") ||
-		!strings.Contains(distStrings[3], ".intermediates/foo/android_common/dex/foo.jar:foo.jar") {
-		t.Errorf(
-			"Expected dist-for-goals entry to contain my_other_goal and new dest dir, but got: %s",
-			distStrings[3])
-	}
-}
-
-func TestDistsWithTag(t *testing.T) {
-	ctx, config := testJava(t, `
-		java_library {
-			name: "foo_without_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-				},
-			],
-		}
-		java_library {
-			name: "foo_with_tag",
-			srcs: ["a.java"],
-			compile_dex: true,
-			dists: [
-				{
-					targets: ["hi"],
-					tag: ".jar",
-				},
-			],
-		}
-	`)
-
-	moduleWithoutTag := ctx.ModuleForTests("foo_without_tag", "android_common").Module()
-	moduleWithTag := ctx.ModuleForTests("foo_with_tag", "android_common").Module()
-
-	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithoutTag)
-	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithTag)
-
-	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
-	}
-
-	distFilesWithoutTag := withoutTagEntries[0].DistFiles
-	distFilesWithTag := withTagEntries[0].DistFiles
-
-	if len(distFilesWithTag[".jar"]) != 1 ||
-		!strings.Contains(distFilesWithTag[".jar"][0].String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected foo_with_tag's .jar-tagged DistFiles to contain classes.jar, got %v", distFilesWithTag[".jar"])
-	}
-	if len(distFilesWithoutTag[".jar"]) > 0 {
-		t.Errorf("did not expect foo_without_tag's .jar-tagged DistFiles to contain files, but got %v", distFilesWithoutTag[".jar"])
-	}
-}
-
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
 	ctx, config := testJava(t, `
 		java_sdk_library {
diff --git a/java/app.go b/java/app.go
index 9ff413c..3384bbd 100755
--- a/java/app.go
+++ b/java/app.go
@@ -566,7 +566,7 @@
 
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, sdkContext(a), a.exportedSdkLibs, aaptLinkFlags...)
+	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -608,7 +608,7 @@
 	}
 	a.dexpreopter.uncompressedDex = *a.dexProperties.Uncompress_dex
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
-	a.dexpreopter.classLoaderContexts = a.exportedSdkLibs
+	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
 
 	if ctx.ModuleName() != "framework-res" {
@@ -779,7 +779,7 @@
 		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
 	}
 
-	a.exportedSdkLibs = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
@@ -788,7 +788,7 @@
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
 	// Add implicit SDK libraries to <uses-library> list.
-	for _, usesLib := range a.exportedSdkLibs.UsesLibs() {
+	for _, usesLib := range a.classLoaderContexts.UsesLibs() {
 		a.usesLibrary.addLib(usesLib, inList(usesLib, dexpreopt.OptionalCompatUsesLibs))
 	}
 
@@ -890,7 +890,7 @@
 
 		if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) {
 			if dep, ok := module.(*cc.Module); ok {
-				if dep.IsNdk() || dep.IsStubs() {
+				if dep.IsNdk(ctx.Config()) || dep.IsStubs() {
 					return false
 				}
 
@@ -1982,7 +1982,7 @@
 				dep := ctx.OtherModuleName(m)
 				if lib, ok := m.(Dependency); ok {
 					clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
-						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ExportedSdkLibs())
+						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{dep})
 				} else {
diff --git a/java/app_test.go b/java/app_test.go
index 6429ab8..6efb0dc 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -291,7 +291,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -335,7 +335,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index d8b617e..4914d74 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -163,12 +163,12 @@
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
+func (d *DeviceHostConverter) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
 	return nil
 }
 
-func (d *DeviceHostConverter) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (d *DeviceHostConverter) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (d *DeviceHostConverter) SrcJarArgs() ([]string, android.Paths) {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index c7a27c2..da8489a 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1050,7 +1050,8 @@
 		return android.Paths{d.stubsSrcJar}, nil
 	case ".docs.zip":
 		return android.Paths{d.docZip}, nil
-	case ".api.txt":
+	case ".api.txt", android.DefaultDistTag:
+		// This is the default dist path for dist properties that have no tag property.
 		return android.Paths{d.apiFilePath}, nil
 	case ".removed-api.txt":
 		return android.Paths{d.removedApiFilePath}, nil
@@ -1748,8 +1749,6 @@
 
 	properties PrebuiltStubsSourcesProperties
 
-	// The source directories containing stubs source files.
-	srcDirs     android.Paths
 	stubsSrcJar android.ModuleOutPath
 }
 
@@ -1769,21 +1768,29 @@
 func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
 
-	p.srcDirs = android.PathsForModuleSrc(ctx, p.properties.Srcs)
+	if len(p.properties.Srcs) != 1 {
+		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
+		return
+	}
+
+	localSrcDir := p.properties.Srcs[0]
+	// Although PathForModuleSrc can return nil if either the path doesn't exist or
+	// the path components are invalid it won't in this case because no components
+	// are specified and the module directory must exist in order to get this far.
+	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
+
+	// Glob the contents of the directory just in case the directory does not exist.
+	srcGlob := localSrcDir + "/**/*"
+	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
 
 	rule := android.NewRuleBuilder()
-	command := rule.Command().
+	rule.Command().
 		BuiltTool(ctx, "soong_zip").
 		Flag("-write_if_changed").
 		Flag("-jar").
-		FlagWithOutput("-o ", p.stubsSrcJar)
-
-	for _, d := range p.srcDirs {
-		dir := d.String()
-		command.
-			FlagWithArg("-C ", dir).
-			FlagWithInput("-D ", d)
-	}
+		FlagWithOutput("-o ", p.stubsSrcJar).
+		FlagWithArg("-C ", srcDir.String()).
+		FlagWithRspFileInputList("-r ", srcPaths)
 
 	rule.Restat()
 
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 1f80e77..8f0e09c 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -255,11 +255,11 @@
 		FlagWithInput("--max-target-p ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-p.txt")).
 		FlagWithInput("--max-target-o ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").
+			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--blocked ",
 			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
 		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).
+			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--unsupported ", android.PathForSource(
 			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
diff --git a/java/java.go b/java/java.go
index d049685..8738e00 100644
--- a/java/java.go
+++ b/java/java.go
@@ -201,7 +201,10 @@
 	// List of modules to use as annotation processors
 	Plugins []string
 
-	// List of modules to export to libraries that directly depend on this library as annotation processors
+	// List of modules to export to libraries that directly depend on this library as annotation
+	// processors.  Note that if the plugins set generates_api: true this will disable the turbine
+	// optimization on modules that depend on this module, which will reduce parallelism and cause
+	// more recompilation.
 	Exported_plugins []string
 
 	// The number of Java source entries each Javac instance can process
@@ -248,6 +251,9 @@
 	Errorprone struct {
 		// List of javac flags that should only be used when running errorprone.
 		Javacflags []string
+
+		// List of java_plugin modules that provide extra errorprone checks.
+		Extra_check_modules []string
 	}
 
 	Proto struct {
@@ -417,7 +423,7 @@
 	overrideManifest android.OptionalPath
 
 	// map of SDK version to class loader context
-	exportedSdkLibs dexpreopt.ClassLoaderContextMap
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	// list of plugins that this java module is exporting
 	exportedPluginJars android.Paths
@@ -425,6 +431,9 @@
 	// list of plugins that this java module is exporting
 	exportedPluginClasses []string
 
+	// if true, the exported plugins generate API and require disabling turbine.
+	exportedDisableTurbine bool
+
 	// list of source files, collected from srcFiles with unique java and all kt files,
 	// will be used by android.IDEInfo struct
 	expandIDEInfoCompiledSrcs []string
@@ -447,8 +456,6 @@
 	// list of the xref extraction files
 	kytheFiles android.Paths
 
-	distFiles android.TaggedDistFiles
-
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
 
@@ -477,6 +484,8 @@
 	switch tag {
 	case "":
 		return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil
+	case android.DefaultDistTag:
+		return android.Paths{j.outputFile}, nil
 	case ".jar":
 		return android.Paths{j.implementationAndResourcesJar}, nil
 	case ".proguard_map":
@@ -509,8 +518,8 @@
 	ImplementationJars() android.Paths
 	ResourceJars() android.Paths
 	AidlIncludeDirs() android.Paths
-	ExportedSdkLibs() dexpreopt.ClassLoaderContextMap
-	ExportedPlugins() (android.Paths, []string)
+	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
+	ExportedPlugins() (android.Paths, []string, bool)
 	SrcJarArgs() ([]string, android.Paths)
 	BaseModuleName() string
 	JacocoReportClassesFile() android.Path
@@ -577,6 +586,7 @@
 	libTag                = dependencyTag{name: "javalib"}
 	java9LibTag           = dependencyTag{name: "java9lib"}
 	pluginTag             = dependencyTag{name: "plugin"}
+	errorpronePluginTag   = dependencyTag{name: "errorprone-plugin"}
 	exportedPluginTag     = dependencyTag{name: "exported-plugin"}
 	bootClasspathTag      = dependencyTag{name: "bootclasspath"}
 	systemModulesTag      = dependencyTag{name: "system modules"}
@@ -775,6 +785,7 @@
 	}
 
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), pluginTag, j.properties.Plugins...)
+	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), errorpronePluginTag, j.properties.Errorprone.Extra_check_modules...)
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), exportedPluginTag, j.properties.Exported_plugins...)
 
 	android.ProtoDeps(ctx, &j.protoProperties)
@@ -862,21 +873,22 @@
 }
 
 type deps struct {
-	classpath          classpath
-	java9Classpath     classpath
-	bootClasspath      classpath
-	processorPath      classpath
-	processorClasses   []string
-	staticJars         android.Paths
-	staticHeaderJars   android.Paths
-	staticResourceJars android.Paths
-	aidlIncludeDirs    android.Paths
-	srcs               android.Paths
-	srcJars            android.Paths
-	systemModules      *systemModules
-	aidlPreprocess     android.OptionalPath
-	kotlinStdlib       android.Paths
-	kotlinAnnotations  android.Paths
+	classpath               classpath
+	java9Classpath          classpath
+	bootClasspath           classpath
+	processorPath           classpath
+	errorProneProcessorPath classpath
+	processorClasses        []string
+	staticJars              android.Paths
+	staticHeaderJars        android.Paths
+	staticResourceJars      android.Paths
+	aidlIncludeDirs         android.Paths
+	srcs                    android.Paths
+	srcJars                 android.Paths
+	systemModules           *systemModules
+	aidlPreprocess          android.OptionalPath
+	kotlinStdlib            android.Paths
+	kotlinAnnotations       android.Paths
 
 	disableTurbine bool
 }
@@ -962,7 +974,9 @@
 		return
 	}
 	otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to))
-	commonMessage := "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source."
+	commonMessage := " In order to fix this, consider adjusting sdk_version: OR platform_apis: " +
+		"property of the source or target module so that target module is built with the same " +
+		"or smaller API set when compared to the source."
 
 	switch myLinkType {
 	case javaCore:
@@ -1037,7 +1051,7 @@
 			case libTag:
 				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
+				j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
 					dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
@@ -1049,10 +1063,11 @@
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
-				pluginJars, pluginClasses := dep.ExportedPlugins()
+				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
+				deps.disableTurbine = deps.disableTurbine || disableTurbine
 			case java9LibTag:
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars()...)
 			case staticLibTag:
@@ -1061,10 +1076,14 @@
 				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
 				deps.staticResourceJars = append(deps.staticResourceJars, dep.ResourceJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
-				pluginJars, pluginClasses := dep.ExportedPlugins()
+				pluginJars, pluginClasses, disableTurbine := dep.ExportedPlugins()
 				addPlugins(&deps, pluginJars, pluginClasses...)
+				// Turbine doesn't run annotation processors, so any module that uses an
+				// annotation processor that generates API is incompatible with the turbine
+				// optimization.
+				deps.disableTurbine = deps.disableTurbine || disableTurbine
 			case pluginTag:
 				if plugin, ok := dep.(*Plugin); ok {
 					if plugin.pluginProperties.Processor_class != nil {
@@ -1072,19 +1091,29 @@
 					} else {
 						addPlugins(&deps, plugin.ImplementationAndResourcesJars())
 					}
+					// Turbine doesn't run annotation processors, so any module that uses an
+					// annotation processor that generates API is incompatible with the turbine
+					// optimization.
 					deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api)
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
+			case errorpronePluginTag:
+				if plugin, ok := dep.(*Plugin); ok {
+					deps.errorProneProcessorPath = append(deps.errorProneProcessorPath, plugin.ImplementationAndResourcesJars()...)
+				} else {
+					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
+				}
 			case exportedPluginTag:
 				if plugin, ok := dep.(*Plugin); ok {
-					if plugin.pluginProperties.Generates_api != nil && *plugin.pluginProperties.Generates_api {
-						ctx.PropertyErrorf("exported_plugins", "Cannot export plugins with generates_api = true, found %v", otherName)
-					}
 					j.exportedPluginJars = append(j.exportedPluginJars, plugin.ImplementationAndResourcesJars()...)
 					if plugin.pluginProperties.Processor_class != nil {
 						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.pluginProperties.Processor_class)
 					}
+					// Turbine doesn't run annotation processors, so any module that uses an
+					// annotation processor that generates API is incompatible with the turbine
+					// optimization.
+					j.exportedDisableTurbine = Bool(plugin.pluginProperties.Generates_api)
 				} else {
 					ctx.PropertyErrorf("exported_plugins", "%q is not a java_plugin module", otherName)
 				}
@@ -1199,7 +1228,7 @@
 	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
 
 	if ctx.Config().RunErrorProne() {
-		if config.ErrorProneClasspath == nil {
+		if config.ErrorProneClasspath == nil && ctx.Config().TestProductVariables == nil {
 			ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
 		}
 
@@ -1219,6 +1248,7 @@
 	flags.classpath = append(flags.classpath, deps.classpath...)
 	flags.java9Classpath = append(flags.java9Classpath, deps.java9Classpath...)
 	flags.processorPath = append(flags.processorPath, deps.processorPath...)
+	flags.errorProneProcessorPath = append(flags.errorProneProcessorPath, deps.errorProneProcessorPath...)
 
 	flags.processors = append(flags.processors, deps.processorClasses...)
 	flags.processors = android.FirstUniqueStrings(flags.processors)
@@ -1913,12 +1943,15 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Module) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
-	return j.exportedSdkLibs
+func (j *Module) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
-func (j *Module) ExportedPlugins() (android.Paths, []string) {
-	return j.exportedPluginJars, j.exportedPluginClasses
+// ExportedPlugins returns the list of jars needed to run the exported plugins, the list of
+// classes for the plugins, and a boolean for whether turbine needs to be disabled due to plugins
+// that generate APIs.
+func (j *Module) ExportedPlugins() (android.Paths, []string, bool) {
+	return j.exportedPluginJars, j.exportedPluginClasses, j.exportedDisableTurbine
 }
 
 func (j *Module) SrcJarArgs() ([]string, android.Paths) {
@@ -2052,7 +2085,7 @@
 		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
 	}
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -2072,15 +2105,13 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		j.DexJarBuildPath(), j.DexJarInstallPath())
 
 	// A non-SDK library may provide a <uses-library> (the name may be different from the module name).
 	if lib := proptools.String(j.usesLibraryProperties.Provides_uses_lib); lib != "" {
-		j.exportedSdkLibs.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
+		j.classLoaderContexts.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
 	}
-
-	j.distFiles = j.GenerateTaggedDistFiles(ctx)
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -2236,6 +2267,9 @@
 type TestOptions struct {
 	// a list of extra test configuration files that should be installed with the module.
 	Extra_test_configs []string `android:"path,arch_variant"`
+
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
 }
 
 type testProperties struct {
@@ -2332,7 +2366,7 @@
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
-		j.testProperties.Test_suites, j.testProperties.Auto_gen_config)
+		j.testProperties.Test_suites, j.testProperties.Auto_gen_config, j.testProperties.Test_options.Unit_test)
 
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
@@ -2351,7 +2385,7 @@
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.prebuiltTestProperties.Test_config, nil,
-		j.prebuiltTestProperties.Test_suites, nil)
+		j.prebuiltTestProperties.Test_suites, nil, nil)
 
 	j.Import.GenerateAndroidBuildActions(ctx)
 }
@@ -2659,7 +2693,7 @@
 	dexJarFile android.Path
 
 	combinedClasspathFile android.Path
-	exportedSdkLibs       dexpreopt.ClassLoaderContextMap
+	classLoaderContexts   dexpreopt.ClassLoaderContextMap
 	exportAidlIncludeDirs android.Paths
 
 	hideApexVariantFromMake bool
@@ -2734,7 +2768,7 @@
 		TransformJetifier(ctx, outputFile, inputFile)
 	}
 	j.combinedClasspathFile = outputFile
-	j.exportedSdkLibs = make(dexpreopt.ClassLoaderContextMap)
+	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
 
@@ -2748,7 +2782,7 @@
 			case libTag, staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars()...)
 				// sdk lib names from dependencies are re-exported
-				j.exportedSdkLibs.AddContextMap(dep.ExportedSdkLibs(), otherName)
+				j.classLoaderContexts.AddContextMap(dep.ClassLoaderContexts(), otherName)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars()...)
 			}
@@ -2757,8 +2791,7 @@
 			case libTag:
 				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
 				// names of sdk libs that are directly depended are exported
-				j.exportedSdkLibs.AddContext(ctx, otherName,
-					dep.DexJarBuildPath(), dep.DexJarInstallPath())
+				j.classLoaderContexts.AddContext(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
 			}
 		}
 	})
@@ -2773,7 +2806,7 @@
 	// add the name of that java_sdk_library to the exported sdk libs to make sure
 	// that, if necessary, a <uses-library> element for that java_sdk_library is
 	// added to the Android manifest.
-	j.exportedSdkLibs.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
+	j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
 		outputFile, installFile)
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
@@ -2856,12 +2889,12 @@
 	return j.exportAidlIncludeDirs
 }
 
-func (j *Import) ExportedSdkLibs() dexpreopt.ClassLoaderContextMap {
-	return j.exportedSdkLibs
+func (j *Import) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return j.classLoaderContexts
 }
 
-func (j *Import) ExportedPlugins() (android.Paths, []string) {
-	return nil, nil
+func (j *Import) ExportedPlugins() (android.Paths, []string, bool) {
+	return nil, nil, false
 }
 
 func (j *Import) SrcJarArgs() ([]string, android.Paths) {
diff --git a/java/java_test.go b/java/java_test.go
index 7f51982..83db443 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -90,8 +91,8 @@
 
 	ctx.PreDepsMutators(python.RegisterPythonPreDepsMutators)
 	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
-	ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory))
-	ctx.RegisterPreSingletonType("sdk_versions", android.SingletonFactoryAdaptor(sdkPreSingletonFactory))
+	ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(ctx.Context, OverlaySingletonFactory))
+	ctx.RegisterPreSingletonType("sdk_versions", android.SingletonFactoryAdaptor(ctx.Context, sdkPreSingletonFactory))
 
 	android.RegisterPrebuiltMutators(ctx)
 
@@ -200,7 +201,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -244,7 +245,7 @@
 		}
 	`)
 
-	testJavaError(t, "Adjust sdk_version: property of the source or target module so that target module is built with the same or smaller API set than the source.", `
+	testJavaError(t, "consider adjusting sdk_version: OR platform_apis:", `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -312,8 +313,9 @@
 
 func TestExportedPlugins(t *testing.T) {
 	type Result struct {
-		library    string
-		processors string
+		library        string
+		processors     string
+		disableTurbine bool
 	}
 	var tests = []struct {
 		name    string
@@ -372,6 +374,18 @@
 				{library: "foo", processors: "-processor com.android.TestPlugin,com.android.TestPlugin2"},
 			},
 		},
+		{
+			name: "Exports plugin to with generates_api to dependee",
+			extra: `
+				java_library{name: "exports", exported_plugins: ["plugin_generates_api"]}
+				java_library{name: "foo", srcs: ["a.java"], libs: ["exports"]}
+				java_library{name: "bar", srcs: ["a.java"], static_libs: ["exports"]}
+			`,
+			results: []Result{
+				{library: "foo", processors: "-processor com.android.TestPlugin", disableTurbine: true},
+				{library: "bar", processors: "-processor com.android.TestPlugin", disableTurbine: true},
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -381,6 +395,11 @@
 					name: "plugin",
 					processor_class: "com.android.TestPlugin",
 				}
+				java_plugin {
+					name: "plugin_generates_api",
+					generates_api: true,
+					processor_class: "com.android.TestPlugin",
+				}
 			`+test.extra)
 
 			for _, want := range test.results {
@@ -388,6 +407,11 @@
 				if javac.Args["processor"] != want.processors {
 					t.Errorf("For library %v, expected %v, found %v", want.library, want.processors, javac.Args["processor"])
 				}
+				turbine := ctx.ModuleForTests(want.library, "android_common").MaybeRule("turbine")
+				disableTurbine := turbine.BuildParams.Rule == nil
+				if disableTurbine != want.disableTurbine {
+					t.Errorf("For library %v, expected disableTurbine %v, found %v", want.library, want.disableTurbine, disableTurbine)
+				}
 			}
 		})
 	}
@@ -620,6 +644,35 @@
 	}
 }
 
+func TestPrebuiltStubsSources(t *testing.T) {
+	test := func(t *testing.T, sourcesPath string, expectedInputs []string) {
+		ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
+prebuilt_stubs_sources {
+  name: "stubs-source",
+	srcs: ["%s"],
+}`, sourcesPath), map[string][]byte{
+			"stubs/sources/pkg/A.java": nil,
+			"stubs/sources/pkg/B.java": nil,
+		})
+
+		zipSrc := ctx.ModuleForTests("stubs-source", "android_common").Rule("zip_src")
+		if expected, actual := expectedInputs, zipSrc.Inputs.Strings(); !reflect.DeepEqual(expected, actual) {
+			t.Errorf("mismatch of inputs to soong_zip: expected %q, actual %q", expected, actual)
+		}
+	}
+
+	t.Run("empty/missing directory", func(t *testing.T) {
+		test(t, "empty-directory", []string{})
+	})
+
+	t.Run("non-empty set of sources", func(t *testing.T) {
+		test(t, "stubs/sources", []string{
+			"stubs/sources/pkg/A.java",
+			"stubs/sources/pkg/B.java",
+		})
+	})
+}
+
 func TestJavaSdkLibraryImport(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
@@ -1377,8 +1430,8 @@
 	baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
 	barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
 
-	if len(jargen.Inputs) != 1 || jargen.Inputs[0].String() != foo.Output.String() {
-		t.Errorf("expected jargen inputs [%q], got %q", foo.Output.String(), jargen.Inputs.Strings())
+	if g, w := jargen.Implicits.Strings(), foo.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected jargen inputs [%q], got %q", w, g)
 	}
 
 	if !strings.Contains(bar.Args["classpath"], jargen.Output.String()) {
@@ -1591,7 +1644,7 @@
 	// test if baz has exported SDK lib names foo and bar to qux
 	qux := ctx.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := quxLib.ExportedSdkLibs().UsesLibs()
+		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
 		if w := []string{"foo", "bar", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
 			t.Errorf("qux should export %q but exports %q", w, sdkLibs)
 		}
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 60ca1c4..77ef294 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -84,11 +84,14 @@
 }
 
 func TestKapt(t *testing.T) {
-	ctx, _ := testJava(t, `
+	bp := `
 		java_library {
 			name: "foo",
 			srcs: ["a.java", "b.kt"],
 			plugins: ["bar", "baz"],
+			errorprone: {
+				extra_check_modules: ["my_check"],
+			},
 		}
 
 		java_plugin {
@@ -102,64 +105,119 @@
 			processor_class: "com.baz",
 			srcs: ["b.java"],
 		}
-		`)
 
-	buildOS := android.BuildOs.String()
+		java_plugin {
+			name: "my_check",
+			srcs: ["b.java"],
+		}
+	`
+	t.Run("", func(t *testing.T) {
+		ctx, _ := testJava(t, bp)
 
-	kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
-	kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+		buildOS := android.BuildOs.String()
 
-	bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
-	baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
+		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+		kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+		javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 
-	// Test that the kotlin and java sources are passed to kapt and kotlinc
-	if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
-		t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
-	}
-	if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
-		t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
-	}
+		bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
+		baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
 
-	// Test that only the java sources are passed to javac
-	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
-		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
-	}
+		// Test that the kotlin and java sources are passed to kapt and kotlinc
+		if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
+		}
+		if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
+		}
 
-	// Test that the kapt srcjar is a dependency of kotlinc and javac rules
-	if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
-		t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
-	}
-	if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
-		t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
-	}
+		// Test that only the java sources are passed to javac
+		if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
+			t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+		}
 
-	// Test that the kapt srcjar is extracted by the kotlinc and javac rules
-	if kotlinc.Args["srcJars"] != kapt.Output.String() {
-		t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
-	}
-	if javac.Args["srcJars"] != kapt.Output.String() {
-		t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
-	}
+		// Test that the kapt srcjar is a dependency of kotlinc and javac rules
+		if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
+			t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
+		}
+		if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
+			t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
+		}
 
-	// Test that the processors are passed to kapt
-	expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
-		" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
-	if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
-		t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
-	}
-	expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
-	if kapt.Args["kaptProcessor"] != expectedProcessor {
-		t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
-	}
+		// Test that the kapt srcjar is extracted by the kotlinc and javac rules
+		if kotlinc.Args["srcJars"] != kapt.Output.String() {
+			t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		}
+		if javac.Args["srcJars"] != kapt.Output.String() {
+			t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		}
 
-	// Test that the processors are not passed to javac
-	if javac.Args["processorPath"] != "" {
-		t.Errorf("expected processorPath '', got %q", javac.Args["processorPath"])
-	}
-	if javac.Args["processor"] != "-proc:none" {
-		t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
-	}
+		// Test that the processors are passed to kapt
+		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
+			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
+		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		}
+		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
+		if kapt.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		}
+
+		// Test that the processors are not passed to javac
+		if javac.Args["processorpath"] != "" {
+			t.Errorf("expected processorPath '', got %q", javac.Args["processorpath"])
+		}
+		if javac.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
+		}
+	})
+
+	t.Run("errorprone", func(t *testing.T) {
+		env := map[string]string{
+			"RUN_ERROR_PRONE": "true",
+		}
+		config := testConfig(env, bp, nil)
+		ctx, _ := testJavaWithConfig(t, config)
+
+		buildOS := android.BuildOs.String()
+
+		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+		//kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
+		javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+		errorprone := ctx.ModuleForTests("foo", "android_common").Description("errorprone")
+
+		bar := ctx.ModuleForTests("bar", buildOS+"_common").Description("javac").Output.String()
+		baz := ctx.ModuleForTests("baz", buildOS+"_common").Description("javac").Output.String()
+		myCheck := ctx.ModuleForTests("my_check", buildOS+"_common").Description("javac").Output.String()
+
+		// Test that the errorprone plugins are not passed to kapt
+		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
+			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
+		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		}
+		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
+		if kapt.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		}
+
+		// Test that the errorprone plugins are not passed to javac
+		if javac.Args["processorpath"] != "" {
+			t.Errorf("expected processorPath '', got %q", javac.Args["processorpath"])
+		}
+		if javac.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", javac.Args["processor"])
+		}
+
+		// Test that the errorprone plugins are passed to errorprone
+		expectedProcessorPath = "-processorpath " + myCheck
+		if errorprone.Args["processorpath"] != expectedProcessorPath {
+			t.Errorf("expected processorpath %q, got %q", expectedProcessorPath, errorprone.Args["processorpath"])
+		}
+		if errorprone.Args["processor"] != "-proc:none" {
+			t.Errorf("expected processor '-proc:none', got %q", errorprone.Args["processor"])
+		}
+	})
 }
 
 func TestKaptEncodeFlags(t *testing.T) {
diff --git a/java/lint.go b/java/lint.go
index 3df582f..11f92e5 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -447,7 +447,7 @@
 	var outputs []*lintOutputs
 	var dirs []string
 	ctx.VisitAllModules(func(m android.Module) {
-		if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
+		if ctx.Config().KatiEnabled() && !m.ExportedToMake() {
 			return
 		}
 
diff --git a/java/robolectric.go b/java/robolectric.go
index 04fc117..62d1d99 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -389,8 +389,10 @@
 		}
 		runtimeFromSourceJar := android.OutputFileForModule(ctx, runtimeFromSourceModule, "")
 
+		// TODO(murj) Update this to ctx.Config().PlatformSdkCodename() once the platform
+		// classes like android.os.Build are updated to S.
 		runtimeName := fmt.Sprintf("android-all-%s-robolectric-r0.jar",
-			ctx.Config().PlatformSdkCodename())
+			"R")
 		installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar)
 		r.runtimes = append(r.runtimes, installedRuntime)
 	}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 21c03cd..32bc077 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -435,8 +435,23 @@
 	// If set to true, the path of dist files is apistubs/core. Defaults to false.
 	Core_lib *bool
 
-	// don't create dist rules.
-	No_dist *bool `blueprint:"mutated"`
+	// If set to true then don't create dist rules.
+	No_dist *bool
+
+	// The stem for the artifacts that are copied to the dist, if not specified
+	// then defaults to the base module name.
+	//
+	// For each scope the following artifacts are copied to the apistubs/<scope>
+	// directory in the dist.
+	// * stubs impl jar -> <dist-stem>.jar
+	// * API specification file -> api/<dist-stem>.txt
+	// * Removed API specification file -> api/<dist-stem>-removed.txt
+	//
+	// Also used to construct the name of the filegroup (created by prebuilt_apis)
+	// that references the latest released API and remove API specification files.
+	// * API specification filegroup -> <dist-stem>.api.<scope>.latest
+	// * Removed API specification filegroup -> <dist-stem>-removed.api.<scope>.latest
+	Dist_stem *string
 
 	// indicates whether system and test apis should be generated.
 	Generate_system_and_test_apis bool `blueprint:"mutated"`
@@ -1109,12 +1124,16 @@
 	}
 }
 
+func (module *SdkLibrary) distStem() string {
+	return proptools.StringDefault(module.sdkLibraryProperties.Dist_stem, module.BaseModuleName())
+}
+
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.BaseModuleName() + ".api." + apiScope.name + ".latest"
+	return ":" + module.distStem() + ".api." + apiScope.name + ".latest"
 }
 
 func (module *SdkLibrary) latestRemovedApiFilegroupName(apiScope *apiScope) string {
-	return ":" + module.BaseModuleName() + "-removed.api." + apiScope.name + ".latest"
+	return ":" + module.distStem() + "-removed.api." + apiScope.name + ".latest"
 }
 
 func childModuleVisibility(childVisibility []string) []string {
@@ -1220,7 +1239,7 @@
 	// Dist the class jar artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
 		props.Dist.Targets = []string{"sdk", "win_sdk"}
-		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.jar", module.BaseModuleName()))
+		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.jar", module.distStem()))
 		props.Dist.Dir = proptools.StringPtr(module.apiDistPath(apiScope))
 		props.Dist.Tag = proptools.StringPtr(".jar")
 	}
@@ -1262,11 +1281,7 @@
 			Include_dirs       []string
 			Local_include_dirs []string
 		}
-		Dist struct {
-			Targets []string
-			Dest    *string
-			Dir     *string
-		}
+		Dists []android.Dist
 	}{}
 
 	// The stubs source processing uses the same compile time classpath when extracting the
@@ -1366,11 +1381,23 @@
 		}
 	}
 
-	// Dist the api txt artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
-		props.Dist.Targets = []string{"sdk", "win_sdk"}
-		props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName()))
-		props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		// Dist the api txt and removed api txt artifacts for sdk builds.
+		distDir := proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+		for _, p := range []struct {
+			tag     string
+			pattern string
+		}{
+			{tag: ".api.txt", pattern: "%s.txt"},
+			{tag: ".removed-api.txt", pattern: "%s-removed.txt"},
+		} {
+			props.Dists = append(props.Dists, android.Dist{
+				Targets: []string{"sdk", "win_sdk"},
+				Dir:     distDir,
+				Dest:    proptools.StringPtr(fmt.Sprintf(p.pattern, module.distStem())),
+				Tag:     proptools.StringPtr(p.tag),
+			})
+		}
 	}
 
 	mctx.CreateModule(DroidstubsFactory, &props)
@@ -1474,10 +1501,6 @@
 	return module.sdkJars(ctx, sdkVersion, false /*headerJars*/)
 }
 
-func (module *SdkLibrary) SetNoDist() {
-	module.sdkLibraryProperties.No_dist = proptools.BoolPtr(true)
-}
-
 var javaSdkLibrariesKey = android.NewOnceKey("javaSdkLibraries")
 
 func javaSdkLibraries(config android.Config) *[]string {
@@ -1505,12 +1528,10 @@
 	}
 
 	// If this builds against standard libraries (i.e. is not part of the core libraries)
-	// then assume it provides both system and test apis. Otherwise, assume it does not and
-	// also assume it does not contribute to the dist build.
+	// then assume it provides both system and test apis.
 	sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
 	hasSystemAndTestApis := sdkDep.hasStandardLibs()
 	module.sdkLibraryProperties.Generate_system_and_test_apis = hasSystemAndTestApis
-	module.sdkLibraryProperties.No_dist = proptools.BoolPtr(!hasSystemAndTestApis)
 
 	missing_current_api := false
 
@@ -2292,11 +2313,10 @@
 			scopeSet.AddProperty("jars", jars)
 
 			// Merge the stubs source jar into the snapshot zip so that when it is unpacked
-			// the source files are also unpacked. Use a glob so that if the directory is missing
-			// (because there are no stubs sources for this scope) it will not fail.
+			// the source files are also unpacked.
 			snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources")
 			ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir)
-			scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir + "/**/*.java"})
+			scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir})
 
 			if properties.CurrentApiFile != nil {
 				currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt")
diff --git a/java/sysprop.go b/java/sysprop.go
index 1a70499..e41aef6 100644
--- a/java/sysprop.go
+++ b/java/sysprop.go
@@ -14,6 +14,10 @@
 
 package java
 
+// This file contains a map to redirect dependencies towards sysprop_library. If a sysprop_library
+// is owned by Platform, and the client module links against system API, the public stub of the
+// sysprop_library should be used. The map will contain public stub names of sysprop_libraries.
+
 import (
 	"sync"
 
diff --git a/python/androidmk.go b/python/androidmk.go
index 8ad5889..e60c538 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -15,8 +15,6 @@
 package python
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
 	"strings"
 
@@ -24,82 +22,72 @@
 )
 
 type subAndroidMkProvider interface {
-	AndroidMk(*Module, *android.AndroidMkData)
+	AndroidMk(*Module, *android.AndroidMkEntries)
 }
 
-func (p *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (p *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
 	if p.subAndroidMkOnce == nil {
 		p.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
 	}
 	if androidmk, ok := obj.(subAndroidMkProvider); ok {
 		if !p.subAndroidMkOnce[androidmk] {
 			p.subAndroidMkOnce[androidmk] = true
-			androidmk.AndroidMk(p, data)
+			androidmk.AndroidMk(p, entries)
 		}
 	}
 }
 
-func (p *Module) AndroidMk() android.AndroidMkData {
-	ret := android.AndroidMkData{OutputFile: p.installSource}
+func (p *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	entries := android.AndroidMkEntries{OutputFile: p.installSource}
 
-	p.subAndroidMk(&ret, p.installer)
+	p.subAndroidMk(&entries, p.installer)
 
-	return ret
+	return []android.AndroidMkEntries{entries}
 }
 
-func (p *binaryDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "EXECUTABLES"
+func (p *binaryDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "EXECUTABLES"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", p.binaryProperties.Test_suites...)
 	})
-	base.subAndroidMk(ret, p.pythonInstaller)
+	base.subAndroidMk(entries, p.pythonInstaller)
 }
 
-func (p *testDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
-	ret.Class = "NATIVE_TESTS"
+func (p *testDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+	entries.Class = "NATIVE_TESTS"
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(p.binaryDecorator.binaryProperties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(p.binaryDecorator.binaryProperties.Test_suites, " "))
-		}
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", p.binaryDecorator.binaryProperties.Test_suites...)
 		if p.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=",
-				p.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
 		}
 
-		if !BoolDefault(p.binaryProperties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
 
-		if len(p.data) > 0 {
-			fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
-				strings.Join(android.AndroidMkDataPaths(p.data), " "))
-		}
+		entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
+
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(p.testProperties.Test_options.Unit_test))
 	})
-	base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
+	base.subAndroidMk(entries, p.binaryDecorator.pythonInstaller)
 }
 
-func (installer *pythonInstaller) AndroidMk(base *Module, ret *android.AndroidMkData) {
+func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
 	// Soong installation is only supported for host modules. Have Make
 	// installation trigger Soong installation.
 	if base.Target().Os.Class == android.Host {
-		ret.OutputFile = android.OptionalPathForPath(installer.path)
+		entries.OutputFile = android.OptionalPathForPath(installer.path)
 	}
 
-	ret.Required = append(ret.Required, "libc++")
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	entries.Required = append(entries.Required, "libc++")
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		path, file := filepath.Split(installer.path.ToMakePath().String())
 		stem := strings.TrimSuffix(file, filepath.Ext(file))
 
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+filepath.Ext(file))
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(installer.androidMkSharedLibs, " "))
-		fmt.Fprintln(w, "LOCAL_CHECK_ELF_FILES := false")
+		entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.AddStrings("LOCAL_SHARED_LIBRARIES", installer.androidMkSharedLibs...)
+		entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
 	})
 }
diff --git a/python/python.go b/python/python.go
index e4c8e94..7e376c6 100644
--- a/python/python.go
+++ b/python/python.go
@@ -34,7 +34,7 @@
 }
 
 func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
+	ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
 }
 
 // the version properties that apply to python libraries and binaries.
@@ -86,6 +86,9 @@
 	// the test. the file extension can be arbitrary except for (.py).
 	Data []string `android:"path,arch_variant"`
 
+	// list of java modules that provide data that should be installed alongside the test.
+	Java_data []string
+
 	// list of the Python libraries compatible both with Python2 and Python3.
 	Libs []string `android:"arch_variant"`
 
@@ -194,7 +197,7 @@
 
 var _ PythonDependency = (*Module)(nil)
 
-var _ android.AndroidMkDataProvider = (*Module)(nil)
+var _ android.AndroidMkEntriesProvider = (*Module)(nil)
 
 func (p *Module) Init() android.Module {
 
@@ -214,10 +217,17 @@
 	name string
 }
 
+type installDependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	name string
+}
+
 var (
 	pythonLibTag         = dependencyTag{name: "pythonLib"}
+	javaDataTag          = dependencyTag{name: "javaData"}
 	launcherTag          = dependencyTag{name: "launcher"}
-	launcherSharedLibTag = dependencyTag{name: "launcherSharedLib"}
+	launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
 	pyIdentifierRegexp   = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
 	pyExt                = ".py"
 	protoExt             = ".proto"
@@ -248,7 +258,7 @@
 				versionNames = append(versionNames, pyVersion2)
 				versionProps = append(versionProps, base.properties.Version.Py2)
 			}
-			modules := mctx.CreateVariations(versionNames...)
+			modules := mctx.CreateLocalVariations(versionNames...)
 			if len(versionNames) > 0 {
 				mctx.AliasVariation(versionNames[0])
 			}
@@ -306,16 +316,20 @@
 func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
 	android.ProtoDeps(ctx, &p.protoProperties)
 
-	if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
-		ctx.AddVariationDependencies(nil, pythonLibTag, "libprotobuf-python")
+	versionVariation := []blueprint.Variation{
+		{"python_version", p.properties.Actual_version},
 	}
-	ctx.AddVariationDependencies(nil, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
+
+	if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
+		ctx.AddVariationDependencies(versionVariation, pythonLibTag, "libprotobuf-python")
+	}
+	ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
 	switch p.properties.Actual_version {
 	case pyVersion2:
 
 		if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled() {
-			ctx.AddVariationDependencies(nil, pythonLibTag, "py2-stdlib")
+			ctx.AddVariationDependencies(versionVariation, pythonLibTag, "py2-stdlib")
 
 			launcherModule := "py2-launcher"
 			if p.bootstrapper.autorun() {
@@ -328,6 +342,7 @@
 			// 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++")
 
 			if ctx.Target().Os.Bionic() {
 				ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
@@ -338,7 +353,7 @@
 	case pyVersion3:
 
 		if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled() {
-			ctx.AddVariationDependencies(nil, pythonLibTag, "py3-stdlib")
+			ctx.AddVariationDependencies(versionVariation, pythonLibTag, "py3-stdlib")
 
 			launcherModule := "py3-launcher"
 			if p.bootstrapper.autorun() {
@@ -366,6 +381,11 @@
 		panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
 			p.properties.Actual_version, ctx.ModuleName()))
 	}
+
+	// Emulate the data property for java_data but with the arch variation overridden to "common"
+	// so that it can point to java modules.
+	javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
+	ctx.AddVariationDependencies(javaDataVariation, javaDataTag, p.properties.Java_data...)
 }
 
 func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -412,6 +432,11 @@
 	// expand data files from "data" property.
 	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
 
+	// Emulate the data property for java_data dependencies.
+	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
+	}
+
 	// sanitize pkg_path.
 	pkgPath := String(p.properties.Pkg_path)
 	if pkgPath != "" {
diff --git a/python/test.go b/python/test.go
index 434e71a..4df71c1 100644
--- a/python/test.go
+++ b/python/test.go
@@ -26,6 +26,12 @@
 	android.RegisterModuleType("python_test", PythonTestFactory)
 }
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// the name of the test configuration (for example "AndroidTest.xml") that should be
 	// installed with the module.
@@ -38,6 +44,12 @@
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
 	Data []string `android:"path,arch_variant"`
+
+	// list of java modules that provide data that should be installed alongside the test.
+	Java_data []string
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type testDecorator struct {
@@ -71,6 +83,13 @@
 	for _, dataSrcPath := range dataSrcPaths {
 		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
 	}
+
+	// Emulate the data property for java_data dependencies.
+	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+		for _, javaDataSrcPath := range android.OutputFilesForModule(ctx, javaData, "") {
+			test.data = append(test.data, android.DataPath{SrcPath: javaDataSrcPath})
+		}
+	}
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *Module {
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 29e4bd7..4e8b14d 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -15,10 +15,7 @@
 package rust
 
 import (
-	"fmt"
-	"io"
 	"path/filepath"
-	"strings"
 
 	"android/soong/android"
 )
@@ -26,14 +23,14 @@
 type AndroidMkContext interface {
 	Name() string
 	Target() android.Target
-	SubAndroidMk(*android.AndroidMkData, interface{})
+	SubAndroidMk(*android.AndroidMkEntries, interface{})
 }
 
 type SubAndroidMkProvider interface {
-	AndroidMk(AndroidMkContext, *android.AndroidMkData)
+	AndroidMk(AndroidMkContext, *android.AndroidMkEntries)
 }
 
-func (mod *Module) SubAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (mod *Module) SubAndroidMk(data *android.AndroidMkEntries, obj interface{}) {
 	if mod.subAndroidMkOnce == nil {
 		mod.subAndroidMkOnce = make(map[SubAndroidMkProvider]bool)
 	}
@@ -45,33 +42,22 @@
 	}
 }
 
-func (mod *Module) AndroidMk() android.AndroidMkData {
-	if mod.Properties.HideFromMake {
-		return android.AndroidMkData{
-			Disabled: true,
-		}
+func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries {
+	if mod.Properties.HideFromMake || mod.hideApexVariantFromMake {
+
+		return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}}
 	}
 
-	ret := android.AndroidMkData{
+	ret := android.AndroidMkEntries{
 		OutputFile: mod.outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				if len(mod.Properties.AndroidMkRlibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_RLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkRlibs, " "))
-				}
-				if len(mod.Properties.AndroidMkDylibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_DYLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkDylibs, " "))
-				}
-				if len(mod.Properties.AndroidMkProcMacroLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_PROC_MACRO_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkProcMacroLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkSharedLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkSharedLibs, " "))
-				}
-				if len(mod.Properties.AndroidMkStaticLibs) > 0 {
-					fmt.Fprintln(w, "LOCAL_STATIC_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkStaticLibs, " "))
-				}
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
+				entries.AddStrings("LOCAL_DYLIB_LIBRARIES", mod.Properties.AndroidMkDylibs...)
+				entries.AddStrings("LOCAL_PROC_MACRO_LIBRARIES", mod.Properties.AndroidMkProcMacroLibs...)
+				entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.Properties.AndroidMkSharedLibs...)
+				entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
 			},
 		},
 	}
@@ -84,10 +70,10 @@
 	}
 	ret.SubName += mod.Properties.SubName
 
-	return ret
+	return []android.AndroidMkEntries{ret}
 }
 
-func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, binary.baseCompiler)
 
 	if binary.distFile.Valid() {
@@ -95,32 +81,26 @@
 	}
 
 	ret.Class = "EXECUTABLES"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if binary.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+binary.coverageOutputZipFile.String())
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputZipFile)
 	})
 }
 
-func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	test.binaryDecorator.AndroidMk(ctx, ret)
 	ret.Class = "NATIVE_TESTS"
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if len(test.Properties.Test_suites) > 0 {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-				strings.Join(test.Properties.Test_suites, " "))
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test.Properties.Test_suites...)
 		if test.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", test.testConfig.String())
+			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
 		}
-		if !BoolDefault(test.Properties.Auto_gen_config, true) {
-			fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
-		}
+		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
+		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
 	})
 	// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
 }
 
-func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, library.baseCompiler)
 
 	if library.rlib() {
@@ -137,15 +117,12 @@
 		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
 	}
 
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if library.coverageOutputZipFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+library.coverageOutputZipFile.String())
-		}
-
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputZipFile)
 	})
 }
 
-func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, procMacro.baseCompiler)
 
 	ret.Class = "PROC_MACRO_LIBRARIES"
@@ -155,29 +132,29 @@
 
 }
 
-func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	outFile := sourceProvider.OutputFiles[0]
 	ret.Class = "ETC"
 	ret.OutputFile = android.OptionalPathForPath(outFile)
 	ret.SubName += sourceProvider.subName
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		_, file := filepath.Split(outFile.String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
-		fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
+		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
 	})
 }
 
-func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, bindgen.BaseSourceProvider)
 }
 
-func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, proto.BaseSourceProvider)
 }
 
-func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	if compiler.path == (android.InstallPath{}) {
 		return
 	}
@@ -191,14 +168,12 @@
 		unstrippedOutputFile = ret.OutputFile
 		ret.OutputFile = compiler.strippedOutputFile
 	}
-	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-		if compiler.strippedOutputFile.Valid() {
-			fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", unstrippedOutputFile)
-		}
+	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
 		path, file := filepath.Split(compiler.path.ToMakePath().String())
 		stem, suffix, _ := android.SplitFileExt(file)
-		fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
-		fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
-		fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
+		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+		entries.SetString("LOCAL_MODULE_PATH", path)
+		entries.SetString("LOCAL_MODULE_STEM", stem)
 	})
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 7cc0fc8..35a807b 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -258,5 +258,6 @@
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
 	deps.StaticLibs = append(deps.StaticLibs, b.ClangProperties.Static_libs...)
+	deps.HeaderLibs = append(deps.StaticLibs, b.ClangProperties.Header_libs...)
 	return deps
 }
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index c7ce42b..af04cfc 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -32,6 +32,7 @@
 			cflags: ["--clang-flag()"],
 			shared_libs: ["libfoo_shared"],
 			static_libs: ["libfoo_static"],
+			header_libs: ["libfoo_header"],
 		}
 		cc_library_shared {
 			name: "libfoo_shared",
@@ -41,6 +42,10 @@
 			name: "libfoo_static",
 			export_include_dirs: ["static_include"],
 		}
+		cc_library_headers {
+			name: "libfoo_header",
+			export_include_dirs: ["header_include"],
+		}
 		cc_defaults {
 			name: "cc_defaults_flags",
 			cflags: ["--default-flag"],
@@ -60,6 +65,9 @@
 	if !strings.Contains(libbindgen.Args["cflags"], "-Istatic_include") {
 		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
 	}
+	if !strings.Contains(libbindgen.Args["cflags"], "-Iheader_include") {
+		t.Errorf("missing static_libs exported includes in rust_bindgen rule: cflags %#v", libbindgen.Args["cflags"])
+	}
 	if !strings.Contains(libbindgen.Args["cflags"], "--default-flag") {
 		t.Errorf("rust_bindgen missing cflags defined in cc_defaults: cflags %#v", libbindgen.Args["cflags"])
 	}
diff --git a/rust/builder.go b/rust/builder.go
index a09b1d1..6079e30 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -200,7 +200,9 @@
 		profileEmitArg := strings.TrimPrefix(cc.PwdPrefix(), "PWD=") + "/"
 
 		if outputFile.Ext() != "" {
-			gcnoFile = android.PathForModuleOut(ctx, pathtools.ReplaceExtension(outputFile.Base(), "gcno"))
+			// rustc seems to split the output filename at the first '.' when determining the gcno filename
+			// so we need to do the same here.
+			gcnoFile = android.PathForModuleOut(ctx, strings.Split(outputFile.Base(), ".")[0]+".gcno")
 			rustcFlags = append(rustcFlags, "-Z profile-emit="+profileEmitArg+android.PathForModuleOut(
 				ctx, pathtools.ReplaceExtension(outputFile.Base(), "gcda")).String())
 		} else {
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index 132b7b8..e24f666 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -18,6 +18,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 func TestClippy(t *testing.T) {
@@ -45,6 +46,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index a6dba9e..2b40727 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 // Test that feature flags are being correctly generated.
@@ -132,6 +133,7 @@
 		}`
 
 	bp = bp + GatherRequiredDepsForTest()
+	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 
 	fs := map[string][]byte{
 		// Reuse the same blueprint file for subdirectories.
diff --git a/rust/config/global.go b/rust/config/global.go
index 6e268a0..22d9567 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.47.0"
+	RustDefaultVersion = "1.48.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/coverage_test.go b/rust/coverage_test.go
index 90155ca..e7f873e 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -160,7 +160,7 @@
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", fizzZipInputs)
 	}
 	if !android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_rlib_dylib-std_cov/librlib.gcno") ||
-		!android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_dylib_cov/libfoo.dylib.gcno") {
+		!android.SuffixInList(libfooZipInputs, "android_arm64_armv8-a_dylib_cov/libfoo.gcno") {
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", libfooZipInputs)
 	}
 	if !android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_cov/obj/foo.gcno") ||
diff --git a/rust/project_json.go b/rust/project_json.go
index 5697408..8d9e50c 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -109,6 +109,10 @@
 		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 {
 			return
 		}
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 69288fc..16699c1 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -131,6 +131,22 @@
 			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
 				rootModule, android.BuildOs.String())
 		}
+		// Check that libbindings1 does not depend on itself.
+		if strings.Contains(rootModule, "libbindings1") {
+			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" {
+					t.Errorf("libbindings1 depends on itself")
+				}
+			}
+		}
 	}
 }
 
diff --git a/rust/protobuf.go b/rust/protobuf.go
index 76fed30..7d6e1fd 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -53,7 +53,7 @@
 	Proto_flags []string `android:"arch_variant"`
 
 	// List of libraries which export include paths required for this module
-	Header_libs []string `android:"arch_variant"`
+	Header_libs []string `android:"arch_variant,variant_prepend"`
 }
 
 type protobufDecorator struct {
diff --git a/rust/rust.go b/rust/rust.go
index 5b94045..b277afc 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -74,6 +74,7 @@
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.ApexModuleBase
 
 	Properties BaseProperties
 
@@ -88,6 +89,8 @@
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
 	outputFile android.OptionalPath
+
+	hideApexVariantFromMake bool
 }
 
 func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
@@ -508,6 +511,7 @@
 	}
 
 	android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
+	android.InitApexModule(mod)
 
 	android.InitDefaultableModule(mod)
 	return mod
@@ -605,6 +609,11 @@
 		ModuleContext: actx,
 	}
 
+	apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		mod.hideApexVariantFromMake = true
+	}
+
 	toolchain := mod.toolchain(ctx)
 
 	if !toolchain.Supported() {
@@ -694,6 +703,11 @@
 	sourceDepTag        = dependencyTag{name: "source"}
 )
 
+func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
+	tag, ok := depTag.(dependencyTag)
+	return ok && tag == dylibDepTag
+}
+
 type autoDep struct {
 	variation string
 	depTag    dependencyTag
@@ -1052,6 +1066,58 @@
 	return android.OptionalPath{}
 }
 
+var _ android.ApexModule = (*Module)(nil)
+
+func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+	return nil
+}
+
+func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	depTag := ctx.OtherModuleDependencyTag(dep)
+
+	if ccm, ok := dep.(*cc.Module); ok {
+		if ccm.HasStubsVariants() {
+			if cc.IsSharedDepTag(depTag) {
+				// dynamic dep to a stubs lib crosses APEX boundary
+				return false
+			}
+			if cc.IsRuntimeDepTag(depTag) {
+				// runtime dep to a stubs lib also crosses APEX boundary
+				return false
+			}
+
+			if cc.IsHeaderDepTag(depTag) {
+				return false
+			}
+		}
+		if mod.Static() && cc.IsSharedDepTag(depTag) {
+			// shared_lib dependency from a static lib is considered as crossing
+			// the APEX boundary because the dependency doesn't actually is
+			// linked; the dependency is used only during the compilation phase.
+			return false
+		}
+	}
+
+	if depTag == procMacroDepTag {
+		return false
+	}
+
+	return true
+}
+
+// Overrides ApexModule.IsInstallabeToApex()
+func (mod *Module) IsInstallableToApex() bool {
+	if mod.compiler != nil {
+		if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+			return true
+		}
+		if _, ok := mod.compiler.(*binaryDecorator); ok {
+			return true
+		}
+	}
+	return false
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 187f0b6..646b252 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -120,6 +120,7 @@
 // attributes of the testRustCtx.
 func (tctx *testRustCtx) generateConfig() {
 	tctx.bp = tctx.bp + GatherRequiredDepsForTest()
+	tctx.bp = tctx.bp + cc.GatherRequiredDepsForTest(android.NoOsType)
 	cc.GatherRequiredFilesForTest(tctx.fs)
 	config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
 	tctx.config = &config
diff --git a/rust/strip.go b/rust/strip.go
index d1bbba6..110e3cc 100644
--- a/rust/strip.go
+++ b/rust/strip.go
@@ -19,11 +19,14 @@
 	"android/soong/cc"
 )
 
-// Stripper encapsulates cc.Stripper.
+// Stripper defines the stripping actions and properties for a module. The Rust
+// implementation reuses the C++ implementation.
 type Stripper struct {
 	cc.Stripper
 }
 
+// StripExecutableOrSharedLib strips a binary or shared library from its debug
+// symbols and other debug information.
 func (s *Stripper) StripExecutableOrSharedLib(ctx ModuleContext, in android.Path, out android.ModuleOutPath) {
 	ccFlags := cc.StripFlags{Toolchain: ctx.RustModule().ccToolchain(ctx)}
 	s.Stripper.StripExecutableOrSharedLib(ctx, in, out, ccFlags)
diff --git a/rust/test.go b/rust/test.go
index bc7f53c..408e03a 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -19,6 +19,12 @@
 	"android/soong/tradefed"
 )
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// Disables the creation of a test-specific directory when used with
 	// relative_install_path. Useful if several tests need to be in the same
@@ -44,6 +50,9 @@
 
 	// if set, build with the standard Rust test harness. Defaults to true.
 	Test_harness *bool
+
+	// Test options.
+	Test_options TestOptions
 }
 
 // A test module is a binary module with extra --test compiler flag
diff --git a/rust/testing.go b/rust/testing.go
index 001f322..a8496d9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -77,6 +77,7 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -93,6 +94,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libtest",
@@ -102,6 +104,7 @@
 			host_supported: true,
                         native_coverage: false,
 			sysroot: true,
+			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 		}
 		rust_library {
 			name: "libprotobuf",
@@ -122,15 +125,11 @@
 			host_supported: true,
 		}
 
-` + cc.GatherRequiredDepsForTest(android.NoOsType)
+`
 	return bp
 }
 
-func CreateTestContext(config android.Config) *android.TestContext {
-	ctx := android.NewTestArchContext(config)
-	android.RegisterPrebuiltMutators(ctx)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
+func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -164,6 +163,14 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+}
+
+func CreateTestContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
+	android.RegisterPrebuiltMutators(ctx)
+	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+	cc.RegisterRequiredBuildComponentsForTest(ctx)
+	RegisterRequiredBuildComponentsForTest(ctx)
 
 	return ctx
 }
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index 89fb1a5..0c868ea 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -12,7 +12,7 @@
 
 export OUT_DIR=${OUT_DIR:-out}
 
-if [ -e ${OUT_DIR}/soong/.soong.in_make ]; then
+if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
   # If ${OUT_DIR} has been created without --skip-make, Soong will create an
   # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
   # expected to be supplied by the .mk files, and that might cause errors in
@@ -32,8 +32,8 @@
 
 my_get_build_var() {
   # get_build_var will run Soong in normal in-make mode where it creates
-  # .soong.in_make. That would clobber our real out directory, so we need to
-  # run it in a different one.
+  # .soong.kati_enabled. That would clobber our real out directory, so we need
+  # to run it in a different one.
   OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
 }
 
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index 63fc9a9..c271211 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -10,6 +10,7 @@
 import re
 import subprocess
 import sys
+import xml.etree.ElementTree
 
 
 # The compiled allow list RE.
@@ -38,46 +39,40 @@
     return False
   return True
 
-# Pattern that matches the class descriptor in a "Class descriptor" line output
-# by dexdump and extracts the class name - with / instead of .
-CLASS_DESCRIPTOR_RE = re.compile("'L([^;]+);'")
-
 def CheckDexJar(dexdump_path, allow_list_path, jar):
   """Check a dex jar file.
   """
-  # Get the class descriptor lines in the dexdump output. This filters out lines
-  # that do not contain class descriptors to reduce the size of the data read by
-  # this script.
-  p = subprocess.Popen(args='%s %s | grep "Class descriptor "' % (dexdump_path, jar),
+  # Use dexdump to generate the XML representation of the dex jar file.
+  p = subprocess.Popen(args='%s -l xml %s' % (dexdump_path, jar),
       stdout=subprocess.PIPE, shell=True)
   stdout, _ = p.communicate()
   if p.returncode != 0:
     return False
-  # Split the output into lines
-  lines = stdout.split('\n')
-  classes = 0
-  for line in lines:
-    # The last line will be empty
-    if line == '':
-      continue
-    # Try and find the descriptor on the line. Fail immediately if it cannot be found
-    # as the dexdump output has probably changed.
-    found = CLASS_DESCRIPTOR_RE.search(line)
-    if not found:
-      print >> sys.stderr, ('Could not find class descriptor in line `%s`' % line)
-      return False
-    # Extract the class name (using / instead of .) from the class descriptor line
-    f = found.group(1)
-    classes += 1
-    package_name = os.path.dirname(f)
-    package_name = package_name.replace('/', '.')
+
+  packages = 0
+  try:
+    # TODO(b/172063475) - improve performance
+    root = xml.etree.ElementTree.fromstring(stdout)
+  except xml.etree.ElementTree.ParseError as e:
+    print >> sys.stderr, 'Error processing jar %s - %s' % (jar, e)
+    print >> sys.stderr, stdout
+    return False
+  for package_elt in root.iterfind('package'):
+    packages += 1
+    package_name = package_elt.get('name')
     if not package_name or not allow_list_re.match(package_name):
+      # Report the name of a class in the package as it is easier to navigate to
+      # the source of a concrete class than to a package which is often required
+      # to investigate this failure.
+      class_name = package_elt[0].get('name')
+      if package_name != "":
+        class_name = package_name + "." + class_name
       print >> sys.stderr, ('Error: %s contains class file %s, whose package name "%s" is empty or'
                             ' not in the allow list %s of packages allowed on the bootclasspath.'
-                            % (jar, f, package_name, allow_list_path))
+                            % (jar, class_name, package_name, allow_list_path))
       return False
-  if classes == 0:
-    print >> sys.stderr, ('Error: %s does not contain any class files.' % jar)
+  if packages == 0:
+    print >> sys.stderr, ('Error: %s does not contain any packages.' % jar)
     return False
   return True
 
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 84e4f28..b1eebe9 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -109,6 +109,7 @@
     name: "mysdk_sdkmember@current",
     sdk_member_name: "sdkmember",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     stl: "none",
@@ -131,6 +132,7 @@
     name: "sdkmember",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "64",
@@ -353,6 +355,7 @@
     name: "mysdk_crtobj@current",
     sdk_member_name: "crtobj",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     sanitize: {
@@ -372,6 +375,7 @@
     name: "crtobj",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     sanitize: {
@@ -480,6 +484,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -511,6 +516,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -575,6 +581,7 @@
     name: "mymodule_exports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     arch: {
@@ -591,6 +598,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     arch: {
         arm64: {
@@ -654,6 +662,7 @@
     name: "myexports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -687,6 +696,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -802,6 +812,7 @@
     name: "myexports_mynativebinary@current",
     sdk_member_name: "mynativebinary",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -822,6 +833,7 @@
     name: "mynativebinary",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -841,6 +853,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -861,6 +874,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -933,6 +947,7 @@
     name: "mymodule_exports_linker@current",
     sdk_member_name: "linker",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -959,6 +974,7 @@
     name: "linker",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1167,6 +1183,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1188,6 +1205,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     shared_libs: [
@@ -1208,6 +1226,7 @@
     name: "mysdk_myothernativelib@current",
     sdk_member_name: "myothernativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1226,6 +1245,7 @@
     name: "myothernativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     system_shared_libs: ["libm"],
@@ -1243,6 +1263,7 @@
     name: "mysdk_mysystemnativelib@current",
     sdk_member_name: "mysystemnativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1260,6 +1281,7 @@
     name: "mysystemnativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     arch: {
@@ -1327,6 +1349,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1355,6 +1378,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     sdk_version: "minimum",
@@ -1449,6 +1473,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1482,6 +1507,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1572,6 +1598,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     stl: "none",
     compile_multilib: "both",
@@ -1592,6 +1619,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -1660,6 +1688,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1687,6 +1716,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1769,6 +1799,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     recovery_available: true,
     vendor_available: true,
@@ -1799,6 +1830,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     recovery_available: true,
     vendor_available: true,
     stl: "none",
@@ -1877,6 +1909,7 @@
     name: "myexports_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     installable: false,
@@ -1899,6 +1932,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -1964,6 +1998,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -1973,6 +2008,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -2016,6 +2052,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -2038,6 +2075,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     stl: "none",
@@ -2113,6 +2151,7 @@
     name: "mysdk_mynativeheaders@current",
     sdk_member_name: "mynativeheaders",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "both",
@@ -2140,6 +2179,7 @@
     name: "mynativeheaders",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     stl: "none",
     compile_multilib: "both",
@@ -2220,6 +2260,7 @@
     name: "mysdk_sslnil@current",
     sdk_member_name: "sslnil",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     arch: {
@@ -2236,6 +2277,7 @@
     name: "sslnil",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     arch: {
         arm64: {
@@ -2251,6 +2293,7 @@
     name: "mysdk_sslempty@current",
     sdk_member_name: "sslempty",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     system_shared_libs: [],
@@ -2268,6 +2311,7 @@
     name: "sslempty",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     system_shared_libs: [],
     arch: {
@@ -2284,6 +2328,7 @@
     name: "mysdk_sslnonempty@current",
     sdk_member_name: "sslnonempty",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     system_shared_libs: ["mysdk_sslnil@current"],
@@ -2301,6 +2346,7 @@
     name: "sslnonempty",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     system_shared_libs: ["sslnil"],
     arch: {
@@ -2350,6 +2396,7 @@
     name: "mysdk_sslvariants@current",
     sdk_member_name: "sslvariants",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     compile_multilib: "both",
@@ -2381,6 +2428,7 @@
     name: "sslvariants",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     compile_multilib: "both",
     target: {
@@ -2456,6 +2504,7 @@
     name: "mysdk_stubslib@current",
     sdk_member_name: "stubslib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     stubs: {
@@ -2479,6 +2528,7 @@
     name: "stubslib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     stubs: {
         versions: [
@@ -2537,6 +2587,7 @@
     name: "mysdk_stubslib@current",
     sdk_member_name: "stubslib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     compile_multilib: "both",
@@ -2572,6 +2623,7 @@
     name: "stubslib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     compile_multilib: "both",
     stubs: {
@@ -2645,6 +2697,7 @@
     name: "mysdk_mylib@current",
     sdk_member_name: "mylib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     installable: false,
     unique_host_soname: true,
@@ -2674,6 +2727,7 @@
     name: "mylib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     unique_host_soname: true,
     compile_multilib: "both",
@@ -2755,6 +2809,7 @@
     name: "mysdk_mynativelib@current",
     sdk_member_name: "mynativelib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     installable: false,
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
@@ -2772,6 +2827,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     compile_multilib: "both",
     export_include_dirs: ["include/include"],
     arch: {
diff --git a/sdk/exports_test.go b/sdk/exports_test.go
index aa1200f..1c59244 100644
--- a/sdk/exports_test.go
+++ b/sdk/exports_test.go
@@ -50,6 +50,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -57,6 +58,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 731e528..b44f66e 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -122,6 +122,7 @@
     name: "mysdk_sdkmember@current",
     sdk_member_name: "sdkmember",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/sdkmember.jar"],
 }
 
@@ -129,6 +130,7 @@
     name: "sdkmember",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/sdkmember.jar"],
 }
 
@@ -247,6 +249,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -254,6 +257,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -302,6 +306,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -311,6 +316,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -357,6 +363,7 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -372,6 +379,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -426,6 +434,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -433,6 +442,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -481,6 +491,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -490,6 +501,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavalib.jar"],
@@ -535,6 +547,7 @@
     name: "myexports_myjavatests@current",
     sdk_member_name: "myjavatests",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavatests.jar"],
     test_config: "java/myjavatests-AndroidTest.xml",
 }
@@ -543,6 +556,7 @@
     name: "myjavatests",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavatests.jar"],
     test_config: "java/myjavatests-AndroidTest.xml",
 }
@@ -588,6 +602,7 @@
     name: "myexports_myjavatests@current",
     sdk_member_name: "myjavatests",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavatests.jar"],
@@ -598,6 +613,7 @@
     name: "myjavatests",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/myjavatests.jar"],
@@ -655,6 +671,7 @@
     name: "mysdk_exported-system-module@current",
     sdk_member_name: "exported-system-module",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/exported-system-module.jar"],
 }
 
@@ -662,6 +679,7 @@
     name: "exported-system-module",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/exported-system-module.jar"],
 }
 
@@ -669,6 +687,7 @@
     name: "mysdk_system-module@current",
     sdk_member_name: "system-module",
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/system-module.jar"],
 }
 
@@ -676,6 +695,7 @@
     name: "mysdk_system-module",
     prefer: false,
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/system-module.jar"],
 }
 
@@ -747,6 +767,7 @@
     name: "mysdk_system-module@current",
     sdk_member_name: "system-module",
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/system-module.jar"],
@@ -756,6 +777,7 @@
     name: "mysdk_system-module",
     prefer: false,
     visibility: ["//visibility:private"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/system-module.jar"],
@@ -836,6 +858,7 @@
     name: "myexports_hostjavalib@current",
     sdk_member_name: "hostjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/hostjavalib.jar"],
@@ -845,6 +868,7 @@
     name: "hostjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     device_supported: false,
     host_supported: true,
     jars: ["java/hostjavalib.jar"],
@@ -854,6 +878,7 @@
     name: "myexports_androidjavalib@current",
     sdk_member_name: "androidjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/androidjavalib.jar"],
 }
 
@@ -861,6 +886,7 @@
     name: "androidjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/androidjavalib.jar"],
 }
 
@@ -868,6 +894,7 @@
     name: "myexports_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -883,6 +910,7 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     host_supported: true,
     target: {
         android: {
@@ -948,21 +976,21 @@
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     test: {
         jars: ["sdk_library/test/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/test/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/test/myjavalib_stub_sources"],
         current_api: "sdk_library/test/myjavalib.txt",
         removed_api: "sdk_library/test/myjavalib-removed.txt",
         sdk_version: "test_current",
@@ -977,21 +1005,21 @@
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     test: {
         jars: ["sdk_library/test/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/test/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/test/myjavalib_stub_sources"],
         current_api: "sdk_library/test/myjavalib.txt",
         removed_api: "sdk_library/test/myjavalib-removed.txt",
         sdk_version: "test_current",
@@ -1045,10 +1073,11 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "none",
@@ -1059,10 +1088,11 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "none",
@@ -1112,10 +1142,11 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1126,10 +1157,11 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1186,14 +1218,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
@@ -1208,14 +1240,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
@@ -1279,21 +1311,21 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     module_lib: {
         jars: ["sdk_library/module-lib/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources"],
         current_api: "sdk_library/module-lib/myjavalib.txt",
         removed_api: "sdk_library/module-lib/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1308,21 +1340,21 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system: {
         jars: ["sdk_library/system/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
         current_api: "sdk_library/system/myjavalib.txt",
         removed_api: "sdk_library/system/myjavalib-removed.txt",
         sdk_version: "system_current",
     },
     module_lib: {
         jars: ["sdk_library/module-lib/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources"],
         current_api: "sdk_library/module-lib/myjavalib.txt",
         removed_api: "sdk_library/module-lib/myjavalib-removed.txt",
         sdk_version: "module_current",
@@ -1387,14 +1419,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system_server: {
         jars: ["sdk_library/system-server/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources"],
         current_api: "sdk_library/system-server/myjavalib.txt",
         removed_api: "sdk_library/system-server/myjavalib-removed.txt",
         sdk_version: "system_server_current",
@@ -1409,14 +1441,14 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
     system_server: {
         jars: ["sdk_library/system-server/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/system-server/myjavalib_stub_sources"],
         current_api: "sdk_library/system-server/myjavalib.txt",
         removed_api: "sdk_library/system-server/myjavalib-removed.txt",
         sdk_version: "system_server_current",
@@ -1476,7 +1508,7 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1492,7 +1524,7 @@
     shared_library: true,
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1547,11 +1579,12 @@
     name: "mysdk_myjavalib@current",
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     doctag_files: ["doctags/docs/known_doctags"],
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
@@ -1562,11 +1595,12 @@
     name: "myjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     shared_library: true,
     doctag_files: ["doctags/docs/known_doctags"],
     public: {
         jars: ["sdk_library/public/myjavalib-stubs.jar"],
-        stub_srcs: ["sdk_library/public/myjavalib_stub_sources/**/*.java"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
         current_api: "sdk_library/public/myjavalib.txt",
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 2e6c62a..c4dc41b 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -181,6 +181,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -192,6 +193,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myjavalib.jar"],
 }
 
@@ -199,6 +201,7 @@
     name: "mysdk_mypublicjavalib@current",
     sdk_member_name: "mypublicjavalib",
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mypublicjavalib.jar"],
 }
 
@@ -206,6 +209,7 @@
     name: "mypublicjavalib",
     prefer: false,
     visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mypublicjavalib.jar"],
 }
 
@@ -217,6 +221,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mydefaultedjavalib.jar"],
 }
 
@@ -228,6 +233,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/mydefaultedjavalib.jar"],
 }
 
@@ -238,6 +244,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myprivatejavalib.jar"],
 }
 
@@ -248,6 +255,7 @@
         "//package",
         "//prebuilts/mysdk",
     ],
+    apex_available: ["//apex_available:platform"],
     jars: ["java/myprivatejavalib.jar"],
 }
 
diff --git a/sdk/testing.go b/sdk/testing.go
index 5f520e5..91aa879 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -87,7 +87,7 @@
 	// * Configure that we are inside make
 	// * Add CommonOS to ensure that androidmk processing works.
 	android.RegisterAndroidMkBuildComponents(ctx)
-	android.SetInMakeForTests(config)
+	android.SetKatiEnabledForTests(config)
 	config.Targets[android.CommonOS] = []android.Target{
 		{android.CommonOS, android.Arch{ArchType: android.Common}, android.NativeBridgeDisabled, "", "", true},
 	}
diff --git a/sdk/update.go b/sdk/update.go
index 7bf5dea..ba63542 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -735,6 +735,24 @@
 		}
 	}
 
+	// Where available copy apex_available properties from the member.
+	if apexAware, ok := variant.(interface{ ApexAvailable() []string }); ok {
+		apexAvailable := apexAware.ApexAvailable()
+		if len(apexAvailable) == 0 {
+			// //apex_available:platform is the default.
+			apexAvailable = []string{android.AvailableToPlatform}
+		}
+
+		// Add in any baseline apex available settings.
+		apexAvailable = append(apexAvailable, apex.BaselineApexAvailable(member.Name())...)
+
+		// Remove duplicates and sort.
+		apexAvailable = android.FirstUniqueStrings(apexAvailable)
+		sort.Strings(apexAvailable)
+
+		m.AddProperty("apex_available", apexAvailable)
+	}
+
 	deviceSupported := false
 	hostSupported := false
 
@@ -749,22 +767,6 @@
 
 	addHostDeviceSupportedProperties(deviceSupported, hostSupported, m)
 
-	// Where available copy apex_available properties from the member.
-	if apexAware, ok := variant.(interface{ ApexAvailable() []string }); ok {
-		apexAvailable := apexAware.ApexAvailable()
-
-		// Add in any baseline apex available settings.
-		apexAvailable = append(apexAvailable, apex.BaselineApexAvailable(member.Name())...)
-
-		if len(apexAvailable) > 0 {
-			// Remove duplicates and sort.
-			apexAvailable = android.FirstUniqueStrings(apexAvailable)
-			sort.Strings(apexAvailable)
-
-			m.AddProperty("apex_available", apexAvailable)
-		}
-	}
-
 	// Disable installation in the versioned module of those modules that are ever installable.
 	if installable, ok := variant.(interface{ EverInstallable() bool }); ok {
 		if installable.EverInstallable() {
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 1740ba8..828d1cf 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// sysprop package defines a module named sysprop_library that can implement sysprop as API
+// See https://source.android.com/devices/architecture/sysprops-apis for details
 package sysprop
 
 import (
@@ -71,6 +73,8 @@
 	})
 }
 
+// syspropJavaGenRule module generates srcjar containing generated java APIs.
+// It also depends on check api rule, so api check has to pass to use sysprop_library.
 func (g *syspropJavaGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	var checkApiFileTimeStamp android.WritablePath
 
@@ -170,6 +174,7 @@
 	syspropLibrariesLock sync.Mutex
 )
 
+// List of sysprop_library used by property_contexts to perform type check.
 func syspropLibraries(config android.Config) *[]string {
 	return config.Once(syspropLibrariesKey, func() interface{} {
 		return &[]string{}
@@ -192,7 +197,7 @@
 	return m.properties.Property_owner
 }
 
-func (m *syspropLibrary) CcModuleName() string {
+func (m *syspropLibrary) CcImplementationModuleName() string {
 	return "lib" + m.BaseModuleName()
 }
 
@@ -223,6 +228,8 @@
 	return m.currentApiFile
 }
 
+// GenerateAndroidBuildActions of sysprop_library handles API dump and API check.
+// generated java_library will depend on these API files.
 func (m *syspropLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	baseModuleName := m.BaseModuleName()
 
@@ -251,7 +258,8 @@
 	// check API rule
 	rule = android.NewRuleBuilder()
 
-	// 1. current.txt <-> api_dump.txt
+	// 1. compares current.txt to api-dump.txt
+	// current.txt should be identical to api-dump.txt.
 	msg := fmt.Sprintf(`\n******************************\n`+
 		`API of sysprop_library %s doesn't match with current.txt\n`+
 		`Please update current.txt by:\n`+
@@ -267,7 +275,8 @@
 		Flag(`"` + msg + `"`).
 		Text("; exit 38) )")
 
-	// 2. current.txt <-> latest.txt
+	// 2. compares current.txt to latest.txt (frozen API)
+	// current.txt should be compatible with latest.txt
 	msg = fmt.Sprintf(`\n******************************\n`+
 		`API of sysprop_library %s doesn't match with latest version\n`+
 		`Please fix the breakage and rebuild.\n`+
@@ -410,14 +419,16 @@
 	installedInVendorOrOdm := ctx.SocSpecific() || ctx.DeviceSpecific()
 	installedInProduct := ctx.ProductSpecific()
 	isOwnerPlatform := false
-	var stub string
+	var javaSyspropStub string
 
+	// javaSyspropStub contains stub libraries used by generated APIs, instead of framework stub.
+	// This is to make sysprop_library link against core_current.
 	if installedInVendorOrOdm {
-		stub = "sysprop-library-stub-vendor"
+		javaSyspropStub = "sysprop-library-stub-vendor"
 	} else if installedInProduct {
-		stub = "sysprop-library-stub-product"
+		javaSyspropStub = "sysprop-library-stub-product"
 	} else {
-		stub = "sysprop-library-stub-platform"
+		javaSyspropStub = "sysprop-library-stub-platform"
 	}
 
 	switch m.Owner() {
@@ -441,8 +452,10 @@
 			"Unknown value %s: must be one of Platform, Vendor or Odm", m.Owner())
 	}
 
+	// Generate a C++ implementation library.
+	// cc_library can receive *.sysprop files as their srcs, generating sources itself.
 	ccProps := ccLibraryProperties{}
-	ccProps.Name = proptools.StringPtr(m.CcModuleName())
+	ccProps.Name = proptools.StringPtr(m.CcImplementationModuleName())
 	ccProps.Srcs = m.properties.Srcs
 	ccProps.Soc_specific = proptools.BoolPtr(ctx.SocSpecific())
 	ccProps.Device_specific = proptools.BoolPtr(ctx.DeviceSpecific())
@@ -463,15 +476,17 @@
 
 	// We need to only use public version, if the partition where sysprop_library will be installed
 	// is different from owner.
-
 	if ctx.ProductSpecific() {
-		// Currently product partition can't own any sysprop_library.
+		// Currently product partition can't own any sysprop_library. So product always uses public.
 		scope = "public"
 	} else if isOwnerPlatform && installedInVendorOrOdm {
 		// Vendor or Odm should use public version of Platform's sysprop_library.
 		scope = "public"
 	}
 
+	// Generate a Java implementation library.
+	// Contrast to C++, syspropJavaGenRule module will generate srcjar and the srcjar will be fed
+	// to Java implementation library.
 	ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{
 		Srcs:  m.properties.Srcs,
 		Scope: scope,
@@ -486,7 +501,7 @@
 		Product_specific: proptools.BoolPtr(ctx.ProductSpecific()),
 		Installable:      m.properties.Installable,
 		Sdk_version:      proptools.StringPtr("core_current"),
-		Libs:             []string{stub},
+		Libs:             []string{javaSyspropStub},
 	})
 
 	// if platform sysprop_library is installed in /system or /system-ext, we regard it as an API
@@ -505,11 +520,13 @@
 			Srcs:        []string{":" + m.javaGenPublicStubName()},
 			Installable: proptools.BoolPtr(false),
 			Sdk_version: proptools.StringPtr("core_current"),
-			Libs:        []string{stub},
+			Libs:        []string{javaSyspropStub},
 			Stem:        proptools.StringPtr(m.BaseModuleName()),
 		})
 	}
 
+	// syspropLibraries will be used by property_contexts to check types.
+	// Record absolute paths of sysprop_library to prevent soong_namespace problem.
 	if m.ExportedToMake() {
 		syspropLibrariesLock.Lock()
 		defer syspropLibrariesLock.Unlock()
@@ -519,6 +536,8 @@
 	}
 }
 
+// syspropDepsMutator adds dependencies from java implementation library to sysprop library.
+// java implementation library then depends on check API rule of sysprop library.
 func syspropDepsMutator(ctx android.BottomUpMutatorContext) {
 	if m, ok := ctx.Module().(*syspropLibrary); ok {
 		ctx.AddReverseDependency(m, nil, m.javaGenModuleName())
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index b35f831..27d71e8 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -188,7 +188,7 @@
 }
 
 func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
-	testSuites []string, autoGenConfig *bool) android.Path {
+	testSuites []string, autoGenConfig *bool, unitTest *bool) android.Path {
 	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
@@ -198,7 +198,11 @@
 			if ctx.Device() {
 				autogenTemplate(ctx, autogenPath, "${JavaTestConfigTemplate}", nil, "")
 			} else {
-				autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", nil, "")
+				if Bool(unitTest) {
+					autogenTemplate(ctx, autogenPath, "${JavaHostUnitTestConfigTemplate}", nil, "")
+				} else {
+					autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", nil, "")
+				}
 			}
 		}
 		return autogenPath
diff --git a/tradefed/config.go b/tradefed/config.go
index f7e8349..f3566a8 100644
--- a/tradefed/config.go
+++ b/tradefed/config.go
@@ -27,6 +27,7 @@
 	pctx.SourcePathVariable("InstrumentationTestConfigTemplate", "build/make/core/instrumentation_test_config_template.xml")
 	pctx.SourcePathVariable("JavaTestConfigTemplate", "build/make/core/java_test_config_template.xml")
 	pctx.SourcePathVariable("JavaHostTestConfigTemplate", "build/make/core/java_host_test_config_template.xml")
+	pctx.SourcePathVariable("JavaHostUnitTestConfigTemplate", "build/make/core/java_host_unit_test_config_template.xml")
 	pctx.SourcePathVariable("NativeBenchmarkTestConfigTemplate", "build/make/core/native_benchmark_test_config_template.xml")
 	pctx.SourcePathVariable("NativeHostTestConfigTemplate", "build/make/core/native_host_test_config_template.xml")
 	pctx.SourcePathVariable("NativeTestConfigTemplate", "build/make/core/native_test_config_template.xml")
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index 7cc7caf..2d36f67 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -24,8 +24,8 @@
 	"android/soong/ui/metrics"
 )
 
-// Main entry point to construct the Bazel build command line, environment variables
-// and post-processing steps (e.g. converge output directories)
+// Main entry point to construct the Bazel build command line, environment
+// variables and post-processing steps (e.g. converge output directories)
 func runBazel(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunBazel, "bazel")
 	defer ctx.EndTrace()
@@ -41,53 +41,86 @@
 		outputGroups = strings.Join(config.ninjaArgs, ",")
 	}
 
+	// Environment variables are the primary mechanism to pass information from
+	// soong_ui configuration or context to Bazel.
+	//
+	// Use *_NINJA variables to pass the root-relative path of the combined,
+	// kati-generated, soong-generated, and packaging Ninja files to Bazel.
+	// Bazel reads these from the lunch() repository rule.
 	config.environ.Set("COMBINED_NINJA", config.CombinedNinjaFile())
 	config.environ.Set("KATI_NINJA", config.KatiBuildNinjaFile())
 	config.environ.Set("PACKAGE_NINJA", config.KatiPackageNinjaFile())
 	config.environ.Set("SOONG_NINJA", config.SoongNinjaFile())
 
+	// `tools/bazel` is the default entry point for executing Bazel in the AOSP
+	// source tree.
 	bazelExecutable := filepath.Join("tools", "bazel")
 	cmd := Command(ctx, config, "bazel", bazelExecutable)
 
-	if extra_startup_args, ok := cmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
-		cmd.Args = append(cmd.Args, strings.Fields(extra_startup_args)...)
+	// Append custom startup flags to the Bazel command. Startup flags affect
+	// the Bazel server itself, and any changes to these flags would incur a
+	// restart of the server, losing much of the in-memory incrementality.
+	if extraStartupArgs, ok := cmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
+		cmd.Args = append(cmd.Args, strings.Fields(extraStartupArgs)...)
 	}
 
+	// Start constructing the `build` command.
 	actionName := "build"
 	cmd.Args = append(cmd.Args,
 		actionName,
+		// Use output_groups to select the set of outputs to produce from a
+		// ninja_build target.
 		"--output_groups="+outputGroups,
+		// Generate a performance profile
 		"--profile="+filepath.Join(shared.BazelMetricsFilename(config.OutDir(), actionName)),
 		"--slim_profile=true",
 	)
 
-	if extra_build_args, ok := cmd.Environment.Get("BAZEL_BUILD_ARGS"); ok {
-		cmd.Args = append(cmd.Args, strings.Fields(extra_build_args)...)
+	// Append custom build flags to the Bazel command. Changes to these flags
+	// may invalidate Bazel's analysis cache.
+	if extraBuildArgs, ok := cmd.Environment.Get("BAZEL_BUILD_ARGS"); ok {
+		cmd.Args = append(cmd.Args, strings.Fields(extraBuildArgs)...)
 	}
 
+	// Append the label of the default ninja_build target.
 	cmd.Args = append(cmd.Args,
 		"//:"+config.TargetProduct()+"-"+config.TargetBuildVariant(),
 	)
 
+	// Ensure that the PATH environment variable value used in the action
+	// environment is the restricted set computed from soong_ui, and not a
+	// user-provided one, for hermeticity reasons.
 	if pathEnvValue, ok := config.environ.Get("PATH"); ok {
 		cmd.Environment.Set("PATH", pathEnvValue)
 		cmd.Args = append(cmd.Args, "--action_env=PATH="+pathEnvValue)
 	}
+
 	cmd.Environment.Set("DIST_DIR", config.DistDir())
 	cmd.Environment.Set("SHELL", "/bin/bash")
 
+	// Print the full command line for debugging purposes.
 	ctx.Println(cmd.Cmd)
+
+	// Execute the command at the root of the directory.
 	cmd.Dir = filepath.Join(config.OutDir(), "..")
 	ctx.Status.Status("Starting Bazel..")
+
+	// Execute the build command.
 	cmd.RunAndStreamOrFatal()
 
+	// Post-processing steps start here. Once the Bazel build completes, the
+	// output files are still stored in the execution root, not in $OUT_DIR.
+	// Ensure that the $OUT_DIR contains the expected set of files by symlinking
+	// the files from the execution root's output direction into $OUT_DIR.
+
 	// Obtain the Bazel output directory for ninja_build.
 	infoCmd := Command(ctx, config, "bazel", bazelExecutable)
 
-	if extra_startup_args, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
-		infoCmd.Args = append(infoCmd.Args, strings.Fields(extra_startup_args)...)
+	if extraStartupArgs, ok := infoCmd.Environment.Get("BAZEL_STARTUP_ARGS"); ok {
+		infoCmd.Args = append(infoCmd.Args, strings.Fields(extraStartupArgs)...)
 	}
 
+	// Obtain the output directory path in the execution root.
 	infoCmd.Args = append(infoCmd.Args,
 		"info",
 		"output_path",
diff --git a/ui/build/build.go b/ui/build/build.go
index 1cf2023..cfd0b83 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -23,13 +23,20 @@
 	"android/soong/ui/metrics"
 )
 
-// Ensures the out directory exists, and has the proper files to prevent kati
-// from recursing into it.
+// SetupOutDir ensures the out directory exists, and has the proper files to
+// prevent kati from recursing into it.
 func SetupOutDir(ctx Context, config Config) {
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
 	if !config.SkipMake() {
-		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.in_make"))
+		// Run soong_build with Kati for a hybrid build, e.g. running the
+		// AndroidMk singleton and postinstall commands. Communicate this to
+		// soong_build by writing an empty .soong.kati_enabled marker file in the
+		// soong_build output directory for the soong_build primary builder to
+		// know if the user wants to run Kati after.
+		//
+		// This does not preclude running Kati for *product configuration purposes*.
+		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.kati_enabled"))
 	}
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
@@ -176,7 +183,7 @@
 		help(ctx, config, what)
 		return
 	} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
-		clean(ctx, config, what)
+		clean(ctx, config)
 		return
 	}
 
@@ -211,12 +218,12 @@
 
 	if inList("installclean", config.Arguments()) ||
 		inList("install-clean", config.Arguments()) {
-		installClean(ctx, config, what)
+		installClean(ctx, config)
 		ctx.Println("Deleted images and staging directories.")
 		return
 	} else if inList("dataclean", config.Arguments()) ||
 		inList("data-clean", config.Arguments()) {
-		dataClean(ctx, config, what)
+		dataClean(ctx, config)
 		ctx.Println("Deleted data files.")
 		return
 	}
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index 03e884a..06f6c63 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -26,8 +26,11 @@
 	"android/soong/ui/metrics"
 )
 
+// Given a series of glob patterns, remove matching files and directories from the filesystem.
+// For example, "malware*" would remove all files and directories in the current directory that begin with "malware".
 func removeGlobs(ctx Context, globs ...string) {
 	for _, glob := range globs {
+		// Find files and directories that match this glob pattern.
 		files, err := filepath.Glob(glob)
 		if err != nil {
 			// Only possible error is ErrBadPattern
@@ -45,13 +48,15 @@
 
 // Remove everything under the out directory. Don't remove the out directory
 // itself in case it's a symlink.
-func clean(ctx Context, config Config, what int) {
+func clean(ctx Context, config Config) {
 	removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
 	ctx.Println("Entire build directory removed.")
 }
 
-func dataClean(ctx Context, config Config, what int) {
+// Remove everything in the data directory.
+func dataClean(ctx Context, config Config) {
 	removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*"))
+	ctx.Println("Entire data directory removed.")
 }
 
 // installClean deletes all of the installed files -- the intent is to remove
@@ -61,8 +66,8 @@
 //
 // This is faster than a full clean, since we're not deleting the
 // intermediates.  Instead of recompiling, we can just copy the results.
-func installClean(ctx Context, config Config, what int) {
-	dataClean(ctx, config, what)
+func installClean(ctx Context, config Config) {
+	dataClean(ctx, config)
 
 	if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" {
 		hostCrossOut := func(path string) string {
@@ -145,85 +150,95 @@
 // Since products and build variants (unfortunately) shared the same
 // PRODUCT_OUT staging directory, things can get out of sync if different
 // build configurations are built in the same tree. This function will
-// notice when the configuration has changed and call installclean to
+// notice when the configuration has changed and call installClean to
 // remove the files necessary to keep things consistent.
 func installCleanIfNecessary(ctx Context, config Config) {
 	configFile := config.DevicePreviousProductConfig()
 	prefix := "PREVIOUS_BUILD_CONFIG := "
 	suffix := "\n"
-	currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
+	currentConfig := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
 
 	ensureDirectoriesExist(ctx, filepath.Dir(configFile))
 
 	writeConfig := func() {
-		err := ioutil.WriteFile(configFile, []byte(currentProduct), 0666)
+		err := ioutil.WriteFile(configFile, []byte(currentConfig), 0666) // a+rw
 		if err != nil {
 			ctx.Fatalln("Failed to write product config:", err)
 		}
 	}
 
-	prev, err := ioutil.ReadFile(configFile)
+	previousConfigBytes, err := ioutil.ReadFile(configFile)
 	if err != nil {
 		if os.IsNotExist(err) {
+			// Just write the new config file, no old config file to worry about.
 			writeConfig()
 			return
 		} else {
 			ctx.Fatalln("Failed to read previous product config:", err)
 		}
-	} else if string(prev) == currentProduct {
+	}
+
+	previousConfig := string(previousConfigBytes)
+	if previousConfig == currentConfig {
+		// Same config as before - nothing to clean.
 		return
 	}
 
-	if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" {
-		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.")
+	if config.Environment().IsEnvTrue("DISABLE_AUTO_INSTALLCLEAN") {
+		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set and true; skipping auto-clean. Your tree may be in an inconsistent state.")
 		return
 	}
 
 	ctx.BeginTrace(metrics.PrimaryNinja, "installclean")
 	defer ctx.EndTrace()
 
-	prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix)
-	currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix)
+	previousProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(previousConfig, suffix), prefix)
+	currentProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(currentConfig, suffix), prefix)
 
-	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig)
+	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", previousProductAndVariant, currentProductAndVariant)
 
-	installClean(ctx, config, 0)
+	installClean(ctx, config)
 
 	writeConfig()
 }
 
 // cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
 // the filesystem if they were removed from the input file since the last execution.
-func cleanOldFiles(ctx Context, basePath, file string) {
-	file = filepath.Join(basePath, file)
-	oldFile := file + ".previous"
+func cleanOldFiles(ctx Context, basePath, newFile string) {
+	newFile = filepath.Join(basePath, newFile)
+	oldFile := newFile + ".previous"
 
-	if _, err := os.Stat(file); err != nil {
-		ctx.Fatalf("Expected %q to be readable", file)
+	if _, err := os.Stat(newFile); err != nil {
+		ctx.Fatalf("Expected %q to be readable", newFile)
 	}
 
 	if _, err := os.Stat(oldFile); os.IsNotExist(err) {
-		if err := os.Rename(file, oldFile); err != nil {
-			ctx.Fatalf("Failed to rename file list (%q->%q): %v", file, oldFile, err)
+		if err := os.Rename(newFile, oldFile); err != nil {
+			ctx.Fatalf("Failed to rename file list (%q->%q): %v", newFile, oldFile, err)
 		}
 		return
 	}
 
-	var newPaths, oldPaths []string
-	if newData, err := ioutil.ReadFile(file); err == nil {
-		if oldData, err := ioutil.ReadFile(oldFile); err == nil {
-			// Common case: nothing has changed
-			if bytes.Equal(newData, oldData) {
-				return
-			}
-			newPaths = strings.Fields(string(newData))
-			oldPaths = strings.Fields(string(oldData))
-		} else {
-			ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
-		}
+	var newData, oldData []byte
+	if data, err := ioutil.ReadFile(newFile); err == nil {
+		newData = data
 	} else {
-		ctx.Fatalf("Failed to read list of installable files (%q): %v", file, err)
+		ctx.Fatalf("Failed to read list of installable files (%q): %v", newFile, err)
 	}
+	if data, err := ioutil.ReadFile(oldFile); err == nil {
+		oldData = data
+	} else {
+		ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
+	}
+
+	// Common case: nothing has changed
+	if bytes.Equal(newData, oldData) {
+		return
+	}
+
+	var newPaths, oldPaths []string
+	newPaths = strings.Fields(string(newData))
+	oldPaths = strings.Fields(string(oldData))
 
 	// These should be mostly sorted by make already, but better make sure Go concurs
 	sort.Strings(newPaths)
@@ -242,42 +257,55 @@
 				continue
 			}
 		}
+
 		// File only exists in the old list; remove if it exists
-		old := filepath.Join(basePath, oldPaths[0])
+		oldPath := filepath.Join(basePath, oldPaths[0])
 		oldPaths = oldPaths[1:]
-		if fi, err := os.Stat(old); err == nil {
-			if fi.IsDir() {
-				if err := os.Remove(old); err == nil {
-					ctx.Println("Removed directory that is no longer installed: ", old)
-					cleanEmptyDirs(ctx, filepath.Dir(old))
+
+		if oldFile, err := os.Stat(oldPath); err == nil {
+			if oldFile.IsDir() {
+				if err := os.Remove(oldPath); err == nil {
+					ctx.Println("Removed directory that is no longer installed: ", oldPath)
+					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
 				} else {
-					ctx.Println("Failed to remove directory that is no longer installed (%q): %v", old, err)
+					ctx.Println("Failed to remove directory that is no longer installed (%q): %v", oldPath, err)
 					ctx.Println("It's recommended to run `m installclean`")
 				}
 			} else {
-				if err := os.Remove(old); err == nil {
-					ctx.Println("Removed file that is no longer installed: ", old)
-					cleanEmptyDirs(ctx, filepath.Dir(old))
+				// Removing a file, not a directory.
+				if err := os.Remove(oldPath); err == nil {
+					ctx.Println("Removed file that is no longer installed: ", oldPath)
+					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
 				} else if !os.IsNotExist(err) {
-					ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", old, err)
+					ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", oldPath, err)
 				}
 			}
 		}
 	}
 
 	// Use the new list as the base for the next build
-	os.Rename(file, oldFile)
+	os.Rename(newFile, oldFile)
 }
 
+// cleanEmptyDirs will delete a directory if it contains no files.
+// If a deletion occurs, then it also recurses upwards to try and delete empty parent directories.
 func cleanEmptyDirs(ctx Context, dir string) {
 	files, err := ioutil.ReadDir(dir)
-	if err != nil || len(files) > 0 {
+	if err != nil {
+		ctx.Println("Could not read directory while trying to clean empty dirs: ", dir)
 		return
 	}
-	if err := os.Remove(dir); err == nil {
-		ctx.Println("Removed directory that is no longer installed: ", dir)
-	} else {
-		ctx.Fatalf("Failed to remove directory that is no longer installed (%q): %v", dir, err)
+	if len(files) > 0 {
+		// Directory is not empty.
+		return
 	}
+
+	if err := os.Remove(dir); err == nil {
+		ctx.Println("Removed empty directory (may no longer be installed?): ", dir)
+	} else {
+		ctx.Fatalf("Failed to remove empty directory (which may no longer be installed?) %q: (%v)", dir, err)
+	}
+
+	// Try and delete empty parent directories too.
 	cleanEmptyDirs(ctx, filepath.Dir(dir))
 }
diff --git a/ui/build/config.go b/ui/build/config.go
index 641aae6..229bd5c 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -703,6 +703,10 @@
 	return c.ninjaArgs
 }
 
+func (c *configImpl) BazelOutDir() string {
+	return filepath.Join(c.OutDir(), "bazel")
+}
+
 func (c *configImpl) SoongOutDir() string {
 	return filepath.Join(c.OutDir(), "soong")
 }
diff --git a/ui/build/environment.go b/ui/build/environment.go
index 9bca7c0..6d8a28f 100644
--- a/ui/build/environment.go
+++ b/ui/build/environment.go
@@ -37,8 +37,8 @@
 // It's equivalent to the os.LookupEnv function, but with this copy of the
 // Environment.
 func (e *Environment) Get(key string) (string, bool) {
-	for _, env := range *e {
-		if k, v, ok := decodeKeyValue(env); ok && k == key {
+	for _, envVar := range *e {
+		if k, v, ok := decodeKeyValue(envVar); ok && k == key {
 			return v, true
 		}
 	}
@@ -65,37 +65,40 @@
 
 // Unset removes the specified keys from the Environment.
 func (e *Environment) Unset(keys ...string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+			// Delete this key.
 			continue
 		}
-		out = append(out, env)
+		newEnv = append(newEnv, envVar)
 	}
-	*e = out
+	*e = newEnv
 }
 
 // UnsetWithPrefix removes all keys that start with prefix.
 func (e *Environment) UnsetWithPrefix(prefix string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && strings.HasPrefix(key, prefix) {
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && strings.HasPrefix(key, prefix) {
+			// Delete this key.
 			continue
 		}
-		out = append(out, env)
+		newEnv = append(newEnv, envVar)
 	}
-	*e = out
+	*e = newEnv
 }
 
 // Allow removes all keys that are not present in the input list
 func (e *Environment) Allow(keys ...string) {
-	out := (*e)[:0]
-	for _, env := range *e {
-		if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
-			out = append(out, env)
+	newEnv := (*e)[:0]
+	for _, envVar := range *e {
+		if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+			// Keep this key.
+			newEnv = append(newEnv, envVar)
 		}
 	}
-	*e = out
+	*e = newEnv
 }
 
 // Environ returns the []string required for exec.Cmd.Env
@@ -105,11 +108,11 @@
 
 // Copy returns a copy of the Environment so that independent changes may be made.
 func (e *Environment) Copy() *Environment {
-	ret := Environment(make([]string, len(*e)))
-	for i, v := range *e {
-		ret[i] = v
+	envCopy := Environment(make([]string, len(*e)))
+	for i, envVar := range *e {
+		envCopy[i] = envVar
 	}
-	return &ret
+	return &envCopy
 }
 
 // IsTrue returns whether an environment variable is set to a positive value (1,y,yes,on,true)
@@ -140,15 +143,20 @@
 	return e.appendFromKati(file)
 }
 
+// Helper function for AppendFromKati. Accepts an io.Reader to make testing easier.
 func (e *Environment) appendFromKati(reader io.Reader) error {
 	scanner := bufio.NewScanner(reader)
 	for scanner.Scan() {
 		text := strings.TrimSpace(scanner.Text())
 
 		if len(text) == 0 || text[0] == '#' {
+			// Skip blank lines and comments.
 			continue
 		}
 
+		// We expect two space-delimited strings, like:
+		// unset 'HOME'
+		// export 'BEST_PIZZA_CITY'='NYC'
 		cmd := strings.SplitN(text, " ", 2)
 		if len(cmd) != 2 {
 			return fmt.Errorf("Unknown kati environment line: %q", text)
@@ -159,6 +167,8 @@
 			if !ok {
 				return fmt.Errorf("Failed to unquote kati line: %q", text)
 			}
+
+			// Actually unset it.
 			e.Unset(str)
 		} else if cmd[0] == "export" {
 			key, value, ok := decodeKeyValue(cmd[1])
@@ -175,6 +185,7 @@
 				return fmt.Errorf("Failed to unquote kati line: %q", text)
 			}
 
+			// Actually set it.
 			e.Set(key, value)
 		} else {
 			return fmt.Errorf("Unknown kati environment command: %q", text)
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 7a85657..2eb84ca 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -27,8 +27,10 @@
 	"android/soong/ui/metrics"
 )
 
-// This file provides an interface to the Finder for use in Soong UI
-// This file stores configuration information about which files to find
+// This file provides an interface to the Finder type for soong_ui. Finder is
+// used to recursively traverse the source tree to gather paths of files, such
+// as Android.bp or Android.mk, and store the lists/database of paths in files
+// under `$OUT_DIR/.module_paths`. This directory can also be dist'd.
 
 // NewSourceFinder returns a new Finder configured to search for source files.
 // Callers of NewSourceFinder should call <f.Shutdown()> when done
@@ -36,14 +38,18 @@
 	ctx.BeginTrace(metrics.RunSetupTool, "find modules")
 	defer ctx.EndTrace()
 
+	// Set up the working directory for the Finder.
 	dir, err := os.Getwd()
 	if err != nil {
 		ctx.Fatalf("No working directory for module-finder: %v", err.Error())
 	}
 	filesystem := fs.OsFs
 
-	// if the root dir is ignored, then the subsequent error messages are very confusing,
-	// so check for that upfront
+	// .out-dir and .find-ignore are markers for Finder to ignore siblings and
+	// subdirectories of the directory Finder finds them in, hence stopping the
+	// search recursively down those branches. It's possible that these files
+	// are in the root directory, and if they are, then the subsequent error
+	// messages are very confusing, so check for that here.
 	pruneFiles := []string{".out-dir", ".find-ignore"}
 	for _, name := range pruneFiles {
 		prunePath := filepath.Join(dir, name)
@@ -53,22 +59,34 @@
 		}
 	}
 
+	// Set up configuration parameters for the Finder cache.
 	cacheParams := finder.CacheParams{
 		WorkingDirectory: dir,
 		RootDirs:         []string{"."},
 		ExcludeDirs:      []string{".git", ".repo"},
 		PruneFiles:       pruneFiles,
 		IncludeFiles: []string{
+			// Kati build definitions.
 			"Android.mk",
+			// Product configuration files.
 			"AndroidProducts.mk",
+			// General Soong build definitions, using the Blueprint syntax.
 			"Android.bp",
+			// build/blueprint build definitions, using the Blueprint syntax.
 			"Blueprints",
+			// Bazel build definitions.
 			"BUILD.bazel",
+			// Kati clean definitions.
 			"CleanSpec.mk",
+			// Ownership definition.
 			"OWNERS",
+			// Test configuration for modules in directories that contain this
+			// file.
 			"TEST_MAPPING",
+			// Bazel top-level file to mark a directory as a Bazel workspace.
 			"WORKSPACE",
 		},
+		// Bazel Starlark configuration files.
 		IncludeSuffixes: []string{".bzl"},
 	}
 	dumpDir := config.FileListDir()
@@ -80,6 +98,7 @@
 	return f
 }
 
+// Finds the list of Bazel-related files (BUILD, WORKSPACE and Starlark) in the tree.
 func findBazelFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
 	matches := []string{}
 	for _, foundName := range entries.FileNames {
@@ -98,12 +117,21 @@
 	dumpDir := config.FileListDir()
 	os.MkdirAll(dumpDir, 0777)
 
+	// Stop searching a subdirectory recursively after finding an Android.mk.
 	androidMks := f.FindFirstNamedAt(".", "Android.mk")
 	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export module list: %v", err)
 	}
 
+	// Stop searching a subdirectory recursively after finding a CleanSpec.mk.
+	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
+	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	// Only consider AndroidProducts.mk in device/, vendor/ and product/, recursively in these directories.
 	androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
@@ -112,31 +140,30 @@
 		ctx.Fatalf("Could not export product list: %v", err)
 	}
 
+	// Recursively look for all Bazel related files.
 	bazelFiles := f.FindMatching(".", findBazelFiles)
 	err = dumpListToFile(ctx, config, bazelFiles, filepath.Join(dumpDir, "bazel.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export bazel BUILD list: %v", err)
 	}
 
-	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
-	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
-	if err != nil {
-		ctx.Fatalf("Could not export module list: %v", err)
-	}
-
+	// Recursively look for all OWNERS files.
 	owners := f.FindNamedAt(".", "OWNERS")
 	err = dumpListToFile(ctx, config, owners, filepath.Join(dumpDir, "OWNERS.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find OWNERS: %v", err)
 	}
 
+	// Recursively look for all TEST_MAPPING files.
 	testMappings := f.FindNamedAt(".", "TEST_MAPPING")
 	err = dumpListToFile(ctx, config, testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
 	}
 
+	// Recursively look for all Android.bp files
 	androidBps := f.FindNamedAt(".", "Android.bp")
+	// The files are named "Blueprints" only in the build/blueprint directory.
 	androidBps = append(androidBps, f.FindNamedAt("build/blueprint", "Blueprints")...)
 	if len(androidBps) == 0 {
 		ctx.Fatalf("No Android.bp found")
@@ -148,10 +175,12 @@
 
 	if config.Dist() {
 		f.WaitForDbDump()
+		// Dist the files.db plain text database.
 		distFile(ctx, config, f.DbPath, "module_paths")
 	}
 }
 
+// Write the .list files to disk.
 func dumpListToFile(ctx Context, config Config, list []string, filePath string) (err error) {
 	desiredText := strings.Join(list, "\n")
 	desiredBytes := []byte(desiredText)
diff --git a/ui/build/kati.go b/ui/build/kati.go
index f6c0f52..06ec646 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -33,12 +33,18 @@
 const katiCleanspecSuffix = "-cleanspec"
 const katiPackageSuffix = "-package"
 
-// genKatiSuffix creates a suffix for kati-generated files so that we can cache
-// them based on their inputs. So this should encode all common changes to Kati
-// inputs. Currently that includes the TARGET_PRODUCT, kati-processed command
-// line arguments, and the directories specified by mm/mmm.
+// genKatiSuffix creates a filename suffix for kati-generated files so that we
+// can cache them based on their inputs. Such files include the generated Ninja
+// files and env.sh environment variable setup files.
+//
+// The filename suffix should encode all common changes to Kati inputs.
+// Currently that includes the TARGET_PRODUCT and kati-processed command line
+// arguments.
 func genKatiSuffix(ctx Context, config Config) {
+	// Construct the base suffix.
 	katiSuffix := "-" + config.TargetProduct()
+
+	// Append kati arguments to the suffix.
 	if args := config.KatiArgs(); len(args) > 0 {
 		katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_"))
 	}
@@ -60,51 +66,101 @@
 	}
 }
 
+// Base function to construct and run the Kati command line with additional
+// arguments, and a custom function closure to mutate the environment Kati runs
+// in.
 func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {
 	executable := config.PrebuiltBuildTool("ckati")
+	// cKati arguments.
 	args = append([]string{
+		// Instead of executing commands directly, generate a Ninja file.
 		"--ninja",
+		// Generate Ninja files in the output directory.
 		"--ninja_dir=" + config.OutDir(),
+		// Filename suffix of the generated Ninja file.
 		"--ninja_suffix=" + config.KatiSuffix() + extraSuffix,
+		// Remove common parts at the beginning of a Ninja file, like build_dir,
+		// local_pool and _kati_always_build_. Allows Kati to be run multiple
+		// times, with generated Ninja files combined in a single invocation
+		// using 'include'.
 		"--no_ninja_prelude",
+		// Support declaring phony outputs in AOSP Ninja.
 		"--use_ninja_phony_output",
+		// Support declaring symlink outputs in AOSP Ninja.
 		"--use_ninja_symlink_outputs",
+		// Regenerate the Ninja file if environment inputs have changed. e.g.
+		// CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some
+		// $(shell ..) results.
 		"--regen",
+		// Skip '-include' directives starting with the specified path. Used to
+		// ignore generated .mk files.
 		"--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
+		// Detect the use of $(shell echo ...).
 		"--detect_android_echo",
+		// Colorful ANSI-based warning and error messages.
 		"--color_warnings",
+		// Generate all targets, not just the top level requested ones.
 		"--gen_all_targets",
+		// Use the built-in emulator of GNU find for better file finding
+		// performance. Used with $(shell find ...).
 		"--use_find_emulator",
+		// Fail when the find emulator encounters problems.
 		"--werror_find_emulator",
+		// Do not provide any built-in rules.
 		"--no_builtin_rules",
+		// Fail when suffix rules are used.
 		"--werror_suffix_rules",
-		"--warn_real_to_phony",
-		"--warn_phony_looks_real",
+		// Fail when a real target depends on a phony target.
 		"--werror_real_to_phony",
-		"--werror_phony_looks_real",
-		"--werror_writable",
+		// Makes real_to_phony checks assume that any top-level or leaf
+		// dependencies that does *not* have a '/' in it is a phony target.
 		"--top_level_phony",
+		// Fail when a phony target contains slashes.
+		"--werror_phony_looks_real",
+		// Fail when writing to a read-only directory.
+		"--werror_writable",
+		// Print Kati's internal statistics, such as the number of variables,
+		// implicit/explicit/suffix rules, and so on.
 		"--kati_stats",
 	}, args...)
 
+	// Generate a minimal Ninja file.
+	//
+	// Used for build_test and multiproduct_kati, which runs Kati several
+	// hundred times for different configurations to test file generation logic.
+	// These can result in generating Ninja files reaching ~1GB or more,
+	// resulting in ~hundreds of GBs of writes.
+	//
+	// Since we don't care about executing the Ninja files in these test cases,
+	// generating the Ninja file content wastes time, so skip writing any
+	// information out with --empty_ninja_file.
+	//
+	// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
 	if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {
 		args = append(args, "--empty_ninja_file")
 	}
 
+	// Apply 'local_pool' to to all rules that don't specify a pool.
 	if config.UseRemoteBuild() {
 		args = append(args, "--default_pool=local_pool")
 	}
 
 	cmd := Command(ctx, config, "ckati", executable, args...)
+
+	// Set up the nsjail sandbox.
 	cmd.Sandbox = katiSandbox
+
+	// Set up stdout and stderr.
 	pipe, err := cmd.StdoutPipe()
 	if err != nil {
 		ctx.Fatalln("Error getting output pipe for ckati:", err)
 	}
 	cmd.Stderr = cmd.Stdout
 
+	// Apply the caller's function closure to mutate the environment variables.
 	envFunc(cmd.Environment)
 
+	// Pass on various build environment metadata to Kati.
 	if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {
 		username := "unknown"
 		if u, err := user.Current(); err == nil {
@@ -125,6 +181,8 @@
 	}
 
 	cmd.StartOrFatal()
+	// Set up the ToolStatus command line reader for Kati for a consistent UI
+	// for the user.
 	status.KatiReader(ctx.Status.StartTool(), pipe)
 	cmd.WaitOrFatal()
 }
@@ -134,35 +192,57 @@
 	defer ctx.EndTrace()
 
 	args := []string{
+		// Mark the output directory as writable.
 		"--writable", config.OutDir() + "/",
+		// Fail when encountering implicit rules. e.g.
+		// %.foo: %.bar
+		//   cp $< $@
 		"--werror_implicit_rules",
+		// Entry point for the Kati Ninja file generation.
 		"-f", "build/make/core/main.mk",
 	}
 
 	if !config.BuildBrokenDupRules() {
+		// Fail when redefining / duplicating a target.
 		args = append(args, "--werror_overriding_commands")
 	}
 
 	args = append(args, config.KatiArgs()...)
 
 	args = append(args,
+		// Location of the Make vars .mk file generated by Soong.
 		"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
+		// Location of the Android.mk file generated by Soong. This
+		// file contains Soong modules represented as Kati modules,
+		// allowing Kati modules to depend on Soong modules.
 		"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
+		// Directory containing outputs for the target device.
 		"TARGET_DEVICE_DIR="+config.TargetDeviceDir(),
+		// Directory containing .mk files for packaging purposes, such as
+		// the dist.mk file, containing dist-for-goals data.
 		"KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())
 
 	runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
 
+	// compress and dist the main build ninja file.
 	distGzipFile(ctx, config, config.KatiBuildNinjaFile())
 
+	// Cleanup steps.
 	cleanCopyHeaders(ctx, config)
 	cleanOldInstalledFiles(ctx, config)
 }
 
+// Clean out obsolete header files on the disk that were *not copied* during the
+// build with BUILD_COPY_HEADERS and LOCAL_COPY_HEADERS.
+//
+// These should be increasingly uncommon, as it's a deprecated feature and there
+// isn't an equivalent feature in Soong.
 func cleanCopyHeaders(ctx Context, config Config) {
 	ctx.BeginTrace("clean", "clean copy headers")
 	defer ctx.EndTrace()
 
+	// Read and parse the list of copied headers from a file in the product
+	// output directory.
 	data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list"))
 	if err != nil {
 		if os.IsNotExist(err) {
@@ -178,6 +258,8 @@
 	headerDir := headers[0]
 	headers = headers[1:]
 
+	// Walk the tree and remove any headers that are not in the list of copied
+	// headers in the current build.
 	filepath.Walk(headerDir,
 		func(path string, info os.FileInfo, err error) error {
 			if err != nil {
@@ -196,6 +278,8 @@
 		})
 }
 
+// Clean out any previously installed files from the disk that are not installed
+// in the current build.
 func cleanOldInstalledFiles(ctx Context, config Config) {
 	ctx.BeginTrace("clean", "clean old installed files")
 	defer ctx.EndTrace()
@@ -213,18 +297,27 @@
 	cleanOldFiles(ctx, config.HostOut(), ".installable_test_files")
 }
 
+// Generate the Ninja file containing the packaging command lines for the dist
+// dir.
 func runKatiPackage(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunKati, "kati package")
 	defer ctx.EndTrace()
 
 	args := []string{
+		// Mark the dist dir as writable.
 		"--writable", config.DistDir() + "/",
+		// Fail when encountering implicit rules. e.g.
 		"--werror_implicit_rules",
+		// Fail when redefining / duplicating a target.
 		"--werror_overriding_commands",
+		// Entry point.
 		"-f", "build/make/packaging/main.mk",
+		// Directory containing .mk files for packaging purposes, such as
+		// the dist.mk file, containing dist-for-goals data.
 		"KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(),
 	}
 
+	// Run Kati against a restricted set of environment variables.
 	runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) {
 		env.Allow([]string{
 			// Some generic basics
@@ -251,16 +344,21 @@
 		}
 	})
 
+	// Compress and dist the packaging Ninja file.
 	distGzipFile(ctx, config, config.KatiPackageNinjaFile())
 }
 
+// Run Kati on the cleanspec files to clean the build.
 func runKatiCleanSpec(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunKati, "kati cleanspec")
 	defer ctx.EndTrace()
 
 	runKati(ctx, config, katiCleanspecSuffix, []string{
+		// Fail when encountering implicit rules. e.g.
 		"--werror_implicit_rules",
+		// Fail when redefining / duplicating a target.
 		"--werror_overriding_commands",
+		// Entry point.
 		"-f", "build/make/core/cleanbuild.mk",
 		"SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(),
 		"TARGET_DEVICE_DIR=" + config.TargetDeviceDir(),
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index fa44cb1..ffd1ab9 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -27,10 +27,16 @@
 	"android/soong/ui/status"
 )
 
+// Constructs and runs the Ninja command line with a restricted set of
+// environment variables. It's important to restrict the environment Ninja runs
+// for hermeticity reasons, and to avoid spurious rebuilds.
 func runNinja(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
 	defer ctx.EndTrace()
 
+	// Sets up the FIFO status updater that reads the Ninja protobuf output, and
+	// translates it to the soong_ui status output, displaying real-time
+	// progress of the build.
 	fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
 	nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
 	defer nr.Close()
@@ -64,8 +70,12 @@
 		"-w", "missingdepfile=err")
 
 	cmd := Command(ctx, config, "ninja", executable, args...)
+
+	// Set up the nsjail sandbox Ninja runs in.
 	cmd.Sandbox = ninjaSandbox
 	if config.HasKatiSuffix() {
+		// Reads and executes a shell script from Kati that sets/unsets the
+		// environment Ninja runs in.
 		cmd.Environment.AppendFromKati(config.KatiEnvFile())
 	}
 
@@ -78,8 +88,8 @@
 		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
 	}
 
-	logPath := filepath.Join(config.OutDir(), ".ninja_log")
 	ninjaHeartbeatDuration := time.Minute * 5
+	// Get the ninja heartbeat interval from the environment before it's filtered away later.
 	if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
 		// For example, "1m"
 		overrideDuration, err := time.ParseDuration(overrideText)
@@ -88,18 +98,22 @@
 		}
 	}
 
-	// Filter the environment, as ninja does not rebuild files when environment variables change.
+	// Filter the environment, as ninja does not rebuild files when environment
+	// variables change.
 	//
-	// Anything listed here must not change the output of rules/actions when the value changes,
-	// otherwise incremental builds may be unsafe. Vars explicitly set to stable values
-	// elsewhere in soong_ui are fine.
+	// Anything listed here must not change the output of rules/actions when the
+	// value changes, otherwise incremental builds may be unsafe. Vars
+	// explicitly set to stable values elsewhere in soong_ui are fine.
 	//
-	// For the majority of cases, either Soong or the makefiles should be replicating any
-	// necessary environment variables in the command line of each action that needs it.
+	// For the majority of cases, either Soong or the makefiles should be
+	// replicating any necessary environment variables in the command line of
+	// each action that needs it.
 	if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") {
 		ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.")
 	} else {
 		cmd.Environment.Allow(append([]string{
+			// Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based
+			// tools can symbolize crashes.
 			"ASAN_SYMBOLIZER_PATH",
 			"HOME",
 			"JAVA_HOME",
@@ -108,14 +122,19 @@
 			"OUT_DIR",
 			"PATH",
 			"PWD",
+			// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
 			"PYTHONDONTWRITEBYTECODE",
 			"TMPDIR",
 			"USER",
 
 			// TODO: remove these carefully
+			// Options for the address sanitizer.
 			"ASAN_OPTIONS",
+			// The list of Android app modules to be built in an unbundled manner.
 			"TARGET_BUILD_APPS",
+			// The variant of the product being built. e.g. eng, userdebug, debug.
 			"TARGET_BUILD_VARIANT",
+			// The product name of the product being built, e.g. aosp_arm, aosp_flame.
 			"TARGET_PRODUCT",
 			// b/147197813 - used by art-check-debug-apex-gen
 			"EMMA_INSTRUMENT_FRAMEWORK",
@@ -162,6 +181,7 @@
 	cmd.Environment.Set("DIST_DIR", config.DistDir())
 	cmd.Environment.Set("SHELL", "/bin/bash")
 
+	// Print the environment variables that Ninja is operating in.
 	ctx.Verboseln("Ninja environment: ")
 	envVars := cmd.Environment.Environ()
 	sort.Strings(envVars)
@@ -169,17 +189,21 @@
 		ctx.Verbosef("  %s", envVar)
 	}
 
-	// Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
+	// Poll the Ninja log for updates regularly based on the heartbeat
+	// frequency. If it isn't updated enough, then we want to surface the
+	// possibility that Ninja is stuck, to the user.
 	done := make(chan struct{})
 	defer close(done)
 	ticker := time.NewTicker(ninjaHeartbeatDuration)
 	defer ticker.Stop()
-	checker := &statusChecker{}
+	ninjaChecker := &ninjaStucknessChecker{
+		logPath: filepath.Join(config.OutDir(), ".ninja_log"),
+	}
 	go func() {
 		for {
 			select {
 			case <-ticker.C:
-				checker.check(ctx, config, logPath)
+				ninjaChecker.check(ctx, config)
 			case <-done:
 				return
 			}
@@ -190,37 +214,36 @@
 	cmd.RunAndStreamOrFatal()
 }
 
-type statusChecker struct {
-	prevTime time.Time
+// A simple struct for checking if Ninja gets stuck, using timestamps.
+type ninjaStucknessChecker struct {
+	logPath     string
+	prevModTime time.Time
 }
 
-func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) {
-	info, err := os.Stat(pathToCheck)
-	var newTime time.Time
+// Check that a file has been modified since the last time it was checked. If
+// the mod time hasn't changed, then assume that Ninja got stuck, and print
+// diagnostics for debugging.
+func (c *ninjaStucknessChecker) check(ctx Context, config Config) {
+	info, err := os.Stat(c.logPath)
+	var newModTime time.Time
 	if err == nil {
-		newTime = info.ModTime()
+		newModTime = info.ModTime()
 	}
-	if newTime == c.prevTime {
-		// ninja may be stuck
-		dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime)
+	if newModTime == c.prevModTime {
+		// The Ninja file hasn't been modified since the last time it was
+		// checked, so Ninja could be stuck. Output some diagnostics.
+		ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime)
+
+		// The "pstree" command doesn't exist on Mac, but "pstree" on Linux
+		// gives more convenient output than "ps" So, we try pstree first, and
+		// ps second
+		commandText := fmt.Sprintf("pstree -pal %v || ps -ef", os.Getpid())
+
+		cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
+		output := cmd.CombinedOutputOrFatal()
+		ctx.Verbose(string(output))
+
+		ctx.Verbosef("done\n")
 	}
-	c.prevTime = newTime
-}
-
-// dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics
-func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) {
-
-	ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated)
-
-	// The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps"
-	// So, we try pstree first, and ps second
-	pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid())
-	psCommandText := "ps -ef"
-	commandText := pstreeCommandText + " || " + psCommandText
-
-	cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
-	output := cmd.CombinedOutputOrFatal()
-	ctx.Verbose(string(output))
-
-	ctx.Verbosef("done\n")
+	c.prevModTime = newModTime
 }
diff --git a/ui/build/path.go b/ui/build/path.go
index 6f5cf78..86e61c0 100644
--- a/ui/build/path.go
+++ b/ui/build/path.go
@@ -29,6 +29,9 @@
 	"android/soong/ui/metrics"
 )
 
+// parsePathDir returns the list of filenames of readable files in a directory.
+// This does not recurse into subdirectories, and does not contain subdirectory
+// names in the list.
 func parsePathDir(dir string) []string {
 	f, err := os.Open(dir)
 	if err != nil {
@@ -54,10 +57,12 @@
 	return ret
 }
 
-// A "lite" version of SetupPath used for dumpvars, or other places that need
-// minimal overhead (but at the expense of logging). If tmpDir is empty, the
-// default TMPDIR is used from config.
+// SetupLitePath is the "lite" version of SetupPath used for dumpvars, or other
+// places that does not need the full logging capabilities of path_interposer,
+// wants the minimal performance overhead, and still get the benefits of $PATH
+// hermeticity.
 func SetupLitePath(ctx Context, config Config, tmpDir string) {
+	// Don't replace the path twice.
 	if config.pathReplaced {
 		return
 	}
@@ -67,6 +72,7 @@
 
 	origPath, _ := config.Environment().Get("PATH")
 
+	// If tmpDir is empty, the default TMPDIR is used from config.
 	if tmpDir == "" {
 		tmpDir, _ = config.Environment().Get("TMPDIR")
 	}
@@ -74,8 +80,10 @@
 	ensureEmptyDirectoriesExist(ctx, myPath)
 
 	os.Setenv("PATH", origPath)
+	// Iterate over the ACL configuration of host tools for this build.
 	for name, pathConfig := range paths.Configuration {
 		if !pathConfig.Symlink {
+			// Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs.
 			continue
 		}
 
@@ -88,6 +96,7 @@
 			continue
 		}
 
+		// Symlink allowed host tools into a directory for hermeticity.
 		err = os.Symlink(origExec, filepath.Join(myPath, name))
 		if err != nil {
 			ctx.Fatalln("Failed to create symlink:", err)
@@ -96,14 +105,26 @@
 
 	myPath, _ = filepath.Abs(myPath)
 
+	// Set up the checked-in prebuilts path directory for the current host OS.
 	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
 	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
 
+	// Set $PATH to be the directories containing the host tool symlinks, and
+	// the prebuilts directory for the current host OS.
 	config.Environment().Set("PATH", myPath)
 	config.pathReplaced = true
 }
 
+// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
+// communicates with the interposer to validate allowed $PATH binaries at
+// runtime, using logs as a medium.
+//
+// This results in hermetic directories in $PATH containing only allowed host
+// tools for the build, and replaces $PATH to contain *only* these directories,
+// and enables an incremental restriction of tools allowed in the $PATH without
+// breaking existing use cases.
 func SetupPath(ctx Context, config Config) {
+	// Don't replace $PATH twice.
 	if config.pathReplaced {
 		return
 	}
@@ -112,9 +133,11 @@
 	defer ctx.EndTrace()
 
 	origPath, _ := config.Environment().Get("PATH")
+	// The directory containing symlinks from binaries in $PATH to the interposer.
 	myPath := filepath.Join(config.OutDir(), ".path")
 	interposer := myPath + "_interposer"
 
+	// Bootstrap the path_interposer Go binary with microfactory.
 	var cfg microfactory.Config
 	cfg.Map("android/soong", "build/soong")
 	cfg.TrimPath, _ = filepath.Abs(".")
@@ -122,15 +145,20 @@
 		ctx.Fatalln("Failed to build path interposer:", err)
 	}
 
+	// Save the original $PATH in a file.
 	if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
 		ctx.Fatalln("Failed to write original path:", err)
 	}
 
+	// Communication with the path interposer works over log entries. Set up the
+	// listener channel for the log entries here.
 	entries, err := paths.LogListener(ctx.Context, interposer+"_log")
 	if err != nil {
 		ctx.Fatalln("Failed to listen for path logs:", err)
 	}
 
+	// Loop over all log entry listener channels to validate usage of only
+	// allowed PATH tools at runtime.
 	go func() {
 		for log := range entries {
 			curPid := os.Getpid()
@@ -140,6 +168,8 @@
 					break
 				}
 			}
+			// Compute the error message along with the process tree, including
+			// parents, for this log line.
 			procPrints := []string{
 				"See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
 			}
@@ -150,6 +180,7 @@
 				}
 			}
 
+			// Validate usage against disallowed or missing PATH tools.
 			config := paths.GetConfig(log.Basename)
 			if config.Error {
 				ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
@@ -165,8 +196,10 @@
 		}
 	}()
 
+	// Create the .path directory.
 	ensureEmptyDirectoriesExist(ctx, myPath)
 
+	// Compute the full list of binaries available in the original $PATH.
 	var execs []string
 	for _, pathEntry := range filepath.SplitList(origPath) {
 		if pathEntry == "" {
@@ -185,8 +218,14 @@
 		ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
 	}
 
+	// Create symlinks from the path_interposer binary to all binaries for each
+	// directory in the original $PATH. This ensures that during the build,
+	// every call to a binary that's expected to be in the $PATH will be
+	// intercepted by the path_interposer binary, and validated with the
+	// LogEntry listener above at build time.
 	for _, name := range execs {
 		if !paths.GetConfig(name).Symlink {
+			// Ignore host tools that shouldn't be symlinked.
 			continue
 		}
 
@@ -200,11 +239,13 @@
 
 	myPath, _ = filepath.Abs(myPath)
 
-	// We put some prebuilts in $PATH, since it's infeasible to add dependencies for all of
-	// them.
+	// We put some prebuilts in $PATH, since it's infeasible to add dependencies
+	// for all of them.
 	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
 	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
 
+	// Replace the $PATH variable with the path_interposer symlinks, and
+	// checked-in prebuilts.
 	config.Environment().Set("PATH", myPath)
 	config.pathReplaced = true
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index b20237c..bb5cbf0 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -133,6 +133,13 @@
 			"-j", strconv.Itoa(config.Parallel()),
 			"--frontend_file", fifo,
 			"-f", filepath.Join(config.SoongOutDir(), file))
+
+		// For Bazel mixed builds.
+		cmd.Environment.Set("BAZEL_PATH", "./tools/bazel")
+		cmd.Environment.Set("BAZEL_HOME", filepath.Join(config.BazelOutDir(), "bazelhome"))
+		cmd.Environment.Set("BAZEL_OUTPUT_BASE", filepath.Join(config.BazelOutDir(), "output"))
+		cmd.Environment.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
+
 		cmd.Environment.Set("SOONG_SANDBOX_SOONG_BUILD", "true")
 		cmd.Sandbox = soongSandbox
 		cmd.RunAndStreamOrFatal()
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index d603586..fc976f6 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -12,6 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// soong_zip is a utility used during the build to create a zip archive by pulling the entries from
+// various sources:
+//  * explicitly specified files
+//  * files whose paths are read from a file
+//  * directories traversed recursively
+// It can optionally change the recorded path of an entry.
+
 package main
 
 import (
diff --git a/zip/zip.go b/zip/zip.go
index e27432c..cb85f5c 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -126,6 +126,7 @@
 	return b
 }
 
+// List reads the file names from the given file and adds them to the source files list.
 func (b *FileArgsBuilder) List(name string) *FileArgsBuilder {
 	if b.err != nil {
 		return b
@@ -150,6 +151,7 @@
 	return b
 }
 
+// RspFile reads the file names from given .rsp file and adds them to the source files list.
 func (b *FileArgsBuilder) RspFile(name string) *FileArgsBuilder {
 	if b.err != nil {
 		return b
@@ -291,7 +293,7 @@
 	return args
 }
 
-func ZipTo(args ZipArgs, w io.Writer) error {
+func zipTo(args ZipArgs, w io.Writer) error {
 	if args.EmulateJar {
 		args.AddDirectoryEntriesToZip = true
 	}
@@ -392,6 +394,7 @@
 	return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SrcJar, args.NumParallelJobs)
 }
 
+// Zip creates an output zip archive from given sources.
 func Zip(args ZipArgs) error {
 	if args.OutputFilePath == "" {
 		return fmt.Errorf("output file path must be nonempty")
@@ -416,7 +419,7 @@
 		out = f
 	}
 
-	err := ZipTo(args, out)
+	err := zipTo(args, out)
 	if err != nil {
 		return err
 	}
@@ -450,7 +453,6 @@
 				RelativeRoot: fa.SourcePrefixToStrip,
 			}
 		}
-
 	}
 	dest = filepath.Join(fa.PathPrefixInZip, dest)
 
@@ -465,10 +467,9 @@
 }
 
 func jarSort(mappings []pathMapping) {
-	less := func(i int, j int) (smaller bool) {
+	sort.SliceStable(mappings, func(i int, j int) bool {
 		return jar.EntryNamesLess(mappings[i].dest, mappings[j].dest)
-	}
-	sort.SliceStable(mappings, less)
+	})
 }
 
 func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar, srcJar bool,
@@ -709,7 +710,7 @@
 	}
 }
 
-func (z *ZipWriter) addManifest(dest string, src string, method uint16) error {
+func (z *ZipWriter) addManifest(dest string, src string, _ uint16) error {
 	if prev, exists := z.createdDirs[dest]; exists {
 		return fmt.Errorf("destination %q is both a directory %q and a file %q", dest, prev, src)
 	}
@@ -963,7 +964,7 @@
 	dir = filepath.Clean(dir)
 
 	// discover any uncreated directories in the path
-	zipDirs := []string{}
+	var zipDirs []string
 	for dir != "" && dir != "." {
 		if _, exists := z.createdDirs[dir]; exists {
 			break
diff --git a/zip/zip_test.go b/zip/zip_test.go
index 302a749..a16e092 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -442,7 +442,7 @@
 			args.Stderr = &bytes.Buffer{}
 
 			buf := &bytes.Buffer{}
-			err := ZipTo(args, buf)
+			err := zipTo(args, buf)
 
 			if (err != nil) != (test.err != nil) {
 				t.Fatalf("want error %v, got %v", test.err, err)
@@ -627,7 +627,7 @@
 	args.Stderr = &bytes.Buffer{}
 
 	buf := &bytes.Buffer{}
-	err := ZipTo(args, buf)
+	err := zipTo(args, buf)
 	if err != nil {
 		t.Fatalf("got error %v", err)
 	}