Merge changes Ib7ad715d,I3a83b5ed

* changes:
  Remove frameworkResModule from sdkCorePlatform.
  Remove the concept of useDefaultLibs from Soong.
diff --git a/Android.bp b/Android.bp
index 0b44198..3075d67 100644
--- a/Android.bp
+++ b/Android.bp
@@ -106,15 +106,24 @@
         },
         arm64: {
             src: "prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/lib/gcc/aarch64-linux-android/4.9.x/libgcc.a",
-            repack_objects_to_keep: ["unwind-dw2.o", "unwind-dw2-fde-dip.o"],
+            repack_objects_to_keep: [
+                "unwind-dw2.o",
+                "unwind-dw2-fde-dip.o",
+            ],
         },
         x86: {
             src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/32/libgcc.a",
-            repack_objects_to_keep: ["unwind-dw2.o", "unwind-dw2-fde-dip.o"],
+            repack_objects_to_keep: [
+                "unwind-dw2.o",
+                "unwind-dw2-fde-dip.o",
+            ],
         },
         x86_64: {
             src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/libgcc.a",
-            repack_objects_to_keep: ["unwind-dw2.o", "unwind-dw2-fde-dip.o"],
+            repack_objects_to_keep: [
+                "unwind-dw2.o",
+                "unwind-dw2-fde-dip.o",
+            ],
         },
     },
 }
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 928ca03..ff85661 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,3 @@
 [Builtin Hooks]
 gofmt = true
+bpfmt = true
diff --git a/android/androidmk.go b/android/androidmk.go
index d579e30..045cb59 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -46,7 +46,7 @@
 type AndroidMkData struct {
 	Class           string
 	SubName         string
-	DistFile        OptionalPath
+	DistFiles       TaggedDistFiles
 	OutputFile      OptionalPath
 	Disabled        bool
 	Include         string
@@ -58,7 +58,7 @@
 
 	Extra []AndroidMkExtraFunc
 
-	preamble bytes.Buffer
+	Entries AndroidMkEntries
 }
 
 type AndroidMkExtraFunc func(w io.Writer, outputFile Path)
@@ -72,7 +72,7 @@
 type AndroidMkEntries struct {
 	Class           string
 	SubName         string
-	DistFile        OptionalPath
+	DistFiles       TaggedDistFiles
 	OutputFile      OptionalPath
 	Disabled        bool
 	Include         string
@@ -137,6 +137,96 @@
 	a.EntryMap[name] = append(a.EntryMap[name], value...)
 }
 
+// Compute the list of Make strings to declare phone goals and dist-for-goals
+// calls from the module's dist and dists properties.
+func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+	amod := mod.(Module).base()
+	name := amod.BaseModuleName()
+
+	var ret []string
+
+	availableTaggedDists := TaggedDistFiles{}
+	if a.DistFiles != nil && len(a.DistFiles[""]) > 0 {
+		availableTaggedDists = a.DistFiles
+	} else if a.OutputFile.Valid() {
+		availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
+	}
+
+	// Iterate over this module's dist structs, merged from the dist and dists properties.
+	for _, dist := range amod.Dists() {
+		// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
+		goals := strings.Join(dist.Targets, " ")
+
+		// Get the tag representing the output files to be dist'd. e.g. ".jar", ".proguard_map"
+		var tag string
+		if dist.Tag == nil {
+			// If the dist struct does not specify a tag, use the default output files tag.
+			tag = ""
+		} else {
+			tag = *dist.Tag
+		}
+
+		// Get the paths of the output files to be dist'd, represented by the tag.
+		// Can be an empty list.
+		tagPaths := availableTaggedDists[tag]
+		if len(tagPaths) == 0 {
+			// Nothing to dist for this tag, continue to the next dist.
+			continue
+		}
+
+		if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
+			errorMessage := "Cannot apply dest/suffix for more than one dist " +
+				"file for %s goals in module %s. The list of dist files, " +
+				"which should have a single element, is:\n%s"
+			panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+		}
+
+		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+
+		// Create dist-for-goals calls for each path in the dist'd files.
+		for _, path := range tagPaths {
+			// It's possible that the Path is nil from errant modules. Be defensive here.
+			if path == nil {
+				tagName := "default" // for error message readability
+				if dist.Tag != nil {
+					tagName = *dist.Tag
+				}
+				panic(fmt.Errorf("Dist file should not be nil for the %s tag in %s", tagName, name))
+			}
+
+			dest := filepath.Base(path.String())
+
+			if dist.Dest != nil {
+				var err error
+				if dest, err = validateSafePath(*dist.Dest); err != nil {
+					// This was checked in ModuleBase.GenerateBuildActions
+					panic(err)
+				}
+			}
+
+			if dist.Suffix != nil {
+				ext := filepath.Ext(dest)
+				suffix := *dist.Suffix
+				dest = strings.TrimSuffix(dest, ext) + suffix + ext
+			}
+
+			if dist.Dir != nil {
+				var err error
+				if dest, err = validateSafePath(*dist.Dir, dest); err != nil {
+					// This was checked in ModuleBase.GenerateBuildActions
+					panic(err)
+				}
+			}
+
+			ret = append(
+				ret,
+				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+		}
+	}
+
+	return ret
+}
+
 func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
 	a.EntryMap = make(map[string][]string)
 	amod := mod.(Module).base()
@@ -149,42 +239,8 @@
 	a.Host_required = append(a.Host_required, amod.commonProperties.Host_required...)
 	a.Target_required = append(a.Target_required, amod.commonProperties.Target_required...)
 
-	// Fill in the header part.
-	if len(amod.commonProperties.Dist.Targets) > 0 {
-		distFile := a.DistFile
-		if !distFile.Valid() {
-			distFile = a.OutputFile
-		}
-		if distFile.Valid() {
-			dest := filepath.Base(distFile.String())
-
-			if amod.commonProperties.Dist.Dest != nil {
-				var err error
-				if dest, err = validateSafePath(*amod.commonProperties.Dist.Dest); err != nil {
-					// This was checked in ModuleBase.GenerateBuildActions
-					panic(err)
-				}
-			}
-
-			if amod.commonProperties.Dist.Suffix != nil {
-				ext := filepath.Ext(dest)
-				suffix := *amod.commonProperties.Dist.Suffix
-				dest = strings.TrimSuffix(dest, ext) + suffix + ext
-			}
-
-			if amod.commonProperties.Dist.Dir != nil {
-				var err error
-				if dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest); err != nil {
-					// This was checked in ModuleBase.GenerateBuildActions
-					panic(err)
-				}
-			}
-
-			goals := strings.Join(amod.commonProperties.Dist.Targets, " ")
-			fmt.Fprintln(&a.header, ".PHONY:", goals)
-			fmt.Fprintf(&a.header, "$(call dist-for-goals,%s,%s:%s)\n",
-				goals, distFile.String(), dest)
-		}
+	for _, distString := range a.GetDistForGoals(mod) {
+		fmt.Fprintf(&a.header, distString)
 	}
 
 	fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)")
@@ -427,10 +483,10 @@
 
 func (data *AndroidMkData) fillInData(config Config, bpPath string, mod blueprint.Module) {
 	// Get the preamble content through AndroidMkEntries logic.
-	entries := AndroidMkEntries{
+	data.Entries = AndroidMkEntries{
 		Class:           data.Class,
 		SubName:         data.SubName,
-		DistFile:        data.DistFile,
+		DistFiles:       data.DistFiles,
 		OutputFile:      data.OutputFile,
 		Disabled:        data.Disabled,
 		Include:         data.Include,
@@ -438,16 +494,12 @@
 		Host_required:   data.Host_required,
 		Target_required: data.Target_required,
 	}
-	entries.fillInEntries(config, bpPath, mod)
-
-	// preamble doesn't need the footer content.
-	entries.footer = bytes.Buffer{}
-	entries.write(&data.preamble)
+	data.Entries.fillInEntries(config, bpPath, mod)
 
 	// copy entries back to data since it is used in Custom
-	data.Required = entries.Required
-	data.Host_required = entries.Host_required
-	data.Target_required = entries.Target_required
+	data.Required = data.Entries.Required
+	data.Host_required = data.Entries.Host_required
+	data.Target_required = data.Entries.Target_required
 }
 
 func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
@@ -503,7 +555,9 @@
 		return
 	}
 
-	w.Write(data.preamble.Bytes())
+	// write preamble via Entries
+	data.Entries.footer = bytes.Buffer{}
+	data.Entries.write(w)
 
 	for _, extra := range data.Extra {
 		extra(w, data.OutputFile.Path())
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 71f8020..250f086 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"io"
 	"reflect"
 	"testing"
@@ -22,10 +23,12 @@
 
 type customModule struct {
 	ModuleBase
-	data AndroidMkData
+	data      AndroidMkData
+	distFiles TaggedDistFiles
 }
 
 func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	m.distFiles = m.GenerateTaggedDistFiles(ctx)
 }
 
 func (m *customModule) AndroidMk() AndroidMkData {
@@ -36,6 +39,26 @@
 	}
 }
 
+func (m *customModule) OutputFiles(tag string) (Paths, error) {
+	switch tag {
+	case "":
+		return PathsForTesting("one.out"), nil
+	case ".multiple":
+		return PathsForTesting("two.out", "three/four.out"), nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
+	return []AndroidMkEntries{
+		{
+			Class:     "CUSTOM_MODULE",
+			DistFiles: m.distFiles,
+		},
+	}
+}
+
 func customModuleFactory() Module {
 	module := &customModule{}
 	InitAndroidModule(module)
@@ -76,3 +99,159 @@
 	assertEqual([]string{"baz"}, m.data.Host_required)
 	assertEqual([]string{"qux"}, m.data.Target_required)
 }
+
+func TestGetDistForGoals(t *testing.T) {
+	testCases := []struct {
+		bp                     string
+		expectedAndroidMkLines []string
+	}{
+		{
+			bp: `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"]
+				}
+			}
+			`,
+			expectedAndroidMkLines: []string{
+				".PHONY: my_goal\n",
+				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
+			},
+		},
+		{
+			bp: `
+			custom {
+				name: "foo",
+				dists: [
+					{
+						targets: ["my_goal"],
+					},
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+			`,
+			expectedAndroidMkLines: []string{
+				".PHONY: my_goal\n",
+				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
+				".PHONY: my_second_goal my_third_goal\n",
+				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
+			},
+		},
+		{
+			bp: `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+				},
+				dists: [
+					{
+						targets: ["my_second_goal", "my_third_goal"],
+					},
+				],
+			}
+			`,
+			expectedAndroidMkLines: []string{
+				".PHONY: my_second_goal my_third_goal\n",
+				"$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
+				".PHONY: my_goal\n",
+				"$(call dist-for-goals,my_goal,one.out:one.out)\n",
+			},
+		},
+		{
+			bp: `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal", "my_other_goal"],
+					tag: ".multiple",
+				},
+				dists: [
+					{
+						targets: ["my_second_goal"],
+						tag: ".multiple",
+					},
+					{
+						targets: ["my_third_goal"],
+						dir: "test/dir",
+					},
+					{
+						targets: ["my_fourth_goal"],
+						suffix: ".suffix",
+					},
+					{
+						targets: ["my_fifth_goal"],
+						dest: "new-name",
+					},
+					{
+						targets: ["my_sixth_goal"],
+						dest: "new-name",
+						dir: "some/dir",
+						suffix: ".suffix",
+					},
+				],
+			}
+			`,
+			expectedAndroidMkLines: []string{
+				".PHONY: my_second_goal\n",
+				"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
+				"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
+				".PHONY: my_third_goal\n",
+				"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
+				".PHONY: my_fourth_goal\n",
+				"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
+				".PHONY: my_fifth_goal\n",
+				"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
+				".PHONY: my_sixth_goal\n",
+				"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
+				".PHONY: my_goal my_other_goal\n",
+				"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
+				"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		config := TestConfig(buildDir, nil, testCase.bp, nil)
+		config.inMake = true // Enable androidmk Singleton
+
+		ctx := NewTestContext()
+		ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
+		ctx.RegisterModuleType("custom", customModuleFactory)
+		ctx.Register(config)
+
+		_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+		FailIfErrored(t, errs)
+		_, errs = ctx.PrepareBuildActions(config)
+		FailIfErrored(t, errs)
+
+		module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+		entries := AndroidMkEntriesForTest(t, config, "", module)
+		if len(entries) != 1 {
+			t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+		}
+		androidMkLines := entries[0].GetDistForGoals(module)
+
+		if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
+			t.Errorf(
+				"Expected %d AndroidMk lines, got %d:\n%v",
+				len(testCase.expectedAndroidMkLines),
+				len(androidMkLines),
+				androidMkLines,
+			)
+		}
+		for idx, line := range androidMkLines {
+			expectedLine := testCase.expectedAndroidMkLines[idx]
+			if line != expectedLine {
+				t.Errorf(
+					"Expected AndroidMk line to be '%s', got '%s'",
+					line,
+					expectedLine,
+				)
+			}
+		}
+	}
+}
diff --git a/android/apex.go b/android/apex.go
index 30152db..47f07ca 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -124,6 +124,10 @@
 	// the private part of the listed APEXes even when it is not included in the
 	// APEXes.
 	TestFor() []string
+
+	// Returns nil if this module supports sdkVersion
+	// Otherwise, returns error with reason
+	ShouldSupportSdkVersion(ctx BaseModuleContext, sdkVersion int) error
 }
 
 type ApexProperties struct {
@@ -477,3 +481,125 @@
 		},
 	})
 }
+
+// TODO(b/158059172): remove minSdkVersion allowlist
+var minSdkVersionAllowlist = map[string]int{
+	"adbd":                  30,
+	"android.net.ipsec.ike": 30,
+	"androidx-constraintlayout_constraintlayout-solver": 30,
+	"androidx.annotation_annotation":                    28,
+	"androidx.arch.core_core-common":                    28,
+	"androidx.collection_collection":                    28,
+	"androidx.lifecycle_lifecycle-common":               28,
+	"apache-commons-compress":                           29,
+	"bouncycastle_ike_digests":                          30,
+	"brotli-java":                                       29,
+	"captiveportal-lib":                                 28,
+	"flatbuffer_headers":                                30,
+	"framework-permission":                              30,
+	"framework-statsd":                                  30,
+	"gemmlowp_headers":                                  30,
+	"ike-internals":                                     30,
+	"kotlinx-coroutines-android":                        28,
+	"kotlinx-coroutines-core":                           28,
+	"libadb_crypto":                                     30,
+	"libadb_pairing_auth":                               30,
+	"libadb_pairing_connection":                         30,
+	"libadb_pairing_server":                             30,
+	"libadb_protos":                                     30,
+	"libadb_tls_connection":                             30,
+	"libadbconnection_client":                           30,
+	"libadbconnection_server":                           30,
+	"libadbd_core":                                      30,
+	"libadbd_services":                                  30,
+	"libadbd":                                           30,
+	"libapp_processes_protos_lite":                      30,
+	"libasyncio":                                        30,
+	"libbrotli":                                         30,
+	"libbuildversion":                                   30,
+	"libcrypto_static":                                  30,
+	"libcrypto_utils":                                   30,
+	"libdiagnose_usb":                                   30,
+	"libeigen":                                          30,
+	"liblz4":                                            30,
+	"libmdnssd":                                         30,
+	"libneuralnetworks_common":                          30,
+	"libneuralnetworks_headers":                         30,
+	"libneuralnetworks":                                 30,
+	"libprocpartition":                                  30,
+	"libprotobuf-java-lite":                             30,
+	"libprotoutil":                                      30,
+	"libqemu_pipe":                                      30,
+	"libstats_jni":                                      30,
+	"libstatslog_statsd":                                30,
+	"libstatsmetadata":                                  30,
+	"libstatspull":                                      30,
+	"libstatssocket":                                    30,
+	"libsync":                                           30,
+	"libtextclassifier_hash_headers":                    30,
+	"libtextclassifier_hash_static":                     30,
+	"libtflite_kernel_utils":                            30,
+	"libwatchdog":                                       29,
+	"libzstd":                                           30,
+	"metrics-constants-protos":                          28,
+	"net-utils-framework-common":                        29,
+	"permissioncontroller-statsd":                       28,
+	"philox_random_headers":                             30,
+	"philox_random":                                     30,
+	"service-permission":                                30,
+	"service-statsd":                                    30,
+	"statsd-aidl-ndk_platform":                          30,
+	"statsd":                                            30,
+	"tensorflow_headers":                                30,
+	"xz-java":                                           29,
+}
+
+// Function called while walking an APEX's payload dependencies.
+//
+// Return true if the `to` module should be visited, false otherwise.
+type PayloadDepsCallback func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool
+
+// UpdatableModule represents updatable APEX/APK
+type UpdatableModule interface {
+	Module
+	WalkPayloadDeps(ctx ModuleContext, do PayloadDepsCallback)
+}
+
+// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version accordingly
+func CheckMinSdkVersion(m UpdatableModule, ctx ModuleContext, minSdkVersion int) {
+	// do not enforce min_sdk_version for host
+	if ctx.Host() {
+		return
+	}
+
+	// do not enforce for coverage build
+	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled() {
+		return
+	}
+
+	// do not enforce deps.min_sdk_version if APEX/APK doesn't set min_sdk_version or
+	// min_sdk_version is not finalized (e.g. current or codenames)
+	if minSdkVersion == FutureApiLevel {
+		return
+	}
+
+	m.WalkPayloadDeps(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
+		if externalDep {
+			// external deps are outside the payload boundary, which is "stable" interface.
+			// We don't have to check min_sdk_version for external dependencies.
+			return false
+		}
+		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+			return false
+		}
+		if err := to.ShouldSupportSdkVersion(ctx, minSdkVersion); err != nil {
+			toName := ctx.OtherModuleName(to)
+			if ver, ok := minSdkVersionAllowlist[toName]; !ok || ver > minSdkVersion {
+				ctx.OtherModuleErrorf(to, "should support min_sdk_version(%v) for %q: %v. Dependency path: %s",
+					minSdkVersion, ctx.ModuleName(), err.Error(), ctx.GetPathString(false))
+				return false
+			}
+		}
+		return true
+	})
+}
diff --git a/android/config.go b/android/config.go
index b906108..6f73a12 100644
--- a/android/config.go
+++ b/android/config.go
@@ -406,6 +406,14 @@
 		return Config{}, err
 	}
 
+	if Bool(config.productVariables.GcovCoverage) && Bool(config.productVariables.ClangCoverage) {
+		return Config{}, fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
+	}
+
+	config.productVariables.Native_coverage = proptools.BoolPtr(
+		Bool(config.productVariables.GcovCoverage) ||
+			Bool(config.productVariables.ClangCoverage))
+
 	return Config{config}, nil
 }
 
@@ -1044,7 +1052,7 @@
 // represents any path.
 func (c *deviceConfig) JavaCoverageEnabledForPath(path string) bool {
 	coverage := false
-	if c.config.productVariables.JavaCoveragePaths == nil ||
+	if len(c.config.productVariables.JavaCoveragePaths) == 0 ||
 		InList("*", c.config.productVariables.JavaCoveragePaths) ||
 		HasAnyPrefix(path, c.config.productVariables.JavaCoveragePaths) {
 		coverage = true
@@ -1057,18 +1065,20 @@
 	return coverage
 }
 
-func (c *config) NativeLineCoverage() bool {
-	return Bool(c.productVariables.NativeLineCoverage)
-}
-
+// Returns true if gcov or clang coverage is enabled.
 func (c *deviceConfig) NativeCoverageEnabled() bool {
-	return Bool(c.config.productVariables.Native_coverage) || Bool(c.config.productVariables.NativeLineCoverage)
+	return Bool(c.config.productVariables.GcovCoverage) ||
+		Bool(c.config.productVariables.ClangCoverage)
 }
 
 func (c *deviceConfig) ClangCoverageEnabled() bool {
 	return Bool(c.config.productVariables.ClangCoverage)
 }
 
+func (c *deviceConfig) GcovCoverageEnabled() bool {
+	return Bool(c.config.productVariables.GcovCoverage)
+}
+
 // NativeCoverageEnabledForPath returns whether (GCOV- or Clang-based) native
 // code coverage is enabled for path. By default, coverage is not enabled for a
 // given path unless it is part of the NativeCoveragePaths product variable (and
diff --git a/android/hooks.go b/android/hooks.go
index f43a007..85fc081 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -120,6 +120,7 @@
 
 type InstallHookContext interface {
 	ModuleContext
+	SrcPath() Path
 	Path() InstallPath
 	Symlink() bool
 }
@@ -134,10 +135,17 @@
 
 type installHookContext struct {
 	ModuleContext
+	srcPath Path
 	path    InstallPath
 	symlink bool
 }
 
+var _ InstallHookContext = &installHookContext{}
+
+func (x *installHookContext) SrcPath() Path {
+	return x.srcPath
+}
+
 func (x *installHookContext) Path() InstallPath {
 	return x.path
 }
@@ -146,10 +154,11 @@
 	return x.symlink
 }
 
-func (x *hooks) runInstallHooks(ctx ModuleContext, path InstallPath, symlink bool) {
+func (x *hooks) runInstallHooks(ctx ModuleContext, srcPath Path, path InstallPath, symlink bool) {
 	if len(x.install) > 0 {
 		mctx := &installHookContext{
 			ModuleContext: ctx,
+			srcPath:       srcPath,
 			path:          path,
 			symlink:       symlink,
 		}
diff --git a/android/module.go b/android/module.go
index f24047c..06079ca 100644
--- a/android/module.go
+++ b/android/module.go
@@ -315,6 +315,28 @@
 	return qualifiedModuleName{pkg: pkg, name: ""}
 }
 
+type Dist struct {
+	// Copy the output of this module to the $DIST_DIR when `dist` is specified on the
+	// command line and any of these targets are also on the command line, or otherwise
+	// built
+	Targets []string `android:"arch_variant"`
+
+	// The name of the output artifact. This defaults to the basename of the output of
+	// the module.
+	Dest *string `android:"arch_variant"`
+
+	// The directory within the dist directory to store the artifact. Defaults to the
+	// top level directory ("").
+	Dir *string `android:"arch_variant"`
+
+	// A suffix to add to the artifact file name (before any extension).
+	Suffix *string `android:"arch_variant"`
+
+	// A string tag to select the OutputFiles associated with the tag. Defaults to the
+	// the empty "" string.
+	Tag *string `android:"arch_variant"`
+}
+
 type nameProperties struct {
 	// The name of the module.  Must be unique across all modules.
 	Name *string
@@ -454,23 +476,13 @@
 	// relative path to a file to include in the list of notices for the device
 	Notice *string `android:"path"`
 
-	Dist struct {
-		// copy the output of this module to the $DIST_DIR when `dist` is specified on the
-		// command line and  any of these targets are also on the command line, or otherwise
-		// built
-		Targets []string `android:"arch_variant"`
+	// configuration to distribute output files from this module to the distribution
+	// directory (default: $OUT/dist, configurable with $DIST_DIR)
+	Dist Dist `android:"arch_variant"`
 
-		// The name of the output artifact. This defaults to the basename of the output of
-		// the module.
-		Dest *string `android:"arch_variant"`
-
-		// The directory within the dist directory to store the artifact. Defaults to the
-		// top level directory ("").
-		Dir *string `android:"arch_variant"`
-
-		// A suffix to add to the artifact file name (before any extension).
-		Suffix *string `android:"arch_variant"`
-	} `android:"arch_variant"`
+	// a list of configurations to distribute output files from this module to the
+	// distribution directory (default: $OUT/dist, configurable with $DIST_DIR)
+	Dists []Dist `android:"arch_variant"`
 
 	// The OsType of artifacts that this module variant is responsible for creating.
 	//
@@ -537,6 +549,14 @@
 	ImageVariation string `blueprint:"mutated"`
 }
 
+// A map of OutputFile tag keys to Paths, for disting purposes.
+type TaggedDistFiles map[string]Paths
+
+func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
+	// The default OutputFile tag is the empty "" string.
+	return TaggedDistFiles{"": paths}
+}
+
 type hostAndDeviceProperties struct {
 	// If set to true, build a variant of the module for the host.  Defaults to false.
 	Host_supported *bool
@@ -815,6 +835,41 @@
 	return m.visibilityPropertyInfo
 }
 
+func (m *ModuleBase) Dists() []Dist {
+	if len(m.commonProperties.Dist.Targets) > 0 {
+		// Make a copy of the underlying Dists slice to protect against
+		// backing array modifications with repeated calls to this method.
+		distsCopy := append([]Dist(nil), m.commonProperties.Dists...)
+		return append(distsCopy, m.commonProperties.Dist)
+	} else {
+		return m.commonProperties.Dists
+	}
+}
+
+func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
+	distFiles := make(TaggedDistFiles)
+	for _, dist := range m.Dists() {
+		var tag string
+		var distFilesForTag Paths
+		if dist.Tag == nil {
+			tag = ""
+		} else {
+			tag = *dist.Tag
+		}
+		distFilesForTag, err := m.base().module.(OutputFileProducer).OutputFiles(tag)
+		if err != nil {
+			ctx.PropertyErrorf("dist.tag", "%s", err.Error())
+		}
+		for _, distFile := range distFilesForTag {
+			if distFile != nil && !distFiles[tag].containsPath(distFile) {
+				distFiles[tag] = append(distFiles[tag], distFile)
+			}
+		}
+	}
+
+	return distFiles
+}
+
 func (m *ModuleBase) Target() Target {
 	return m.commonProperties.CompileTarget
 }
