Merge changes from topic "uses-libs-5"

* changes:
  Rewrite construct_context.sh in Python.
  Refactor class loader context generation.
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 3487b28..045cb59 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -58,7 +58,7 @@
 
 	Extra []AndroidMkExtraFunc
 
-	preamble bytes.Buffer
+	Entries AndroidMkEntries
 }
 
 type AndroidMkExtraFunc func(w io.Writer, outputFile Path)
@@ -483,7 +483,7 @@
 
 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,
 		DistFiles:       data.DistFiles,
@@ -494,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,
@@ -559,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/config.go b/android/config.go
index 16f3d73..6f73a12 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1052,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
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 6b246db..3994084 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -28,6 +28,7 @@
 // module based on it.
 
 import (
+	"sort"
 	"sync"
 
 	"github.com/google/blueprint"
@@ -161,6 +162,11 @@
 
 // Should NOT be used in the same mutator as addOverride.
 func (b *OverridableModuleBase) getOverrides() []OverrideModule {
+	b.overridesLock.Lock()
+	sort.Slice(b.overrides, func(i, j int) bool {
+		return b.overrides[i].Name() < b.overrides[j].Name()
+	})
+	b.overridesLock.Unlock()
 	return b.overrides
 }
 
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 726746b..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,
 }
 `,
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 a9be1a8..d0c1a09 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1329,6 +1329,7 @@
 
 	targets := ctx.MultiTargets()
 	config := ctx.DeviceConfig()
+	imageVariation := a.getImageVariation(ctx)
 
 	a.combineProperties(ctx)
 
@@ -1348,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 {
@@ -1367,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 {
@@ -1382,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)
 			}
 		}
 	}
@@ -1501,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) {
@@ -1541,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")
@@ -1785,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.
@@ -2099,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
@@ -2107,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
@@ -2203,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 {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index f1638c3..befb814 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2105,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",
@@ -2161,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 {
diff --git a/apex/builder.go b/apex/builder.go
index ede11d0..5fb9a5f 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -255,10 +255,15 @@
 
 	output := android.PathForModuleOut(ctx, "file_contexts")
 	rule := android.NewRuleBuilder()
+	// remove old file
 	rule.Command().Text("rm").FlagWithOutput("-f ", output)
+	// copy file_contexts
 	rule.Command().Text("cat").Input(fileContexts).Text(">>").Output(output)
+	// new line
 	rule.Command().Text("echo").Text(">>").Output(output)
+	// force-label /apex_manifest.pb and / as system_file so that apexd can read them
 	rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
+	rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
 	rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
 
 	a.fileContexts = output.OutputPath
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/cc.go b/cc/cc.go
index c3b0d88..0a23967 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -873,7 +873,7 @@
 }
 
 func (c *Module) IsNdk() bool {
-	return inList(c.Name(), ndkMigratedLibs)
+	return inList(c.Name(), ndkKnownLibs)
 }
 
 func (c *Module) isLlndk(config android.Config) bool {
@@ -1759,8 +1759,6 @@
 	variantNdkLibs := []string{}
 	variantLateNdkLibs := []string{}
 	if ctx.Os() == android.Android {
-		version := ctx.sdkVersion()
-
 		// rewriteLibs takes a list of names of shared libraries and scans it for three types
 		// of names:
 		//
@@ -1802,12 +1800,8 @@
 			for _, entry := range list {
 				// strip #version suffix out
 				name, _ := StubsLibNameAndVersion(entry)
-				if ctx.useSdk() && inList(name, ndkPrebuiltSharedLibraries) {
-					if !inList(name, ndkMigratedLibs) {
-						nonvariantLibs = append(nonvariantLibs, name+".ndk."+version)
-					} else {
-						variantLibs = append(variantLibs, name+ndkLibrarySuffix)
-					}
+				if ctx.useSdk() && inList(name, ndkKnownLibs) {
+					variantLibs = append(variantLibs, name+ndkLibrarySuffix)
 				} else if ctx.useVndk() {
 					nonvariantLibs = append(nonvariantLibs, rewriteVendorLibs(entry))
 				} else if (ctx.Platform() || ctx.ProductSpecific()) && inList(name, *vendorPublicLibraries) {
@@ -2988,6 +2982,7 @@
 		&BinaryLinkerProperties{},
 		&TestProperties{},
 		&TestBinaryProperties{},
+		&BenchmarkProperties{},
 		&FuzzProperties{},
 		&StlProperties{},
 		&SanitizeProperties{},
diff --git a/cc/makevars.go b/cc/makevars.go
index 0f9f4c1..968eeb5 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -97,7 +97,6 @@
 	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ClangExternalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideClangGlobalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE", "")
-	ctx.Strict("NDK_PREBUILT_SHARED_LIBRARIES", strings.Join(ndkPrebuiltSharedLibs, " "))
 
 	ctx.Strict("BOARD_VNDK_VERSION", ctx.DeviceConfig().VndkVersion())
 
@@ -174,8 +173,8 @@
 	ctx.StrictRaw("SRC_HEADERS", strings.Join(includes, " "))
 	ctx.StrictRaw("SRC_SYSTEM_HEADERS", strings.Join(systemIncludes, " "))
 
-	sort.Strings(ndkMigratedLibs)
-	ctx.Strict("NDK_MIGRATED_LIBS", strings.Join(ndkMigratedLibs, " "))
+	sort.Strings(ndkKnownLibs)
+	ctx.Strict("NDK_KNOWN_LIBS", strings.Join(ndkKnownLibs, " "))
 
 	hostTargets := ctx.Config().Targets[android.BuildOs]
 	makeVarsToolchain(ctx, "", hostTargets[0])
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 5ef9a78..6299b00 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -47,37 +47,10 @@
 
 	ndkLibrarySuffix = ".ndk"
 
-	ndkPrebuiltSharedLibs = []string{
-		"aaudio",
-		"amidi",
-		"android",
-		"binder_ndk",
-		"c",
-		"camera2ndk",
-		"dl",
-		"EGL",
-		"GLESv1_CM",
-		"GLESv2",
-		"GLESv3",
-		"jnigraphics",
-		"log",
-		"mediandk",
-		"nativewindow",
-		"m",
-		"neuralnetworks",
-		"OpenMAXAL",
-		"OpenSLES",
-		"stdc++",
-		"sync",
-		"vulkan",
-		"z",
-	}
-	ndkPrebuiltSharedLibraries = addPrefix(append([]string(nil), ndkPrebuiltSharedLibs...), "lib")
-
-	// These libraries have migrated over to the new ndk_library, which is added
-	// as a variation dependency via depsMutator.
-	ndkMigratedLibs     = []string{}
-	ndkMigratedLibsLock sync.Mutex // protects ndkMigratedLibs writes during parallel BeginMutator
+	// Added as a variation dependency via depsMutator.
+	ndkKnownLibs = []string{}
+	// protects ndkKnownLibs writes during parallel BeginMutator.
+	ndkKnownLibsLock sync.Mutex
 )
 
 // Creates a stub shared library based on the provided version file.
@@ -257,14 +230,14 @@
 		ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix)
 	}
 
-	ndkMigratedLibsLock.Lock()
-	defer ndkMigratedLibsLock.Unlock()
-	for _, lib := range ndkMigratedLibs {
+	ndkKnownLibsLock.Lock()
+	defer ndkKnownLibsLock.Unlock()
+	for _, lib := range ndkKnownLibs {
 		if lib == name {
 			return
 		}
 	}
-	ndkMigratedLibs = append(ndkMigratedLibs, name)
+	ndkKnownLibs = append(ndkKnownLibs, name)
 }
 
 func addStubLibraryCompilerFlags(flags Flags) Flags {
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/testing.go b/cc/testing.go
index b5cf45c..b0c3c162 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -32,6 +32,7 @@
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_object", NdkPrebuiltObjectFactory)
+	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
 }
 
 func GatherRequiredDepsForTest(oses ...android.OsType) string {
@@ -393,25 +394,22 @@
 			system_shared_libs: [],
 		}
 
-		cc_library {
-			name: "libc.ndk.current",
-			sdk_version: "current",
-			stl: "none",
-			system_shared_libs: [],
+		ndk_library {
+			name: "libc",
+			first_version: "minimum",
+			symbol_file: "libc.map.txt",
 		}
 
-		cc_library {
-			name: "libm.ndk.current",
-			sdk_version: "current",
-			stl: "none",
-			system_shared_libs: [],
+		ndk_library {
+			name: "libm",
+			first_version: "minimum",
+			symbol_file: "libm.map.txt",
 		}
 
-		cc_library {
-			name: "libdl.ndk.current",
-			sdk_version: "current",
-			stl: "none",
-			system_shared_libs: [],
+		ndk_library {
+			name: "libdl",
+			first_version: "minimum",
+			symbol_file: "libdl.map.txt",
 		}
 
 		ndk_prebuilt_object {
@@ -503,7 +501,6 @@
 	ctx.RegisterModuleType("cc_fuzz", FuzzFactory)
 	ctx.RegisterModuleType("cc_test", TestFactory)
 	ctx.RegisterModuleType("llndk_headers", llndkHeadersFactory)
-	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
 	ctx.RegisterModuleType("vendor_public_library", vendorPublicLibraryFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 4dad730..fec0c8b 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -569,7 +569,13 @@
 			return m.outputFile.Valid() && proptools.BoolDefault(m.VendorProperties.Vendor_available, true)
 		}
 		if l.shared() {
-			return m.outputFile.Valid() && !m.IsVndk()
+			if !m.outputFile.Valid() {
+				return false
+			}
+			if !m.IsVndk() {
+				return true
+			}
+			return m.isVndkExt()
 		}
 		return true
 	}
@@ -669,7 +675,16 @@
 
 		// Common properties among snapshots.
 		prop.ModuleName = ctx.ModuleName(m)
-		prop.RelativeInstallPath = m.RelativeInstallPath()
+		if m.isVndkExt() {
+			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
+			if m.isVndkSp() {
+				prop.RelativeInstallPath = "vndk-sp"
+			} else {
+				prop.RelativeInstallPath = "vndk"
+			}
+		} else {
+			prop.RelativeInstallPath = m.RelativeInstallPath()
+		}
 		prop.RuntimeLibs = m.Properties.SnapshotRuntimeLibs
 		prop.Required = m.RequiredModuleNames()
 		for _, path := range m.InitRc() {
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_apks/main.go b/cmd/extract_apks/main.go
index e9a850e..db54ffb 100644
--- a/cmd/extract_apks/main.go
+++ b/cmd/extract_apks/main.go
@@ -24,6 +24,7 @@
 	"math"
 	"os"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -355,7 +356,7 @@
 
 // Writes out selected entries, renaming them as needed
 func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
-	writer Zip2ZipWriter) error {
+	writer Zip2ZipWriter, partition string) ([]string, error) {
 	// Renaming rules:
 	//  splits/MODULE-master.apk to STEM.apk
 	// else
@@ -389,10 +390,11 @@
 	}
 
 	entryOrigin := make(map[string]string) // output entry to input entry
+	var apkcerts []string
 	for _, apk := range selected.entries {
 		apkFile, ok := apkSet.entries[apk]
 		if !ok {
-			return fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
+			return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
 		}
 		inName := apkFile.Name
 		outName, ok := renamer(inName)
@@ -405,10 +407,15 @@
 		}
 		entryOrigin[outName] = inName
 		if err := writer.CopyFrom(apkFile, outName); err != nil {
-			return err
+			return nil, err
+		}
+		if partition != "" {
+			apkcerts = append(apkcerts, fmt.Sprintf(
+				`name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
 		}
 	}
