Merge changes from topic "filter_arch_properties"

* changes:
  Make CreateModule return the newly created module
  Move arch properties to proptools.FilterPropertyStruct
diff --git a/Android.bp b/Android.bp
index afbae17..1dfac87 100644
--- a/Android.bp
+++ b/Android.bp
@@ -172,6 +172,7 @@
         "cc/vndk_prebuilt.go",
         "cc/xom.go",
 
+        "cc/cflag_artifacts.go",
         "cc/cmakelists.go",
         "cc/compdb.go",
         "cc/compiler.go",
@@ -304,7 +305,6 @@
         "java/jdeps_test.go",
         "java/kotlin_test.go",
         "java/plugin_test.go",
-        "java/robolectric_test.go",
         "java/sdk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/android/androidmk.go b/android/androidmk.go
index 124523f..9071347 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -80,12 +80,14 @@
 	footer bytes.Buffer
 
 	ExtraEntries []AndroidMkExtraEntriesFunc
+	ExtraFooters []AndroidMkExtraFootersFunc
 
 	EntryMap   map[string][]string
 	entryOrder []string
 }
 
 type AndroidMkExtraEntriesFunc func(entries *AndroidMkEntries)
+type AndroidMkExtraFootersFunc func(w io.Writer, name, prefix, moduleDir string, entries *AndroidMkEntries)
 
 func (a *AndroidMkEntries) SetString(name, value string) {
 	if _, ok := a.EntryMap[name]; !ok {
@@ -94,6 +96,13 @@
 	a.EntryMap[name] = []string{value}
 }
 
+func (a *AndroidMkEntries) SetPath(name string, path Path) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = []string{path.String()}
+}
+
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
@@ -103,6 +112,17 @@
 	}
 }
 
+func (a *AndroidMkEntries) SetBool(name string, flag bool) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	if flag {
+		a.EntryMap[name] = []string{"true"}
+	} else {
+		a.EntryMap[name] = []string{"false"}
+	}
+}
+
 func (a *AndroidMkEntries) AddStrings(name string, value ...string) {
 	if len(value) == 0 {
 		return
@@ -254,9 +274,21 @@
 
 	// Write to footer.
 	fmt.Fprintln(&a.footer, "include "+a.Include)
+	blueprintDir := filepath.Dir(bpPath)
+	for _, footerFunc := range a.ExtraFooters {
+		footerFunc(&a.footer, name, prefix, blueprintDir, a)
+	}
 }
 
 func (a *AndroidMkEntries) write(w io.Writer) {
+	if a.Disabled {
+		return
+	}
+
+	if !a.OutputFile.Valid() {
+		return
+	}
+
 	w.Write(a.header.Bytes())
 	for _, name := range a.entryOrder {
 		fmt.Fprintln(w, name+" := "+strings.Join(a.EntryMap[name], " "))
@@ -264,6 +296,10 @@
 	w.Write(a.footer.Bytes())
 }
 
+func (a *AndroidMkEntries) FooterLinesForTests() []string {
+	return strings.Split(string(a.footer.Bytes()), "\n")
+}
+
 func AndroidMkSingleton() Singleton {
 	return &androidMkSingleton{}
 }
diff --git a/android/apex.go b/android/apex.go
index 99b13ab..557febf 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -78,12 +78,23 @@
 
 	// Return the no_apex property
 	NoApex() bool
+
+	// Tests if this module is available for the specified APEX or ":platform"
+	AvailableFor(what string) bool
 }
 
 type ApexProperties struct {
 	// Whether this module should not be part of any APEX. Default is false.
+	// TODO(b/128708192): remove this as this is equal to apex_available: [":platform"]
 	No_apex *bool
 
+	// Availability of this module in APEXes. Only the listed APEXes can include this module.
+	// "//apex_available:anyapex" is a pseudo APEX name that matches to any APEX.
+	// "//apex_available:platform" refers to non-APEX partitions like "system.img".
+	// Default is ["//apex_available:platform", "//apex_available:anyapex"].
+	// TODO(b/128708192) change the default to ["//apex_available:platform"]
+	Apex_available []string
+
 	// Name of the apex variant that this module is mutated into
 	ApexName string `blueprint:"mutated"`
 }
@@ -136,15 +147,46 @@
 	return proptools.Bool(m.ApexProperties.No_apex)
 }
 
+const (
+	availableToPlatform = "//apex_available:platform"
+	availableToAnyApex  = "//apex_available:anyapex"
+)
+
+func (m *ApexModuleBase) AvailableFor(what string) bool {
+	if len(m.ApexProperties.Apex_available) == 0 {
+		// apex_available defaults to ["//apex_available:platform", "//apex_available:anyapex"],
+		// which means 'available to everybody'.
+		return true
+	}
+	return InList(what, m.ApexProperties.Apex_available) ||
+		(what != availableToPlatform && InList(availableToAnyApex, m.ApexProperties.Apex_available))
+}
+
+func (m *ApexModuleBase) checkApexAvailableProperty(mctx BaseModuleContext) {
+	for _, n := range m.ApexProperties.Apex_available {
+		if n == availableToPlatform || n == availableToAnyApex {
+			continue
+		}
+		if !mctx.OtherModuleExists(n) {
+			mctx.PropertyErrorf("apex_available", "%q is not a valid module name", n)
+		}
+	}
+}
+
 func (m *ApexModuleBase) CreateApexVariations(mctx BottomUpMutatorContext) []blueprint.Module {
 	if len(m.apexVariations) > 0 {
+		m.checkApexAvailableProperty(mctx)
 		sort.Strings(m.apexVariations)
-		variations := []string{""} // Original variation for platform
+		variations := []string{}
+		availableForPlatform := m.AvailableFor(availableToPlatform)
+		if availableForPlatform {
+			variations = append(variations, "") // Original variation for platform
+		}
 		variations = append(variations, m.apexVariations...)
 
 		modules := mctx.CreateVariations(variations...)
 		for i, m := range modules {
-			if i == 0 {
+			if availableForPlatform && i == 0 {
 				continue
 			}
 			m.(ApexModule).setApexName(variations[i])
diff --git a/android/config.go b/android/config.go
index cb1bdf5..d03d38e 100644
--- a/android/config.go
+++ b/android/config.go
@@ -800,7 +800,7 @@
 func (c *config) EnforceRROForModule(name string) bool {
 	enforceList := c.productVariables.EnforceRROTargets
 	if enforceList != nil {
-		if len(enforceList) == 1 && (enforceList)[0] == "*" {
+		if InList("*", enforceList) {
 			return true
 		}
 		return InList(name, enforceList)
diff --git a/android/module.go b/android/module.go
index 8076a99..a1a01a5 100644
--- a/android/module.go
+++ b/android/module.go
@@ -154,6 +154,7 @@
 	CheckbuildFile(srcPath Path)
 
 	InstallInData() bool
+	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
 	InstallInRecovery() bool
 	InstallBypassMake() bool
@@ -192,6 +193,7 @@
 	Enabled() bool
 	Target() Target
 	InstallInData() bool
+	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
 	InstallInRecovery() bool
 	InstallBypassMake() bool
@@ -832,6 +834,10 @@
 	return false
 }
 
+func (m *ModuleBase) InstallInTestcases() bool {
+	return false
+}
+
 func (m *ModuleBase) InstallInSanitizerDir() bool {
 	return false
 }
@@ -1504,6 +1510,10 @@
 	return m.module.InstallInData()
 }
 
+func (m *moduleContext) InstallInTestcases() bool {
+	return m.module.InstallInTestcases()
+}
+
 func (m *moduleContext) InstallInSanitizerDir() bool {
 	return m.module.InstallInSanitizerDir()
 }
diff --git a/android/paths.go b/android/paths.go
index 0d64a61..8bd2c61 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -44,6 +44,7 @@
 	BaseModuleContext
 
 	InstallInData() bool
+	InstallInTestcases() bool
 	InstallInSanitizerDir() bool
 	InstallInRecovery() bool
 	InstallBypassMake() bool
@@ -1155,6 +1156,8 @@
 	var partition string
 	if ctx.InstallInData() {
 		partition = "data"
+	} else if ctx.InstallInTestcases() {
+		partition = "testcases"
 	} else if ctx.InstallInRecovery() {
 		// the layout of recovery partion is the same as that of system partition
 		partition = "recovery/root/system"
diff --git a/android/paths_test.go b/android/paths_test.go
index f2996bf..b66eb1e 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -201,6 +201,7 @@
 	baseModuleContext
 
 	inData         bool
+	inTestcases    bool
 	inSanitizerDir bool
 	inRecovery     bool
 }
@@ -219,6 +220,10 @@
 	return m.inData
 }
 
+func (m moduleInstallPathContextImpl) InstallInTestcases() bool {
+	return m.inTestcases
+}
+
 func (m moduleInstallPathContextImpl) InstallInSanitizerDir() bool {
 	return m.inSanitizerDir
 }
diff --git a/android/util.go b/android/util.go
index 0102442..71ded5e 100644
--- a/android/util.go
+++ b/android/util.go
@@ -319,3 +319,36 @@
 
 	return root, suffix, ext
 }
+
+// ShardPaths takes a Paths, and returns a slice of Paths where each one has at most shardSize paths.
+func ShardPaths(paths Paths, shardSize int) []Paths {
+	if len(paths) == 0 {
+		return nil
+	}
+	ret := make([]Paths, 0, (len(paths)+shardSize-1)/shardSize)
+	for len(paths) > shardSize {
+		ret = append(ret, paths[0:shardSize])
+		paths = paths[shardSize:]
+	}
+	if len(paths) > 0 {
+		ret = append(ret, paths)
+	}
+	return ret
+}
+
+// ShardStrings takes a slice of strings, and returns a slice of slices of strings where each one has at most shardSize
+// elements.
+func ShardStrings(s []string, shardSize int) [][]string {
+	if len(s) == 0 {
+		return nil
+	}
+	ret := make([][]string, 0, (len(s)+shardSize-1)/shardSize)
+	for len(s) > shardSize {
+		ret = append(ret, s[0:shardSize])
+		s = s[shardSize:]
+	}
+	if len(s) > 0 {
+		ret = append(ret, s)
+	}
+	return ret
+}
diff --git a/android/util_test.go b/android/util_test.go
index 1df1c5a..90fefee 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -469,3 +469,102 @@
 		}
 	})
 }
+
+func Test_Shard(t *testing.T) {
+	type args struct {
+		strings   []string
+		shardSize int
+	}
+	tests := []struct {
+		name string
+		args args
+		want [][]string
+	}{
+		{
+			name: "empty",
+			args: args{
+				strings:   nil,
+				shardSize: 1,
+			},
+			want: [][]string(nil),
+		},
+		{
+			name: "single shard",
+			args: args{
+				strings:   []string{"a", "b"},
+				shardSize: 2,
+			},
+			want: [][]string{{"a", "b"}},
+		},
+		{
+			name: "single short shard",
+			args: args{
+				strings:   []string{"a", "b"},
+				shardSize: 3,
+			},
+			want: [][]string{{"a", "b"}},
+		},
+		{
+			name: "shard per input",
+			args: args{
+				strings:   []string{"a", "b", "c"},
+				shardSize: 1,
+			},
+			want: [][]string{{"a"}, {"b"}, {"c"}},
+		},
+		{
+			name: "balanced shards",
+			args: args{
+				strings:   []string{"a", "b", "c", "d"},
+				shardSize: 2,
+			},
+			want: [][]string{{"a", "b"}, {"c", "d"}},
+		},
+		{
+			name: "unbalanced shards",
+			args: args{
+				strings:   []string{"a", "b", "c"},
+				shardSize: 2,
+			},
+			want: [][]string{{"a", "b"}, {"c"}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Run("strings", func(t *testing.T) {
+				if got := ShardStrings(tt.args.strings, tt.args.shardSize); !reflect.DeepEqual(got, tt.want) {
+					t.Errorf("ShardStrings(%v, %v) = %v, want %v",
+						tt.args.strings, tt.args.shardSize, got, tt.want)
+				}
+			})
+
+			t.Run("paths", func(t *testing.T) {
+				stringsToPaths := func(strings []string) Paths {
+					if strings == nil {
+						return nil
+					}
+					paths := make(Paths, len(strings))
+					for i, s := range strings {
+						paths[i] = PathForTesting(s)
+					}
+					return paths
+				}
+
+				paths := stringsToPaths(tt.args.strings)
+
+				var want []Paths
+				if sWant := tt.want; sWant != nil {
+					want = make([]Paths, len(sWant))
+					for i, w := range sWant {
+						want[i] = stringsToPaths(w)
+					}
+				}
+
+				if got := ShardPaths(paths, tt.args.shardSize); !reflect.DeepEqual(got, want) {
+					t.Errorf("ShardPaths(%v, %v) = %v, want %v",
+						paths, tt.args.shardSize, got, want)
+				}
+			})
+		})
+	}
+}
diff --git a/apex/apex.go b/apex/apex.go
index 4e72d09..aff8d50 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -47,13 +47,15 @@
 		Description: "fs_config ${out}",
 	}, "ro_paths", "exec_paths")
 