@@ -1959,7 +2014,7 @@
 	rule blueprint.Rule, deps []Path) InstallPath {
 
 	fullInstallPath := installPath.Join(m, name)
-	m.module.base().hooks.runInstallHooks(m, fullInstallPath, false)
+	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
 
 	if !m.skipInstall(fullInstallPath) {
 
@@ -1993,7 +2048,7 @@
 
 func (m *moduleContext) InstallSymlink(installPath InstallPath, name string, srcPath InstallPath) InstallPath {
 	fullInstallPath := installPath.Join(m, name)
-	m.module.base().hooks.runInstallHooks(m, fullInstallPath, true)
+	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, true)
 
 	if !m.skipInstall(fullInstallPath) {
 
@@ -2022,7 +2077,7 @@
 // (e.g. /apex/...)
 func (m *moduleContext) InstallAbsoluteSymlink(installPath InstallPath, name string, absPath string) InstallPath {
 	fullInstallPath := installPath.Join(m, name)
-	m.module.base().hooks.runInstallHooks(m, fullInstallPath, true)
+	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
 	if !m.skipInstall(fullInstallPath) {
 		m.Build(pctx, BuildParams{
diff --git a/android/neverallow.go b/android/neverallow.go
index 26e42e6..526d399 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -145,11 +145,18 @@
 		"prebuilts",
 	}
 
-	// Core library constraints. The sdk_version: "none" can only be used in core library projects.
+	// Additional whitelisted path only used for ART testing, which needs access to core library
+	// targets. This does not affect the contents of a device image (system, vendor, etc.).
+	var artTests = []string{
+		"art/test",
+	}
+
+	// Core library constraints. The sdk_version: "none" can only be used in core library projects and ART tests.
 	// Access to core library targets is restricted using visibility rules.
 	rules := []Rule{
 		NeverAllow().
 			NotIn(coreLibraryProjects...).
+			NotIn(artTests...).
 			With("sdk_version", "none").
 			WithoutMatcher("name", Regexp("^android_.*stubs_current$")),
 	}
diff --git a/android/override_module.go b/android/override_module.go
index 7e58890..6b246db 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -42,6 +42,11 @@
 	setOverridingProperties(properties []interface{})
 
 	getOverrideModuleProperties() *OverrideModuleProperties
+
+	// Internal funcs to handle interoperability between override modules and prebuilts.
+	// i.e. cases where an overriding module, too, is overridden by a prebuilt module.
+	setOverriddenByPrebuilt(overridden bool)
+	getOverriddenByPrebuilt() bool
 }
 
 // Base module struct for override module types
@@ -49,6 +54,8 @@
 	moduleProperties OverrideModuleProperties
 
 	overridingProperties []interface{}
+
+	overriddenByPrebuilt bool
 }
 
 type OverrideModuleProperties struct {
@@ -74,6 +81,14 @@
 	return proptools.String(o.moduleProperties.Base)
 }
 
+func (o *OverrideModuleBase) setOverriddenByPrebuilt(overridden bool) {
+	o.overriddenByPrebuilt = overridden
+}
+
+func (o *OverrideModuleBase) getOverriddenByPrebuilt() bool {
+	return o.overriddenByPrebuilt
+}
+
 func InitOverrideModule(m OverrideModule) {
 	m.setOverridingProperties(m.GetProperties())
 
@@ -208,21 +223,24 @@
 // next phase.
 func overrideModuleDepsMutator(ctx BottomUpMutatorContext) {
 	if module, ok := ctx.Module().(OverrideModule); ok {
-		// Skip this overriding module if there's a prebuilt module that overrides it with prefer flag.
-		overriddenByPrebuilt := false
+		base := String(module.getOverrideModuleProperties().Base)
+		if !ctx.OtherModuleExists(base) {
+			ctx.PropertyErrorf("base", "%q is not a valid module name", base)
+			return
+		}
+		// See if there's a prebuilt module that overrides this override module with prefer flag,
+		// in which case we call SkipInstall on the corresponding variant later.
 		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(dep Module) {
 			prebuilt, ok := dep.(PrebuiltInterface)
 			if !ok {
 				panic("PrebuiltDepTag leads to a non-prebuilt module " + dep.Name())
 			}
 			if prebuilt.Prebuilt().UsePrebuilt() {
-				overriddenByPrebuilt = true
+				module.setOverriddenByPrebuilt(true)
 				return
 			}
 		})
-		if !overriddenByPrebuilt {
-			ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)
-		}
+		ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)
 	}
 }
 
@@ -258,6 +276,10 @@
 		ctx.AliasVariation(variants[0])
 		for i, o := range overrides {
 			mods[i+1].(OverridableModule).override(ctx, o)
+			if o.getOverriddenByPrebuilt() {
+				// The overriding module itself, too, is overridden by a prebuilt. Skip its installation.
+				mods[i+1].SkipInstall()
+			}
 		}
 	} else if o, ok := ctx.Module().(OverrideModule); ok {
 		// Create a variant of the overriding module with its own name. This matches the above local
diff --git a/android/paths.go b/android/paths.go
index bed6f3f..d8d51a7 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -220,6 +220,15 @@
 // Paths is a slice of Path objects, with helpers to operate on the collection.
 type Paths []Path
 
+func (paths Paths) containsPath(path Path) bool {
+	for _, p := range paths {
+		if p == path {
+			return true
+		}
+	}
+	return false
+}
+
 // PathsForSource returns Paths rooted from SrcDir
 func PathsForSource(ctx PathContext, paths []string) Paths {
 	ret := make(Paths, len(paths))
diff --git a/android/variable.go b/android/variable.go
index b2149c3..2c8bd07 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -270,12 +270,14 @@
 	JavaCoveragePaths        []string `json:",omitempty"`
 	JavaCoverageExcludePaths []string `json:",omitempty"`
 
-	NativeLineCoverage         *bool    `json:",omitempty"`
-	Native_coverage            *bool    `json:",omitempty"`
+	GcovCoverage               *bool    `json:",omitempty"`
 	ClangCoverage              *bool    `json:",omitempty"`
 	NativeCoveragePaths        []string `json:",omitempty"`
 	NativeCoverageExcludePaths []string `json:",omitempty"`
 
+	// Set by NewConfig
+	Native_coverage *bool
+
 	SanitizeHost       []string `json:",omitempty"`
 	SanitizeDevice     []string `json:",omitempty"`
 	SanitizeDeviceDiag []string `json:",omitempty"`
diff --git a/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index 4aadbe8..03cf74d 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -34,6 +34,7 @@
 	defs              []bpparser.Definition
 	localAssignments  map[string]*bpparser.Property
 	globalAssignments map[string]*bpparser.Expression
+	variableRenames   map[string]string
 	scope             mkparser.Scope
 	module            *bpparser.Module
 
@@ -43,6 +44,10 @@
 	inModule bool
 }
 
+var invalidVariableStringToReplacement = map[string]string{
+	"-": "_dash_",
+}
+
 func (f *bpFile) insertComment(s string) {
 	f.comments = append(f.comments, &bpparser.CommentGroup{
 		Comments: []*bpparser.Comment{
@@ -120,6 +125,7 @@
 		scope:             androidScope(),
 		localAssignments:  make(map[string]*bpparser.Property),
 		globalAssignments: make(map[string]*bpparser.Expression),
+		variableRenames:   make(map[string]string),
 	}
 
 	var conds []*conditional
@@ -224,6 +230,25 @@
 	return string(out), errs
 }
 
+func renameVariableWithInvalidCharacters(name string) string {
+	renamed := ""
+	for invalid, replacement := range invalidVariableStringToReplacement {
+		if strings.Contains(name, invalid) {
+			renamed = strings.ReplaceAll(name, invalid, replacement)
+		}
+	}
+
+	return renamed
+}
+
+func invalidVariableStrings() string {
+	invalidStrings := make([]string, 0, len(invalidVariableStringToReplacement))
+	for s := range invalidVariableStringToReplacement {
+		invalidStrings = append(invalidStrings, "\""+s+"\"")
+	}
+	return strings.Join(invalidStrings, ", ")
+}
+
 func handleAssignment(file *bpFile, assignment *mkparser.Assignment, c *conditional) {
 	if !assignment.Name.Const() {
 		file.errorf(assignment, "unsupported non-const variable name")
@@ -238,6 +263,12 @@
 	name := assignment.Name.Value(nil)
 	prefix := ""
 
+	if newName := renameVariableWithInvalidCharacters(name); newName != "" {
+		file.warnf("Variable names cannot contain: %s. Renamed \"%s\" to \"%s\"", invalidVariableStrings(), name, newName)
+		file.variableRenames[name] = newName
+		name = newName
+	}
+
 	if strings.HasPrefix(name, "LOCAL_") {
 		for _, x := range propertyPrefixes {
 			if strings.HasSuffix(name, "_"+x.mk) {
@@ -341,11 +372,11 @@
 	var err error
 	switch typ {
 	case bpparser.ListType:
-		exp, err = makeToListExpression(val, file.scope)
+		exp, err = makeToListExpression(val, file)
 	case bpparser.StringType:
-		exp, err = makeToStringExpression(val, file.scope)
+		exp, err = makeToStringExpression(val, file)
 	case bpparser.BoolType:
-		exp, err = makeToBoolExpression(val)
+		exp, err = makeToBoolExpression(val, file)
 	default:
 		panic("unknown type")
 	}
@@ -358,7 +389,6 @@
 }
 
 func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error {
-
 	if prefix != "" {
 		name = prefix + "." + name
 	}
@@ -428,6 +458,9 @@
 			}
 			file.defs = append(file.defs, a)
 		} else {
+			if _, ok := file.globalAssignments[name]; ok {
+				return fmt.Errorf("cannot assign a variable multiple times: \"%s\"", name)
+			}
 			a := &bpparser.Assignment{
 				Name:      name,
 				NamePos:   pos,
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 54bd586..2448acc 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -876,7 +876,7 @@
 prebuilt_etc {
 	name: "etc.test1",
 	src: "mymod",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 
 }
 `,
@@ -896,7 +896,7 @@
 	name: "etc.test1",
 
 	src: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 
 }
 `,
@@ -913,7 +913,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
     device_specific: true,
 
 }
@@ -931,7 +931,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	product_specific: true,
 
 
@@ -950,7 +950,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	product_specific: true,
 
 }
@@ -968,7 +968,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	system_ext_specific: true,
 
 }
@@ -986,7 +986,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	system_ext_specific: true,
 
 
@@ -1005,7 +1005,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1023,7 +1023,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1041,7 +1041,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	proprietary: true,
 
 }
@@ -1059,7 +1059,7 @@
 		expected: `
 prebuilt_etc {
 	name: "etc.test1",
-	sub_dir: "foo/bar",
+	relative_install_path: "foo/bar",
 	recovery: true,
 
 }
@@ -1098,7 +1098,7 @@
 	name: "foo",
 
 	src: "foo.txt",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1174,7 +1174,7 @@
 	name: "foo",
 
 	src: "foo.txt",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1193,7 +1193,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1212,7 +1212,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 }
 `,
 	},
@@ -1231,7 +1231,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 	proprietary: true,
 }
 `,
@@ -1251,7 +1251,7 @@
 	name: "foo",
 
 	src: "foo.fw",
-	sub_dir: "bar",
+	relative_install_path: "bar",
 	proprietary: true,
 }
 `,
@@ -1367,6 +1367,57 @@
 `,
 	},
 	{
+		desc: "dashed_variable gets renamed",
+		in: `
+		include $(CLEAR_VARS)
+
+		dashed-variable:= a.cpp
+
+		LOCAL_MODULE:= test
+		LOCAL_SRC_FILES:= $(dashed-variable)
+		include $(BUILD_EXECUTABLE)
+		`,
+		expected: `
+
+// ANDROIDMK TRANSLATION WARNING: Variable names cannot contain: "-". Renamed "dashed-variable" to "dashed_dash_variable"
+dashed_dash_variable = ["a.cpp"]
+cc_binary {
+
+    name: "test",
+    srcs: dashed_dash_variable,
+}
+`,
+	},
+	{
+		desc: "variableReassigned",
+		in: `
+include $(CLEAR_VARS)
+
+src_files:= a.cpp
+
+LOCAL_SRC_FILES:= $(src_files)
+LOCAL_MODULE:= test
+include $(BUILD_EXECUTABLE)
+
+# clear locally used variable
+src_files:=
+`,
+		expected: `
+
+
+src_files = ["a.cpp"]
+cc_binary {
+    name: "test",
+
+    srcs: src_files,
+}
+
+// clear locally used variable
+// ANDROIDMK TRANSLATION ERROR: cannot assign a variable multiple times: "src_files"
+// src_files :=
+`,
+	},
+	{
 		desc: "undefined_boolean_var",
 		in: `
 include $(CLEAR_VARS)
diff --git a/androidmk/androidmk/values.go b/androidmk/androidmk/values.go
index 6b18a65..9618142 100644
--- a/androidmk/androidmk/values.go
+++ b/androidmk/androidmk/values.go
@@ -60,8 +60,7 @@
 	}, nil
 }
 
-func makeToStringExpression(ms *mkparser.MakeString, scope mkparser.Scope) (bpparser.Expression, error) {
-
+func makeToStringExpression(ms *mkparser.MakeString, file *bpFile) (bpparser.Expression, error) {
 	var val bpparser.Expression
 	var err error
 
@@ -70,18 +69,18 @@
 	}
 
 	for i, s := range ms.Strings[1:] {
-		if ret, ok := ms.Variables[i].EvalFunction(scope); ok {
+		if ret, ok := ms.Variables[i].EvalFunction(file.scope); ok {
 			if len(ret) > 1 {
 				return nil, fmt.Errorf("Unexpected list value %s", ms.Dump())
 			}
 			val, err = addValues(val, stringToStringValue(ret[0]))
 		} else {
-			name := ms.Variables[i].Name
-			if !name.Const() {
-				return nil, fmt.Errorf("Unsupported non-const variable name %s", name.Dump())
+			name, err := extractVariableName(ms.Variables[i].Name, file)
+			if err != nil {
+				return nil, err
 			}
 			tmp := &bpparser.Variable{
-				Name:  name.Value(nil),
+				Name:  name,
 				Value: &bpparser.String{},
 			}
 
@@ -125,8 +124,7 @@
 
 }
 
-func makeToListExpression(ms *mkparser.MakeString, scope mkparser.Scope) (bpparser.Expression, error) {
-
+func makeToListExpression(ms *mkparser.MakeString, file *bpFile) (bpparser.Expression, error) {
 	fields := ms.Split(" \t")
 
 	var listOfListValues []bpparser.Expression
@@ -135,14 +133,14 @@
 
 	for _, f := range fields {
 		if len(f.Variables) == 1 && f.Strings[0] == "" && f.Strings[1] == "" {
-			if ret, ok := f.Variables[0].EvalFunction(scope); ok {
+			if ret, ok := f.Variables[0].EvalFunction(file.scope); ok {
 				listValue.Values = append(listValue.Values, stringListToStringValueList(ret)...)
 			} else {
-				// Variable by itself, variable is probably a list
-				if !f.Variables[0].Name.Const() {
-					return nil, fmt.Errorf("unsupported non-const variable name")
+				name, err := extractVariableName(f.Variables[0].Name, file)
+				if err != nil {
+					return nil, err
 				}
-				if f.Variables[0].Name.Value(nil) == "TOP" {
+				if name == "TOP" {
 					listValue.Values = append(listValue.Values, &bpparser.String{
 						Value: ".",
 					})
@@ -151,14 +149,14 @@
 						listOfListValues = append(listOfListValues, listValue)
 					}
 					listOfListValues = append(listOfListValues, &bpparser.Variable{
-						Name:  f.Variables[0].Name.Value(nil),
+						Name:  name,
 						Value: &bpparser.List{},
 					})
 					listValue = &bpparser.List{}
 				}
 			}
 		} else {
-			s, err := makeToStringExpression(f, scope)
+			s, err := makeToStringExpression(f, file)
 			if err != nil {
 				return nil, err
 			}
@@ -208,15 +206,15 @@
 	}, nil
 }
 
-func makeToBoolExpression(ms *mkparser.MakeString) (bpparser.Expression, error) {
+func makeToBoolExpression(ms *mkparser.MakeString, file *bpFile) (bpparser.Expression, error) {
 	if !ms.Const() {
 		if len(ms.Variables) == 1 && ms.Strings[0] == "" && ms.Strings[1] == "" {
-			name := ms.Variables[0].Name
-			if !name.Const() {
-				return nil, fmt.Errorf("unsupported non-const variable name")
+			name, err := extractVariableName(ms.Variables[0].Name, file)
+			if err != nil {
+				return nil, err
 			}
 			return &bpparser.Variable{
-				Name:  name.Value(nil),
+				Name:  name,
 				Value: &bpparser.Bool{},
 			}, nil
 		} else {
@@ -226,3 +224,17 @@
 
 	return stringToBoolValue(ms.Value(nil))
 }
+
+func extractVariableName(name *mkparser.MakeString, file *bpFile) (string, error) {
+	if !name.Const() {
+		return "", fmt.Errorf("Unsupported non-const variable name %s", name.Dump())
+	}
+
+	variableName := name.Value(nil)
+
+	if newName, ok := file.variableRenames[variableName]; ok {
+		variableName = newName
+	}
+
+	return variableName, nil
+}
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 4dd14d8..7595238 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -33,14 +33,7 @@
 			Disabled: true,
 		}
 	}
-	writers := []android.AndroidMkData{}
-	writers = append(writers, a.androidMkForType())
-	return android.AndroidMkData{
-		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
-			for _, data := range writers {
-				data.Custom(w, name, prefix, moduleDir, data)
-			}
-		}}
+	return a.androidMkForType()
 }
 
 func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, apexName, moduleDir string) []string {
@@ -308,6 +301,20 @@
 				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
 				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+apexType.suffix())
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
+
+				// Because apex writes .mk with Custom(), we need to write manually some common properties
+				// which are available via data.Entries
+				commonProperties := []string{
+					"LOCAL_INIT_RC", "LOCAL_VINTF_FRAGMENTS",
+					"LOCAL_PROPRIETARY_MODULE", "LOCAL_VENDOR_MODULE", "LOCAL_ODM_MODULE", "LOCAL_PRODUCT_MODULE", "LOCAL_SYSTEM_EXT_MODULE",
+					"LOCAL_MODULE_OWNER",
+				}
+				for _, name := range commonProperties {
+					if value, ok := data.Entries.EntryMap[name]; ok {
+						fmt.Fprintln(w, name+" := "+strings.Join(value, " "))
+					}
+				}
+
 				if len(a.overridableProperties.Overrides) > 0 {
 					fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(a.overridableProperties.Overrides, " "))
 				}
diff --git a/apex/apex.go b/apex/apex.go
index a4af7aa..d0c1a09 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"path"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -369,7 +368,6 @@
 		"libbinderthreadstateutils",
 		"libbluetooth-types-header",
 		"libbufferhub_headers",
-		"libc_scudo",
 		"libcodec2",
 		"libcodec2_headers",
 		"libcodec2_hidl@1.0",
@@ -985,9 +983,6 @@
 	// List of providing APEXes' names so that this APEX can depend on provided shared libraries.
 	Uses []string
 
-	// A txt file containing list of files that are allowed to be included in this APEX.
-	Allowed_files *string
-
 	// package format of this apex variant; could be non-flattened, flattened, or zip.
 	// imageApex, zipApex or flattened
 	ApexType apexPackaging `blueprint:"mutated"`
@@ -1063,6 +1058,9 @@
 	// Apex Container Package Name.
 	// Override value for attribute package:name in AndroidManifest.xml
 	Package_name string
+
+	// A txt file containing list of files that are allowed to be included in this APEX.
+	Allowed_files *string `android:"path"`
 }
 
 type apexPackaging int
@@ -1243,7 +1241,7 @@
 	container_certificate_file android.Path
 	container_private_key_file android.Path
 
-	fileContexts android.Path
+	fileContexts android.WritablePath
 
 	// list of files to be included in this apex
 	filesInfo []apexFile
@@ -1331,6 +1329,7 @@
 
 	targets := ctx.MultiTargets()
 	config := ctx.DeviceConfig()
+	imageVariation := a.getImageVariation(ctx)
 
 	a.combineProperties(ctx)
 
@@ -1350,13 +1349,13 @@
 				Jni_libs:           a.properties.Jni_libs,
 				Binaries:           nil,
 			},
-			target, a.getImageVariation(config))
+			target, imageVariation)
 
 		// Add native modules targetting both ABIs
 		addDependenciesForNativeModules(ctx,
 			a.properties.Multilib.Both,
 			target,
-			a.getImageVariation(config))
+			imageVariation)
 
 		isPrimaryAbi := i == 0
 		if isPrimaryAbi {
@@ -1369,13 +1368,13 @@
 					Jni_libs:           nil,
 					Binaries:           a.properties.Binaries,
 				},
-				target, a.getImageVariation(config))
+				target, imageVariation)
 
 			// Add native modules targetting the first ABI
 			addDependenciesForNativeModules(ctx,
 				a.properties.Multilib.First,
 				target,
-				a.getImageVariation(config))
+				imageVariation)
 		}
 
 		switch target.Arch.ArchType.Multilib {
@@ -1384,24 +1383,24 @@
 			addDependenciesForNativeModules(ctx,
 				a.properties.Multilib.Lib32,
 				target,
-				a.getImageVariation(config))
+				imageVariation)
 
 			addDependenciesForNativeModules(ctx,
 				a.properties.Multilib.Prefer32,
 				target,
-				a.getImageVariation(config))
+				imageVariation)
 		case "lib64":
 			// Add native modules targetting 64-bit ABI
 			addDependenciesForNativeModules(ctx,
 				a.properties.Multilib.Lib64,
 				target,
-				a.getImageVariation(config))
+				imageVariation)
 
 			if !has32BitTarget {
 				addDependenciesForNativeModules(ctx,
 					a.properties.Multilib.Prefer32,
 					target,
-					a.getImageVariation(config))
+					imageVariation)
 			}
 		}
 	}
@@ -1454,6 +1453,9 @@
 }
 
 func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
+	if a.overridableProperties.Allowed_files != nil {
+		android.ExtractSourceDeps(ctx, a.overridableProperties.Allowed_files)
+	}
 	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
 		androidAppTag, a.overridableProperties.Apps...)
 	ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(),
@@ -1500,15 +1502,33 @@
 	return proptools.Bool(a.properties.Test_only_unsigned_payload)
 }
 
-func (a *apexBundle) getImageVariation(config android.DeviceConfig) string {
+func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
+	deviceConfig := ctx.DeviceConfig()
 	if a.vndkApex {
-		return cc.VendorVariationPrefix + a.vndkVersion(config)
+		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
 	}
-	if config.VndkVersion() != "" && proptools.Bool(a.properties.Use_vendor) {
-		return cc.VendorVariationPrefix + config.PlatformVndkVersion()
-	} else {
-		return android.CoreVariation
+
+	var prefix string
+	var vndkVersion string
+	if deviceConfig.VndkVersion() != "" {
+		if proptools.Bool(a.properties.Use_vendor) {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.PlatformVndkVersion()
+		} else if a.SocSpecific() || a.DeviceSpecific() {
+			prefix = cc.VendorVariationPrefix
+			vndkVersion = deviceConfig.VndkVersion()
+		} else if a.ProductSpecific() {
+			prefix = cc.ProductVariationPrefix
+			vndkVersion = deviceConfig.ProductVndkVersion()
+		}
 	}
+	if vndkVersion == "current" {
+		vndkVersion = deviceConfig.PlatformVndkVersion()
+	}
+	if vndkVersion != "" {
+		return prefix + vndkVersion
+	}
+	return android.CoreVariation
 }
 
 func (a *apexBundle) EnableSanitizer(sanitizerName string) {
@@ -1540,7 +1560,7 @@
 		for _, target := range ctx.MultiTargets() {
 			if target.Arch.ArchType.Multilib == "lib64" {
 				ctx.AddFarVariationDependencies(append(target.Variations(), []blueprint.Variation{
-					{Mutator: "image", Variation: a.getImageVariation(ctx.DeviceConfig())},
+					{Mutator: "image", Variation: a.getImageVariation(ctx)},
 					{Mutator: "link", Variation: "shared"},
 					{Mutator: "version", Variation: ""}, // "" is the non-stub variant
 				}...), sharedLibTag, "libclang_rt.hwasan-aarch64-android")
@@ -1553,7 +1573,7 @@
 var _ cc.Coverage = (*apexBundle)(nil)
 
 func (a *apexBundle) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
-	return ctx.Device() && (ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled())
+	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
 func (a *apexBundle) PreventInstall() {
@@ -1722,13 +1742,8 @@
 	return true
 }
 
-// Function called while walking an APEX's payload dependencies.
-//
-// Return true if the `to` module should be visited, false otherwise.
-type payloadDepsCallback func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool
-
 // Visit dependencies that contributes to the payload of this APEX
-func (a *apexBundle) walkPayloadDeps(ctx android.ModuleContext, do payloadDepsCallback) {
+func (a *apexBundle) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		am, ok := child.(android.ApexModule)
 		if !ok || !am.CanHaveApexVariants() {
@@ -1754,7 +1769,21 @@
 }
 
 func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) int {
-	ver := proptools.StringDefault(a.properties.Min_sdk_version, "current")
+	ver := proptools.String(a.properties.Min_sdk_version)
+	if ver == "" {
+		return android.FutureApiLevel
+	}
+	// Treat the current codenames as "current", which means future API version (10000)
+	// Otherwise, ApiStrToNum converts codename(non-finalized) to a value from [9000...]
+	// and would fail to build against "current".
+	if android.InList(ver, ctx.Config().PlatformVersionActiveCodenames()) {
+		return android.FutureApiLevel
+	}
+	// In "REL" branch, "current" is mapped to finalized sdk version
+	if ctx.Config().PlatformSdkCodename() == "REL" && ver == "current" {
+		return ctx.Config().PlatformSdkVersionInt()
+	}
+	// Finalized codenames are OKAY and will be converted to int
 	intVer, err := android.ApiStrToNum(ctx, ver)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
@@ -1775,6 +1804,12 @@
 		return
 	}
 
+	// Because APEXes targeting other than system/system_ext partitions
+	// can't set apex_available, we skip checks for these APEXes
+	if ctx.SocSpecific() || ctx.DeviceSpecific() || ctx.ProductSpecific() {
+		return
+	}
+
 	// Coverage build adds additional dependencies for the coverage-only runtime libraries.
 	// Requiring them and their transitive depencies with apex_available is not right
 	// because they just add noise.
@@ -1782,7 +1817,7 @@
 		return
 	}
 
-	a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		if externalDep {
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
 			return false
@@ -1818,6 +1853,17 @@
 	}
 }
 
+func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
+	if a.testApex || a.vndkApex {
+		return
+	}
+	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
+	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
+		return
+	}
+	android.CheckMinSdkVersion(a, ctx, a.minSdkVersion(ctx))
+}
+
 // Ensures that a lib providing stub isn't statically linked
 func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext) {
 	// Practically, we only care about regular APEXes on the device.
@@ -1825,7 +1871,7 @@
 		return
 	}
 