-	return nil
+	sort.Strings(apkcerts)
+	return apkcerts, nil
 }
 
 func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
@@ -433,6 +440,9 @@
 	}
 	extractSingle = flag.Bool("extract-single", false,
 		"extract a single target and output it uncompressed. only available for standalone apks and apexes.")
+	apkcertsOutput = flag.String("apkcerts", "",
+		"optional apkcerts.txt output file containing signing info of all outputted apks")
+	partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
 )
 
 // Parse abi values
@@ -485,7 +495,8 @@
 func processArgs() {
 	flag.Usage = func() {
 		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> -sdk-version value -abis value `+
-			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] <APK set>`)
+			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
+			`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
 		flag.PrintDefaults()
 		os.Exit(2)
 	}
@@ -498,7 +509,8 @@
 		"allow prereleased")
 	flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
 	flag.Parse()
-	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 || (targetConfig.stem == "" && !*extractSingle) {
+	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
+		(targetConfig.stem == "" && !*extractSingle) || (*apkcertsOutput != "" && *partition == "") {
 		flag.Usage()
 	}
 	targetConfig.sdkVersion = int32(*version)
@@ -536,7 +548,20 @@
 				log.Fatal(err)
 			}
 		}()
-		err = apkSet.writeApks(sel, targetConfig, writer)
+		apkcerts, err := apkSet.writeApks(sel, targetConfig, writer, *partition)
+		if err == nil && *apkcertsOutput != "" {
+			apkcertsFile, err := os.Create(*apkcertsOutput)
+			if err != nil {
+				log.Fatal(err)
+			}
+			defer apkcertsFile.Close()
+			for _, a := range apkcerts {
+				_, err = apkcertsFile.WriteString(a + "\n")
+				if err != nil {
+					log.Fatal(err)
+				}
+			}
+		}
 	}
 	if err != nil {
 		log.Fatal(err)
diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go
index bdd4bec..c3e6a2d 100644
--- a/cmd/extract_apks/main_test.go
+++ b/cmd/extract_apks/main_test.go
@@ -16,10 +16,11 @@
 
 import (
 	"fmt"
-	"github.com/golang/protobuf/proto"
 	"reflect"
 	"testing"
 
+	"github.com/golang/protobuf/proto"
+
 	bp "android/soong/cmd/extract_apks/bundle_proto"
 	"android/soong/third_party/zip"
 )
@@ -430,48 +431,63 @@
 	return nil
 }
 
-type testCaseWriteZip struct {
+type testCaseWriteApks struct {
 	name       string
 	moduleName string
 	stem       string
+	partition  string
 	// what we write from what
-	expected map[string]string
+	expectedZipEntries map[string]string
+	expectedApkcerts   []string
 }
 
-func TestWriteZip(t *testing.T) {
-	testCases := []testCaseWriteZip{
+func TestWriteApks(t *testing.T) {
+	testCases := []testCaseWriteApks{
 		{
 			name:       "splits",
 			moduleName: "mybase",
 			stem:       "Foo",
-			expected: map[string]string{
+			partition:  "system",
+			expectedZipEntries: map[string]string{
 				"Foo.apk":       "splits/mybase-master.apk",
 				"Foo-xhdpi.apk": "splits/mybase-xhdpi.apk",
 			},
+			expectedApkcerts: []string{
+				`name="Foo-xhdpi.apk" certificate="PRESIGNED" private_key="" partition="system"`,
+				`name="Foo.apk" certificate="PRESIGNED" private_key="" partition="system"`,
+			},
 		},
 		{
 			name:       "universal",
 			moduleName: "base",
 			stem:       "Bar",
-			expected: map[string]string{
+			partition:  "product",
+			expectedZipEntries: map[string]string{
 				"Bar.apk": "universal.apk",
 			},
+			expectedApkcerts: []string{
+				`name="Bar.apk" certificate="PRESIGNED" private_key="" partition="product"`,
+			},
 		},
 	}
 	for _, testCase := range testCases {
 		apkSet := ApkSet{entries: make(map[string]*zip.File)}
 		sel := SelectionResult{moduleName: testCase.moduleName}
-		for _, in := range testCase.expected {
+		for _, in := range testCase.expectedZipEntries {
 			apkSet.entries[in] = &zip.File{FileHeader: zip.FileHeader{Name: in}}
 			sel.entries = append(sel.entries, in)
 		}
 		writer := testZip2ZipWriter{make(map[string]string)}
 		config := TargetConfig{stem: testCase.stem}
-		if err := apkSet.writeApks(sel, config, writer); err != nil {
+		apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition)
+		if err != nil {
 			t.Error(err)
 		}
-		if !reflect.DeepEqual(testCase.expected, writer.entries) {
-			t.Errorf("expected %v, got %v", testCase.expected, writer.entries)
+		if !reflect.DeepEqual(testCase.expectedZipEntries, writer.entries) {
+			t.Errorf("expected zip entries %v, got %v", testCase.expectedZipEntries, writer.entries)
+		}
+		if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) {
+			t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts)
 		}
 	}
 }
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/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 8b80871..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
diff --git a/java/androidmk.go b/java/androidmk.go
index e73b030..618e15d 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -719,6 +719,7 @@
 				func(entries *android.AndroidMkEntries) {
 					entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", apkSet.Privileged())
 					entries.SetString("LOCAL_APK_SET_MASTER_FILE", apkSet.masterFile)
+					entries.SetPath("LOCAL_APKCERTS_FILE", apkSet.apkcertsFile)
 					entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", apkSet.properties.Overrides...)
 				},
 			},
diff --git a/java/app.go b/java/app.go
index 6a806a9..98bce94 100755
--- a/java/app.go
+++ b/java/app.go
@@ -79,6 +79,7 @@
 	properties   AndroidAppSetProperties
 	packedOutput android.WritablePath
 	masterFile   string
+	apkcertsFile android.ModuleOutPath
 }
 
 func (as *AndroidAppSet) Name() string {
@@ -130,6 +131,7 @@
 
 func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+	as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt")
 	// We are assuming here that the master file in the APK
 	// set has `.apk` suffix. If it doesn't the build will fail.
 	// APK sets containing APEX files are handled elsewhere.
@@ -142,16 +144,19 @@
 	// TODO(asmundak): do we support device features
 	ctx.Build(pctx,
 		android.BuildParams{
-			Rule:        extractMatchingApks,
-			Description: "Extract APKs from APK set",
-			Output:      as.packedOutput,
-			Inputs:      android.Paths{as.prebuilt.SingleSourcePath(ctx)},
+			Rule:           extractMatchingApks,
+			Description:    "Extract APKs from APK set",
+			Output:         as.packedOutput,
+			ImplicitOutput: as.apkcertsFile,
+			Inputs:         android.Paths{as.prebuilt.SingleSourcePath(ctx)},
 			Args: map[string]string{
 				"abis":              strings.Join(SupportedAbis(ctx), ","),
 				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
 				"screen-densities":  screenDensities,
 				"sdk-version":       ctx.Config().PlatformSdkVersion(),
 				"stem":              as.BaseModuleName(),
+				"apkcerts":          as.apkcertsFile.String(),
+				"partition":         as.PartitionTag(ctx.DeviceConfig()),
 			},
 		})
 }
diff --git a/java/app_test.go b/java/app_test.go
index e0c5820..d4323bb 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -147,7 +147,7 @@
 			name: "foo",
 			set: "prebuilts/apks/app.apks",
 			prerelease: true,
-        }`)
+		}`)
 	module := ctx.ModuleForTests("foo", "android_common")
 	const packedSplitApks = "foo.zip"
 	params := module.Output(packedSplitApks)
@@ -157,6 +157,9 @@
 	if s := params.Args["allow-prereleased"]; s != "true" {
 		t.Errorf("wrong allow-prereleased value: '%s', expected 'true'", s)
 	}
+	if s := params.Args["partition"]; s != "system" {
+		t.Errorf("wrong partition value: '%s', expected 'system'", s)
+	}
 	mkEntries := android.AndroidMkEntriesForTest(t, config, "", module.Module())[0]
 	actualMaster := mkEntries.EntryMap["LOCAL_APK_SET_MASTER_FILE"]
 	expectedMaster := []string{"foo.apk"}
diff --git a/java/builder.go b/java/builder.go
index a27e5c3..7318fcb 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -120,10 +120,11 @@
 				`${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` +
 				`-sdk-version=${sdk-version} -abis=${abis} ` +
 				`--screen-densities=${screen-densities} --stem=${stem} ` +
+				`-apkcerts=${apkcerts} -partition=${partition} ` +
 				`${in}`,
 			CommandDeps: []string{"${config.ExtractApksCmd}"},
 		},
-		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem")
+		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition")
 
 	turbine, turbineRE = remoteexec.StaticRules(pctx, "turbine",
 		blueprint.RuleParams{
diff --git a/java/config/config.go b/java/config/config.go
index bb5be3a..0fe74c8 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -28,14 +28,14 @@
 var (
 	pctx = android.NewPackageContext("android/soong/java/config")
 
-	// TODO(b/157640067): Don't depend on the legacy API by default in the long term.
-	DefaultBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
-	DefaultSystemModules          = "legacy-core-platform-api-stubs-system-modules"
-	DefaultLibraries              = []string{"ext", "framework"}
-	DefaultLambdaStubsLibrary     = "core-lambda-stubs"
-	SdkLambdaStubsPath            = "prebuilts/sdk/tools/core-lambda-stubs.jar"
+	LegacyCorePlatformBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
+	LegacyCorePlatformSystemModules          = "legacy-core-platform-api-stubs-system-modules"
+	FrameworkLibraries                       = []string{"ext", "framework"}
+	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",
@@ -110,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 981a736..708a72a 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -25,9 +25,11 @@
 }
 
 func makeVarsProvider(ctx android.MakeVarsContext) {
-	ctx.Strict("TARGET_DEFAULT_JAVA_LIBRARIES", strings.Join(DefaultLibraries, " "))
-	ctx.Strict("TARGET_DEFAULT_BOOTCLASSPATH_LIBRARIES", strings.Join(DefaultBootclasspathLibraries, " "))
-	ctx.Strict("DEFAULT_SYSTEM_MODULES", DefaultSystemModules)
+	ctx.Strict("FRAMEWORK_LIBRARIES", strings.Join(FrameworkLibraries, " "))
+
+	// These are used by make when LOCAL_PRIVATE_PLATFORM_APIS is set (equivalent to platform_apis in blueprint):
+	ctx.Strict("LEGACY_CORE_PLATFORM_BOOTCLASSPATH_LIBRARIES", strings.Join(LegacyCorePlatformBootclasspathLibraries, " "))
+	ctx.Strict("LEGACY_CORE_PLATFORM_SYSTEM_MODULES", LegacyCorePlatformSystemModules)
 
 	ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}")
 	ctx.Strict("ANDROID_JAVA8_HOME", "prebuilts/jdk/jdk8/${hostPrebuiltTag}")
@@ -64,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/droiddoc.go b/java/droiddoc.go
index 73b897a..99bfb6d 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -440,16 +440,11 @@
 func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
 	if ctx.Device() {
 		sdkDep := decodeSdkDep(ctx, sdkContext(j))
-		if sdkDep.useDefaultLibs {
-			ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
-			ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
-			if sdkDep.hasFrameworkLibs() {
-				ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
-			}
-		} else if sdkDep.useModule {
+		if sdkDep.useModule {
 			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
 			ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
 			ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
+			ctx.AddVariationDependencies(nil, libTag, sdkDep.classpath...)
 		}
 	}
 
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 0a8eb99..0d46672 100644
--- a/java/java.go
+++ b/java/java.go
@@ -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
 
@@ -591,7 +597,7 @@
 }
 
 type sdkDep struct {
-	useModule, useFiles, useDefaultLibs, invalidVersion bool
+	useModule, useFiles, invalidVersion bool
 
 	// The modules that will be added to the bootclasspath when targeting 1.8 or lower
 	bootclasspath []string
@@ -600,7 +606,11 @@
 	// modules are to be used.
 	systemModules string
 
+	// The modules that will be added to the classpath regardless of the Java language level targeted
+	classpath []string
+
 	// The modules that will be added ot the classpath when targeting 1.9 or higher
+	// (normally these will be on the bootclasspath when targeting 1.8 or lower)
 	java9Classpath []string
 
 	frameworkResModule string
@@ -694,18 +704,15 @@
 		j.linter.deps(ctx)
 
 		sdkDep := decodeSdkDep(ctx, sdkContext(j))
-		if sdkDep.useDefaultLibs {
-			ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
-			ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
-			if sdkDep.hasFrameworkLibs() {
-				ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
-			}
-		} else if sdkDep.useModule {
+		if sdkDep.useModule {
 			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
 			ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
+			ctx.AddVariationDependencies(nil, libTag, sdkDep.classpath...)
 			if j.deviceProperties.EffectiveOptimizeEnabled() && sdkDep.hasStandardLibs() {
-				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultBootclasspathLibraries...)
-				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultLibraries...)
+				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.LegacyCorePlatformBootclasspathLibraries...)
+			}
+			if j.deviceProperties.EffectiveOptimizeEnabled() && sdkDep.hasFrameworkLibs() {
+				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.FrameworkLibraries...)
 			}
 		}
 		if sdkDep.systemModules != "" {
@@ -1631,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
@@ -1909,6 +1919,10 @@
 	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
 }
@@ -2903,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/sdk.go b/java/sdk.go
index 2a08f32..c4861e3 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -94,9 +94,9 @@
 	case sdkCorePlatform:
 		return "core_platform"
 	case sdkModule:
-		return "module"
+		return "module-lib"
 	case sdkSystemServer:
-		return "system_server"
+		return "system-server"
 	default:
 		return "invalid"
 	}
@@ -412,7 +412,10 @@
 	switch sdkVersion.kind {
 	case sdkPrivate:
 		return sdkDep{
-			useDefaultLibs:     true,
+			useModule:          true,
+			systemModules:      config.LegacyCorePlatformSystemModules,
+			bootclasspath:      config.LegacyCorePlatformBootclasspathLibraries,
+			classpath:          config.FrameworkLibraries,
 			frameworkResModule: "framework-res",
 		}
 	case sdkNone:
@@ -434,9 +437,10 @@
 		}
 	case sdkCorePlatform:
 		return sdkDep{
-			useDefaultLibs:     true,
-			frameworkResModule: "framework-res",
-			noFrameworksLibs:   true,
+			useModule:        true,
+			systemModules:    config.LegacyCorePlatformSystemModules,
+			bootclasspath:    config.LegacyCorePlatformBootclasspathLibraries,
+			noFrameworksLibs: true,
 		}
 	case sdkPublic:
 		return toModule([]string{"android_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 91869ed..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{}{
diff --git a/java/sdk_test.go b/java/sdk_test.go
index e5d322c..773a70c 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -49,27 +49,27 @@
 	}{
 		{
 			name:           "default",
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
-			java8classpath: config.DefaultLibraries,
-			java9classpath: config.DefaultLibraries,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
+			java8classpath: config.FrameworkLibraries,
+			java9classpath: config.FrameworkLibraries,
 			aidl:           "-Iframework/aidl",
 		},
 		{
 			name:           `sdk_version:"core_platform"`,
 			properties:     `sdk_version:"core_platform"`,
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
 			java8classpath: []string{},
 			aidl:           "",
 		},
 		{
 			name:           "blank sdk version",
 			properties:     `sdk_version: "",`,
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
-			java8classpath: config.DefaultLibraries,
-			java9classpath: config.DefaultLibraries,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
+			java8classpath: config.FrameworkLibraries,
+			java9classpath: config.FrameworkLibraries,
 			aidl:           "-Iframework/aidl",
 		},
 		{
diff --git a/java/testing.go b/java/testing.go
index 6fc10da..f5688e6 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -197,6 +197,7 @@
 	systemModules := []string{
 		"core-current-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
+		"stable-core-platform-api-stubs-system-modules",
 	}
 
 	for _, extra := range systemModules {
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/binary.go b/rust/binary.go
index 56d6f0b..a1cd410 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -131,3 +131,11 @@
 func (binary *binaryDecorator) coverageOutputZipPath() android.OptionalPath {
 	return binary.coverageOutputZipFile
 }
+
+func (binary *binaryDecorator) autoDep() autoDep {
+	if binary.preferDynamic() {
+		return dylibAutoDep
+	} else {
+		return rlibAutoDep
+	}
+}
diff --git a/rust/builder.go b/rust/builder.go
index 7dbb59d..16d7306 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} " +
+				"$rustcFlags $clippyFlags",
+			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,38 +69,38 @@
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
 }
 
-func TransformSrcToBinary(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, includeDirs []string) buildOutput {
+func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, linkDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
 
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", includeDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", linkDirs)
 }
 
-func TransformSrctoRlib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, includeDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", includeDirs)
+func TransformSrctoRlib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, linkDirs []string) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", linkDirs)
 }
 
-func TransformSrctoDylib(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, includeDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", includeDirs)
+func TransformSrctoDylib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, linkDirs []string) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", linkDirs)
 }
 
-func TransformSrctoStatic(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, includeDirs []string) buildOutput {
+func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, linkDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", includeDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", linkDirs)
 }
 
-func TransformSrctoShared(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, includeDirs []string) buildOutput {
+func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, linkDirs []string) buildOutput {
 	flags.RustFlags = append(flags.RustFlags, "-C lto")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", includeDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", linkDirs)
 }
 
-func TransformSrctoProcMacro(ctx android.ModuleContext, mainSrc android.Path, deps PathDeps,
-	flags Flags, outputFile android.WritablePath, includeDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", includeDirs)
+func TransformSrctoProcMacro(ctx ModuleContext, mainSrc android.Path, deps PathDeps,
+	flags Flags, outputFile android.WritablePath, linkDirs []string) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", linkDirs)
 }
 
 func rustLibsToPaths(libs RustLibraries) android.Paths {
@@ -99,8 +111,8 @@
 	return paths
 }
 
-func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, crate_type string, includeDirs []string) buildOutput {
+func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, crate_type string, linkDirs []string) buildOutput {
 
 	var inputs android.Paths
 	var implicits 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,12 +137,10 @@
 		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
-		rustcFlags = append(rustcFlags, "--sysroot=/dev/null")
-	}
+
+	// Suppress an implicit sysroot
+	rustcFlags = append(rustcFlags, "--sysroot=/dev/null")
+
 	// Collect linker flags
 	linkFlags = append(linkFlags, flags.GlobalLinkFlags...)
 	linkFlags = append(linkFlags, flags.LinkFlags...)
@@ -146,7 +156,7 @@
 		libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String())
 	}
 
-	for _, path := range includeDirs {
+	for _, path := range linkDirs {
 		libFlags = append(libFlags, "-L "+path)
 	}
 
@@ -179,6 +189,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 +227,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..3144173
--- /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_dylib").Output("libfoo.dylib.so")
+	fooClippy := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").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_dylib").Output("libfoobar.dylib.so")
+	foobarClippy := ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").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..92a3b07 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -28,10 +28,6 @@
 	return proptools.StringDefault(compiler.Properties.Edition, config.DefaultEdition)
 }
 
-func getDenyWarnings(compiler *baseCompiler) bool {
-	return BoolDefault(compiler.Properties.Deny_warnings, config.DefaultDenyWarnings)
-}
-
 func (compiler *baseCompiler) setNoStdlibs() {
 	compiler.Properties.No_stdlibs = proptools.BoolPtr(true)
 }
@@ -56,8 +52,8 @@
 	// path to the source file that is the main entry point of the program (e.g. main.rs or lib.rs)
 	Srcs []string `android:"path,arch_variant"`
 
-	// whether to pass "-D warnings" to rustc. Defaults to true.
-	Deny_warnings *bool
+	// whether to suppress the standard lint flags - default to false
+	No_lint *bool
 
 	// flags to pass to rustc
 	Flags []string `android:"path,arch_variant"`
@@ -71,6 +67,9 @@
 	// list of rust dylib crate dependencies
 	Dylibs []string `android:"arch_variant"`
 
+	// list of rust automatic crate dependencies
+	Rustlibs []string `android:"arch_variant"`
+
 	// list of rust proc_macro crate dependencies
 	Proc_macros []string `android:"arch_variant"`
 
@@ -104,8 +103,6 @@
 
 type baseCompiler struct {
 	Properties   BaseCompilerProperties
-	depFlags     []string
-	linkDirs     []string
 	coverageFile android.Path //rustc generates a single gcno file
 
 	// Install related
@@ -145,8 +142,8 @@
 
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 
-	if getDenyWarnings(compiler) {
-		flags.RustFlags = append(flags.RustFlags, "-D warnings")
+	if !Bool(compiler.Properties.No_lint) {
+		flags.RustFlags = append(flags.RustFlags, config.RustcLintsForDir(ctx.ModuleDir()))
 	}
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
 	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(compiler.Properties.Features)...)
@@ -182,6 +179,7 @@
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
 	deps.Dylibs = append(deps.Dylibs, compiler.Properties.Dylibs...)
+	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs...)
 	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...)
 	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
 	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...)
@@ -193,17 +191,7 @@
 				stdlib = stdlib + "_" + ctx.toolchain().RustTriple()
 			}
 
-			// This check is technically insufficient - on the host, where
-			// static linking is the default, if one of our static
-			// dependencies uses a dynamic library, we need to dynamically
-			// link the stdlib as well.
-			if (len(deps.Dylibs) > 0) || ctx.Device() {
-				// Dynamically linked stdlib
-				deps.Dylibs = append(deps.Dylibs, stdlib)
-			} else if ctx.Host() && !ctx.TargetPrimary() {
-				// Otherwise use the static in-tree stdlib for host secondary arch
-				deps.Rlibs = append(deps.Rlibs, stdlib+".static")
-			}
+			deps.Rustlibs = append(deps.Rustlibs, stdlib)
 		}
 	}
 	return deps
@@ -253,7 +241,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..bcfac7c 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -9,6 +9,7 @@
         "arm_device.go",
         "arm64_device.go",
         "global.go",
+        "lints.go",
         "toolchain.go",
         "allowed_list.go",
         "x86_darwin_host.go",
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index a339050..0204cd2 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -15,13 +15,15 @@
 		"rust_library",
 		"rust_library_dylib",
 		"rust_library_rlib",
-		"rust_library_shared",
-		"rust_library_static",
+		"rust_ffi",
+		"rust_ffi_shared",
+		"rust_ffi_static",
 		"rust_library_host",
 		"rust_library_host_dylib",
 		"rust_library_host_rlib",
-		"rust_library_host_shared",
-		"rust_library_host_static",
+		"rust_ffi_host",
+		"rust_ffi_host_shared",
+		"rust_ffi_host_static",
 		"rust_proc_macro",
 		"rust_test",
 		"rust_test_host",
diff --git a/rust/config/global.go b/rust/config/global.go
index 63624c0..e1b1775 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{
@@ -32,8 +32,6 @@
 		"libtest",
 	}
 
-	DefaultDenyWarnings = true
-
 	GlobalRustFlags = []string{
 		"--remap-path-prefix $$(pwd)=",
 		"-C codegen-units=1",
diff --git a/rust/config/lints.go b/rust/config/lints.go
new file mode 100644
index 0000000..529d094
--- /dev/null
+++ b/rust/config/lints.go
@@ -0,0 +1,150 @@
+// 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"
+)
+
+// Overarching principles for Rust lints on Android:
+// The Android build system tries to avoid reporting warnings during the build.
+// Therefore, by default, we upgrade warnings to denials. For some of these
+// lints, an allow exception is setup, using the variables below.
+// There are different default lints depending on the repository location (see
+// DefaultLocalClippyChecks).
+// The lints are split into two categories. The first one contains the built-in
+// lints (https://doc.rust-lang.org/rustc/lints/index.html). The second is
+// specific to Clippy lints (https://rust-lang.github.io/rust-clippy/master/).
+//
+// When developing a module, it is possible to use the `no_lint` property to
+// disable the built-in lints configuration. It is also possible to set
+// `clippy` to false to disable the clippy verification. Expect some
+// questioning during review if you enable one of these options. For external/
+// code, if you need to use them, it is likely a bug. Otherwise, it may be
+// useful to add an exception (that is, move a lint from deny to allow).
+var (
+	// Default Rust lints that applies to Google-authored modules.
+	defaultRustcLints = []string{
+		"-A deprecated",
+		"-D missing-docs",
+		"-D warnings",
+	}
+	// Default Clippy lints. These are applied on top of defaultRustcLints.
+	// It should be assumed that any warning lint will be promoted to a
+	// deny.
+	defaultClippyLints = []string{
+		"-A clippy::type-complexity",
+	}
+
+	// Rust lints for vendor code.
+	defaultRustcVendorLints = []string{
+		"-A deprecated",
+		"-D warnings",
+	}
+	// Clippy lints for vendor source. These are applied on top of
+	// defaultRustcVendorLints.  It should be assumed that any warning lint
+	// will be promoted to a deny.
+	defaultClippyVendorLints = []string{
+		"-A clippy::complexity",
+		"-A clippy::perf",
+		"-A clippy::style",
+	}
+
+	// For prebuilts/ and external/, no linting is expected. If a warning
+	// or a deny is reported, it should be fixed upstream.
+	allowAllLints = []string{
+		"--cap-lints allow",
+	}
+)
+
+func init() {
+	// Default Rust lints. These apply to all Google-authored modules.
+	pctx.VariableFunc("RustDefaultLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("RUST_DEFAULT_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultRustcLints, " ")
+	})
+	pctx.VariableFunc("ClippyDefaultLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("CLIPPY_DEFAULT_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultClippyLints, " ")
+	})
+
+	// Rust lints that only applies to external code.
+	pctx.VariableFunc("RustVendorLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("RUST_VENDOR_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultRustcVendorLints, " ")
+	})
+	pctx.VariableFunc("ClippyVendorLints", func(ctx android.PackageVarContext) string {
+		if override := ctx.Config().Getenv("CLIPPY_VENDOR_LINTS"); override != "" {
+			return override
+		}
+		return strings.Join(defaultClippyVendorLints, " ")
+	})
+	pctx.StaticVariable("RustAllowAllLints", strings.Join(allowAllLints, " "))
+}
+
+type PathBasedClippyConfig struct {
+	PathPrefix    string
+	RustcConfig   string
+	ClippyEnabled bool
+	ClippyConfig  string
+}
+
+const noLint = ""
+const rustcDefault = "${config.RustDefaultLints}"
+const rustcVendor = "${config.RustVendorLints}"
+const rustcAllowAll = "${config.RustAllowAllLints}"
+const clippyDefault = "${config.ClippyDefaultLints}"
+const clippyVendor = "${config.ClippyVendorLints}"
+
+// This is a map of local path prefixes to a set of parameters for the linting:
+// - a string for the lints to apply to rustc.
+// - a boolean to indicate if clippy should be executed.
+// - a string for the lints to apply to clippy.
+// The first entry matching will be used.
+var DefaultLocalClippyChecks = []PathBasedClippyConfig{
+	{"external/", rustcAllowAll, false, noLint},
+	{"hardware/", rustcVendor, true, clippyVendor},
+	{"prebuilts/", rustcAllowAll, false, noLint},
+	{"vendor/google", rustcDefault, true, clippyDefault},
+	{"vendor/", rustcVendor, true, clippyVendor},
+}
+
+// ClippyLintsForDir returns a boolean if Clippy should be executed and if so, the lints to be used.
+func ClippyLintsForDir(dir string) (bool, string) {
+	for _, pathCheck := range DefaultLocalClippyChecks {
+		if strings.HasPrefix(dir, pathCheck.PathPrefix) {
+			return pathCheck.ClippyEnabled, pathCheck.ClippyConfig
+		}
+	}
+	return true, clippyDefault
+}
+
+// RustcLintsForDir returns the standard lints to be used for a repository.
+func RustcLintsForDir(dir string) string {
+	for _, pathCheck := range DefaultLocalClippyChecks {
+		if strings.HasPrefix(dir, pathCheck.PathPrefix) {
+			return pathCheck.RustcConfig
+		}
+	}
+	return rustcDefault
+}
diff --git a/rust/coverage.go b/rust/coverage.go
index 4e3977b..223ba4f 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -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/coverage_test.go b/rust/coverage_test.go
index 27acad3..357c2e8 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -105,6 +105,12 @@
 			rlibs: ["librlib"],
 			crate_name: "foo",
 		}
+                rust_ffi_static {
+                        name: "libbaz",
+                        srcs: ["foo.rs"],
+                        rlibs: ["librlib"],
+                        crate_name: "baz",
+                }
 		rust_library_rlib {
 			name: "librlib",
 			srcs: ["foo.rs"],
@@ -113,17 +119,17 @@
 		rust_binary {
 			name: "fizz",
 			rlibs: ["librlib"],
-			static_libs: ["libfoo"],
+			static_libs: ["libbaz"],
 			srcs: ["foo.rs"],
 		}
 		cc_binary {
 			name: "buzz",
-			static_libs: ["libfoo"],
+			static_libs: ["libbaz"],
 			srcs: ["foo.c"],
 		}
 		cc_library {
 			name: "libbar",
-			static_libs: ["libfoo"],
+			static_libs: ["libbaz"],
 			compile_multilib: "64",
 			srcs: ["foo.c"],
 		}`)