-	injectApexDependency = pctx.StaticRule("injectApexDependency", blueprint.RuleParams{
+	apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{
 		Command: `rm -f $out && ${jsonmodify} $in ` +
 			`-a provideNativeLibs ${provideNativeLibs} ` +
-			`-a requireNativeLibs ${requireNativeLibs} -o $out`,
+			`-a requireNativeLibs ${requireNativeLibs} ` +
+			`${opt} ` +
+			`-o $out`,
 		CommandDeps: []string{"${jsonmodify}"},
-		Description: "Inject dependency into ${out}",
-	}, "provideNativeLibs", "requireNativeLibs")
+		Description: "prepare ${out}",
+	}, "provideNativeLibs", "requireNativeLibs", "opt")
 
 	// TODO(b/113233103): make sure that file_contexts is sane, i.e., validate
 	// against the binary policy using sefcontext_compiler -p <policy>.
@@ -150,7 +152,6 @@
 var (
 	whitelistNoApex = map[string][]string{
 		"apex_test_build_features":       []string{"libbinder"},
-		"com.android.media":              []string{"libbinder"},
 		"com.android.media.swcodec":      []string{"libbinder"},
 		"test_com.android.media.swcodec": []string{"libbinder"},
 		"com.android.vndk":               []string{"libbinder"},
@@ -1214,6 +1215,16 @@
 		}
 	}
 
+	// check apex_available requirements
+	for _, fi := range filesInfo {
+		if am, ok := fi.module.(android.ApexModule); ok {
+			if !am.AvailableFor(ctx.ModuleName()) {
+				ctx.ModuleErrorf("requires %q that is not available for the APEX", fi.module.Name())
+				return
+			}
+		}
+	}
+
 	// prepend the name of this APEX to the module names. These names will be the names of
 	// modules that will be defined if the APEX is flattened.
 	for i := range filesInfo {
@@ -1223,18 +1234,28 @@
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = filesInfo
 
+	// prepare apex_manifest.json
 	a.manifestOut = android.PathForModuleOut(ctx, "apex_manifest.json")
-	// put dependency({provide|require}NativeLibs) in apex_manifest.json
 	manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "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
+	optCommands := []string{}
+	if a.properties.Apex_name != nil {
+		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
+	}
+
 	ctx.Build(pctx, android.BuildParams{
-		Rule:   injectApexDependency,
+		Rule:   apexManifestRule,
 		Input:  manifestSrc,
 		Output: a.manifestOut,
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
 			"requireNativeLibs": strings.Join(requireNativeLibs, " "),
+			"opt":               strings.Join(optCommands, " "),
 		},
 	})
 
@@ -1448,6 +1469,12 @@
 			optFlags = append(optFlags, "--no_hashtree")
 		}
 
+		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.
+			optFlags = append(optFlags, "--do_not_check_keyname")
+		}
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        apexRule,
 			Implicits:   implicitInputs,
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 91c6426..ecfa46f 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -771,9 +771,9 @@
 	// Ensure that runtime_libs dep in included
 	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
 
-	injectRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("injectApexDependency")
-	ensureListEmpty(t, names(injectRule.Args["provideNativeLibs"]))
-	ensureListContains(t, names(injectRule.Args["requireNativeLibs"]), "libfoo.so")
+	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.so")
 
 }
 
@@ -821,11 +821,11 @@
 	// Ensure that LLNDK dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libbar.so")
 
-	injectRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("injectApexDependency")
-	ensureListEmpty(t, names(injectRule.Args["provideNativeLibs"]))
+	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 
 	// Ensure that LLNDK dep is required
-	ensureListContains(t, names(injectRule.Args["requireNativeLibs"]), "libbar.so")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libbar.so")
 
 }
 