-	a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		if ccm, ok := to.(*cc.Module); ok {
 			apexName := ctx.ModuleName()
 			fromName := ctx.OtherModuleName(from)
@@ -1864,7 +1910,7 @@
 }
 
 func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuild()
+	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
 	switch a.properties.ApexType {
 	case imageApex:
 		if buildFlattenedAsDefault {
@@ -1900,6 +1946,7 @@
 
 	a.checkApexAvailability(ctx)
 	a.checkUpdatable(ctx)
+	a.checkMinSdkVersion(ctx)
 	a.checkStaticLinkingToStubLibraries(ctx)
 
 	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
@@ -1933,7 +1980,7 @@
 	})
 
 	var filesInfo []apexFile
-	// TODO(jiyong) do this using walkPayloadDeps
+	// TODO(jiyong) do this using WalkPayloadDeps
 	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
 		depTag := ctx.OtherModuleDependencyTag(child)
 		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
@@ -2077,7 +2124,7 @@
 						}
 						af := apexFileForNativeLibrary(ctx, cc, handleSpecialLibs)
 						af.transitiveDep = true
-						if !a.Host() && !android.DirectlyInApex(ctx.ModuleName(), ctx.OtherModuleName(cc)) && (cc.IsStubs() || cc.HasStubsVariants()) {
+						if !a.Host() && !android.DirectlyInApex(ctx.ModuleName(), depName) && (cc.IsStubs() || cc.HasStubsVariants()) {
 							// If the dependency is a stubs lib, don't include it in this APEX,
 							// but make sure that the lib is installed on the device.
 							// In case no APEX is having the lib, the lib is installed to the system
@@ -2085,8 +2132,17 @@
 							//
 							// Always include if we are a host-apex however since those won't have any
 							// system libraries.
-							if !android.DirectlyInAnyApex(ctx, cc.Name()) && !android.InList(cc.BaseModuleName(), a.requiredDeps) {
-								a.requiredDeps = append(a.requiredDeps, cc.BaseModuleName())
+							if !android.DirectlyInAnyApex(ctx, depName) {
+								// we need a module name for Make
+								name := cc.BaseModuleName() + cc.Properties.SubName
+								if proptools.Bool(a.properties.Use_vendor) {
+									// we don't use subName(.vendor) for a "use_vendor: true" apex
+									// which is supposed to be installed in /system
+									name = cc.BaseModuleName()
+								}
+								if !android.InList(name, a.requiredDeps) {
+									a.requiredDeps = append(a.requiredDeps, name)
+								}
 							}
 							requireNativeLibs = append(requireNativeLibs, af.Stem())
 							// Don't track further
@@ -2172,22 +2228,6 @@
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = filesInfo
 
-	if a.properties.ApexType != zipApex {
-		if a.properties.File_contexts == nil {
-			a.fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
-		} else {
-			a.fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
-			if a.Platform() {
-				if matched, err := path.Match("system/sepolicy/**/*", a.fileContexts.String()); err != nil || !matched {
-					ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", a.fileContexts)
-				}
-			}
-		}
-		if !android.ExistentPathForSource(ctx, a.fileContexts.String()).Valid() {
-			ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
-			return
-		}
-	}
 	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
 	// transitive dependencies, don't place them inside the APEX, but place a symlink pointing
 	// the same library in the system partition, thus effectively sharing the same libraries
@@ -2197,6 +2237,12 @@
 		a.installable() &&
 		!proptools.Bool(a.properties.Use_vendor)
 
+	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
+	// So we can't link them to /system/lib libs which are core variants.
+	if a.SocSpecific() || a.DeviceSpecific() || a.ProductSpecific() {
+		a.linkToSystemLib = false
+	}
+
 	// We don't need the optimization for updatable APEXes, as it might give false signal
 	// to the system health when the APEXes are still bundled (b/149805758)
 	if a.Updatable() && a.properties.ApexType == imageApex {
@@ -2211,6 +2257,8 @@
 	// prepare apex_manifest.json
 	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
 
+	a.buildFileContexts(ctx)
+
 	a.setCertificateAndPrivateKey(ctx)
 	if a.properties.ApexType == flattenedApex {
 		a.buildFlattenedApex(ctx)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a7a7765..befb814 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -206,6 +206,7 @@
 	config.TestProductVariables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
 	config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("Q")
 	config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(false)
+	config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
 	config.TestProductVariables.Platform_vndk_version = proptools.StringPtr("VER")
 
 	for _, handler := range handlers {
@@ -1104,13 +1105,13 @@
 	}{
 		{
 			name:          "should link to the latest",
-			minSdkVersion: "current",
+			minSdkVersion: "",
 			shouldLink:    "30",
 			shouldNotLink: []string{"29"},
 		},
 		{
 			name:          "should link to llndk#29",
-			minSdkVersion: "29",
+			minSdkVersion: "min_sdk_version: \"29\",",
 			shouldLink:    "29",
 			shouldNotLink: []string{"30"},
 		},
@@ -1123,7 +1124,7 @@
 				key: "myapex.key",
 				use_vendor: true,
 				native_shared_libs: ["mylib"],
-				min_sdk_version: "`+tc.minSdkVersion+`",
+				`+tc.minSdkVersion+`
 			}
 
 			apex_key {
@@ -1140,6 +1141,7 @@
 				system_shared_libs: [],
 				stl: "none",
 				apex_available: [ "myapex" ],
+				min_sdk_version: "29",
 			}
 
 			cc_library {
@@ -1270,24 +1272,24 @@
 	ensureContains(t, libFlags, "libdl/android_arm64_armv8-a_shared/libdl.so")
 }
 
-func TestApexUseStubsAccordingToMinSdkVersionInUnbundledBuild(t *testing.T) {
+func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) {
 	// there are three links between liba --> libz
-	// 1) myapex -> libx -> liba -> libz    : this should be #2 link, but fallback to #1
-	// 2) otherapex -> liby -> liba -> libz : this should be #3 link
+	// 1) myapex -> libx -> liba -> libz    : this should be #29 link, but fallback to #28
+	// 2) otherapex -> liby -> liba -> libz : this should be #30 link
 	// 3) (platform) -> liba -> libz        : this should be non-stub link
 	ctx, _ := testApex(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
 			native_shared_libs: ["libx"],
-			min_sdk_version: "2",
+			min_sdk_version: "29",
 		}
 
 		apex {
 			name: "otherapex",
 			key: "myapex.key",
 			native_shared_libs: ["liby"],
-			min_sdk_version: "3",
+			min_sdk_version: "30",
 		}
 
 		apex_key {
@@ -1302,6 +1304,7 @@
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
 		}
 
 		cc_library {
@@ -1310,6 +1313,7 @@
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "otherapex" ],
+			min_sdk_version: "29",
 		}
 
 		cc_library {
@@ -1321,6 +1325,7 @@
 				"//apex_available:anyapex",
 				"//apex_available:platform",
 			],
+			min_sdk_version: "29",
 		}
 
 		cc_library {
@@ -1328,10 +1333,10 @@
 			system_shared_libs: [],
 			stl: "none",
 			stubs: {
-				versions: ["1", "3"],
+				versions: ["28", "30"],
 			},
 		}
-	`, withUnbundledBuild)
+	`)
 
 	expectLink := func(from, from_variant, to, to_variant string) {
 		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
@@ -1343,13 +1348,13 @@
 	}
 	// platform liba is linked to non-stub version
 	expectLink("liba", "shared", "libz", "shared")
-	// liba in myapex is linked to #1
-	expectLink("liba", "shared_myapex", "libz", "shared_1")
-	expectNoLink("liba", "shared_myapex", "libz", "shared_3")
+	// liba in myapex is linked to #28
+	expectLink("liba", "shared_myapex", "libz", "shared_28")
+	expectNoLink("liba", "shared_myapex", "libz", "shared_30")
 	expectNoLink("liba", "shared_myapex", "libz", "shared")
-	// liba in otherapex is linked to #3
-	expectLink("liba", "shared_otherapex", "libz", "shared_3")
-	expectNoLink("liba", "shared_otherapex", "libz", "shared_1")
+	// liba in otherapex is linked to #30
+	expectLink("liba", "shared_otherapex", "libz", "shared_30")
+	expectNoLink("liba", "shared_otherapex", "libz", "shared_28")
 	expectNoLink("liba", "shared_otherapex", "libz", "shared")
 }
 
@@ -1374,6 +1379,7 @@
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
+			min_sdk_version: "R",
 		}
 
 		cc_library {
@@ -1407,7 +1413,7 @@
 	expectNoLink("libx", "shared_myapex", "libz", "shared")
 }
 
-func TestApexMinSdkVersionDefaultsToLatest(t *testing.T) {
+func TestApexMinSdkVersion_DefaultsToLatest(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
 			name: "myapex",
@@ -1516,6 +1522,7 @@
 			name: "libx",
 			shared_libs: ["libbar"],
 			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
 		}
 
 		cc_library {
@@ -1553,6 +1560,7 @@
 		cc_library {
 			name: "libx",
 			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
 		}
 	`)
 
@@ -1564,7 +1572,7 @@
 	ensureListNotContains(t, cm.Properties.AndroidMkStaticLibs, "libgcc_stripped")
 }
 
-func TestInvalidMinSdkVersion(t *testing.T) {
+func TestApexMinSdkVersion_ErrorIfIncompatibleStubs(t *testing.T) {
 	testApexError(t, `"libz" .*: not found a version\(<=29\)`, `
 		apex {
 			name: "myapex",
@@ -1585,6 +1593,7 @@
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
 		}
 
 		cc_library {
@@ -1596,12 +1605,15 @@
 			},
 		}
 	`)
+}
 
-	testApexError(t, `"myapex" .*: min_sdk_version: SDK version should be .*`, `
+func TestApexMinSdkVersion_ErrorIfIncompatibleVersion(t *testing.T) {
+	testApexError(t, `module "mylib".*: should support min_sdk_version\(29\)`, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			min_sdk_version: "abc",
+			native_shared_libs: ["mylib"],
+			min_sdk_version: "29",
 		}
 
 		apex_key {
@@ -1609,6 +1621,67 @@
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
+}
+
+func TestApexMinSdkVersion_Okay(t *testing.T) {
+	testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["libfoo"],
+			java_libs: ["libbar"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "libfoo",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libfoo_dep"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+
+		cc_library {
+			name: "libfoo_dep",
+			srcs: ["mylib.cpp"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+
+		java_library {
+			name: "libbar",
+			sdk_version: "current",
+			srcs: ["a.java"],
+			static_libs: ["libbar_dep"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+
+		java_library {
+			name: "libbar_dep",
+			sdk_version: "current",
+			srcs: ["a.java"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
 	`)
 }
 
@@ -1659,6 +1732,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "current",
 					apex_available: ["myapex"],
+					min_sdk_version: "29",
 				}
 			`,
 		},
@@ -1728,6 +1802,135 @@
 	}
 }
 
+func TestApexMinSdkVersion_ErrorIfDepIsNewer(t *testing.T) {
+	testApexError(t, `module "mylib2".*: should support min_sdk_version\(29\) for "myapex"`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "29",
+		}
+
+		// indirect part of the apex
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
+}
+
+func TestApexMinSdkVersion_ErrorIfDepIsNewer_Java(t *testing.T) {
+	testApexError(t, `module "bar".*: should support min_sdk_version\(29\) for "myapex"`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apps: ["AppFoo"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		android_app {
+			name: "AppFoo",
+			srcs: ["foo/bar/MyClass.java"],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			system_modules: "none",
+			stl: "none",
+			static_libs: ["bar"],
+			apex_available: [ "myapex" ],
+		}
+
+		java_library {
+			name: "bar",
+			sdk_version: "current",
+			srcs: ["a.java"],
+			apex_available: [ "myapex" ],
+		}
+	`)
+}
+
+func TestApexMinSdkVersion_OkayEvenWhenDepIsNewer_IfItSatisfiesApexMinSdkVersion(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		// mylib in myapex will link to mylib2#29
+		// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: ["myapex", "otherapex"],
+			min_sdk_version: "29",
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: ["otherapex"],
+			stubs: { versions: ["29", "30"] },
+			min_sdk_version: "30",
+		}
+
+		apex {
+			name: "otherapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "mylib2"],
+			min_sdk_version: "30",
+		}
+	`)
+	expectLink := func(from, from_variant, to, to_variant string) {
+		ld := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld")
+		libFlags := ld.Args["libFlags"]
+		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
+	}
+	expectLink("mylib", "shared_myapex", "mylib2", "shared_29")
+	expectLink("mylib", "shared_otherapex", "mylib2", "shared_otherapex")
+}
+
 func TestFilesInSubDir(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
@@ -1902,7 +2105,7 @@
 	ensureNotContains(t, inputsString, "android_arm64_armv8-a_shared_myapex/mylib2.so")
 }
 
-func TestUseVendorRestriction(t *testing.T) {
+func TestUseVendorNotAllowedForSystemApexes(t *testing.T) {
 	testApexError(t, `module "myapex" .*: use_vendor: not allowed`, `
 		apex {
 			name: "myapex",
@@ -1958,6 +2161,141 @@
 	`)
 }
 
+func TestVendorApex(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["mybin"],
+			vendor: true,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		cc_binary {
+			name: "mybin",
+			vendor: true,
+			shared_libs: ["libfoo"],
+		}
+		cc_library {
+			name: "libfoo",
+			proprietary: true,
+		}
+	`)
+
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+		"bin/mybin",
+		"lib64/libfoo.so",
+		// TODO(b/159195575): Add an option to use VNDK libs from VNDK APEX
+		"lib64/libc++.so",
+	})
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, config, "", apexBundle)
+	name := apexBundle.BaseModuleName()
+	prefix := "TARGET_"
+	var builder strings.Builder
+	data.Custom(&builder, name, prefix, "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, `LOCAL_MODULE_PATH := /tmp/target/product/test_device/vendor/apex`)
+}
+
+func TestAndroidMk_UseVendorRequired(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			use_vendor: true,
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			vendor_available: true,
+			apex_available: ["myapex"],
+		}
+	`, func(fs map[string][]byte, config android.Config) {
+		setUseVendorAllowListForTest(config, []string{"myapex"})
+	})
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, config, "", apexBundle)
+	name := apexBundle.BaseModuleName()
+	prefix := "TARGET_"
+	var builder strings.Builder
+	data.Custom(&builder, name, prefix, "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += libc libm libdl\n")
+}
+
+func TestAndroidMk_VendorApexRequired(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			vendor: true,
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			vendor_available: true,
+		}
+	`)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, config, "", apexBundle)
+	name := apexBundle.BaseModuleName()
+	prefix := "TARGET_"
+	var builder strings.Builder
+	data.Custom(&builder, name, prefix, "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += libc.vendor libm.vendor libdl.vendor\n")
+}
+
+func TestAndroidMkWritesCommonProperties(t *testing.T) {
+	ctx, config := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			vintf_fragments: ["fragment.xml"],
+			init_rc: ["init.rc"],
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		cc_binary {
+			name: "mybin",
+		}
+	`)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, config, "", apexBundle)
+	name := apexBundle.BaseModuleName()
+	prefix := "TARGET_"
+	var builder strings.Builder
+	data.Custom(&builder, name, prefix, "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_VINTF_FRAGMENTS := fragment.xml\n")
+	ensureContains(t, androidMk, "LOCAL_INIT_RC := init.rc\n")
+}
+
 func TestStaticLinking(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex {
@@ -2217,6 +2555,7 @@
 				"otherapex",
 			],
 			recovery_available: true,
+			min_sdk_version: "29",
 		}
 		cc_library {
 			name: "mylib2",
@@ -2228,6 +2567,7 @@
 				"otherapex",
 			],
 			use_apex_name_macro: true,
+			min_sdk_version: "29",
 		}
 	`)
 
@@ -3275,110 +3615,104 @@
 	}
 }
 
-func TestFileContexts(t *testing.T) {
+func TestFileContexts_FindInDefaultLocationIfNotSet(t *testing.T) {
 	ctx, _ := testApex(t, `
-	apex {
-		name: "myapex",
-		key: "myapex.key",
-	}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+		}
 
-	apex_key {
-		name: "myapex.key",
-		public_key: "testkey.avbpubkey",
-		private_key: "testkey.pem",
-	}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
 	`)
 	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	apexRule := module.Rule("apexRule")
-	actual := apexRule.Args["file_contexts"]
-	expected := "system/sepolicy/apex/myapex-file_contexts"
-	if actual != expected {
-		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
-	}
+	rule := module.Output("file_contexts")
+	ensureContains(t, rule.RuleParams.Command, "cat system/sepolicy/apex/myapex-file_contexts")
+}
 
+func TestFileContexts_ShouldBeUnderSystemSepolicyForSystemApexes(t *testing.T) {
 	testApexError(t, `"myapex" .*: file_contexts: should be under system/sepolicy`, `
-	apex {
-		name: "myapex",
-		key: "myapex.key",
-		file_contexts: "my_own_file_contexts",
-	}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			file_contexts: "my_own_file_contexts",
+		}
 
-	apex_key {
-		name: "myapex.key",
-		public_key: "testkey.avbpubkey",
-		private_key: "testkey.pem",
-	}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
 	`, withFiles(map[string][]byte{
 		"my_own_file_contexts": nil,
 	}))
+}
 
+func TestFileContexts_ProductSpecificApexes(t *testing.T) {
 	testApexError(t, `"myapex" .*: file_contexts: cannot find`, `
-	apex {
-		name: "myapex",
-		key: "myapex.key",
-		product_specific: true,
-		file_contexts: "product_specific_file_contexts",
-	}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: "product_specific_file_contexts",
+		}
 
-	apex_key {
-		name: "myapex.key",
-		public_key: "testkey.avbpubkey",
-		private_key: "testkey.pem",
-	}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
 	`)
 
-	ctx, _ = testApex(t, `
-	apex {
-		name: "myapex",
-		key: "myapex.key",
-		product_specific: true,
-		file_contexts: "product_specific_file_contexts",
-	}
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: "product_specific_file_contexts",
+		}
 
-	apex_key {
-		name: "myapex.key",
-		public_key: "testkey.avbpubkey",
-		private_key: "testkey.pem",
-	}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
 	`, withFiles(map[string][]byte{
 		"product_specific_file_contexts": nil,
 	}))
-	module = ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	apexRule = module.Rule("apexRule")
-	actual = apexRule.Args["file_contexts"]
-	expected = "product_specific_file_contexts"
-	if actual != expected {
-		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
-	}
+	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	rule := module.Output("file_contexts")
+	ensureContains(t, rule.RuleParams.Command, "cat product_specific_file_contexts")
+}
 
-	ctx, _ = testApex(t, `
-	apex {
-		name: "myapex",
-		key: "myapex.key",
-		product_specific: true,
-		file_contexts: ":my-file-contexts",
-	}
+func TestFileContexts_SetViaFileGroup(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: ":my-file-contexts",
+		}
 
-	apex_key {
-		name: "myapex.key",
-		public_key: "testkey.avbpubkey",
-		private_key: "testkey.pem",
-	}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
 
-	filegroup {
-		name: "my-file-contexts",
-		srcs: ["product_specific_file_contexts"],
-	}
+		filegroup {
+			name: "my-file-contexts",
+			srcs: ["product_specific_file_contexts"],
+		}
 	`, withFiles(map[string][]byte{
 		"product_specific_file_contexts": nil,
 	}))
-	module = ctx.ModuleForTests("myapex", "android_common_myapex_image")
-	apexRule = module.Rule("apexRule")
-	actual = apexRule.Args["file_contexts"]
-	expected = "product_specific_file_contexts"
-	if actual != expected {
-		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
-	}
+	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	rule := module.Output("file_contexts")
+	ensureContains(t, rule.RuleParams.Command, "cat product_specific_file_contexts")
 }
 
 func TestApexKeyFromOtherModule(t *testing.T) {
@@ -4349,6 +4683,7 @@
 			stl: "libc++",
 			system_shared_libs: [],
 			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
 		}
 	`, withUnbundledBuild)
 
@@ -4749,6 +5084,7 @@
 				"myapex.updatable",
 				"//apex_available:platform",
 			],
+			min_sdk_version: "current",
 		}
 
 		cc_library {
@@ -4761,6 +5097,7 @@
 				"myapex.updatable",
 				"//apex_available:platform",
 			],
+			min_sdk_version: "current",
 		}
 
 		java_library {
@@ -4774,6 +5111,7 @@
 				"myapex.updatable",
 				"//apex_available:platform",
 			],
+			min_sdk_version: "current",
 		}
 
 		java_library {
@@ -4786,6 +5124,7 @@
 				"myapex.updatable",
 				"//apex_available:platform",
 			],
+			min_sdk_version: "current",
 		}
 	`
 
@@ -5393,7 +5732,62 @@
 	apexKeysText := ctx.SingletonForTests("apex_keys_text")
 	content := apexKeysText.MaybeDescription("apexkeys.txt").BuildParams.Args["content"]
 	ensureContains(t, content, `name="myapex_set.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED" partition="system"`)
-	ensureNotContains(t, content, "myapex.apex")
+	ensureContains(t, content, `name="myapex.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED" partition="system"`)
+}
+
+func TestAllowedFiles(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apps: ["app"],
+			allowed_files: "allowed.txt",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		android_app {
+			name: "app",
+			srcs: ["foo/bar/MyClass.java"],
+			package_name: "foo",
+			sdk_version: "none",
+			system_modules: "none",
+			apex_available: [ "myapex" ],
+		}
+	`, withFiles(map[string][]byte{
+		"sub/Android.bp": []byte(`
+			override_apex {
+				name: "override_myapex",
+				base: "myapex",
+				apps: ["override_app"],
+				allowed_files: ":allowed",
+			}
+			// Overridable "path" property should be referenced indirectly
+			filegroup {
+				name: "allowed",
+				srcs: ["allowed.txt"],
+			}
+			override_android_app {
+				name: "override_app",
+				base: "app",
+				package_name: "bar",
+			}
+			`),
+	}))
+
+	rule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("diffApexContentRule")
+	if expected, actual := "allowed.txt", rule.Args["allowed_files_file"]; expected != actual {
+		t.Errorf("allowed_files_file: expected %q but got %q", expected, actual)
+	}
+
+	rule2 := ctx.ModuleForTests("myapex", "android_common_override_myapex_myapex_image").Rule("diffApexContentRule")
+	if expected, actual := "sub/allowed.txt", rule2.Args["allowed_files_file"]; expected != actual {
+		t.Errorf("allowed_files_file: expected %q but got %q", expected, actual)
+	}
 }
 
 func TestMain(m *testing.M) {
diff --git a/apex/builder.go b/apex/builder.go
index 53c1193..ede11d0 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -17,6 +17,7 @@
 import (
 	"encoding/json"
 	"fmt"
+	"path"
 	"path/filepath"
 	"runtime"
 	"sort"
@@ -231,10 +232,42 @@
 	})
 }
 
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) {
+	if a.properties.ApexType == zipApex {
+		return
+	}
+	var fileContexts android.Path
+	if a.properties.File_contexts == nil {
+		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
+	} else {
+		fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
+	}
+	if a.Platform() {
+		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
+			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
+			return
+		}
+	}
+	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
+		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
+		return
+	}
+
+	output := android.PathForModuleOut(ctx, "file_contexts")
+	rule := android.NewRuleBuilder()
+	rule.Command().Text("rm").FlagWithOutput("-f ", output)
+	rule.Command().Text("cat").Input(fileContexts).Text(">>").Output(output)
+	rule.Command().Text("echo").Text(">>").Output(output)
+	rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
+	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
+
+	a.fileContexts = output.OutputPath
+}
+
 func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
 	var noticeFiles android.Paths
 
-	a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		if externalDep {
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
 			return false
@@ -391,7 +424,7 @@
 	emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 
-	if a.properties.Allowed_files != nil {
+	if a.overridableProperties.Allowed_files != nil {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        emitApexContentRule,
 			Implicits:   implicitInputs,
@@ -402,7 +435,7 @@
 			},
 		})
 		implicitInputs = append(implicitInputs, imageContentFile)
-		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.Allowed_files))
+		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
 
 		phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
 		ctx.Build(pctx, android.BuildParams{
@@ -733,7 +766,7 @@
 	}
 
 	depInfos := android.DepNameToDepInfoMap{}
-	a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		if from.Name() == to.Name() {
 			// This can happen for cc.reuseObjTag. We are not interested in tracking this.
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
diff --git a/apex/key.go b/apex/key.go
index a68f6e1..d2d5786 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -160,12 +160,6 @@
 				presigned: true,
 				partition: m.PartitionTag(ctx.DeviceConfig()),
 			}
-
-			for _, om := range m.Overrides() {
-				if _, ok := apexKeyMap[om]; ok {
-					delete(apexKeyMap, om)
-				}
-			}
 			apexKeyMap[m.BaseModuleName()] = entry
 		}
 	})
diff --git a/apex/vndk_test.go b/apex/vndk_test.go
index 05cdfcd..60b6ed5 100644
--- a/apex/vndk_test.go
+++ b/apex/vndk_test.go
@@ -118,6 +118,7 @@
 
 	t.Run("VNDK APEX supports coverage variants", func(t *testing.T) {
 		ctx, _ := testApex(t, bp, func(fs map[string][]byte, config android.Config) {
+			config.TestProductVariables.GcovCoverage = proptools.BoolPtr(true)
 			config.TestProductVariables.Native_coverage = proptools.BoolPtr(true)
 		})
 
diff --git a/bpfix/Android.bp b/bpfix/Android.bp
index e291578..b244e3a 100644
--- a/bpfix/Android.bp
+++ b/bpfix/Android.bp
@@ -44,11 +44,9 @@
         "bpfix/bpfix.go",
     ],
     testSrcs: [
-      "bpfix/bpfix_test.go",
+        "bpfix/bpfix_test.go",
     ],
     deps: [
         "blueprint-parser",
     ],
 }