@@ -149,7 +155,7 @@
 
 	// Make sure the expected inputs are provided to the zip rule.
 	if !android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_rlib_cov/librlib.gcno") ||
-		!android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") ||
+		!android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_static_cov/libbaz.gcno") ||
 		!android.SuffixInList(fizzZipInputs, "android_arm64_armv8-a_cov/fizz.gcno") {
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", fizzZipInputs)
 	}
@@ -158,11 +164,11 @@
 		t.Fatalf("missing expected coverage files for rust 'fizz' binary: %#v", libfooZipInputs)
 	}
 	if !android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_cov/obj/foo.gcno") ||
-		!android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") {
+		!android.SuffixInList(buzzZipInputs, "android_arm64_armv8-a_static_cov/libbaz.gcno") {
 		t.Fatalf("missing expected coverage files for cc 'buzz' binary: %#v", buzzZipInputs)
 	}
 	if !android.SuffixInList(libbarZipInputs, "android_arm64_armv8-a_static_cov/obj/foo.gcno") ||
-		!android.SuffixInList(libbarZipInputs, "android_arm64_armv8-a_static_cov/libfoo.gcno") {
+		!android.SuffixInList(libbarZipInputs, "android_arm64_armv8-a_static_cov/libbaz.gcno") {
 		t.Fatalf("missing expected coverage files for cc 'libbar' library: %#v", libbarZipInputs)
 	}
 }