@@ -1582,34 +1582,56 @@
 		}
 	`)
 
-	var injectRule android.TestingBuildParams
+	var apexManifestRule android.TestingBuildParams
 	var provideNativeLibs, requireNativeLibs []string
 
-	injectRule = ctx.ModuleForTests("myapex_nodep", "android_common_myapex_nodep").Rule("injectApexDependency")
-	provideNativeLibs = names(injectRule.Args["provideNativeLibs"])
-	requireNativeLibs = names(injectRule.Args["requireNativeLibs"])
+	apexManifestRule = ctx.ModuleForTests("myapex_nodep", "android_common_myapex_nodep").Rule("apexManifestRule")
+	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
+	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListEmpty(t, provideNativeLibs)
 	ensureListEmpty(t, requireNativeLibs)
 
-	injectRule = ctx.ModuleForTests("myapex_dep", "android_common_myapex_dep").Rule("injectApexDependency")
-	provideNativeLibs = names(injectRule.Args["provideNativeLibs"])
-	requireNativeLibs = names(injectRule.Args["requireNativeLibs"])
+	apexManifestRule = ctx.ModuleForTests("myapex_dep", "android_common_myapex_dep").Rule("apexManifestRule")
+	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
+	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListEmpty(t, provideNativeLibs)
 	ensureListContains(t, requireNativeLibs, "libfoo.so")
 
-	injectRule = ctx.ModuleForTests("myapex_provider", "android_common_myapex_provider").Rule("injectApexDependency")
-	provideNativeLibs = names(injectRule.Args["provideNativeLibs"])
-	requireNativeLibs = names(injectRule.Args["requireNativeLibs"])
+	apexManifestRule = ctx.ModuleForTests("myapex_provider", "android_common_myapex_provider").Rule("apexManifestRule")
+	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
+	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListContains(t, provideNativeLibs, "libfoo.so")
 	ensureListEmpty(t, requireNativeLibs)
 
-	injectRule = ctx.ModuleForTests("myapex_selfcontained", "android_common_myapex_selfcontained").Rule("injectApexDependency")
-	provideNativeLibs = names(injectRule.Args["provideNativeLibs"])
-	requireNativeLibs = names(injectRule.Args["requireNativeLibs"])
+	apexManifestRule = ctx.ModuleForTests("myapex_selfcontained", "android_common_myapex_selfcontained").Rule("apexManifestRule")
+	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
+	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListContains(t, provideNativeLibs, "libfoo.so")
 	ensureListEmpty(t, requireNativeLibs)
 }
 
+func TestApexName(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apex_name: "com.android.myapex",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	apexManifestRule := module.Rule("apexManifestRule")
+	ensureContains(t, apexManifestRule.Args["opt"], "-v name com.android.myapex")
+	apexRule := module.Rule("apexRule")
+	ensureContains(t, apexRule.Args["opt_flags"], "--do_not_check_keyname")
+}
+
 func TestNonTestApex(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
@@ -2400,6 +2422,158 @@
 
 }
 
+func TestApexAvailable(t *testing.T) {
+	// libfoo is not available to myapex, but only to otherapex
+	testApexError(t, "requires \"libfoo\" that is not available for the APEX", `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		native_shared_libs: ["libfoo"],
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	apex {
+		name: "otherapex",
+		key: "otherapex.key",
+		native_shared_libs: ["libfoo"],
+	}
+
+	apex_key {
+		name: "otherapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "libfoo",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["otherapex"],
+	}`)
+
+	// libbar is an indirect dep
+	testApexError(t, "requires \"libbar\" that is not available for the APEX", `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		native_shared_libs: ["libfoo"],
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	apex {
+		name: "otherapex",
+		key: "otherapex.key",
+		native_shared_libs: ["libfoo"],
+	}
+
+	apex_key {
+		name: "otherapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "libfoo",
+		stl: "none",
+		shared_libs: ["libbar"],
+		system_shared_libs: [],
+		apex_available: ["myapex", "otherapex"],
+	}
+
+	cc_library {
+		name: "libbar",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["otherapex"],
+	}`)
+
+	testApexError(t, "\"otherapex\" is not a valid module name", `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		native_shared_libs: ["libfoo"],
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "libfoo",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["otherapex"],
+	}`)
+
+	ctx, _ := testApex(t, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		native_shared_libs: ["libfoo", "libbar"],
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "libfoo",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["myapex"],
+	}
+
+	cc_library {
+		name: "libbar",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["//apex_available:anyapex"],
+	}`)
+
+	// check that libfoo and libbar are created only for myapex, but not for the platform
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo"), "android_arm64_armv8-a_core_shared_myapex")
+	ensureListNotContains(t, ctx.ModuleVariantsForTests("libfoo"), "android_arm64_armv8-a_core_shared")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_core_shared_myapex")
+	ensureListNotContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_core_shared")
+
+	ctx, _ = testApex(t, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	cc_library {
+		name: "libfoo",
+		stl: "none",
+		system_shared_libs: [],
+		apex_available: ["//apex_available:platform"],
+	}`)
+
+	// check that libfoo is created only for the platform
+	ensureListNotContains(t, ctx.ModuleVariantsForTests("libfoo"), "android_arm64_armv8-a_core_shared_myapex")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo"), "android_arm64_armv8-a_core_shared")
+}
+
 func TestMain(m *testing.M) {
 	run := func() int {
 		setUp()
diff --git a/build_kzip.bash b/build_kzip.bash
index c1d09f7..1e0d48f 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -1,32 +1,27 @@
-# /bin/bash -uv
+#! /bin/bash -uv
 #
 # Build kzip files (source files for the indexing pipeline) for the given configuration,
 # merge them and place the resulting all.kzip into $DIST_DIR.
 # It is assumed that the current directory is the top of the source tree.
 # The following environment variables affect the result:
-#   TARGET_PRODUCT        target device name, e.g., 'aosp_blueline'
-#   TARGET_BUILD_VARIANT  variant, e.g., `userdebug`
-#   OUT_DIR               absolute path  where the build is happening ($PWD/out if not specified})
-#   DIST_DIR              where the resulting all.kzip will be placed
-#   XREF_CORPUS           source code repository URI, e.g., 'android.googlesource.com/platform/superproject'
 #   BUILD_NUMBER          build number, used to generate unique ID (will use UUID if not set)
+#   DIST_DIR              where the resulting all.kzip will be placed
+#   OUT_DIR               output directory (out if not specified})
+#   TARGET_BUILD_VARIANT  variant, e.g., `userdebug`
+#   TARGET_PRODUCT        target device name, e.g., 'aosp_blueline'
+#   XREF_CORPUS           source code repository URI, e.g., 'android.googlesource.com/platform/superproject'
 
-# If OUT_DIR is not set, the build will use out/ as output directory, which is
-# a relative path. Make it absolute, otherwise the indexer will not know that it
-# contains only generated files.
-: ${OUT_DIR:=$PWD/out}
-[[ "$OUT_DIR" =~ ^/ ]] || { echo "$OUT_DIR is not an absolute path"; exit 1; }
+: ${BUILD_NUMBER:=$(uuidgen)}
 
-# The extraction might fail for some source files, so run with -k
-OUT_DIR=$OUT_DIR build/soong/soong_ui.bash --build-mode --all-modules --dir=$PWD -k merge_zips xref_cxx xref_java
-
-# We build with -k, so check that we have generated at least 100K files
-# (the actual number is 180K+)
-declare -r kzip_count=$(find $OUT_DIR -name '*.kzip' | wc -l)
+# The extraction might fail for some source files, so run with -k and then check that
+# sufficiently many files were generated.
+build/soong/soong_ui.bash --build-mode --all-modules --dir=$PWD -k merge_zips xref_cxx xref_java
+declare -r out="${OUT_DIR:-out}"
+declare -r kzip_count=$(find "$out" -name '*.kzip' | wc -l)
 (($kzip_count>100000)) || { printf "Too few kzip files were generated: %d\n" $kzip_count; exit 1; }
 
 # Pack
 # TODO(asmundak): this should be done by soong.
-declare -r allkzip=all.kzip
-"$OUT_DIR/soong/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find $OUT_DIR -name '*.kzip')
-echo "${BUILD_NUMBER:-$(uuidgen)}" >"$DIST_DIR/revision.txt"
+declare -r allkzip="$BUILD_NUMBER.kzip"
+"$out/soong/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
+
diff --git a/cc/builder.go b/cc/builder.go
index b353814..c4f65da 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -224,12 +224,13 @@
 
 	_ = pctx.SourcePathVariable("cxxExtractor",
 		"prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/cxx_extractor")
+	_ = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json")
 	_ = pctx.VariableFunc("kytheCorpus",
 		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() })
 	kytheExtract = pctx.StaticRule("kythe",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && KYTHE_CORPUS=${kytheCorpus} KYTHE_OUTPUT_FILE=$out $cxxExtractor $cFlags $in ",
-			CommandDeps: []string{"$cxxExtractor"},
+			Command:     "rm -f $out && KYTHE_CORPUS=${kytheCorpus} KYTHE_OUTPUT_FILE=$out KYTHE_VNAMES=$kytheVnames $cxxExtractor $cFlags $in ",
+			CommandDeps: []string{"$cxxExtractor", "$kytheVnames"},
 		},
 		"cFlags")
 )
diff --git a/cc/cflag_artifacts.go b/cc/cflag_artifacts.go
new file mode 100644
index 0000000..9ed3876
--- /dev/null
+++ b/cc/cflag_artifacts.go
@@ -0,0 +1,188 @@
+package cc
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterSingletonType("cflag_artifacts_text", cflagArtifactsTextFactory)
+}
+
+var (
+	TrackedCFlags = []string{
+		"-Wall",
+		"-Werror",
+		"-Wextra",
+		"-Wthread-safety",
+		"-O3",
+	}
+
+	TrackedCFlagsDir = []string{
+		"device/google/",
+		"vendor/google/",
+	}
+)
+
+const FileBP = 50
+
+// Stores output files.
+type cflagArtifactsText struct {
+	interOutputs map[string]android.WritablePaths
+	outputs      android.WritablePaths
+}
+
+// allowedDir verifies if the directory/project is part of the TrackedCFlagsDir
+// filter.
+func allowedDir(subdir string) bool {
+	subdir += "/"
+	for _, prefix := range TrackedCFlagsDir {
+		if strings.HasPrefix(subdir, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
+func (s *cflagArtifactsText) genFlagFilename(flag string) string {
+	return fmt.Sprintf("module_cflags%s.txt", flag)
+}
+
+// incrementFile is used to generate an output path object with the passed in flag
+// and part number.
+// e.g. FLAG + part # -> out/soong/cflags/module_cflags-FLAG.txt.0
+func (s *cflagArtifactsText) incrementFile(ctx android.SingletonContext,
+	flag string, part int) (string, android.OutputPath) {
+
+	filename := fmt.Sprintf("%s.%d", s.genFlagFilename(flag), part)
+	filepath := android.PathForOutput(ctx, "cflags", filename)
+	s.interOutputs[flag] = append(s.interOutputs[flag], filepath)
+	return filename, filepath
+}
+
+// GenCFlagArtifactParts is used to generate the build rules which produce the
+// intermediary files for each desired C Flag artifact
+// e.g. module_cflags-FLAG.txt.0, module_cflags-FLAG.txt.1, ...
+func (s *cflagArtifactsText) GenCFlagArtifactParts(ctx android.SingletonContext,
+	flag string, using bool, modules []string, part int) int {
+
+	cleanedName := strings.Replace(flag, "=", "_", -1)
+	filename, filepath := s.incrementFile(ctx, cleanedName, part)
+	rule := android.NewRuleBuilder()
+	rule.Command().Textf("rm -f %s", filepath.String())
+
+	if using {
+		rule.Command().
+			Textf("echo '# Modules using %s'", flag).
+			FlagWithOutput(">> ", filepath)
+	} else {
+		rule.Command().
+			Textf("echo '# Modules not using %s'", flag).
+			FlagWithOutput(">> ", filepath)
+	}
+
+	length := len(modules)
+
+	if length == 0 {
+		rule.Build(pctx, ctx, filename, "gen "+filename)
+		part++
+	}
+
+	// Following loop splits the module list for each tracked C Flag into
+	// chunks of length FileBP (file breakpoint) and generates a partial artifact
+	// (intermediary file) build rule for each split.
+	moduleShards := android.ShardStrings(modules, FileBP)
+	for index, shard := range moduleShards {
+		rule.Command().
+			Textf("for m in %s; do echo $m",
+				strings.Join(proptools.ShellEscapeList(shard), " ")).
+			FlagWithOutput(">> ", filepath).
+			Text("; done")
+		rule.Build(pctx, ctx, filename, "gen "+filename)
+
+		if index+1 != len(moduleShards) {
+			filename, filepath = s.incrementFile(ctx, cleanedName, part+index+1)
+			rule = android.NewRuleBuilder()
+			rule.Command().Textf("rm -f %s", filepath.String())
+		}
+	}
+
+	return part + len(moduleShards)
+}
+
+// GenCFlagArtifacts is used to generate build rules which combine the
+// intermediary files of a specific tracked flag into a single C Flag artifact
+// for each tracked flag.
+// e.g. module_cflags-FLAG.txt.0 + module_cflags-FLAG.txt.1 = module_cflags-FLAG.txt
+func (s *cflagArtifactsText) GenCFlagArtifacts(ctx android.SingletonContext) {
+	// Scans through s.interOutputs and creates a build rule for each tracked C
+	// Flag that concatenates the associated intermediary file into a single
+	// artifact.
+	for _, flag := range TrackedCFlags {
+		// Generate build rule to combine related intermediary files into a
+		// C Flag artifact
+		rule := android.NewRuleBuilder()
+		filename := s.genFlagFilename(flag)
+		outputpath := android.PathForOutput(ctx, "cflags", filename)
+		rule.Command().
+			Text("cat").
+			Inputs(s.interOutputs[flag].Paths()).
+			FlagWithOutput("> ", outputpath)
+		rule.Build(pctx, ctx, filename, "gen "+filename)
+		s.outputs = append(s.outputs, outputpath)
+	}
+}
+
+func (s *cflagArtifactsText) GenerateBuildActions(ctx android.SingletonContext) {
+	modulesWithCFlag := make(map[string][]string)
+
+	// Scan through all modules, selecting the ones that are part of the filter,
+	// and then storing into a map which tracks whether or not tracked C flag is
+	// used or not.
+	ctx.VisitAllModules(func(module android.Module) {
+		if ccModule, ok := module.(*Module); ok {
+			if allowedDir(ctx.ModuleDir(ccModule)) {
+				cflags := ccModule.flags.CFlags
+				cppflags := ccModule.flags.CppFlags
+				module := fmt.Sprintf("%s:%s (%s)",
+					ctx.BlueprintFile(ccModule),
+					ctx.ModuleName(ccModule),
+					ctx.ModuleSubDir(ccModule))
+				for _, flag := range TrackedCFlags {
+					if inList(flag, cflags) || inList(flag, cppflags) {
+						modulesWithCFlag[flag] = append(modulesWithCFlag[flag], module)
+					} else {
+						modulesWithCFlag["!"+flag] = append(modulesWithCFlag["!"+flag], module)
+					}
+				}
+			}
+		}
+	})
+
+	// Traversing map and setting up rules to produce intermediary files which
+	// contain parts of each expected C Flag artifact.
+	for _, flag := range TrackedCFlags {
+		sort.Strings(modulesWithCFlag[flag])
+		part := s.GenCFlagArtifactParts(ctx, flag, true, modulesWithCFlag[flag], 0)
+		sort.Strings(modulesWithCFlag["!"+flag])
+		s.GenCFlagArtifactParts(ctx, flag, false, modulesWithCFlag["!"+flag], part)
+	}
+
+	// Combine intermediary files into a single C Flag artifact.
+	s.GenCFlagArtifacts(ctx)
+}
+
+func cflagArtifactsTextFactory() android.Singleton {
+	return &cflagArtifactsText{
+		interOutputs: make(map[string]android.WritablePaths),
+	}
+}
+
+func (s *cflagArtifactsText) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("SOONG_MODULES_CFLAG_ARTIFACTS", strings.Join(s.outputs.Strings(), " "))
+}
diff --git a/cc/config/clang.go b/cc/config/clang.go
index e59aa37..71bea42 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -163,10 +163,6 @@
 		// new warnings are fixed.
 		"-Wno-tautological-constant-compare",
 		"-Wno-tautological-type-limit-compare",
-
-		// Disable c++98-specific warning since Android is not concerned with C++98
-		// compatibility.
-		"-Wno-c++98-compat-extra-semi",
 	}, " "))
 
 	// Extra cflags for projects under external/ directory to disable warnings that are infeasible
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 7806e83..c19fdc5 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -16,8 +16,8 @@
 
 import (
 	"path/filepath"
+	"strings"
 
-	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -161,6 +161,7 @@
 // Responsible for generating GNU Make rules that package fuzz targets into
 // their architecture & target/host specific zip file.
 type fuzzPackager struct {
+	packages android.Paths
 }
 
 func fuzzPackagingFactory() android.Singleton {
@@ -220,15 +221,12 @@
 		}
 	})
 
-	// List of architecture + host/device specific packages to build via. 'make fuzz'.
-	var archDirTargets android.Paths
-
 	for archDir, filesToZip := range archDirs {
 		arch := archDir.Base()
 		hostOrTarget := filepath.Base(filepath.Dir(archDir.String()))
 		builder := android.NewRuleBuilder()
 		outputFile := android.PathForOutput(ctx, "fuzz-"+hostOrTarget+"-"+arch+".zip")
-		archDirTargets = append(archDirTargets, outputFile)
+		s.packages = append(s.packages, outputFile)
 
 		command := builder.Command().BuiltTool(ctx, "soong_zip").
 			Flag("-j").
@@ -242,11 +240,12 @@
 		builder.Build(pctx, ctx, "create-fuzz-package-"+arch+"-"+hostOrTarget,
 			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
 	}
+}
 
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        blueprint.Phony,
-		Output:      android.PathForPhony(ctx, "fuzz"),
-		Implicits:   archDirTargets,
-		Description: "Build all Android fuzz targets, and create packages.",
-	})
+func (s *fuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+	// TODO(mitchp): Migrate this to use MakeVarsContext::DistForGoal() when it's
+	// ready to handle phony targets created in Soong. In the meantime, this
+	// exports the phony 'fuzz' target and dependencies on packages to
+	// core/main.mk so that we can use dist-for-goals.
+	ctx.Strict("SOONG_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(s.packages.Strings(), " "))
 }
diff --git a/cc/library.go b/cc/library.go
index c402ea0..0f33611 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1333,6 +1333,7 @@
 		rule := android.NewRuleBuilder()
 		rule.Command().
 			BuiltTool(ctx, "bssl_inject_hash").
+			Flag("-sha256").
 			FlagWithInput("-in-object ", outputFile).
 			FlagWithOutput("-o ", hashedOutputfile)
 		rule.Build(pctx, ctx, "injectCryptoHash", "inject crypto hash")
diff --git a/cc/object.go b/cc/object.go
index f619c79..1f1ac8e 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -52,8 +52,9 @@
 // input to a cc_genrule module.
 func ObjectFactory() android.Module {
 	module := newBaseModule(android.HostAndDeviceSupported, android.MultilibBoth)
+	module.sanitize = &sanitize{}
 	module.linker = &objectLinker{
-		baseLinker: NewBaseLinker(nil),
+		baseLinker: NewBaseLinker(module.sanitize),
 	}
 	module.compiler = NewBaseCompiler()
 
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 415518c..c0a7c63 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -674,7 +674,7 @@
 
 func isSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
 	t, ok := tag.(dependencyTag)
-	return ok && t.library || t == reuseObjTag
+	return ok && t.library || t == reuseObjTag || t == objDepTag
 }
 
 // Propagate sanitizer requirements down from binaries
diff --git a/cc/test.go b/cc/test.go
index ba0b7e4..0e66e28 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -80,6 +80,11 @@
 	// Add MinApiLevelModuleController to auto generated test config. If the device property of
 	// "ro.build.version.sdk" < Test_min_sdk_version, then skip this module.
 	Test_min_sdk_version *int64
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 func init() {
@@ -362,7 +367,7 @@
 	}
 
 	test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
-		test.Properties.Test_config_template, test.Properties.Test_suites, configs)
+		test.Properties.Test_config_template, test.Properties.Test_suites, configs, test.Properties.Auto_gen_config)
 
 	test.binaryDecorator.baseInstaller.dir = "nativetest"
 	test.binaryDecorator.baseInstaller.dir64 = "nativetest64"
@@ -453,6 +458,11 @@
 	// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
 	// with root permission.
 	Require_root *bool
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type benchmarkDecorator struct {
@@ -490,7 +500,7 @@
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
 	}
 	benchmark.testConfig = tradefed.AutoGenNativeBenchmarkTestConfig(ctx, benchmark.Properties.Test_config,
-		benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs)
+		benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs, benchmark.Properties.Auto_gen_config)
 
 	benchmark.binaryDecorator.baseInstaller.dir = filepath.Join("benchmarktest", ctx.ModuleName())
 	benchmark.binaryDecorator.baseInstaller.dir64 = filepath.Join("benchmarktest64", ctx.ModuleName())
diff --git a/java/aapt2.go b/java/aapt2.go
index f0eb99c..cfe0dea 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -63,7 +63,7 @@
 func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
 	flags []string) android.WritablePaths {
 
-	shards := shardPaths(paths, AAPT2_SHARD_SIZE)
+	shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
 
 	ret := make(android.WritablePaths, 0, len(paths))
 
diff --git a/java/androidmk.go b/java/androidmk.go
index f006705..bc61297 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -22,7 +22,9 @@
 	"android/soong/android"
 )
 
-func (library *Library) AndroidMkHostDex(w io.Writer, name string, data android.AndroidMkData) {
+// TODO(jungjw): We'll probably want AndroidMkEntriesProvider.AndroidMkEntries to return multiple
+// entries so that this can be more error-proof.
+func (library *Library) AndroidMkHostDex(w io.Writer, name string, entries *android.AndroidMkEntries) {
 	if Bool(library.deviceProperties.Hostdex) && !library.Host() {
 		fmt.Fprintln(w, "include $(CLEAR_VARS)")
 		fmt.Fprintln(w, "LOCAL_MODULE := "+name+"-hostdex")
@@ -38,14 +40,14 @@
 		}
 		fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
 		fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String())
-		if len(data.Required) > 0 {
-			fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(data.Required, " "))
+		if len(entries.Required) > 0 {
+			fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(entries.Required, " "))
 		}
-		if len(data.Host_required) > 0 {
-			fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES :=", strings.Join(data.Host_required, " "))
+		if len(entries.Host_required) > 0 {
+			fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES :=", strings.Join(entries.Host_required, " "))
 		}
-		if len(data.Target_required) > 0 {
-			fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES :=", strings.Join(data.Target_required, " "))
+		if len(entries.Target_required) > 0 {
+			fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES :=", strings.Join(entries.Target_required, " "))
 		}
 		if r := library.deviceProperties.Target.Hostdex.Required; len(r) > 0 {
 			fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(r, " "))
@@ -54,75 +56,64 @@
 	}
 }
 
-func (library *Library) AndroidMk() android.AndroidMkData {
+func (library *Library) AndroidMkEntries() android.AndroidMkEntries {
 	if !library.IsForPlatform() {
-		return android.AndroidMkData{
+		return android.AndroidMkEntries{
 			Disabled: true,
 		}
 	}
-	return android.AndroidMkData{
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(library.outputFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
 				if len(library.logtagsSrcs) > 0 {
 					var logtags []string
 					for _, l := range library.logtagsSrcs {
 						logtags = append(logtags, l.Rel())
 					}
-					fmt.Fprintln(w, "LOCAL_LOGTAGS_FILES :=", strings.Join(logtags, " "))
+					entries.AddStrings("LOCAL_LOGTAGS_FILES", logtags...)
 				}
 
 				if library.installFile == nil {
-					fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+					entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
 				}
 				if library.dexJarFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String())
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile)
 				}
 				if len(library.dexpreopter.builtInstalled) > 0 {
-					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", library.dexpreopter.builtInstalled)
+					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
 				}
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion())
-				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
+				entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion())
+				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
+				entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 
 				if library.jacocoReportClassesFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", library.jacocoReportClassesFile.String())
+					entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 				}
 
-				if len(library.exportedSdkLibs) != 0 {
-					fmt.Fprintln(w, "LOCAL_EXPORT_SDK_LIBRARIES :=", strings.Join(library.exportedSdkLibs, " "))
-				}
+				entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.exportedSdkLibs...)
 
 				if len(library.additionalCheckedModules) != 0 {
-					fmt.Fprintln(w, "LOCAL_ADDITIONAL_CHECKED_MODULE +=", strings.Join(library.additionalCheckedModules.Strings(), " "))
+					entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
 				}
 
 				if library.proguardDictionary != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_PROGUARD_DICT :=", library.proguardDictionary.String())
+					entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", library.proguardDictionary)
 				}
 			},
 		},
-		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-			android.WriteAndroidMkData(w, data)
-			library.AndroidMkHostDex(w, name, data)
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+				library.AndroidMkHostDex(w, name, entries)
+			},
 		},
 	}
 }
 
 // Called for modules that are a component of a test suite.
-func testSuiteComponent(w io.Writer, test_suites []string) {
-	fmt.Fprintln(w, "LOCAL_MODULE_TAGS := tests")
-	if len(test_suites) > 0 {
-		fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
-			strings.Join(test_suites, " "))
-	} else {
-		fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := null-suite")
-	}
-}
-
-func testSuiteComponentEntries(entries *android.AndroidMkEntries, test_suites []string) {
+func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
 		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test_suites...)
@@ -131,179 +122,175 @@
 	}
 }
 
-func (j *Test) AndroidMk() android.AndroidMkData {
-	data := j.Library.AndroidMk()
-	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
-		testSuiteComponent(w, j.testProperties.Test_suites)
+func (j *Test) AndroidMkEntries() android.AndroidMkEntries {
+	entries := j.Library.AndroidMkEntries()
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		testSuiteComponent(entries, j.testProperties.Test_suites)
 		if j.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", j.testConfig.String())
+			entries.SetPath("LOCAL_FULL_TEST_CONFIG", j.testConfig)
 		}
+		androidMkWriteTestData(j.data, entries)
 	})
 
-	androidMkWriteTestData(j.data, &data)
-
-	return data
+	return entries
 }
 
-func (j *TestHelperLibrary) AndroidMk() android.AndroidMkData {
-	data := j.Library.AndroidMk()
-	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
-		testSuiteComponent(w, j.testHelperLibraryProperties.Test_suites)
+func (j *TestHelperLibrary) AndroidMkEntries() android.AndroidMkEntries {
+	entries := j.Library.AndroidMkEntries()
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites)
 	})
 
-	return data
+	return entries
 }
 
-func (prebuilt *Import) AndroidMk() android.AndroidMkData {
+func (prebuilt *Import) AndroidMkEntries() android.AndroidMkEntries {
 	if !prebuilt.IsForPlatform() || !prebuilt.ContainingSdk().IsCurrentVersion() {
-		return android.AndroidMkData{
+		return android.AndroidMkEntries{
 			Disabled: true,
 		}
 	}
-	return android.AndroidMkData{
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(prebuilt.combinedClasspathFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable))
-				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.combinedClasspathFile.String())
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable))
+				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile)
+				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile)
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
 			},
 		},
 	}
 }
 
-func (prebuilt *DexImport) AndroidMk() android.AndroidMkData {
+func (prebuilt *DexImport) AndroidMkEntries() android.AndroidMkEntries {
 	if !prebuilt.IsForPlatform() {
-		return android.AndroidMkData{
+		return android.AndroidMkEntries{
 			Disabled: true,
 		}
 	}
-	return android.AndroidMkData{
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(prebuilt.maybeStrippedDexJarFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
 				if prebuilt.dexJarFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", prebuilt.dexJarFile.String())
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile)
 					// TODO(b/125517186): export the dex jar as a classes jar to match some mis-uses in Make until
 					// boot_jars_package_check.mk can check dex jars.
-					fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.dexJarFile.String())
-					fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.dexJarFile.String())
+					entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.dexJarFile)
+					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.dexJarFile)
 				}
 				if len(prebuilt.dexpreopter.builtInstalled) > 0 {
-					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", prebuilt.dexpreopter.builtInstalled)
+					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", prebuilt.dexpreopter.builtInstalled)
 				}
 			},
 		},
 	}
 }
 
-func (prebuilt *AARImport) AndroidMk() android.AndroidMkData {
-	return android.AndroidMkData{
+func (prebuilt *AARImport) AndroidMkEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(prebuilt.classpathFile),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
-				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.classpathFile.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.classpathFile.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", prebuilt.exportPackage.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String())
-				fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String())
-				fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", prebuilt.manifest.String())
-				fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.classpathFile)
+				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.classpathFile)
+				entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", prebuilt.exportPackage)
+				entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags)
+				entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile)
+				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest)
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion())
 			},
 		},
 	}
 }
 
-func (binary *Binary) AndroidMk() android.AndroidMkData {
+func (binary *Binary) AndroidMkEntries() android.AndroidMkEntries {
 
 	if !binary.isWrapperVariant {
-		return android.AndroidMkData{
+		return android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
 			OutputFile: android.OptionalPathForPath(binary.outputFile),
 			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-			Extra: []android.AndroidMkExtraFunc{
-				func(w io.Writer, outputFile android.Path) {
-					fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", binary.headerJarFile.String())
-					fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", binary.implementationAndResourcesJar.String())
+			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+				func(entries *android.AndroidMkEntries) {
+					entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile)
+					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar)
 					if binary.dexJarFile != nil {
-						fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", binary.dexJarFile.String())
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile)
 					}
 					if len(binary.dexpreopter.builtInstalled) > 0 {
-						fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", binary.dexpreopter.builtInstalled)
+						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
 					}
 				},
 			},
-			Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-				android.WriteAndroidMkData(w, data)
-
-				fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)")
+			ExtraFooters: []android.AndroidMkExtraFootersFunc{
+				func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+					fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)")
+				},
 			},
 		}
 	} else {
-		return android.AndroidMkData{
+		return android.AndroidMkEntries{
 			Class:      "EXECUTABLES",
 			OutputFile: android.OptionalPathForPath(binary.wrapperFile),
-			Extra: []android.AndroidMkExtraFunc{
-				func(w io.Writer, outputFile android.Path) {
-					fmt.Fprintln(w, "LOCAL_STRIP_MODULE := false")
+			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+				func(entries *android.AndroidMkEntries) {
+					entries.SetBool("LOCAL_STRIP_MODULE", false)
 				},
 			},
-			Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-				android.WriteAndroidMkData(w, data)
-
-				// Ensure that the wrapper script timestamp is always updated when the jar is updated
-				fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)")
-				fmt.Fprintln(w, "jar_installed_module :=")
+			ExtraFooters: []android.AndroidMkExtraFootersFunc{
+				func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+					// Ensure that the wrapper script timestamp is always updated when the jar is updated
+					fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)")
+					fmt.Fprintln(w, "jar_installed_module :=")
+				},
 			},
 		}
 	}
 }
 
-func (app *AndroidApp) AndroidMk() android.AndroidMkData {
-	return android.AndroidMkData{
+func (app *AndroidApp) AndroidMkEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
 		Class:      "APPS",
 		OutputFile: android.OptionalPathForPath(app.outputFile),
 		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
-				// TODO(jungjw): This, outputting two LOCAL_MODULE lines, works, but is not ideal. Find a better solution.
-				if app.Name() != app.installApkName {
-					fmt.Fprintln(w, "# Overridden by PRODUCT_PACKAGE_NAME_OVERRIDES")
-					fmt.Fprintln(w, "LOCAL_MODULE :=", app.installApkName)
-				}
-				fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", app.exportPackage.String())
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
+				// App module names can be overridden.
+				entries.SetString("LOCAL_MODULE", app.installApkName)
+				entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", app.exportPackage)
 				if app.dexJarFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", app.dexJarFile.String())
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile)
 				}
 				if app.implementationAndResourcesJar != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", app.implementationAndResourcesJar.String())
+					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", app.implementationAndResourcesJar)
 				}
 				if app.headerJarFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", app.headerJarFile.String())
+					entries.SetPath("LOCAL_SOONG_HEADER_JAR", app.headerJarFile)
 				}
 				if app.bundleFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_BUNDLE :=", app.bundleFile.String())
+					entries.SetPath("LOCAL_SOONG_BUNDLE", app.bundleFile)
 				}
 				if app.jacocoReportClassesFile != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", app.jacocoReportClassesFile.String())
+					entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", app.jacocoReportClassesFile)
 				}
 				if app.proguardDictionary != nil {
-					fmt.Fprintln(w, "LOCAL_SOONG_PROGUARD_DICT :=", app.proguardDictionary.String())
+					entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", app.proguardDictionary)
 				}
 
 				if app.Name() == "framework-res" {
-					fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)")
+					entries.SetString("LOCAL_MODULE_PATH", "$(TARGET_OUT_JAVA_LIBRARIES)")
 					// Make base_rules.mk not put framework-res in a subdirectory called
 					// framework_res.
-					fmt.Fprintln(w, "LOCAL_NO_STANDARD_LIBRARIES := true")
+					entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true)
 				}
 
 				filterRRO := func(filter overlayType) android.Paths {
@@ -319,38 +306,37 @@
 				}
 				deviceRRODirs := filterRRO(device)
 				if len(deviceRRODirs) > 0 {
-					fmt.Fprintln(w, "LOCAL_SOONG_DEVICE_RRO_DIRS :=", strings.Join(deviceRRODirs.Strings(), " "))
+					entries.AddStrings("LOCAL_SOONG_DEVICE_RRO_DIRS", deviceRRODirs.Strings()...)
 				}
 				productRRODirs := filterRRO(product)
 				if len(productRRODirs) > 0 {
-					fmt.Fprintln(w, "LOCAL_SOONG_PRODUCT_RRO_DIRS :=", strings.Join(productRRODirs.Strings(), " "))
+					entries.AddStrings("LOCAL_SOONG_PRODUCT_RRO_DIRS", productRRODirs.Strings()...)
 				}
 
-				if Bool(app.appProperties.Export_package_resources) {
-					fmt.Fprintln(w, "LOCAL_EXPORT_PACKAGE_RESOURCES := true")
-				}
+				entries.SetBoolIfTrue("LOCAL_EXPORT_PACKAGE_RESOURCES", Bool(app.appProperties.Export_package_resources))
 
-				fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", app.manifestPath.String())
+				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", app.manifestPath)
 
-				if Bool(app.appProperties.Privileged) {
-					fmt.Fprintln(w, "LOCAL_PRIVILEGED_MODULE := true")
-				}
+				entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", Bool(app.appProperties.Privileged))
 
-				fmt.Fprintln(w, "LOCAL_CERTIFICATE :=", app.certificate.Pem.String())
-				if overriddenPkgs := app.getOverriddenPackages(); len(overriddenPkgs) > 0 {
-					fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES :=", strings.Join(overriddenPkgs, " "))
-				}
+				entries.SetPath("LOCAL_CERTIFICATE", app.certificate.Pem)
+				entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", app.getOverriddenPackages()...)
 
 				for _, jniLib := range app.installJniLibs {
-					fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), "+=", jniLib.name)
+					entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name)
 				}
 				if len(app.dexpreopter.builtInstalled) > 0 {
-					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", app.dexpreopter.builtInstalled)
+					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", app.dexpreopter.builtInstalled)
 				}
 				for _, split := range app.aapt.splits {
-					install := "$(LOCAL_MODULE_PATH)/" + strings.TrimSuffix(app.installApkName, ".apk") + split.suffix + ".apk"
-					fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED +=", split.path.String()+":"+install)
+					install := app.onDeviceDir + "/" +
+						strings.TrimSuffix(app.installApkName, ".apk") + "_" + split.suffix + ".apk"
+					entries.AddStrings("LOCAL_SOONG_BUILT_INSTALLED", split.path.String()+":"+install)
 				}
+			},
+		},
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
 				if app.noticeOutputs.Merged.Valid() {
 					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
 						app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE")
@@ -379,85 +365,116 @@
 	return overridden
 }
 
-func (a *AndroidTest) AndroidMk() android.AndroidMkData {
-	data := a.AndroidApp.AndroidMk()
-	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
-		testSuiteComponent(w, a.testProperties.Test_suites)
+func (a *AndroidTest) AndroidMkEntries() android.AndroidMkEntries {
+	entries := a.AndroidApp.AndroidMkEntries()
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		testSuiteComponent(entries, a.testProperties.Test_suites)
 		if a.testConfig != nil {
-			fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", a.testConfig.String())
+			entries.SetPath("LOCAL_FULL_TEST_CONFIG", a.testConfig)
 		}
-	})
-	androidMkWriteTestData(a.data, &data)
-
-	return data
-}
-
-func (a *AndroidTestHelperApp) AndroidMk() android.AndroidMkData {
-	data := a.AndroidApp.AndroidMk()
-	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
-		testSuiteComponent(w, a.appTestHelperAppProperties.Test_suites)
+		androidMkWriteTestData(a.data, entries)
 	})
 
-	return data
+	return entries
 }
 
-func (a *AndroidLibrary) AndroidMk() android.AndroidMkData {
-	data := a.Library.AndroidMk()
+func (a *AndroidTestHelperApp) AndroidMkEntries() android.AndroidMkEntries {
+	entries := a.AndroidApp.AndroidMkEntries()
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+	})
 
-	data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
+	return entries
+}
+
+func (a *AndroidLibrary) AndroidMkEntries() android.AndroidMkEntries {
+	entries := a.Library.AndroidMkEntries()
+
+	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		if a.aarFile != nil {
-			fmt.Fprintln(w, "LOCAL_SOONG_AAR :=", a.aarFile.String())
+			entries.SetPath("LOCAL_SOONG_AAR", a.aarFile)
 		}
 
 		if a.Name() == "framework-res" {
-			fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)")
+			entries.SetString("LOCAL_MODULE_PATH", "$(TARGET_OUT_JAVA_LIBRARIES)")
 			// Make base_rules.mk not put framework-res in a subdirectory called
 			// framework_res.
-			fmt.Fprintln(w, "LOCAL_NO_STANDARD_LIBRARIES := true")
+			entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true)
 		}
 
-		fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", a.exportPackage.String())
-		fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", a.extraAaptPackagesFile.String())
-		fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", a.mergedManifestFile.String())
-		fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=",
-			strings.Join(a.exportedProguardFlagFiles.Strings(), " "))
-		fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+		entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", a.exportPackage)
+		entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", a.extraAaptPackagesFile)
+		entries.SetPath("LOCAL_FULL_MANIFEST_FILE", a.mergedManifestFile)
+		entries.AddStrings("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.exportedProguardFlagFiles.Strings()...)
+		entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
 	})
 
-	return data
+	return entries
 }
 
-func (jd *Javadoc) AndroidMk() android.AndroidMkData {
-	return android.AndroidMkData{
+func (jd *Javadoc) AndroidMkEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(jd.stubsSrcJar),
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
 				if BoolDefault(jd.properties.Installable, true) {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_DOC_ZIP := ", jd.docZip.String())
+					entries.SetPath("LOCAL_DROIDDOC_DOC_ZIP", jd.docZip)
 				}
 				if jd.stubsSrcJar != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", jd.stubsSrcJar.String())
+					entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", jd.stubsSrcJar)
 				}
 			},
 		},
 	}
 }
 
-func (ddoc *Droiddoc) AndroidMk() android.AndroidMkData {
-	return android.AndroidMkData{
+func (ddoc *Droiddoc) AndroidMkEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(ddoc.stubsSrcJar),
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
 				if BoolDefault(ddoc.Javadoc.properties.Installable, true) && ddoc.Javadoc.docZip != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_DOC_ZIP := ", ddoc.Javadoc.docZip.String())
+					entries.SetPath("LOCAL_DROIDDOC_DOC_ZIP", ddoc.Javadoc.docZip)
 				}
 				if ddoc.Javadoc.stubsSrcJar != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", ddoc.Javadoc.stubsSrcJar.String())
+					entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", ddoc.Javadoc.stubsSrcJar)
 				}
+				apiFilePrefix := "INTERNAL_PLATFORM_"
+				if String(ddoc.properties.Api_tag_name) != "" {
+					apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_"
+				}
+				if ddoc.apiFile != nil {
+					entries.SetPath(apiFilePrefix+"API_FILE", ddoc.apiFile)
+				}
+				if ddoc.dexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"DEX_API_FILE", ddoc.dexApiFile)
+				}
+				if ddoc.privateApiFile != nil {
+					entries.SetPath(apiFilePrefix+"PRIVATE_API_FILE", ddoc.privateApiFile)
+				}
+				if ddoc.privateDexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"PRIVATE_DEX_API_FILE", ddoc.privateDexApiFile)
+				}
+				if ddoc.removedApiFile != nil {
+					entries.SetPath(apiFilePrefix+"REMOVED_API_FILE", ddoc.removedApiFile)
+				}
+				if ddoc.removedDexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"REMOVED_DEX_API_FILE", ddoc.removedDexApiFile)
+				}
+				if ddoc.exactApiFile != nil {
+					entries.SetPath(apiFilePrefix+"EXACT_API_FILE", ddoc.exactApiFile)
+				}
+				if ddoc.proguardFile != nil {
+					entries.SetPath(apiFilePrefix+"PROGUARD_FILE", ddoc.proguardFile)
+				}
+			},
+		},
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
 				if ddoc.checkCurrentApiTimestamp != nil {
 					fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-current-api")
 					fmt.Fprintln(w, ddoc.Name()+"-check-current-api:",
@@ -493,58 +510,59 @@
 						fmt.Fprintln(w, "droidcore: checkapi")
 					}
 				}
-				apiFilePrefix := "INTERNAL_PLATFORM_"
-				if String(ddoc.properties.Api_tag_name) != "" {
-					apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_"
-				}
-				if ddoc.apiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", ddoc.apiFile.String())
-				}
-				if ddoc.dexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"DEX_API_FILE := ", ddoc.dexApiFile.String())
-				}
-				if ddoc.privateApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", ddoc.privateApiFile.String())
-				}
-				if ddoc.privateDexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", ddoc.privateDexApiFile.String())
-				}
-				if ddoc.removedApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", ddoc.removedApiFile.String())
-				}
-				if ddoc.removedDexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", ddoc.removedDexApiFile.String())
-				}
-				if ddoc.exactApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", ddoc.exactApiFile.String())
-				}
-				if ddoc.proguardFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"PROGUARD_FILE := ", ddoc.proguardFile.String())
-				}
 			},
 		},
 	}
 }
 
-func (dstubs *Droidstubs) AndroidMk() android.AndroidMkData {
-	return android.AndroidMkData{
+func (dstubs *Droidstubs) AndroidMkEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(dstubs.stubsSrcJar),
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
-		Extra: []android.AndroidMkExtraFunc{
-			func(w io.Writer, outputFile android.Path) {
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(entries *android.AndroidMkEntries) {
 				if dstubs.Javadoc.stubsSrcJar != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", dstubs.Javadoc.stubsSrcJar.String())
+					entries.SetPath("LOCAL_DROIDDOC_STUBS_SRCJAR", dstubs.Javadoc.stubsSrcJar)
 				}
 				if dstubs.apiVersionsXml != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_API_VERSIONS_XML := ", dstubs.apiVersionsXml.String())
+					entries.SetPath("LOCAL_DROIDDOC_API_VERSIONS_XML", dstubs.apiVersionsXml)
 				}
 				if dstubs.annotationsZip != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_ANNOTATIONS_ZIP := ", dstubs.annotationsZip.String())
+					entries.SetPath("LOCAL_DROIDDOC_ANNOTATIONS_ZIP", dstubs.annotationsZip)
 				}
 				if dstubs.jdiffDocZip != nil {
-					fmt.Fprintln(w, "LOCAL_DROIDDOC_JDIFF_DOC_ZIP := ", dstubs.jdiffDocZip.String())
+					entries.SetPath("LOCAL_DROIDDOC_JDIFF_DOC_ZIP", dstubs.jdiffDocZip)
 				}
+				apiFilePrefix := "INTERNAL_PLATFORM_"
+				if String(dstubs.properties.Api_tag_name) != "" {
+					apiFilePrefix += String(dstubs.properties.Api_tag_name) + "_"
+				}
+				if dstubs.apiFile != nil {
+					entries.SetPath(apiFilePrefix+"API_FILE", dstubs.apiFile)
+				}
+				if dstubs.dexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"DEX_API_FILE", dstubs.dexApiFile)
+				}
+				if dstubs.privateApiFile != nil {
+					entries.SetPath(apiFilePrefix+"PRIVATE_API_FILE", dstubs.privateApiFile)
+				}
+				if dstubs.privateDexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"PRIVATE_DEX_API_FILE", dstubs.privateDexApiFile)
+				}
+				if dstubs.removedApiFile != nil {
+					entries.SetPath(apiFilePrefix+"REMOVED_API_FILE", dstubs.removedApiFile)
+				}
+				if dstubs.removedDexApiFile != nil {
+					entries.SetPath(apiFilePrefix+"REMOVED_DEX_API_FILE", dstubs.removedDexApiFile)
+				}
+				if dstubs.exactApiFile != nil {
+					entries.SetPath(apiFilePrefix+"EXACT_API_FILE", dstubs.exactApiFile)
+				}
+			},
+		},
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
 				if dstubs.checkCurrentApiTimestamp != nil {
 					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-current-api")
 					fmt.Fprintln(w, dstubs.Name()+"-check-current-api:",
@@ -588,31 +606,6 @@
 					fmt.Fprintln(w, ".PHONY:", "droidcore")
 					fmt.Fprintln(w, "droidcore: ", dstubs.Name()+"-check-nullability-warnings")
 				}
-				apiFilePrefix := "INTERNAL_PLATFORM_"
-				if String(dstubs.properties.Api_tag_name) != "" {
-					apiFilePrefix += String(dstubs.properties.Api_tag_name) + "_"
-				}
-				if dstubs.apiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", dstubs.apiFile.String())
-				}
-				if dstubs.dexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"DEX_API_FILE := ", dstubs.dexApiFile.String())
-				}
-				if dstubs.privateApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", dstubs.privateApiFile.String())
-				}
-				if dstubs.privateDexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", dstubs.privateDexApiFile.String())
-				}
-				if dstubs.removedApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", dstubs.removedApiFile.String())
-				}
-				if dstubs.removedDexApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", dstubs.removedDexApiFile.String())
-				}
-				if dstubs.exactApiFile != nil {
-					fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", dstubs.exactApiFile.String())
-				}
 			},
 		},
 	}
@@ -627,7 +620,7 @@
 			func(entries *android.AndroidMkEntries) {
 				entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", Bool(a.properties.Privileged))
 				if a.certificate != nil {
-					entries.SetString("LOCAL_CERTIFICATE", a.certificate.Pem.String())
+					entries.SetPath("LOCAL_CERTIFICATE", a.certificate.Pem)
 				} else {
 					entries.SetString("LOCAL_CERTIFICATE", "PRESIGNED")
 				}
@@ -644,25 +637,13 @@
 func (a *AndroidTestImport) AndroidMkEntries() android.AndroidMkEntries {
 	entries := a.AndroidAppImport.AndroidMkEntries()
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
-		testSuiteComponentEntries(entries, a.testProperties.Test_suites)
-		androidMkEntriesWriteTestData(a.data, entries)
+		testSuiteComponent(entries, a.testProperties.Test_suites)
+		androidMkWriteTestData(a.data, entries)
 	})
 	return entries
 }
 
-func androidMkWriteTestData(data android.Paths, ret *android.AndroidMkData) {
-	var testFiles []string
-	for _, d := range data {
-		testFiles = append(testFiles, d.String()+":"+d.Rel())
-	}
-	if len(testFiles) > 0 {
-		ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
-			fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUPPORT_FILES := "+strings.Join(testFiles, " "))
-		})
-	}
-}
-
-func androidMkEntriesWriteTestData(data android.Paths, entries *android.AndroidMkEntries) {
+func androidMkWriteTestData(data android.Paths, entries *android.AndroidMkEntries) {
 	var testFiles []string
 	for _, d := range data {
 		testFiles = append(testFiles, d.String()+":"+d.Rel())
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index fbf2baa..438b66a 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -15,107 +15,12 @@
 package java
 
 import (
-	"bytes"
-	"io"
-	"io/ioutil"
-	"strings"
+	"reflect"
 	"testing"
 
 	"android/soong/android"
 )
 
-type testAndroidMk struct {
-	*testing.T
-	body []byte
-}
-
-type testAndroidMkModule struct {
-	*testing.T
-	props map[string]string
-}
-
-func newTestAndroidMk(t *testing.T, r io.Reader) *testAndroidMk {
-	t.Helper()
-	buf, err := ioutil.ReadAll(r)
-	if err != nil {
-		t.Fatal("failed to open read Android.mk.", err)
-	}
-	return &testAndroidMk{
-		T:    t,
-		body: buf,
-	}
-}
-
-func parseAndroidMkProps(lines []string) map[string]string {
-	props := make(map[string]string)
-	for _, line := range lines {
-		line = strings.TrimLeft(line, " ")
-		if line == "" || strings.HasPrefix(line, "#") {
-			continue
-		}
-		tokens := strings.Split(line, " ")
-		if tokens[1] == "+=" {
-			props[tokens[0]] += " " + strings.Join(tokens[2:], " ")
-		} else {
-			props[tokens[0]] = strings.Join(tokens[2:], " ")
-		}
-	}
-	return props
-}
-
-func (t *testAndroidMk) moduleFor(moduleName string) *testAndroidMkModule {
-	t.Helper()
-	lines := strings.Split(string(t.body), "\n")
-	index := android.IndexList("LOCAL_MODULE := "+moduleName, lines)
-	if index == -1 {
-		t.Fatalf("%q is not found.", moduleName)
-	}
-	lines = lines[index:]
-	includeIndex := android.IndexListPred(func(line string) bool {
-		return strings.HasPrefix(line, "include")
-	}, lines)
-	if includeIndex == -1 {
-		t.Fatalf("%q is not properly defined. (\"include\" not found).", moduleName)
-	}
-	props := parseAndroidMkProps(lines[:includeIndex])
-	return &testAndroidMkModule{
-		T:     t.T,
-		props: props,
-	}
-}
-
-func (t *testAndroidMkModule) hasRequired(dep string) {
-	t.Helper()
-	required, ok := t.props["LOCAL_REQUIRED_MODULES"]
-	if !ok {
-		t.Error("LOCAL_REQUIRED_MODULES is not found.")
-		return
-	}
-	if !android.InList(dep, strings.Split(required, " ")) {
-		t.Errorf("%q is expected in LOCAL_REQUIRED_MODULES, but not found in %q.", dep, required)
-	}
-}
-
-func (t *testAndroidMkModule) hasNoRequired(dep string) {
-	t.Helper()
-	required, ok := t.props["LOCAL_REQUIRED_MODULES"]
-	if !ok {
-		return
-	}
-	if android.InList(dep, strings.Split(required, " ")) {
-		t.Errorf("%q is not expected in LOCAL_REQUIRED_MODULES, but found.", dep)
-	}
-}
-
-func getAndroidMk(t *testing.T, ctx *android.TestContext, config android.Config, name string) *testAndroidMk {
-	t.Helper()
-	lib, _ := ctx.ModuleForTests(name, "android_common").Module().(*Library)
-	data := android.AndroidMkDataForTest(t, config, "", lib)
-	w := &bytes.Buffer{}
-	data.Custom(w, name, "", "", data)
-	return newTestAndroidMk(t, w)
-}
-
 func TestRequired(t *testing.T) {
 	ctx, config := testJava(t, `
 		java_library {
@@ -125,8 +30,14 @@
 		}
 	`)
 
-	mk := getAndroidMk(t, ctx, config, "foo")
-	mk.moduleFor("foo").hasRequired("libfoo")
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", mod)
+
+	expected := []string{"libfoo"}
+	actual := entries.EntryMap["LOCAL_REQUIRED_MODULES"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual)
+	}
 }
 
 func TestHostdex(t *testing.T) {
@@ -138,9 +49,19 @@
 		}
 	`)
 