-
-
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index e731750..689cbd1 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -533,7 +533,7 @@
 		updated = true
 	} else if trimmedPath := strings.TrimPrefix(path, f.prefix+"/"); trimmedPath != path {
 		m.Properties = append(m.Properties, &parser.Property{
-			Name:  "sub_dir",
+			Name:  "relative_install_path",
 			Value: &parser.String{Value: trimmedPath},
 		})
 		updated = true
@@ -581,6 +581,8 @@
 		// 'srcs' --> 'src' conversion
 		convertToSingleSource(mod, "src")
 
+		renameProperty(mod, "sub_dir", "relative_install_dir")
+
 		// The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute
 		// 'local_module_path'. Analyze its contents and create the correct sub_dir:,
 		// filename: and boolean attributes combination
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index 64a7b93..8988177 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -742,6 +742,22 @@
 		}
 		`,
 		},
+		{
+			name: "prebuilt_etc sub_dir",
+			in: `
+			prebuilt_etc {
+			name: "foo",
+			src: "bar",
+			sub_dir: "baz",
+		}
+		`,
+			out: `prebuilt_etc {
+			name: "foo",
+			src: "bar",
+			relative_install_dir: "baz",
+		}
+		`,
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index b3ad610..3f812c2 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -249,7 +249,10 @@
 		entries.Class = "HEADER_LIBRARIES"
 	}
 
-	entries.DistFile = library.distFile
+	if library.distFile != nil {
+		entries.DistFiles = android.MakeDefaultDistFiles(library.distFile)
+	}
+
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		library.androidMkWriteExportedFlags(entries)
 		library.androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries)
@@ -318,7 +321,7 @@
 	ctx.subAndroidMk(entries, binary.baseInstaller)
 
 	entries.Class = "EXECUTABLES"
-	entries.DistFile = binary.distFile
+	entries.DistFiles = binary.distFiles
 	entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", binary.unstrippedOutputFile.String())
 		if len(binary.symlinks) > 0 {
diff --git a/cc/binary.go b/cc/binary.go
index 251b7f0..565cb8a 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -98,8 +98,8 @@
 	// Output archive of gcno coverage information
 	coverageOutputFile android.OptionalPath
 
-	// Location of the file that should be copied to dist dir when requested
-	distFile android.OptionalPath
+	// Location of the files that should be copied to dist dir when requested
+	distFiles android.TaggedDistFiles
 
 	post_install_cmds []string
 }
@@ -367,11 +367,11 @@
 			binary.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		} else {
 			versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
-			binary.distFile = android.OptionalPathForPath(versionedOutputFile)
+			binary.distFiles = android.MakeDefaultDistFiles(versionedOutputFile)
 
 			if binary.stripper.needsStrip(ctx) {
 				out := android.PathForModuleOut(ctx, "versioned-stripped", fileName)
-				binary.distFile = android.OptionalPathForPath(out)
+				binary.distFiles = android.MakeDefaultDistFiles(out)
 				binary.stripper.stripExecutableOrSharedLib(ctx, versionedOutputFile, out, builderFlags)
 			}
 
diff --git a/cc/cc.go b/cc/cc.go
index ebdb46c..81df094 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2905,6 +2905,54 @@
 	return true
 }
 
+// b/154667674: refactor this to handle "current" in a consistent way
+func decodeSdkVersionString(ctx android.BaseModuleContext, versionString string) (int, error) {
+	if versionString == "" {
+		return 0, fmt.Errorf("not specified")
+	}
+	if versionString == "current" {
+		if ctx.Config().PlatformSdkCodename() == "REL" {
+			return ctx.Config().PlatformSdkVersionInt(), nil
+		}
+		return android.FutureApiLevel, nil
+	}
+	return android.ApiStrToNum(ctx, versionString)
+}
+
+func (c *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// We ignore libclang_rt.* prebuilt libs since they declare sdk_version: 14(b/121358700)
+	if strings.HasPrefix(ctx.OtherModuleName(c), "libclang_rt") {
+		return nil
+	}
+	// b/154569636: set min_sdk_version correctly for toolchain_libraries
+	if c.ToolchainLibrary() {
+		return nil
+	}
+	// We don't check for prebuilt modules
+	if _, ok := c.linker.(prebuiltLinkerInterface); ok {
+		return nil
+	}
+	minSdkVersion := c.MinSdkVersion()
+	if minSdkVersion == "apex_inherit" {
+		return nil
+	}
+	if minSdkVersion == "" {
+		// JNI libs within APK-in-APEX fall into here
+		// Those are okay to set sdk_version instead
+		// We don't have to check if this is a SDK variant because
+		// non-SDK variant resets sdk_version, which works too.
+		minSdkVersion = c.SdkVersion()
+	}
+	ver, err := decodeSdkVersionString(ctx, minSdkVersion)
+	if err != nil {
+		return err
+	}
+	if ver > sdkVersion {
+		return fmt.Errorf("newer SDK(%v)", ver)
+	}
+	return nil
+}
+
 //
 // Defaults
 //
@@ -2940,6 +2988,7 @@
 		&BinaryLinkerProperties{},
 		&TestProperties{},
 		&TestBinaryProperties{},
+		&BenchmarkProperties{},
 		&FuzzProperties{},
 		&StlProperties{},
 		&SanitizeProperties{},
diff --git a/cc/coverage.go b/cc/coverage.go
index 1a559a9..c823324 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -74,8 +74,8 @@
 }
 
 func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
-	gcovCoverage := ctx.DeviceConfig().NativeCoverageEnabled()
 	clangCoverage := ctx.DeviceConfig().ClangCoverageEnabled()
+	gcovCoverage := ctx.DeviceConfig().GcovCoverageEnabled()
 
 	if !gcovCoverage && !clangCoverage {
 		return flags, deps
@@ -161,7 +161,7 @@
 func SetCoverageProperties(ctx android.BaseModuleContext, properties CoverageProperties, moduleTypeHasCoverage bool,
 	useSdk bool, sdkVersion string) CoverageProperties {
 	// Coverage is disabled globally
-	if !ctx.DeviceConfig().NativeCoverageEnabled() && !ctx.DeviceConfig().ClangCoverageEnabled() {
+	if !ctx.DeviceConfig().NativeCoverageEnabled() {
 		return properties
 	}
 
diff --git a/cc/library.go b/cc/library.go
index 968702e..98f4d48 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -369,7 +369,7 @@
 	unstrippedOutputFile android.Path
 
 	// Location of the file that should be copied to dist dir when requested
-	distFile android.OptionalPath
+	distFile android.Path
 
 	versionScriptPath android.ModuleGenPath
 
@@ -894,7 +894,7 @@
 			library.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		} else {
 			versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
-			library.distFile = android.OptionalPathForPath(versionedOutputFile)
+			library.distFile = versionedOutputFile
 			library.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		}
 	}
@@ -988,11 +988,11 @@
 			library.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
 		} else {
 			versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
-			library.distFile = android.OptionalPathForPath(versionedOutputFile)
+			library.distFile = versionedOutputFile
 
 			if library.stripper.needsStrip(ctx) {
 				out := android.PathForModuleOut(ctx, "versioned-stripped", fileName)
-				library.distFile = android.OptionalPathForPath(out)
+				library.distFile = out
 				library.stripper.stripExecutableOrSharedLib(ctx, versionedOutputFile, out, builderFlags)
 			}
 
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 4410302..9c54399 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -380,7 +380,11 @@
 	// Make sure that the include directories are unique.
 	p.ExportedIncludeDirs = android.FirstUniquePaths(exportedIncludeDirs)
 	p.exportedGeneratedIncludeDirs = android.FirstUniquePaths(exportedGeneratedIncludeDirs)
-	p.ExportedSystemIncludeDirs = android.FirstUniquePaths(ccModule.ExportedSystemIncludeDirs())
+
+	// Take a copy before filtering out duplicates to avoid changing the slice owned by the
+	// ccModule.
+	dirs := append(android.Paths(nil), ccModule.ExportedSystemIncludeDirs()...)
+	p.ExportedSystemIncludeDirs = android.FirstUniquePaths(dirs)
 
 	p.ExportedFlags = ccModule.ExportedFlags()
 	if ccModule.linker != nil {
diff --git a/cc/linker.go b/cc/linker.go
index c9cbd9b..58f8a29 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -298,17 +298,6 @@
 			}
 		}
 
-		if inList("libc_scudo", deps.SharedLibs) {
-			// libc_scudo is an alternate implementation of all
-			// allocation functions (malloc, free), that uses
-			// the scudo allocator instead of the default native
-			// allocator. If this library is in the list, make
-			// sure it's first so it properly overrides the
-			// allocation functions of all other shared libraries.
-			_, deps.SharedLibs = removeFromList("libc_scudo", deps.SharedLibs)
-			deps.SharedLibs = append([]string{"libc_scudo"}, deps.SharedLibs...)
-		}
-
 		// If libc and libdl are both in system_shared_libs make sure libdl comes after libc
 		// to avoid loading libdl before libc.
 		if inList("libdl", systemSharedLibs) && inList("libc", systemSharedLibs) &&
diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go
index c4d7708..acdc581 100644
--- a/cc/ndk_prebuilt.go
+++ b/cc/ndk_prebuilt.go
@@ -145,6 +145,7 @@
 		libraryDecorator: library,
 	}
 	module.installer = nil
+	module.Properties.Sdk_version = StringPtr("minimum")
 	module.Properties.HideFromMake = true
 	module.Properties.AlwaysSdk = true
 	module.Properties.Sdk_version = StringPtr("current")
diff --git a/cc/pgo.go b/cc/pgo.go
index 88903bb..9298e7a 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -34,7 +34,7 @@
 
 	globalPgoProfileProjects = []string{
 		"toolchain/pgo-profiles",
-		"vendor/google_data/pgo-profiles",
+		"vendor/google_data/pgo_profile",
 	}
 )
 
diff --git a/cc/sanitize.go b/cc/sanitize.go
index aaaf694..72ad6d7 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -408,16 +408,6 @@
 		return deps
 	}
 
-	if ctx.Device() {
-		if Bool(sanitize.Properties.Sanitize.Address) {
-			// Compiling asan and having libc_scudo in the same
-			// executable will cause the executable to crash.
-			// Remove libc_scudo since it is only used to override
-			// allocation functions which asan already overrides.
-			_, deps.SharedLibs = removeFromList("libc_scudo", deps.SharedLibs)
-		}
-	}
-
 	return deps
 }
 
diff --git a/cc/testing.go b/cc/testing.go
index 479b424..b5cf45c 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -273,6 +273,7 @@
 			vendor_available: true,
 			recovery_available: true,
 			host_supported: true,
+			min_sdk_version: "29",
 			apex_available: [
 				"//apex_available:platform",
 				"//apex_available:anyapex",
@@ -287,6 +288,7 @@
 			vendor_available: true,
 			recovery_available: true,
 			host_supported: true,
+			min_sdk_version: "29",
 			vndk: {
 				enabled: true,
 				support_system_process: true,
@@ -305,6 +307,7 @@
 			host_supported: false,
 			vendor_available: true,
 			recovery_available: true,
+			min_sdk_version: "29",
 			apex_available: [
 				"//apex_available:platform",
 				"//apex_available:anyapex",
@@ -338,6 +341,7 @@
 			recovery_available: true,
 			vendor_available: true,
 			native_bridge_supported: true,
+			min_sdk_version: "29",
 			stl: "none",
 		}
 
@@ -365,6 +369,7 @@
 			recovery_available: true,
 			vendor_available: true,
 			native_bridge_supported: true,
+			min_sdk_version: "29",
 			stl: "none",
 		}
 
diff --git a/cmd/extract_apks/Android.bp b/cmd/extract_apks/Android.bp
index 90548cd..f8fe864 100644
--- a/cmd/extract_apks/Android.bp
+++ b/cmd/extract_apks/Android.bp
@@ -6,7 +6,7 @@
         "golang-protobuf-proto",
         "soong-cmd-extract_apks-proto",
     ],
-    testSrcs: ["main_test.go"]
+    testSrcs: ["main_test.go"],
 }
 
 bootstrap_go_package {
diff --git a/cmd/extract_jar_packages/Android.bp b/cmd/extract_jar_packages/Android.bp
index ea0cbbf..4ea8798 100644
--- a/cmd/extract_jar_packages/Android.bp
+++ b/cmd/extract_jar_packages/Android.bp
@@ -22,4 +22,3 @@
         "extract_jar_packages.go",
     ],
 }
-
diff --git a/cmd/extract_linker/Android.bp b/cmd/extract_linker/Android.bp
index fe76ae4..690c4fa 100644
--- a/cmd/extract_linker/Android.bp
+++ b/cmd/extract_linker/Android.bp
@@ -17,4 +17,3 @@
     srcs: ["main.go"],
     testSrcs: ["main_test.go"],
 }
-
diff --git a/cmd/merge_zips/Android.bp b/cmd/merge_zips/Android.bp
index f70c86e..8c97b6d 100644
--- a/cmd/merge_zips/Android.bp
+++ b/cmd/merge_zips/Android.bp
@@ -15,10 +15,10 @@
 blueprint_go_binary {
     name: "merge_zips",
     deps: [
-      "android-archive-zip",
-      "blueprint-pathtools",
-      "soong-jar",
-      "soong-zip",
+        "android-archive-zip",
+        "blueprint-pathtools",
+        "soong-jar",
+        "soong-zip",
     ],
     srcs: [
         "merge_zips.go",
@@ -27,4 +27,3 @@
         "merge_zips_test.go",
     ],
 }
-
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index a706810d..6fa304e 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -19,4 +19,3 @@
         "sbox.go",
     ],
 }
-
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 6b79823..b559bac 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -29,4 +29,3 @@
     ],
     primaryBuilder: true,
 }
-
diff --git a/cmd/soong_env/Android.bp b/cmd/soong_env/Android.bp
index 4cdc396..4db0da3 100644
--- a/cmd/soong_env/Android.bp
+++ b/cmd/soong_env/Android.bp
@@ -22,4 +22,3 @@
     ],
     default: true,
 }
-
diff --git a/cmd/zip2zip/Android.bp b/cmd/zip2zip/Android.bp
index 68d8bc7..2c4cd82 100644
--- a/cmd/zip2zip/Android.bp
+++ b/cmd/zip2zip/Android.bp
@@ -24,4 +24,3 @@
     ],
     testSrcs: ["zip2zip_test.go"],
 }
-
diff --git a/cmd/zipsync/Android.bp b/cmd/zipsync/Android.bp
index 22feadc..49b5f3e 100644
--- a/cmd/zipsync/Android.bp
+++ b/cmd/zipsync/Android.bp
@@ -22,4 +22,3 @@
         "zipsync.go",
     ],
 }
-
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index bc44b21..4a4e834 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -383,7 +383,7 @@
 		SoongZip:         ctx.Config().HostToolPath(ctx, "soong_zip"),
 		Zip2zip:          ctx.Config().HostToolPath(ctx, "zip2zip"),
 		ManifestCheck:    ctx.Config().HostToolPath(ctx, "manifest_check"),
-		ConstructContext: android.PathForSource(ctx, "build/make/core/construct_context.sh"),
+		ConstructContext: android.PathForSource(ctx, "build/soong/scripts/construct_context.sh"),
 	}
 }
 
diff --git a/dexpreopt/dexpreopt_gen/Android.bp b/dexpreopt/dexpreopt_gen/Android.bp
index 0790391..3f0619c 100644
--- a/dexpreopt/dexpreopt_gen/Android.bp
+++ b/dexpreopt/dexpreopt_gen/Android.bp
@@ -8,4 +8,4 @@
         "blueprint-pathtools",
         "blueprint-proptools",
     ],
-}
\ No newline at end of file
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index df6d79d..0f7b8df 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -42,9 +42,12 @@
 	// Source file of this prebuilt.
 	Src *string `android:"path,arch_variant"`
 
-	// optional subdirectory under which this file is installed into
+	// optional subdirectory under which this file is installed into, cannot be specified with relative_install_path, prefer relative_install_path
 	Sub_dir *string `android:"arch_variant"`
 
+	// optional subdirectory under which this file is installed into, cannot be specified with sub_dir
+	Relative_install_path *string `android:"arch_variant"`
+
 	// optional name for the installed file. If unspecified, name of the module is used as the file name
 	Filename *string `android:"arch_variant"`
 
@@ -158,7 +161,10 @@
 }
 
 func (p *PrebuiltEtc) SubDir() string {
-	return android.String(p.properties.Sub_dir)
+	if subDir := proptools.String(p.properties.Sub_dir); subDir != "" {
+		return subDir
+	}
+	return proptools.String(p.properties.Relative_install_path)
 }
 
 func (p *PrebuiltEtc) Installable() bool {
@@ -181,13 +187,17 @@
 	}
 	p.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
 
+	if p.properties.Sub_dir != nil && p.properties.Relative_install_path != nil {
+		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
+	}
+
 	// If soc install dir was specified and SOC specific is set, set the installDirPath to the specified
 	// socInstallDirBase.
 	installBaseDir := p.installDirBase
 	if ctx.SocSpecific() && p.socInstallDirBase != "" {
 		installBaseDir = p.socInstallDirBase
 	}
-	p.installDirPath = android.PathForModuleInstall(ctx, installBaseDir, proptools.String(p.properties.Sub_dir))
+	p.installDirPath = android.PathForModuleInstall(ctx, installBaseDir, p.SubDir())
 
 	// This ensures that outputFilePath has the correct name for others to
 	// use, as the source file may have a different name.
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 4ce1984..8fc36c2 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -49,7 +49,7 @@
 	os.Exit(run())
 }
 
-func testPrebuiltEtc(t *testing.T, bp string) (*android.TestContext, android.Config) {
+func testPrebuiltEtcContext(t *testing.T, bp string) (*android.TestContext, android.Config) {
 	fs := map[string][]byte{
 		"foo.conf": nil,
 		"bar.conf": nil,
@@ -67,6 +67,14 @@
 	ctx.RegisterModuleType("prebuilt_firmware", PrebuiltFirmwareFactory)
 	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
 	ctx.Register(config)
+
+	return ctx, config
+}
+
+func testPrebuiltEtc(t *testing.T, bp string) (*android.TestContext, android.Config) {
+	t.Helper()
+
+	ctx, config := testPrebuiltEtcContext(t, bp)
 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
 	android.FailIfErrored(t, errs)
 	_, errs = ctx.PrepareBuildActions(config)
@@ -75,6 +83,24 @@
 	return ctx, config
 }
 
+func testPrebuiltEtcError(t *testing.T, pattern, bp string) {
+	t.Helper()
+
+	ctx, config := testPrebuiltEtcContext(t, bp)
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	if len(errs) > 0 {
+		android.FailIfNoMatchingErrors(t, pattern, errs)
+		return
+	}
+
+	_, errs = ctx.PrepareBuildActions(config)
+	if len(errs) > 0 {
+		android.FailIfNoMatchingErrors(t, pattern, errs)
+		return
+	}
+
+	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
+}
 func TestPrebuiltEtcVariants(t *testing.T) {
 	ctx, _ := testPrebuiltEtc(t, `
 		prebuilt_etc {
@@ -184,6 +210,33 @@
 	}
 }
 