diff --git a/rust/library.go b/rust/library.go
index 3c948ea..8b8e797 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -19,7 +19,6 @@
 	"strings"
 
 	"android/soong/android"
-	"android/soong/rust/config"
 )
 
 func init() {
@@ -29,14 +28,17 @@
 	android.RegisterModuleType("rust_library_host", RustLibraryHostFactory)
 	android.RegisterModuleType("rust_library_host_dylib", RustLibraryDylibHostFactory)
 	android.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory)
-	android.RegisterModuleType("rust_library_shared", RustLibrarySharedFactory)
-	android.RegisterModuleType("rust_library_static", RustLibraryStaticFactory)
-	android.RegisterModuleType("rust_library_host_shared", RustLibrarySharedHostFactory)
-	android.RegisterModuleType("rust_library_host_static", RustLibraryStaticHostFactory)
+	android.RegisterModuleType("rust_ffi", RustFFIFactory)
+	android.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
+	android.RegisterModuleType("rust_ffi_static", RustFFIStaticFactory)
+	android.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
+	android.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
+	android.RegisterModuleType("rust_ffi_host_static", RustFFIStaticHostFactory)
 }
 
 type VariantLibraryProperties struct {
-	Enabled *bool `android:"arch_variant"`
+	Enabled *bool    `android:"arch_variant"`
+	Srcs    []string `android:"path,arch_variant"`
 }
 
 type LibraryCompilerProperties struct {
@@ -71,6 +73,7 @@
 
 type libraryDecorator struct {
 	*baseCompiler
+	*flagExporter
 
 	Properties        LibraryCompilerProperties
 	MutatedProperties LibraryMutatedProperties
@@ -96,6 +99,8 @@
 	setStatic()
 
 	// Build a specific library variant
+	BuildOnlyFFI()
+	BuildOnlyRust()
 	BuildOnlyRlib()
 	BuildOnlyDylib()
 	BuildOnlyStatic()
@@ -106,22 +111,6 @@
 	return true
 }
 
-func (library *libraryDecorator) exportedDirs() []string {
-	return library.linkDirs
-}
-
-func (library *libraryDecorator) exportedDepFlags() []string {
-	return library.depFlags
-}
-
-func (library *libraryDecorator) reexportDirs(dirs ...string) {
-	library.linkDirs = android.FirstUniqueStrings(append(library.linkDirs, dirs...))
-}
-
-func (library *libraryDecorator) reexportDepFlags(flags ...string) {
-	library.depFlags = android.FirstUniqueStrings(append(library.depFlags, flags...))
-}
-
 func (library *libraryDecorator) rlib() bool {
 	return library.MutatedProperties.VariantIsRlib
 }
@@ -182,12 +171,31 @@
 	library.MutatedProperties.VariantIsDylib = false
 }
 