-	mk := getAndroidMk(t, ctx, config, "foo")
-	mk.moduleFor("foo")
-	mk.moduleFor("foo-hostdex")
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", mod)
+
+	expected := []string{"foo"}
+	actual := entries.EntryMap["LOCAL_MODULE"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected module name - expected: %q, actual: %q", expected, actual)
+	}
+
+	footerLines := entries.FooterLinesForTests()
+	if !android.InList("LOCAL_MODULE := foo-hostdex", footerLines) {
+		t.Errorf("foo-hostdex is not found in the footers: %q", footerLines)
+	}
 }
 
 func TestHostdexRequired(t *testing.T) {
@@ -153,9 +74,19 @@
 		}
 	`)
 
-	mk := getAndroidMk(t, ctx, config, "foo")
-	mk.moduleFor("foo").hasRequired("libfoo")
-	mk.moduleFor("foo-hostdex").hasRequired("libfoo")
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", mod)
+
+	expected := []string{"libfoo"}
+	actual := entries.EntryMap["LOCAL_REQUIRED_MODULES"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected required modules - expected: %q, actual: %q", expected, actual)
+	}
+
+	footerLines := entries.FooterLinesForTests()
+	if !android.InList("LOCAL_REQUIRED_MODULES := libfoo", footerLines) {
+		t.Errorf("Wrong or missing required line for foo-hostdex in the footers: %q", footerLines)
+	}
 }
 
 func TestHostdexSpecificRequired(t *testing.T) {
@@ -172,7 +103,15 @@
 		}
 	`)
 