+func TestPrebuiltEtcRelativeInstallPathInstallDirPath(t *testing.T) {
+	ctx, _ := testPrebuiltEtc(t, `
+		prebuilt_etc {
+			name: "foo.conf",
+			src: "foo.conf",
+			relative_install_path: "bar",
+		}
+	`)
+
+	p := ctx.ModuleForTests("foo.conf", "android_arm64_armv8-a").Module().(*PrebuiltEtc)
+	expected := buildDir + "/target/product/test_device/system/etc/bar"
+	if p.installDirPath.String() != expected {
+		t.Errorf("expected %q, got %q", expected, p.installDirPath.String())
+	}
+}
+
+func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
+	testPrebuiltEtcError(t, "relative_install_path is set. Cannot set sub_dir", `
+		prebuilt_etc {
+			name: "foo.conf",
+			src: "foo.conf",
+			sub_dir: "bar",
+			relative_install_path: "bar",
+		}
+	`)
+}
+
 func TestPrebuiltEtcHost(t *testing.T) {
 	ctx, _ := testPrebuiltEtc(t, `
 		prebuilt_etc_host {
diff --git a/finder/Android.bp b/finder/Android.bp
index 47b47c9..a5d7fd4 100644
--- a/finder/Android.bp
+++ b/finder/Android.bp
@@ -17,7 +17,7 @@
 //
 
 subdirs = [
-    "cmd"
+    "cmd",
 ]
 
 bootstrap_go_package {
@@ -30,8 +30,6 @@
         "finder_test.go",
     ],
     deps: [
-      "soong-finder-fs",
+        "soong-finder-fs",
     ],
 }
-
-
diff --git a/finder/cmd/Android.bp b/finder/cmd/Android.bp
index 9dc84ae..e066c39 100644
--- a/finder/cmd/Android.bp
+++ b/finder/cmd/Android.bp
@@ -22,8 +22,6 @@
         "finder.go",
     ],
     deps: [
-        "soong-finder"
+        "soong-finder",
     ],
 }
-
-
diff --git a/finder/finder.go b/finder/finder.go
index 89be0f5..f20bed2 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -1393,17 +1393,25 @@
 	for _, child := range children {
 		linkBits := child.Mode() & os.ModeSymlink
 		isLink := linkBits != 0
-		if child.IsDir() {
-			if !isLink {
+		if isLink {
+			childPath := filepath.Join(path, child.Name())
+			childStat, err := f.filesystem.Stat(childPath)
+			if err != nil {
+				// If stat fails this is probably a broken or dangling symlink, treat it as a file.
+				subfiles = append(subfiles, child.Name())
+			} else if childStat.IsDir() {
 				// Skip symlink dirs.
 				// We don't have to support symlink dirs because
 				// that would cause duplicates.
-				subdirs = append(subdirs, child.Name())
+			} else {
+				// We do have to support symlink files because the link name might be
+				// different than the target name
+				// (for example, Android.bp -> build/soong/root.bp)
+				subfiles = append(subfiles, child.Name())
 			}
+		} else if child.IsDir() {
+			subdirs = append(subdirs, child.Name())
 		} else {
-			// We do have to support symlink files because the link name might be
-			// different than the target name
-			// (for example, Android.bp -> build/soong/root.bp)
 			subfiles = append(subfiles, child.Name())
 		}
 
diff --git a/finder/finder_test.go b/finder/finder_test.go
index f6d0aa9..88b0c05 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -20,11 +20,8 @@
 	"log"
 	"os"
 	"path/filepath"
-	"reflect"
-	"runtime/debug"
 	"sort"
 	"testing"
-	"time"
 
 	"android/soong/finder/fs"
 )
@@ -41,7 +38,7 @@
 func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder {
 	f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads)
 	if err != nil {
-		fatal(t, err.Error())
+		t.Fatal(err.Error())
 	}
 	return f
 }
@@ -62,7 +59,7 @@
 func finderWithSameParams(t *testing.T, original *Finder) *Finder {
 	f, err := finderAndErrorWithSameParams(t, original)
 	if err != nil {
-		fatal(t, err.Error())
+		t.Fatal(err.Error())
 	}
 	return f
 }
@@ -78,146 +75,13 @@
 	return f, err
 }
 
-func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
-	parent := filepath.Dir(path)
-	filesystem.MkDirs(parent)
-	err := filesystem.WriteFile(path, []byte(content), 0777)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func create(t *testing.T, path string, filesystem *fs.MockFs) {
-	write(t, path, "hi", filesystem)
-}
-
-func delete(t *testing.T, path string, filesystem *fs.MockFs) {
-	err := filesystem.Remove(path)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
-	err := filesystem.RemoveAll(path)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
-	err := filesystem.Rename(oldPath, newPath)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) {
-	parentPath := filepath.Dir(newPath)
-	err := filesystem.MkDirs(parentPath)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	err = filesystem.Symlink(oldPath, newPath)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-func read(t *testing.T, path string, filesystem *fs.MockFs) string {
-	reader, err := filesystem.Open(path)
-	if err != nil {
-		t.Fatalf(err.Error())
-	}
-	bytes, err := ioutil.ReadAll(reader)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	return string(bytes)
-}
-func modTime(t *testing.T, path string, filesystem *fs.MockFs) time.Time {
-	stats, err := filesystem.Lstat(path)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	return stats.ModTime()
-}
-func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs) {
-	err := filesystem.SetReadable(path, readable)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-}
-
-func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) {
-	err := filesystem.SetReadErr(path, readErr)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-}
-
-func fatal(t *testing.T, message string) {
-	t.Error(message)
-	debug.PrintStack()
-	t.FailNow()
-}
-
-func assertSameResponse(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Expected Finder to return these %v paths:\n  %v,\ninstead returned these %v paths:  %v\n",
-				len(expected), expected, len(actual), actual),
-		)
-	}
-}
-
-func assertSameStatCalls(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Finder made incorrect Stat calls.\n"+
-					"Actual:\n"+
-					"%v\n"+
-					"Expected:\n"+
-					"%v\n"+
-					"\n",
-				actual, expected),
-		)
-	}
-}
-func assertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Finder made incorrect ReadDir calls.\n"+
-					"Actual:\n"+
-					"%v\n"+
-					"Expected:\n"+
-					"%v\n"+
-					"\n",
-				actual, expected),
-		)
-	}
-}
-
 // runSimpleTests creates a few files, searches for findme.txt, and checks for the expected matches
 func runSimpleTest(t *testing.T, existentPaths []string, expectedMatches []string) {
 	filesystem := newFs()
 	root := "/tmp"
 	filesystem.MkDirs(root)
 	for _, path := range existentPaths {
-		create(t, filepath.Join(root, path), filesystem)
+		fs.Create(t, filepath.Join(root, path), filesystem)
 	}
 
 	finder := newFinder(t,
@@ -237,7 +101,7 @@
 	for i := range expectedMatches {
 		absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i]))
 	}
-	assertSameResponse(t, foundPaths, absoluteMatches)
+	fs.AssertSameResponse(t, foundPaths, absoluteMatches)
 }
 
 // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
@@ -288,7 +152,7 @@
 func TestEmptyPath(t *testing.T) {
 	filesystem := newFs()
 	root := "/tmp"
-	create(t, filepath.Join(root, "findme.txt"), filesystem)
+	fs.Create(t, filepath.Join(root, "findme.txt"), filesystem)
 
 	finder := newFinder(
 		t,
@@ -302,7 +166,7 @@
 
 	foundPaths := finder.FindNamedAt("", "findme.txt")
 
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 }
 
 func TestFilesystemRoot(t *testing.T) {
@@ -311,7 +175,7 @@
 		filesystem := newFs()
 		root := "/"
 		createdPath := "/findme.txt"
-		create(t, createdPath, filesystem)
+		fs.Create(t, createdPath, filesystem)
 
 		finder := newFinderWithNumThreads(
 			t,
@@ -326,7 +190,7 @@
 
 		foundPaths := finder.FindNamedAt(root, "findme.txt")
 
-		assertSameResponse(t, foundPaths, []string{createdPath})
+		fs.AssertSameResponse(t, foundPaths, []string{createdPath})
 	}
 
 	testAgainstSeveralThreadcounts(t, testWithNumThreads)
@@ -334,7 +198,7 @@
 
 func TestNonexistentDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
 
 	_, err := newFinderAndErr(
 		t,
@@ -346,18 +210,18 @@
 		1,
 	)
 	if err == nil {
-		fatal(t, "Did not fail when given a nonexistent root directory")
+		t.Fatal("Did not fail when given a nonexistent root directory")
 	}
 }
 
 func TestExcludeDirs(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/exclude/findme.txt", filesystem)
-	create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
-	create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
-	create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
-	create(t, "/tmp/subdir/findme.txt", filesystem)
-	create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/exclude/findme.txt", filesystem)
+	fs.Create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -372,7 +236,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/subdir/findme.txt",
 			"/tmp/subdir/subdir/findme.txt"})
@@ -380,15 +244,15 @@
 
 func TestPruneFiles(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/out/findme.txt", filesystem)
-	create(t, "/tmp/out/.ignore-out-dir", filesystem)
-	create(t, "/tmp/out/child/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out/child/findme.txt", filesystem)
 
-	create(t, "/tmp/out2/.ignore-out-dir", filesystem)
-	create(t, "/tmp/out2/sub/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out2/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out2/sub/findme.txt", filesystem)
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/include/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/include/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -403,7 +267,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/include/findme.txt"})
 }
@@ -412,10 +276,10 @@
 // tests of the filesystem root are in TestFilesystemRoot
 func TestRootDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/subdir/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/subdir/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -429,17 +293,17 @@
 
 	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/a/subdir/findme.txt"})
 }
 
 func TestUncachedDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/subdir/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/subdir/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -456,7 +320,7 @@
 	// fail to notice its slowness. Instead, we only ever search the cache for files
 	// to return, which enforces that we can determine which files will be
 	// interesting upfront.
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 
 	finder.Shutdown()
 }
@@ -464,9 +328,9 @@
 func TestSearchingForFilesExcludedFromCache(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/misc.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/misc.txt", filesystem)
 
 	// set up the finder and run it
 	finder := newFinder(
@@ -483,7 +347,7 @@
 	// fail to notice its slowness. Instead, we only ever search the cache for files
 	// to return, which enforces that we can determine which files will be
 	// interesting upfront.
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 
 	finder.Shutdown()
 }
@@ -491,12 +355,12 @@
 func TestRelativeFilePaths(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/ignore/hi.txt", filesystem)
-	create(t, "/tmp/include/hi.txt", filesystem)
-	create(t, "/cwd/hi.txt", filesystem)
-	create(t, "/cwd/a/hi.txt", filesystem)
-	create(t, "/cwd/a/a/hi.txt", filesystem)
-	create(t, "/rel/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/ignore/hi.txt", filesystem)
+	fs.Create(t, "/tmp/include/hi.txt", filesystem)
+	fs.Create(t, "/cwd/hi.txt", filesystem)
+	fs.Create(t, "/cwd/a/hi.txt", filesystem)
+	fs.Create(t, "/cwd/a/a/hi.txt", filesystem)
+	fs.Create(t, "/rel/a/hi.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -509,25 +373,25 @@
 	defer finder.Shutdown()
 
 	foundPaths := finder.FindNamedAt("a", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"a/hi.txt",
 			"a/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
 
 	foundPaths = finder.FindNamedAt(".", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"hi.txt",
 			"a/hi.txt",
 			"a/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/rel", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/rel/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
 }
 
 // have to run this test with the race-detector (`go test -race src/android/soong/finder/*.go`)
@@ -535,7 +399,7 @@
 func TestRootDirsContainedInOtherRootDirs(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -549,15 +413,15 @@
 
 	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt"})
 }
 
 func TestFindFirst(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/b/hi.txt", filesystem)
-	create(t, "/tmp/b/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/a/hi.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -571,7 +435,7 @@
 
 	foundPaths := finder.FindFirstNamed("hi.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/hi.txt",
 			"/tmp/b/hi.txt"},
 	)
@@ -593,7 +457,7 @@
 		}
 		sort.Strings(paths)
 		for _, path := range paths {
-			create(t, path, filesystem)
+			fs.Create(t, path, filesystem)
 		}
 
 		// set up a finder
@@ -621,7 +485,7 @@
 		// check that each response was correct
 		for i := 0; i < numTests; i++ {
 			foundPaths := <-results
-			assertSameResponse(t, foundPaths, paths)
+			fs.AssertSameResponse(t, foundPaths, paths)
 		}
 	}
 
@@ -649,7 +513,7 @@
 	}
 	sort.Strings(allFiles)
 	for _, path := range allFiles {
-		create(t, path, filesystem)
+		fs.Create(t, path, filesystem)
 	}
 
 	// set up a finder
@@ -687,16 +551,16 @@
 	// check that each response was correct
 	for i := 0; i < numTests; i++ {
 		testRun := <-testRuns
-		assertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
+		fs.AssertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
 	}
 }
 
 func TestStrangelyFormattedPaths(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -710,7 +574,7 @@
 
 	foundPaths := finder.FindNamedAt("//tmp//a//..", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/b/findme.txt",
 			"/tmp/findme.txt"})
@@ -719,9 +583,9 @@
 func TestCorruptedCacheHeader(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	write(t, "/finder/finder-db", "sample header", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Write(t, "/finder/finder-db", "sample header", filesystem)
 
 	finder := newFinder(
 		t,
@@ -735,7 +599,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/findme.txt"})
 }
@@ -743,8 +607,8 @@
 func TestCanUseCache(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -759,11 +623,11 @@
 	// check the response of the first finder
 	correctResponse := []string{"/tmp/a/findme.txt",
 		"/tmp/findme.txt"}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	finder.Shutdown()
 
 	// check results
-	cacheText := read(t, finder.DbPath, filesystem)
+	cacheText := fs.Read(t, finder.DbPath, filesystem)
 	if len(cacheText) < 1 {
 		t.Fatalf("saved cache db is empty\n")
 	}
@@ -780,8 +644,8 @@
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 	// check results
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
 
 	finder2.Shutdown()
 }
@@ -789,8 +653,8 @@
 func TestCorruptedCacheBody(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -807,7 +671,7 @@
 	// check the response of the first finder
 	correctResponse := []string{"/tmp/a/findme.txt",
 		"/tmp/findme.txt"}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	numStatCalls := len(filesystem.StatCalls)
 	numReadDirCalls := len(filesystem.ReadDirCalls)
 
@@ -828,7 +692,7 @@
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 	// check results
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	numNewStatCalls := len(filesystem.StatCalls)
 	numNewReadDirCalls := len(filesystem.ReadDirCalls)
 	// It's permissable to make more Stat calls with a corrupted cache because
@@ -853,7 +717,7 @@
 func TestStatCalls(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run finder
 	finder := newFinder(
@@ -868,19 +732,19 @@
 	finder.Shutdown()
 
 	// check response
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
 }
 
 func TestFileAdded(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/ignore.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/ignore.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -895,11 +759,11 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/c/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/findme.txt", filesystem)
 	filesystem.Clock.Tick()
 	filesystem.ClearMetrics()
 
@@ -908,9 +772,9 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
 	finder2.Shutdown()
 
 }
@@ -918,11 +782,11 @@
 func TestDirectoriesAdded(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/ignore.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/ignore.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -936,13 +800,13 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/c/new/findme.txt", filesystem)
-	create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
-	create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -950,11 +814,11 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
 
 	finder2.Shutdown()
 }
@@ -962,8 +826,8 @@
 func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi1.txt", filesystem)
-	create(t, "/tmp/a/hi1.txt", filesystem)
+	fs.Create(t, "/tmp/hi1.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi1.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -977,12 +841,12 @@
 	foundPaths := finder.FindNamedAt("/tmp", "hi1.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/hi2.txt", filesystem)
-	create(t, "/tmp/a/hi2.txt", filesystem)
+	fs.Create(t, "/tmp/hi2.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi2.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -990,11 +854,11 @@
 	foundPaths = finder2.FindAll()
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
 
 	finder2.Shutdown()
 }
@@ -1002,11 +866,11 @@
 func TestFileDeleted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1020,11 +884,11 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	delete(t, "/tmp/b/findme.txt", filesystem)
+	fs.Delete(t, "/tmp/b/findme.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1032,9 +896,9 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
 
 	finder2.Shutdown()
 }
@@ -1042,11 +906,11 @@
 func TestDirectoriesDeleted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1060,7 +924,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1069,7 +933,7 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	removeAll(t, "/tmp/a/1", filesystem)
+	fs.RemoveAll(t, "/tmp/a/1", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1077,7 +941,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"})
 	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
 	// if the Finder detects the nonexistence of /tmp/a/1
@@ -1087,9 +951,9 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
 
 	finder2.Shutdown()
 }
@@ -1097,11 +961,11 @@
 func TestDirectoriesMoved(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1115,7 +979,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1124,7 +988,7 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	move(t, "/tmp/a", "/tmp/c", filesystem)
+	fs.Move(t, "/tmp/a", "/tmp/c", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1132,7 +996,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/b/findme.txt",
 			"/tmp/c/findme.txt",
@@ -1146,20 +1010,20 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
 	finder2.Shutdown()
 }
 
 func TestDirectoriesSwapped(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1173,7 +1037,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1182,9 +1046,9 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	move(t, "/tmp/a", "/tmp/temp", filesystem)
-	move(t, "/tmp/b", "/tmp/a", filesystem)
-	move(t, "/tmp/temp", "/tmp/b", filesystem)
+	fs.Move(t, "/tmp/a", "/tmp/temp", filesystem)
+	fs.Move(t, "/tmp/b", "/tmp/a", filesystem)
+	fs.Move(t, "/tmp/temp", "/tmp/b", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1192,7 +1056,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/b/findme.txt",
@@ -1206,9 +1070,9 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
 	finder2.Shutdown()
 }
 
@@ -1217,15 +1081,15 @@
 // runFsReplacementTest is a helper method called by other tests
 func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) {
 	// setup fs1
-	create(t, "/tmp/findme.txt", fs1)
-	create(t, "/tmp/a/findme.txt", fs1)
-	create(t, "/tmp/a/a/findme.txt", fs1)
+	fs.Create(t, "/tmp/findme.txt", fs1)
+	fs.Create(t, "/tmp/a/findme.txt", fs1)
+	fs.Create(t, "/tmp/a/a/findme.txt", fs1)
 
 	// setup fs2 to have the same directories but different files
-	create(t, "/tmp/findme.txt", fs2)
-	create(t, "/tmp/a/findme.txt", fs2)
-	create(t, "/tmp/a/a/ignoreme.txt", fs2)
-	create(t, "/tmp/a/b/findme.txt", fs2)
+	fs.Create(t, "/tmp/findme.txt", fs2)
+	fs.Create(t, "/tmp/a/findme.txt", fs2)
+	fs.Create(t, "/tmp/a/a/ignoreme.txt", fs2)
+	fs.Create(t, "/tmp/a/b/findme.txt", fs2)
 
 	// run the first finder
 	finder := newFinder(
@@ -1239,12 +1103,12 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"})
 
 	// copy the cache data from the first filesystem to the second
-	cacheContent := read(t, finder.DbPath, fs1)
-	write(t, finder.DbPath, cacheContent, fs2)
+	cacheContent := fs.Read(t, finder.DbPath, fs1)
+	fs.Write(t, finder.DbPath, cacheContent, fs2)
 
 	// run the second finder, with the same config and same cache contents but a different filesystem
 	finder2 := newFinder(
@@ -1258,11 +1122,11 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"})
-	assertSameStatCalls(t, fs2.StatCalls,
+	fs.AssertSameStatCalls(t, fs2.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
-	assertSameReadDirCalls(t, fs2.ReadDirCalls,
+	fs.AssertSameReadDirCalls(t, fs2.ReadDirCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
 	finder2.Shutdown()
 }
@@ -1292,7 +1156,7 @@
 	// setup filesystem
 	filesystem := newFs()
 	for i := 0; i < 5; i++ {
-		create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
+		fs.Create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
 	}
 
 	// run the first finder
@@ -1308,7 +1172,7 @@
 	finder.Shutdown()
 
 	// read db file
-	string1 := read(t, finder.DbPath, filesystem)
+	string1 := fs.Read(t, finder.DbPath, filesystem)
 
 	err := filesystem.Remove(finder.DbPath)
 	if err != nil {
@@ -1320,7 +1184,7 @@
 	finder2.FindNamedAt("/tmp", "findme.txt")
 	finder2.Shutdown()
 
-	string2 := read(t, finder.DbPath, filesystem)
+	string2 := fs.Read(t, finder.DbPath, filesystem)
 
 	if string1 != string2 {
 		t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+
@@ -1343,9 +1207,9 @@
 func TestNumSyscallsOfSecondFind(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/misc.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/misc.txt", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1357,15 +1221,15 @@
 		},
 	)
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
 
 	filesystem.ClearMetrics()
 
 	// run the finder again and confirm it doesn't check the filesystem
 	refoundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, refoundPaths, foundPaths)
-	assertSameStatCalls(t, filesystem.StatCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, refoundPaths, foundPaths)
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 
 	finder.Shutdown()
 }
@@ -1373,9 +1237,9 @@
 func TestChangingParamsOfSecondFind(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/metoo.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/metoo.txt", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1387,15 +1251,15 @@
 		},
 	)
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
 
 	filesystem.ClearMetrics()
 
 	// run the finder again and confirm it gets the right answer without asking the filesystem
 	refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt")
-	assertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 
 	finder.Shutdown()
 }
@@ -1403,15 +1267,15 @@
 func TestSymlinkPointingToFile(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/a/ignoreme.txt", filesystem)
-	link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
-	link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
-	link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
-	link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
-	link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
-	link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
-	link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/ignoreme.txt", filesystem)
+	fs.Link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
+	fs.Link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
+	fs.Link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
+	fs.Link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1432,20 +1296,21 @@
 		"/tmp/d/hi.txt",
 		"/tmp/f/hi.txt",
 	}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 
 }
 
 func TestSymlinkPointingToDirectory(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/dir/hi.txt", filesystem)
-	create(t, "/tmp/dir/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/dir/hi.txt", filesystem)
+	fs.Create(t, "/tmp/dir/ignoreme.txt", filesystem)
 
-	link(t, "/tmp/links/dir", "../dir", filesystem)
-	link(t, "/tmp/links/link", "../dir", filesystem)
-	link(t, "/tmp/links/broken", "nothingHere", filesystem)
-	link(t, "/tmp/links/recursive", "recursive", filesystem)
+	fs.Link(t, "/tmp/links/dir", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/link", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/hi.txt", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/broken", "nothingHere", filesystem)
+	fs.Link(t, "/tmp/links/recursive", "recursive", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1463,7 +1328,7 @@
 	correctResponse := []string{
 		"/tmp/dir/hi.txt",
 	}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 
 }
 
@@ -1472,9 +1337,9 @@
 func TestAddPruneFile(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/out/hi.txt", filesystem)
-	create(t, "/tmp/out/a/hi.txt", filesystem)
-	create(t, "/tmp/hi.txt", filesystem)
+	fs.Create(t, "/tmp/out/hi.txt", filesystem)
+	fs.Create(t, "/tmp/out/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
 
 	// do find
 	finder := newFinder(
@@ -1490,7 +1355,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
 
 	// check result
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/hi.txt",
 			"/tmp/out/hi.txt",
 			"/tmp/out/a/hi.txt"},
@@ -1499,19 +1364,19 @@
 
 	// modify filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/out/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out/.ignore-out-dir", filesystem)
 	// run another find and check its result
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
 	finder2.Shutdown()
 }
 
 func TestUpdatingDbIffChanged(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/b/bye.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/bye.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1526,11 +1391,11 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
 	filesystem.Clock.Tick()
 	filesystem.ClearMetrics()
 
@@ -1539,10 +1404,10 @@
 	foundPaths = finder2.FindAll()
 	finder2.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
 	expectedDbWriteTime := filesystem.Clock.Time()
-	actualDbWriteTime := modTime(t, finder2.DbPath, filesystem)
+	actualDbWriteTime := fs.ModTime(t, finder2.DbPath, filesystem)
 	if actualDbWriteTime != expectedDbWriteTime {
 		t.Fatalf("Expected to write db at %v, actually wrote db at %v\n",
 			expectedDbWriteTime, actualDbWriteTime)
@@ -1556,10 +1421,10 @@
 	foundPaths = finder3.FindAll()
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 	finder3.Shutdown()
-	actualDbWriteTime = modTime(t, finder3.DbPath, filesystem)
+	actualDbWriteTime = fs.ModTime(t, finder3.DbPath, filesystem)
 	if actualDbWriteTime != expectedDbWriteTime {
 		t.Fatalf("Re-wrote db even when contents did not change")
 	}
@@ -1569,10 +1434,10 @@
 func TestDirectoryNotPermitted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi.txt", filesystem)
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/a/a/hi.txt", filesystem)
-	create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1588,12 +1453,12 @@
 	finder.Shutdown()
 	allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"}
 	// check results
-	assertSameResponse(t, foundPaths, allPaths)
+	fs.AssertSameResponse(t, foundPaths, allPaths)
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
 
-	setReadable(t, "/tmp/a", false, filesystem)
+	fs.SetReadable(t, "/tmp/a", false, filesystem)
 	filesystem.Clock.Tick()
 
 	// run the second finder
@@ -1601,24 +1466,24 @@
 	foundPaths = finder2.FindAll()
 	finder2.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
 
 	// modify the filesystem back
-	setReadable(t, "/tmp/a", true, filesystem)
+	fs.SetReadable(t, "/tmp/a", true, filesystem)
 
 	// run the third finder
 	finder3 := finderWithSameParams(t, finder2)
 	foundPaths = finder3.FindAll()
 	finder3.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, allPaths)
+	fs.AssertSameResponse(t, foundPaths, allPaths)
 }
 
 func TestFileNotPermitted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi.txt", filesystem)
-	setReadable(t, "/tmp/hi.txt", false, filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
+	fs.SetReadable(t, "/tmp/hi.txt", false, filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1633,13 +1498,13 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
 }
 
 func TestCacheEntryPathUnexpectedError(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1654,14 +1519,14 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
 
 	// make the directory not readable
-	setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
+	fs.SetReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
 
 	// run the second finder
 	_, err := finderAndErrorWithSameParams(t, finder)
 	if err == nil {
-		fatal(t, "Failed to detect unexpected filesystem error")
+		t.Fatal("Failed to detect unexpected filesystem error")
 	}
 }
diff --git a/finder/fs/Android.bp b/finder/fs/Android.bp
index 27e3c7d..85929ae 100644
--- a/finder/fs/Android.bp
+++ b/finder/fs/Android.bp
@@ -22,8 +22,10 @@
     srcs: [
         "fs.go",
         "readdir.go",
+        "test.go",
     ],
     testSrcs: [
+        "fs_test.go",
         "readdir_test.go",
     ],
     darwin: {
@@ -37,4 +39,3 @@
         ],
     },
 }
-
diff --git a/finder/fs/fs.go b/finder/fs/fs.go
index 071f764..d2e3e4a 100644
--- a/finder/fs/fs.go
+++ b/finder/fs/fs.go
@@ -51,6 +51,7 @@
 	// getting information about files
 	Open(name string) (file io.ReadCloser, err error)
 	Lstat(path string) (stats os.FileInfo, err error)
+	Stat(path string) (stats os.FileInfo, err error)
 	ReadDir(path string) (contents []DirEntryInfo, err error)
 
 	InodeNumber(info os.FileInfo) (number uint64, err error)
@@ -99,6 +100,10 @@
 	return os.Lstat(path)
 }
 
+func (osFs) Stat(path string) (stats os.FileInfo, err error) {
+	return os.Stat(path)
+}
+
 func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
 	entries, err := readdir(path)
 	if err != nil {
@@ -376,7 +381,7 @@
 	size         int64
 	modTime      time.Time // time at which the inode's contents were modified
 	permTime     time.Time // time at which the inode's permissions were modified
-	isDir        bool
+	mode         os.FileMode
 	inodeNumber  uint64
 	deviceNumber uint64
 }
@@ -390,7 +395,7 @@
 }
 
 func (m *mockFileInfo) Mode() os.FileMode {
-	return 0
+	return m.mode
 }
 
 func (m *mockFileInfo) ModTime() time.Time {
@@ -398,7 +403,7 @@
 }
 
 func (m *mockFileInfo) IsDir() bool {
-	return m.isDir
+	return m.mode&os.ModeDir != 0
 }
 
 func (m *mockFileInfo) Sys() interface{} {
@@ -407,11 +412,11 @@
 
 func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      d.modTime,
 		permTime:     d.permTime,
-		isDir:        true,
+		mode:         os.ModeDir,
 		inodeNumber:  d.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -420,11 +425,11 @@
 
 func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      f.modTime,
 		permTime:     f.permTime,
-		isDir:        false,
+		mode:         0,
 		inodeNumber:  f.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -432,11 +437,11 @@
 
 func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      l.modTime,
 		permTime:     l.permTime,
-		isDir:        false,
+		mode:         os.ModeSymlink,
 		inodeNumber:  l.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -485,6 +490,16 @@
 	}
 }
 
+func (m *MockFs) Stat(path string) (stats os.FileInfo, err error) {
+	// resolve symlinks
+	path, err = m.resolve(path, true)
+	if err != nil {
+		return nil, err
+	}
+
+	return m.Lstat(path)
+}
+
 func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
 	mockInfo, ok := info.(*mockFileInfo)
 	if ok {
diff --git a/finder/fs/fs_test.go b/finder/fs/fs_test.go
new file mode 100644
index 0000000..22a4d7a
--- /dev/null
+++ b/finder/fs/fs_test.go
@@ -0,0 +1,76 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMockFs_LstatStatSymlinks(t *testing.T) {
+	// setup filesystem
+	filesystem := NewMockFs(nil)
+	Create(t, "/tmp/realdir/hi.txt", filesystem)
+	Create(t, "/tmp/realdir/ignoreme.txt", filesystem)
+
+	Link(t, "/tmp/links/dir", "../realdir", filesystem)
+	Link(t, "/tmp/links/file", "../realdir/hi.txt", filesystem)
+	Link(t, "/tmp/links/broken", "nothingHere", filesystem)
+	Link(t, "/tmp/links/recursive", "recursive", filesystem)
+
+	assertStat := func(t *testing.T, stat os.FileInfo, err error, wantName string, wantMode os.FileMode) {
+		t.Helper()
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if g, w := stat.Name(), wantName; g != w {
+			t.Errorf("want name %q, got %q", w, g)
+		}
+		if g, w := stat.Mode(), wantMode; g != w {
+			t.Errorf("%s: want mode %q, got %q", wantName, w, g)
+		}
+	}
+
+	assertErr := func(t *testing.T, err error, wantErr string) {
+		if err == nil || err.Error() != wantErr {
+			t.Errorf("want error %q, got %q", wantErr, err)
+		}
+	}
+
+	stat, err := filesystem.Lstat("/tmp/links/dir")
+	assertStat(t, stat, err, "dir", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/dir")
+	assertStat(t, stat, err, "realdir", os.ModeDir)
+
+	stat, err = filesystem.Lstat("/tmp/links/file")
+	assertStat(t, stat, err, "file", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/file")
+	assertStat(t, stat, err, "hi.txt", 0)
+
+	stat, err = filesystem.Lstat("/tmp/links/broken")
+	assertStat(t, stat, err, "broken", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/broken")
+	assertErr(t, err, "stat /tmp/links/nothingHere: file does not exist")
+
+	stat, err = filesystem.Lstat("/tmp/links/recursive")
+	assertStat(t, stat, err, "recursive", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/recursive")
+	assertErr(t, err, "read /tmp/links/recursive: too many levels of symbolic links")
+}
diff --git a/finder/fs/test.go b/finder/fs/test.go
new file mode 100644
index 0000000..cb2140e
--- /dev/null
+++ b/finder/fs/test.go
@@ -0,0 +1,146 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"io/ioutil"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+)
+
+func Write(t *testing.T, path string, content string, filesystem *MockFs) {
+	parent := filepath.Dir(path)
+	filesystem.MkDirs(parent)
+	err := filesystem.WriteFile(path, []byte(content), 0777)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Create(t *testing.T, path string, filesystem *MockFs) {
+	Write(t, path, "hi", filesystem)
+}
+
+func Delete(t *testing.T, path string, filesystem *MockFs) {
+	err := filesystem.Remove(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func RemoveAll(t *testing.T, path string, filesystem *MockFs) {
+	err := filesystem.RemoveAll(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Move(t *testing.T, oldPath string, newPath string, filesystem *MockFs) {
+	err := filesystem.Rename(oldPath, newPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Link(t *testing.T, newPath string, oldPath string, filesystem *MockFs) {
+	parentPath := filepath.Dir(newPath)
+	err := filesystem.MkDirs(parentPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	err = filesystem.Symlink(oldPath, newPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Read(t *testing.T, path string, filesystem *MockFs) string {
+	reader, err := filesystem.Open(path)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	bytes, err := ioutil.ReadAll(reader)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	return string(bytes)
+}
+
+func ModTime(t *testing.T, path string, filesystem *MockFs) time.Time {
+	stats, err := filesystem.Lstat(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	return stats.ModTime()
+}
+
+func SetReadable(t *testing.T, path string, readable bool, filesystem *MockFs) {
+	err := filesystem.SetReadable(path, readable)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func SetReadErr(t *testing.T, path string, readErr error, filesystem *MockFs) {
+	err := filesystem.SetReadErr(path, readErr)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func AssertSameResponse(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Expected Finder to return these %v paths:\n  %v,\ninstead returned these %v paths:  %v\n",
+			len(expected), expected, len(actual), actual)
+	}
+}
+
+func AssertSameStatCalls(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Finder made incorrect Stat calls.\n"+
+			"Actual:\n"+
+			"%v\n"+
+			"Expected:\n"+
+			"%v\n"+
+			"\n",
+			actual, expected)
+	}
+}
+
+func AssertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Finder made incorrect ReadDir calls.\n"+
+			"Actual:\n"+
+			"%v\n"+
+			"Expected:\n"+
+			"%v\n"+
+			"\n",
+			actual, expected)
+	}
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index f6904f1..00baa57 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -83,7 +83,6 @@
 
 type generatorProperties struct {
 	// The command to run on one or more input files. Cmd supports substitution of a few variables
-	// (the actual substitution is implemented in GenerateAndroidBuildActions below)
 	//
 	// Available variables for substitution:
 	//
@@ -94,9 +93,6 @@
 	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true
 	//  $(genDir): the sandbox directory for this tool; contains $(out)
 	//  $$: a literal $
-	//
-	// All files used must be declared as inputs (to ensure proper up-to-date checks).
-	// Use "$(in)" directly in Cmd to ensure that all inputs used are declared.
 	Cmd *string
 
 	// Enable reading a file containing dependencies in gcc format after the command completes
@@ -559,6 +555,12 @@
 	}
 }
 
+func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
+	// we can safely ignore the check here.
+	return nil
+}
+
 func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
 	module := &Module{
 		taskGenerator: taskGenerator,
diff --git a/java/aar.go b/java/aar.go
index 074ead4..500788f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -762,6 +762,10 @@
 	return a.depIsInSameApex(ctx, dep)
 }
 
+func (g *AARImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	return nil
+}
+
 var _ android.PrebuiltInterface = (*Import)(nil)
 
 // android_library_import imports an `.aar` file into the build graph as if it was built with android_library.
diff --git a/java/androidmk.go b/java/androidmk.go
index 62cf169..e73b030 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -91,7 +91,7 @@
 	} else {
 		mainEntries = android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
-			DistFile:   android.OptionalPathForPath(library.distFile),
+			DistFiles:  library.distFiles,
 			OutputFile: android.OptionalPathForPath(library.outputFile),
 			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
@@ -550,14 +550,14 @@
 	// needed because an invalid output file would prevent the make entries from
 	// being written.
 	// TODO(b/146727827): Revert when we do not need to generate stubs and API separately.
-	distFile := android.OptionalPathForPath(dstubs.apiFile)
+	distFile := dstubs.apiFile
 	outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar)
 	if !outputFile.Valid() {
-		outputFile = distFile
+		outputFile = android.OptionalPathForPath(distFile)
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		DistFile:   distFile,
+		DistFiles:  android.MakeDefaultDistFiles(distFile),
 		OutputFile: outputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index d471fb7..075b7aa 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -156,17 +156,158 @@
 		}
 	`)
 
-	without_tag_entries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
-	with_tag_entries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
+	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
+	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
 
-	if len(without_tag_entries) != 2 || len(with_tag_entries) != 2 {
-		t.Errorf("two mk entries per module expected, got %d and %d", len(without_tag_entries), len(with_tag_entries))
+	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
+		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
 	}
-	if !with_tag_entries[0].DistFile.Valid() || !strings.Contains(with_tag_entries[0].DistFile.String(), "/javac/foo_with_tag.jar") {
-		t.Errorf("expected classes.jar DistFile, got %v", with_tag_entries[0].DistFile)
+	if len(withTagEntries[0].DistFiles[".jar"]) != 1 ||
+		!strings.Contains(withTagEntries[0].DistFiles[".jar"][0].String(), "/javac/foo_with_tag.jar") {
+		t.Errorf("expected DistFiles to contain classes.jar, got %v", withTagEntries[0].DistFiles)
 	}
-	if without_tag_entries[0].DistFile.Valid() {
-		t.Errorf("did not expect explicit DistFile, got %v", without_tag_entries[0].DistFile)
+	if len(withoutTagEntries[0].DistFiles[".jar"]) > 0 {
+		t.Errorf("did not expect explicit DistFile for .jar tag, got %v", withoutTagEntries[0].DistFiles[".jar"])
+	}
+}
+
+func TestDistWithDest(t *testing.T) {
+	ctx, config := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			compile_dex: true,
+			dist: {
+				targets: ["my_goal"],
+				dest: "my/custom/dest/dir",
+			},
+		}
+	`)
+
+	module := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", module)
+	if len(entries) != 2 {
+		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
+	}
+
+	distStrings := entries[0].GetDistForGoals(module)
+
+	if len(distStrings) != 2 {
+		t.Errorf("Expected 2 entries for dist: PHONY and dist-for-goals, but got %q", distStrings)
+	}
+
+	if distStrings[0] != ".PHONY: my_goal\n" {
+		t.Errorf("Expected .PHONY entry to declare my_goal, but got: %s", distStrings[0])
+	}
+
+	if !strings.Contains(distStrings[1], "$(call dist-for-goals,my_goal") ||
+		!strings.Contains(distStrings[1], ".intermediates/foo/android_common/dex/foo.jar:my/custom/dest/dir") {
+		t.Errorf(
+			"Expected dist-for-goals entry to contain my_goal and new dest dir, but got: %s", distStrings[1])
+	}
+}
+
+func TestDistsWithAllProperties(t *testing.T) {
+	ctx, config := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			compile_dex: true,
+			dist: {
+				targets: ["baz"],
+			},
+			dists: [
+				{
+					targets: ["bar"],
+					tag: ".jar",
+					dest: "bar.jar",
+					dir: "bar/dir",
+					suffix: ".qux",
+				},
+			]
+		}
+	`)
+
+	module := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, config, "", module)
+	if len(entries) != 2 {
+		t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
+	}
+
+	distStrings := entries[0].GetDistForGoals(module)
+
+	if len(distStrings) != 4 {
+		t.Errorf("Expected 4 entries for dist: PHONY and dist-for-goals, but got %d", len(distStrings))
+	}
+
+	if distStrings[0] != ".PHONY: bar\n" {
+		t.Errorf("Expected .PHONY entry to declare bar, but got: %s", distStrings[0])
+	}
+
+	if !strings.Contains(distStrings[1], "$(call dist-for-goals,bar") ||
+		!strings.Contains(
+			distStrings[1],
+			".intermediates/foo/android_common/javac/foo.jar:bar/dir/bar.qux.jar") {
+		t.Errorf(
+			"Expected dist-for-goals entry to contain bar and new dest dir, but got: %s", distStrings[1])
+	}
+
+	if distStrings[2] != ".PHONY: baz\n" {
+		t.Errorf("Expected .PHONY entry to declare baz, but got: %s", distStrings[2])
+	}
+
+	if !strings.Contains(distStrings[3], "$(call dist-for-goals,baz") ||
+		!strings.Contains(distStrings[3], ".intermediates/foo/android_common/dex/foo.jar:foo.jar") {
+		t.Errorf(
+			"Expected dist-for-goals entry to contain my_other_goal and new dest dir, but got: %s",
+			distStrings[3])
+	}
+}
+
+func TestDistsWithTag(t *testing.T) {
+	ctx, config := testJava(t, `
+		java_library {
+			name: "foo_without_tag",
+			srcs: ["a.java"],
+			compile_dex: true,
+			dists: [
+				{
+					targets: ["hi"],
+				},
+			],
+		}
+		java_library {
+			name: "foo_with_tag",
+			srcs: ["a.java"],
+			compile_dex: true,
+			dists: [
+				{
+					targets: ["hi"],
+					tag: ".jar",
+				},
+			],
+		}
+	`)
+
+	moduleWithoutTag := ctx.ModuleForTests("foo_without_tag", "android_common").Module()
+	moduleWithTag := ctx.ModuleForTests("foo_with_tag", "android_common").Module()
+
+	withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithoutTag)
+	withTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithTag)
+
+	if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
+		t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
+	}
+
+	distFilesWithoutTag := withoutTagEntries[0].DistFiles
+	distFilesWithTag := withTagEntries[0].DistFiles
+
+	if len(distFilesWithTag[".jar"]) != 1 ||
+		!strings.Contains(distFilesWithTag[".jar"][0].String(), "/javac/foo_with_tag.jar") {
+		t.Errorf("expected foo_with_tag's .jar-tagged DistFiles to contain classes.jar, got %v", distFilesWithTag[".jar"])
+	}
+	if len(distFilesWithoutTag[".jar"]) > 0 {
+		t.Errorf("did not expect foo_without_tag's .jar-tagged DistFiles to contain files, but got %v", distFilesWithoutTag[".jar"])
 	}
 }
 
diff --git a/java/app.go b/java/app.go
index 4bb292d..37a6453 100755
--- a/java/app.go
+++ b/java/app.go
@@ -151,7 +151,7 @@
 				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
 				"screen-densities":  screenDensities,
 				"sdk-version":       ctx.Config().PlatformSdkVersion(),
-				"stem":              ctx.ModuleName(),
+				"stem":              as.BaseModuleName(),
 			},
 		})
 }
@@ -421,8 +421,10 @@
 		if String(a.deviceProperties.Min_sdk_version) == "" {
 			ctx.PropertyErrorf("updatable", "updatable apps must set min_sdk_version.")
 		}
+
 		if minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx); err == nil {
 			a.checkJniLibsSdkVersion(ctx, minSdkVersion)
+			android.CheckMinSdkVersion(a, ctx, int(minSdkVersion))
 		} else {
 			ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		}
@@ -862,13 +864,13 @@
 	return jniLibs, certificates
 }
 
-func (a *AndroidApp) walkPayloadDeps(ctx android.ModuleContext,
-	do func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool)) {
-
+func (a *AndroidApp) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		isExternal := !a.DepIsInSameApex(ctx, child)
 		if am, ok := child.(android.ApexModule); ok {
-			do(ctx, parent, am, isExternal)
+			if !do(ctx, parent, am, isExternal) {
+				return false
+			}
 		}
 		return !isExternal
 	})
@@ -880,7 +882,7 @@
 	}
 
 	depsInfo := android.DepNameToDepInfoMap{}
-	a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) {
+	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		depName := to.Name()
 		if info, exist := depsInfo[depName]; exist {
 			info.From = append(info.From, from.Name())
@@ -900,6 +902,7 @@
 				MinSdkVersion: toMinSdkVersion,
 			}
 		}
+		return true
 	})
 
 	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(), depsInfo)
@@ -938,7 +941,7 @@
 }
 
 func (a *AndroidApp) IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool {
-	return ctx.Device() && (ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled())
+	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
 func (a *AndroidApp) PreventInstall() {
@@ -1563,6 +1566,11 @@
 	return sdkSpecFrom("")
 }
 
+func (j *AndroidAppImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
+	return nil
+}
+
 func createVariantGroupType(variants []string, variantGroupName string) reflect.Type {
 	props := reflect.TypeOf((*AndroidAppImportProperties)(nil))
 
diff --git a/java/app_test.go b/java/app_test.go
index e45ba70..120dc00 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -473,6 +473,24 @@
 	}
 }
 
+func TestUpdatableApps_TransitiveDepsShouldSetMinSdkVersion(t *testing.T) {
+	testJavaError(t, `module "bar".*: should support min_sdk_version\(29\)`, cc.GatherRequiredDepsForTest(android.Android)+`
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			updatable: true,
+			sdk_version: "current",
+			min_sdk_version: "29",
+			static_libs: ["bar"],
+		}
+
+		java_library {
+			name: "bar",
+			sdk_version: "current",
+		}
+	`)
+}
+
 func TestUpdatableApps_JniLibsShouldShouldSupportMinSdkVersion(t *testing.T) {
 	testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		android_app {
diff --git a/java/config/config.go b/java/config/config.go
index 0351009..0fe74c8 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -34,7 +34,8 @@
 	DefaultLambdaStubsLibrary                = "core-lambda-stubs"
 	SdkLambdaStubsPath                       = "prebuilts/sdk/tools/core-lambda-stubs.jar"
 
-	DefaultJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
+	DefaultMakeJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
+	DefaultJacocoExcludeFilter     = []string{"org.junit.**", "org.jacoco.**", "org.mockito.**"}
 
 	InstrumentFrameworkModules = []string{
 		"framework",
@@ -109,7 +110,7 @@
 	pctx.SourcePathVariable("JavaKytheExtractorJar", "prebuilts/build-tools/common/framework/javac_extractor.jar")
 	pctx.SourcePathVariable("Ziptime", "prebuilts/build-tools/${hostPrebuiltTag}/bin/ziptime")
 
-	pctx.SourcePathVariable("GenKotlinBuildFileCmd", "build/soong/scripts/gen-kotlin-build-file.sh")
+	pctx.HostBinToolVariable("GenKotlinBuildFileCmd", "gen-kotlin-build-file.py")
 
 	pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh")
 	pctx.SourcePathVariable("PackageCheckCmd", "build/soong/scripts/package-check.sh")
diff --git a/java/config/makevars.go b/java/config/makevars.go
index 29f1f9b..708a72a 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -66,7 +66,7 @@
 	ctx.Strict("ZIPSYNC", "${ZipSyncCmd}")
 
 	ctx.Strict("JACOCO_CLI_JAR", "${JacocoCLIJar}")
-	ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultJacocoExcludeFilter, ","))
+	ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultMakeJacocoExcludeFilter, ","))
 
 	ctx.Strict("EXTRACT_JAR_PACKAGES", "${ExtractJarPackagesCmd}")
 
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 7f1afd6..f1b7178 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -173,7 +173,7 @@
 			profileBootListing = android.ExistentPathForSource(ctx,
 				ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot")
 			profileIsTextListing = true
-		} else {
+		} else if global.ProfileDir != "" {
 			profileClassListing = android.ExistentPathForSource(ctx,
 				global.ProfileDir, ctx.ModuleName()+".prof")
 		}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 43fb964..99bfb6d 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1320,7 +1320,6 @@
 			cmd.Flag("--exclude-documentation-from-stubs")
 		}
 	}
-	cmd.FlagWithArg("--hide ", "ShowingMemberInHiddenClass") // b/159121253 -- remove it once all the violations are fixed.
 }
 
 func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
@@ -1414,7 +1413,7 @@
 }
 
 func (d *Droidstubs) apiToXmlFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() {
+	if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() && d.apiFile != nil {
 		if d.apiFile.String() == "" {
 			ctx.ModuleErrorf("API signature file has to be specified in Metalava when jdiff is enabled.")
 		}
@@ -1842,13 +1841,19 @@
 			Flag("-XDignore.symbol.file").
 			FlagWithArg("-doclet ", "jdiff.JDiff").
 			FlagWithInput("-docletpath ", jdiff).
-			Flag("-quiet").
-			FlagWithArg("-newapi ", strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext())).
-			FlagWithArg("-newapidir ", filepath.Dir(d.apiXmlFile.String())).
-			Implicit(d.apiXmlFile).
-			FlagWithArg("-oldapi ", strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext())).
-			FlagWithArg("-oldapidir ", filepath.Dir(d.lastReleasedApiXmlFile.String())).
-			Implicit(d.lastReleasedApiXmlFile)
+			Flag("-quiet")
+
+		if d.apiXmlFile != nil {
+			cmd.FlagWithArg("-newapi ", strings.TrimSuffix(d.apiXmlFile.Base(), d.apiXmlFile.Ext())).
+				FlagWithArg("-newapidir ", filepath.Dir(d.apiXmlFile.String())).
+				Implicit(d.apiXmlFile)
+		}
+
+		if d.lastReleasedApiXmlFile != nil {
+			cmd.FlagWithArg("-oldapi ", strings.TrimSuffix(d.lastReleasedApiXmlFile.Base(), d.lastReleasedApiXmlFile.Ext())).
+				FlagWithArg("-oldapidir ", filepath.Dir(d.lastReleasedApiXmlFile.String())).
+				Implicit(d.lastReleasedApiXmlFile)
+		}
 
 		rule.Command().
 			BuiltTool(ctx, "soong_zip").
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index ce624bf..130b634 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -59,10 +59,9 @@
 
 var _ hiddenAPIIntf = (*hiddenAPI)(nil)
 
-func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, dexJar android.ModuleOutPath,
+func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, name string, primary bool, dexJar android.ModuleOutPath,
 	implementationJar android.Path, uncompressDex bool) android.ModuleOutPath {
 	if !ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
-		name := ctx.ModuleName()
 
 		// Modules whose names are of the format <x>-hiddenapi provide hiddenapi information
 		// for the boot jar module <x>. Otherwise, the module provides information for itself.
@@ -90,7 +89,14 @@
 			// the gathered information in the generated dex file.
 			if name == bootJarName {
 				hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", name+".jar")
-				h.bootDexJarPath = dexJar
+
+				// More than one library with the same classes can be encoded but only one can
+				// be added to the global set of flags, otherwise it will result in duplicate
+				// classes which is an error. Therefore, only add the dex jar of one of them
+				// to the global set of flags.
+				if primary {
+					h.bootDexJarPath = dexJar
+				}
 				hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex)
 				dexJar = hiddenAPIJar
 			}
diff --git a/java/jacoco.go b/java/jacoco.go
index bce9822..9162161 100644
--- a/java/jacoco.go
+++ b/java/jacoco.go
@@ -25,6 +25,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/java/config"
 )
 
 var (
@@ -76,7 +77,8 @@
 	if err != nil {
 		ctx.PropertyErrorf("jacoco.include_filter", "%s", err.Error())
 	}
-	excludes, err := jacocoFiltersToSpecs(j.properties.Jacoco.Exclude_filter)
+	// Also include the default list of classes to exclude from instrumentation.
+	excludes, err := jacocoFiltersToSpecs(append(j.properties.Jacoco.Exclude_filter, config.DefaultJacocoExcludeFilter...))
 	if err != nil {
 		ctx.PropertyErrorf("jacoco.exclude_filter", "%s", err.Error())
 	}
diff --git a/java/java.go b/java/java.go
index cb2ae28..0d46672 100644
--- a/java/java.go
+++ b/java/java.go
@@ -44,7 +44,7 @@
 			PropertyName: "java_libs",
 		},
 		func(j *Library) android.Path {
-			implementationJars := j.ImplementationJars()
+			implementationJars := j.ImplementationAndResourcesJars()
 			if len(implementationJars) != 1 {
 				panic(fmt.Errorf("there must be only one implementation jar from %q", j.Name()))
 			}
@@ -342,6 +342,12 @@
 	// otherwise provides defaults libraries to add to the bootclasspath.
 	System_modules *string
 
+	// The name of the module as used in build configuration.
+	//
+	// Allows a library to separate its actual name from the name used in
+	// build configuration, e.g.ctx.Config().BootJars().
+	ConfigurationName *string `blueprint:"mutated"`
+
 	// set the name of the output
 	Stem *string
 
@@ -479,7 +485,7 @@
 	// list of the xref extraction files
 	kytheFiles android.Paths
 
-	distFile android.Path
+	distFiles android.TaggedDistFiles
 
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
@@ -1632,8 +1638,11 @@
 			return
 		}
 
+		configurationName := j.ConfigurationName()
+		primary := configurationName == ctx.ModuleName()
+
 		// Hidden API CSV generation and dex encoding
-		dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, dexOutputFile, j.implementationJarFile,
+		dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, configurationName, primary, dexOutputFile, j.implementationJarFile,
 			proptools.Bool(j.deviceProperties.Uncompress_dex))
 
 		// merge dex jar with resources if necessary
@@ -1888,10 +1897,32 @@
 	return j.depIsInSameApex(ctx, dep)
 }
 
+func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	sdkSpec := j.minSdkVersion()
+	if !sdkSpec.specified() {
+		return fmt.Errorf("min_sdk_version is not specified")
+	}
+	if sdkSpec.kind == sdkCore {
+		return nil
+	}
+	ver, err := sdkSpec.effectiveVersion(ctx)
+	if err != nil {
+		return err
+	}
+	if int(ver) > sdkVersion {
+		return fmt.Errorf("newer SDK(%v)", ver)
+	}
+	return nil
+}
+
 func (j *Module) Stem() string {
 	return proptools.StringDefault(j.deviceProperties.Stem, j.Name())
 }
 
+func (j *Module) ConfigurationName() string {
+	return proptools.StringDefault(j.deviceProperties.ConfigurationName, j.BaseModuleName())
+}
+
 func (j *Module) JacocoReportClassesFile() android.Path {
 	return j.jacocoReportClassesFile
 }
@@ -1904,18 +1935,9 @@
 // Java libraries (.jar file)
 //
 
-type LibraryProperties struct {
-	Dist struct {
-		// The tag of the output of this module that should be output.
-		Tag *string `android:"arch_variant"`
-	} `android:"arch_variant"`
-}
-
 type Library struct {
 	Module
 
-	libraryProperties LibraryProperties
-
 	InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.Paths)
 }
 
@@ -1977,14 +1999,7 @@
 			j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
 
-	// Verify Dist.Tag is set to a supported output
-	if j.libraryProperties.Dist.Tag != nil {
-		distFiles, err := j.OutputFiles(*j.libraryProperties.Dist.Tag)
-		if err != nil {
-			ctx.PropertyErrorf("dist.tag", "%s", err.Error())
-		}
-		j.distFile = distFiles[0]
-	}
+	j.distFiles = j.GenerateTaggedDistFiles(ctx)
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -2102,7 +2117,6 @@
 	module := &Library{}
 
 	module.addHostAndDeviceProperties()
-	module.AddProperties(&module.libraryProperties)
 
 	module.initModuleAndImport(&module.ModuleBase)
 
@@ -2652,6 +2666,11 @@
 	return j.depIsInSameApex(ctx, dep)
 }
 
+func (j *Import) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
+	return nil
+}
+
 // Add compile time check for interface implementation
 var _ android.IDEInfo = (*Import)(nil)
 var _ android.IDECustomizedModuleName = (*Import)(nil)
@@ -2821,6 +2840,11 @@
 	return j.dexJarFile
 }
 
+func (j *DexImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// we don't check prebuilt modules for sdk_version
+	return nil
+}
+
 // dex_import imports a `.jar` file containing classes.dex files.
 //
 // A dex_import module cannot be used as a dependency of a java_* or android_* module, it can only be installed
@@ -2893,6 +2917,7 @@
 		&appProperties{},
 		&appTestProperties{},
 		&overridableAppProperties{},
+		&testProperties{},
 		&ImportProperties{},
 		&AARImportProperties{},
 		&sdkLibraryProperties{},
diff --git a/java/java_test.go b/java/java_test.go
index 1d07e70..def42db 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -25,6 +25,7 @@
 	"strings"
 	"testing"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -171,6 +172,20 @@
 	}
 }
 
+func checkModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
+	t.Helper()
+	module := ctx.ModuleForTests(name, variant).Module()
+	deps := []string{}
+	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
+		deps = append(deps, m.Name())
+	})
+	sort.Strings(deps)
+
+	if actual := deps; !reflect.DeepEqual(expected, actual) {
+		t.Errorf("expected %#q, found %#q", expected, actual)
+	}
+}
+
 func TestJavaLinkType(t *testing.T) {
 	testJava(t, `
 		java_library {
@@ -630,6 +645,90 @@
 			t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], sdklibStubsJar.String())
 		}
 	}
+
+	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`prebuilt_sdklib.stubs.source.test`,
+		`prebuilt_sdklib.stubs.system`,
+		`prebuilt_sdklib.stubs.test`,
+	})
+}
+
+func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	checkModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+		`sdklib.impl`,
+		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
+		// dependency is added after prebuilts may have been renamed and so has to use
+		// the renamed name.
+		`sdklib.stubs`,
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			prefer: true,
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		// This should be sdklib.stubs but is switched to the prebuilt because it is preferred.
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	checkModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		`sdklib.xml`,
+	})
 }
 
 func TestDefaults(t *testing.T) {
@@ -1379,6 +1478,28 @@
 		`)
 }
 
+func TestJavaSdkLibrary_Deps(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+		`)
+
+	checkModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+}
+
 func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
 	testJava(t, `
 		java_sdk_library_import {
diff --git a/java/kotlin.go b/java/kotlin.go
index 673970b..e3356be 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -31,7 +31,9 @@
 		Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
 			`mkdir -p "$classesDir" "$srcJarDir" "$emptyDir" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-			`${config.GenKotlinBuildFileCmd} $classpath "$name" $classesDir $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
+			` --out_dir "$classesDir" --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
+			` --out "$kotlinBuildFile" && ` +
 			`${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` +
 			`-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile -kotlin-home $emptyDir && ` +
 			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` +
@@ -74,7 +76,7 @@
 		Inputs:      srcFiles,
 		Implicits:   deps,
 		Args: map[string]string{
-			"classpath":       flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"classpath":       flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":    flags.kotlincFlags,
 			"srcJars":         strings.Join(srcJars.Strings(), " "),
 			"classesDir":      android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
@@ -93,7 +95,9 @@
 		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` +
 			`mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
-			`${config.GenKotlinBuildFileCmd} $classpath "$name" "" $out.rsp $srcJarDir/list > $kotlinBuildFile &&` +
+			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
+			` --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
+			` --out "$kotlinBuildFile" && ` +
 			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` +
 			`-Xplugin=${config.KotlinKaptJar} ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` +
@@ -162,7 +166,7 @@
 		Inputs:         srcFiles,
 		Implicits:      deps,
 		Args: map[string]string{
-			"classpath":         flags.kotlincClasspath.FormJavaClassPath("-classpath"),
+			"classpath":         flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":      flags.kotlincFlags,
 			"srcJars":           strings.Join(srcJars.Strings(), " "),
 			"srcJarDir":         android.PathForModuleOut(ctx, "kapt", "srcJars").String(),
diff --git a/java/lint.go b/java/lint.go
index fac9a19..b73d6a5 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -88,7 +88,7 @@
 }
 
 func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
-	rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir android.WritablePath, deps android.Paths) {
+	rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) {
 
 	var resourcesList android.WritablePath
 	if len(l.resources) > 0 {
@@ -106,6 +106,7 @@
 	// Lint looks for a lint.xml file next to the project.xml file, give it one.
 	configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
 	cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
+	homeDir = android.PathForModuleOut(ctx, "lint", "home")
 
 	srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
@@ -154,8 +155,11 @@
 	cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
 	deps = append(deps, l.extraLintCheckJars...)
 
-	// The cache tag in project.xml is relative to the project.xml file.
-	cmd.FlagWithArg("--cache_dir ", "cache")
+	cmd.FlagWithArg("--root_dir ", "$PWD")
+
+	// The cache tag in project.xml is relative to the root dir, or the project.xml file if
+	// the root dir is not set.
+	cmd.FlagWithArg("--cache_dir ", cacheDir.String())
 
 	cmd.FlagWithInput("@",
 		android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
@@ -165,7 +169,7 @@
 	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
 	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
 
-	return projectXMLPath, configXMLPath, cacheDir, deps
+	return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
 }
 
 // generateManifest adds a command to the rule to write a dummy manifest cat contains the
@@ -207,18 +211,19 @@
 		l.manifest = manifest
 	}
 
-	projectXML, lintXML, cacheDir, deps := l.writeLintProjectXML(ctx, rule)
+	projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
 
 	l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
 	l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
 	l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
 
-	rule.Command().Text("rm -rf").Flag(cacheDir.String())
-	rule.Command().Text("mkdir -p").Flag(cacheDir.String())
+	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
+	rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
 
 	rule.Command().
 		Text("(").
 		Flag("JAVA_OPTS=-Xmx2048m").
+		FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
 		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
 		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
 		Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
@@ -239,7 +244,7 @@
 		Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
 		Text(")")
 
-	rule.Command().Text("rm -rf").Flag(cacheDir.String())
+	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 
 	rule.Build(pctx, ctx, "lint", "lint")
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 782c94a..8f8f8ce 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1087,15 +1087,22 @@
 
 // Creates the implementation java library
 func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) {
+
+	moduleNamePtr := proptools.StringPtr(module.BaseModuleName())
+
 	props := struct {
-		Name       *string
-		Visibility []string
-		Instrument bool
+		Name              *string
+		Visibility        []string
+		Instrument        bool
+		ConfigurationName *string
 	}{
 		Name:       proptools.StringPtr(module.implLibraryModuleName()),
 		Visibility: module.sdkLibraryProperties.Impl_library_visibility,
 		// Set the instrument property to ensure it is instrumented when instrumentation is required.
 		Instrument: true,
+
+		// Make the created library behave as if it had the same name as this module.
+		ConfigurationName: moduleNamePtr,
 	}
 
 	properties := []interface{}{
@@ -1905,6 +1912,11 @@
 	return false
 }
 
+func (module *SdkLibraryImport) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// we don't check prebuilt modules for sdk_version
+	return nil
+}
+
 func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) {
 	return module.commonOutputFiles(tag)
 }
@@ -2071,6 +2083,11 @@
 	// do nothing
 }
 
+func (module *sdkLibraryXml) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	// sdkLibraryXml doesn't need to be checked separately because java_sdk_library is checked
+	return nil
+}
+
 // File path to the runtime implementation library
 func (module *sdkLibraryXml) implPath() string {
 	implName := proptools.String(module.properties.Lib_name)
@@ -2193,8 +2210,12 @@
 			properties.Jars = jars
 			properties.SdkVersion = sdk.sdkVersionForStubsLibrary(ctx.SdkModuleContext(), apiScope)
 			properties.StubsSrcJar = paths.stubsSrcJar.Path()
-			properties.CurrentApiFile = paths.currentApiFilePath.Path()
-			properties.RemovedApiFile = paths.removedApiFilePath.Path()
+			if paths.currentApiFilePath.Valid() {
+				properties.CurrentApiFile = paths.currentApiFilePath.Path()
+			}
+			if paths.removedApiFilePath.Valid() {
+				properties.RemovedApiFile = paths.removedApiFilePath.Path()
+			}
 			s.Scopes[apiScope] = properties
 		}
 	}
diff --git a/python/python.go b/python/python.go
index 8b912be..a6c9e2a 100644
--- a/python/python.go
+++ b/python/python.go
@@ -251,6 +251,18 @@
 	return android.OptionalPathForPath(p.installer.(*binaryDecorator).path)
 }
 
+func (p *Module) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		if outputFile := p.installSource; outputFile.Valid() {
+			return android.Paths{outputFile.Path()}, nil
+		}
+		return android.Paths{}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
 func (p *Module) isEmbeddedLauncherEnabled(actual_version string) bool {
 	switch actual_version {
 	case pyVersion2:
diff --git a/rust/Android.bp b/rust/Android.bp
index b06ea8e..d56de87 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -9,24 +9,26 @@
     ],
     srcs: [
         "androidmk.go",
-        "compiler.go",
-        "coverage.go",
         "binary.go",
         "builder.go",
+        "clippy.go",
+        "compiler.go",
+        "coverage.go",
         "library.go",
         "prebuilt.go",
         "proc_macro.go",
-	"project_json.go",
+        "project_json.go",
         "rust.go",
         "test.go",
         "testing.go",
     ],
     testSrcs: [
         "binary_test.go",
+        "clippy_test.go",
         "compiler_test.go",
         "coverage_test.go",
         "library_test.go",
-	"project_json_test.go",
+        "project_json_test.go",
         "rust_test.go",
         "test_test.go",
     ],
diff --git a/rust/OWNERS b/rust/OWNERS
index afd06e4..b5b795c 100644
--- a/rust/OWNERS
+++ b/rust/OWNERS
@@ -1,5 +1,5 @@
 # Additional owner/reviewers for rust rules, including parent directory owners.
-per-file * = chh@google.com, ivanlozano@google.com, jeffv@google.com, srhines@google.com
+per-file * = chh@google.com, ivanlozano@google.com, jeffv@google.com, mmaurer@google.com, srhines@google.com
 
 # Limited owners/reviewers of the allowed list.
-per-file allowed_list.go = chh@google.com, ivanlozano@google.com, jeffv@google.com, jgalenson@google.com, srhines@google.com
+per-file allowed_list.go = chh@google.com, ivanlozano@google.com, jeffv@google.com, jgalenson@google.com, mmaurer@google.com, srhines@google.com
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 69d0df5..aea899b 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -86,8 +86,11 @@
 func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
 	ctx.subAndroidMk(ret, binary.baseCompiler)
 
+	if binary.distFile.Valid() {
+		ret.DistFiles = android.MakeDefaultDistFiles(binary.distFile.Path())
+	}
+
 	ret.Class = "EXECUTABLES"
-	ret.DistFile = binary.distFile
 	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
 		fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", binary.unstrippedOutputFile.String())
 		if binary.coverageOutputZipFile.Valid() {
@@ -127,7 +130,10 @@
 		ret.Class = "SHARED_LIBRARIES"
 	}
 
-	ret.DistFile = library.distFile
+	if library.distFile.Valid() {
+		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
+	}
+
 	ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
 		if !library.rlib() {
 			fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", library.unstrippedOutputFile.String())
@@ -143,7 +149,9 @@
 	ctx.subAndroidMk(ret, procMacro.baseCompiler)
 
 	ret.Class = "PROC_MACRO_LIBRARIES"
-	ret.DistFile = procMacro.distFile
+	if procMacro.distFile.Valid() {
+		ret.DistFiles = android.MakeDefaultDistFiles(procMacro.distFile.Path())
+	}
 
 }
 
diff --git a/rust/builder.go b/rust/builder.go
index 7dbb59d..8bc11bb 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -39,6 +39,18 @@
 		},
 		"rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd")
 
+	_            = pctx.SourcePathVariable("clippyCmd", "${config.RustBin}/clippy-driver")
+	clippyDriver = pctx.AndroidStaticRule("clippy",
+		blueprint.RuleParams{
+			Command: "$clippyCmd " +
+				// Because clippy-driver uses rustc as backend, we need to have some output even during the linting.
+				// Use the metadata output as it has the smallest footprint.
+				"--emit metadata -o $out $in ${libFlags} " +
+				"$clippyFlags $rustcFlags",
+			CommandDeps: []string{"$clippyCmd"},
+		},
+		"rustcFlags", "libFlags", "clippyFlags")
+
 	zip = pctx.AndroidStaticRule("zip",
 		blueprint.RuleParams{
 			Command:        "cat $out.rsp | tr ' ' '\\n' | tr -d \\' | sort -u > ${out}.tmp && ${SoongZipCmd} -o ${out} -C $$OUT_DIR -l ${out}.tmp",
@@ -57,36 +69,36 @@
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
 }
 
-func TransformSrcToBinary(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, includeDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
 
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", includeDirs)
 }
 
-func TransformSrctoRlib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+func TransformSrctoRlib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, includeDirs []string) buildOutput {
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", includeDirs)
 }
 
-func TransformSrctoDylib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+func TransformSrctoDylib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, includeDirs []string) buildOutput {
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", includeDirs)
 }
 
-func TransformSrctoStatic(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, includeDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", includeDirs)
 }
 
-func TransformSrctoShared(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, includeDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", includeDirs)
 }
 
-func TransformSrctoProcMacro(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps,
+func TransformSrctoProcMacro(ctx ModuleContext, mainSrc android.Path, deps PathDeps,
 	flags Flags, outputFile android.WritablePath, includeDirs []string) buildOutput {
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", includeDirs)
 }
@@ -99,7 +111,7 @@
 	return paths
 }
 
-func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps PathDeps, flags Flags,
+func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath, crate_type string, includeDirs []string) buildOutput {
 
 	var inputs android.Paths
@@ -109,8 +121,8 @@
 	var implicitOutputs android.WritablePaths
 
 	output.outputFile = outputFile
-	crate_name := ctx.(ModuleContext).CrateName()
-	targetTriple := ctx.(ModuleContext).toolchain().RustTriple()
+	crate_name := ctx.RustModule().CrateName()
+	targetTriple := ctx.toolchain().RustTriple()
 
 	inputs = append(inputs, main)
 
@@ -125,10 +137,14 @@
 		rustcFlags = append(rustcFlags, "--target="+targetTriple)
 		linkFlags = append(linkFlags, "-target "+targetTriple)
 	}
-	// TODO once we have static libraries in the host prebuilt .bp, this
-	// should be unconditionally added.
-	if !(ctx.Host() && ctx.TargetPrimary()) {
-		// If we're not targeting the host primary arch, do not use an implicit sysroot
+	// TODO(b/159718669): Once we have defined static libraries in the host
+	// prebuilts Blueprint file, sysroot should be unconditionally sourced
+	// from /dev/null. Explicitly set sysroot to avoid clippy-driver to
+	// internally call rustc.
+	if ctx.Host() && ctx.TargetPrimary() {
+		rustcFlags = append(rustcFlags, "--sysroot=${config.RustPath}")
+	} else {
+		// If we're not targeting the host primary arch, do not use a sysroot.
 		rustcFlags = append(rustcFlags, "--sysroot=/dev/null")
 	}
 	// Collect linker flags
@@ -179,6 +195,25 @@
 		output.coverageFile = gcnoFile
 	}
 
+	if flags.Clippy {
+		clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:            clippyDriver,
+			Description:     "clippy " + main.Rel(),
+			Output:          clippyFile,
+			ImplicitOutputs: nil,
+			Inputs:          inputs,
+			Implicits:       implicits,
+			Args: map[string]string{
+				"rustcFlags":  strings.Join(rustcFlags, " "),
+				"libFlags":    strings.Join(libFlags, " "),
+				"clippyFlags": strings.Join(flags.ClippyFlags, " "),
+			},
+		})
+		// Declare the clippy build as an implicit dependency of the original crate.
+		implicits = append(implicits, clippyFile)
+	}
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:            rustc,
 		Description:     "rustc " + main.Rel(),
@@ -198,7 +233,7 @@
 	return output
 }
 
-func TransformCoverageFilesToZip(ctx android.ModuleContext,
+func TransformCoverageFilesToZip(ctx ModuleContext,
 	covFiles android.Paths, baseName string) android.OptionalPath {
 	if len(covFiles) > 0 {
 
diff --git a/rust/clippy.go b/rust/clippy.go
new file mode 100644
index 0000000..e1f567d
--- /dev/null
+++ b/rust/clippy.go
@@ -0,0 +1,42 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"android/soong/rust/config"
+)
+
+type ClippyProperties struct {
+	// whether to run clippy.
+	Clippy *bool
+}
+
+type clippy struct {
+	Properties ClippyProperties
+}
+
+func (c *clippy) props() []interface{} {
+	return []interface{}{&c.Properties}
+}
+
+func (c *clippy) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
+	if c.Properties.Clippy != nil && !*c.Properties.Clippy {
+		return flags, deps
+	}
+	enabled, lints := config.ClippyLintsForDir(ctx.ModuleDir())
+	flags.Clippy = enabled
+	flags.ClippyFlags = append(flags.ClippyFlags, lints)
+	return flags, deps
+}
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
new file mode 100644
index 0000000..af5cd17
--- /dev/null
+++ b/rust/clippy_test.go
@@ -0,0 +1,46 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"testing"
+)
+
+func TestClippy(t *testing.T) {
+	ctx := testRust(t, `
+		rust_library {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+		}
+		rust_library {
+			name: "libfoobar",
+			srcs: ["foo.rs"],
+			crate_name: "foobar",
+			clippy: false,
+		}`)
+
+	ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Output("libfoo.so")
+	fooClippy := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").MaybeRule("clippy")
+	if fooClippy.Rule.String() != "android/soong/rust.clippy" {
+		t.Errorf("Clippy output (default) for libfoo was not generated: %+v", fooClippy)
+	}
+
+	ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_shared").Output("libfoobar.so")
+	foobarClippy := ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_shared").MaybeRule("clippy")
+	if foobarClippy.Rule != nil {
+		t.Errorf("Clippy output for libfoobar is not empty")
+	}
+}
diff --git a/rust/compiler.go b/rust/compiler.go
index efc1ce4..050a259 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -253,7 +253,7 @@
 }
 
 func (compiler *baseCompiler) getStemWithoutSuffix(ctx BaseModuleContext) string {
-	stem := ctx.baseModuleName()
+	stem := ctx.ModuleName()
 	if String(compiler.Properties.Stem) != "" {
 		stem = String(compiler.Properties.Stem)
 	}
diff --git a/rust/config/Android.bp b/rust/config/Android.bp
index 5026da3..1d30f82 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -9,6 +9,7 @@
         "arm_device.go",
         "arm64_device.go",
         "global.go",
+        "clippy.go",
         "toolchain.go",
         "allowed_list.go",
         "x86_darwin_host.go",
diff --git a/rust/config/clippy.go b/rust/config/clippy.go
new file mode 100644
index 0000000..c199ff2
--- /dev/null
+++ b/rust/config/clippy.go
@@ -0,0 +1,80 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"strings"
+
+	"android/soong/android"
+)
+
+var (
+	defaultLints = []string{
+		"-D missing-docs",
+		"-D clippy::missing-safety-doc",
+	}
+	defaultVendorLints = []string{
+		"",
+	}
+)
+
+func init() {
+	// Default Rust lints. These apply to all Google-authored modules.
+	pctx.VariableFunc("ClippyDefaultLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("CLIPPY_DEFAULT_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultLints, " ")
+	})
+
+	// Rust lints that only applies to external code.
+	pctx.VariableFunc("ClippyVendorLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("CLIPPY_VENDOR_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultVendorLints, " ")
+	})
+}
+
+type PathBasedClippyConfig struct {
+	PathPrefix   string
+	Enabled      bool
+	ClippyConfig string
+}
+
+const clippyNone = ""
+const clippyDefault = "${config.ClippyDefaultLints}"
+const clippyVendor = "${config.ClippyVendorLints}"
+
+// This is a map of local path prefixes to a boolean indicating if the lint
+// rule should be generated and if so, the set of lints to use. The first entry
+// matching will be used. If no entry is matching, clippyDefault will be used.
+var DefaultLocalTidyChecks = []PathBasedClippyConfig{
+	{"external/", false, clippyNone},
+	{"hardware/", true, clippyVendor},
+	{"prebuilts/", false, clippyNone},
+	{"vendor/google", true, clippyDefault},
+	{"vendor/", true, clippyVendor},
+}
+
+// ClippyLintsForDir returns the Clippy lints to be used for a repository.
+func ClippyLintsForDir(dir string) (bool, string) {
+	for _, pathCheck := range DefaultLocalTidyChecks {
+		if strings.HasPrefix(dir, pathCheck.PathPrefix) {
+			return pathCheck.Enabled, pathCheck.ClippyConfig
+		}
+	}
+	return true, clippyDefault
+}
diff --git a/rust/config/global.go b/rust/config/global.go
index 63624c0..663514d 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.43.0"
+	RustDefaultVersion = "1.44.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/coverage.go b/rust/coverage.go
index 9be57dc..223ba4f 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -45,7 +45,7 @@
 
 func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
 
-	if !ctx.DeviceConfig().NativeCoverageEnabled() && !ctx.DeviceConfig().ClangCoverageEnabled() {
+	if !ctx.DeviceConfig().NativeCoverageEnabled() {
 		return flags, deps
 	}
 
@@ -67,6 +67,6 @@
 		// Host coverage not yet supported.
 	} else {
 		// Update useSdk and sdkVersion args if Rust modules become SDK aware.
-		cov.Properties = cc.SetCoverageProperties(ctx, cov.Properties, ctx.nativeCoverage(), false, "")
+		cov.Properties = cc.SetCoverageProperties(ctx, cov.Properties, ctx.RustModule().nativeCoverage(), false, "")
 	}
 }
diff --git a/rust/library.go b/rust/library.go
index 3c948ea..f070c34 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -329,7 +329,7 @@
 }
 
 func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.baseModuleName())
+	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 	flags = library.baseCompiler.compilerFlags(ctx, flags)
 	if library.shared() || library.static() {
 		library.includeDirs = append(library.includeDirs, android.PathsForModuleSrc(ctx, library.Properties.Include_dirs)...)
diff --git a/rust/rust.go b/rust/rust.go
index 7b82b1f..6c60348 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -49,8 +49,10 @@
 	GlobalLinkFlags []string // Flags that apply globally to linker
 	RustFlags       []string // Flags that apply to rust
 	LinkFlags       []string // Flags that apply to linker
+	ClippyFlags     []string // Flags that apply to clippy-driver, during the linting
 	Toolchain       config.Toolchain
 	Coverage        bool
+	Clippy          bool
 }
 
 type BaseProperties struct {
@@ -75,6 +77,7 @@
 
 	compiler         compiler
 	coverage         *coverage
+	clippy           *clippy
 	cachedToolchain  config.Toolchain
 	subAndroidMkOnce map[subAndroidMkProvider]bool
 	outputFile       android.OptionalPath
@@ -306,6 +309,7 @@
 		&PrebuiltProperties{},
 		&TestProperties{},
 		&cc.CoverageProperties{},
+		&ClippyProperties{},
 	)
 
 	android.InitDefaultsModule(module)
@@ -456,6 +460,9 @@
 	if mod.coverage != nil {
 		mod.AddProperties(mod.coverage.props()...)
 	}
+	if mod.clippy != nil {
+		mod.AddProperties(mod.clippy.props()...)
+	}
 
 	android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
 
@@ -487,6 +494,7 @@
 func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	module := newBaseModule(hod, multilib)
 	module.coverage = &coverage{}
+	module.clippy = &clippy{}
 	return module
 }
 
@@ -506,39 +514,50 @@
 }
 
 type ModuleContextIntf interface {
+	RustModule() *Module
 	toolchain() config.Toolchain
-	baseModuleName() string
-	CrateName() string
-	nativeCoverage() bool
 }
 
 type depsContext struct {
 	android.BottomUpMutatorContext
-	moduleContextImpl
 }
 
 type moduleContext struct {
 	android.ModuleContext
-	moduleContextImpl
 }
 
-func (ctx *moduleContextImpl) nativeCoverage() bool {
-	return ctx.mod.nativeCoverage()
+type baseModuleContext struct {
+	android.BaseModuleContext
+}
+
+func (ctx *moduleContext) RustModule() *Module {
+	return ctx.Module().(*Module)
+}
+
+func (ctx *moduleContext) toolchain() config.Toolchain {
+	return ctx.RustModule().toolchain(ctx)
+}
+
+func (ctx *depsContext) RustModule() *Module {
+	return ctx.Module().(*Module)
+}
+
+func (ctx *depsContext) toolchain() config.Toolchain {
+	return ctx.RustModule().toolchain(ctx)
+}
+
+func (ctx *baseModuleContext) RustModule() *Module {
+	return ctx.Module().(*Module)
+}
+
+func (ctx *baseModuleContext) toolchain() config.Toolchain {
+	return ctx.RustModule().toolchain(ctx)
 }
 
 func (mod *Module) nativeCoverage() bool {
 	return mod.compiler != nil && mod.compiler.nativeCoverage()
 }
 
-type moduleContextImpl struct {
-	mod *Module
-	ctx BaseModuleContext
-}
-
-func (ctx *moduleContextImpl) toolchain() config.Toolchain {
-	return ctx.mod.toolchain(ctx.ctx)
-}
-
 func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if mod.cachedToolchain == nil {
 		mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -552,11 +571,7 @@
 func (mod *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
 	ctx := &moduleContext{
 		ModuleContext: actx,
-		moduleContextImpl: moduleContextImpl{
-			mod: mod,
-		},
 	}
-	ctx.ctx = ctx
 
 	toolchain := mod.toolchain(ctx)
 
@@ -576,6 +591,9 @@
 	if mod.coverage != nil {
 		flags, deps = mod.coverage.flags(ctx, flags, deps)
 	}
+	if mod.clippy != nil {
+		flags, deps = mod.clippy.flags(ctx, flags, deps)
+	}
 
 	if mod.compiler != nil {
 		outputFile := mod.compiler.compile(ctx, flags, deps)
@@ -607,14 +625,6 @@
 
 }
 
-func (ctx *moduleContextImpl) baseModuleName() string {
-	return ctx.mod.ModuleBase.BaseModuleName()
-}
-
-func (ctx *moduleContextImpl) CrateName() string {
-	return ctx.mod.CrateName()
-}
-
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name       string
@@ -811,11 +821,7 @@
 func (mod *Module) DepsMutator(actx android.BottomUpMutatorContext) {
 	ctx := &depsContext{
 		BottomUpMutatorContext: actx,
-		moduleContextImpl: moduleContextImpl{
-			mod: mod,
-		},
 	}
-	ctx.ctx = ctx
 
 	deps := mod.deps(ctx)
 	commonDepVariations := []blueprint.Variation{}
@@ -862,19 +868,10 @@
 	}
 }
 
-type baseModuleContext struct {
-	android.BaseModuleContext
-	moduleContextImpl
-}
-
 func (mod *Module) beginMutator(actx android.BottomUpMutatorContext) {
 	ctx := &baseModuleContext{
 		BaseModuleContext: actx,
-		moduleContextImpl: moduleContextImpl{
-			mod: mod,
-		},
 	}
-	ctx.ctx = ctx
 
 	mod.begin(ctx)
 }
@@ -889,6 +886,18 @@
 	return name
 }
 
+var _ android.HostToolProvider = (*Module)(nil)
+
+func (mod *Module) HostToolPath() android.OptionalPath {
+	if !mod.Host() {
+		return android.OptionalPath{}
+	}
+	if _, ok := mod.compiler.(*binaryDecorator); ok {
+		return mod.outputFile
+	}
+	return android.OptionalPath{}
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/rust/rust_test.go b/rust/rust_test.go
index fe21e3a..280c22a 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -88,11 +88,11 @@
 	config := testConfig(bp)
 
 	if coverage {
+		config.TestProductVariables.GcovCoverage = proptools.BoolPtr(true)
 		config.TestProductVariables.Native_coverage = proptools.BoolPtr(true)
 		config.TestProductVariables.NativeCoveragePaths = []string{"*"}
 	}
 
-	t.Helper()
 	ctx := CreateTestContext()
 	ctx.Register(config)
 
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 1f55030..d962a41 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -106,7 +106,7 @@
         py3: {
             enabled: false,
         },
-    }
+    },
 }
 
 python_binary_host {
@@ -152,5 +152,17 @@
 python_binary_host {
     name: "lint-project-xml",
     main: "lint-project-xml.py",
-    srcs: ["lint-project-xml.py"],
+    srcs: [
+        "lint-project-xml.py",
+        "ninja_rsp.py",
+    ],
+}
+
+python_binary_host {
+    name: "gen-kotlin-build-file.py",
+    main: "gen-kotlin-build-file.py",
+    srcs: [
+        "gen-kotlin-build-file.py",
+        "ninja_rsp.py",
+    ],
 }
diff --git a/scripts/OWNERS b/scripts/OWNERS
index dd0966f..0274554 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,3 +1,4 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
 per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
+per-file construct_context.sh = ngeoffray@google.com,calin@google.com,mathieuc@google.com,skvadrik@google.com
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index 9b68a82..1bc78e6 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -23,6 +23,15 @@
   runtime-module-host-exports
   i18n-module-test-exports
   i18n-module-sdk
+  platform-mainline-sdk
+  platform-mainline-host-exports
+)
+
+# List of libraries installed on the platform that are needed for ART chroot
+# testing.
+PLATFORM_LIBRARIES=(
+  liblog
+  libartpalette-system
 )
 
 # We want to create apex modules for all supported architectures.
@@ -49,7 +58,8 @@
 for product in "${PRODUCTS[@]}"; do
   echo_and_run build/soong/soong_ui.bash --make-mode $@ \
     TARGET_PRODUCT=${product} \
-    ${MAINLINE_MODULES[@]}
+    ${MAINLINE_MODULES[@]} \
+    ${PLATFORM_LIBRARIES[@]}
 
   PRODUCT_OUT=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var PRODUCT_OUT)
   TARGET_ARCH=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var TARGET_ARCH)
@@ -58,9 +68,11 @@
   for module in "${MAINLINE_MODULES[@]}"; do
     echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/apex/${module}.apex ${DIST_DIR}/${TARGET_ARCH}/
   done
+  for library in "${PLATFORM_LIBRARIES[@]}"; do
+    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/lib/${library}.so ${DIST_DIR}/${TARGET_ARCH}/
+  done
 done
 
-
 # Create multi-archs SDKs in a different out directory. The multi-arch script
 # uses Soong in --skip-make mode which cannot use the same directory as normal
 # mode with make.
diff --git a/scripts/construct_context.sh b/scripts/construct_context.sh
new file mode 100755
index 0000000..d620d08
--- /dev/null
+++ b/scripts/construct_context.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+# target_sdk_version: parsed from manifest
+#
+# outputs
+# class_loader_context_arg: final class loader conext arg
+# stored_class_loader_context_arg: final stored class loader context arg
+
+if [ -z "${target_sdk_version}" ]; then
+    echo "ERROR: target_sdk_version not set"
+    exit 2
+fi
+
+# The hidl.manager shared library has a dependency on hidl.base. We'll manually
+# add that information to the class loader context if we see those libraries.
+hidl_manager="android.hidl.manager-V1.0-java"
+hidl_base="android.hidl.base-V1.0-java"
+
+function add_to_contexts {
+  for i in $1; do
+    if [[ -z "${class_loader_context}" ]]; then
+      export class_loader_context="PCL[$i]"
+    else
+      export class_loader_context+="#PCL[$i]"
+    fi
+    if [[ $i == *"$hidl_manager"* ]]; then
+      export class_loader_context+="{PCL[${i/$hidl_manager/$hidl_base}]}"
+    fi
+  done
+
+  for i in $2; do
+    if [[ -z "${stored_class_loader_context}" ]]; then
+      export stored_class_loader_context="PCL[$i]"
+    else
+      export stored_class_loader_context+="#PCL[$i]"
+    fi
+    if [[ $i == *"$hidl_manager"* ]]; then
+      export stored_class_loader_context+="{PCL[${i/$hidl_manager/$hidl_base}]}"
+    fi
+  done
+}
+
+# The order below must match what the package manager also computes for
+# class loader context.
+
+if [[ "${target_sdk_version}" -lt "28" ]]; then
+  add_to_contexts "${conditional_host_libs_28}" "${conditional_target_libs_28}"
+fi
+
+if [[ "${target_sdk_version}" -lt "29" ]]; then
+  add_to_contexts "${conditional_host_libs_29}" "${conditional_target_libs_29}"
+fi
+
+if [[ "${target_sdk_version}" -lt "30" ]]; then
+  add_to_contexts "${conditional_host_libs_30}" "${conditional_target_libs_30}"
+fi
+
+add_to_contexts "${dex_preopt_host_libraries}" "${dex_preopt_target_libraries}"
+
+# Generate the actual context string.
+export class_loader_context_arg="--class-loader-context=PCL[]{${class_loader_context}}"
+export stored_class_loader_context_arg="--stored-class-loader-context=PCL[]{${stored_class_loader_context}}"
diff --git a/scripts/gen-kotlin-build-file.py b/scripts/gen-kotlin-build-file.py
new file mode 100644
index 0000000..83b4cd8
--- /dev/null
+++ b/scripts/gen-kotlin-build-file.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates kotlinc module xml file to drive kotlinc
+
+import argparse
+import os
+
+from ninja_rsp import NinjaRspFileReader
+
+def parse_args():
+  """Parse commandline arguments."""
+
+  def convert_arg_line_to_args(arg_line):
+    for arg in arg_line.split():
+      if arg.startswith('#'):
+        return
+      if not arg.strip():
+        continue
+      yield arg
+
+  parser = argparse.ArgumentParser(fromfile_prefix_chars='@')
+  parser.convert_arg_line_to_args = convert_arg_line_to_args
+  parser.add_argument('--out', dest='out',
+                      help='file to which the module.xml contents will be written.')
+  parser.add_argument('--classpath', dest='classpath', action='append', default=[],
+                      help='classpath to pass to kotlinc.')
+  parser.add_argument('--name', dest='name',
+                      help='name of the module.')
+  parser.add_argument('--out_dir', dest='out_dir',
+                      help='directory to which kotlinc will write output files.')
+  parser.add_argument('--srcs', dest='srcs', action='append', default=[],
+                      help='file containing whitespace separated list of source files.')
+  parser.add_argument('--common_srcs', dest='common_srcs', action='append', default=[],
+                      help='file containing whitespace separated list of common multiplatform source files.')
+
+  return parser.parse_args()
+
+def main():
+  """Program entry point."""
+  args = parse_args()
+
+  if not args.out:
+    raise RuntimeError('--out argument is required')
+
+  if not args.name:
+    raise RuntimeError('--name argument is required')
+
+  with open(args.out, 'w') as f:
+    # Print preamble
+    f.write('<modules>\n')
+    f.write('  <module name="%s" type="java-production" outputDir="%s">\n' % (args.name, args.out_dir or ''))
+
+    # Print classpath entries
+    for c in args.classpath:
+      for entry in c.split(':'):
+        path = os.path.abspath(entry)
+        f.write('    <classpath path="%s"/>\n' % path)
+
+    # For each rsp file, print source entries
+    for rsp_file in args.srcs:
+      for src in NinjaRspFileReader(rsp_file):
+        path = os.path.abspath(src)
+        if src.endswith('.java'):
+          f.write('    <javaSourceRoots path="%s"/>\n' % path)
+        elif src.endswith('.kt'):
+          f.write('    <sources path="%s"/>\n' % path)
+        else:
+          raise RuntimeError('unknown source file type %s' % file)
+
+    for rsp_file in args.common_srcs:
+      for src in NinjaRspFileReader(rsp_file):
+        path = os.path.abspath(src)
+        f.write('    <sources path="%s"/>\n' % path)
+        f.write('    <commonSources path="%s"/>\n' % path)
+
+    f.write('  </module>\n')
+    f.write('</modules>\n')
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/gen-kotlin-build-file.sh b/scripts/gen-kotlin-build-file.sh
deleted file mode 100755
index 177ca1b..0000000
--- a/scripts/gen-kotlin-build-file.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/bash -e
-
-# Copyright 2018 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Generates kotlinc module xml file to standard output based on rsp files
-
-if [[ -z "$1" ]]; then
-  echo "usage: $0 <classpath> <name> <outDir> <rspFiles>..." >&2
-  exit 1
-fi
-
-# Classpath variable has a tendency to be prefixed by "-classpath", remove it.
-if [[ $1 == "-classpath" ]]; then
-  shift
-fi;
-
-classpath=$1
-name=$2
-out_dir=$3
-shift 3
-
-# Path in the build file may be relative to the build file, we need to make them
-# absolute
-prefix="$(pwd)"
-
-get_abs_path () {
-  local file="$1"
-  if [[ "${file:0:1}" == '/' ]] ; then
-    echo "${file}"
-  else
-    echo "${prefix}/${file}"
-  fi
-}
-
-# Print preamble
-echo "<modules><module name=\"${name}\" type=\"java-production\" outputDir=\"${out_dir}\">"
-
-# Print classpath entries
-for file in $(echo "$classpath" | tr ":" "\n"); do
-  path="$(get_abs_path "$file")"
-  echo "  <classpath path=\"${path}\"/>"
-done
-
-# For each rsp file, print source entries
-while (( "$#" )); do
-  for file in $(cat "$1"); do
-    path="$(get_abs_path "$file")"
-    if [[ $file == *.java ]]; then
-      echo "  <javaSourceRoots path=\"${path}\"/>"
-    elif [[ $file == *.kt ]]; then
-      echo "  <sources path=\"${path}\"/>"
-    else
-      echo "Unknown source file type ${file}"
-      exit 1
-    fi
-  done
-
-  shift
-done
-
-echo "</module></modules>"
diff --git a/scripts/lint-project-xml.py b/scripts/lint-project-xml.py
index 7ab4f01..f1ef85d 100755
--- a/scripts/lint-project-xml.py
+++ b/scripts/lint-project-xml.py
@@ -19,6 +19,8 @@
 
 import argparse
 
+from ninja_rsp import NinjaRspFileReader
+
 
 def check_action(check_type):
   """
@@ -77,6 +79,8 @@
                       help='mark the module as a test.')
   parser.add_argument('--cache_dir', dest='cache_dir',
                       help='directory to use for cached file.')
+  parser.add_argument('--root_dir', dest='root_dir',
+                      help='directory to use for root dir.')
   group = parser.add_argument_group('check arguments', 'later arguments override earlier ones.')
   group.add_argument('--fatal_check', dest='checks', action=check_action('fatal'), default=[],
                      help='treat a lint issue as a fatal error.')
@@ -89,79 +93,13 @@
   return parser.parse_args()
 
 
-class NinjaRspFileReader:
-  """
-  Reads entries from a Ninja rsp file.  Ninja escapes any entries in the file that contain a
-  non-standard character by surrounding the whole entry with single quotes, and then replacing
-  any single quotes in the entry with the escape sequence '\''.
-  """
-
-  def __init__(self, filename):
-    self.f = open(filename, 'r')
-    self.r = self.character_reader(self.f)
-
-  def __iter__(self):
-    return self
-
-  def character_reader(self, f):
-    """Turns a file into a generator that returns one character at a time."""
-    while True:
-      c = f.read(1)
-      if c:
-        yield c
-      else:
-        return
-
-  def __next__(self):
-    entry = self.read_entry()
-    if entry:
-      return entry
-    else:
-      raise StopIteration
-
-  def read_entry(self):
-    c = next(self.r, "")
-    if not c:
-      return ""
-    elif c == "'":
-      return self.read_quoted_entry()
-    else:
-      entry = c
-      for c in self.r:
-        if c == " " or c == "\n":
-          break
-        entry += c
-      return entry
-
-  def read_quoted_entry(self):
-    entry = ""
-    for c in self.r:
-      if c == "'":
-        # Either the end of the quoted entry, or the beginning of an escape sequence, read the next
-        # character to find out.
-        c = next(self.r)
-        if not c or c == " " or c == "\n":
-          # End of the item
-          return entry
-        elif c == "\\":
-          # Escape sequence, expect a '
-          c = next(self.r)
-          if c != "'":
-            # Malformed escape sequence
-            raise "malformed escape sequence %s'\\%s" % (entry, c)
-          entry += "'"
-        else:
-          raise "malformed escape sequence %s'%s" % (entry, c)
-      else:
-        entry += c
-    raise "unterminated quoted entry %s" % entry
-
-
 def write_project_xml(f, args):
   test_attr = "test='true' " if args.test else ""
 
   f.write("<?xml version='1.0' encoding='utf-8'?>\n")
   f.write("<project>\n")
+  if args.root_dir:
+    f.write("  <root dir='%s' />\n" % args.root_dir)
   f.write("  <module name='%s' android='true' %sdesugar='full' >\n" % (args.name, "library='true' " if args.library else ""))
   if args.manifest:
     f.write("    <manifest file='%s' %s/>\n" % (args.manifest, test_attr))
diff --git a/scripts/ninja_rsp.py b/scripts/ninja_rsp.py
new file mode 100644
index 0000000..004ce47
--- /dev/null
+++ b/scripts/ninja_rsp.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""This file reads entries from a Ninja rsp file."""
+
+class NinjaRspFileReader:
+  """
+  Reads entries from a Ninja rsp file.  Ninja escapes any entries in the file that contain a
+  non-standard character by surrounding the whole entry with single quotes, and then replacing
+  any single quotes in the entry with the escape sequence '\''.
+  """
+
+  def __init__(self, filename):
+    self.f = open(filename, 'r')
+    self.r = self.character_reader(self.f)
+
+  def __iter__(self):
+    return self
+
+  def character_reader(self, f):
+    """Turns a file into a generator that returns one character at a time."""
+    while True:
+      c = f.read(1)
+      if c:
+        yield c
+      else:
+        return
+
+  def __next__(self):
+    entry = self.read_entry()
+    if entry:
+      return entry
+    else:
+      raise StopIteration
+
+  def read_entry(self):
+    c = next(self.r, "")
+    if not c:
+      return ""
+    elif c == "'":
+      return self.read_quoted_entry()
+    else:
+      entry = c
+      for c in self.r:
+        if c == " " or c == "\n":
+          break
+        entry += c
+      return entry
+
+  def read_quoted_entry(self):
+    entry = ""
+    for c in self.r:
+      if c == "'":
+        # Either the end of the quoted entry, or the beginning of an escape sequence, read the next
+        # character to find out.
+        c = next(self.r)
+        if not c or c == " " or c == "\n":
+          # End of the item
+          return entry
+        elif c == "\\":
+          # Escape sequence, expect a '
+          c = next(self.r)
+          if c != "'":
+            # Malformed escape sequence
+            raise "malformed escape sequence %s'\\%s" % (entry, c)
+          entry += "'"
+        else:
+          raise "malformed escape sequence %s'%s" % (entry, c)
+      else:
+        entry += c
+    raise "unterminated quoted entry %s" % entry
diff --git a/scripts/rustfmt.toml b/scripts/rustfmt.toml
new file mode 100644
index 0000000..617d425
--- /dev/null
+++ b/scripts/rustfmt.toml
@@ -0,0 +1,5 @@
+# Android Format Style
+
+edition = "2018"
+use_small_heuristics = "Max"
+newline_style = "Unix"
diff --git a/scripts/transitive-deps.sh b/scripts/transitive-deps.sh
index c6f8b76..ba36ba4 100755
--- a/scripts/transitive-deps.sh
+++ b/scripts/transitive-deps.sh
@@ -56,7 +56,7 @@
                     temporary / intermediate files.
   -sep=<delim>      Use 'delim' as output field separator between notice
                     checksum and notice filename in notice output.
-                    e.g. sep='\t'
+                    e.g. sep='\\t'
                     (Default space)
   -csv              Shorthand for -sep=','
   -directories=<f>  Output directory names of dependencies to 'f'.
@@ -280,7 +280,7 @@
           notices=)
             notices_out=$(expr "${flag}" : '^.*=\(.*\)$');;
           *)
-            die "Unknown flag ${1}";;
+            die "${usage}\n\nUnknown flag ${1}";;
         esac
       ;;
       *)
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 77a4e94..7496b20 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -23,6 +23,7 @@
 
 	fs := map[string][]byte{
 		"Test.java":              nil,
+		"resource.test":          nil,
 		"aidl/foo/bar/Test.aidl": nil,
 
 		// For java_sdk_library
@@ -348,6 +349,7 @@
 		java_library {
 			name: "myjavalib",
 			srcs: ["Test.java"],
+			java_resources: ["resource.txt"],
 			aidl: {
 				export_include_dirs: ["aidl"],
 			},
@@ -381,7 +383,7 @@
 
 `),
 		checkAllCopyRules(`
-.intermediates/myjavalib/android_common/javac/myjavalib.jar -> java/myjavalib.jar
+.intermediates/myjavalib/android_common/withres/myjavalib.jar -> java/myjavalib.jar
 aidl/foo/bar/Test.aidl -> aidl/aidl/foo/bar/Test.aidl
 `),
 	)
diff --git a/sdk/sdk.go b/sdk/sdk.go
index cb5a605..b9b8199 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -291,7 +291,7 @@
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "FAKE",
 		OutputFile: s.snapshotFile,
-		DistFile:   s.snapshotFile,
+		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path()),
 		Include:    "$(BUILD_PHONY_PACKAGE)",
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string, entries *android.AndroidMkEntries) {
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 7bb267d..f28b2b6 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -107,6 +107,8 @@
 
 	testProperties TestProperties
 
+	installDir android.InstallPath
+
 	data       android.Paths
 	testConfig android.Path
 }
@@ -176,13 +178,13 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
+				entries.SetString("LOCAL_MODULE_RELATIVE_PATH", proptools.String(s.properties.Sub_dir))
 			},
 		},
 	}}
 }
 
 func (s *ShBinary) customAndroidMkEntries(entries *android.AndroidMkEntries) {
-	entries.SetString("LOCAL_MODULE_RELATIVE_PATH", proptools.String(s.properties.Sub_dir))
 	entries.SetString("LOCAL_MODULE_SUFFIX", "")
 	entries.SetString("LOCAL_MODULE_STEM", s.outputFilePath.Rel())
 	if len(s.properties.Symlinks) > 0 {
@@ -201,8 +203,14 @@
 	} else if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
 		testDir = filepath.Join(testDir, ctx.Arch().ArchType.String())
 	}
-	installDir := android.PathForModuleInstall(ctx, testDir, proptools.String(s.properties.Sub_dir))
-	s.installedFile = ctx.InstallExecutable(installDir, s.outputFilePath.Base(), s.outputFilePath)
+	if s.SubDir() != "" {
+		// Don't add the module name to the installation path if sub_dir is specified for backward
+		// compatibility.
+		s.installDir = android.PathForModuleInstall(ctx, testDir, s.SubDir())
+	} else {
+		s.installDir = android.PathForModuleInstall(ctx, testDir, s.Name())
+	}
+	s.installedFile = ctx.InstallExecutable(s.installDir, s.outputFilePath.Base(), s.outputFilePath)
 
 	s.data = android.PathsForModuleSrc(ctx, s.testProperties.Data)
 
@@ -229,7 +237,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
-
+				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
 				entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 6c0d96a..3bfe611 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -55,7 +55,27 @@
 	return ctx, config
 }
 
-func TestShTestTestData(t *testing.T) {
+func TestShTestSubDir(t *testing.T) {
+	ctx, config := testShBinary(t, `
+		sh_test {
+			name: "foo",
+			src: "test.sh",
+			sub_dir: "foo_test"
+		}
+	`)
+
+	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+
+	entries := android.AndroidMkEntriesForTest(t, config, "", mod)[0]
+
+	expectedPath := "/tmp/target/product/test_device/data/nativetest64/foo_test"
+	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
+	if expectedPath != actualPath {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH expected: %q, actual: %q", expectedPath, actualPath)
+	}
+}
+
+func TestShTest(t *testing.T) {
 	ctx, config := testShBinary(t, `
 		sh_test {
 			name: "foo",
@@ -71,10 +91,17 @@
 	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
 	entries := android.AndroidMkEntriesForTest(t, config, "", mod)[0]
-	expected := []string{":testdata/data1", ":testdata/sub/data2"}
-	actual := entries.EntryMap["LOCAL_TEST_DATA"]
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Unexpected test data expected: %q, actual: %q", expected, actual)
+
+	expectedPath := "/tmp/target/product/test_device/data/nativetest64/foo"
+	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
+	if expectedPath != actualPath {
+		t.Errorf("Unexpected LOCAL_MODULE_PATH expected: %q, actual: %q", expectedPath, actualPath)
+	}
+
+	expectedData := []string{":testdata/data1", ":testdata/sub/data2"}
+	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
+	if !reflect.DeepEqual(expectedData, actualData) {
+		t.Errorf("Unexpected test data expected: %q, actual: %q", expectedData, actualData)
 	}
 }
 
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 9f27647..768c8e5 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -310,6 +310,10 @@
 		}}
 }
 
+func (m *syspropLibrary) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
+	return fmt.Errorf("sysprop_library is not supposed to be part of apex modules")
+}
+
 // sysprop_library creates schematized APIs from sysprop description files (.sysprop).
 // Both Java and C++ modules can link against sysprop_library, and API stability check
 // against latest APIs (see build/soong/scripts/freeze-sysprop-api-files.sh)
diff --git a/ui/build/build.go b/ui/build/build.go
index 89c3fad..396f54c 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -243,6 +243,8 @@
 	// Write combined ninja file
 	createCombinedBuildNinjaFile(ctx, config)
 
+	distGzipFile(ctx, config, config.CombinedNinjaFile())
+
 	if what&RunBuildTests != 0 {
 		testForDanglingRules(ctx, config)
 	}
@@ -256,3 +258,47 @@
 		runNinja(ctx, config)
 	}
 }
+
+// distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures
+// are printed but non-fatal.
+func distGzipFile(ctx Context, config Config, src string, subDirs ...string) {
+	if !config.Dist() {
+		return
+	}
+
+	subDir := filepath.Join(subDirs...)
+	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
+
+	err := os.MkdirAll(destDir, 0777)
+	if err != nil {
+		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
+
+	}
+
+	err = gzipFileToDir(src, destDir)
+	if err != nil {
+		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
+	}
+}
+
+// distFile writes a copy of src to the distDir if dist is enabled.  Failures are printed but
+// non-fatal.
+func distFile(ctx Context, config Config, src string, subDirs ...string) {
+	if !config.Dist() {
+		return
+	}
+
+	subDir := filepath.Join(subDirs...)
+	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
+
+	err := os.MkdirAll(destDir, 0777)
+	if err != nil {
+		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
+
+	}
+
+	_, err = copyFile(src, filepath.Join(destDir, filepath.Base(src)))
+	if err != nil {
+		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
+	}
+}
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 0f34b5e..6786337 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -86,7 +86,7 @@
 	os.MkdirAll(dumpDir, 0777)
 
 	androidMks := f.FindFirstNamedAt(".", "Android.mk")
-	err := dumpListToFile(androidMks, filepath.Join(dumpDir, "Android.mk.list"))
+	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export module list: %v", err)
 	}
@@ -94,25 +94,25 @@
 	androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
 	androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
-	err = dumpListToFile(androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
+	err = dumpListToFile(ctx, config, androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export product list: %v", err)
 	}
 
 	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
-	err = dumpListToFile(cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
+	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export module list: %v", err)
 	}
 
 	owners := f.FindNamedAt(".", "OWNERS")
-	err = dumpListToFile(owners, filepath.Join(dumpDir, "OWNERS.list"))
+	err = dumpListToFile(ctx, config, owners, filepath.Join(dumpDir, "OWNERS.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find OWNERS: %v", err)
 	}
 
 	testMappings := f.FindNamedAt(".", "TEST_MAPPING")
-	err = dumpListToFile(testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
+	err = dumpListToFile(ctx, config, testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
 	}
@@ -122,18 +122,24 @@
 	if len(androidBps) == 0 {
 		ctx.Fatalf("No Android.bp found")
 	}
-	err = dumpListToFile(androidBps, filepath.Join(dumpDir, "Android.bp.list"))
+	err = dumpListToFile(ctx, config, androidBps, filepath.Join(dumpDir, "Android.bp.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find modules: %v", err)
 	}
 }
 
-func dumpListToFile(list []string, filePath string) (err error) {
+func dumpListToFile(ctx Context, config Config, list []string, filePath string) (err error) {
 	desiredText := strings.Join(list, "\n")
 	desiredBytes := []byte(desiredText)
 	actualBytes, readErr := ioutil.ReadFile(filePath)
 	if readErr != nil || !bytes.Equal(desiredBytes, actualBytes) {
 		err = ioutil.WriteFile(filePath, desiredBytes, 0777)
+		if err != nil {
+			return err
+		}
 	}
-	return err
+
+	distFile(ctx, config, filePath, "module_paths")
+
+	return nil
 }
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 2eb7850..1cd5fea 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -156,6 +156,8 @@
 
 	runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
 
+	distGzipFile(ctx, config, config.KatiBuildNinjaFile())
+
 	cleanCopyHeaders(ctx, config)
 	cleanOldInstalledFiles(ctx, config)
 }
@@ -251,6 +253,8 @@
 			env.Set("DIST_DIR", config.DistDir())
 		}
 	})
+
+	distGzipFile(ctx, config, config.KatiPackageNinjaFile())
 }
 
 func runKatiCleanSpec(ctx Context, config Config) {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 6a12add..749acb3 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -139,6 +139,13 @@
 	soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
 	logSoongBuildMetrics(ctx, soongBuildMetrics)
 
+	distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
+
+	if !config.SkipMake() {
+		distGzipFile(ctx, config, config.SoongAndroidMk(), "soong")
+		distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong")
+	}
+
 	if ctx.Metrics != nil {
 		ctx.Metrics.SetSoongBuildMetrics(soongBuildMetrics)
 	}
diff --git a/ui/build/util.go b/ui/build/util.go
index d44cd6d..d4c3431 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -15,6 +15,8 @@
 package build
 
 import (
+	"compress/gzip"
+	"fmt"
 	"io"
 	"os"
 	"path/filepath"
@@ -142,3 +144,29 @@
 
 	return io.Copy(destination, source)
 }
+
+// gzipFileToDir writes a compressed copy of src to destDir with the suffix ".gz".
+func gzipFileToDir(src, destDir string) error {
+	in, err := os.Open(src)
+	if err != nil {
+		return fmt.Errorf("failed to open %s: %s", src, err.Error())
+	}
+	defer in.Close()
+
+	dest := filepath.Join(destDir, filepath.Base(src)+".gz")
+
+	out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0666)
+	if err != nil {
+		return fmt.Errorf("failed to open %s: %s", dest, err.Error())
+	}
+	defer out.Close()
+	gz := gzip.NewWriter(out)
+	defer gz.Close()
+
+	_, err = io.Copy(gz, in)
+	if err != nil {
+		return fmt.Errorf("failed to gzip %s: %s", dest, err.Error())
+	}
+
+	return nil
+}
diff --git a/zip/Android.bp b/zip/Android.bp
index 259e010..5081e91 100644
--- a/zip/Android.bp
+++ b/zip/Android.bp
@@ -27,7 +27,6 @@
         "rate_limit.go",
     ],
     testSrcs: [
-      "zip_test.go",
+        "zip_test.go",
     ],
 }
-