+func (library *libraryDecorator) autoDep() autoDep {
+	if library.rlib() || library.static() {
+		return rlibAutoDep
+	} else if library.dylib() || library.shared() {
+		return dylibAutoDep
+	} else {
+		return rlibAutoDep
+	}
+}
+
 var _ compiler = (*libraryDecorator)(nil)
 var _ libraryInterface = (*libraryDecorator)(nil)
+var _ exportedFlagsProducer = (*libraryDecorator)(nil)
 
-// rust_library produces all variants.
+// rust_library produces all rust variants.
 func RustLibraryFactory() android.Module {
-	module, _ := NewRustLibrary(android.HostAndDeviceSupported)
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyRust()
+	return module.Init()
+}
+
+// rust_ffi produces all ffi variants.
+func RustFFIFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyFFI()
 	return module.Init()
 }
 
@@ -205,23 +213,31 @@
 	return module.Init()
 }
 
-// rust_library_shared produces a shared library.
-func RustLibrarySharedFactory() android.Module {
+// rust_ffi_shared produces a shared library.
+func RustFFISharedFactory() android.Module {
 	module, library := NewRustLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyShared()
 	return module.Init()
 }
 
-// rust_library_static produces a static library.
-func RustLibraryStaticFactory() android.Module {
+// rust_ffi_static produces a static library.
+func RustFFIStaticFactory() android.Module {
 	module, library := NewRustLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyStatic()
 	return module.Init()
 }
 
-// rust_library_host produces all variants.
+// rust_library_host produces all rust variants.
 func RustLibraryHostFactory() android.Module {
-	module, _ := NewRustLibrary(android.HostSupported)
+	module, library := NewRustLibrary(android.HostSupported)
+	library.BuildOnlyRust()
+	return module.Init()
+}
+
+// rust_ffi_host produces all FFI variants.
+func RustFFIHostFactory() android.Module {
+	module, library := NewRustLibrary(android.HostSupported)
+	library.BuildOnlyFFI()
 	return module.Init()
 }
 
@@ -239,44 +255,60 @@
 	return module.Init()
 }
 
-// rust_library_static_host produces a static library.
-func RustLibraryStaticHostFactory() android.Module {
+// rust_ffi_static_host produces a static library.
+func RustFFIStaticHostFactory() android.Module {
 	module, library := NewRustLibrary(android.HostSupported)
 	library.BuildOnlyStatic()
 	return module.Init()
 }
 
-// rust_library_shared_host produces an shared library.
-func RustLibrarySharedHostFactory() android.Module {
+// rust_ffi_shared_host produces an shared library.
+func RustFFISharedHostFactory() android.Module {
 	module, library := NewRustLibrary(android.HostSupported)
 	library.BuildOnlyShared()
 	return module.Init()
 }
 
+func (library *libraryDecorator) BuildOnlyFFI() {
+	library.MutatedProperties.BuildDylib = false
+	library.MutatedProperties.BuildRlib = false
+	library.MutatedProperties.BuildShared = true
+	library.MutatedProperties.BuildStatic = true
+}
+
+func (library *libraryDecorator) BuildOnlyRust() {
+	library.MutatedProperties.BuildDylib = true
+	library.MutatedProperties.BuildRlib = true
+	library.MutatedProperties.BuildShared = false
+	library.MutatedProperties.BuildStatic = false
+}
+
 func (library *libraryDecorator) BuildOnlyDylib() {
+	library.MutatedProperties.BuildDylib = true
 	library.MutatedProperties.BuildRlib = false
 	library.MutatedProperties.BuildShared = false
 	library.MutatedProperties.BuildStatic = false
-
 }
 
 func (library *libraryDecorator) BuildOnlyRlib() {
 	library.MutatedProperties.BuildDylib = false
+	library.MutatedProperties.BuildRlib = true
 	library.MutatedProperties.BuildShared = false
 	library.MutatedProperties.BuildStatic = false
 }
 
 func (library *libraryDecorator) BuildOnlyStatic() {
-	library.MutatedProperties.BuildShared = false
 	library.MutatedProperties.BuildRlib = false
 	library.MutatedProperties.BuildDylib = false
-
+	library.MutatedProperties.BuildShared = false
+	library.MutatedProperties.BuildStatic = true
 }
 
 func (library *libraryDecorator) BuildOnlyShared() {
-	library.MutatedProperties.BuildStatic = false
 	library.MutatedProperties.BuildRlib = false
 	library.MutatedProperties.BuildDylib = false
+	library.MutatedProperties.BuildStatic = false
+	library.MutatedProperties.BuildShared = true
 }
 
 func NewRustLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
@@ -284,12 +316,13 @@
 
 	library := &libraryDecorator{
 		MutatedProperties: LibraryMutatedProperties{
-			BuildDylib:  true,
-			BuildRlib:   true,
-			BuildShared: true,
-			BuildStatic: true,
+			BuildDylib:  false,
+			BuildRlib:   false,
+			BuildShared: false,
+			BuildStatic: false,
 		},
 		baseCompiler: NewBaseCompiler("lib", "lib64", InstallInSystem),
+		flagExporter: NewFlagExporter(),
 	}
 
 	module.compiler = library
@@ -304,15 +337,6 @@
 }
 
 func (library *libraryDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
-
-	// TODO(b/155498724) Remove if C static libraries no longer require libstd as an rlib dependency.
-	if !ctx.Host() && library.static() {
-		library.setNoStdlibs()
-		for _, stdlib := range config.Stdlibs {
-			deps.Rlibs = append(deps.Rlibs, stdlib+".static")
-		}
-	}
-
 	deps = library.baseCompiler.compilerDeps(ctx, deps)
 
 	if ctx.toolchain().Bionic() && (library.dylib() || library.shared()) {
@@ -329,7 +353,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)...)
@@ -391,8 +415,8 @@
 	library.coverageOutputZipFile = TransformCoverageFilesToZip(ctx, coverageFiles, library.getStem(ctx))
 
 	if library.rlib() || library.dylib() {
-		library.reexportDirs(deps.linkDirs...)
-		library.reexportDepFlags(deps.depFlags...)
+		library.exportLinkDirs(deps.linkDirs...)
+		library.exportDepFlags(deps.depFlags...)
 	}
 	library.unstrippedOutputFile = outputFile
 
diff --git a/rust/library_test.go b/rust/library_test.go
index 9d2f6c0..8a91cf1 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -29,13 +29,18 @@
 			name: "libfoo",
 			srcs: ["foo.rs"],
 			crate_name: "foo",
-		}`)
+		}
+                rust_ffi_host {
+                        name: "libfoo.ffi",
+                        srcs: ["foo.rs"],
+                        crate_name: "foo"
+                }`)
 
 	// Test all variants are being built.
 	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib").Output("libfoo.rlib")
 	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