-	mk := getAndroidMk(t, ctx, config, "foo")
-	mk.moduleFor("foo").hasNoRequired("libfoo")
-	mk.moduleFor("foo-hostdex").hasRequired("libfoo")
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", mod)
+
+	if r, ok := entries.EntryMap["LOCAL_REQUIRED_MODULES"]; ok {
+		t.Errorf("Unexpected required modules: %q", r)
+	}
+
+	footerLines := entries.FooterLinesForTests()
+	if !android.InList("LOCAL_REQUIRED_MODULES += libfoo", footerLines) {
+		t.Errorf("Wrong or missing required line for foo-hostdex in the footers: %q", footerLines)
+	}
 }
diff --git a/java/app.go b/java/app.go
index d00c4c0..3ee8b8d 100644
--- a/java/app.go
+++ b/java/app.go
@@ -126,6 +126,10 @@
 	// the install APK name is normally the same as the module name, but can be overridden with PRODUCT_PACKAGE_NAME_OVERRIDES.
 	installApkName string
 
+	installDir android.OutputPath
+
+	onDeviceDir string
+
 	additionalAaptFlags []string
 
 	noticeOutputs android.NoticeOutputs
@@ -319,7 +323,6 @@
 	} else {
 		installDir = filepath.Join("app", a.installApkName)
 	}
-
 	a.dexpreopter.installPath = android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
 	a.dexpreopter.isInstallable = Bool(a.properties.Installable)
 	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
@@ -352,7 +355,7 @@
 	return jniJarFile
 }
 
-func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) {
+func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) {
 	// Collect NOTICE files from all dependencies.
 	seenModules := make(map[android.Module]bool)
 	noticePathSet := make(map[android.Path]bool)
@@ -392,7 +395,7 @@
 		return noticePaths[i].String() < noticePaths[j].String()
 	})
 
-	a.noticeOutputs = android.BuildNoticeOutput(ctx, installDir, a.installApkName+".apk", noticePaths)
+	a.noticeOutputs = android.BuildNoticeOutput(ctx, a.installDir, a.installApkName+".apk", noticePaths)
 }
 
 // Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it
@@ -438,17 +441,19 @@
 	// Check if the install APK name needs to be overridden.
 	a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name())
 
-	var installDir android.OutputPath
 	if ctx.ModuleName() == "framework-res" {
 		// framework-res.apk is installed as system/framework/framework-res.apk
-		installDir = android.PathForModuleInstall(ctx, "framework")
+		a.installDir = android.PathForModuleInstall(ctx, "framework")
 	} else if Bool(a.appProperties.Privileged) {
-		installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
+		a.installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
+	} else if ctx.InstallInTestcases() {
+		a.installDir = android.PathForModuleInstall(ctx, a.installApkName)
 	} else {
-		installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
+		a.installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
 	}