-	libfooStatic := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_static").Output("libfoo.a")
-	libfooShared := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_shared").Output("libfoo.so")
+	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").Output("libfoo.ffi.a")
+	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Output("libfoo.ffi.so")
 
 	rlibCrateType := "rlib"
 	dylibCrateType := "dylib"
@@ -119,7 +124,7 @@
 
 func TestSharedLibrary(t *testing.T) {
 	ctx := testRust(t, `
-		rust_library {
+		rust_ffi_shared {
 			name: "libfoo",
 			srcs: ["foo.rs"],
 			crate_name: "foo",
@@ -138,3 +143,50 @@
 			libfoo.Module().(*Module).Properties.AndroidMkDylibs)
 	}
 }
+
+// Test that variants pull in the right type of rustlib autodep
+func TestAutoDeps(t *testing.T) {
+
+	ctx := testRust(t, `
+                rust_library_host {
+                        name: "libbar",
+                        srcs: ["bar.rs"],
+                        crate_name: "bar",
+                }
+		rust_library_host {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+                        rustlibs: ["libbar"],
+		}
+                rust_ffi_host {
+                        name: "libfoo.ffi",
+                        srcs: ["foo.rs"],
+                        crate_name: "foo",
+                        rustlibs: ["libbar"],
+                }`)
+
+	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib")
+	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static")
+	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared")
+
+	for _, static := range []android.TestingModule{libfooRlib, libfooStatic} {
+		if !android.InList("libbar", static.Module().(*Module).Properties.AndroidMkRlibs) {
+			t.Errorf("libbar not present as static dependency in static lib")
+		}
+		if android.InList("libbar", static.Module().(*Module).Properties.AndroidMkDylibs) {
+			t.Errorf("libbar present as dynamic dependency in static lib")
+		}
+	}
+
+	for _, dyn := range []android.TestingModule{libfooDylib, libfooShared} {
+		if !android.InList("libbar", dyn.Module().(*Module).Properties.AndroidMkDylibs) {
+			t.Errorf("libbar not present as dynamic dependency in dynamic lib")
+		}
+		if android.InList("libbar", dyn.Module().(*Module).Properties.AndroidMkRlibs) {
+			t.Errorf("libbar present as static dependency in dynamic lib")
+		}
+
+	}
+}
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 1d97650..67d649d 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -19,12 +19,16 @@
 )
 
 func init() {
+	android.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
+	android.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
 }
 
 type PrebuiltProperties struct {
 	// path to the prebuilt file
 	Srcs []string `android:"path,arch_variant"`
+	// directories containing associated rlib dependencies
+	Link_dirs []string `android:"path,arch_variant"`
 }
 
 type prebuiltLibraryDecorator struct {
@@ -33,32 +37,65 @@
 }
 
 var _ compiler = (*prebuiltLibraryDecorator)(nil)
+var _ exportedFlagsProducer = (*prebuiltLibraryDecorator)(nil)
+
+func PrebuiltLibraryFactory() android.Module {
+	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	return module.Init()
+}
 
 func PrebuiltDylibFactory() android.Module {
 	module, _ := NewPrebuiltDylib(android.HostAndDeviceSupported)
 	return module.Init()
 }
 
-func NewPrebuiltDylib(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
+func PrebuiltRlibFactory() android.Module {
+	module, _ := NewPrebuiltRlib(android.HostAndDeviceSupported)
+	return module.Init()
+}
+
+func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
 	module, library := NewRustLibrary(hod)
-	library.BuildOnlyDylib()
+	library.BuildOnlyRust()
 	library.setNoStdlibs()
-	library.setDylib()
 	prebuilt := &prebuiltLibraryDecorator{
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
-	module.AddProperties(&library.Properties)
+	return module, prebuilt
+}
+
+func NewPrebuiltDylib(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
+	module, library := NewRustLibrary(hod)
+	library.BuildOnlyDylib()
+	library.setNoStdlibs()
+	prebuilt := &prebuiltLibraryDecorator{
+		libraryDecorator: library,
+	}
+	module.compiler = prebuilt
+	return module, prebuilt
+}
+
+func NewPrebuiltRlib(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
+	module, library := NewRustLibrary(hod)
+	library.BuildOnlyRlib()
+	library.setNoStdlibs()
+	prebuilt := &prebuiltLibraryDecorator{
+		libraryDecorator: library,
+	}
+	module.compiler = prebuilt
 	return module, prebuilt
 }
 
 func (prebuilt *prebuiltLibraryDecorator) compilerProps() []interface{} {
-	return append(prebuilt.baseCompiler.compilerProps(),
+	return append(prebuilt.libraryDecorator.compilerProps(),
 		&prebuilt.Properties)
 }
 
 func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
-	srcPath := srcPathFromModuleSrcs(ctx, prebuilt.Properties.Srcs)
+	prebuilt.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
+
+	srcPath := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
 
 	prebuilt.unstrippedOutputFile = srcPath
 
@@ -73,3 +110,15 @@
 func (prebuilt *prebuiltLibraryDecorator) nativeCoverage() bool {
 	return false
 }
+
+func (prebuilt *prebuiltLibraryDecorator) prebuiltSrcs() []string {
+	srcs := prebuilt.Properties.Srcs
+	if prebuilt.rlib() {
+		srcs = append(srcs, prebuilt.libraryDecorator.Properties.Rlib.Srcs...)
+	}
+	if prebuilt.dylib() {
+		srcs = append(srcs, prebuilt.libraryDecorator.Properties.Dylib.Srcs...)
+	}
+
+	return srcs
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 42c8537..2719161 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -27,6 +27,7 @@
 
 type procMacroDecorator struct {
 	*baseCompiler
+	*flagExporter
 
 	Properties ProcMacroCompilerProperties
 }
@@ -35,6 +36,7 @@
 }
 
 var _ compiler = (*procMacroDecorator)(nil)
+var _ exportedFlagsProducer = (*procMacroDecorator)(nil)
 
 func ProcMacroFactory() android.Module {
 	module, _ := NewProcMacro(android.HostSupportedNoCross)
@@ -46,6 +48,7 @@
 
 	procMacro := &procMacroDecorator{
 		baseCompiler: NewBaseCompiler("lib", "lib64", InstallInSystem),
+		flagExporter: NewFlagExporter(),
 	}
 
 	module.compiler = procMacro
@@ -76,3 +79,7 @@
 
 	return stem + String(procMacro.baseCompiler.Properties.Suffix)
 }
+
+func (procMacro *procMacroDecorator) autoDep() autoDep {
+	return rlibAutoDep
+}
diff --git a/rust/rust.go b/rust/rust.go
index 7b82b1f..72301a7 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
@@ -214,6 +217,7 @@
 type Deps struct {
 	Dylibs     []string
 	Rlibs      []string
+	Rustlibs   []string
 	ProcMacros []string
 	SharedLibs []string
 	StaticLibs []string
@@ -258,6 +262,43 @@
 	nativeCoverage() bool
 }
 
+type exportedFlagsProducer interface {
+	exportedLinkDirs() []string
+	exportedDepFlags() []string
+	exportLinkDirs(...string)
+	exportDepFlags(...string)
+}
+
+type flagExporter struct {
+	depFlags []string
+	linkDirs []string
+}
+
+func (flagExporter *flagExporter) exportedLinkDirs() []string {
+	return flagExporter.linkDirs
+}
+
+func (flagExporter *flagExporter) exportedDepFlags() []string {
+	return flagExporter.depFlags
+}
+
+func (flagExporter *flagExporter) exportLinkDirs(dirs ...string) {
+	flagExporter.linkDirs = android.FirstUniqueStrings(append(flagExporter.linkDirs, dirs...))
+}
+
+func (flagExporter *flagExporter) exportDepFlags(flags ...string) {
+	flagExporter.depFlags = android.FirstUniqueStrings(append(flagExporter.depFlags, flags...))
+}
+
+var _ exportedFlagsProducer = (*flagExporter)(nil)
+
+func NewFlagExporter() *flagExporter {
+	return &flagExporter{
+		depFlags: []string{},
+		linkDirs: []string{},
+	}
+}
+
 func (mod *Module) isCoverageVariant() bool {
 	return mod.coverage.Properties.IsCoverageVariant
 }
@@ -306,6 +347,7 @@
 		&PrebuiltProperties{},
 		&TestProperties{},
 		&cc.CoverageProperties{},
+		&ClippyProperties{},
 	)
 
 	android.InitDefaultsModule(module)
@@ -411,7 +453,7 @@
 func (mod *Module) StubsVersions() []string {
 	// For now, Rust has no stubs versions.
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(*libraryDecorator); ok {
+		if _, ok := mod.compiler.(libraryInterface); ok {
 			return []string{}
 		}
 	}
@@ -435,6 +477,9 @@
 
 func (mod *Module) CoverageFiles() android.Paths {
 	if mod.compiler != nil {
+		if !mod.compiler.nativeCoverage() {
+			return android.Paths{}
+		}
 		if library, ok := mod.compiler.(*libraryDecorator); ok {
 			if library.coverageFile != nil {
 				return android.Paths{library.coverageFile}
@@ -456,6 +501,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 +535,7 @@
 func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	module := newBaseModule(hod, multilib)
 	module.coverage = &coverage{}
+	module.clippy = &clippy{}
 	return module
 }
 
@@ -506,39 +555,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 +612,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 +632,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)
@@ -599,6 +658,7 @@
 
 	deps.Rlibs = android.LastUniqueStrings(deps.Rlibs)
 	deps.Dylibs = android.LastUniqueStrings(deps.Dylibs)
+	deps.Rustlibs = android.LastUniqueStrings(deps.Rustlibs)
 	deps.ProcMacros = android.LastUniqueStrings(deps.ProcMacros)
 	deps.SharedLibs = android.LastUniqueStrings(deps.SharedLibs)
 	deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs)
@@ -607,14 +667,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
@@ -629,6 +681,20 @@
 	testPerSrcDepTag = dependencyTag{name: "rust_unit_tests"}
 )
 
+type autoDep struct {
+	variation string
+	depTag    dependencyTag
+}
+
+var (
+	rlibAutoDep  = autoDep{variation: "rlib", depTag: rlibDepTag}
+	dylibAutoDep = autoDep{variation: "dylib", depTag: dylibDepTag}
+)
+
+type autoDeppable interface {
+	autoDep() autoDep
+}
+
 func (mod *Module) begin(ctx BaseModuleContext) {
 	if mod.coverage != nil {
 		mod.coverage.begin(ctx)
@@ -679,19 +745,15 @@
 			}
 
 			//Append the dependencies exportedDirs
-			if lib, ok := rustDep.compiler.(*libraryDecorator); ok {
-				depPaths.linkDirs = append(depPaths.linkDirs, lib.exportedDirs()...)
+			if lib, ok := rustDep.compiler.(exportedFlagsProducer); ok {
+				depPaths.linkDirs = append(depPaths.linkDirs, lib.exportedLinkDirs()...)
 				depPaths.depFlags = append(depPaths.depFlags, lib.exportedDepFlags()...)
 			}
 
-			// Append this dependencies output to this mod's linkDirs so they can be exported to dependencies
-			// This can be probably be refactored by defining a common exporter interface similar to cc's
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
 				linkDir := linkPathFromFilePath(linkFile.Path())
-				if lib, ok := mod.compiler.(*libraryDecorator); ok {
-					lib.linkDirs = append(lib.linkDirs, linkDir)
-				} else if procMacro, ok := mod.compiler.(*procMacroDecorator); ok {
-					procMacro.linkDirs = append(procMacro.linkDirs, linkDir)
+				if lib, ok := mod.compiler.(exportedFlagsProducer); ok {
+					lib.exportLinkDirs(linkDir)
 				}
 			}
 
@@ -742,14 +804,10 @@
 			}
 
 			// Make sure these dependencies are propagated
-			if lib, ok := mod.compiler.(*libraryDecorator); ok && exportDep {
-				lib.linkDirs = append(lib.linkDirs, linkPath)
-				lib.depFlags = append(lib.depFlags, depFlag)
-			} else if procMacro, ok := mod.compiler.(*procMacroDecorator); ok && exportDep {
-				procMacro.linkDirs = append(procMacro.linkDirs, linkPath)
-				procMacro.depFlags = append(procMacro.depFlags, depFlag)
+			if lib, ok := mod.compiler.(exportedFlagsProducer); ok && exportDep {
+				lib.exportLinkDirs(linkPath)
+				lib.exportDepFlags(depFlag)
 			}
-
 		}
 	})
 
@@ -811,11 +869,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{}
@@ -838,6 +892,15 @@
 			{Mutator: "link", Variation: ""}}...),
 		dylibDepTag, deps.Dylibs...)
 
+	if deps.Rustlibs != nil {
+		autoDep := mod.compiler.(autoDeppable).autoDep()
+		actx.AddVariationDependencies(
+			append(commonDepVariations, []blueprint.Variation{
+				{Mutator: "rust_libraries", Variation: autoDep.variation},
+				{Mutator: "link", Variation: ""}}...),
+			autoDep.depTag, deps.Rustlibs...)
+	}
+
 	actx.AddVariationDependencies(append(commonDepVariations,
 		blueprint.Variation{Mutator: "link", Variation: "shared"}),
 		cc.SharedDepTag, deps.SharedLibs...)
@@ -862,19 +925,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 +943,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 280c22a..08bc8ca 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -164,12 +164,12 @@
 // Test to make sure dependencies are being picked up correctly.
 func TestDepsTracking(t *testing.T) {
 	ctx := testRust(t, `
-		rust_library_host_static {
+		rust_ffi_host_static {
 			name: "libstatic",
 			srcs: ["foo.rs"],
 			crate_name: "static",
 		}
-		rust_library_host_shared {
+		rust_ffi_host_shared {
 			name: "libshared",
 			srcs: ["foo.rs"],
 			crate_name: "shared",
diff --git a/rust/test.go b/rust/test.go
index 416c557..e27a70c 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -105,6 +105,10 @@
 	return flags
 }
 
+func (test *testDecorator) autoDep() autoDep {
+	return rlibAutoDep
+}
+
 func init() {
 	// Rust tests are binary files built with --test.
 	android.RegisterModuleType("rust_test", RustTestFactory)
diff --git a/rust/testing.go b/rust/testing.go
index 4e186d3..3d583e1 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -21,14 +21,26 @@
 
 func GatherRequiredDepsForTest() string {
 	bp := `
-		rust_prebuilt_dylib {
+		rust_prebuilt_library {
 				name: "libstd_x86_64-unknown-linux-gnu",
-				srcs: [""],
+                                crate_name: "std",
+                                rlib: {
+                                    srcs: ["libstd.rlib"],
+                                },
+                                dylib: {
+                                    srcs: ["libstd.so"],
+                                },
 				host_supported: true,
 		}
-		rust_prebuilt_dylib {
+		rust_prebuilt_library {
 				name: "libtest_x86_64-unknown-linux-gnu",
-				srcs: [""],
+                                crate_name: "test",
+                                rlib: {
+                                    srcs: ["libtest.rlib"],
+                                },
+                                dylib: {
+                                    srcs: ["libtest.so"],
+                                },
 				host_supported: true,
 		}
 
@@ -41,34 +53,21 @@
 			nocrt: true,
 			system_shared_libs: [],
 		}
-		rust_library_dylib {
+		rust_library {
 			name: "libstd",
 			crate_name: "std",
 			srcs: ["foo.rs"],
 			no_stdlibs: true,
 			host_supported: true,
+                        native_coverage: false,
 		}
-		rust_library_rlib {
-			name: "libstd.static",
-			crate_name: "std",
-			srcs: ["foo.rs"],
-			no_stdlibs: true,
-			host_supported: true,
-		}
-		rust_library_dylib {
+		rust_library {
 			name: "libtest",
 			crate_name: "test",
 			srcs: ["foo.rs"],
 			no_stdlibs: true,
 			host_supported: true,
-
-		}
-		rust_library_rlib {
-			name: "libtest.static",
-			crate_name: "test",
-			srcs: ["foo.rs"],
-			no_stdlibs: true,
-			host_supported: true,
+                        native_coverage: false,
 		}
 
 ` + cc.GatherRequiredDepsForTest(android.NoOsType)
@@ -83,17 +82,21 @@
 	ctx.RegisterModuleType("rust_test", RustTestFactory)
 	ctx.RegisterModuleType("rust_test_host", RustTestHostFactory)
 	ctx.RegisterModuleType("rust_library", RustLibraryFactory)
-	ctx.RegisterModuleType("rust_library_host", RustLibraryHostFactory)
-	ctx.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory)
-	ctx.RegisterModuleType("rust_library_host_dylib", RustLibraryDylibHostFactory)
-	ctx.RegisterModuleType("rust_library_rlib", RustLibraryRlibFactory)
 	ctx.RegisterModuleType("rust_library_dylib", RustLibraryDylibFactory)
-	ctx.RegisterModuleType("rust_library_shared", RustLibrarySharedFactory)
-	ctx.RegisterModuleType("rust_library_static", RustLibraryStaticFactory)
-	ctx.RegisterModuleType("rust_library_host_shared", RustLibrarySharedHostFactory)
-	ctx.RegisterModuleType("rust_library_host_static", RustLibraryStaticHostFactory)
+	ctx.RegisterModuleType("rust_library_rlib", RustLibraryRlibFactory)
+	ctx.RegisterModuleType("rust_library_host", RustLibraryHostFactory)
+	ctx.RegisterModuleType("rust_library_host_dylib", RustLibraryDylibHostFactory)
+	ctx.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory)
+	ctx.RegisterModuleType("rust_ffi", RustFFIFactory)
+	ctx.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
+	ctx.RegisterModuleType("rust_ffi_static", RustFFIStaticFactory)
+	ctx.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
+	ctx.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
+	ctx.RegisterModuleType("rust_ffi_host_static", RustFFIStaticHostFactory)
 	ctx.RegisterModuleType("rust_proc_macro", ProcMacroFactory)
+	ctx.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	ctx.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
+	ctx.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
 	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		// rust mutators
 		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
diff --git a/scripts/Android.bp b/scripts/Android.bp
index ac2f42f..7782c68 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -106,7 +106,7 @@
         py3: {
             enabled: false,
         },
-    }
+    },
 }
 
 python_binary_host {
@@ -192,5 +192,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/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/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 38c57ca..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):
   """
@@ -91,74 +93,6 @@
   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 ""
 
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/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",
     ],
 }
-