+	a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
 
-	a.noticeBuildActions(ctx, installDir)
+	a.noticeBuildActions(ctx)
 	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
 		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
 	}
@@ -494,9 +499,9 @@
 	a.bundleFile = bundleFile
 
 	// Install the app package.
-	ctx.InstallFile(installDir, a.installApkName+".apk", a.outputFile)
+	ctx.InstallFile(a.installDir, a.installApkName+".apk", a.outputFile)
 	for _, split := range a.aapt.splits {
-		ctx.InstallFile(installDir, a.installApkName+"_"+split.suffix+".apk", split.path)
+		ctx.InstallFile(a.installDir, a.installApkName+"_"+split.suffix+".apk", split.path)
 	}
 }
 
@@ -598,6 +603,10 @@
 	data       android.Paths
 }
 
+func (a *AndroidTest) InstallInTestcases() bool {
+	return true
+}
+
 func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Check if the instrumentation target package is overridden before generating build actions.
 	if a.appTestProperties.Instrumentation_for != nil {
@@ -608,7 +617,8 @@
 	}
 	a.generateAndroidBuildActions(ctx)
 
-	a.testConfig = tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config, a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites)
+	a.testConfig = tradefed.AutoGenInstrumentationTestConfig(ctx, a.testProperties.Test_config,
+		a.testProperties.Test_config_template, a.manifestPath, a.testProperties.Test_suites, a.testProperties.Auto_gen_config)
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
 }
 
@@ -656,6 +666,11 @@
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type AndroidTestHelperApp struct {
diff --git a/java/builder.go b/java/builder.go
index 9e068fa..0a5c79b 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -64,6 +64,7 @@
 
 	_ = pctx.VariableFunc("kytheCorpus",
 		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() })
+	_ = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json")
 	// Run it with -add-opens=java.base/java.nio=ALL-UNNAMED to avoid JDK9's warning about
 	// "Illegal reflective access by com.google.protobuf.Utf8$UnsafeProcessor ...
 	// to field java.nio.Buffer.address"
@@ -74,6 +75,7 @@
 				`( [ ! -s $srcJarDir/list -a ! -s $out.rsp ] || ` +
 				`KYTHE_ROOT_DIRECTORY=. KYTHE_OUTPUT_FILE=$out ` +
 				`KYTHE_CORPUS=${kytheCorpus} ` +
+				`KYTHE_VNAMES=${kytheVnames} ` +
 				`${config.SoongJavacWrapper} ${config.JavaCmd} ` +
 				`--add-opens=java.base/java.nio=ALL-UNNAMED ` +
 				`-jar ${config.JavaKytheExtractorJar} ` +
@@ -84,6 +86,7 @@
 			CommandDeps: []string{
 				"${config.JavaCmd}",
 				"${config.JavaKytheExtractorJar}",
+				"${kytheVnames}",
 				"${config.ZipSyncCmd}",
 			},
 			CommandOrderOnly: []string{"${config.SoongJavacWrapper}"},
diff --git a/java/java.go b/java/java.go
index 4dd6a49..f7b0f53 100644
--- a/java/java.go
+++ b/java/java.go
@@ -580,18 +580,6 @@
 	return false
 }
 
-func shardPaths(paths android.Paths, shardSize int) []android.Paths {
-	ret := make([]android.Paths, 0, (len(paths)+shardSize-1)/shardSize)
-	for len(paths) > shardSize {
-		ret = append(ret, paths[0:shardSize])
-		paths = paths[shardSize:]
-	}
-	if len(paths) > 0 {
-		ret = append(ret, paths)
-	}
-	return ret
-}
-
 func (j *Module) hasSrcExt(ext string) bool {
 	return hasSrcExt(j.properties.Srcs, ext)
 }
@@ -1156,7 +1144,7 @@
 			shardSize := int(*(j.properties.Javac_shard_size))
 			var shardSrcs []android.Paths
 			if len(uniqueSrcFiles) > 0 {
-				shardSrcs = shardPaths(uniqueSrcFiles, shardSize)
+				shardSrcs = android.ShardPaths(uniqueSrcFiles, shardSize)
 				for idx, shardSrc := range shardSrcs {
 					classes := j.compileJavaClasses(ctx, jarName, idx, shardSrc,
 						nil, flags, extraJarDeps)
@@ -1685,6 +1673,11 @@
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
 	Data []string `android:"path"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type testHelperLibraryProperties struct {
@@ -1709,7 +1702,8 @@
 }
 
 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.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
+		j.testProperties.Test_suites, j.testProperties.Auto_gen_config)
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
 	j.Library.GenerateAndroidBuildActions(ctx)
diff --git a/java/robolectric.go b/java/robolectric.go
index 9669e1b..b7646eb 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -123,25 +123,6 @@
 	}
 }
 
-func shardTests(paths []string, shards int) [][]string {
-	if shards > len(paths) {
-		shards = len(paths)
-	}
-	if shards == 0 {
-		return nil
-	}
-	ret := make([][]string, 0, shards)
-	shardSize := (len(paths) + shards - 1) / shards
-	for len(paths) > shardSize {
-		ret = append(ret, paths[0:shardSize])
-		paths = paths[shardSize:]
-	}
-	if len(paths) > 0 {
-		ret = append(ret, paths)
-	}
-	return ret
-}
-
 func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath, instrumentedApp *AndroidApp) {
 	manifest := instrumentedApp.mergedManifestFile
 	resourceApk := instrumentedApp.outputFile
@@ -177,32 +158,34 @@
 	TransformResourcesToJar(ctx, outputFile, srcJarArgs, srcJarDeps)
 }
 
-func (r *robolectricTest) AndroidMk() android.AndroidMkData {
-	data := r.Library.AndroidMk()
+func (r *robolectricTest) AndroidMkEntries() android.AndroidMkEntries {
+	entries := r.Library.AndroidMkEntries()
 
-	data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-		android.WriteAndroidMkData(w, data)
+	entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
+		func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+			if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 {
+				numShards := int(*s)
+				shardSize := (len(r.tests) + numShards - 1) / numShards
+				shards := android.ShardStrings(r.tests, shardSize)
+				for i, shard := range shards {
+					r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard)
+				}
 
-		if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 {
-			shards := shardTests(r.tests, int(*s))
-			for i, shard := range shards {
-				r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard)
+				// TODO: add rules to dist the outputs of the individual tests, or combine them together?
+				fmt.Fprintln(w, "")
+				fmt.Fprintln(w, ".PHONY:", "Run"+name)
+				fmt.Fprintln(w, "Run"+name, ": \\")
+				for i := range shards {
+					fmt.Fprintln(w, "   ", "Run"+name+strconv.Itoa(i), "\\")
+				}
+				fmt.Fprintln(w, "")
+			} else {
+				r.writeTestRunner(w, name, "Run"+name, r.tests)
 			}
-
-			// TODO: add rules to dist the outputs of the individual tests, or combine them together?
-			fmt.Fprintln(w, "")
-			fmt.Fprintln(w, ".PHONY:", "Run"+name)
-			fmt.Fprintln(w, "Run"+name, ": \\")
-			for i := range shards {
-				fmt.Fprintln(w, "   ", "Run"+name+strconv.Itoa(i), "\\")
-			}
-			fmt.Fprintln(w, "")
-		} else {
-			r.writeTestRunner(w, name, "Run"+name, r.tests)
-		}
+		},
 	}
 
-	return data
+	return entries
 }
 
 func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) {
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
deleted file mode 100644
index e89c6e7..0000000
--- a/java/robolectric_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2019 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package java
-
-import (
-	"reflect"
-	"testing"
-)
-
-func Test_shardTests(t *testing.T) {
-	type args struct {
-		paths  []string
-		shards int
-	}
-	tests := []struct {
-		name string
-		args args
-		want [][]string
-	}{
-		{
-			name: "empty",
-			args: args{
-				paths:  nil,
-				shards: 1,
-			},
-			want: [][]string(nil),
-		},
-		{
-			name: "too many shards",
-			args: args{
-				paths:  []string{"a", "b"},
-				shards: 3,
-			},
-			want: [][]string{{"a"}, {"b"}},
-		},
-		{
-			name: "single shard",
-			args: args{
-				paths:  []string{"a", "b"},
-				shards: 1,
-			},
-			want: [][]string{{"a", "b"}},
-		},
-		{
-			name: "shard per input",
-			args: args{
-				paths:  []string{"a", "b", "c"},
-				shards: 3,
-			},
-			want: [][]string{{"a"}, {"b"}, {"c"}},
-		},
-		{
-			name: "balanced shards",
-			args: args{
-				paths:  []string{"a", "b", "c", "d"},
-				shards: 2,
-			},
-			want: [][]string{{"a", "b"}, {"c", "d"}},
-		},
-		{
-			name: "unbalanced shards",
-			args: args{
-				paths:  []string{"a", "b", "c"},
-				shards: 2,
-			},
-			want: [][]string{{"a", "b"}, {"c"}},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := shardTests(tt.args.paths, tt.args.shards); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("shardTests() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index cc7256b..476e549 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -197,65 +197,65 @@
 	})
 }
 
-func (module *SdkLibrary) AndroidMk() android.AndroidMkData {
-	data := module.Library.AndroidMk()
-	data.Required = append(data.Required, module.xmlFileName())
+func (module *SdkLibrary) AndroidMkEntries() android.AndroidMkEntries {
+	entries := module.Library.AndroidMkEntries()
+	entries.Required = append(entries.Required, module.xmlFileName())
 
-	data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-		android.WriteAndroidMkData(w, data)
-
-		module.Library.AndroidMkHostDex(w, name, data)
-		if !Bool(module.sdkLibraryProperties.No_dist) {
-			// Create a phony module that installs the impl library, for the case when this lib is
-			// in PRODUCT_PACKAGES.
-			owner := module.ModuleBase.Owner()
-			if owner == "" {
-				if Bool(module.sdkLibraryProperties.Core_lib) {
-					owner = "core"
-				} else {
-					owner = "android"
+	entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
+		func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
+			module.Library.AndroidMkHostDex(w, name, entries)
+			if !Bool(module.sdkLibraryProperties.No_dist) {
+				// Create a phony module that installs the impl library, for the case when this lib is
+				// in PRODUCT_PACKAGES.
+				owner := module.ModuleBase.Owner()
+				if owner == "" {
+					if Bool(module.sdkLibraryProperties.Core_lib) {
+						owner = "core"
+					} else {
+						owner = "android"
+					}
+				}
+				// Create dist rules to install the stubs libs to the dist dir
+				if len(module.publicApiStubsPath) == 1 {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.publicApiStubsImplPath.Strings()[0]+
+						":"+path.Join("apistubs", owner, "public",
+						module.BaseModuleName()+".jar")+")")
+				}
+				if len(module.systemApiStubsPath) == 1 {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.systemApiStubsImplPath.Strings()[0]+
+						":"+path.Join("apistubs", owner, "system",
+						module.BaseModuleName()+".jar")+")")
+				}
+				if len(module.testApiStubsPath) == 1 {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.testApiStubsImplPath.Strings()[0]+
+						":"+path.Join("apistubs", owner, "test",
+						module.BaseModuleName()+".jar")+")")
+				}
+				if module.publicApiFilePath != nil {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.publicApiFilePath.String()+
+						":"+path.Join("apistubs", owner, "public", "api",
+						module.BaseModuleName()+".txt")+")")
+				}
+				if module.systemApiFilePath != nil {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.systemApiFilePath.String()+
+						":"+path.Join("apistubs", owner, "system", "api",
+						module.BaseModuleName()+".txt")+")")
+				}
+				if module.testApiFilePath != nil {
+					fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+						module.testApiFilePath.String()+
+						":"+path.Join("apistubs", owner, "test", "api",
+						module.BaseModuleName()+".txt")+")")
 				}
 			}
-			// Create dist rules to install the stubs libs to the dist dir
-			if len(module.publicApiStubsPath) == 1 {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.publicApiStubsImplPath.Strings()[0]+
-					":"+path.Join("apistubs", owner, "public",
-					module.BaseModuleName()+".jar")+")")
-			}
-			if len(module.systemApiStubsPath) == 1 {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.systemApiStubsImplPath.Strings()[0]+
-					":"+path.Join("apistubs", owner, "system",
-					module.BaseModuleName()+".jar")+")")
-			}
-			if len(module.testApiStubsPath) == 1 {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.testApiStubsImplPath.Strings()[0]+
-					":"+path.Join("apistubs", owner, "test",
-					module.BaseModuleName()+".jar")+")")
-			}
-			if module.publicApiFilePath != nil {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.publicApiFilePath.String()+
-					":"+path.Join("apistubs", owner, "public", "api",
-					module.BaseModuleName()+".txt")+")")
-			}
-			if module.systemApiFilePath != nil {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.systemApiFilePath.String()+
-					":"+path.Join("apistubs", owner, "system", "api",
-					module.BaseModuleName()+".txt")+")")
-			}
-			if module.testApiFilePath != nil {
-				fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
-					module.testApiFilePath.String()+
-					":"+path.Join("apistubs", owner, "test", "api",
-					module.BaseModuleName()+".txt")+")")
-			}
-		}
+		},
 	}
-	return data
+	return entries
 }
 
 // Module name of the stubs library
diff --git a/java/testing.go b/java/testing.go
index 5315b85..acbefb9 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -103,7 +103,6 @@
 	`
 
 	systemModules := []string{
-		"core-system-modules",
 		"core-current-stubs-system-modules",
 		"core-platform-api-stubs-system-modules",
 		"android_stubs_current_system_modules",
diff --git a/python/binary.go b/python/binary.go
index 140f07a..695fa12 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -47,6 +47,11 @@
 	// false it will act much like the normal `python` executable, but with the sources and
 	// libraries automatically included in the PYTHONPATH.
 	Autorun *bool `android:"arch_variant"`
+
+	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
+	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
+	// explicitly.
+	Auto_gen_config *bool
 }
 
 type binaryDecorator struct {
diff --git a/python/test.go b/python/test.go
index 55b0ab5..f684fd5 100644
--- a/python/test.go
+++ b/python/test.go
@@ -50,7 +50,8 @@
 
 func (test *testDecorator) install(ctx android.ModuleContext, file android.Path) {
 	test.testConfig = tradefed.AutoGenPythonBinaryHostTestConfig(ctx, test.testProperties.Test_config,
-		test.testProperties.Test_config_template, test.binaryDecorator.binaryProperties.Test_suites)
+		test.testProperties.Test_config_template, test.binaryDecorator.binaryProperties.Test_suites,
+		test.binaryDecorator.binaryProperties.Auto_gen_config)
 
 	test.binaryDecorator.pythonInstaller.dir = "nativetest"
 	test.binaryDecorator.pythonInstaller.dir64 = "nativetest64"
diff --git a/rust/builder.go b/rust/builder.go
index 6611492..104313f 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -29,8 +29,7 @@
 			Command: "$rustcCmd " +
 				"-C linker=${config.RustLinker} " +
 				"-C link-args=\"${crtBegin} ${config.RustLinkerArgs} ${linkFlags} ${crtEnd}\" " +
-				"-o $out $in ${libFlags} $rustcFlags " +
-				"&& $rustcCmd --emit=dep-info -o $out.d $in ${libFlags} $rustcFlags",
+				"--emit link -o $out --emit dep-info=$out.d $in ${libFlags} $rustcFlags",
 			CommandDeps: []string{"$rustcCmd"},
 			Depfile:     "$out.d",
 			Deps:        blueprint.DepsGCC, // Rustc deps-info writes out make compatible dep files: https://github.com/rust-lang/rust/issues/7633
diff --git a/rust/compiler.go b/rust/compiler.go
index f45744b..3bfef76 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -25,7 +25,8 @@
 func NewBaseCompiler(dir, dir64 string) *baseCompiler {
 	return &baseCompiler{
 		Properties: BaseCompilerProperties{
-			Edition: &config.DefaultEdition,
+			Edition:       &config.DefaultEdition,
+			Deny_warnings: config.DefaultDenyWarnings,
 		},
 		dir:   dir,
 		dir64: dir64,
@@ -33,6 +34,9 @@
 }
 
 type BaseCompilerProperties struct {
+	// whether to pass "-D warnings" to rustc. Defaults to true.
+	Deny_warnings *bool
+
 	// flags to pass to rustc
 	Flags []string `android:"path,arch_variant"`
 
@@ -109,10 +113,14 @@
 
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 
+	if Bool(compiler.Properties.Deny_warnings) {
+		flags.RustFlags = append(flags.RustFlags, "-D warnings")
+	}
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
 	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(compiler.Properties.Features)...)
 	flags.RustFlags = append(flags.RustFlags, "--edition="+*compiler.Properties.Edition)
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, ctx.toolchain().ToolchainRustFlags())
 	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, ctx.toolchain().ToolchainLinkFlags())
 
diff --git a/rust/config/global.go b/rust/config/global.go
index cec5a74..ae50804 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -17,6 +17,8 @@
 import (
 	"strings"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	_ "android/soong/cc/config"
 )
@@ -33,6 +35,12 @@
 		"libtest",
 	}
 
+	DefaultDenyWarnings = proptools.BoolPtr(true)
+
+	GlobalRustFlags = []string{
+		"--remap-path-prefix $$(pwd)=",
+	}
+
 	deviceGlobalRustFlags = []string{}
 
 	deviceGlobalLinkFlags = []string{
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index 1400345..3d30cfa 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -44,10 +44,11 @@
 	CommandDeps: []string{"$template"},
 }, "name", "template", "extraConfigs")
 
-func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string) (path android.Path, autogenPath android.WritablePath) {
-	if p := getTestConfig(ctx, prop); p != nil {
+func testConfigPath(ctx android.ModuleContext, prop *string, testSuites []string, autoGenConfig *bool) (path android.Path, autogenPath android.WritablePath) {
+	p := getTestConfig(ctx, prop)
+	if !Bool(autoGenConfig) && p != nil {
 		return p, nil
-	} else if !android.InList("cts", testSuites) {
+	} else if !android.InList("cts", testSuites) && BoolDefault(autoGenConfig, true) {
 		outputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".config")
 		return nil, outputFile
 	} else {
@@ -124,8 +125,8 @@
 }
 
 func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -143,8 +144,8 @@
 }
 
 func AutoGenNativeBenchmarkTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, configs []Config) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	testConfigTemplateProp *string, testSuites []string, configs []Config, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -157,8 +158,9 @@
 	return path
 }
 
-func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string, testSuites []string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
+	testSuites []string, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -176,9 +178,9 @@
 }
 
 func AutoGenPythonBinaryHostTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string) android.Path {
+	testConfigTemplateProp *string, testSuites []string, autoGenConfig *bool) android.Path {
 
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
@@ -200,8 +202,9 @@
 	},
 }, "name", "template")
 
-func AutoGenInstrumentationTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string, manifest android.Path, testSuites []string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites)
+func AutoGenInstrumentationTestConfig(ctx android.ModuleContext, testConfigProp *string,
+	testConfigTemplateProp *string, manifest android.Path, testSuites []string, autoGenConfig *bool) android.Path {
+	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig)
 	if autogenPath != nil {
 		template := "${InstrumentationTestConfigTemplate}"
 		moduleTemplate := getTestConfigTemplate(ctx, testConfigTemplateProp)
@@ -222,3 +225,6 @@
 	}
 	return path
 }
+
+var Bool = proptools.Bool
+var BoolDefault = proptools.BoolDefault
diff --git a/vnames.json b/vnames.json
new file mode 100644
index 0000000..7b34c52
--- /dev/null
+++ b/vnames.json
@@ -0,0 +1,18 @@
+[
+  {
+    "pattern": "out/(.*)",
+    "vname": {
+      "corpus": "CORPUS",
+      "root": "out",
+      "path": "@1@"
+    }
+  },
+  {
+    "pattern": "(.*)",
+    "vname": {
+      "corpus": "CORPUS",
+      "path": "@1@"
+    }
+  }
+]
+