Merge "Define __ANDROID_VENDOR__ and __ANDROID_PRODUCT__"
diff --git a/android/Android.bp b/android/Android.bp
index 588ad5e..9f6ae02 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -12,6 +12,7 @@
         "soong",
         "soong-android-soongconfig",
         "soong-bazel",
+        "soong-cquery",
         "soong-shared",
         "soong-ui-metrics_proto",
     ],
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 2f568fb..230b1ec 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -141,21 +141,17 @@
 // bp module and then returns the config and the custom module called "foo".
 func buildContextAndCustomModuleFoo(t *testing.T, bp string) (*TestContext, *customModule) {
 	t.Helper()
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.katiEnabled = true // Enable androidmk Singleton
+	result := emptyTestFixtureFactory.RunTest(t,
+		// Enable androidmk Singleton
+		PrepareForTestWithAndroidMk,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("custom", customModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
-	ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
-	ctx.RegisterModuleType("custom", customModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	module := ctx.ModuleForTests("foo", "").Module().(*customModule)
-	return ctx, module
+	module := result.ModuleForTests("foo", "").Module().(*customModule)
+	return result.TestContext, module
 }
 
 func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
diff --git a/android/apex.go b/android/apex.go
index a014592..0d5cac8 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -257,6 +257,9 @@
 }
 
 // Marker interface that identifies dependencies that are excluded from APEX contents.
+//
+// Unless the tag also implements the AlwaysRequireApexVariantTag this will prevent an apex variant
+// from being created for the module.
 type ExcludeFromApexContentsTag interface {
 	blueprint.DependencyTag
 
@@ -264,6 +267,17 @@
 	ExcludeFromApexContents()
 }
 
+// Marker interface that identifies dependencies that always requires an APEX variant to be created.
+//
+// It is possible for a dependency to require an apex variant but exclude the module from the APEX
+// contents. See sdk.sdkMemberDependencyTag.
+type AlwaysRequireApexVariantTag interface {
+	blueprint.DependencyTag
+
+	// Return true if this tag requires that the target dependency has an apex variant.
+	AlwaysRequireApexVariant() bool
+}
+
 // Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
 // from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
 // their implementation is in an apex.
@@ -719,6 +733,8 @@
 // Generate two module out files:
 // 1. FullList with transitive deps and their parents in the dep graph
 // 2. FlatList with a flat list of transitive deps
+// In both cases transitive deps of external deps are not included. Neither are deps that are only
+// available to APEXes; they are developed with updatability in mind and don't need manual approval.
 func (d *ApexBundleDepsInfo) BuildDepsInfoLists(ctx ModuleContext, minSdkVersion string, depInfos DepNameToDepInfoMap) {
 	var fullContent strings.Builder
 	var flatContent strings.Builder
diff --git a/android/apex_test.go b/android/apex_test.go
index 1f786e6..b5323e8 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -121,7 +121,7 @@
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			config := TestConfig(buildDir, nil, "", nil)
+			config := TestConfig(t.TempDir(), nil, "", nil)
 			ctx := &configErrorWrapper{config: config}
 			gotMerged, gotAliases := mergeApexVariations(ctx, tt.in)
 			if !reflect.DeepEqual(gotMerged, tt.wantMerged) {
diff --git a/android/arch_test.go b/android/arch_test.go
index 4cef4c8..09cb523 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -273,6 +273,13 @@
 	return m
 }
 
+var prepareForArchTest = GroupFixturePreparers(
+	PrepareForTestWithArchMutator,
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("module", archTestModuleFactory)
+	}),
+)
+
 func TestArchMutator(t *testing.T) {
 	var buildOSVariants []string
 	var buildOS32Variants []string
@@ -309,7 +316,7 @@
 
 	testCases := []struct {
 		name        string
-		config      func(Config)
+		preparer    FixturePreparer
 		fooVariants []string
 		barVariants []string
 		bazVariants []string
@@ -317,7 +324,7 @@
 	}{
 		{
 			name:        "normal",
-			config:      nil,
+			preparer:    nil,
 			fooVariants: []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			barVariants: append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
 			bazVariants: nil,
@@ -325,11 +332,11 @@
 		},
 		{
 			name: "host-only",
-			config: func(config Config) {
+			preparer: FixtureModifyConfig(func(config Config) {
 				config.BuildOSTarget = Target{}
 				config.BuildOSCommonTarget = Target{}
 				config.Targets[Android] = nil
-			},
+			}),
 			fooVariants: nil,
 			barVariants: buildOSVariants,
 			bazVariants: nil,
@@ -351,19 +358,13 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.name, func(t *testing.T) {
-			config := TestArchConfig(buildDir, nil, bp, nil)
-
-			ctx := NewTestArchContext(config)
-			ctx.RegisterModuleType("module", archTestModuleFactory)
-			ctx.Register()
-			if tt.config != nil {
-				tt.config(config)
-			}
-
-			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
+			result := emptyTestFixtureFactory.RunTest(t,
+				prepareForArchTest,
+				// Test specific preparer
+				OptionalFixturePreparer(tt.preparer),
+				FixtureWithRootAndroidBp(bp),
+			)
+			ctx := result.TestContext
 
 			if g, w := enabledVariants(ctx, "foo"), tt.fooVariants; !reflect.DeepEqual(w, g) {
 				t.Errorf("want foo variants:\n%q\ngot:\n%q\n", w, g)
@@ -412,14 +413,14 @@
 
 	testCases := []struct {
 		name        string
-		config      func(Config)
+		preparer    FixturePreparer
 		fooVariants []string
 		barVariants []string
 		bazVariants []string
 	}{
 		{
 			name:        "normal",
-			config:      nil,
+			preparer:    nil,
 			fooVariants: []string{"android_x86_64_silvermont", "android_x86_silvermont"},
 			barVariants: []string{"android_x86_64_silvermont", "android_native_bridge_arm64_armv8-a", "android_x86_silvermont", "android_native_bridge_arm_armv7-a-neon"},
 			bazVariants: []string{"android_native_bridge_arm64_armv8-a", "android_native_bridge_arm_armv7-a-neon"},
@@ -440,19 +441,23 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.name, func(t *testing.T) {
-			config := TestArchConfigNativeBridge(buildDir, nil, bp, nil)
+			result := emptyTestFixtureFactory.RunTest(t,
+				prepareForArchTest,
+				// Test specific preparer
+				OptionalFixturePreparer(tt.preparer),
+				// Prepare for native bridge test
+				FixtureModifyConfig(func(config Config) {
+					config.Targets[Android] = []Target{
+						{Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
+						{Android, Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", "", false},
+						{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled, "x86_64", "arm64", false},
+						{Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled, "x86", "arm", false},
+					}
+				}),
+				FixtureWithRootAndroidBp(bp),
+			)
 
-			ctx := NewTestArchContext(config)
-			ctx.RegisterModuleType("module", archTestModuleFactory)
-			ctx.Register()
-			if tt.config != nil {
-				tt.config(config)
-			}
-
-			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
+			ctx := result.TestContext
 
 			if g, w := enabledVariants(ctx, "foo"), tt.fooVariants; !reflect.DeepEqual(w, g) {
 				t.Errorf("want foo variants:\n%q\ngot:\n%q\n", w, g)
diff --git a/android/bazel.go b/android/bazel.go
index 9939bd5..683495b 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -14,19 +14,49 @@
 
 package android
 
-import "android/soong/bazel"
+import (
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type bazelModuleProperties struct {
+	// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
+	// will import the handcrafted build target into the autogenerated file. Note: this may result in
+	// a conflict due to duplicate targets if bp2build_available is also set.
+	Label *string
+
+	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
+	// cause a conflict due to the duplicate targets if label is also set.
+	Bp2build_available bool
+}
+
+// Properties contains common module properties for Bazel migration purposes.
+type properties struct {
+	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
+	// this Soong module.
+	Bazel_module bazelModuleProperties
+}
 
 // BazelModuleBase contains the property structs with metadata for modules which can be converted to
 // Bazel.
 type BazelModuleBase struct {
-	bazelProperties bazel.Properties
+	bazelProperties properties
 }
 
 // Bazelable is specifies the interface for modules that can be converted to Bazel.
 type Bazelable interface {
-	bazelProps() *bazel.Properties
-	GetBazelLabel() string
+	bazelProps() *properties
+	HasHandcraftedLabel() bool
+	HandcraftedLabel() string
+	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
 	ConvertWithBp2build() bool
+	GetBazelBuildFileContents(c Config, path, name string) (string, error)
+	ConvertedToBazel() bool
 }
 
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@@ -42,16 +72,60 @@
 }
 
 // bazelProps returns the Bazel properties for the given BazelModuleBase.
-func (b *BazelModuleBase) bazelProps() *bazel.Properties {
+func (b *BazelModuleBase) bazelProps() *properties {
 	return &b.bazelProperties
 }
 
+// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
+func (b *BazelModuleBase) HasHandcraftedLabel() bool {
+	return b.bazelProperties.Bazel_module.Label != nil
+}
+
+// HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none
+func (b *BazelModuleBase) HandcraftedLabel() string {
+	return proptools.String(b.bazelProperties.Bazel_module.Label)
+}
+
 // GetBazelLabel returns the Bazel label for the given BazelModuleBase.
-func (b *BazelModuleBase) GetBazelLabel() string {
-	return b.bazelProperties.Bazel_module.Label
+func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
+	if b.HasHandcraftedLabel() {
+		return b.HandcraftedLabel()
+	}
+	if b.ConvertWithBp2build() {
+		return bp2buildModuleLabel(ctx, module)
+	}
+	return "" // no label for unconverted module
 }
 
 // ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
 func (b *BazelModuleBase) ConvertWithBp2build() bool {
 	return b.bazelProperties.Bazel_module.Bp2build_available
 }
+
+// GetBazelBuildFileContents returns the file contents of a hand-crafted BUILD file if available or
+// an error if there are errors reading the file.
+// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
+// something more targeted based on the rule type and target.
+func (b *BazelModuleBase) GetBazelBuildFileContents(c Config, path, name string) (string, error) {
+	if !strings.Contains(b.HandcraftedLabel(), path) {
+		return "", fmt.Errorf("%q not found in bazel_module.label %q", path, b.HandcraftedLabel())
+	}
+	name = filepath.Join(path, name)
+	f, err := c.fs.Open(name)
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return "", err
+	}
+	return string(data[:]), nil
+}
+
+// ConvertedToBazel returns whether this module has been converted to Bazel, whether automatically
+// or manually
+func (b *BazelModuleBase) ConvertedToBazel() bool {
+	return b.ConvertWithBp2build() || b.HasHandcraftedLabel()
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 6675840..0595d68 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -26,6 +26,7 @@
 	"strings"
 	"sync"
 
+	"android/soong/bazel/cquery"
 	"github.com/google/blueprint/bootstrap"
 
 	"android/soong/bazel"
@@ -43,7 +44,7 @@
 // Map key to describe bazel cquery requests.
 type cqueryKey struct {
 	label       string
-	requestType CqueryRequestType
+	requestType cquery.RequestType
 	archType    ArchType
 }
 
@@ -53,14 +54,15 @@
 	// has been queued to be run later.
 
 	// Returns result files built by building the given bazel target label.
-	GetAllFiles(label string, archType ArchType) ([]string, bool)
+	GetOutputFiles(label string, archType ArchType) ([]string, bool)
 
 	// Returns object files produced by compiling the given cc-related target.
 	// Retrieves these files from Bazel's CcInfo provider.
 	GetCcObjectFiles(label string, archType ArchType) ([]string, bool)
 
-	// Returns the results of GetAllFiles and GetCcObjectFiles in a single query (in that order).
-	GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool)
+	// TODO(cparsons): Other cquery-related methods should be added here.
+	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
+	GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool)
 
 	// ** End cquery methods
 
@@ -109,7 +111,7 @@
 	AllFiles map[string][]string
 }
 
-func (m MockBazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
+func (m MockBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
 	result, ok := m.AllFiles[label]
 	return result, ok
 }
@@ -119,7 +121,7 @@
 	return result, ok
 }
 
-func (m MockBazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+func (m MockBazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
 	result, ok := m.AllFiles[label]
 	return result, result, ok
 }
@@ -142,43 +144,42 @@
 
 var _ BazelContext = MockBazelContext{}
 
-func (bazelCtx *bazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
-	result, ok := bazelCtx.cquery(label, getAllFiles, archType)
+func (bazelCtx *bazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
+	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, archType)
+	var ret []string
 	if ok {
-		bazelOutput := strings.TrimSpace(result)
-		return strings.Split(bazelOutput, ", "), true
-	} else {
-		return nil, false
+		bazelOutput := strings.TrimSpace(rawString)
+		ret = cquery.GetOutputFiles.ParseResult(bazelOutput).([]string)
 	}
+	return ret, ok
 }
 
 func (bazelCtx *bazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
-	result, ok := bazelCtx.cquery(label, getCcObjectFiles, archType)
+	rawString, ok := bazelCtx.cquery(label, cquery.GetCcObjectFiles, archType)
+	var returnResult []string
 	if ok {
-		bazelOutput := strings.TrimSpace(result)
-		return strings.Split(bazelOutput, ", "), true
-	} else {
-		return nil, false
+		bazelOutput := strings.TrimSpace(rawString)
+		returnResult = cquery.GetCcObjectFiles.ParseResult(bazelOutput).([]string)
 	}
+	return returnResult, ok
 }
 
-func (bazelCtx *bazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
-	var allFiles []string
+func (bazelCtx *bazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+	var outputFiles []string
 	var ccObjects []string
 
-	result, ok := bazelCtx.cquery(label, getAllFilesAndCcObjectFiles, archType)
+	result, ok := bazelCtx.cquery(label, cquery.GetOutputFilesAndCcObjectFiles, archType)
 	if ok {
 		bazelOutput := strings.TrimSpace(result)
-		splitString := strings.Split(bazelOutput, "|")
-		allFilesString := splitString[0]
-		ccObjectsString := splitString[1]
-		allFiles = strings.Split(allFilesString, ", ")
-		ccObjects = strings.Split(ccObjectsString, ", ")
+		returnResult := cquery.GetOutputFilesAndCcObjectFiles.ParseResult(bazelOutput).(cquery.GetOutputFilesAndCcObjectFiles_Result)
+		outputFiles = returnResult.OutputFiles
+		ccObjects = returnResult.CcObjectFiles
 	}
-	return allFiles, ccObjects, ok
+
+	return outputFiles, ccObjects, ok
 }
 
-func (n noopBazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
+func (n noopBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
 	panic("unimplemented")
 }
 
@@ -186,7 +187,7 @@
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetAllFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
+func (n noopBazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
 	panic("unimplemented")
 }
 
@@ -260,7 +261,7 @@
 // If the given request was already made (and the results are available), then
 // returns (result, true). If the request is queued but no results are available,
 // then returns ("", false).
-func (context *bazelContext) cquery(label string, requestType CqueryRequestType,
+func (context *bazelContext) cquery(label string, requestType cquery.RequestType,
 	archType ArchType) (string, bool) {
 	key := cqueryKey{label, requestType, archType}
 	if result, ok := context.results[key]; ok {
@@ -485,38 +486,66 @@
 		strings.Join(deps_arm, ",\n            ")))
 }
 
+func indent(original string) string {
+	result := ""
+	for _, line := range strings.Split(original, "\n") {
+		result += "  " + line + "\n"
+	}
+	return result
+}
+
 // Returns the file contents of the buildroot.cquery file that should be used for the cquery
 // expression in order to obtain information about buildroot and its dependencies.
 // The contents of this file depend on the bazelContext's requests; requests are enumerated
 // and grouped by their request type. The data retrieved for each label depends on its
 // request type.
 func (context *bazelContext) cqueryStarlarkFileContents() []byte {
+	requestTypeToCqueryIdEntries := map[cquery.RequestType][]string{}
+	for val, _ := range context.requests {
+		cqueryId := getCqueryId(val)
+		mapEntryString := fmt.Sprintf("%q : True", cqueryId)
+		requestTypeToCqueryIdEntries[val.requestType] =
+			append(requestTypeToCqueryIdEntries[val.requestType], mapEntryString)
+	}
+	labelRegistrationMapSection := ""
+	functionDefSection := ""
+	mainSwitchSection := ""
+
+	mapDeclarationFormatString := `
+%s = {
+  %s
+}
+`
+	functionDefFormatString := `
+def %s(target):
+%s
+`
+	mainSwitchSectionFormatString := `
+  if id_string in %s:
+    return id_string + ">>" + %s(target)
+`
+
+	for _, requestType := range cquery.RequestTypes {
+		labelMapName := requestType.Name() + "_Labels"
+		functionName := requestType.Name() + "_Fn"
+		labelRegistrationMapSection += fmt.Sprintf(mapDeclarationFormatString,
+			labelMapName,
+			strings.Join(requestTypeToCqueryIdEntries[requestType], ",\n  "))
+		functionDefSection += fmt.Sprintf(functionDefFormatString,
+			functionName,
+			indent(requestType.StarlarkFunctionBody()))
+		mainSwitchSection += fmt.Sprintf(mainSwitchSectionFormatString,
+			labelMapName, functionName)
+	}
+
 	formatString := `
 # This file is generated by soong_build. Do not edit.
-getAllFilesLabels = {
-  %s
-}
 
-getCcObjectFilesLabels = {
-  %s
-}
+# Label Map Section
+%s
 
-getAllFilesAndCcObjectFilesLabels = {
-  %s
-}
-
-def get_all_files(target):
-  return [f.path for f in target.files.to_list()]
-
-def get_cc_object_files(target):
-  result = []
-  linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
-
-  for linker_input in linker_inputs:
-    for library in linker_input.libraries:
-      for object in library.objects:
-        result += [object.path]
-  return result
+# Function Def Section
+%s
 
 def get_arch(target):
   buildoptions = build_options(target)
@@ -536,39 +565,16 @@
 
 def format(target):
   id_string = str(target.label) + "|" + get_arch(target)
-  if id_string in getAllFilesLabels:
-    return id_string + ">>" + ', '.join(get_all_files(target))
-  elif id_string in getCcObjectFilesLabels:
-    return id_string + ">>" + ', '.join(get_cc_object_files(target))
-  elif id_string in getAllFilesAndCcObjectFilesLabels:
-    return id_string + ">>" + ', '.join(get_all_files(target)) + "|" + ', '.join(get_cc_object_files(target))
-  else:
-    # This target was not requested via cquery, and thus must be a dependency
-    # of a requested target.
-    return id_string + ">>NONE"
+
+  # Main switch section
+  %s
+  # This target was not requested via cquery, and thus must be a dependency
+  # of a requested target.
+  return id_string + ">>NONE"
 `
-	var getAllFilesDeps []string = nil
-	var getCcObjectFilesDeps []string = nil
-	var getAllFilesAndCcObjectFilesDeps []string = nil
 
-	for val, _ := range context.requests {
-		labelWithArch := getCqueryId(val)
-		mapEntryString := fmt.Sprintf("%q : True", labelWithArch)
-		switch val.requestType {
-		case getAllFiles:
-			getAllFilesDeps = append(getAllFilesDeps, mapEntryString)
-		case getCcObjectFiles:
-			getCcObjectFilesDeps = append(getCcObjectFilesDeps, mapEntryString)
-		case getAllFilesAndCcObjectFiles:
-			getAllFilesAndCcObjectFilesDeps = append(getAllFilesAndCcObjectFilesDeps, mapEntryString)
-		}
-	}
-	getAllFilesDepsString := strings.Join(getAllFilesDeps, ",\n  ")
-	getCcObjectFilesDepsString := strings.Join(getCcObjectFilesDeps, ",\n  ")
-	getAllFilesAndCcObjectFilesDepsString := strings.Join(getAllFilesAndCcObjectFilesDeps, ",\n  ")
-
-	return []byte(fmt.Sprintf(formatString, getAllFilesDepsString, getCcObjectFilesDepsString,
-		getAllFilesAndCcObjectFilesDepsString))
+	return []byte(fmt.Sprintf(formatString, labelRegistrationMapSection, functionDefSection,
+		mainSwitchSection))
 }
 
 // Returns a workspace-relative path containing build-related metadata required
@@ -711,7 +717,7 @@
 
 	// Add ninja file dependencies for files which all bazel invocations require.
 	bazelBuildList := absolutePath(filepath.Join(
-		filepath.Dir(bootstrap.ModuleListFile), "bazel.list"))
+		filepath.Dir(bootstrap.CmdlineModuleListFile()), "bazel.list"))
 	ctx.AddNinjaFileDeps(bazelBuildList)
 
 	data, err := ioutil.ReadFile(bazelBuildList)
diff --git a/android/config.go b/android/config.go
index bc1aa3a..e091731 100644
--- a/android/config.go
+++ b/android/config.go
@@ -19,6 +19,7 @@
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -68,6 +69,18 @@
 	return c.buildDir
 }
 
+func (c Config) NinjaBuildDir() string {
+	return c.buildDir
+}
+
+func (c Config) DebugCompilation() bool {
+	return false // Never compile Go code in the main build for debugging
+}
+
+func (c Config) SrcDir() string {
+	return c.srcDir
+}
+
 // A DeviceConfig object represents the configuration for a particular device
 // being built. For now there will only be one of these, but in the future there
 // may be multiple devices being built.
@@ -270,23 +283,6 @@
 	return Config{config}
 }
 
-// TestArchConfigNativeBridge returns a Config object suitable for using
-// for tests that need to run the arch mutator for native bridge supported
-// archs.
-func TestArchConfigNativeBridge(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
-	testConfig := TestArchConfig(buildDir, env, bp, fs)
-	config := testConfig.config
-
-	config.Targets[Android] = []Target{
-		{Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
-		{Android, Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", "", false},
-		{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled, "x86_64", "arm64", false},
-		{Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled, "x86", "arm", false},
-	}
-
-	return testConfig
-}
-
 func fuchsiaTargets() map[OsType][]Target {
 	return map[OsType][]Target{
 		Fuchsia: {
@@ -498,6 +494,10 @@
 	c.stopBefore = stopBefore
 }
 
+func (c *config) SetAllowMissingDependencies() {
+	c.productVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
+}
+
 var _ bootstrap.ConfigStopBefore = (*config)(nil)
 
 // BlueprintToolLocation returns the directory containing build system tools
@@ -1005,7 +1005,7 @@
 }
 
 func (c *config) FrameworksBaseDirExists(ctx PathContext) bool {
-	return ExistentPathForSource(ctx, "frameworks", "base").Valid()
+	return ExistentPathForSource(ctx, "frameworks", "base", "Android.bp").Valid()
 }
 
 func (c *config) VndkSnapshotBuildArtifacts() bool {
@@ -1411,6 +1411,62 @@
 	return c.config.productVariables.RecoverySnapshotModules
 }
 
+func createDirsMap(previous map[string]bool, dirs []string) (map[string]bool, error) {
+	var ret = make(map[string]bool)
+	for _, dir := range dirs {
+		clean := filepath.Clean(dir)
+		if previous[clean] || ret[clean] {
+			return nil, fmt.Errorf("Duplicate entry %s", dir)
+		}
+		ret[clean] = true
+	}
+	return ret, nil
+}
+
+func (c *deviceConfig) createDirsMapOnce(onceKey OnceKey, previous map[string]bool, dirs []string) map[string]bool {
+	dirMap := c.Once(onceKey, func() interface{} {
+		ret, err := createDirsMap(previous, dirs)
+		if err != nil {
+			panic(fmt.Errorf("%s: %w", onceKey.key, err))
+		}
+		return ret
+	})
+	if dirMap == nil {
+		return nil
+	}
+	return dirMap.(map[string]bool)
+}
+
+var vendorSnapshotDirsExcludedKey = NewOnceKey("VendorSnapshotDirsExcludedMap")
+
+func (c *deviceConfig) VendorSnapshotDirsExcludedMap() map[string]bool {
+	return c.createDirsMapOnce(vendorSnapshotDirsExcludedKey, nil,
+		c.config.productVariables.VendorSnapshotDirsExcluded)
+}
+
+var vendorSnapshotDirsIncludedKey = NewOnceKey("VendorSnapshotDirsIncludedMap")
+
+func (c *deviceConfig) VendorSnapshotDirsIncludedMap() map[string]bool {
+	excludedMap := c.VendorSnapshotDirsExcludedMap()
+	return c.createDirsMapOnce(vendorSnapshotDirsIncludedKey, excludedMap,
+		c.config.productVariables.VendorSnapshotDirsIncluded)
+}
+
+var recoverySnapshotDirsExcludedKey = NewOnceKey("RecoverySnapshotDirsExcludedMap")
+
+func (c *deviceConfig) RecoverySnapshotDirsExcludedMap() map[string]bool {
+	return c.createDirsMapOnce(recoverySnapshotDirsExcludedKey, nil,
+		c.config.productVariables.RecoverySnapshotDirsExcluded)
+}
+
+var recoverySnapshotDirsIncludedKey = NewOnceKey("RecoverySnapshotDirsIncludedMap")
+
+func (c *deviceConfig) RecoverySnapshotDirsIncludedMap() map[string]bool {
+	excludedMap := c.RecoverySnapshotDirsExcludedMap()
+	return c.createDirsMapOnce(recoverySnapshotDirsIncludedKey, excludedMap,
+		c.config.productVariables.RecoverySnapshotDirsIncluded)
+}
+
 func (c *deviceConfig) ShippingApiLevel() ApiLevel {
 	if c.config.productVariables.ShippingApiLevel == nil {
 		return NoneApiLevel
@@ -1567,6 +1623,20 @@
 	return nil
 }
 
+func (l *ConfiguredJarList) MarshalJSON() ([]byte, error) {
+	if len(l.apexes) != len(l.jars) {
+		return nil, errors.New(fmt.Sprintf("Inconsistent ConfiguredJarList: apexes: %q, jars: %q", l.apexes, l.jars))
+	}
+
+	list := make([]string, 0, len(l.apexes))
+
+	for i := 0; i < len(l.apexes); i++ {
+		list = append(list, l.apexes[i]+":"+l.jars[i])
+	}
+
+	return json.Marshal(list)
+}
+
 // ModuleStem hardcodes the stem of framework-minus-apex to return "framework".
 //
 // TODO(b/139391334): hard coded until we find a good way to query the stem of a
diff --git a/android/config_test.go b/android/config_test.go
index a11115d..9df5288 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"reflect"
 	"strings"
 	"testing"
@@ -87,6 +88,37 @@
 	}
 }
 
+func verifyProductVariableMarshaling(t *testing.T, v productVariables) {
+	dir := t.TempDir()
+	path := filepath.Join(dir, "test.variables")
+	err := saveToConfigFile(&v, path)
+	if err != nil {
+		t.Errorf("Couldn't save default product config: %q", err)
+	}
+
+	var v2 productVariables
+	err = loadFromConfigFile(&v2, path)
+	if err != nil {
+		t.Errorf("Couldn't load default product config: %q", err)
+	}
+}
+func TestDefaultProductVariableMarshaling(t *testing.T) {
+	v := productVariables{}
+	v.SetDefaultConfig()
+	verifyProductVariableMarshaling(t, v)
+}
+
+func TestBootJarsMarshaling(t *testing.T) {
+	v := productVariables{}
+	v.SetDefaultConfig()
+	v.BootJars = ConfiguredJarList{
+		apexes: []string{"apex"},
+		jars:   []string{"jar"},
+	}
+
+	verifyProductVariableMarshaling(t, v)
+}
+
 func assertStringEquals(t *testing.T, expected, actual string) {
 	if actual != expected {
 		t.Errorf("expected %q found %q", expected, actual)
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 56d2408..20bd035 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -15,7 +15,11 @@
 package android
 
 func init() {
-	RegisterModuleType("csuite_config", CSuiteConfigFactory)
+	registerCSuiteBuildComponents(InitRegistrationContext)
+}
+
+func registerCSuiteBuildComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("csuite_config", CSuiteConfigFactory)
 }
 
 type csuiteConfigProperties struct {
diff --git a/android/csuite_config_test.go b/android/csuite_config_test.go
index 9ac959e..d30ff69 100644
--- a/android/csuite_config_test.go
+++ b/android/csuite_config_test.go
@@ -18,32 +18,21 @@
 	"testing"
 )
 
-func testCSuiteConfig(test *testing.T, bpFileContents string) *TestContext {
-	config := TestArchConfig(buildDir, nil, bpFileContents, nil)
-
-	ctx := NewTestArchContext(config)
-	ctx.RegisterModuleType("csuite_config", CSuiteConfigFactory)
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(test, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(test, errs)
-	return ctx
-}
-
 func TestCSuiteConfig(t *testing.T) {
-	ctx := testCSuiteConfig(t, `
-csuite_config { name: "plain"}
-csuite_config { name: "with_manifest", test_config: "manifest.xml" }
-`)
+	result := emptyTestFixtureFactory.RunTest(t,
+		PrepareForTestWithArchMutator,
+		FixtureRegisterWithContext(registerCSuiteBuildComponents),
+		FixtureWithRootAndroidBp(`
+			csuite_config { name: "plain"}
+			csuite_config { name: "with_manifest", test_config: "manifest.xml" }
+		`),
+	)
 
-	variants := ctx.ModuleVariantsForTests("plain")
+	variants := result.ModuleVariantsForTests("plain")
 	if len(variants) > 1 {
 		t.Errorf("expected 1, got %d", len(variants))
 	}
-	expectedOutputFilename := ctx.ModuleForTests(
+	outputFilename := result.ModuleForTests(
 		"plain", variants[0]).Module().(*CSuiteConfig).OutputFilePath.Base()
-	if expectedOutputFilename != "plain" {
-		t.Errorf("expected plain, got %q", expectedOutputFilename)
-	}
+	AssertStringEquals(t, "output file name", "plain", outputFilename)
 }
diff --git a/android/defaults_test.go b/android/defaults_test.go
index 2689d86..b33cb52 100644
--- a/android/defaults_test.go
+++ b/android/defaults_test.go
@@ -15,10 +15,7 @@
 package android
 
 import (
-	"reflect"
 	"testing"
-
-	"github.com/google/blueprint/proptools"
 )
 
 type defaultsTestProperties struct {
@@ -58,6 +55,14 @@
 	return defaults
 }
 
+var prepareForDefaultsTest = GroupFixturePreparers(
+	PrepareForTestWithDefaults,
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("test", defaultsTestModuleFactory)
+		ctx.RegisterModuleType("defaults", defaultsTestDefaultsFactory)
+	}),
+)
+
 func TestDefaults(t *testing.T) {
 	bp := `
 		defaults {
@@ -78,27 +83,14 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
+	result := emptyTestFixtureFactory.RunTest(t,
+		prepareForDefaultsTest,
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
+	foo := result.Module("foo", "").(*defaultsTestModule)
 
-	ctx.RegisterModuleType("test", defaultsTestModuleFactory)
-	ctx.RegisterModuleType("defaults", defaultsTestDefaultsFactory)
-
-	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	foo := ctx.ModuleForTests("foo", "").Module().(*defaultsTestModule)
-
-	if g, w := foo.properties.Foo, []string{"transitive", "defaults", "module"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected foo %q, got %q", w, g)
-	}
+	AssertDeepEquals(t, "foo", []string{"transitive", "defaults", "module"}, foo.properties.Foo)
 }
 
 func TestDefaultsAllowMissingDependencies(t *testing.T) {
@@ -122,34 +114,18 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
+	result := emptyTestFixtureFactory.RunTest(t,
+		prepareForDefaultsTest,
+		PrepareForTestWithAllowMissingDependencies,
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
-	ctx.SetAllowMissingDependencies(true)
+	missingDefaults := result.ModuleForTests("missing_defaults", "").Output("out")
+	missingTransitiveDefaults := result.ModuleForTests("missing_transitive_defaults", "").Output("out")
 
-	ctx.RegisterModuleType("test", defaultsTestModuleFactory)
-	ctx.RegisterModuleType("defaults", defaultsTestDefaultsFactory)
+	AssertSame(t, "missing_defaults rule", ErrorRule, missingDefaults.Rule)
 
-	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	missingDefaults := ctx.ModuleForTests("missing_defaults", "").Output("out")
-	missingTransitiveDefaults := ctx.ModuleForTests("missing_transitive_defaults", "").Output("out")
-
-	if missingDefaults.Rule != ErrorRule {
-		t.Errorf("expected missing_defaults rule to be ErrorRule, got %#v", missingDefaults.Rule)
-	}
-
-	if g, w := missingDefaults.Args["error"], "module missing_defaults missing dependencies: missing\n"; g != w {
-		t.Errorf("want error %q, got %q", w, g)
-	}
+	AssertStringEquals(t, "missing_defaults", "module missing_defaults missing dependencies: missing\n", missingDefaults.Args["error"])
 
 	// TODO: missing transitive defaults is currently not handled
 	_ = missingTransitiveDefaults
diff --git a/android/defs.go b/android/defs.go
index 1a76721..1a7c459 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -145,7 +145,7 @@
 
 func buildWriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
 	content = echoEscaper.Replace(content)
-	content = proptools.ShellEscape(content)
+	content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content))
 	if content == "" {
 		content = "''"
 	}
diff --git a/android/deptag_test.go b/android/deptag_test.go
index bdd449e..7f55896 100644
--- a/android/deptag_test.go
+++ b/android/deptag_test.go
@@ -80,21 +80,20 @@
 		}
 	`
 
-	config := TestArchConfig(buildDir, nil, bp, nil)
-	ctx := NewTestArchContext(config)
+	result := emptyTestFixtureFactory.RunTest(t,
+		PrepareForTestWithArchMutator,
+		FixtureWithRootAndroidBp(bp),
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("test_module", testInstallDependencyTagModuleFactory)
+		}),
+	)
 
-	ctx.RegisterModuleType("test_module", testInstallDependencyTagModuleFactory)
+	config := result.Config
 
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	hostFoo := ctx.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
-	hostInstallDep := ctx.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
-	hostTransitive := ctx.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
-	hostDep := ctx.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostFoo := result.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
+	hostInstallDep := result.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostTransitive := result.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
+	hostDep := result.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
 
 	if g, w := hostFoo.Implicits.Strings(), hostInstallDep.Output.String(); !InList(w, g) {
 		t.Errorf("expected host dependency %q, got %q", w, g)
@@ -112,10 +111,10 @@
 		t.Errorf("expected no host dependency %q, got %q", w, g)
 	}
 
-	deviceFoo := ctx.ModuleForTests("foo", "android_common").Description("install")
-	deviceInstallDep := ctx.ModuleForTests("install_dep", "android_common").Description("install")
-	deviceTransitive := ctx.ModuleForTests("transitive", "android_common").Description("install")
-	deviceDep := ctx.ModuleForTests("dep", "android_common").Description("install")
+	deviceFoo := result.ModuleForTests("foo", "android_common").Description("install")
+	deviceInstallDep := result.ModuleForTests("install_dep", "android_common").Description("install")
+	deviceTransitive := result.ModuleForTests("transitive", "android_common").Description("install")
+	deviceDep := result.ModuleForTests("dep", "android_common").Description("install")
 
 	if g, w := deviceFoo.OrderOnly.Strings(), deviceInstallDep.Output.String(); !InList(w, g) {
 		t.Errorf("expected device dependency %q, got %q", w, g)
diff --git a/android/fixture.go b/android/fixture.go
index d8893f7..4e445c0 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -221,7 +221,8 @@
 //
 // The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
 // the package level setUp method. It has to be a pointer to the variable as the variable will not
-// have been initialized at the time the factory is created.
+// have been initialized at the time the factory is created. If it is nil then a test specific
+// temporary directory will be created instead.
 func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
 	return &fixtureFactory{
 		buildDirSupplier: buildDirSupplier,
@@ -380,6 +381,19 @@
 	return &compositeFixturePreparer{dedupAndFlattenPreparers(nil, preparers)}
 }
 
+// NullFixturePreparer is a preparer that does nothing.
+var NullFixturePreparer = GroupFixturePreparers()
+
+// OptionalFixturePreparer will return the supplied preparer if it is non-nil, otherwise it will
+// return the NullFixturePreparer
+func OptionalFixturePreparer(preparer FixturePreparer) FixturePreparer {
+	if preparer == nil {
+		return NullFixturePreparer
+	} else {
+		return preparer
+	}
+}
+
 type simpleFixturePreparerVisitor func(preparer *simpleFixturePreparer)
 
 // FixturePreparer is an opaque interface that can change a fixture.
@@ -492,6 +506,14 @@
 	},
 )
 
+// FixtureIgnoreErrors ignores any errors.
+//
+// If this is used then it is the responsibility of the test to check the TestResult.Errs does not
+// contain any unexpected errors.
+var FixtureIgnoreErrors = FixtureCustomErrorHandler(func(t *testing.T, result *TestResult) {
+	// Ignore the errors
+})
+
 // FixtureExpectsAtLeastOneMatchingError returns an error handler that will cause the test to fail
 // if at least one error that matches the regular expression is not found.
 //
@@ -560,6 +582,10 @@
 
 	// The errors that were reported during the test.
 	Errs []error
+
+	// The ninja deps is a list of the ninja files dependencies that were added by the modules and
+	// singletons via the *.AddNinjaFileDeps() methods.
+	NinjaDeps []string
 }
 
 var _ FixtureFactory = (*fixtureFactory)(nil)
@@ -585,7 +611,16 @@
 }
 
 func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
-	config := TestConfig(*f.buildDirSupplier, nil, "", nil)
+	var buildDir string
+	if f.buildDirSupplier == nil {
+		// Create a new temporary directory for this run. It will be automatically cleaned up when the
+		// test finishes.
+		buildDir = t.TempDir()
+	} else {
+		// Retrieve the buildDir from the supplier.
+		buildDir = *f.buildDirSupplier
+	}
+	config := TestConfig(buildDir, nil, "", nil)
 	ctx := NewTestContext(config)
 	fixture := &fixture{
 		factory:      f,
@@ -691,9 +726,14 @@
 	}
 
 	ctx.Register()
-	_, errs := ctx.ParseBlueprintsFiles("ignored")
+	var ninjaDeps []string
+	extraNinjaDeps, errs := ctx.ParseBlueprintsFiles("ignored")
 	if len(errs) == 0 {
-		_, errs = ctx.PrepareBuildActions(f.config)
+		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
+		extraNinjaDeps, errs = ctx.PrepareBuildActions(f.config)
+		if len(errs) == 0 {
+			ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
+		}
 	}
 
 	result := &TestResult{
@@ -701,6 +741,7 @@
 		fixture:     f,
 		Config:      f.config,
 		Errs:        errs,
+		NinjaDeps:   ninjaDeps,
 	}
 
 	f.errorHandler.CheckErrors(f.t, result)
diff --git a/android/fixture_test.go b/android/fixture_test.go
index a31ef16..0042e5b 100644
--- a/android/fixture_test.go
+++ b/android/fixture_test.go
@@ -30,9 +30,10 @@
 	preparer1 := appendToList("preparer1")
 	preparer2 := appendToList("preparer2")
 	preparer3 := appendToList("preparer3")
-	preparer4 := appendToList("preparer4")
+	preparer4 := OptionalFixturePreparer(appendToList("preparer4"))
+	nilPreparer := OptionalFixturePreparer(nil)
 
-	preparer1Then2 := GroupFixturePreparers(preparer1, preparer2)
+	preparer1Then2 := GroupFixturePreparers(preparer1, preparer2, nilPreparer)
 
 	preparer2Then1 := GroupFixturePreparers(preparer2, preparer1)
 
diff --git a/android/module_test.go b/android/module_test.go
index e3cc613..99bf30a 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -163,6 +163,10 @@
 	return m
 }
 
+var prepareForModuleTests = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.RegisterModuleType("deps", depsModuleFactory)
+})
+
 func TestErrorDependsOnDisabledModule(t *testing.T) {
 	bp := `
 		deps {
@@ -175,20 +179,15 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-
-	ctx := NewTestContext(config)
-	ctx.RegisterModuleType("deps", depsModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfNoMatchingErrors(t, `module "foo": depends on disabled module "bar"`, errs)
+	emptyTestFixtureFactory.
+		ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": depends on disabled module "bar"`)).
+		RunTest(t,
+			prepareForModuleTests,
+			FixtureWithRootAndroidBp(bp))
 }
 
 func TestValidateCorrectBuildParams(t *testing.T) {
-	config := TestConfig(buildDir, nil, "", nil)
+	config := TestConfig(t.TempDir(), nil, "", nil)
 	pathContext := PathContextForTesting(config)
 	bparams := convertBuildParams(BuildParams{
 		// Test with Output
@@ -214,7 +213,7 @@
 }
 
 func TestValidateIncorrectBuildParams(t *testing.T) {
-	config := TestConfig(buildDir, nil, "", nil)
+	config := TestConfig(t.TempDir(), nil, "", nil)
 	pathContext := PathContextForTesting(config)
 	params := BuildParams{
 		Output:          PathForOutput(pathContext, "regular_output"),
@@ -257,16 +256,6 @@
  		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-
-	ctx := NewTestContext(config)
-	ctx.RegisterModuleType("deps", depsModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-
 	expectedErrs := []string{
 		"\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E",
 		"\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E",
@@ -278,5 +267,10 @@
 		"\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E",
 		"\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E",
 	}
-	CheckErrorsAgainstExpectations(t, errs, expectedErrs)
+
+	emptyTestFixtureFactory.
+		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)).
+		RunTest(t,
+			prepareForModuleTests,
+			FixtureWithRootAndroidBp(bp))
 }
diff --git a/android/mutator_test.go b/android/mutator_test.go
index 1c395c7..46d26d1 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -16,12 +16,10 @@
 
 import (
 	"fmt"
-	"reflect"
 	"strings"
 	"testing"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/proptools"
 )
 
 type mutatorTestModule struct {
@@ -67,28 +65,20 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
+	result := emptyTestFixtureFactory.RunTest(t,
+		PrepareForTestWithAllowMissingDependencies,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.TopDown("add_missing_dependencies", addMissingDependenciesMutator)
+			})
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
-	ctx.SetAllowMissingDependencies(true)
+	foo := result.ModuleForTests("foo", "").Module().(*mutatorTestModule)
 
-	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.TopDown("add_missing_dependencies", addMissingDependenciesMutator)
-	})
-
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	foo := ctx.ModuleForTests("foo", "").Module().(*mutatorTestModule)
-
-	if g, w := foo.missingDeps, []string{"added_missing_dep", "regular_missing_dep"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want foo missing deps %q, got %q", w, g)
-	}
+	AssertDeepEquals(t, "foo missing deps", []string{"added_missing_dep", "regular_missing_dep"}, foo.missingDeps)
 }
 
 func TestModuleString(t *testing.T) {
@@ -98,52 +88,47 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-
-	ctx := NewTestContext(config)
-
 	var moduleStrings []string
 
-	ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("pre_arch", func(ctx BottomUpMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-			ctx.CreateVariations("a", "b")
-		})
-		ctx.TopDown("rename_top_down", func(ctx TopDownMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-			ctx.Rename(ctx.Module().base().Name() + "_renamed1")
-		})
-	})
+	emptyTestFixtureFactory.RunTest(t,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
 
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("pre_deps", func(ctx BottomUpMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-			ctx.CreateVariations("c", "d")
-		})
-	})
+			ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("pre_arch", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.CreateVariations("a", "b")
+				})
+				ctx.TopDown("rename_top_down", func(ctx TopDownMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.Rename(ctx.Module().base().Name() + "_renamed1")
+				})
+			})
 
-	ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("post_deps", func(ctx BottomUpMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-			ctx.CreateLocalVariations("e", "f")
-		})
-		ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-			ctx.Rename(ctx.Module().base().Name() + "_renamed2")
-		})
-		ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
-			moduleStrings = append(moduleStrings, ctx.Module().String())
-		})
-	})
+			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("pre_deps", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.CreateVariations("c", "d")
+				})
+			})
 
-	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+			ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("post_deps", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.CreateLocalVariations("e", "f")
+				})
+				ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.Rename(ctx.Module().base().Name() + "_renamed2")
+				})
+				ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+				})
+			})
 
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
 	want := []string{
 		// Initial name.
@@ -184,9 +169,7 @@
 		"foo_renamed2{pre_arch:b,pre_deps:d,post_deps:f}",
 	}
 
-	if !reflect.DeepEqual(moduleStrings, want) {
-		t.Errorf("want module String() values:\n%q\ngot:\n%q", want, moduleStrings)
-	}
+	AssertDeepEquals(t, "module String() values", want, moduleStrings)
 }
 
 func TestFinalDepsPhase(t *testing.T) {
@@ -202,52 +185,46 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-
-	ctx := NewTestContext(config)
-
 	finalGot := map[string]int{}
 
-	dep1Tag := struct {
-		blueprint.BaseDependencyTag
-	}{}
-	dep2Tag := struct {
-		blueprint.BaseDependencyTag
-	}{}
+	emptyTestFixtureFactory.RunTest(t,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			dep1Tag := struct {
+				blueprint.BaseDependencyTag
+			}{}
+			dep2Tag := struct {
+				blueprint.BaseDependencyTag
+			}{}
 
-	ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("far_deps_1", func(ctx BottomUpMutatorContext) {
-			if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
-				ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep1Tag, "common_dep_1")
-			}
-		})
-		ctx.BottomUp("variant", func(ctx BottomUpMutatorContext) {
-			ctx.CreateLocalVariations("a", "b")
-		})
-	})
-
-	ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("far_deps_2", func(ctx BottomUpMutatorContext) {
-			if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
-				ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep2Tag, "common_dep_2")
-			}
-		})
-		ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
-			finalGot[ctx.Module().String()] += 1
-			ctx.VisitDirectDeps(func(mod Module) {
-				finalGot[fmt.Sprintf("%s -> %s", ctx.Module().String(), mod)] += 1
+			ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("far_deps_1", func(ctx BottomUpMutatorContext) {
+					if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
+						ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep1Tag, "common_dep_1")
+					}
+				})
+				ctx.BottomUp("variant", func(ctx BottomUpMutatorContext) {
+					ctx.CreateLocalVariations("a", "b")
+				})
 			})
-		})
-	})
 
-	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+			ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("far_deps_2", func(ctx BottomUpMutatorContext) {
+					if !strings.HasPrefix(ctx.ModuleName(), "common_dep") {
+						ctx.AddFarVariationDependencies([]blueprint.Variation{}, dep2Tag, "common_dep_2")
+					}
+				})
+				ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+					finalGot[ctx.Module().String()] += 1
+					ctx.VisitDirectDeps(func(mod Module) {
+						finalGot[fmt.Sprintf("%s -> %s", ctx.Module().String(), mod)] += 1
+					})
+				})
+			})
 
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
 	finalWant := map[string]int{
 		"common_dep_1{variant:a}":                   1,
@@ -262,37 +239,31 @@
 		"foo{variant:b} -> common_dep_2{variant:a}": 1,
 	}
 
-	if !reflect.DeepEqual(finalWant, finalGot) {
-		t.Errorf("want:\n%q\ngot:\n%q", finalWant, finalGot)
-	}
+	AssertDeepEquals(t, "final", finalWant, finalGot)
 }
 
 func TestNoCreateVariationsInFinalDeps(t *testing.T) {
-	config := TestConfig(buildDir, nil, `test {name: "foo"}`, nil)
-	ctx := NewTestContext(config)
-
 	checkErr := func() {
 		if err := recover(); err == nil || !strings.Contains(fmt.Sprintf("%s", err), "not allowed in FinalDepsMutators") {
 			panic("Expected FinalDepsMutators consistency check to fail")
 		}
 	}
 
-	ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("vars", func(ctx BottomUpMutatorContext) {
-			defer checkErr()
-			ctx.CreateVariations("a", "b")
-		})
-		ctx.BottomUp("local_vars", func(ctx BottomUpMutatorContext) {
-			defer checkErr()
-			ctx.CreateLocalVariations("a", "b")
-		})
-	})
+	emptyTestFixtureFactory.RunTest(t,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("vars", func(ctx BottomUpMutatorContext) {
+					defer checkErr()
+					ctx.CreateVariations("a", "b")
+				})
+				ctx.BottomUp("local_vars", func(ctx BottomUpMutatorContext) {
+					defer checkErr()
+					ctx.CreateLocalVariations("a", "b")
+				})
+			})
 
-	ctx.RegisterModuleType("test", mutatorTestModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(`test {name: "foo"}`),
+	)
 }
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 45e2cdb..1caf5a8 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -143,7 +143,7 @@
 }
 
 func TestDependingOnModuleInNonImportedNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -378,7 +378,7 @@
 }
 
 func TestImportingNonexistentNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -402,7 +402,7 @@
 }
 
 func TestNamespacesDontInheritParentNamespaces(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -455,7 +455,7 @@
 }
 
 func TestNamespaceImportsNotTransitive(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -496,7 +496,7 @@
 }
 
 func TestTwoNamepacesInSameDir(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -516,7 +516,7 @@
 }
 
 func TestNamespaceNotAtTopOfFile(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			test_module {
@@ -537,7 +537,7 @@
 }
 
 func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(
+	_, errs := setupTestExpectErrs(t,
 		map[string]string{
 			"dir1": `
 			soong_namespace {
@@ -562,7 +562,7 @@
 }
 
 func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) {
-	_, errs := setupTestFromFiles(
+	_, errs := setupTestFromFiles(t,
 		map[string][]byte{
 			"Android.bp": []byte(`
 				build = ["include.bp"]
@@ -632,39 +632,38 @@
 	return files
 }
 
-func setupTestFromFiles(bps map[string][]byte) (ctx *TestContext, errs []error) {
-	config := TestConfig(buildDir, nil, "", bps)
+func setupTestFromFiles(t *testing.T, bps MockFS) (ctx *TestContext, errs []error) {
+	result := emptyTestFixtureFactory.
+		// Ignore errors for now so tests can check them later.
+		ExtendWithErrorHandler(FixtureIgnoreErrors).
+		RunTest(t,
+			FixtureModifyContext(func(ctx *TestContext) {
+				ctx.RegisterModuleType("test_module", newTestModule)
+				ctx.RegisterModuleType("soong_namespace", NamespaceFactory)
+				ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
+				ctx.PreArchMutators(RegisterNamespaceMutator)
+				ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+					ctx.BottomUp("rename", renameMutator)
+				})
+			}),
+			bps.AddToFixture(),
+		)
 
-	ctx = NewTestContext(config)
-	ctx.RegisterModuleType("test_module", newTestModule)
-	ctx.RegisterModuleType("soong_namespace", NamespaceFactory)
-	ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
-	ctx.PreArchMutators(RegisterNamespaceMutator)
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("rename", renameMutator)
-	})
-	ctx.Register()
-
-	_, errs = ctx.ParseBlueprintsFiles("Android.bp")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
+	return result.TestContext, result.Errs
 }
 
-func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) {
+func setupTestExpectErrs(t *testing.T, bps map[string]string) (ctx *TestContext, errs []error) {
 	files := make(map[string][]byte, len(bps))
 	files["Android.bp"] = []byte("")
 	for dir, text := range bps {
 		files[filepath.Join(dir, "Android.bp")] = []byte(text)
 	}
-	return setupTestFromFiles(files)
+	return setupTestFromFiles(t, files)
 }
 
 func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) {
 	t.Helper()
-	ctx, errs := setupTestExpectErrs(bps)
+	ctx, errs := setupTestExpectErrs(t, bps)
 	FailIfErrored(t, errs)
 	return ctx
 }
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index b761065..5ac97e7 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -28,7 +28,7 @@
 	rules []Rule
 
 	// Additional contents to add to the virtual filesystem used by the tests.
-	fs map[string][]byte
+	fs MockFS
 
 	// The expected error patterns. If empty then no errors are expected, otherwise each error
 	// reported must be matched by at least one of these patterns. A pattern matches if the error
@@ -285,41 +285,36 @@
 	},
 }
 
+var prepareForNeverAllowTest = GroupFixturePreparers(
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("cc_library", newMockCcLibraryModule)
+		ctx.RegisterModuleType("java_library", newMockJavaLibraryModule)
+		ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
+		ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
+		ctx.RegisterModuleType("makefile_goal", newMockMakefileGoalModule)
+		ctx.PostDepsMutators(RegisterNeverallowMutator)
+	}),
+)
+
 func TestNeverallow(t *testing.T) {
 	for _, test := range neverallowTests {
-		// Create a test per config to allow for test specific config, e.g. test rules.
-		config := TestConfig(buildDir, nil, "", test.fs)
-
 		t.Run(test.name, func(t *testing.T) {
-			// If the test has its own rules then use them instead of the default ones.
-			if test.rules != nil {
-				SetTestNeverallowRules(config, test.rules)
-			}
-			_, errs := testNeverallow(config)
-			CheckErrorsAgainstExpectations(t, errs, test.expectedErrors)
+			emptyTestFixtureFactory.
+				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+				RunTest(t,
+					prepareForNeverAllowTest,
+					FixtureModifyConfig(func(config Config) {
+						// If the test has its own rules then use them instead of the default ones.
+						if test.rules != nil {
+							SetTestNeverallowRules(config, test.rules)
+						}
+					}),
+					test.fs.AddToFixture(),
+				)
 		})
 	}
 }
 
-func testNeverallow(config Config) (*TestContext, []error) {
-	ctx := NewTestContext(config)
-	ctx.RegisterModuleType("cc_library", newMockCcLibraryModule)
-	ctx.RegisterModuleType("java_library", newMockJavaLibraryModule)
-	ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
-	ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
-	ctx.RegisterModuleType("makefile_goal", newMockMakefileGoalModule)
-	ctx.PostDepsMutators(RegisterNeverallowMutator)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
-}
-
 type mockCcLibraryProperties struct {
 	Include_dirs     []string
 	Vendor_available *bool
diff --git a/android/ninja_deps_test.go b/android/ninja_deps_test.go
index d3775ed..7e5864d 100644
--- a/android/ninja_deps_test.go
+++ b/android/ninja_deps_test.go
@@ -53,23 +53,20 @@
 }
 
 func TestNinjaDeps(t *testing.T) {
-	fs := map[string][]byte{
+	fs := MockFS{
 		"test_ninja_deps/exists": nil,
 	}
-	config := TestConfig(buildDir, nil, "", fs)
 
-	ctx := NewTestContext(config)
-	ctx.RegisterSingletonType("test_ninja_deps_singleton", testNinjaDepsSingletonFactory)
-	ctx.RegisterSingletonType("ninja_deps_singleton", ninjaDepsSingletonFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	ninjaDeps, errs := ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	result := emptyTestFixtureFactory.RunTest(t,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterSingletonType("test_ninja_deps_singleton", testNinjaDepsSingletonFactory)
+			ctx.RegisterSingletonType("ninja_deps_singleton", ninjaDepsSingletonFactory)
+		}),
+		fs.AddToFixture(),
+	)
 
 	// Verify that the ninja file has a dependency on the test_ninja_deps directory.
-	if g, w := ninjaDeps, "test_ninja_deps"; !InList(w, g) {
+	if g, w := result.NinjaDeps, "test_ninja_deps"; !InList(w, g) {
 		t.Errorf("expected %q in %q", w, g)
 	}
 }
diff --git a/android/package_test.go b/android/package_test.go
index 99be13f..d5b4db4 100644
--- a/android/package_test.go
+++ b/android/package_test.go
@@ -6,7 +6,7 @@
 
 var packageTests = []struct {
 	name           string
-	fs             map[string][]byte
+	fs             MockFS
 	expectedErrors []string
 }{
 	// Package default_visibility handling is tested in visibility_test.go
@@ -61,43 +61,13 @@
 func TestPackage(t *testing.T) {
 	for _, test := range packageTests {
 		t.Run(test.name, func(t *testing.T) {
-			_, errs := testPackage(test.fs)
-
-			expectedErrors := test.expectedErrors
-			if expectedErrors == nil {
-				FailIfErrored(t, errs)
-			} else {
-				for _, expectedError := range expectedErrors {
-					FailIfNoMatchingErrors(t, expectedError, errs)
-				}
-				if len(errs) > len(expectedErrors) {
-					t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
-					for i, expectedError := range expectedErrors {
-						t.Errorf("expectedErrors[%d] = %s", i, expectedError)
-					}
-					for i, err := range errs {
-						t.Errorf("errs[%d] = %s", i, err)
-					}
-				}
-			}
+			emptyTestFixtureFactory.
+				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+				RunTest(t,
+					PrepareForTestWithArchMutator,
+					PrepareForTestWithPackageModule,
+					test.fs.AddToFixture(),
+				)
 		})
 	}
 }
-
-func testPackage(fs map[string][]byte) (*TestContext, []error) {
-
-	// Create a new config per test as visibility information is stored in the config.
-	config := TestArchConfig(buildDir, nil, "", fs)
-
-	ctx := NewTestArchContext(config)
-	RegisterPackageBuildComponents(ctx)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles(".")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
-}
diff --git a/android/packaging_test.go b/android/packaging_test.go
index 2c99b97..eb7f26f 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"reflect"
 	"testing"
 
 	"github.com/google/blueprint"
@@ -87,33 +86,30 @@
 func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
 	t.Helper()
 
-	config := TestArchConfig(buildDir, nil, bp, nil)
-
-	ctx := NewTestArchContext(config)
-	ctx.RegisterModuleType("component", componentTestModuleFactory)
-
 	var archVariant string
+	var moduleFactory ModuleFactory
 	if multitarget {
 		archVariant = "android_common"
-		ctx.RegisterModuleType("package_module", packageMultiTargetTestModuleFactory)
+		moduleFactory = packageMultiTargetTestModuleFactory
 	} else {
 		archVariant = "android_arm64_armv8-a"
-		ctx.RegisterModuleType("package_module", packageTestModuleFactory)
+		moduleFactory = packageTestModuleFactory
 	}
-	ctx.Register()
 
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	result := emptyTestFixtureFactory.RunTest(t,
+		PrepareForTestWithArchMutator,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("component", componentTestModuleFactory)
+			ctx.RegisterModuleType("package_module", moduleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	p := ctx.ModuleForTests("package", archVariant).Module().(*packageTestModule)
+	p := result.Module("package", archVariant).(*packageTestModule)
 	actual := p.entries
 	actual = SortedUniqueStrings(actual)
 	expected = SortedUniqueStrings(expected)
-	if !reflect.DeepEqual(actual, expected) {
-		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
-	}
+	AssertDeepEquals(t, "package entries", expected, actual)
 }
 
 func TestPackagingBaseMultiTarget(t *testing.T) {
diff --git a/android/paths.go b/android/paths.go
index 3f4d3f2..f648c55 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -339,6 +339,7 @@
 	EarlyModulePathContext
 
 	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
+	Module() Module
 	OtherModuleName(m blueprint.Module) string
 	OtherModuleDir(m blueprint.Module) string
 }
@@ -434,15 +435,45 @@
 // already be resolved by either deps mutator or path deps mutator.
 func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string) bazel.Label {
 	m, _ := ctx.GetDirectDep(dep)
-	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
-	otherModuleName := ctx.OtherModuleName(m)
-	var label bazel.Label
-	if otherDir, dir := ctx.OtherModuleDir(m), ctx.ModuleDir(); otherDir != dir {
-		label.Label = fmt.Sprintf("//%s:%s", otherDir, otherModuleName)
-	} else {
-		label.Label = fmt.Sprintf(":%s", otherModuleName)
+	otherLabel := bazelModuleLabel(ctx, m, tag)
+	label := bazelModuleLabel(ctx, ctx.Module(), "")
+	if samePackage(label, otherLabel) {
+		otherLabel = bazelShortLabel(otherLabel)
 	}
-	return label
+
+	return bazel.Label{
+		Label: otherLabel,
+	}
+}
+
+func bazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module, tag string) string {
+	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
+	b, ok := module.(Bazelable)
+	// TODO(b/181155349): perhaps return an error here if the module can't be/isn't being converted
+	if !ok || !b.ConvertedToBazel() {
+		return bp2buildModuleLabel(ctx, module)
+	}
+	return b.GetBazelLabel(ctx, module)
+}
+
+func bazelShortLabel(label string) string {
+	i := strings.Index(label, ":")
+	return label[i:]
+}
+
+func bazelPackage(label string) string {
+	i := strings.Index(label, ":")
+	return label[0:i]
+}
+
+func samePackage(label1, label2 string) bool {
+	return bazelPackage(label1) == bazelPackage(label2)
+}
+
+func bp2buildModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
+	moduleName := ctx.OtherModuleName(module)
+	moduleDir := ctx.OtherModuleDir(module)
+	return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
 }
 
 // OutputPaths is a slice of OutputPath objects, with helpers to operate on the collection.
diff --git a/android/paths_test.go b/android/paths_test.go
index 14a4773..c5fc10e 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -21,8 +21,6 @@
 	"strconv"
 	"strings"
 	"testing"
-
-	"github.com/google/blueprint/proptools"
 )
 
 type strsTestCase struct {
@@ -977,7 +975,7 @@
 	rel  string
 }
 
-func testPathForModuleSrc(t *testing.T, buildDir string, tests []pathForModuleSrcTestCase) {
+func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			fgBp := `
@@ -995,7 +993,7 @@
 				}
 			`
 
-			mockFS := map[string][]byte{
+			mockFS := MockFS{
 				"fg/Android.bp":     []byte(fgBp),
 				"foo/Android.bp":    []byte(test.bp),
 				"ofp/Android.bp":    []byte(ofpBp),
@@ -1007,37 +1005,21 @@
 				"foo/src_special/$": nil,
 			}
 
-			config := TestConfig(buildDir, nil, "", mockFS)
+			result := emptyTestFixtureFactory.RunTest(t,
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
+					ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
+					ctx.RegisterModuleType("filegroup", FileGroupFactory)
+				}),
+				mockFS.AddToFixture(),
+			)
 
-			ctx := NewTestContext(config)
+			m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
 
-			ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
-			ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
-			ctx.RegisterModuleType("filegroup", FileGroupFactory)
-
-			ctx.Register()
-			_, errs := ctx.ParseFileList(".", []string{"fg/Android.bp", "foo/Android.bp", "ofp/Android.bp"})
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			FailIfErrored(t, errs)
-
-			m := ctx.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
-
-			if g, w := m.srcs, test.srcs; !reflect.DeepEqual(g, w) {
-				t.Errorf("want srcs %q, got %q", w, g)
-			}
-
-			if g, w := m.rels, test.rels; !reflect.DeepEqual(g, w) {
-				t.Errorf("want rels %q, got %q", w, g)
-			}
-
-			if g, w := m.src, test.src; g != w {
-				t.Errorf("want src %q, got %q", w, g)
-			}
-
-			if g, w := m.rel, test.rel; g != w {
-				t.Errorf("want rel %q, got %q", w, g)
-			}
+			AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs)
+			AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels)
+			AssertStringPathRelativeToTopEquals(t, "src", result.Config, test.src, m.src)
+			AssertStringPathRelativeToTopEquals(t, "rel", result.Config, test.rel, m.rel)
 		})
 	}
 }
@@ -1094,7 +1076,7 @@
 				name: "foo",
 				srcs: [":b"],
 			}`,
-			srcs: []string{buildDir + "/.intermediates/ofp/b/gen/b"},
+			srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
 			rels: []string{"gen/b"},
 		},
 		{
@@ -1104,7 +1086,7 @@
 				name: "foo",
 				srcs: [":b{.tagged}"],
 			}`,
-			srcs: []string{buildDir + "/.intermediates/ofp/b/gen/c"},
+			srcs: []string{"out/soong/.intermediates/ofp/b/gen/c"},
 			rels: []string{"gen/c"},
 		},
 		{
@@ -1119,7 +1101,7 @@
 				name: "c",
 				outs: ["gen/c"],
 			}`,
-			srcs: []string{buildDir + "/.intermediates/ofp/b/gen/b"},
+			srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
 			rels: []string{"gen/b"},
 		},
 		{
@@ -1134,7 +1116,7 @@
 		},
 	}
 
-	testPathForModuleSrc(t, buildDir, tests)
+	testPathForModuleSrc(t, tests)
 }
 
 func TestPathForModuleSrc(t *testing.T) {
@@ -1176,7 +1158,7 @@
 				name: "foo",
 				src: ":b",
 			}`,
-			src: buildDir + "/.intermediates/ofp/b/gen/b",
+			src: "out/soong/.intermediates/ofp/b/gen/b",
 			rel: "gen/b",
 		},
 		{
@@ -1186,7 +1168,7 @@
 				name: "foo",
 				src: ":b{.tagged}",
 			}`,
-			src: buildDir + "/.intermediates/ofp/b/gen/c",
+			src: "out/soong/.intermediates/ofp/b/gen/c",
 			rel: "gen/c",
 		},
 		{
@@ -1201,7 +1183,7 @@
 		},
 	}
 
-	testPathForModuleSrc(t, buildDir, tests)
+	testPathForModuleSrc(t, tests)
 }
 
 func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) {
@@ -1221,44 +1203,70 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
+	result := emptyTestFixtureFactory.RunTest(t,
+		PrepareForTestWithAllowMissingDependencies,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
-	ctx.SetAllowMissingDependencies(true)
+	foo := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
 
-	ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
+	AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps)
+	AssertArrayString(t, "foo srcs", []string{}, foo.srcs)
+	AssertStringEquals(t, "foo src", "", foo.src)
 
-	ctx.Register()
+	bar := result.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule)
 
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps)
+	AssertArrayString(t, "bar srcs", []string{}, bar.srcs)
+}
 
-	foo := ctx.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
+func TestPathRelativeToTop(t *testing.T) {
+	testConfig := pathTestConfig("/tmp/build/top")
+	deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}
 
-	if g, w := foo.missingDeps, []string{"a", "b", "c"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want foo missing deps %q, got %q", w, g)
+	ctx := &testModuleInstallPathContext{
+		baseModuleContext: baseModuleContext{
+			os:     deviceTarget.Os,
+			target: deviceTarget,
+		},
 	}
+	ctx.baseModuleContext.config = testConfig
 
-	if g, w := foo.srcs, []string{}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want foo srcs %q, got %q", w, g)
-	}
+	t.Run("install for soong", func(t *testing.T) {
+		p := PathForModuleInstall(ctx, "install/path")
+		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
+	})
+	t.Run("install for make", func(t *testing.T) {
+		p := PathForModuleInstall(ctx, "install/path").ToMakePath()
+		AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
+	})
+	t.Run("output", func(t *testing.T) {
+		p := PathForOutput(ctx, "output/path")
+		AssertPathRelativeToTopEquals(t, "output path", "out/soong/output/path", p)
+	})
+	t.Run("source", func(t *testing.T) {
+		p := PathForSource(ctx, "source/path")
+		AssertPathRelativeToTopEquals(t, "source path", "source/path", p)
+	})
+	t.Run("mixture", func(t *testing.T) {
+		paths := Paths{
+			PathForModuleInstall(ctx, "install/path"),
+			PathForModuleInstall(ctx, "install/path").ToMakePath(),
+			PathForOutput(ctx, "output/path"),
+			PathForSource(ctx, "source/path"),
+		}
 
-	if g, w := foo.src, ""; g != w {
-		t.Errorf("want foo src %q, got %q", w, g)
-	}
-
-	bar := ctx.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule)
-
-	if g, w := bar.missingDeps, []string{"d", "e"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want bar missing deps %q, got %q", w, g)
-	}
-
-	if g, w := bar.srcs, []string{}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want bar srcs %q, got %q", w, g)
-	}
+		expected := []string{
+			"out/soong/target/product/test_device/system/install/path",
+			"out/target/product/test_device/system/install/path",
+			"out/soong/output/path",
+			"source/path",
+		}
+		AssertPathsRelativeToTopEquals(t, "mixture", expected, paths)
+	})
 }
 
 func ExampleOutputPath_ReplaceExtension() {
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 84501fe..75f1b5d 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -385,6 +385,21 @@
 	return rspFileInputs
 }
 
+// RspFile returns the path to the rspfile that was passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
+func (r *RuleBuilder) RspFile() WritablePath {
+	var rspFile WritablePath
+	for _, c := range r.commands {
+		if c.rspFile != nil {
+			if rspFile != nil {
+				panic("Multiple commands in a rule may not have rsp file inputs")
+			}
+			rspFile = c.rspFile
+		}
+	}
+
+	return rspFile
+}
+
 // Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
 func (r *RuleBuilder) Commands() []string {
 	var commands []string
@@ -394,16 +409,6 @@
 	return commands
 }
 
-// NinjaEscapedCommands returns a slice containing the built command line after ninja escaping for each call to
-// RuleBuilder.Command.
-func (r *RuleBuilder) NinjaEscapedCommands() []string {
-	var commands []string
-	for _, c := range r.commands {
-		commands = append(commands, c.NinjaEscapedString())
-	}
-	return commands
-}
-
 // BuilderContext is a subset of ModuleContext and SingletonContext.
 type BuilderContext interface {
 	PathContext
@@ -458,9 +463,11 @@
 	}
 
 	tools := r.Tools()
-	commands := r.NinjaEscapedCommands()
+	commands := r.Commands()
 	outputs := r.Outputs()
 	inputs := r.Inputs()
+	rspFileInputs := r.RspFileInputs()
+	rspFilePath := r.RspFile()
 
 	if len(commands) == 0 {
 		return
@@ -516,6 +523,12 @@
 			})
 		}
 
+		// Outputs that were marked Temporary will not be checked that they are in the output
+		// directory by the loop above, check them here.
+		for path := range r.temporariesSet {
+			Rel(r.ctx, r.outDir.String(), path.String())
+		}
+
 		// Add a hash of the list of input files to the manifest so that the textproto file
 		// changes when the list of input files changes and causes the sbox rule that
 		// depends on it to rerun.
@@ -559,15 +572,14 @@
 	}
 
 	// Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
-	// ImplicitOutputs.  RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and
+	// ImplicitOutputs.  RuleBuilder doesn't use "$out", so the distinction between Outputs and
 	// ImplicitOutputs doesn't matter.
 	output := outputs[0]
 	implicitOutputs := outputs[1:]
 
 	var rspFile, rspFileContent string
-	rspFileInputs := r.RspFileInputs()
-	if rspFileInputs != nil {
-		rspFile = "$out.rsp"
+	if rspFilePath != nil {
+		rspFile = rspFilePath.String()
 		rspFileContent = "$in"
 	}
 
@@ -585,10 +597,10 @@
 
 	r.ctx.Build(r.pctx, BuildParams{
 		Rule: r.ctx.Rule(pctx, name, blueprint.RuleParams{
-			Command:        commandString,
-			CommandDeps:    tools.Strings(),
+			Command:        proptools.NinjaEscape(commandString),
+			CommandDeps:    proptools.NinjaEscapeList(tools.Strings()),
 			Restat:         r.restat,
-			Rspfile:        rspFile,
+			Rspfile:        proptools.NinjaEscape(rspFile),
 			RspfileContent: rspFileContent,
 			Pool:           pool,
 		}),
@@ -620,9 +632,7 @@
 	tools          Paths
 	packagedTools  []PackagingSpec
 	rspFileInputs  Paths
-
-	// spans [start,end) of the command that should not be ninja escaped
-	unescapedSpans [][2]int
+	rspFile        WritablePath
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
@@ -1020,8 +1030,9 @@
 }
 
 // FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
-// between them.  The paths will be written to the rspfile.
-func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, paths Paths) *RuleBuilderCommand {
+// between them.  The paths will be written to the rspfile.  If sbox is enabled, the rspfile must
+// be outside the sbox directory.
+func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, rspFile WritablePath, paths Paths) *RuleBuilderCommand {
 	if c.rspFileInputs != nil {
 		panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided")
 	}
@@ -1033,10 +1044,16 @@
 	}
 
 	c.rspFileInputs = paths
+	c.rspFile = rspFile
 
-	rspFile := "$out.rsp"
-	c.FlagWithArg(flag, rspFile)
-	c.unescapedSpans = append(c.unescapedSpans, [2]int{c.buf.Len() - len(rspFile), c.buf.Len()})
+	if c.rule.sbox {
+		if _, isRel, _ := maybeRelErr(c.rule.outDir.String(), rspFile.String()); isRel {
+			panic(fmt.Errorf("FlagWithRspFileInputList rspfile %q must not be inside out dir %q",
+				rspFile.String(), c.rule.outDir.String()))
+		}
+	}
+
+	c.FlagWithArg(flag, rspFile.String())
 	return c
 }
 
@@ -1045,11 +1062,6 @@
 	return c.buf.String()
 }
 
-// String returns the command line.
-func (c *RuleBuilderCommand) NinjaEscapedString() string {
-	return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
-}
-
 // RuleBuilderSboxProtoForTests takes the BuildParams for the manifest passed to RuleBuilder.Sbox()
 // and returns sbox testproto generated by the RuleBuilder.
 func RuleBuilderSboxProtoForTests(t *testing.T, params TestingBuildParams) *sbox_proto.Manifest {
@@ -1063,25 +1075,6 @@
 	return &manifest
 }
 
-func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
-	if len(spans) == 0 {
-		return proptools.NinjaEscape(s)
-	}
-
-	sb := strings.Builder{}
-	sb.Grow(len(s) * 11 / 10)
-
-	i := 0
-	for _, span := range spans {
-		sb.WriteString(proptools.NinjaEscape(s[i:span[0]]))
-		sb.WriteString(s[span[0]:span[1]])
-		i = span[1]
-	}
-	sb.WriteString(proptools.NinjaEscape(s[i:]))
-
-	return sb.String()
-}
-
 func ninjaNameEscape(s string) string {
 	b := []byte(s)
 	escaped := false
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 06ea124..bd35820 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -17,7 +17,6 @@
 import (
 	"fmt"
 	"path/filepath"
-	"reflect"
 	"regexp"
 	"strings"
 	"testing"
@@ -267,10 +266,10 @@
 	ctx := builderContext()
 	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
 		Tool(PathForSource(ctx, "javac")).
-		FlagWithRspFileInputList("@", PathsForTesting("a.java", "b.java")).
-		NinjaEscapedString())
+		FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")).
+		String())
 	// Output:
-	// javac @$out.rsp
+	// javac @out/foo.rsp
 }
 
 func ExampleRuleBuilderCommand_String() {
@@ -283,16 +282,6 @@
 	// FOO=foo echo $FOO
 }
 
-func ExampleRuleBuilderCommand_NinjaEscapedString() {
-	ctx := builderContext()
-	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
-		Text("FOO=foo").
-		Text("echo $FOO").
-		NinjaEscapedString())
-	// Output:
-	// FOO=foo echo $$FOO
-}
-
 func TestRuleBuilder(t *testing.T) {
 	fs := map[string][]byte{
 		"dep_fixer":  nil,
@@ -371,32 +360,16 @@
 
 		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer out/DepFile out/depfile out/ImplicitDepFile out/depfile2"
 
-		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
-			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
-		if g, w := rule.Inputs(), wantInputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", w, g)
-		}
-		if g, w := rule.Outputs(), wantOutputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.SymlinkOutputs(), wantSymlinkOutputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.SymlinkOutputs() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.DepFiles(), wantDepFiles; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.DepFiles() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.Tools(), wantTools; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", w, g)
-		}
-		if g, w := rule.OrderOnlys(), wantOrderOnlys; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
+		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
+		AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
+		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
+		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
+		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
-		if g, w := rule.depFileMergerCmd(rule.DepFiles()).String(), wantDepMergerCommand; g != w {
-			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
-		}
+		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
 
 	t.Run("sbox", func(t *testing.T) {
@@ -412,29 +385,15 @@
 
 		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
-			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
-		if g, w := rule.Inputs(), wantInputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", w, g)
-		}
-		if g, w := rule.Outputs(), wantOutputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.DepFiles(), wantDepFiles; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.DepFiles() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.Tools(), wantTools; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", w, g)
-		}
-		if g, w := rule.OrderOnlys(), wantOrderOnlys; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
+		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
+		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
+		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
+		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
-		if g, w := rule.depFileMergerCmd(rule.DepFiles()).String(), wantDepMergerCommand; g != w {
-			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
-		}
+		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
 
 	t.Run("sbox tools", func(t *testing.T) {
@@ -450,29 +409,15 @@
 
 		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
-			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
-		if g, w := rule.Inputs(), wantInputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", w, g)
-		}
-		if g, w := rule.Outputs(), wantOutputs; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.DepFiles(), wantDepFiles; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.DepFiles() = %#v\n                  got %#v", w, g)
-		}
-		if g, w := rule.Tools(), wantTools; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", w, g)
-		}
-		if g, w := rule.OrderOnlys(), wantOrderOnlys; !reflect.DeepEqual(w, g) {
-			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
-		}
+		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
+		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
+		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
+		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
+		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
-		if g, w := rule.depFileMergerCmd(rule.DepFiles()).String(), wantDepMergerCommand; g != w {
-			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
-		}
+		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
 }
 
@@ -534,8 +479,13 @@
 	rule.Build("rule", "desc")
 }
 
+var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
+	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
+})
+
 func TestRuleBuilder_Build(t *testing.T) {
-	fs := map[string][]byte{
+	fs := MockFS{
 		"bar": nil,
 		"cp":  nil,
 	}
@@ -553,60 +503,46 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, fs)
-	ctx := NewTestContext(config)
-	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
-	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	result := emptyTestFixtureFactory.RunTest(t,
+		prepareForRuleBuilderTest,
+		FixtureWithRootAndroidBp(bp),
+		fs.AddToFixture(),
+	)
 
 	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
 		t.Helper()
 		command := params.RuleParams.Command
 		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
 		command = re.ReplaceAllLiteralString(command, "")
-		if command != wantCommand {
-			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
-		}
+
+		AssertStringEquals(t, "RuleParams.Command", wantCommand, command)
 
 		wantDeps := append([]string{"cp"}, extraCmdDeps...)
-		if !reflect.DeepEqual(params.RuleParams.CommandDeps, wantDeps) {
-			t.Errorf("\nwant RuleParams.CommandDeps = %q\n                          got %q", wantDeps, params.RuleParams.CommandDeps)
-		}
+		AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps)
 
-		if params.RuleParams.Restat != wantRestat {
-			t.Errorf("want RuleParams.Restat = %v, got %v", wantRestat, params.RuleParams.Restat)
-		}
+		AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
 
 		wantImplicits := append([]string{"bar"}, extraImplicits...)
-		if !reflect.DeepEqual(params.Implicits.Strings(), wantImplicits) {
-			t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
-		}
+		AssertArrayString(t, "Implicits", wantImplicits, params.Implicits.Strings())
 
-		if params.Output.String() != wantOutput {
-			t.Errorf("want Output = %q, got %q", wantOutput, params.Output)
-		}
+		AssertStringEquals(t, "Output", wantOutput, params.Output.String())
 
 		if len(params.ImplicitOutputs) != 0 {
 			t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings())
 		}
 
-		if params.Depfile.String() != wantDepfile {
-			t.Errorf("want Depfile = %q, got %q", wantDepfile, params.Depfile)
-		}
+		AssertStringEquals(t, "Depfile", wantDepfile, params.Depfile.String())
 
 		if params.Deps != blueprint.DepsGCC {
 			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
 		}
 	}
 
+	buildDir := result.Config.BuildDir()
+
 	t.Run("module", func(t *testing.T) {
 		outFile := filepath.Join(buildDir, ".intermediates", "foo", "gen", "foo")
-		check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
+		check(t, result.ModuleForTests("foo", "").Rule("rule"),
 			"cp bar "+outFile,
 			outFile, outFile+".d", true, nil, nil)
 	})
@@ -615,96 +551,22 @@
 		outFile := filepath.Join(outDir, "gen/foo_sbox")
 		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
 		manifest := filepath.Join(outDir, "sbox.textproto")
-		sbox := filepath.Join(buildDir, "host", config.PrebuiltOS(), "bin/sbox")
+		sbox := filepath.Join(buildDir, "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir(buildDir)
 
 		cmd := `rm -rf ` + outDir + `/gen && ` +
 			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
 
-		check(t, ctx.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
+		check(t, result.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
 			cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("singleton", func(t *testing.T) {
 		outFile := filepath.Join(buildDir, "singleton/gen/baz")
-		check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
+		check(t, result.SingletonForTests("rule_builder_test").Rule("rule"),
 			"cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
 	})
 }
 
-func Test_ninjaEscapeExceptForSpans(t *testing.T) {
-	type args struct {
-		s     string
-		spans [][2]int
-	}
-	tests := []struct {
-		name string
-		args args
-		want string
-	}{
-		{
-			name: "empty",
-			args: args{
-				s: "",
-			},
-			want: "",
-		},
-		{
-			name: "unescape none",
-			args: args{
-				s: "$abc",
-			},
-			want: "$$abc",
-		},
-		{
-			name: "unescape all",
-			args: args{
-				s:     "$abc",
-				spans: [][2]int{{0, 4}},
-			},
-			want: "$abc",
-		},
-		{
-			name: "unescape first",
-			args: args{
-				s:     "$abc$",
-				spans: [][2]int{{0, 1}},
-			},
-			want: "$abc$$",
-		},
-		{
-			name: "unescape last",
-			args: args{
-				s:     "$abc$",
-				spans: [][2]int{{4, 5}},
-			},
-			want: "$$abc$",
-		},
-		{
-			name: "unescape middle",
-			args: args{
-				s:     "$a$b$c$",
-				spans: [][2]int{{2, 5}},
-			},
-			want: "$$a$b$c$$",
-		},
-		{
-			name: "unescape multiple",
-			args: args{
-				s:     "$a$b$c$",
-				spans: [][2]int{{2, 3}, {4, 5}},
-			},
-			want: "$$a$b$c$$",
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := ninjaEscapeExceptForSpans(tt.args.s, tt.args.spans); got != tt.want {
-				t.Errorf("ninjaEscapeExceptForSpans() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
 func TestRuleBuilderHashInputs(t *testing.T) {
 	// The basic idea here is to verify that the command (in the case of a
 	// non-sbox rule) or the sbox textproto manifest contain a hash of the
@@ -750,29 +612,22 @@
 		},
 	}
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	ctx := NewTestContext(config)
-	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	result := emptyTestFixtureFactory.RunTest(t,
+		prepareForRuleBuilderTest,
+		FixtureWithRootAndroidBp(bp),
+	)
 
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
 			t.Run("sbox", func(t *testing.T) {
-				gen := ctx.ModuleForTests(test.name+"_sbox", "")
+				gen := result.ModuleForTests(test.name+"_sbox", "")
 				manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto"))
 				hash := manifest.Commands[0].GetInputHash()
 
-				if g, w := hash, test.expectedHash; g != w {
-					t.Errorf("Expected has %q, got %q", w, g)
-				}
+				AssertStringEquals(t, "hash", test.expectedHash, hash)
 			})
 			t.Run("", func(t *testing.T) {
-				gen := ctx.ModuleForTests(test.name+"", "")
+				gen := result.ModuleForTests(test.name+"", "")
 				command := gen.Output("gen/" + test.name).RuleParams.Command
 				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
 					t.Errorf("Expected command line to end with %q, got %q", w, g)
diff --git a/android/singleton_module_test.go b/android/singleton_module_test.go
index 9232eb4..41dd4bb 100644
--- a/android/singleton_module_test.go
+++ b/android/singleton_module_test.go
@@ -15,8 +15,6 @@
 package android
 
 import (
-	"reflect"
-	"strings"
 	"testing"
 )
 
@@ -43,23 +41,14 @@
 	return tsm
 }
 
-func runSingletonModuleTest(bp string) (*TestContext, []error) {
-	config := TestConfig(buildDir, nil, bp, nil)
+var prepareForSingletonModuleTest = GroupFixturePreparers(
 	// Enable Kati output to test SingletonModules with MakeVars.
-	config.katiEnabled = true
-	ctx := NewTestContext(config)
-	ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
-	ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	if len(errs) > 0 {
-		return ctx, errs
-	}
-
-	_, errs = ctx.PrepareBuildActions(config)
-	return ctx, errs
-}
+	PrepareForTestWithAndroidMk,
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
+		ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
+	}),
+)
 
 func TestSingletonModule(t *testing.T) {
 	bp := `
@@ -67,16 +56,15 @@
 			name: "test_singleton_module",
 		}
 	`
-	ctx, errs := runSingletonModuleTest(bp)
-	if len(errs) > 0 {
-		t.Fatal(errs)
-	}
+	result := emptyTestFixtureFactory.
+		RunTest(t,
+			prepareForSingletonModuleTest,
+			FixtureWithRootAndroidBp(bp),
+		)
 
-	ops := ctx.ModuleForTests("test_singleton_module", "").Module().(*testSingletonModule).ops
+	ops := result.ModuleForTests("test_singleton_module", "").Module().(*testSingletonModule).ops
 	wantOps := []string{"GenerateAndroidBuildActions", "GenerateSingletonBuildActions", "MakeVars"}
-	if !reflect.DeepEqual(ops, wantOps) {
-		t.Errorf("Expected operations %q, got %q", wantOps, ops)
-	}
+	AssertDeepEquals(t, "operations", wantOps, ops)
 }
 
 func TestDuplicateSingletonModule(t *testing.T) {
@@ -89,23 +77,22 @@
 			name: "test_singleton_module2",
 		}
 	`
-	_, errs := runSingletonModuleTest(bp)
-	if len(errs) == 0 {
-		t.Fatal("expected duplicate SingletonModule error")
-	}
-	if len(errs) != 1 || !strings.Contains(errs[0].Error(), `Duplicate SingletonModule "test_singleton_module", previously used in`) {
-		t.Fatalf("expected duplicate SingletonModule error, got %q", errs)
-	}
+
+	emptyTestFixtureFactory.
+		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
+			`\QDuplicate SingletonModule "test_singleton_module", previously used in\E`,
+		})).RunTest(t,
+		prepareForSingletonModuleTest,
+		FixtureWithRootAndroidBp(bp),
+	)
 }
 
 func TestUnusedSingletonModule(t *testing.T) {
-	bp := ``
-	ctx, errs := runSingletonModuleTest(bp)
-	if len(errs) > 0 {
-		t.Fatal(errs)
-	}
+	result := emptyTestFixtureFactory.RunTest(t,
+		prepareForSingletonModuleTest,
+	)
 
-	singleton := ctx.SingletonForTests("test_singleton_module").Singleton()
+	singleton := result.SingletonForTests("test_singleton_module").Singleton()
 	sm := singleton.(*singletonModuleSingletonAdaptor).sm
 	ops := sm.(*testSingletonModule).ops
 	if ops != nil {
@@ -126,24 +113,17 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	ctx := NewTestContext(config)
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("test_singleton_module_mutator", testVariantSingletonModuleMutator)
-	})
-	ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-
-	if len(errs) == 0 {
-		_, errs = ctx.PrepareBuildActions(config)
-	}
-
-	if len(errs) == 0 {
-		t.Fatal("expected duplicate SingletonModule error")
-	}
-	if len(errs) != 1 || !strings.Contains(errs[0].Error(), `GenerateAndroidBuildActions already called for variant`) {
-		t.Fatalf("expected duplicate SingletonModule error, got %q", errs)
-	}
+	emptyTestFixtureFactory.
+		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
+			`\QGenerateAndroidBuildActions already called for variant\E`,
+		})).
+		RunTest(t,
+			prepareForSingletonModuleTest,
+			FixtureRegisterWithContext(func(ctx RegistrationContext) {
+				ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+					ctx.BottomUp("test_singleton_module_mutator", testVariantSingletonModuleMutator)
+				})
+			}),
+			FixtureWithRootAndroidBp(bp),
+		)
 }
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 45463fd..a72b160 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"reflect"
 	"testing"
 )
 
@@ -181,17 +180,23 @@
 		}
     `
 
-	run := func(t *testing.T, bp string, fs map[string][]byte) {
+	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
+		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.VendorVars = vars
+		})
+	}
+
+	run := func(t *testing.T, bp string, fs MockFS) {
 		testCases := []struct {
 			name                     string
-			config                   Config
+			preparer                 FixturePreparer
 			fooExpectedFlags         []string
 			fooDefaultsExpectedFlags []string
 		}{
 			{
 				name: "withValues",
-				config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
-					"acme": map[string]string{
+				preparer: fixtureForVendorVars(map[string]map[string]string{
+					"acme": {
 						"board":    "soc_a",
 						"size":     "42",
 						"feature1": "true",
@@ -221,8 +226,8 @@
 			},
 			{
 				name: "empty_prop_for_string_var",
-				config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
-					"acme": map[string]string{"board": "soc_c"}}),
+				preparer: fixtureForVendorVars(map[string]map[string]string{
+					"acme": {"board": "soc_c"}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -237,8 +242,8 @@
 			},
 			{
 				name: "unused_string_var",
-				config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{
-					"acme": map[string]string{"board": "soc_d"}}),
+				preparer: fixtureForVendorVars(map[string]map[string]string{
+					"acme": {"board": "soc_d"}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -254,8 +259,8 @@
 			},
 
 			{
-				name:   "conditions_default",
-				config: testConfigWithVendorVars(buildDir, bp, fs, map[string]map[string]string{}),
+				name:     "conditions_default",
+				preparer: fixtureForVendorVars(map[string]map[string]string{}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -272,32 +277,29 @@
 		}
 
 		for _, tc := range testCases {
-			ctx := NewTestContext(tc.config)
-			ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-			ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-			ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-			ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
-			ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
-			ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
-			ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-			ctx.Register()
+			t.Run(tc.name, func(t *testing.T) {
+				result := emptyTestFixtureFactory.RunTest(t,
+					tc.preparer,
+					PrepareForTestWithDefaults,
+					FixtureRegisterWithContext(func(ctx RegistrationContext) {
+						ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
+						ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
+						ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
+						ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+						ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
+						ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
+					}),
+					fs.AddToFixture(),
+					FixtureWithRootAndroidBp(bp),
+				)
 
-			_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-			FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(tc.config)
-			FailIfErrored(t, errs)
+				foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
+				AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags)
 
-			foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
-			if g, w := foo.props.Cflags, tc.fooExpectedFlags; !reflect.DeepEqual(g, w) {
-				t.Errorf("%s: wanted foo cflags %q, got %q", tc.name, w, g)
-			}
-
-			fooDefaults := ctx.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
-			if g, w := fooDefaults.props.Cflags, tc.fooDefaultsExpectedFlags; !reflect.DeepEqual(g, w) {
-				t.Errorf("%s: wanted foo_with_defaults cflags %q, got %q", tc.name, w, g)
-			}
+				fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
+				AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags)
+			})
 		}
-
 	}
 
 	t.Run("single file", func(t *testing.T) {
diff --git a/android/test_asserts.go b/android/test_asserts.go
index d0de523..4b5e934 100644
--- a/android/test_asserts.go
+++ b/android/test_asserts.go
@@ -22,6 +22,15 @@
 
 // This file contains general purpose test assert functions.
 
+// AssertSame checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func AssertSame(t *testing.T, message string, expected interface{}, actual interface{}) {
+	t.Helper()
+	if actual != expected {
+		t.Errorf("%s: expected:\n%#v\nactual:\n%#v", message, expected, actual)
+	}
+}
+
 // AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
 // reports an error prefixed with the supplied message and including a reason for why it failed.
 func AssertBoolEquals(t *testing.T, message string, expected bool, actual bool) {
@@ -31,6 +40,15 @@
 	}
 }
 
+// AssertIntEquals checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func AssertIntEquals(t *testing.T, message string, expected int, actual int) {
+	t.Helper()
+	if actual != expected {
+		t.Errorf("%s: expected %d, actual %d", message, expected, actual)
+	}
+}
+
 // AssertStringEquals checks if the expected and actual values are equal and if they are not then
 // it reports an error prefixed with the supplied message and including a reason for why it failed.
 func AssertStringEquals(t *testing.T, message string, expected string, actual string) {
@@ -40,6 +58,34 @@
 	}
 }
 
+// AssertPathRelativeToTopEquals checks if the expected value is equal to the result of calling
+// PathRelativeToTop on the actual Path.
+func AssertPathRelativeToTopEquals(t *testing.T, message string, expected string, actual Path) {
+	t.Helper()
+	AssertStringEquals(t, message, expected, PathRelativeToTop(actual))
+}
+
+// AssertPathsRelativeToTopEquals checks if the expected value is equal to the result of calling
+// PathsRelativeToTop on the actual Paths.
+func AssertPathsRelativeToTopEquals(t *testing.T, message string, expected []string, actual Paths) {
+	t.Helper()
+	AssertDeepEquals(t, message, expected, PathsRelativeToTop(actual))
+}
+
+// AssertStringPathRelativeToTopEquals checks if the expected value is equal to the result of calling
+// StringPathRelativeToTop on the actual string path.
+func AssertStringPathRelativeToTopEquals(t *testing.T, message string, config Config, expected string, actual string) {
+	t.Helper()
+	AssertStringEquals(t, message, expected, StringPathRelativeToTop(config.buildDir, actual))
+}
+
+// AssertStringPathsRelativeToTopEquals checks if the expected value is equal to the result of
+// calling StringPathsRelativeToTop on the actual string paths.
+func AssertStringPathsRelativeToTopEquals(t *testing.T, message string, config Config, expected []string, actual []string) {
+	t.Helper()
+	AssertDeepEquals(t, message, expected, StringPathsRelativeToTop(config.buildDir, actual))
+}
+
 // AssertErrorMessageEquals checks if the error is not nil and has the expected message. If it does
 // not then this reports an error prefixed with the supplied message and including a reason for why
 // it failed.
diff --git a/android/test_suites.go b/android/test_suites.go
index 7ecb8d2..6b7b909 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -68,7 +68,7 @@
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
 		FlagWithArg("-C ", testCasesDir.String()).
-		FlagWithRspFileInputList("-r ", installedPaths.Paths())
+		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths())
 	rule.Build("robolectric_tests_zip", "robolectric-tests.zip")
 
 	return outputFile
diff --git a/android/testing.go b/android/testing.go
index dd3d607..af360fa 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -813,6 +813,9 @@
 // that is relative to the root of the source tree.
 //
 // The build and source paths should be distinguishable based on their contents.
+//
+// deprecated: use PathRelativeToTop instead as it handles make install paths and differentiates
+// between output and source properly.
 func NormalizePathForTesting(path Path) string {
 	if path == nil {
 		return "<nil path>"
@@ -828,6 +831,11 @@
 	return p
 }
 
+// NormalizePathsForTesting creates a slice of strings where each string is the result of applying
+// NormalizePathForTesting to the corresponding Path in the input slice.
+//
+// deprecated: use PathsRelativeToTop instead as it handles make install paths and differentiates
+// between output and source properly.
 func NormalizePathsForTesting(paths Paths) []string {
 	var result []string
 	for _, path := range paths {
@@ -836,3 +844,100 @@
 	}
 	return result
 }
+
+// PathRelativeToTop returns a string representation of the path relative to a notional top
+// directory.
+//
+// For a WritablePath it applies StringPathRelativeToTop to it, using the buildDir returned from the
+// WritablePath's buildDir() method. For all other paths, i.e. source paths, that are already
+// relative to the top it just returns their string representation.
+func PathRelativeToTop(path Path) string {
+	if path == nil {
+		return "<nil path>"
+	}
+	p := path.String()
+	if w, ok := path.(WritablePath); ok {
+		buildDir := w.buildDir()
+		return StringPathRelativeToTop(buildDir, p)
+	}
+	return p
+}
+
+// PathsRelativeToTop creates a slice of strings where each string is the result of applying
+// PathRelativeToTop to the corresponding Path in the input slice.
+func PathsRelativeToTop(paths Paths) []string {
+	var result []string
+	for _, path := range paths {
+		relative := PathRelativeToTop(path)
+		result = append(result, relative)
+	}
+	return result
+}
+
+// StringPathRelativeToTop returns a string representation of the path relative to a notional top
+// directory.
+//
+// A standard build has the following structure:
+//   ../top/
+//          out/ - make install files go here.
+//          out/soong - this is the buildDir passed to NewTestConfig()
+//          ... - the source files
+//
+// This function converts a path so that it appears relative to the ../top/ directory, i.e.
+// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
+//   relative path "out/<path>"
+// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
+//   converted into the top relative path "out/soong/<path>".
+// * Source paths are already relative to the top.
+//
+// This is provided for processing paths that have already been converted into a string, e.g. paths
+// in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
+// which it can try and relativize paths. PathRelativeToTop must be used for process Path objects.
+func StringPathRelativeToTop(soongOutDir string, path string) string {
+
+	// A relative path must be a source path so leave it as it is.
+	if !filepath.IsAbs(path) {
+		return path
+	}
+
+	// Check to see if the path is relative to the soong out dir.
+	rel, isRel, err := maybeRelErr(soongOutDir, path)
+	if err != nil {
+		panic(err)
+	}
+
+	if isRel {
+		// The path is in the soong out dir so indicate that in the relative path.
+		return filepath.Join("out/soong", rel)
+	}
+
+	// Check to see if the path is relative to the top level out dir.
+	outDir := filepath.Dir(soongOutDir)
+	rel, isRel, err = maybeRelErr(outDir, path)
+	if err != nil {
+		panic(err)
+	}
+
+	if isRel {
+		// The path is in the out dir so indicate that in the relative path.
+		return filepath.Join("out", rel)
+	}
+
+	// This should never happen.
+	panic(fmt.Errorf("internal error: absolute path %s is not relative to the out dir %s", path, outDir))
+}
+
+// StringPathsRelativeToTop creates a slice of strings where each string is the result of applying
+// StringPathRelativeToTop to the corresponding string path in the input slice.
+//
+// This is provided for processing paths that have already been converted into a string, e.g. paths
+// in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
+// which it can try and relativize paths. PathsRelativeToTop must be used for process Paths objects.
+func StringPathsRelativeToTop(soongOutDir string, paths []string) []string {
+	var result []string
+	for _, path := range paths {
+		relative := StringPathRelativeToTop(soongOutDir, path)
+		result = append(result, relative)
+	}
+	return result
+}
diff --git a/android/variable.go b/android/variable.go
index be12a0a..08fa12c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -314,6 +314,11 @@
 	DirectedRecoverySnapshot bool            `json:",omitempty"`
 	RecoverySnapshotModules  map[string]bool `json:",omitempty"`
 
+	VendorSnapshotDirsIncluded   []string `json:",omitempty"`
+	VendorSnapshotDirsExcluded   []string `json:",omitempty"`
+	RecoverySnapshotDirsExcluded []string `json:",omitempty"`
+	RecoverySnapshotDirsIncluded []string `json:",omitempty"`
+
 	BoardVendorSepolicyDirs      []string `json:",omitempty"`
 	BoardOdmSepolicyDirs         []string `json:",omitempty"`
 	BoardReqdMaskPolicy          []string `json:",omitempty"`
@@ -421,6 +426,9 @@
 		Malloc_zero_contents:         boolPtr(true),
 		Malloc_pattern_fill_contents: boolPtr(false),
 		Safestack:                    boolPtr(false),
+
+		BootJars:          ConfiguredJarList{apexes: []string{}, jars: []string{}},
+		UpdatableBootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}},
 	}
 
 	if runtime.GOOS == "linux" {
diff --git a/android/variable_test.go b/android/variable_test.go
index 393fe01..d16e458 100644
--- a/android/variable_test.go
+++ b/android/variable_test.go
@@ -181,32 +181,30 @@
 			name: "baz",
 		}
 	`
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.TestProductVariables.Eng = proptools.BoolPtr(true)
 
-	ctx := NewTestContext(config)
-	// A module type that has a srcs property but not a cflags property.
-	ctx.RegisterModuleType("module1", testProductVariableModuleFactoryFactory(&struct {
-		Srcs []string
-	}{}))
-	// A module type that has a cflags property but not a srcs property.
-	ctx.RegisterModuleType("module2", testProductVariableModuleFactoryFactory(&struct {
-		Cflags []string
-	}{}))
-	// A module type that does not have any properties that match product_variables.
-	ctx.RegisterModuleType("module3", testProductVariableModuleFactoryFactory(&struct {
-		Foo []string
-	}{}))
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("variable", VariableMutator).Parallel()
-	})
-
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
+	emptyTestFixtureFactory.RunTest(t,
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.Eng = proptools.BoolPtr(true)
+		}),
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			// A module type that has a srcs property but not a cflags property.
+			ctx.RegisterModuleType("module1", testProductVariableModuleFactoryFactory(&struct {
+				Srcs []string
+			}{}))
+			// A module type that has a cflags property but not a srcs property.
+			ctx.RegisterModuleType("module2", testProductVariableModuleFactoryFactory(&struct {
+				Cflags []string
+			}{}))
+			// A module type that does not have any properties that match product_variables.
+			ctx.RegisterModuleType("module3", testProductVariableModuleFactoryFactory(&struct {
+				Foo []string
+			}{}))
+			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.BottomUp("variable", VariableMutator).Parallel()
+			})
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 }
 
 var testProductVariableDefaultsProperties = struct {
@@ -290,32 +288,23 @@
 		}
 	`
 
-	config := TestConfig(buildDir, nil, bp, nil)
-	config.TestProductVariables.Eng = boolPtr(true)
+	result := emptyTestFixtureFactory.RunTest(t,
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.Eng = boolPtr(true)
+		}),
+		PrepareForTestWithDefaults,
+		PrepareForTestWithVariables,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("test", productVariablesDefaultsTestModuleFactory)
+			ctx.RegisterModuleType("defaults", productVariablesDefaultsTestDefaultsFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	)
 
-	ctx := NewTestContext(config)
-
-	ctx.RegisterModuleType("test", productVariablesDefaultsTestModuleFactory)
-	ctx.RegisterModuleType("defaults", productVariablesDefaultsTestDefaultsFactory)
-
-	ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
-	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("variable", VariableMutator).Parallel()
-	})
-
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	FailIfErrored(t, errs)
-
-	foo := ctx.ModuleForTests("foo", "").Module().(*productVariablesDefaultsTestModule)
+	foo := result.ModuleForTests("foo", "").Module().(*productVariablesDefaultsTestModule)
 
 	want := []string{"defaults", "module", "product_variable_defaults", "product_variable_module"}
-	if g, w := foo.properties.Foo, want; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected foo %q, got %q", w, g)
-	}
+	AssertDeepEquals(t, "foo", want, foo.properties.Foo)
 }
 
 func BenchmarkSliceToTypeArray(b *testing.B) {
diff --git a/apex/apex.go b/apex/apex.go
index e5b5c92..429465d 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -102,6 +102,9 @@
 	// List of prebuilt files that are embedded inside this APEX bundle.
 	Prebuilts []string
 
+	// List of platform_compat_config files that are embedded inside this APEX bundle.
+	Compat_configs []string
+
 	// List of BPF programs inside this APEX bundle.
 	Bpfs []string
 
@@ -546,23 +549,35 @@
 	// Determines if the dependent will be part of the APEX payload. Can be false for the
 	// dependencies to the signing key module, etc.
 	payload bool
+
+	// True if the dependent can only be a source module, false if a prebuilt module is a suitable
+	// replacement. This is needed because some prebuilt modules do not provide all the information
+	// needed by the apex.
+	sourceOnly bool
 }
 
+func (d dependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return !d.sourceOnly
+}
+
+var _ android.ReplaceSourceWithPrebuilt = &dependencyTag{}
+
 var (
-	androidAppTag  = dependencyTag{name: "androidApp", payload: true}
-	bpfTag         = dependencyTag{name: "bpf", payload: true}
-	certificateTag = dependencyTag{name: "certificate"}
-	executableTag  = dependencyTag{name: "executable", payload: true}
-	fsTag          = dependencyTag{name: "filesystem", payload: true}
-	bootImageTag   = dependencyTag{name: "bootImage", payload: true}
-	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
-	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
-	keyTag         = dependencyTag{name: "key"}
-	prebuiltTag    = dependencyTag{name: "prebuilt", payload: true}
-	rroTag         = dependencyTag{name: "rro", payload: true}
-	sharedLibTag   = dependencyTag{name: "sharedLib", payload: true}
-	testForTag     = dependencyTag{name: "test for"}
-	testTag        = dependencyTag{name: "test", payload: true}
+	androidAppTag   = dependencyTag{name: "androidApp", payload: true}
+	bpfTag          = dependencyTag{name: "bpf", payload: true}
+	certificateTag  = dependencyTag{name: "certificate"}
+	executableTag   = dependencyTag{name: "executable", payload: true}
+	fsTag           = dependencyTag{name: "filesystem", payload: true}
+	bootImageTag    = dependencyTag{name: "bootImage", payload: true}
+	compatConfigTag = dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
+	javaLibTag      = dependencyTag{name: "javaLib", payload: true}
+	jniLibTag       = dependencyTag{name: "jniLib", payload: true}
+	keyTag          = dependencyTag{name: "key"}
+	prebuiltTag     = dependencyTag{name: "prebuilt", payload: true}
+	rroTag          = dependencyTag{name: "rro", payload: true}
+	sharedLibTag    = dependencyTag{name: "sharedLib", payload: true}
+	testForTag      = dependencyTag{name: "test for"}
+	testTag         = dependencyTag{name: "test", payload: true}
 )
 
 // TODO(jiyong): shorten this function signature
@@ -737,6 +752,7 @@
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
+	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
 
 	if a.artApex {
 		// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
@@ -830,6 +846,19 @@
 		if !ok || !am.CanHaveApexVariants() {
 			return false
 		}
+		depTag := mctx.OtherModuleDependencyTag(child)
+
+		// Check to see if the tag always requires that the child module has an apex variant for every
+		// apex variant of the parent module. If it does not then it is still possible for something
+		// else, e.g. the DepIsInSameApex(...) method to decide that a variant is required.
+		if required, ok := depTag.(android.AlwaysRequireApexVariantTag); ok && required.AlwaysRequireApexVariant() {
+			return true
+		}
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			// The tag defines a dependency that never requires the child module to be part of the same
+			// apex as the parent so it does not need an apex variant created.
+			return false
+		}
 		if !parent.(android.DepIsInSameApex).DepIsInSameApex(mctx, child) {
 			return false
 		}
@@ -1735,10 +1764,14 @@
 			case prebuiltTag:
 				if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
 					filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
-				} else if prebuilt, ok := child.(java.PlatformCompatConfigIntf); ok {
-					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, prebuilt, depName))
 				} else {
-					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc and not a platform_compat_config module", depName)
+					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc module", depName)
+				}
+			case compatConfigTag:
+				if compatConfig, ok := child.(java.PlatformCompatConfigIntf); ok {
+					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, compatConfig, depName))
+				} else {
+					ctx.PropertyErrorf("compat_configs", "%q is not a platform_compat_config module", depName)
 				}
 			case testTag:
 				if ccTest, ok := child.(*cc.Module); ok {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 5e9ab45..ac98b07 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -127,6 +127,8 @@
 	config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
 }
 
+var emptyFixtureFactory = android.NewFixtureFactory(&buildDir)
+
 var apexFixtureFactory = android.NewFixtureFactory(
 	&buildDir,
 	// General preparers in alphabetical order as test infrastructure will enforce correct
@@ -520,7 +522,7 @@
 		}
 
 		rust_binary {
-		        name: "foo.rust",
+			name: "foo.rust",
 			srcs: ["foo.rs"],
 			rlibs: ["libfoo.rlib.rust"],
 			dylibs: ["libfoo.dylib.rust"],
@@ -528,14 +530,14 @@
 		}
 
 		rust_library_rlib {
-		        name: "libfoo.rlib.rust",
+			name: "libfoo.rlib.rust",
 			srcs: ["foo.rs"],
 			crate_name: "foo",
 			apex_available: ["myapex"],
 		}
 
 		rust_library_dylib {
-		        name: "libfoo.dylib.rust",
+			name: "libfoo.dylib.rust",
 			srcs: ["foo.rs"],
 			crate_name: "foo",
 			apex_available: ["myapex"],
@@ -730,14 +732,12 @@
 
 	fullDepsInfo := strings.Split(ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("depsinfo/fulllist.txt").Args["content"], "\\n")
 	ensureListContains(t, fullDepsInfo, "  myjar(minSdkVersion:(no version)) <- myapex")
-	ensureListContains(t, fullDepsInfo, "  mylib(minSdkVersion:(no version)) <- myapex")
 	ensureListContains(t, fullDepsInfo, "  mylib2(minSdkVersion:(no version)) <- mylib")
 	ensureListContains(t, fullDepsInfo, "  myotherjar(minSdkVersion:(no version)) <- myjar")
 	ensureListContains(t, fullDepsInfo, "  mysharedjar(minSdkVersion:(no version)) (external) <- myjar")
 
 	flatDepsInfo := strings.Split(ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("depsinfo/flatlist.txt").Args["content"], "\\n")
 	ensureListContains(t, flatDepsInfo, "myjar(minSdkVersion:(no version))")
-	ensureListContains(t, flatDepsInfo, "mylib(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "mylib2(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "myotherjar(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "mysharedjar(minSdkVersion:(no version)) (external)")
@@ -1236,13 +1236,9 @@
 	ensureNotContains(t, libFooStubsLdFlags, "libbar.so")
 
 	fullDepsInfo := strings.Split(ctx.ModuleForTests("myapex2", "android_common_myapex2_image").Output("depsinfo/fulllist.txt").Args["content"], "\\n")
-	ensureListContains(t, fullDepsInfo, "  mylib(minSdkVersion:(no version)) <- myapex2")
-	ensureListContains(t, fullDepsInfo, "  libbaz(minSdkVersion:(no version)) <- mylib")
 	ensureListContains(t, fullDepsInfo, "  libfoo(minSdkVersion:(no version)) (external) <- mylib")
 
 	flatDepsInfo := strings.Split(ctx.ModuleForTests("myapex2", "android_common_myapex2_image").Output("depsinfo/flatlist.txt").Args["content"], "\\n")
-	ensureListContains(t, flatDepsInfo, "mylib(minSdkVersion:(no version))")
-	ensureListContains(t, flatDepsInfo, "libbaz(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "libfoo(minSdkVersion:(no version)) (external)")
 }
 
@@ -1316,9 +1312,10 @@
 
 }
 
-func TestRuntimeApexShouldInstallHwasanIfLibcDependsOnIt(t *testing.T) {
-	ctx := testApex(t, "", func(fs map[string][]byte, config android.Config) {
-		bp := `
+var prepareForTestOfRuntimeApexWithHwasan = android.GroupFixturePreparers(
+	cc.PrepareForTestWithCcBuildComponents,
+	PrepareForTestWithApexBuildComponents,
+	android.FixtureAddTextFile("bionic/apex/Android.bp", `
 		apex {
 			name: "com.android.runtime",
 			key: "com.android.runtime.key",
@@ -1331,7 +1328,12 @@
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}
+	`),
+	android.FixtureAddFile("system/sepolicy/apex/com.android.runtime-file_contexts", nil),
+)
 
+func TestRuntimeApexShouldInstallHwasanIfLibcDependsOnIt(t *testing.T) {
+	result := emptyFixtureFactory.Extend(prepareForTestOfRuntimeApexWithHwasan).RunTestWithBp(t, `
 		cc_library {
 			name: "libc",
 			no_libcrt: true,
@@ -1358,12 +1360,8 @@
 			sanitize: {
 				never: true,
 			},
-		}
-		`
-		// override bp to use hard-coded names: com.android.runtime and libc
-		fs["Android.bp"] = []byte(bp)
-		fs["system/sepolicy/apex/com.android.runtime-file_contexts"] = nil
-	})
+		}	`)
+	ctx := result.TestContext
 
 	ensureExactContents(t, ctx, "com.android.runtime", "android_common_hwasan_com.android.runtime_image", []string{
 		"lib64/bionic/libc.so",
@@ -1381,21 +1379,12 @@
 }
 
 func TestRuntimeApexShouldInstallHwasanIfHwaddressSanitized(t *testing.T) {
-	ctx := testApex(t, "", func(fs map[string][]byte, config android.Config) {
-		bp := `
-		apex {
-			name: "com.android.runtime",
-			key: "com.android.runtime.key",
-			native_shared_libs: ["libc"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "com.android.runtime.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
+	result := emptyFixtureFactory.Extend(
+		prepareForTestOfRuntimeApexWithHwasan,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"hwaddress"}
+		}),
+	).RunTestWithBp(t, `
 		cc_library {
 			name: "libc",
 			no_libcrt: true,
@@ -1419,13 +1408,8 @@
 				never: true,
 			},
 		}
-		`
-		// override bp to use hard-coded names: com.android.runtime and libc
-		fs["Android.bp"] = []byte(bp)
-		fs["system/sepolicy/apex/com.android.runtime-file_contexts"] = nil
-
-		config.TestProductVariables.SanitizeDevice = []string{"hwaddress"}
-	})
+		`)
+	ctx := result.TestContext
 
 	ensureExactContents(t, ctx, "com.android.runtime", "android_common_hwasan_com.android.runtime_image", []string{
 		"lib64/bionic/libc.so",
@@ -2049,7 +2033,7 @@
 				java_library {
 					name: "myjar",
 					srcs: ["foo/bar/MyClass.java"],
-					sdk_version: "core_platform",
+					sdk_version: "test_current",
 					apex_available: ["myapex"],
 				}
 			`,
@@ -2096,13 +2080,16 @@
 				java_library {
 					name: "myjar",
 					srcs: ["foo/bar/MyClass.java"],
-					sdk_version: "core_platform",
+					sdk_version: "test_current",
 					apex_available: ["myapex"],
 				}
 			`,
 		},
 		{
-			name:          "Updatable apex with non-stable transitive dep",
+			name: "Updatable apex with non-stable transitive dep",
+			// This is not actually detecting that the transitive dependency is unstable, rather it is
+			// detecting that the transitive dependency is building against a wider API surface than the
+			// module that depends on it is using.
 			expectedError: "compiles against Android API, but dependency \"transitive-jar\" is compiling against private API.",
 			bp: `
 				apex {
@@ -6036,11 +6023,13 @@
 }
 
 func TestCompatConfig(t *testing.T) {
-	ctx := testApex(t, `
+	result := apexFixtureFactory.
+		Extend(java.PrepareForTestWithPlatformCompatConfig).
+		RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			prebuilts: ["myjar-platform-compat-config"],
+			compat_configs: ["myjar-platform-compat-config"],
 			java_libs: ["myjar"],
 			updatable: false,
 		}
@@ -6063,7 +6052,15 @@
 			system_modules: "none",
 			apex_available: [ "myapex" ],
 		}
+
+		// Make sure that a preferred prebuilt does not affect the apex contents.
+		prebuilt_platform_compat_config {
+			name: "myjar-platform-compat-config",
+			metadata: "compat-config/metadata.xml",
+			prefer: true,
+		}
 	`)
+	ctx := result.TestContext
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"etc/compatconfig/myjar-platform-compat-config.xml",
 		"javalib/myjar.jar",
diff --git a/apex/builder.go b/apex/builder.go
index 2663a67..da800d4 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -926,9 +926,15 @@
 			return !externalDep
 		}
 
+		// Skip dependencies that are only available to APEXes; they are developed with updatability
+		// in mind and don't need manual approval.
+		if to.(android.ApexModule).NotAvailableForPlatform() {
+			return !externalDep
+		}
+
 		depTag := ctx.OtherModuleDependencyTag(to)
+		// Check to see if dependency been marked to skip the dependency check
 		if skipDepCheck, ok := depTag.(android.SkipApexAllowedDependenciesCheck); ok && skipDepCheck.SkipApexAllowedDependenciesCheck() {
-			// Check to see if dependency been marked to skip the dependency check
 			return !externalDep
 		}
 
diff --git a/bazel/Android.bp b/bazel/Android.bp
index 117fd46..b7c185a 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -12,6 +12,7 @@
     ],
     testSrcs: [
         "aquery_test.go",
+        "properties_test.go",
     ],
     pluginFor: [
         "soong_build",
diff --git a/bazel/cquery/Android.bp b/bazel/cquery/Android.bp
new file mode 100644
index 0000000..3a71e9c
--- /dev/null
+++ b/bazel/cquery/Android.bp
@@ -0,0 +1,14 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-cquery",
+    pkgPath: "android/soong/bazel/cquery",
+    srcs: [
+        "request_type.go",
+    ],
+    pluginFor: [
+        "soong_build",
+    ],
+}
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
new file mode 100644
index 0000000..864db3d
--- /dev/null
+++ b/bazel/cquery/request_type.go
@@ -0,0 +1,110 @@
+package cquery
+
+import (
+	"strings"
+)
+
+var (
+	GetOutputFiles                 RequestType = &getOutputFilesRequestType{}
+	GetCcObjectFiles               RequestType = &getCcObjectFilesRequestType{}
+	GetOutputFilesAndCcObjectFiles RequestType = &getOutputFilesAndCcObjectFilesType{}
+)
+
+type GetOutputFilesAndCcObjectFiles_Result struct {
+	OutputFiles   []string
+	CcObjectFiles []string
+}
+
+var RequestTypes []RequestType = []RequestType{
+	GetOutputFiles, GetCcObjectFiles, GetOutputFilesAndCcObjectFiles}
+
+type RequestType interface {
+	// Name returns a string name for this request type. Such request type names must be unique,
+	// and must only consist of alphanumeric characters.
+	Name() string
+
+	// StarlarkFunctionBody returns a straark function body to process this request type.
+	// The returned string is the body of a Starlark function which obtains
+	// all request-relevant information about a target and returns a string containing
+	// this information.
+	// The function should have the following properties:
+	//   - `target` is the only parameter to this function (a configured target).
+	//   - The return value must be a string.
+	//   - The function body should not be indented outside of its own scope.
+	StarlarkFunctionBody() string
+
+	// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
+	// The given rawString must correspond to the string output which was created by evaluating the
+	// Starlark given in StarlarkFunctionBody.
+	// The type of this value depends on the request type; it is up to the caller to
+	// cast to the correct type.
+	ParseResult(rawString string) interface{}
+}
+
+type getOutputFilesRequestType struct{}
+
+func (g getOutputFilesRequestType) Name() string {
+	return "getOutputFiles"
+}
+
+func (g getOutputFilesRequestType) StarlarkFunctionBody() string {
+	return "return ', '.join([f.path for f in target.files.to_list()])"
+}
+
+func (g getOutputFilesRequestType) ParseResult(rawString string) interface{} {
+	return strings.Split(rawString, ", ")
+}
+
+type getCcObjectFilesRequestType struct{}
+
+func (g getCcObjectFilesRequestType) Name() string {
+	return "getCcObjectFiles"
+}
+
+func (g getCcObjectFilesRequestType) StarlarkFunctionBody() string {
+	return `
+result = []
+linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+
+for linker_input in linker_inputs:
+  for library in linker_input.libraries:
+    for object in library.objects:
+      result += [object.path]
+return ', '.join(result)`
+}
+
+func (g getCcObjectFilesRequestType) ParseResult(rawString string) interface{} {
+	return strings.Split(rawString, ", ")
+}
+
+type getOutputFilesAndCcObjectFilesType struct{}
+
+func (g getOutputFilesAndCcObjectFilesType) Name() string {
+	return "getOutputFilesAndCcObjectFiles"
+}
+
+func (g getOutputFilesAndCcObjectFilesType) StarlarkFunctionBody() string {
+	return `
+outputFiles = [f.path for f in target.files.to_list()]
+
+ccObjectFiles = []
+linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+
+for linker_input in linker_inputs:
+  for library in linker_input.libraries:
+    for object in library.objects:
+      ccObjectFiles += [object.path]
+return ', '.join(outputFiles) + "|" + ', '.join(ccObjectFiles)`
+}
+
+func (g getOutputFilesAndCcObjectFilesType) ParseResult(rawString string) interface{} {
+	var outputFiles []string
+	var ccObjects []string
+
+	splitString := strings.Split(rawString, "|")
+	outputFilesString := splitString[0]
+	ccObjectsString := splitString[1]
+	outputFiles = strings.Split(outputFilesString, ", ")
+	ccObjects = strings.Split(ccObjectsString, ", ")
+	return GetOutputFilesAndCcObjectFiles_Result{outputFiles, ccObjects}
+}
diff --git a/bazel/properties.go b/bazel/properties.go
index a5ffa55..abdc107 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -14,22 +14,10 @@
 
 package bazel
 
-import "fmt"
-
-type bazelModuleProperties struct {
-	// The label of the Bazel target replacing this Soong module.
-	Label string
-
-	// If true, bp2build will generate the converted Bazel target for this module.
-	Bp2build_available bool
-}
-
-// Properties contains common module properties for Bazel migration purposes.
-type Properties struct {
-	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
-	// this Soong module.
-	Bazel_module bazelModuleProperties
-}
+import (
+	"fmt"
+	"sort"
+)
 
 // BazelTargetModuleProperties contain properties and metadata used for
 // Blueprint to BUILD file conversion.
@@ -66,6 +54,28 @@
 	}
 }
 
+func UniqueBazelLabels(originalLabels []Label) []Label {
+	uniqueLabelsSet := make(map[Label]bool)
+	for _, l := range originalLabels {
+		uniqueLabelsSet[l] = true
+	}
+	var uniqueLabels []Label
+	for l, _ := range uniqueLabelsSet {
+		uniqueLabels = append(uniqueLabels, l)
+	}
+	sort.SliceStable(uniqueLabels, func(i, j int) bool {
+		return uniqueLabels[i].Label < uniqueLabels[j].Label
+	})
+	return uniqueLabels
+}
+
+func UniqueBazelLabelList(originalLabelList LabelList) LabelList {
+	var uniqueLabelList LabelList
+	uniqueLabelList.Includes = UniqueBazelLabels(originalLabelList.Includes)
+	uniqueLabelList.Excludes = UniqueBazelLabels(originalLabelList.Excludes)
+	return uniqueLabelList
+}
+
 // StringListAttribute corresponds to the string_list Bazel attribute type with
 // support for additional metadata, like configurations.
 type StringListAttribute struct {
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
new file mode 100644
index 0000000..0fcb904
--- /dev/null
+++ b/bazel/properties_test.go
@@ -0,0 +1,89 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bazel
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestUniqueBazelLabels(t *testing.T) {
+	testCases := []struct {
+		originalLabels       []Label
+		expectedUniqueLabels []Label
+	}{
+		{
+			originalLabels: []Label{
+				{Label: "a"},
+				{Label: "b"},
+				{Label: "a"},
+				{Label: "c"},
+			},
+			expectedUniqueLabels: []Label{
+				{Label: "a"},
+				{Label: "b"},
+				{Label: "c"},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualUniqueLabels := UniqueBazelLabels(tc.originalLabels)
+		if !reflect.DeepEqual(tc.expectedUniqueLabels, actualUniqueLabels) {
+			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabels, actualUniqueLabels)
+		}
+	}
+}
+
+func TestUniqueBazelLabelList(t *testing.T) {
+	testCases := []struct {
+		originalLabelList       LabelList
+		expectedUniqueLabelList LabelList
+	}{
+		{
+			originalLabelList: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "a"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+			expectedUniqueLabelList: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualUniqueLabelList := UniqueBazelLabelList(tc.originalLabelList)
+		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
+			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
+		}
+	}
+}
diff --git a/bootstrap_test.sh b/bootstrap_test.sh
new file mode 100755
index 0000000..87f5e31
--- /dev/null
+++ b/bootstrap_test.sh
@@ -0,0 +1,319 @@
+#!/bin/bash -eu
+
+# This test exercises the bootstrapping process of the build system
+# in a source tree that only contains enough files for Bazel and Soong to work.
+
+HARDWIRED_MOCK_TOP=
+# Uncomment this to be able to view the source tree after a test is run
+# HARDWIRED_MOCK_TOP=/tmp/td
+
+REAL_TOP="$(readlink -f "$(dirname "$0")"/../..)"
+
+function fail {
+  echo ERROR: $1
+  exit 1
+}
+
+function copy_directory() {
+  local dir="$1"
+  local parent="$(dirname "$dir")"
+
+  mkdir -p "$MOCK_TOP/$parent"
+  cp -R "$REAL_TOP/$dir" "$MOCK_TOP/$parent"
+}
+
+function symlink_file() {
+  local file="$1"
+
+  mkdir -p "$MOCK_TOP/$(dirname "$file")"
+  ln -s "$REAL_TOP/$file" "$MOCK_TOP/$file"
+}
+
+function symlink_directory() {
+  local dir="$1"
+
+  mkdir -p "$MOCK_TOP/$dir"
+  # We need to symlink the contents of the directory individually instead of
+  # using one symlink for the whole directory because finder.go doesn't follow
+  # symlinks when looking for Android.bp files
+  for i in $(ls "$REAL_TOP/$dir"); do
+    local target="$MOCK_TOP/$dir/$i"
+    local source="$REAL_TOP/$dir/$i"
+
+    if [[ -e "$target" ]]; then
+      if [[ ! -d "$source" || ! -d "$target" ]]; then
+        fail "Trying to symlink $dir twice"
+      fi
+    else
+      ln -s "$REAL_TOP/$dir/$i" "$MOCK_TOP/$dir/$i";
+    fi
+  done
+}
+
+function setup_bazel() {
+  copy_directory build/bazel
+
+  symlink_directory prebuilts/bazel
+  symlink_directory prebuilts/jdk
+
+  symlink_file WORKSPACE
+  symlink_file tools/bazel
+}
+
+function setup() {
+  if [[ ! -z "$HARDWIRED_MOCK_TOP" ]]; then
+    MOCK_TOP="$HARDWIRED_MOCK_TOP"
+    rm -fr "$MOCK_TOP"
+    mkdir -p "$MOCK_TOP"
+  else
+    MOCK_TOP=$(mktemp -t -d st.XXXXX)
+    trap 'echo cd / && echo rm -fr "$MOCK_TOP"' EXIT
+  fi
+
+  echo "Test case: ${FUNCNAME[1]}, mock top path: $MOCK_TOP"
+  cd "$MOCK_TOP"
+
+  copy_directory build/blueprint
+  copy_directory build/soong
+
+  symlink_directory prebuilts/go
+  symlink_directory prebuilts/build-tools
+  symlink_directory external/golang-protobuf
+
+  touch "$MOCK_TOP/Android.bp"
+
+  export ALLOW_MISSING_DEPENDENCIES=true
+
+  mkdir -p out/soong
+}
+
+function run_soong() {
+  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests
+}
+
+function test_smoke {
+  setup
+  run_soong
+}
+
+function test_bazel_smoke {
+  setup
+  setup_bazel
+
+  tools/bazel info
+
+}
+function test_null_build() {
+  setup
+  run_soong
+  local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  run_soong
+  local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
+
+  if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
+    # Bootstrapping is always done. It doesn't take a measurable amount of time.
+    fail "Bootstrap Ninja file did not change on null build"
+  fi
+
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "Output Ninja file changed on null build"
+  fi
+}
+
+function test_soong_build_rebuilt_if_blueprint_changes() {
+  setup
+  run_soong
+  local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+
+  sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
+
+  run_soong
+  local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+
+  if [[ "$mtime1" == "$mtime2" ]]; then
+    fail "Bootstrap Ninja file did not change"
+  fi
+}
+
+function test_change_android_bp() {
+  setup
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_little_binary_host",
+  srcs: ["my_little_binary_host.py"]
+}
+EOF
+  touch a/my_little_binary_host.py
+  run_soong
+
+  grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja || fail "module not found"
+
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_great_binary_host",
+  srcs: ["my_great_binary_host.py"]
+}
+EOF
+  touch a/my_great_binary_host.py
+  run_soong
+
+  grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja && fail "old module found"
+  grep -q "^# Module:.*my_great_binary_host" out/soong/build.ninja || fail "new module not found"
+}
+
+
+function test_add_android_bp() {
+  setup
+  run_soong
+  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_little_binary_host",
+  srcs: ["my_little_binary_host.py"]
+}
+EOF
+  touch a/my_little_binary_host.py
+  run_soong
+
+  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$mtime1" == "$mtime2" ]]; then
+    fail "Output Ninja file did not change"
+  fi
+
+  grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "New module not in output"
+
+  run_soong
+}
+
+function test_delete_android_bp() {
+  setup
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_little_binary_host",
+  srcs: ["my_little_binary_host.py"]
+}
+EOF
+  touch a/my_little_binary_host.py
+  run_soong
+
+  grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "Module not in output"
+
+  rm a/Android.bp
+  run_soong
+
+  grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja && fail "Old module in output"
+}
+
+function test_add_file_to_glob() {
+  setup
+
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+python_binary_host {
+  name: "my_little_binary_host",
+  srcs: ["*.py"],
+}
+EOF
+  touch a/my_little_binary_host.py
+  run_soong
+  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  touch a/my_little_library.py
+  run_soong
+
+  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$mtime1" == "$mtime2" ]]; then
+    fail "Output Ninja file did not change"
+  fi
+
+  grep -q my_little_library.py out/soong/build.ninja || fail "new file is not in output"
+}
+
+function test_add_file_to_soong_build() {
+  setup
+  run_soong
+  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  mkdir -p a
+  cat > a/Android.bp <<'EOF'
+bootstrap_go_package {
+  name: "picard-soong-rules",
+  pkgPath: "android/soong/picard",
+  deps: [
+    "blueprint",
+    "soong",
+    "soong-android",
+  ],
+  srcs: [
+    "picard.go",
+  ],
+  pluginFor: ["soong_build"],
+}
+EOF
+
+  cat > a/picard.go <<'EOF'
+package picard
+
+import (
+  "android/soong/android"
+  "github.com/google/blueprint"
+)
+
+var (
+  pctx = android.NewPackageContext("picard")
+)
+
+func init() {
+  android.RegisterSingletonType("picard", PicardSingleton)
+}
+
+func PicardSingleton() android.Singleton {
+  return &picardSingleton{}
+}
+
+type picardSingleton struct{}
+
+func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+  picardRule := ctx.Rule(pctx, "picard",
+    blueprint.RuleParams{
+      Command: "echo Make it so. > ${out}",
+      CommandDeps: []string{},
+      Description: "Something quotable",
+    })
+
+  outputFile := android.PathForOutput(ctx, "picard", "picard.txt")
+  var deps android.Paths
+
+  ctx.Build(pctx, android.BuildParams{
+    Rule: picardRule,
+    Output: outputFile,
+    Inputs: deps,
+  })
+}
+
+EOF
+
+  run_soong
+  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$mtime1" == "$mtime2" ]]; then
+    fail "Output Ninja file did not change"
+  fi
+
+  grep -q "Make it so" out/soong/build.ninja || fail "New action not present"
+}
+
+test_bazel_smoke
+test_smoke
+test_null_build
+test_soong_build_rebuilt_if_blueprint_changes
+test_add_file_to_glob
+test_add_android_bp
+test_change_android_bp
+test_delete_android_bp
+test_add_file_to_soong_build
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 99d706c..c74f902 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -11,6 +11,7 @@
         "build_conversion.go",
         "bzl_conversion.go",
         "configurability.go",
+        "constants.go",
         "conversion.go",
         "metrics.go",
     ],
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 7169d7e..97a5137 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -22,7 +22,7 @@
 
 // The Bazel bp2build code generator is responsible for writing .bzl files that are equivalent to
 // Android.bp files that are capable of being built with Bazel.
-func Codegen(ctx CodegenContext) CodegenMetrics {
+func Codegen(ctx *CodegenContext) CodegenMetrics {
 	outputDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(outputDir)
 
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 7fa4996..9c98c76 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -99,9 +99,10 @@
 }
 
 type CodegenContext struct {
-	config  android.Config
-	context android.Context
-	mode    CodegenMode
+	config         android.Config
+	context        android.Context
+	mode           CodegenMode
+	additionalDeps []string
 }
 
 func (c *CodegenContext) Mode() CodegenMode {
@@ -137,14 +138,26 @@
 	}
 }
 
-func (ctx CodegenContext) AddNinjaFileDeps(...string) {}
-func (ctx CodegenContext) Config() android.Config     { return ctx.config }
-func (ctx CodegenContext) Context() android.Context   { return ctx.context }
+// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
+// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
+// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
+// call AdditionalNinjaDeps and add them manually to the ninja file.
+func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
+	ctx.additionalDeps = append(ctx.additionalDeps, deps...)
+}
+
+// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
+func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
+	return ctx.additionalDeps
+}
+
+func (ctx *CodegenContext) Config() android.Config   { return ctx.config }
+func (ctx *CodegenContext) Context() android.Context { return ctx.context }
 
 // NewCodegenContext creates a wrapper context that conforms to PathContext for
 // writing BUILD files in the output directory.
-func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) CodegenContext {
-	return CodegenContext{
+func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) *CodegenContext {
+	return &CodegenContext{
 		context: context,
 		config:  config,
 		mode:    mode,
@@ -163,12 +176,14 @@
 	return attributes
 }
 
-func GenerateBazelTargets(ctx CodegenContext) (map[string]BazelTargets, CodegenMetrics) {
+func GenerateBazelTargets(ctx *CodegenContext) (map[string]BazelTargets, CodegenMetrics) {
 	buildFileToTargets := make(map[string]BazelTargets)
+	buildFileToAppend := make(map[string]bool)
 
 	// Simple metrics tracking for bp2build
-	totalModuleCount := 0
-	ruleClassCount := make(map[string]int)
+	metrics := CodegenMetrics{
+		RuleClassCount: make(map[string]int),
+	}
 
 	bpCtx := ctx.Context()
 	bpCtx.VisitAllModules(func(m blueprint.Module) {
@@ -177,13 +192,29 @@
 
 		switch ctx.Mode() {
 		case Bp2Build:
-			if b, ok := m.(android.BazelTargetModule); !ok {
-				// Only include regular Soong modules (non-BazelTargetModules) into the total count.
-				totalModuleCount += 1
-				return
+			if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
+				metrics.handCraftedTargetCount += 1
+				metrics.TotalModuleCount += 1
+				pathToBuildFile := getBazelPackagePath(b)
+				// We are using the entire contents of handcrafted build file, so if multiple targets within
+				// a package have handcrafted targets, we only want to include the contents one time.
+				if _, exists := buildFileToAppend[pathToBuildFile]; exists {
+					return
+				}
+				var err error
+				t, err = getHandcraftedBuildContent(ctx, b, pathToBuildFile)
+				if err != nil {
+					panic(fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
+				}
+				// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
+				// something more targeted based on the rule type and target
+				buildFileToAppend[pathToBuildFile] = true
+			} else if btm, ok := m.(android.BazelTargetModule); ok {
+				t = generateBazelTarget(bpCtx, m, btm)
+				metrics.RuleClassCount[t.ruleClass] += 1
 			} else {
-				t = generateBazelTarget(bpCtx, m, b)
-				ruleClassCount[t.ruleClass] += 1
+				metrics.TotalModuleCount += 1
+				return
 			}
 		case QueryView:
 			// Blocklist certain module types from being generated.
@@ -200,17 +231,34 @@
 		buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
 	})
 
-	metrics := CodegenMetrics{
-		TotalModuleCount: totalModuleCount,
-		RuleClassCount:   ruleClassCount,
-	}
-
 	return buildFileToTargets, metrics
 }
 
-func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module, b android.BazelTargetModule) BazelTarget {
-	ruleClass := b.RuleClass()
-	bzlLoadLocation := b.BzlLoadLocation()
+func getBazelPackagePath(b android.Bazelable) string {
+	label := b.HandcraftedLabel()
+	pathToBuildFile := strings.TrimPrefix(label, "//")
+	pathToBuildFile = strings.Split(pathToBuildFile, ":")[0]
+	return pathToBuildFile
+}
+
+func getHandcraftedBuildContent(ctx *CodegenContext, b android.Bazelable, pathToBuildFile string) (BazelTarget, error) {
+	p := android.ExistentPathForSource(ctx, pathToBuildFile, HandcraftedBuildFileName)
+	if !p.Valid() {
+		return BazelTarget{}, fmt.Errorf("Could not find file %q for handcrafted target.", pathToBuildFile)
+	}
+	c, err := b.GetBazelBuildFileContents(ctx.Config(), pathToBuildFile, HandcraftedBuildFileName)
+	if err != nil {
+		return BazelTarget{}, err
+	}
+	// TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
+	return BazelTarget{
+		content: c,
+	}, nil
+}
+
+func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module, btm android.BazelTargetModule) BazelTarget {
+	ruleClass := btm.RuleClass()
+	bzlLoadLocation := btm.BzlLoadLocation()
 
 	// extract the bazel attributes from the module.
 	props := getBuildProperties(ctx, m)
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index aa4fc1d..89acbe9 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -1221,3 +1221,157 @@
 		}
 	}
 }
+
+func TestCombineBuildFilesBp2buildTargets(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		preArchMutators                    []android.RegisterMutatorFunc
+		depsMutators                       []android.RegisterMutatorFunc
+		bp                                 string
+		expectedBazelTargets               []string
+		fs                                 map[string]string
+		dir                                string
+	}{
+		{
+			description:                        "filegroup bazel_module.label",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			bp: `filegroup {
+    name: "fg_foo",
+    bazel_module: { label: "//other:fg_foo" },
+}`,
+			expectedBazelTargets: []string{
+				`// BUILD file`,
+			},
+			fs: map[string]string{
+				"other/BUILD.bazel": `// BUILD file`,
+			},
+		},
+		{
+			description:                        "multiple bazel_module.label same BUILD",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			bp: `filegroup {
+    name: "fg_foo",
+    bazel_module: { label: "//other:fg_foo" },
+}
+
+filegroup {
+    name: "foo",
+    bazel_module: { label: "//other:foo" },
+}`,
+			expectedBazelTargets: []string{
+				`// BUILD file`,
+			},
+			fs: map[string]string{
+				"other/BUILD.bazel": `// BUILD file`,
+			},
+		},
+		{
+			description:                        "filegroup bazel_module.label and bp2build",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			bp: `filegroup {
+    name: "fg_foo",
+    bazel_module: {
+      label: "//other:fg_foo",
+      bp2build_available: true,
+    },
+}`,
+			expectedBazelTargets: []string{
+				`filegroup(
+    name = "fg_foo",
+)`,
+				`// BUILD file`,
+			},
+			fs: map[string]string{
+				"other/BUILD.bazel": `// BUILD file`,
+			},
+		},
+		{
+			description:                        "filegroup bazel_module.label and filegroup bp2build",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			bp: `filegroup {
+    name: "fg_foo",
+    bazel_module: {
+      label: "//other:fg_foo",
+    },
+}
+
+filegroup {
+    name: "fg_bar",
+    bazel_module: {
+      bp2build_available: true,
+    },
+}`,
+			expectedBazelTargets: []string{
+				`filegroup(
+    name = "fg_bar",
+)`,
+				`// BUILD file`,
+			},
+			fs: map[string]string{
+				"other/BUILD.bazel": `// BUILD file`,
+			},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		fs := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.fs {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			fs[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
+		ctx := android.NewTestContext(config)
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		for _, m := range testCase.depsMutators {
+			ctx.DepsBp2BuildMutators(m)
+		}
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		checkDir := dir
+		if testCase.dir != "" {
+			checkDir = testCase.dir
+		}
+		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/constants.go b/bp2build/constants.go
new file mode 100644
index 0000000..23bca83
--- /dev/null
+++ b/bp2build/constants.go
@@ -0,0 +1,25 @@
+// 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 bp2build
+
+var (
+	// When both a BUILD and BUILD.bazel file are exist in the same package, the BUILD.bazel file will
+	// be preferred for use within a Bazel build.
+
+	// The file name used for automatically generated files. Files with this name are ignored by git.
+	GeneratedBuildFileName = "BUILD"
+	// The file name used for hand-crafted build targets.
+	HandcraftedBuildFileName = "BUILD.bazel"
+)
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 1225f2b..7877bb8 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -24,9 +24,9 @@
 	// Write top level files: WORKSPACE and BUILD. These files are empty.
 	files = append(files, newFile("", "WORKSPACE", ""))
 	// Used to denote that the top level directory is a package.
-	files = append(files, newFile("", "BUILD", ""))
+	files = append(files, newFile("", GeneratedBuildFileName, ""))
 
-	files = append(files, newFile(bazelRulesSubDir, "BUILD", ""))
+	files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, ""))
 
 	if mode == QueryView {
 		// These files are only used for queryview.
@@ -47,7 +47,14 @@
 	files := make([]BazelFile, 0, len(buildToTargets))
 	for _, dir := range android.SortedStringKeys(buildToTargets) {
 		targets := buildToTargets[dir]
-		sort.Slice(targets, func(i, j int) bool { return targets[i].name < targets[j].name })
+		sort.Slice(targets, func(i, j int) bool {
+			// this will cover all bp2build generated targets
+			if targets[i].name < targets[j].name {
+				return true
+			}
+			// give a strict ordering to content from hand-crafted targets
+			return targets[i].content < targets[j].content
+		})
 		content := soongModuleLoad
 		if mode == Bp2Build {
 			content = `# This file was automatically generated by bp2build for the Bazel migration project.
@@ -62,7 +69,7 @@
 			content += "\n\n"
 		}
 		content += targets.String()
-		files = append(files, newFile(dir, "BUILD", content))
+		files = append(files, newFile(dir, GeneratedBuildFileName, content))
 	}
 	return files
 }
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index 916129f..65b06c6 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -13,6 +13,9 @@
 
 	// Counts of generated Bazel targets per Bazel rule class
 	RuleClassCount map[string]int
+
+	// Total number of handcrafted targets
+	handCraftedTargetCount int
 }
 
 // Print the codegen metrics to stdout.
@@ -24,7 +27,8 @@
 		generatedTargetCount += count
 	}
 	fmt.Printf(
-		"[bp2build] Generated %d total BUILD targets from %d Android.bp modules.\n",
+		"[bp2build] Generated %d total BUILD targets and included %d handcrafted BUILD targets from %d Android.bp modules.\n",
 		generatedTargetCount,
+		metrics.handCraftedTargetCount,
 		metrics.TotalModuleCount)
 }
diff --git a/bp2build/testing.go b/bp2build/testing.go
index bd75a8f..a15a4a5 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -175,7 +175,7 @@
 }
 
 // Helper method for tests to easily access the targets in a dir.
-func generateBazelTargetsForDir(codegenCtx CodegenContext, dir string) BazelTargets {
+func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) BazelTargets {
 	buildFileToTargets, _ := GenerateBazelTargets(codegenCtx)
 	return buildFileToTargets[dir]
 }
diff --git a/bpf/bpf_test.go b/bpf/bpf_test.go
index eb0d8c8..0bf15db 100644
--- a/bpf/bpf_test.go
+++ b/bpf/bpf_test.go
@@ -15,7 +15,6 @@
 package bpf
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -23,34 +22,12 @@
 	"android/soong/cc"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "genrule_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
-
+	os.Exit(m.Run())
 }
 
 var bpfFactory = android.NewFixtureFactory(
-	&buildDir,
+	nil,
 	cc.PrepareForTestWithCcDefaultModules,
 	android.FixtureMergeMockFs(
 		map[string][]byte{
diff --git a/cc/cc.go b/cc/cc.go
index c335dac..cab7459 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1605,7 +1605,7 @@
 
 // Returns true if Bazel was successfully used for the analysis of this module.
 func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
-	bazelModuleLabel := c.GetBazelLabel()
+	bazelModuleLabel := c.GetBazelLabel(actx, c)
 	bazelActionsUsed := false
 	if c.bazelHandler != nil && actx.Config().BazelContext.BazelEnabled() && len(bazelModuleLabel) > 0 {
 		bazelActionsUsed = c.bazelHandler.generateBazelBuildActions(actx, bazelModuleLabel)
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 9fe5b17..5219ebc 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -440,7 +440,8 @@
 			command := builder.Command().BuiltTool("soong_zip").
 				Flag("-j").
 				FlagWithOutput("-o ", corpusZip)
-			command.FlagWithRspFileInputList("-r ", fuzzModule.corpus)
+			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
+			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
 			files = append(files, fileToZip{corpusZip, ""})
 		}
 
diff --git a/cc/library.go b/cc/library.go
index 6a3b876..22a36c6 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -415,38 +415,39 @@
 
 func (handler *staticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	outputPaths, objPaths, ok := bazelCtx.GetAllFilesAndCcObjectFiles(label, ctx.Arch().ArchType)
-	if ok {
-		if len(outputPaths) != 1 {
-			// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
-			// We should support this.
-			ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, objPaths)
-			return false
-		}
-		outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
-		handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
-
-		objFiles := make(android.Paths, len(objPaths))
-		for i, objPath := range objPaths {
-			objFiles[i] = android.PathForBazelOut(ctx, objPath)
-		}
-		objects := Objects{
-			objFiles: objFiles,
-		}
-
-		ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
-			StaticLibrary: outputFilePath,
-			ReuseObjects:  objects,
-			Objects:       objects,
-
-			// TODO(cparsons): Include transitive static libraries in this provider to support
-			// static libraries with deps.
-			TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
-				Direct(outputFilePath).
-				Build(),
-		})
-		handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+	outputPaths, objPaths, ok := bazelCtx.GetOutputFilesAndCcObjectFiles(label, ctx.Arch().ArchType)
+	if !ok {
+		return ok
 	}
+	if len(outputPaths) != 1 {
+		// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
+		// We should support this.
+		ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, objPaths)
+		return false
+	}
+	outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+
+	objFiles := make(android.Paths, len(objPaths))
+	for i, objPath := range objPaths {
+		objFiles[i] = android.PathForBazelOut(ctx, objPath)
+	}
+	objects := Objects{
+		objFiles: objFiles,
+	}
+
+	ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
+		StaticLibrary: outputFilePath,
+		ReuseObjects:  objects,
+		Objects:       objects,
+
+		// TODO(cparsons): Include transitive static libraries in this provider to support
+		// static libraries with deps.
+		TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
+			Direct(outputFilePath).
+			Build(),
+	})
+	handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
 	return ok
 }
 
diff --git a/cc/object.go b/cc/object.go
index 126bd65..f9e6d2d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -53,17 +53,8 @@
 }
 
 func (handler *objectBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
-	bazelCtx := ctx.Config().BazelContext
-	objPaths, ok := bazelCtx.GetCcObjectFiles(label, ctx.Arch().ArchType)
-	if ok {
-		if len(objPaths) != 1 {
-			ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
-			return false
-		}
-
-		handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
-	}
-	return ok
+	// TODO(b/181794963): restore mixed builds once cc_object incompatibility resolved
+	return false
 }
 
 type ObjectLinkerProperties struct {
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index f9aea0c..bbb8896 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -18,6 +18,7 @@
 // snapshot mutators and snapshot information maps which are also defined in this file.
 
 import (
+	"path/filepath"
 	"strings"
 
 	"android/soong/android"
@@ -45,9 +46,9 @@
 	// directory, such as device/, vendor/, etc.
 	//
 	// For a given snapshot (e.g., vendor, recovery, etc.) if
-	// isProprietaryPath(dir) returns true, then the module in dir will be
-	// built from sources.
-	isProprietaryPath(dir string) bool
+	// isProprietaryPath(dir, deviceConfig) returns true, then the module in dir
+	// will be built from sources.
+	isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool
 
 	// Whether to include VNDK in the snapshot for this image.
 	includeVndk() bool
@@ -82,6 +83,31 @@
 type vendorSnapshotImage struct{}
 type recoverySnapshotImage struct{}
 
+type directoryMap map[string]bool
+
+var (
+	// Modules under following directories are ignored. They are OEM's and vendor's
+	// proprietary modules(device/, kernel/, vendor/, and hardware/).
+	defaultDirectoryExcludedMap = directoryMap{
+		"device":   true,
+		"hardware": true,
+		"kernel":   true,
+		"vendor":   true,
+	}
+
+	// Modules under following directories are included as they are in AOSP,
+	// although hardware/ and kernel/ are normally for vendor's own.
+	defaultDirectoryIncludedMap = directoryMap{
+		"kernel/configs":              true,
+		"kernel/prebuilts":            true,
+		"kernel/tests":                true,
+		"hardware/interfaces":         true,
+		"hardware/libhardware":        true,
+		"hardware/libhardware_legacy": true,
+		"hardware/ril":                true,
+	}
+)
+
 func (vendorSnapshotImage) init(ctx android.RegistrationContext) {
 	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
 	ctx.RegisterModuleType("vendor_snapshot", vendorSnapshotFactory)
@@ -107,8 +133,25 @@
 	return m.IsVndkPrivate()
 }
 
-func (vendorSnapshotImage) isProprietaryPath(dir string) bool {
-	return isVendorProprietaryPath(dir)
+func isDirectoryExcluded(dir string, excludedMap directoryMap, includedMap directoryMap) bool {
+	if dir == "." || dir == "/" {
+		return false
+	}
+	if includedMap[dir] {
+		return false
+	} else if excludedMap[dir] {
+		return true
+	} else if defaultDirectoryIncludedMap[dir] {
+		return false
+	} else if defaultDirectoryExcludedMap[dir] {
+		return true
+	} else {
+		return isDirectoryExcluded(filepath.Dir(dir), excludedMap, includedMap)
+	}
+}
+
+func (vendorSnapshotImage) isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return isDirectoryExcluded(dir, deviceConfig.VendorSnapshotDirsExcludedMap(), deviceConfig.VendorSnapshotDirsIncludedMap())
 }
 
 // vendor snapshot includes static/header libraries with vndk: {enabled: true}.
@@ -172,8 +215,8 @@
 	return false
 }
 
-func (recoverySnapshotImage) isProprietaryPath(dir string) bool {
-	return isRecoveryProprietaryPath(dir)
+func (recoverySnapshotImage) isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return isDirectoryExcluded(dir, deviceConfig.RecoverySnapshotDirsExcludedMap(), deviceConfig.RecoverySnapshotDirsIncludedMap())
 }
 
 // recovery snapshot does NOT treat vndk specially.
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index c50ef45..c32fa36 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -80,7 +80,7 @@
 	}
 
 	for _, image := range []snapshotImage{vendorSnapshotImageSingleton, recoverySnapshotImageSingleton} {
-		if isSnapshotAware(ctx.DeviceConfig(), m, image.isProprietaryPath(ctx.ModuleDir()), apexInfo, image) {
+		if isSnapshotAware(ctx.DeviceConfig(), m, image.isProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()), apexInfo, image) {
 			return true
 		}
 	}
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 7077b71..4014fe0 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -90,73 +90,24 @@
 	fake bool
 }
 
-var (
-	// Modules under following directories are ignored. They are OEM's and vendor's
-	// proprietary modules(device/, kernel/, vendor/, and hardware/).
-	vendorProprietaryDirs = []string{
-		"device",
-		"kernel",
-		"vendor",
-		"hardware",
-	}
-
-	// Modules under following directories are ignored. They are OEM's and vendor's
-	// proprietary modules(device/, kernel/, vendor/, and hardware/).
-	recoveryProprietaryDirs = []string{
-		"device",
-		"hardware",
-		"kernel",
-		"vendor",
-	}
-
-	// Modules under following directories are included as they are in AOSP,
-	// although hardware/ and kernel/ are normally for vendor's own.
-	aospDirsUnderProprietary = []string{
-		"kernel/configs",
-		"kernel/prebuilts",
-		"kernel/tests",
-		"hardware/interfaces",
-		"hardware/libhardware",
-		"hardware/libhardware_legacy",
-		"hardware/ril",
-	}
-)
-
-// Determine if a dir under source tree is an SoC-owned proprietary directory, such as
-// device/, vendor/, etc.
-func isVendorProprietaryPath(dir string) bool {
-	return isProprietaryPath(dir, vendorProprietaryDirs)
+// Determine if a dir under source tree is an SoC-owned proprietary directory based
+// on vendor snapshot configuration
+// Examples: device/, vendor/
+func isVendorProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return VendorSnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
 }
 
-func isRecoveryProprietaryPath(dir string) bool {
-	return isProprietaryPath(dir, recoveryProprietaryDirs)
-}
-
-// Determine if a dir under source tree is an SoC-owned proprietary directory, such as
-// device/, vendor/, etc.
-func isProprietaryPath(dir string, proprietaryDirs []string) bool {
-	for _, p := range proprietaryDirs {
-		if strings.HasPrefix(dir, p) {
-			// filter out AOSP defined directories, e.g. hardware/interfaces/
-			aosp := false
-			for _, p := range aospDirsUnderProprietary {
-				if strings.HasPrefix(dir, p) {
-					aosp = true
-					break
-				}
-			}
-			if !aosp {
-				return true
-			}
-		}
-	}
-	return false
+// Determine if a dir under source tree is an SoC-owned proprietary directory based
+// on recovery snapshot configuration
+// Examples: device/, vendor/
+func isRecoveryProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return RecoverySnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
 }
 
 func isVendorProprietaryModule(ctx android.BaseModuleContext) bool {
 	// Any module in a vendor proprietary path is a vendor proprietary
 	// module.
-	if isVendorProprietaryPath(ctx.ModuleDir()) {
+	if isVendorProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
 		return true
 	}
 
@@ -177,7 +128,7 @@
 
 	// Any module in a recovery proprietary path is a recovery proprietary
 	// module.
-	if isRecoveryProprietaryPath(ctx.ModuleDir()) {
+	if isRecoveryProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
 		return true
 	}
 
@@ -513,7 +464,7 @@
 		}
 
 		moduleDir := ctx.ModuleDir(module)
-		inProprietaryPath := c.image.isProprietaryPath(moduleDir)
+		inProprietaryPath := c.image.isProprietaryPath(moduleDir, ctx.DeviceConfig())
 		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
 		if c.image.excludeFromSnapshot(m) {
@@ -577,10 +528,11 @@
 		ctx,
 		snapshotDir,
 		c.name+"-"+ctx.Config().DeviceName()+"_list")
+	rspFile := snapshotOutputList.ReplaceExtension(ctx, "rsp")
 	zipRule.Command().
 		Text("tr").
 		FlagWithArg("-d ", "\\'").
-		FlagWithRspFileInputList("< ", snapshotOutputs).
+		FlagWithRspFileInputList("< ", rspFile, snapshotOutputs).
 		FlagWithOutput("> ", snapshotOutputList)
 
 	zipRule.Temporary(snapshotOutputList)
diff --git a/cc/vndk.go b/cc/vndk.go
index 85028d0..b7047e9 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -827,10 +827,11 @@
 
 	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
 	snapshotOutputList := android.PathForOutput(ctx, snapshotDir, "android-vndk-"+ctx.DeviceConfig().DeviceArch()+"_list")
+	rspFile := snapshotOutputList.ReplaceExtension(ctx, "rsp")
 	zipRule.Command().
 		Text("tr").
 		FlagWithArg("-d ", "\\'").
-		FlagWithRspFileInputList("< ", snapshotOutputs).
+		FlagWithRspFileInputList("< ", rspFile, snapshotOutputs).
 		FlagWithOutput("> ", snapshotOutputList)
 
 	zipRule.Temporary(snapshotOutputList)
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index f8919a4..f47c601 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -229,13 +229,18 @@
 		return "", err
 	}
 
+	pathToTempDirInSbox := tempDir
+	if command.GetChdir() {
+		pathToTempDirInSbox = "."
+	}
+
 	if strings.Contains(rawCommand, depFilePlaceholder) {
-		depFile = filepath.Join(tempDir, "deps.d")
+		depFile = filepath.Join(pathToTempDirInSbox, "deps.d")
 		rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
 	}
 
 	if strings.Contains(rawCommand, sandboxDirPlaceholder) {
-		rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, tempDir, -1)
+		rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, pathToTempDirInSbox, -1)
 	}
 
 	// Emulate ninja's behavior of creating the directories for any output files before
@@ -254,6 +259,15 @@
 
 	if command.GetChdir() {
 		cmd.Dir = tempDir
+		path := os.Getenv("PATH")
+		absPath, err := makeAbsPathEnv(path)
+		if err != nil {
+			return "", err
+		}
+		err = os.Setenv("PATH", absPath)
+		if err != nil {
+			return "", fmt.Errorf("Failed to update PATH: %w", err)
+		}
 	}
 	err = cmd.Run()
 
@@ -466,3 +480,17 @@
 	}
 	return filepath.Join(dir, file)
 }
+
+func makeAbsPathEnv(pathEnv string) (string, error) {
+	pathEnvElements := filepath.SplitList(pathEnv)
+	for i, p := range pathEnvElements {
+		if !filepath.IsAbs(p) {
+			absPath, err := filepath.Abs(p)
+			if err != nil {
+				return "", fmt.Errorf("failed to make PATH entry %q absolute: %w", p, err)
+			}
+			pathEnvElements[i] = absPath
+		}
+	}
+	return strings.Join(pathEnvElements, string(filepath.ListSeparator)), nil
+}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 4586f44..11d3620 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -80,7 +80,7 @@
 }
 
 func newConfig(srcDir string) android.Config {
-	configuration, err := android.NewConfig(srcDir, bootstrap.BuildDir, bootstrap.ModuleListFile)
+	configuration, err := android.NewConfig(srcDir, bootstrap.CmdlineBuildDir(), bootstrap.CmdlineModuleListFile())
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -101,6 +101,10 @@
 	configuration := newConfig(srcDir)
 	extraNinjaDeps := []string{configuration.ProductVariablesFileName}
 
+	if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
+		configuration.SetAllowMissingDependencies()
+	}
+
 	// These two are here so that we restart a non-debugged soong_build when the
 	// user sets SOONG_DELVE the first time.
 	configuration.Getenv("SOONG_DELVE")
@@ -127,7 +131,7 @@
 		// the incorrect results from the first pass, and file I/O is expensive.
 		firstCtx := newContext(configuration)
 		configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
-		bootstrap.Main(firstCtx.Context, configuration, extraNinjaDeps...)
+		bootstrap.Main(firstCtx.Context, configuration, false, extraNinjaDeps...)
 		// Invoke bazel commands and save results for second pass.
 		if err := configuration.BazelContext.InvokeBazel(); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
@@ -140,10 +144,10 @@
 			os.Exit(1)
 		}
 		ctx = newContext(secondPassConfig)
-		bootstrap.Main(ctx.Context, secondPassConfig, extraNinjaDeps...)
+		bootstrap.Main(ctx.Context, secondPassConfig, false, extraNinjaDeps...)
 	} else {
 		ctx = newContext(configuration)
-		bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
+		bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
 	}
 
 	// Convert the Soong module graph into Bazel BUILD files.
@@ -158,7 +162,7 @@
 	}
 
 	if docFile != "" {
-		if err := writeDocs(ctx, docFile); err != nil {
+		if err := writeDocs(ctx, configuration, docFile); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
 			os.Exit(1)
 		}
@@ -167,7 +171,7 @@
 	// TODO(ccross): make this a command line argument.  Requires plumbing through blueprint
 	//  to affect the command line of the primary builder.
 	if shouldPrepareBuildActions(configuration) {
-		metricsFile := filepath.Join(bootstrap.BuildDir, "soong_build_metrics.pb")
+		metricsFile := filepath.Join(bootstrap.CmdlineBuildDir(), "soong_build_metrics.pb")
 		err := android.WriteMetrics(configuration, metricsFile)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err)
@@ -193,17 +197,16 @@
 	// Android.bp files. It must not depend on the values of per-build product
 	// configurations or variables, since those will generate different BUILD
 	// files based on how the user has configured their tree.
-	bp2buildCtx.SetModuleListFile(bootstrap.ModuleListFile)
+	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineModuleListFile())
 	extraNinjaDeps, err := bp2buildCtx.ListModulePaths(srcDir)
 	if err != nil {
 		panic(err)
 	}
-	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
 
 	// Run the loading and analysis pipeline to prepare the graph of regular
 	// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 	// from the regular Modules.
-	bootstrap.Main(bp2buildCtx.Context, configuration, extraNinjaDeps...)
+	bootstrap.Main(bp2buildCtx.Context, configuration, false, extraNinjaDeps...)
 
 	// Run the code-generation phase to convert BazelTargetModules to BUILD files
 	// and print conversion metrics to the user.
@@ -215,6 +218,9 @@
 	// 1:1 mapping for each module.
 	metrics.Print()
 
+	extraNinjaDeps = append(extraNinjaDeps, codegenContext.AdditionalNinjaDeps()...)
+	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
+
 	// Workarounds to support running bp2build in a clean AOSP checkout with no
 	// prior builds, and exiting early as soon as the BUILD files get generated,
 	// therefore not creating build.ninja files that soong_ui and callers of
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 0a77d67..edc8a42 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -22,7 +22,7 @@
 	"path/filepath"
 )
 
-func createBazelQueryView(ctx bp2build.CodegenContext, bazelQueryViewDir string) error {
+func createBazelQueryView(ctx *bp2build.CodegenContext, bazelQueryViewDir string) error {
 	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
 
 	// Ignore metrics reporting for queryview, since queryview is already a full-repo
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index f2c2c9b..a69de6a 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -95,13 +95,13 @@
 	return result
 }
 
-func getPackages(ctx *android.Context) ([]*bpdoc.Package, error) {
+func getPackages(ctx *android.Context, config interface{}) ([]*bpdoc.Package, error) {
 	moduleTypeFactories := android.ModuleTypeFactoriesForDocs()
-	return bootstrap.ModuleTypeDocs(ctx.Context, moduleTypeFactories)
+	return bootstrap.ModuleTypeDocs(ctx.Context, config, moduleTypeFactories)
 }
 
-func writeDocs(ctx *android.Context, filename string) error {
-	packages, err := getPackages(ctx)
+func writeDocs(ctx *android.Context, config interface{}, filename string) error {
+	packages, err := getPackages(ctx, config)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 1c5e78a..390a9ec 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -70,7 +70,7 @@
 			return build.NewConfig(ctx, args...)
 		},
 		stdio: stdio,
-		run:   make,
+		run:   runMake,
 	}, {
 		flag:         "--dumpvar-mode",
 		description:  "print the value of the legacy make variable VAR to stdout",
@@ -92,7 +92,7 @@
 		description: "build modules based on the specified build action",
 		config:      buildActionConfig,
 		stdio:       stdio,
-		run:         make,
+		run:         runMake,
 	},
 }
 
@@ -478,7 +478,7 @@
 	return build.NewBuildActionConfig(buildAction, *dir, ctx, args...)
 }
 
-func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
+func runMake(ctx build.Context, config build.Config, _ []string, logsDir string) {
 	if config.IsVerbose() {
 		writer := ctx.Writer
 		fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index fdb00bd..4999bc7 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -261,21 +261,17 @@
 
 	} else if module.EnforceUsesLibraries {
 		// Generate command that saves target SDK version in a shell variable.
-		if module.ManifestPath != nil {
-			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(globalSoong.ManifestCheck).
-				Flag("--extract-target-sdk-version").
-				Input(module.ManifestPath).
-				Text(`)"`)
-		} else {
-			// No manifest to extract targetSdkVersion from, hope that DexJar is an APK
-			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(globalSoong.Aapt).
-				Flag("dump badging").
-				Input(module.DexPath).
-				Text(`| grep "targetSdkVersion" | sed -n "s/targetSdkVersion:'\(.*\)'/\1/p"`).
-				Text(`)"`)
+		manifestOrApk := module.ManifestPath
+		if manifestOrApk == nil {
+			// No manifest to extract targetSdkVersion from, hope that dexjar is an APK.
+			manifestOrApk = module.DexPath
 		}
+		rule.Command().Text(`target_sdk_version="$(`).
+			Tool(globalSoong.ManifestCheck).
+			Flag("--extract-target-sdk-version").
+			Input(manifestOrApk).
+			FlagWithInput("--aapt ", ctx.Config().HostToolPath(ctx, "aapt")).
+			Text(`)"`)
 
 		// Generate command that saves host and target class loader context in shell variables.
 		clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts)
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 09f2e8c..f800c48 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -15,7 +15,6 @@
 package etc
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -23,33 +22,12 @@
 	"android/soong/android"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_etc_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
 var prebuiltEtcFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+	nil,
 	android.PrepareForTestWithArchMutator,
 	PrepareForTestWithPrebuiltEtc,
 	android.FixtureMergeMockFs(android.MockFS{
@@ -170,8 +148,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := buildDir + "/target/product/test_device/system/etc/bar"
-	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
+	expected := "out/soong/target/product/test_device/system/etc/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
 func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
@@ -212,8 +190,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := buildDir + "/target/product/test_device/system/usr/share/bar"
-	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
+	expected := "out/soong/target/product/test_device/system/usr/share/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
 func TestPrebuiltUserShareHostInstallDirPath(t *testing.T) {
@@ -227,8 +205,8 @@
 
 	buildOS := android.BuildOs.String()
 	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
-	expected := filepath.Join(buildDir, "host", result.Config.PrebuiltOS(), "usr", "share", "bar")
-	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
+	expected := filepath.Join("out/soong/host", result.Config.PrebuiltOS(), "usr", "share", "bar")
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
 func TestPrebuiltFontInstallDirPath(t *testing.T) {
@@ -240,12 +218,12 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := buildDir + "/target/product/test_device/system/fonts"
-	android.AssertStringEquals(t, "install dir", expected, p.installDirPath.String())
+	expected := "out/soong/target/product/test_device/system/fonts"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
 func TestPrebuiltFirmwareDirPath(t *testing.T) {
-	targetPath := buildDir + "/target/product/test_device"
+	targetPath := "out/soong/target/product/test_device"
 	tests := []struct {
 		description  string
 		config       string
@@ -273,13 +251,13 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prebuiltEtcFixtureFactory.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertStringEquals(t, "install dir", tt.expectedPath, p.installDirPath.String())
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
 		})
 	}
 }
 
 func TestPrebuiltDSPDirPath(t *testing.T) {
-	targetPath := filepath.Join(buildDir, "/target/product/test_device")
+	targetPath := "out/soong/target/product/test_device"
 	tests := []struct {
 		description  string
 		config       string
@@ -307,7 +285,7 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prebuiltEtcFixtureFactory.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertStringEquals(t, "install dir", tt.expectedPath, p.installDirPath.String())
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
 		})
 	}
 }
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index dcdbdcf..791019d 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -14,6 +14,7 @@
         "bootimg.go",
         "filesystem.go",
         "logical_partition.go",
+        "vbmeta.go",
     ],
     testSrcs: [
     ],
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 876e189..3dcc416 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"strconv"
+	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -62,6 +63,10 @@
 	// Optional kernel commandline
 	Cmdline *string
 
+	// File that contains bootconfig parameters. This can be set only when `vendor_boot` is true
+	// and `header_version` is greater than or equal to 4.
+	Bootconfig *string `android:"arch_variant,path"`
+
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
 
@@ -189,6 +194,19 @@
 		return output
 	}
 
+	bootconfig := proptools.String(b.properties.Bootconfig)
+	if bootconfig != "" {
+		if !vendor {
+			ctx.PropertyErrorf("bootconfig", "requires vendor_boot: true")
+			return output
+		}
+		if verNum < 4 {
+			ctx.PropertyErrorf("bootconfig", "requires header_version: 4 or later")
+			return output
+		}
+		cmd.FlagWithInput("--vendor_bootconfig ", android.PathForModuleSrc(ctx, bootconfig))
+	}
+
 	flag := "--output "
 	if vendor {
 		flag = "--vendor_boot "
@@ -200,22 +218,46 @@
 }
 
 func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.OutputPath) android.OutputPath {
-	output := android.PathForModuleOut(ctx, b.installFileName()).OutputPath
-	key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
+	propFile, toolDeps := b.buildPropFile(ctx)
 
+	output := android.PathForModuleOut(ctx, b.installFileName()).OutputPath
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Text("cp").Input(unsignedImage).Output(output)
-	builder.Command().
-		BuiltTool("avbtool").
-		Flag("add_hash_footer").
-		FlagWithArg("--partition_name ", b.partitionName()).
-		FlagWithInput("--key ", key).
-		FlagWithOutput("--image ", output)
+	builder.Command().BuiltTool("verity_utils").
+		Input(propFile).
+		Implicits(toolDeps).
+		Output(output)
 
 	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
 	return output
 }
 
+func (b *bootimg) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
+	var sb strings.Builder
+	var deps android.Paths
+	addStr := func(name string, value string) {
+		fmt.Fprintf(&sb, "%s=%s\n", name, value)
+	}
+	addPath := func(name string, path android.Path) {
+		addStr(name, path.String())
+		deps = append(deps, path)
+	}
+
+	addStr("avb_hash_enable", "true")
+	addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool"))
+	algorithm := proptools.StringDefault(b.properties.Avb_algorithm, "SHA256_RSA4096")
+	addStr("avb_algorithm", algorithm)
+	key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
+	addPath("avb_key_path", key)
+	addStr("avb_add_hash_footer_args", "") // TODO(jiyong): add --rollback_index
+	partitionName := proptools.StringDefault(b.properties.Partition_name, b.Name())
+	addStr("partition_name", partitionName)
+
+	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
+	android.WriteFileRule(ctx, propFile, sb.String())
+	return propFile, deps
+}
+
 var _ android.AndroidMkEntriesProvider = (*bootimg)(nil)
 
 // Implements android.AndroidMkEntriesProvider
@@ -238,6 +280,13 @@
 	return b.output
 }
 
+func (b *bootimg) SignedOutputPath() android.Path {
+	if proptools.Bool(b.properties.Use_avb) {
+		return b.OutputPath()
+	}
+	return nil
+}
+
 var _ android.OutputFileProducer = (*bootimg)(nil)
 
 // Implements android.OutputFileProducer
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 3b0a7ae..8974eba 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -55,6 +55,9 @@
 	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
 	Avb_algorithm *string
 
+	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
+	Partition_name *string
+
 	// Type of the filesystem. Currently, ext4, cpio, and compressed_cpio are supported. Default
 	// is ext4.
 	Type *string
@@ -279,7 +282,8 @@
 		key := android.PathForModuleSrc(ctx, proptools.String(f.properties.Avb_private_key))
 		addPath("avb_key_path", key)
 		addStr("avb_add_hashtree_footer_args", "--do_not_generate_fec")
-		addStr("partition_name", f.Name())
+		partitionName := proptools.StringDefault(f.properties.Partition_name, f.Name())
+		addStr("partition_name", partitionName)
 	}
 
 	if proptools.String(f.properties.File_contexts) != "" {
@@ -381,6 +385,10 @@
 type Filesystem interface {
 	android.Module
 	OutputPath() android.Path
+
+	// Returns the output file that is signed by avbtool. If this module is not signed, returns
+	// nil.
+	SignedOutputPath() android.Path
 }
 
 var _ Filesystem = (*filesystem)(nil)
@@ -388,3 +396,10 @@
 func (f *filesystem) OutputPath() android.Path {
 	return f.output
 }
+
+func (f *filesystem) SignedOutputPath() android.Path {
+	if proptools.Bool(f.properties.Use_avb) {
+		return f.OutputPath()
+	}
+	return nil
+}
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 16b6037..20d9622 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -209,6 +209,10 @@
 	return l.output
 }
 
+func (l *logicalPartition) SignedOutputPath() android.Path {
+	return nil // logical partition is not signed by itself
+}
+
 var _ android.OutputFileProducer = (*logicalPartition)(nil)
 
 // Implements android.OutputFileProducer
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
new file mode 100644
index 0000000..f823387
--- /dev/null
+++ b/filesystem/vbmeta.go
@@ -0,0 +1,265 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterModuleType("vbmeta", vbmetaFactory)
+}
+
+type vbmeta struct {
+	android.ModuleBase
+
+	properties vbmetaProperties
+
+	output     android.OutputPath
+	installDir android.InstallPath
+}
+
+type vbmetaProperties struct {
+	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
+	Partition_name *string
+
+	// Set the name of the output. Defaults to <module_name>.img.
+	Stem *string
+
+	// Path to the private key that avbtool will use to sign this vbmeta image.
+	Private_key *string `android:"path"`
+
+	// Algorithm that avbtool will use to sign this vbmeta image. Default is SHA256_RSA4096.
+	Algorithm *string
+
+	// File whose content will provide the rollback index. If unspecified, the rollback index
+	// is from PLATFORM_SECURITY_PATCH
+	Rollback_index_file *string `android:"path"`
+
+	// Rollback index location of this vbmeta image. Must be 0, 1, 2, etc. Default is 0.
+	Rollback_index_location *int64
+
+	// List of filesystem modules that this vbmeta has descriptors for. The filesystem modules
+	// have to be signed (use_avb: true).
+	Partitions []string
+
+	// List of chained partitions that this vbmeta deletages the verification.
+	Chained_partitions []chainedPartitionProperties
+}
+
+type chainedPartitionProperties struct {
+	// Name of the chained partition
+	Name *string
+
+	// Rollback index location of the chained partition. Must be 0, 1, 2, etc. Default is the
+	// index of this partition in the list + 1.
+	Rollback_index_location *int64
+
+	// Path to the public key that the chained partition is signed with. If this is specified,
+	// private_key is ignored.
+	Public_key *string `android:"path"`
+
+	// Path to the private key that the chained partition is signed with. If this is specified,
+	// and public_key is not specified, a public key is extracted from this private key and
+	// the extracted public key is embedded in the vbmeta image.
+	Private_key *string `android:"path"`
+}
+
+// vbmeta is the partition image that has the verification information for other partitions.
+func vbmetaFactory() android.Module {
+	module := &vbmeta{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+type vbmetaDep struct {
+	blueprint.BaseDependencyTag
+	kind string
+}
+
+var vbmetaPartitionDep = vbmetaDep{kind: "partition"}
+
+func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions...)
+}
+
+func (v *vbmeta) installFileName() string {
+	return proptools.StringDefault(v.properties.Stem, v.BaseModuleName()+".img")
+}
+
+func (v *vbmeta) partitionName() string {
+	return proptools.StringDefault(v.properties.Partition_name, v.BaseModuleName())
+}
+
+func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	extractedPublicKeys := v.extractPublicKeys(ctx)
+
+	v.output = android.PathForModuleOut(ctx, v.installFileName()).OutputPath
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image")
+
+	key := android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key))
+	cmd.FlagWithInput("--key ", key)
+
+	algorithm := proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096")
+	cmd.FlagWithArg("--algorithm ", algorithm)
+
+	cmd.FlagWithArg("--rollback_index ", v.rollbackIndexCommand(ctx))
+	ril := proptools.IntDefault(v.properties.Rollback_index_location, 0)
+	if ril < 0 {
+		ctx.PropertyErrorf("rollback_index_location", "must be 0, 1, 2, ...")
+		return
+	}
+	cmd.FlagWithArg("--rollback_index_location ", strconv.Itoa(ril))
+
+	for _, p := range ctx.GetDirectDepsWithTag(vbmetaPartitionDep) {
+		f, ok := p.(Filesystem)
+		if !ok {
+			ctx.PropertyErrorf("partitions", "%q(type: %s) is not supported",
+				p.Name(), ctx.OtherModuleType(p))
+			continue
+		}
+		signedImage := f.SignedOutputPath()
+		if signedImage == nil {
+			ctx.PropertyErrorf("partitions", "%q(type: %s) is not signed. Use `use_avb: true`",
+				p.Name(), ctx.OtherModuleType(p))
+			continue
+		}
+		cmd.FlagWithInput("--include_descriptors_from_image ", signedImage)
+	}
+
+	for i, cp := range v.properties.Chained_partitions {
+		name := proptools.String(cp.Name)
+		if name == "" {
+			ctx.PropertyErrorf("chained_partitions", "name must be specified")
+			continue
+		}
+
+		ril := proptools.IntDefault(cp.Rollback_index_location, i+1)
+		if ril < 0 {
+			ctx.PropertyErrorf("chained_partitions", "must be 0, 1, 2, ...")
+			continue
+		}
+
+		var publicKey android.Path
+		if cp.Public_key != nil {
+			publicKey = android.PathForModuleSrc(ctx, proptools.String(cp.Public_key))
+		} else {
+			publicKey = extractedPublicKeys[name]
+		}
+		cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
+		cmd.Implicit(publicKey)
+	}
+
+	cmd.FlagWithOutput("--output ", v.output)
+	builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
+
+	v.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(v.installDir, v.installFileName(), v.output)
+}
+
+// Returns the embedded shell command that prints the rollback index
+func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
+	var cmd string
+	if v.properties.Rollback_index_file != nil {
+		f := android.PathForModuleSrc(ctx, proptools.String(v.properties.Rollback_index_file))
+		cmd = "cat " + f.String()
+	} else {
+		cmd = "date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s"
+	}
+	// Take the first line and remove the newline char
+	return "$(" + cmd + " | head -1 | tr -d '\n'" + ")"
+}
+
+// Extract public keys from chained_partitions.private_key. The keys are indexed with the partition
+// name.
+func (v *vbmeta) extractPublicKeys(ctx android.ModuleContext) map[string]android.OutputPath {
+	result := make(map[string]android.OutputPath)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	for _, cp := range v.properties.Chained_partitions {
+		if cp.Private_key == nil {
+			continue
+		}
+
+		name := proptools.String(cp.Name)
+		if name == "" {
+			ctx.PropertyErrorf("chained_partitions", "name must be specified")
+			continue
+		}
+
+		if _, ok := result[name]; ok {
+			ctx.PropertyErrorf("chained_partitions", "name %q is duplicated", name)
+			continue
+		}
+
+		privateKeyFile := android.PathForModuleSrc(ctx, proptools.String(cp.Private_key))
+		publicKeyFile := android.PathForModuleOut(ctx, name+".avbpubkey").OutputPath
+
+		builder.Command().
+			BuiltTool("avbtool").
+			Text("extract_public_key").
+			FlagWithInput("--key ", privateKeyFile).
+			FlagWithOutput("--output ", publicKeyFile)
+
+		result[name] = publicKeyFile
+	}
+	builder.Build("vbmeta_extract_public_key", fmt.Sprintf("Extract public keys for %s", ctx.ModuleName()))
+	return result
+}
+
+var _ android.AndroidMkEntriesProvider = (*vbmeta)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (v *vbmeta) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(v.output),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
+			},
+		},
+	}}
+}
+
+var _ Filesystem = (*vbmeta)(nil)
+
+func (v *vbmeta) OutputPath() android.Path {
+	return v.output
+}
+
+func (v *vbmeta) SignedOutputPath() android.Path {
+	return v.OutputPath() // vbmeta is always signed
+}
+
+var _ android.OutputFileProducer = (*vbmeta)(nil)
+
+// Implements android.OutputFileProducer
+func (v *vbmeta) OutputFiles(tag string) (android.Paths, error) {
+	if tag == "" {
+		return []android.Path{v.output}, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 5349906..b43f28e 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -227,7 +227,7 @@
 // Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
 func (c *Module) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetAllFiles(label, ctx.Arch().ArchType)
+	filePaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
 	if ok {
 		var bazelOutputFiles android.Paths
 		for _, bazelOutputFile := range filePaths {
@@ -538,7 +538,7 @@
 
 	g.outputFiles = outputFiles.Paths()
 
-	bazelModuleLabel := g.GetBazelLabel()
+	bazelModuleLabel := g.GetBazelLabel(ctx, g)
 	bazelActionsUsed := false
 	if ctx.Config().BazelContext.BazelEnabled() && len(bazelModuleLabel) > 0 {
 		bazelActionsUsed = g.generateBazelBuildActions(ctx, bazelModuleLabel)
@@ -824,6 +824,11 @@
 		return
 	}
 
+	if ctx.ModuleType() != "genrule" {
+		// Not a regular genrule. Could be a cc_genrule or java_genrule.
+		return
+	}
+
 	// Bazel only has the "tools" attribute.
 	tools := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
 	tool_files := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 690277c..199a7df 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -15,7 +15,6 @@
 package genrule
 
 import (
-	"io/ioutil"
 	"os"
 	"regexp"
 	"testing"
@@ -25,33 +24,12 @@
 	"github.com/google/blueprint/proptools"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "genrule_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
 var genruleFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+	nil,
 	android.PrepareForTestWithArchMutator,
 	android.PrepareForTestWithDefaults,
 
@@ -320,6 +298,14 @@
 			`,
 			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out",
 		},
+		{
+			name: "$",
+			prop: `
+				out: ["out"],
+				cmd: "echo $$ > $(out)",
+			`,
+			expect: "echo $ > __SBOX_SANDBOX_DIR__/out/out",
+		},
 
 		{
 			name: "error empty location",
@@ -572,8 +558,14 @@
 			cmds: []string{
 				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
 			},
-			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
-			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
+			deps: []string{
+				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
+			},
+			files: []string{
+				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
+			},
 		},
 		{
 			name: "shards",
@@ -587,8 +579,16 @@
 				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
 				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'",
 			},
-			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
-			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
+			deps: []string{
+				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in3.h",
+			},
+			files: []string{
+				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
+				"out/soong/.intermediates/gen/gen/gensrcs/in3.h",
+			},
 		},
 	}
 
@@ -616,9 +616,9 @@
 			gen := result.Module("gen", "").(*Module)
 			android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands)
 
-			android.AssertDeepEquals(t, "deps", test.deps, gen.outputDeps.Strings())
+			android.AssertPathsRelativeToTopEquals(t, "deps", test.deps, gen.outputDeps)
 
-			android.AssertDeepEquals(t, "files", test.files, gen.outputFiles.Strings())
+			android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles)
 		})
 	}
 }
diff --git a/java/Android.bp b/java/Android.bp
index 9e2db83..56cc401 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -75,6 +75,7 @@
         "java_test.go",
         "jdeps_test.go",
         "kotlin_test.go",
+        "platform_compat_config_test.go",
         "plugin_test.go",
         "rro_test.go",
         "sdk_test.go",
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index e758a92..3477956 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -135,7 +135,10 @@
 }
 
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo-shared_library", "foo-no_shared_library"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo-shared_library",
 			srcs: ["a.java"],
@@ -148,7 +151,7 @@
 		`)
 
 	// Verify the existence of internal modules
-	ctx.ModuleForTests("foo-shared_library.xml", "android_common")
+	result.ModuleForTests("foo-shared_library.xml", "android_common")
 
 	testCases := []struct {
 		moduleName string
@@ -158,8 +161,8 @@
 		{"foo-no_shared_library", nil},
 	}
 	for _, tc := range testCases {
-		mod := ctx.ModuleForTests(tc.moduleName, "android_common").Module()
-		entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+		mod := result.ModuleForTests(tc.moduleName, "android_common").Module()
+		entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 		actual := entries.EntryMap["LOCAL_REQUIRED_MODULES"]
 		if !reflect.DeepEqual(tc.expected, actual) {
 			t.Errorf("Unexpected required modules - expected: %q, actual: %q", tc.expected, actual)
diff --git a/java/app.go b/java/app.go
index e98fe31..1b6e0e3 100755
--- a/java/app.go
+++ b/java/app.go
@@ -302,7 +302,7 @@
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
 // b/155209650: until min_sdk_version is properly supported, use sdk_version instead.
 // because, sdk_version is overridden by min_sdk_version (if set as smaller)
-// and linkType is checked with dependencies so we can be sure that the whole dependency tree
+// and sdkLinkType is checked with dependencies so we can be sure that the whole dependency tree
 // will meet the requirements.
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion sdkVersion) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
@@ -812,6 +812,13 @@
 	depsInfo := android.DepNameToDepInfoMap{}
 	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		depName := to.Name()
+
+		// Skip dependencies that are only available to APEXes; they are developed with updatability
+		// in mind and don't need manual approval.
+		if to.(android.ApexModule).NotAvailableForPlatform() {
+			return true
+		}
+
 		if info, exist := depsInfo[depName]; exist {
 			info.From = append(info.From, from.Name())
 			info.IsExternal = info.IsExternal && externalDep
@@ -1274,10 +1281,13 @@
 	u.usesLibraryProperties.Enforce_uses_libs = &enforce
 }
 
-// verifyUsesLibrariesManifest checks the <uses-library> tags in an AndroidManifest.xml against the ones specified
-// in the uses_libs and optional_uses_libs properties.  It returns the path to a copy of the manifest.
-func (u *usesLibrary) verifyUsesLibrariesManifest(ctx android.ModuleContext, manifest android.Path) android.Path {
-	outputFile := android.PathForModuleOut(ctx, "manifest_check", "AndroidManifest.xml")
+// verifyUsesLibraries checks the <uses-library> tags in the manifest against the ones specified
+// in the `uses_libs`/`optional_uses_libs` properties. The input can be either an XML manifest, or
+// an APK with the manifest embedded in it (manifest_check will know which one it is by the file
+// extension: APKs are supposed to end with '.apk').
+func (u *usesLibrary) verifyUsesLibraries(ctx android.ModuleContext, inputFile android.Path,
+	outputFile android.WritablePath) android.Path {
+
 	statusFile := dexpreopt.UsesLibrariesStatusFile(ctx)
 
 	// Disable verify_uses_libraries check if dexpreopt is globally disabled. Without dexpreopt the
@@ -1285,15 +1295,19 @@
 	// non-linux build platforms where dexpreopt is generally disabled (the check may fail due to
 	// various unrelated reasons, such as a failure to get manifest from an APK).
 	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
-		return manifest
+		return inputFile
 	}
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 	cmd := rule.Command().BuiltTool("manifest_check").
 		Flag("--enforce-uses-libraries").
-		Input(manifest).
+		Input(inputFile).
 		FlagWithOutput("--enforce-uses-libraries-status ", statusFile).
-		FlagWithOutput("-o ", outputFile)
+		FlagWithInput("--aapt ", ctx.Config().HostToolPath(ctx, "aapt"))
+
+	if outputFile != nil {
+		cmd.FlagWithOutput("-o ", outputFile)
+	}
 
 	if dexpreopt.GetGlobalConfig(ctx).RelaxUsesLibraryCheck {
 		cmd.Flag("--enforce-uses-libraries-relax")
@@ -1308,35 +1322,20 @@
 	}
 
 	rule.Build("verify_uses_libraries", "verify <uses-library>")
-
 	return outputFile
 }
 
-// verifyUsesLibrariesAPK checks the <uses-library> tags in the manifest of an APK against the ones specified
-// in the uses_libs and optional_uses_libs properties.  It returns the path to a copy of the APK.
+// verifyUsesLibrariesManifest checks the <uses-library> tags in an AndroidManifest.xml against
+// the build system and returns the path to a copy of the manifest.
+func (u *usesLibrary) verifyUsesLibrariesManifest(ctx android.ModuleContext, manifest android.Path) android.Path {
+	outputFile := android.PathForModuleOut(ctx, "manifest_check", "AndroidManifest.xml")
+	return u.verifyUsesLibraries(ctx, manifest, outputFile)
+}
+
+// verifyUsesLibrariesAPK checks the <uses-library> tags in the manifest of an APK against the build
+// system and returns the path to a copy of the APK.
 func (u *usesLibrary) verifyUsesLibrariesAPK(ctx android.ModuleContext, apk android.Path) android.Path {
+	u.verifyUsesLibraries(ctx, apk, nil) // for APKs manifest_check does not write output file
 	outputFile := android.PathForModuleOut(ctx, "verify_uses_libraries", apk.Base())
-	statusFile := dexpreopt.UsesLibrariesStatusFile(ctx)
-
-	// Disable verify_uses_libraries check if dexpreopt is globally disabled. Without dexpreopt the
-	// check is not necessary, and although it is good to have, it is difficult to maintain on
-	// non-linux build platforms where dexpreopt is generally disabled (the check may fail due to
-	// various unrelated reasons, such as a failure to get manifest from an APK).
-	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
-		return apk
-	}
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	aapt := ctx.Config().HostToolPath(ctx, "aapt")
-	rule.Command().
-		Textf("aapt_binary=%s", aapt.String()).Implicit(aapt).
-		Textf(`uses_library_names="%s"`, strings.Join(u.usesLibraryProperties.Uses_libs, " ")).
-		Textf(`optional_uses_library_names="%s"`, strings.Join(u.usesLibraryProperties.Optional_uses_libs, " ")).
-		Textf(`relax_check="%t"`, dexpreopt.GetGlobalConfig(ctx).RelaxUsesLibraryCheck).
-		Tool(android.PathForSource(ctx, "build/make/core/verify_uses_libraries.sh")).Input(apk).Output(statusFile)
-	rule.Command().Text("cp -f").Input(apk).Output(outputFile)
-
-	rule.Build("verify_uses_libraries", "verify <uses-library>")
-
 	return outputFile
 }
diff --git a/java/app_test.go b/java/app_test.go
index 78e1a57..7168a96 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -28,70 +28,68 @@
 	"android/soong/cc"
 )
 
-var (
-	resourceFiles = []string{
+// testAppConfig is a legacy way of creating a test Config for testing java app modules.
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
+func testAppConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
+	return testConfig(env, bp, fs)
+}
+
+// testApp runs tests using the javaFixtureFactory
+//
+// See testJava for an explanation as to how to stop using this deprecated method.
+//
+// deprecated
+func testApp(t *testing.T, bp string) *android.TestContext {
+	t.Helper()
+	result := javaFixtureFactory.RunTestWithBp(t, bp)
+	return result.TestContext
+}
+
+func TestApp(t *testing.T) {
+	resourceFiles := []string{
 		"res/layout/layout.xml",
 		"res/values/strings.xml",
 		"res/values-en-rUS/strings.xml",
 	}
 
-	compiledResourceFiles = []string{
+	compiledResourceFiles := []string{
 		"aapt2/res/layout_layout.xml.flat",
 		"aapt2/res/values_strings.arsc.flat",
 		"aapt2/res/values-en-rUS_strings.arsc.flat",
 	}
-)
 
-func testAppConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
-	appFS := map[string][]byte{}
-	for k, v := range fs {
-		appFS[k] = v
-	}
-
-	for _, file := range resourceFiles {
-		appFS[file] = nil
-	}
-
-	return testConfig(env, bp, appFS)
-}
-
-func testApp(t *testing.T, bp string) *android.TestContext {
-	config := testAppConfig(nil, bp, nil)
-
-	ctx := testContext(config)
-
-	run(t, ctx, config)
-
-	return ctx
-}
-
-func TestApp(t *testing.T) {
 	for _, moduleType := range []string{"android_app", "android_library"} {
 		t.Run(moduleType, func(t *testing.T) {
-			ctx := testApp(t, moduleType+` {
+			result := javaFixtureFactory.Extend(
+				android.FixtureModifyMockFS(func(fs android.MockFS) {
+					for _, file := range resourceFiles {
+						fs[file] = nil
+					}
+				}),
+			).RunTestWithBp(t, moduleType+` {
 					name: "foo",
 					srcs: ["a.java"],
 					sdk_version: "current"
 				}
 			`)
 
-			foo := ctx.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests("foo", "android_common")
 
 			var expectedLinkImplicits []string
 
 			manifestFixer := foo.Output("manifest_fixer/AndroidManifest.xml")
 			expectedLinkImplicits = append(expectedLinkImplicits, manifestFixer.Output.String())
 
-			frameworkRes := ctx.ModuleForTests("framework-res", "android_common")
+			frameworkRes := result.ModuleForTests("framework-res", "android_common")
 			expectedLinkImplicits = append(expectedLinkImplicits,
 				frameworkRes.Output("package-res.apk").Output.String())
 
 			// Test the mapping from input files to compiled output file names
 			compile := foo.Output(compiledResourceFiles[0])
-			if !reflect.DeepEqual(resourceFiles, compile.Inputs.Strings()) {
-				t.Errorf("expected aapt2 compile inputs expected:\n  %#v\n got:\n  %#v",
-					resourceFiles, compile.Inputs.Strings())
-			}
+			android.AssertDeepEquals(t, "aapt2 compile inputs", resourceFiles, compile.Inputs.Strings())
 
 			compiledResourceOutputs := compile.Outputs.Strings()
 			sort.Strings(compiledResourceOutputs)
@@ -102,11 +100,8 @@
 			expectedLinkImplicits = append(expectedLinkImplicits, list.Output.String())
 
 			// Check that the link rule uses
-			res := ctx.ModuleForTests("foo", "android_common").Output("package-res.apk")
-			if !reflect.DeepEqual(expectedLinkImplicits, res.Implicits.Strings()) {
-				t.Errorf("expected aapt2 link implicits expected:\n  %#v\n got:\n  %#v",
-					expectedLinkImplicits, res.Implicits.Strings())
-			}
+			res := result.ModuleForTests("foo", "android_common").Output("package-res.apk")
+			android.AssertDeepEquals(t, "aapt2 link implicits", expectedLinkImplicits, res.Implicits.Strings())
 		})
 	}
 }
@@ -375,11 +370,15 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			if test.expectedError == "" {
-				testJava(t, test.bp)
-			} else {
-				testJavaError(t, test.expectedError, test.bp)
+			errorHandler := android.FixtureExpectsNoErrors
+			if test.expectedError != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
 			}
+			javaFixtureFactory.
+				Extend(FixtureWithPrebuiltApis(map[string][]string{
+					"29": {"foo"},
+				})).
+				ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, test.bp)
 		})
 	}
 }
@@ -989,12 +988,8 @@
 	}
 }
 
-func checkSdkVersion(t *testing.T, config android.Config, expectedSdkVersion string) {
-	ctx := testContext(config)
-
-	run(t, ctx, config)
-
-	foo := ctx.ModuleForTests("foo", "android_common")
+func checkSdkVersion(t *testing.T, result *android.TestResult, expectedSdkVersion string) {
+	foo := result.ModuleForTests("foo", "android_common")
 	link := foo.Output("package-res.apk")
 	linkFlags := strings.Split(link.Args["flags"], " ")
 	min := android.IndexList("--min-sdk-version", linkFlags)
@@ -1007,15 +1002,9 @@
 	gotMinSdkVersion := linkFlags[min+1]
 	gotTargetSdkVersion := linkFlags[target+1]
 
-	if gotMinSdkVersion != expectedSdkVersion {
-		t.Errorf("incorrect --min-sdk-version, expected %q got %q",
-			expectedSdkVersion, gotMinSdkVersion)
-	}
+	android.AssertStringEquals(t, "incorrect --min-sdk-version", expectedSdkVersion, gotMinSdkVersion)
 
-	if gotTargetSdkVersion != expectedSdkVersion {
-		t.Errorf("incorrect --target-sdk-version, expected %q got %q",
-			expectedSdkVersion, gotTargetSdkVersion)
-	}
+	android.AssertStringEquals(t, "incorrect --target-sdk-version", expectedSdkVersion, gotTargetSdkVersion)
 }
 
 func TestAppSdkVersion(t *testing.T) {
@@ -1088,13 +1077,19 @@
 					%s
 				}`, moduleType, test.sdkVersion, platformApiProp)
 
-				config := testAppConfig(nil, bp, nil)
-				config.TestProductVariables.Platform_sdk_version = &test.platformSdkInt
-				config.TestProductVariables.Platform_sdk_codename = &test.platformSdkCodename
-				config.TestProductVariables.Platform_version_active_codenames = test.activeCodenames
-				config.TestProductVariables.Platform_sdk_final = &test.platformSdkFinal
-				checkSdkVersion(t, config, test.expectedMinSdkVersion)
+				result := javaFixtureFactory.Extend(
+					android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+						variables.Platform_sdk_version = &test.platformSdkInt
+						variables.Platform_sdk_codename = &test.platformSdkCodename
+						variables.Platform_version_active_codenames = test.activeCodenames
+						variables.Platform_sdk_final = &test.platformSdkFinal
+					}),
+					FixtureWithPrebuiltApis(map[string][]string{
+						"14": {"foo"},
+					}),
+				).RunTestWithBp(t, bp)
 
+				checkSdkVersion(t, result, test.expectedMinSdkVersion)
 			})
 		}
 	}
@@ -1150,13 +1145,22 @@
 						vendor: true,
 					}`, moduleType, sdkKind, test.sdkVersion)
 
-					config := testAppConfig(nil, bp, nil)
-					config.TestProductVariables.Platform_sdk_version = &test.platformSdkInt
-					config.TestProductVariables.Platform_sdk_codename = &test.platformSdkCodename
-					config.TestProductVariables.Platform_sdk_final = &test.platformSdkFinal
-					config.TestProductVariables.DeviceCurrentApiLevelForVendorModules = &test.deviceCurrentApiLevelForVendorModules
-					config.TestProductVariables.DeviceSystemSdkVersions = []string{"28", "29"}
-					checkSdkVersion(t, config, test.expectedMinSdkVersion)
+					result := javaFixtureFactory.Extend(
+						android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+							variables.Platform_sdk_version = &test.platformSdkInt
+							variables.Platform_sdk_codename = &test.platformSdkCodename
+							variables.Platform_sdk_final = &test.platformSdkFinal
+							variables.DeviceCurrentApiLevelForVendorModules = &test.deviceCurrentApiLevelForVendorModules
+							variables.DeviceSystemSdkVersions = []string{"28", "29"}
+						}),
+						FixtureWithPrebuiltApis(map[string][]string{
+							"28":      {"foo"},
+							"29":      {"foo"},
+							"current": {"foo"},
+						}),
+					).RunTestWithBp(t, bp)
+
+					checkSdkVersion(t, result, test.expectedMinSdkVersion)
 				})
 			}
 		}
@@ -2365,15 +2369,16 @@
 		}
 	`
 
-	config := testAppConfig(nil, bp, nil)
-	config.TestProductVariables.MissingUsesLibraries = []string{"baz"}
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("runtime-library", "foo", "quuz", "qux", "bar", "fred"),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.MissingUsesLibraries = []string{"baz"}
+		}),
+	).RunTestWithBp(t, bp)
 
-	ctx := testContext(config)
-
-	run(t, ctx, config)
-
-	app := ctx.ModuleForTests("app", "android_common")
-	prebuilt := ctx.ModuleForTests("prebuilt", "android_common")
+	app := result.ModuleForTests("app", "android_common")
+	prebuilt := result.ModuleForTests("prebuilt", "android_common")
 
 	// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
 	// This should not include explicit `uses_libs`/`optional_uses_libs` entries.
@@ -2385,10 +2390,7 @@
 		`--uses-library com.non.sdk.lib ` + // TODO(b/132357300): "com.non.sdk.lib" should not be passed to manifest_fixer
 		`--uses-library bar ` + // TODO(b/132357300): "bar" should not be passed to manifest_fixer
 		`--uses-library runtime-library`
-	if actualManifestFixerArgs != expectManifestFixerArgs {
-		t.Errorf("unexpected manifest_fixer args:\n\texpect: %q\n\tactual: %q",
-			expectManifestFixerArgs, actualManifestFixerArgs)
-	}
+	android.AssertStringEquals(t, "manifest_fixer args", expectManifestFixerArgs, actualManifestFixerArgs)
 
 	// Test that all libraries are verified (library order matters).
 	verifyCmd := app.Rule("verify_uses_libraries").RuleParams.Command
@@ -2399,20 +2401,16 @@
 		`--uses-library runtime-library ` +
 		`--optional-uses-library bar ` +
 		`--optional-uses-library baz `
-	if !strings.Contains(verifyCmd, verifyArgs) {
-		t.Errorf("wanted %q in %q", verifyArgs, verifyCmd)
-	}
+	android.AssertStringDoesContain(t, "verify cmd args", verifyCmd, verifyArgs)
 
 	// Test that all libraries are verified for an APK (library order matters).
 	verifyApkCmd := prebuilt.Rule("verify_uses_libraries").RuleParams.Command
-	verifyApkReqLibs := `uses_library_names="foo com.non.sdk.lib android.test.runner"`
-	verifyApkOptLibs := `optional_uses_library_names="bar baz"`
-	if !strings.Contains(verifyApkCmd, verifyApkReqLibs) {
-		t.Errorf("wanted %q in %q", verifyApkReqLibs, verifyApkCmd)
-	}
-	if !strings.Contains(verifyApkCmd, verifyApkOptLibs) {
-		t.Errorf("wanted %q in %q", verifyApkOptLibs, verifyApkCmd)
-	}
+	verifyApkArgs := `--uses-library foo ` +
+		`--uses-library com.non.sdk.lib ` +
+		`--uses-library android.test.runner ` +
+		`--optional-uses-library bar ` +
+		`--optional-uses-library baz `
+	android.AssertStringDoesContain(t, "verify apk cmd args", verifyApkCmd, verifyApkArgs)
 
 	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
 	cmd := app.Rule("dexpreopt").RuleParams.Command
@@ -2423,46 +2421,39 @@
 		`PCL[/system/framework/non-sdk-lib.jar]#` +
 		`PCL[/system/framework/bar.jar]#` +
 		`PCL[/system/framework/runtime-library.jar]`
-	if !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt app cmd args", cmd, w)
 
 	// Test conditional context for target SDK version 28.
-	if w := `--target-context-for-sdk 28` +
-		` PCL[/system/framework/org.apache.http.legacy.jar] `; !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt app cmd 28", cmd,
+		`--target-context-for-sdk 28`+
+			` PCL[/system/framework/org.apache.http.legacy.jar] `)
 
 	// Test conditional context for target SDK version 29.
-	if w := `--target-context-for-sdk 29` +
-		` PCL[/system/framework/android.hidl.manager-V1.0-java.jar]` +
-		`#PCL[/system/framework/android.hidl.base-V1.0-java.jar] `; !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt app cmd 29", cmd,
+		`--target-context-for-sdk 29`+
+			` PCL[/system/framework/android.hidl.manager-V1.0-java.jar]`+
+			`#PCL[/system/framework/android.hidl.base-V1.0-java.jar] `)
 
 	// Test conditional context for target SDK version 30.
 	// "android.test.mock" is absent because "android.test.runner" is not used.
-	if w := `--target-context-for-sdk 30` +
-		` PCL[/system/framework/android.test.base.jar] `; !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt app cmd 30", cmd,
+		`--target-context-for-sdk 30`+
+			` PCL[/system/framework/android.test.base.jar] `)
 
 	cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
-	if w := `--target-context-for-sdk any` +
-		` PCL[/system/framework/foo.jar]` +
-		`#PCL[/system/framework/non-sdk-lib.jar]` +
-		`#PCL[/system/framework/android.test.runner.jar]` +
-		`#PCL[/system/framework/bar.jar] `; !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt prebuilt cmd", cmd,
+		`--target-context-for-sdk any`+
+			` PCL[/system/framework/foo.jar]`+
+			`#PCL[/system/framework/non-sdk-lib.jar]`+
+			`#PCL[/system/framework/android.test.runner.jar]`+
+			`#PCL[/system/framework/bar.jar] `)
 
 	// Test conditional context for target SDK version 30.
 	// "android.test.mock" is present because "android.test.runner" is used.
-	if w := `--target-context-for-sdk 30` +
-		` PCL[/system/framework/android.test.base.jar]` +
-		`#PCL[/system/framework/android.test.mock.jar] `; !strings.Contains(cmd, w) {
-		t.Errorf("wanted %q in %q", w, cmd)
-	}
+	android.AssertStringDoesContain(t, "dexpreopt prebuilt cmd 30", cmd,
+		`--target-context-for-sdk 30`+
+			` PCL[/system/framework/android.test.base.jar]`+
+			`#PCL[/system/framework/android.test.mock.jar] `)
 }
 
 func TestCodelessApp(t *testing.T) {
@@ -2727,28 +2718,24 @@
 	test := func(t *testing.T, bp string, want bool, unbundled bool) {
 		t.Helper()
 
-		config := testAppConfig(nil, bp, nil)
-		if unbundled {
-			config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
-			config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
-		}
+		result := javaFixtureFactory.Extend(
+			PrepareForTestWithPrebuiltsOfCurrentApi,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				if unbundled {
+					variables.Unbundled_build = proptools.BoolPtr(true)
+					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
+				}
+			}),
+		).RunTestWithBp(t, bp)
 
-		ctx := testContext(config)
-
-		run(t, ctx, config)
-
-		foo := ctx.ModuleForTests("foo", "android_common")
+		foo := result.ModuleForTests("foo", "android_common")
 		dex := foo.Rule("r8")
 		uncompressedInDexJar := strings.Contains(dex.Args["zipFlags"], "-L 0")
 		aligned := foo.MaybeRule("zipalign").Rule != nil
 
-		if uncompressedInDexJar != want {
-			t.Errorf("want uncompressed in dex %v, got %v", want, uncompressedInDexJar)
-		}
+		android.AssertBoolEquals(t, "uncompressed in dex", want, uncompressedInDexJar)
 
-		if aligned != want {
-			t.Errorf("want aligned %v, got %v", want, aligned)
-		}
+		android.AssertBoolEquals(t, "aligne", want, aligned)
 	}
 
 	for _, tt := range testCases {
diff --git a/java/boot_image.go b/java/boot_image.go
index 12e2874..25a4f17 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+
 	"github.com/google/blueprint"
 )
 
@@ -56,9 +57,9 @@
 func bootImageFactory() android.Module {
 	m := &BootImageModule{}
 	m.AddProperties(&m.properties)
-	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
+	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
 	return m
 }
 
@@ -210,11 +211,11 @@
 func prebuiltBootImageFactory() android.Module {
 	m := &prebuiltBootImageModule{}
 	m.AddProperties(&m.properties)
-	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
 	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
 	// array.
 	android.InitPrebuiltModule(m, &[]string{"placeholder"})
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
+	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
 	return m
 }
diff --git a/java/droiddoc.go b/java/droiddoc.go
index f0decec..da13c62 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -812,7 +812,7 @@
 		BuiltTool("soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
 		Flag(config.JavacVmFlags).
 		FlagWithArg("-encoding ", "UTF-8").
-		FlagWithRspFileInputList("@", srcs).
+		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "javadoc.rsp"), srcs).
 		FlagWithInput("@", srcJarList)
 
 	// TODO(ccross): Remove this if- statement once we finish migration for all Doclava
@@ -1243,7 +1243,7 @@
 		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
 		FlagWithArg("-encoding ", "UTF-8").
 		FlagWithArg("-source ", javaVersion.String()).
-		FlagWithRspFileInputList("@", srcs).
+		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "metalava.rsp"), srcs).
 		FlagWithInput("@", srcJarList)
 
 	if javaHome := ctx.Config().Getenv("ANDROID_JAVA_HOME"); javaHome != "" {
@@ -1446,7 +1446,9 @@
 	// add a large number of inputs to a file without exceeding bash command length limits (which
 	// would happen if we use the WriteFile rule). The cp is needed because RuleBuilder sets the
 	// rsp file to be ${output}.rsp.
-	impCmd.Text("cp").FlagWithRspFileInputList("", cmd.GetImplicits()).Output(implicitsRsp)
+	impCmd.Text("cp").
+		FlagWithRspFileInputList("", android.PathForModuleOut(ctx, "metalava-implicits.rsp"), cmd.GetImplicits()).
+		Output(implicitsRsp)
 	impRule.Build("implicitsGen", "implicits generation")
 	cmd.Implicit(implicitsRsp)
 
@@ -1750,7 +1752,7 @@
 		Flag("-jar").
 		FlagWithOutput("-o ", p.stubsSrcJar).
 		FlagWithArg("-C ", srcDir.String()).
-		FlagWithRspFileInputList("-r ", srcPaths)
+		FlagWithRspFileInputList("-r ", p.stubsSrcJar.ReplaceExtension(ctx, "rsp"), srcPaths)
 
 	rule.Restat()
 
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 82e8b3f..6ad4ff3 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -31,6 +31,8 @@
 	ctx.RegisterModuleType("hiddenapi_flags", hiddenAPIFlagsFactory)
 }
 
+var PrepareForTestWithHiddenApiBuildComponents = android.FixtureRegisterWithContext(RegisterHiddenApiSingletonComponents)
+
 type hiddenAPISingletonPathsStruct struct {
 	// The path to the CSV file that contains the flags that will be encoded into the dex boot jars.
 	//
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index fb63820..f17d436 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -16,68 +16,50 @@
 
 import (
 	"fmt"
-	"strings"
+	"path/filepath"
 	"testing"
 
 	"android/soong/android"
-
 	"github.com/google/blueprint/proptools"
 )
 
-func testConfigWithBootJars(bp string, bootJars []string, prebuiltHiddenApiDir *string) android.Config {
-	config := testConfig(nil, bp, nil)
-	config.TestProductVariables.BootJars = android.CreateTestConfiguredJarList(bootJars)
-	config.TestProductVariables.PrebuiltHiddenApiDir = prebuiltHiddenApiDir
-	return config
+func fixtureSetBootJarsProductVariable(bootJars ...string) android.FixturePreparer {
+	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.BootJars = android.CreateTestConfiguredJarList(bootJars)
+	})
 }
 
-func testContextWithHiddenAPI(config android.Config) *android.TestContext {
-	ctx := testContext(config)
-	RegisterHiddenApiSingletonComponents(ctx)
-	return ctx
+func fixtureSetPrebuiltHiddenApiDirProductVariable(prebuiltHiddenApiDir *string) android.FixturePreparer {
+	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.PrebuiltHiddenApiDir = prebuiltHiddenApiDir
+	})
 }
 
-func testHiddenAPIWithConfig(t *testing.T, config android.Config) *android.TestContext {
-	t.Helper()
-
-	ctx := testContextWithHiddenAPI(config)
-
-	run(t, ctx, config)
-	return ctx
-}
-
-func testHiddenAPIBootJars(t *testing.T, bp string, bootJars []string, prebuiltHiddenApiDir *string) (*android.TestContext, android.Config) {
-	config := testConfigWithBootJars(bp, bootJars, prebuiltHiddenApiDir)
-
-	return testHiddenAPIWithConfig(t, config), config
-}
-
-func testHiddenAPIUnbundled(t *testing.T, unbundled bool) (*android.TestContext, android.Config) {
-	config := testConfig(nil, ``, nil)
-	config.TestProductVariables.Always_use_prebuilt_sdks = proptools.BoolPtr(unbundled)
-
-	return testHiddenAPIWithConfig(t, config), config
-}
+var hiddenApiFixtureFactory = javaFixtureFactory.Extend(PrepareForTestWithHiddenApiBuildComponents)
 
 func TestHiddenAPISingleton(t *testing.T) {
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
 			compile_dex: true,
 		}
-	`, []string{"platform:foo"}, nil)
+	`)
 
-	hiddenAPI := ctx.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests("hiddenapi")
 	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	want := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
-	if !strings.Contains(hiddenapiRule.RuleParams.Command, want) {
-		t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", want, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPIIndexSingleton(t *testing.T) {
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("bar"),
+		fixtureSetBootJarsProductVariable("platform:foo", "platform:bar"),
+	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -106,9 +88,9 @@
 			srcs: ["a.java"],
 			compile_dex: true,
 		}
-	`, []string{"platform:foo", "platform:bar"}, nil)
+	`)
 
-	hiddenAPIIndex := ctx.SingletonForTests("hiddenapi_index")
+	hiddenAPIIndex := result.SingletonForTests("hiddenapi_index")
 	indexRule := hiddenAPIIndex.Rule("singleton-merged-hiddenapi-index")
 	CheckHiddenAPIRuleInputs(t, `
 .intermediates/bar/android_common/hiddenapi/index.csv
@@ -118,7 +100,7 @@
 
 	// Make sure that the foo-hiddenapi-annotations.jar is included in the inputs to the rules that
 	// creates the index.csv file.
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests("foo", "android_common")
 	indexParams := foo.Output("hiddenapi/index.csv")
 	CheckHiddenAPIRuleInputs(t, `
 .intermediates/foo-hiddenapi-annotations/android_common/javac/foo-hiddenapi-annotations.jar
@@ -127,7 +109,15 @@
 }
 
 func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
-	config := testConfigWithBootJars(`
+	expectedErrorMessage :=
+		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been" +
+			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a" +
+			" suitable boot dex jar"
+
+	hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorMessage)).
+		RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -139,35 +129,30 @@
 			jars: ["a.jar"],
 			prefer: true,
 		}
-	`, []string{"platform:foo"}, nil)
-
-	ctx := testContextWithHiddenAPI(config)
-
-	runWithErrors(t, ctx, config,
-		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been"+
-			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a"+
-			" suitable boot dex jar")
+	`)
 }
 
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+	).RunTestWithBp(t, `
 		java_import {
 			name: "foo",
 			jars: ["a.jar"],
 			compile_dex: true,
 	}
-	`, []string{"platform:foo"}, nil)
+	`)
 
-	hiddenAPI := ctx.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests("hiddenapi")
 	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	want := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
-	if !strings.Contains(hiddenapiRule.RuleParams.Command, want) {
-		t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", want, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPISingletonWithPrebuiltUseSource(t *testing.T) {
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -180,23 +165,21 @@
 			compile_dex: true,
 			prefer: false,
 		}
-	`, []string{"platform:foo"}, nil)
+	`)
 
-	hiddenAPI := ctx.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests("hiddenapi")
 	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	fromSourceJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
-	if !strings.Contains(hiddenapiRule.RuleParams.Command, fromSourceJarArg) {
-		t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", fromSourceJarArg, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 
 	prebuiltJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/dex/foo.jar"
-	if strings.Contains(hiddenapiRule.RuleParams.Command, prebuiltJarArg) {
-		t.Errorf("Did not expect %s in hiddenapi command, but it was present: %s", prebuiltJarArg, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 }
 
 func TestHiddenAPISingletonWithPrebuiltOverrideSource(t *testing.T) {
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -209,19 +192,15 @@
 			compile_dex: true,
 			prefer: true,
 		}
-	`, []string{"platform:foo"}, nil)
+	`)
 
-	hiddenAPI := ctx.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests("hiddenapi")
 	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	prebuiltJarArg := "--boot-dex=" + buildDir + "/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
-	if !strings.Contains(hiddenapiRule.RuleParams.Command, prebuiltJarArg) {
-		t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", prebuiltJarArg, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 
 	fromSourceJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
-	if strings.Contains(hiddenapiRule.RuleParams.Command, fromSourceJarArg) {
-		t.Errorf("Did not expect %s in hiddenapi command, but it was present: %s", fromSourceJarArg, hiddenapiRule.RuleParams.Command)
-	}
+	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 }
 
 func TestHiddenAPISingletonSdks(t *testing.T) {
@@ -232,6 +211,9 @@
 		systemStub       string
 		testStub         string
 		corePlatformStub string
+
+		// Additional test preparer
+		preparer android.FixturePreparer
 	}{
 		{
 			name:             "testBundled",
@@ -240,6 +222,7 @@
 			systemStub:       "android_system_stubs_current",
 			testStub:         "android_test_stubs_current",
 			corePlatformStub: "legacy.core.platform.api.stubs",
+			preparer:         android.GroupFixturePreparers(),
 		}, {
 			name:             "testUnbundled",
 			unbundledBuild:   true,
@@ -247,33 +230,31 @@
 			systemStub:       "sdk_system_current_android",
 			testStub:         "sdk_test_current_android",
 			corePlatformStub: "legacy.core.platform.api.stubs",
+			preparer:         PrepareForTestWithPrebuiltsOfCurrentApi,
 		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			ctx, _ := testHiddenAPIUnbundled(t, tc.unbundledBuild)
+			result := hiddenApiFixtureFactory.Extend(
+				tc.preparer,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(tc.unbundledBuild)
+				}),
+			).RunTest(t)
 
-			hiddenAPI := ctx.SingletonForTests("hiddenapi")
+			hiddenAPI := result.SingletonForTests("hiddenapi")
 			hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
-			if !strings.Contains(hiddenapiRule.RuleParams.Command, wantPublicStubs) {
-				t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", wantPublicStubs, hiddenapiRule.RuleParams.Command)
-			}
+			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)
 
 			wantSystemStubs := "--system-stub-classpath=" + generateSdkDexPath(tc.systemStub, tc.unbundledBuild)
-			if !strings.Contains(hiddenapiRule.RuleParams.Command, wantSystemStubs) {
-				t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", wantSystemStubs, hiddenapiRule.RuleParams.Command)
-			}
+			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantSystemStubs)
 
 			wantTestStubs := "--test-stub-classpath=" + generateSdkDexPath(tc.testStub, tc.unbundledBuild)
-			if !strings.Contains(hiddenapiRule.RuleParams.Command, wantTestStubs) {
-				t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", wantTestStubs, hiddenapiRule.RuleParams.Command)
-			}
+			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantTestStubs)
 
-			wantCorePlatformStubs := "--core-platform-stub-classpath=" + generateDexPath(tc.corePlatformStub)
-			if !strings.Contains(hiddenapiRule.RuleParams.Command, wantCorePlatformStubs) {
-				t.Errorf("Expected %s in hiddenapi command, but it was not present: %s", wantCorePlatformStubs, hiddenapiRule.RuleParams.Command)
-			}
+			wantCorePlatformStubs := "--core-platform-stub-classpath=" + generateDexPath(defaultJavaDir, tc.corePlatformStub)
+			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantCorePlatformStubs)
 		})
 	}
 }
@@ -282,15 +263,15 @@
 	return fmt.Sprintf("%s/.intermediates/%s/android_common/%s/%s.jar", buildDir, subDir, dex, module)
 }
 
-func generateDexPath(module string) string {
-	return generateDexedPath(module, "dex", module)
+func generateDexPath(moduleDir string, module string) string {
+	return generateDexedPath(filepath.Join(moduleDir, module), "dex", module)
 }
 
 func generateSdkDexPath(module string, unbundled bool) string {
 	if unbundled {
 		return generateDexedPath("prebuilts/sdk/"+module, "dex", module)
 	}
-	return generateDexPath(module)
+	return generateDexPath(defaultJavaDir, module)
 }
 
 func TestHiddenAPISingletonWithPrebuiltCsvFile(t *testing.T) {
@@ -304,36 +285,33 @@
 	// Where to find the prebuilt hiddenapi files:
 	prebuiltHiddenApiDir := "path/to/prebuilt/hiddenapi"
 
-	ctx, _ := testHiddenAPIBootJars(t, `
+	result := hiddenApiFixtureFactory.Extend(
+		fixtureSetBootJarsProductVariable("platform:foo"),
+		fixtureSetPrebuiltHiddenApiDirProductVariable(&prebuiltHiddenApiDir),
+	).RunTestWithBp(t, `
 		java_import {
 			name: "foo",
 			jars: ["a.jar"],
 			compile_dex: true,
 	}
-	`, []string{"platform:foo"}, &prebuiltHiddenApiDir)
+	`)
 
 	expectedCpInput := prebuiltHiddenApiDir + "/hiddenapi-flags.csv"
 	expectedCpOutput := buildDir + "/hiddenapi/hiddenapi-flags.csv"
 	expectedFlagsCsv := buildDir + "/hiddenapi/hiddenapi-flags.csv"
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests("foo", "android_common")
 
-	hiddenAPI := ctx.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests("hiddenapi")
 	cpRule := hiddenAPI.Rule("Cp")
 	actualCpInput := cpRule.BuildParams.Input
 	actualCpOutput := cpRule.BuildParams.Output
 	encodeDexRule := foo.Rule("hiddenAPIEncodeDex")
 	actualFlagsCsv := encodeDexRule.BuildParams.Args["flagsCsv"]
 
-	if actualCpInput.String() != expectedCpInput {
-		t.Errorf("Prebuilt hiddenapi cp rule input mismatch, actual: %s, expected: %s", actualCpInput, expectedCpInput)
-	}
+	android.AssertStringEquals(t, "hiddenapi cp rule input", expectedCpInput, actualCpInput.String())
 
-	if actualCpOutput.String() != expectedCpOutput {
-		t.Errorf("Prebuilt hiddenapi cp rule output mismatch, actual: %s, expected: %s", actualCpOutput, expectedCpOutput)
-	}
+	android.AssertStringEquals(t, "hiddenapi cp rule output", expectedCpOutput, actualCpOutput.String())
 
-	if actualFlagsCsv != expectedFlagsCsv {
-		t.Errorf("Prebuilt hiddenapi encode dex rule flags csv mismatch, actual: %s, expected: %s", actualFlagsCsv, expectedFlagsCsv)
-	}
+	android.AssertStringEquals(t, "hiddenapi encode dex rule flags csv", expectedFlagsCsv, actualFlagsCsv)
 }
diff --git a/java/java.go b/java/java.go
index 567ebbb..8c714ee 100644
--- a/java/java.go
+++ b/java/java.go
@@ -37,6 +37,36 @@
 func init() {
 	RegisterJavaBuildComponents(android.InitRegistrationContext)
 
+	RegisterJavaSdkMemberTypes()
+}
+
+func RegisterJavaBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("java_defaults", DefaultsFactory)
+
+	ctx.RegisterModuleType("java_library", LibraryFactory)
+	ctx.RegisterModuleType("java_library_static", LibraryStaticFactory)
+	ctx.RegisterModuleType("java_library_host", LibraryHostFactory)
+	ctx.RegisterModuleType("java_binary", BinaryFactory)
+	ctx.RegisterModuleType("java_binary_host", BinaryHostFactory)
+	ctx.RegisterModuleType("java_test", TestFactory)
+	ctx.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory)
+	ctx.RegisterModuleType("java_test_host", TestHostFactory)
+	ctx.RegisterModuleType("java_test_import", JavaTestImportFactory)
+	ctx.RegisterModuleType("java_import", ImportFactory)
+	ctx.RegisterModuleType("java_import_host", ImportFactoryHost)
+	ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory)
+	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
+	ctx.RegisterModuleType("dex_import", DexImportFactory)
+
+	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator).Parallel()
+	})
+
+	ctx.RegisterSingletonType("logtags", LogtagsSingleton)
+	ctx.RegisterSingletonType("kythe_java_extract", kytheExtractJavaFactory)
+}
+
+func RegisterJavaSdkMemberTypes() {
 	// Register sdk member types.
 	android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType)
 
@@ -89,76 +119,7 @@
 			PropertyName: "java_tests",
 		},
 	})
-}
 
-func RegisterJavaBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("java_defaults", DefaultsFactory)
-
-	ctx.RegisterModuleType("java_library", LibraryFactory)
-	ctx.RegisterModuleType("java_library_static", LibraryStaticFactory)
-	ctx.RegisterModuleType("java_library_host", LibraryHostFactory)
-	ctx.RegisterModuleType("java_binary", BinaryFactory)
-	ctx.RegisterModuleType("java_binary_host", BinaryHostFactory)
-	ctx.RegisterModuleType("java_test", TestFactory)
-	ctx.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory)
-	ctx.RegisterModuleType("java_test_host", TestHostFactory)
-	ctx.RegisterModuleType("java_test_import", JavaTestImportFactory)
-	ctx.RegisterModuleType("java_import", ImportFactory)
-	ctx.RegisterModuleType("java_import_host", ImportFactoryHost)
-	ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory)
-	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
-	ctx.RegisterModuleType("dex_import", DexImportFactory)
-
-	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator).Parallel()
-	})
-
-	ctx.RegisterSingletonType("logtags", LogtagsSingleton)
-	ctx.RegisterSingletonType("kythe_java_extract", kytheExtractJavaFactory)
-}
-
-func (j *Module) CheckStableSdkVersion() error {
-	sdkVersion := j.sdkVersion()
-	if sdkVersion.stable() {
-		return nil
-	}
-	return fmt.Errorf("non stable SDK %v", sdkVersion)
-}
-
-func (j *Module) checkSdkVersions(ctx android.ModuleContext) {
-	if j.RequiresStableAPIs(ctx) {
-		if sc, ok := ctx.Module().(sdkContext); ok {
-			if !sc.sdkVersion().specified() {
-				ctx.PropertyErrorf("sdk_version",
-					"sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).")
-			}
-		}
-	}
-
-	ctx.VisitDirectDeps(func(module android.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		switch module.(type) {
-		// TODO(satayev): cover other types as well, e.g. imports
-		case *Library, *AndroidLibrary:
-			switch tag {
-			case bootClasspathTag, libTag, staticLibTag, java9LibTag:
-				checkLinkType(ctx, j, module.(linkTypeContext), tag.(dependencyTag))
-			}
-		}
-	})
-}
-
-func (j *Module) checkPlatformAPI(ctx android.ModuleContext) {
-	if sc, ok := ctx.Module().(sdkContext); ok {
-		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
-		sdkVersionSpecified := sc.sdkVersion().specified()
-		if usePlatformAPI && sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
-		} else if !usePlatformAPI && !sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
-		}
-
-	}
 }
 
 // TODO:
@@ -170,7 +131,8 @@
 // DroidDoc
 // Findbugs
 
-type CompilerProperties struct {
+// Properties that are common to most Java modules, i.e. whether it's a host or device module.
+type CommonProperties struct {
 	// list of source files used to compile the Java module.  May be .java, .kt, .logtags, .proto,
 	// or .aidl files.
 	Srcs []string `android:"path,arch_variant"`
@@ -303,7 +265,9 @@
 	Hiddenapi_additional_annotations []string
 }
 
-type CompilerDeviceProperties struct {
+// Properties that are specific to device modules. Host module factories should not add these when
+// constructing a new module.
+type DeviceProperties struct {
 	// if not blank, set to the version of the sdk to compile against.
 	// Defaults to compiling against the current platform.
 	Sdk_version *string
@@ -412,9 +376,9 @@
 	// Functionality common to Module and Import.
 	embeddableInModuleAndImport
 
-	properties       CompilerProperties
+	properties       CommonProperties
 	protoProperties  android.ProtoProperties
-	deviceProperties CompilerDeviceProperties
+	deviceProperties DeviceProperties
 
 	// jar file containing header classes including static library dependencies, suitable for
 	// inserting into the bootclasspath/classpath of another compile
@@ -499,6 +463,62 @@
 	hideApexVariantFromMake bool
 }
 
+func (j *Module) CheckStableSdkVersion() error {
+	sdkVersion := j.sdkVersion()
+	if sdkVersion.stable() {
+		return nil
+	}
+	if sdkVersion.kind == sdkCorePlatform {
+		if useLegacyCorePlatformApiByName(j.BaseModuleName()) {
+			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
+		} else {
+			// Treat stable core platform as stable.
+			return nil
+		}
+	} else {
+		return fmt.Errorf("non stable SDK %v", sdkVersion)
+	}
+}
+
+// checkSdkVersions enforces restrictions around SDK dependencies.
+func (j *Module) checkSdkVersions(ctx android.ModuleContext) {
+	if j.RequiresStableAPIs(ctx) {
+		if sc, ok := ctx.Module().(sdkContext); ok {
+			if !sc.sdkVersion().specified() {
+				ctx.PropertyErrorf("sdk_version",
+					"sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).")
+			}
+		}
+	}
+
+	// Make sure this module doesn't statically link to modules with lower-ranked SDK link type.
+	// See rank() for details.
+	ctx.VisitDirectDeps(func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		switch module.(type) {
+		// TODO(satayev): cover other types as well, e.g. imports
+		case *Library, *AndroidLibrary:
+			switch tag {
+			case bootClasspathTag, libTag, staticLibTag, java9LibTag:
+				j.checkSdkLinkType(ctx, module.(moduleWithSdkDep), tag.(dependencyTag))
+			}
+		}
+	})
+}
+
+func (j *Module) checkPlatformAPI(ctx android.ModuleContext) {
+	if sc, ok := ctx.Module().(sdkContext); ok {
+		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
+		sdkVersionSpecified := sc.sdkVersion().specified()
+		if usePlatformAPI && sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
+		} else if !usePlatformAPI && !sdkVersionSpecified {
+			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
+		}
+
+	}
+}
+
 func (j *Module) addHostProperties() {
 	j.AddProperties(
 		&j.properties,
@@ -999,13 +1019,13 @@
 	}
 }
 
-type linkType int
+type sdkLinkType int
 
 const (
 	// TODO(jiyong) rename these for better readability. Make the allowed
 	// and disallowed link types explicit
 	// order is important here. See rank()
-	javaCore linkType = iota
+	javaCore sdkLinkType = iota
 	javaSdk
 	javaSystem
 	javaModule
@@ -1013,7 +1033,7 @@
 	javaPlatform
 )
 
-func (lt linkType) String() string {
+func (lt sdkLinkType) String() string {
 	switch lt {
 	case javaCore:
 		return "core Java API"
@@ -1032,18 +1052,19 @@
 	}
 }
 
-// rank determins the total order among linkTypes. A link type of rank A can link to another link
-// type of rank B only when B <= A
-func (lt linkType) rank() int {
+// rank determines the total order among sdkLinkType. An SDK link type of rank A can link to
+// another SDK link type of rank B only when B <= A. For example, a module linking to Android SDK
+// can't statically depend on modules that use Platform API.
+func (lt sdkLinkType) rank() int {
 	return int(lt)
 }
 
-type linkTypeContext interface {
+type moduleWithSdkDep interface {
 	android.Module
-	getLinkType(name string) (ret linkType, stubs bool)
+	getSdkLinkType(name string) (ret sdkLinkType, stubs bool)
 }
 
-func (m *Module) getLinkType(name string) (ret linkType, stubs bool) {
+func (m *Module) getSdkLinkType(name string) (ret sdkLinkType, stubs bool) {
 	switch name {
 	case "core.current.stubs", "legacy.core.platform.api.stubs", "stable.core.platform.api.stubs",
 		"stub-annotations", "private-stub-annotations-jar",
@@ -1087,23 +1108,26 @@
 	return javaSdk, false
 }
 
-func checkLinkType(ctx android.ModuleContext, from *Module, to linkTypeContext, tag dependencyTag) {
+// checkSdkLinkType make sures the given dependency doesn't have a lower SDK link type rank than
+// this module's. See the comment on rank() for details and an example.
+func (j *Module) checkSdkLinkType(
+	ctx android.ModuleContext, dep moduleWithSdkDep, tag dependencyTag) {
 	if ctx.Host() {
 		return
 	}
 
-	myLinkType, stubs := from.getLinkType(ctx.ModuleName())
+	myLinkType, stubs := j.getSdkLinkType(ctx.ModuleName())
 	if stubs {
 		return
 	}
-	otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to))
+	depLinkType, _ := dep.getSdkLinkType(ctx.OtherModuleName(dep))
 
-	if myLinkType.rank() < otherLinkType.rank() {
+	if myLinkType.rank() < depLinkType.rank() {
 		ctx.ModuleErrorf("compiles against %v, but dependency %q is compiling against %v. "+
 			"In order to fix this, consider adjusting sdk_version: OR platform_apis: "+
 			"property of the source or target module so that target module is built "+
 			"with the same or smaller API set when compared to the source.",
-			myLinkType, ctx.OtherModuleName(to), otherLinkType)
+			myLinkType, ctx.OtherModuleName(dep), depLinkType)
 	}
 }
 
@@ -1124,7 +1148,7 @@
 		}
 	}
 
-	linkType, _ := j.getLinkType(ctx.ModuleName())
+	sdkLinkType, _ := j.getSdkLinkType(ctx.ModuleName())
 
 	ctx.VisitDirectDeps(func(module android.Module) {
 		otherName := ctx.OtherModuleName(module)
@@ -1148,7 +1172,7 @@
 			}
 		} else if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
 			dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
-			if linkType != javaPlatform &&
+			if sdkLinkType != javaPlatform &&
 				ctx.OtherModuleHasProvider(module, SyspropPublicStubInfoProvider) {
 				// dep is a sysprop implementation library, but this module is not linking against
 				// the platform, so it gets the sysprop public stubs library instead.  Replace
@@ -2496,6 +2520,10 @@
 	j.deps(ctx)
 }
 
+func (j *TestHost) AddExtraResource(p android.Path) {
+	j.extraResources = append(j.extraResources, p)
+}
+
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if j.testProperties.Test_options.Unit_test == nil && ctx.Host() {
 		// TODO(b/): Clean temporary heuristic to avoid unexpected onboarding.
@@ -2659,13 +2687,23 @@
 	module.AddProperties(&module.testProperties)
 	module.AddProperties(&module.testHostProperties)
 
-	module.Module.properties.Installable = proptools.BoolPtr(true)
+	InitTestHost(
+		module,
+		proptools.BoolPtr(true),
+		nil,
+		nil)
 
 	InitJavaModuleMultiTargets(module, android.HostSupported)
 
 	return module
 }
 
+func InitTestHost(th *TestHost, installable *bool, testSuites []string, autoGenConfig *bool) {
+	th.properties.Installable = installable
+	th.testProperties.Auto_gen_config = autoGenConfig
+	th.testProperties.Test_suites = testSuites
+}
+
 //
 // Java Binaries (.jar file plus wrapper script)
 //
@@ -3323,8 +3361,8 @@
 	module := &Defaults{}
 
 	module.AddProperties(
-		&CompilerProperties{},
-		&CompilerDeviceProperties{},
+		&CommonProperties{},
+		&DeviceProperties{},
 		&DexProperties{},
 		&DexpreoptProperties{},
 		&android.ProtoProperties{},
diff --git a/java/java_test.go b/java/java_test.go
index 670eefc..b68945f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -48,23 +48,24 @@
 	os.RemoveAll(buildDir)
 }
 
+var emptyFixtureFactory = android.NewFixtureFactory(&buildDir)
+
 // Factory to use to create fixtures for tests in this package.
-var javaFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+var javaFixtureFactory = emptyFixtureFactory.Extend(
 	genrule.PrepareForTestWithGenRuleBuildComponents,
 	// Get the CC build components but not default modules.
 	cc.PrepareForTestWithCcBuildComponents,
 	// Include all the default java modules.
 	PrepareForTestWithJavaDefaultModules,
+	python.PrepareForTestWithPythonBuildComponents,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
 		ctx.RegisterModuleType("java_plugin", PluginFactory)
-		ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
 
-		ctx.PreDepsMutators(python.RegisterPythonPreDepsMutators)
 		ctx.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
 		ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
 	}),
 	javaMockFS().AddToFixture(),
+	PrepareForTestWithJavaSdkLibraryFiles,
 	dexpreopt.PrepareForTestWithDexpreopt,
 )
 
@@ -99,11 +100,9 @@
 	RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	ctx.PreArchMutators(android.RegisterComponentsMutator)
 
-	ctx.PreDepsMutators(python.RegisterPythonPreDepsMutators)
 	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
 	ctx.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
 	ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
@@ -147,7 +146,11 @@
 // deprecated
 func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContext, android.Config) {
 	t.Helper()
-	return testJavaErrorWithConfig(t, pattern, testConfig(nil, bp, nil))
+	result := javaFixtureFactory.
+		Extend(dexpreopt.PrepareForTestWithDexpreopt).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
+		RunTestWithBp(t, bp)
+	return result.TestContext, result.Config
 }
 
 // testJavaErrorWithConfig is a legacy way of running tests of java modules that expect errors.
@@ -364,13 +367,9 @@
 	barTurbine := filepath.Join(buildDir, ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
 	bazTurbine := filepath.Join(buildDir, ".intermediates", "baz", "android_common", "turbine-combined", "baz.jar")
 
-	if !strings.Contains(javac.Args["classpath"], barTurbine) {
-		t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
-	}
+	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], barTurbine)
 
-	if !strings.Contains(javac.Args["classpath"], bazTurbine) {
-		t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], bazTurbine)
-	}
+	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], bazTurbine)
 
 	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
@@ -740,7 +739,7 @@
 }
 
 func TestJavaSdkLibraryImport(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -778,16 +777,14 @@
 		`)
 
 	for _, scope := range []string{"", ".system", ".test"} {
-		fooModule := ctx.ModuleForTests("foo"+scope, "android_common")
+		fooModule := result.ModuleForTests("foo"+scope, "android_common")
 		javac := fooModule.Rule("javac")
 
-		sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
-		if !strings.Contains(javac.Args["classpath"], sdklibStubsJar.String()) {
-			t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], sdklibStubsJar.String())
-		}
+		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
+		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
 	}
 
-	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
 		`prebuilt_sdklib.stubs.system`,
@@ -796,7 +793,10 @@
 }
 
 func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "sdklib",
 			srcs: ["a.java"],
@@ -815,7 +815,7 @@
 		}
 		`)
 
-	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
@@ -824,7 +824,7 @@
 		`sdklib.xml`,
 	})
 
-	CheckModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
@@ -835,7 +835,10 @@
 }
 
 func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "sdklib",
 			srcs: ["a.java"],
@@ -855,7 +858,7 @@
 		}
 		`)
 
-	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
@@ -864,7 +867,7 @@
 		`sdklib.xml`,
 	})
 
-	CheckModuleDependencies(t, ctx, "prebuilt_sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		`sdklib.xml`,
@@ -895,7 +898,7 @@
 		allowList                  []string
 	}
 
-	createTestConfig := func(info testConfigInfo) android.Config {
+	createPreparer := func(info testConfigInfo) android.FixturePreparer {
 		bpFileTemplate := `
 			java_library {
 				name: "foo",
@@ -918,58 +921,70 @@
 			info.libraryType,
 			partitionToBpOption(info.toPartition))
 
-		config := testConfig(nil, bpFile, nil)
-		configVariables := config.TestProductVariables
+		return android.GroupFixturePreparers(
+			PrepareForTestWithJavaSdkLibraryFiles,
+			FixtureWithLastReleaseApis("bar"),
+			android.FixtureWithRootAndroidBp(bpFile),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+				if info.enforceVendorInterface {
+					variables.DeviceVndkVersion = proptools.StringPtr("current")
+				}
+				variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+				variables.InterPartitionJavaLibraryAllowList = info.allowList
+			}),
+		)
+	}
 
-		configVariables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
-		if info.enforceVendorInterface {
-			configVariables.DeviceVndkVersion = proptools.StringPtr("current")
-		}
-		configVariables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
-		configVariables.InterPartitionJavaLibraryAllowList = info.allowList
-
-		return config
+	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
+		t.Run(fmt.Sprintf("%#v", info), func(t *testing.T) {
+			errorHandler := android.FixtureExpectsNoErrors
+			if expectedErrorPattern != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
+			}
+			javaFixtureFactory.ExtendWithErrorHandler(errorHandler).RunTest(t, createPreparer(info))
+		})
 	}
 
 	errorMessage := "is not allowed across the partitions"
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "product",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: false,
-	}))
+	}, "")
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "product",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    false,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, "")
 
-	testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "product",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, errorMessage)
 
-	testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "vendor",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, errorMessage)
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "vendor",
 		toPartition:                "system",
@@ -977,43 +992,43 @@
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
 		allowList:                  []string{"bar"},
-	}))
+	}, "")
 
-	testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_library",
 		fromPartition:              "vendor",
 		toPartition:                "product",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, errorMessage)
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_sdk_library",
 		fromPartition:              "product",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, "")
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_sdk_library",
 		fromPartition:              "vendor",
 		toPartition:                "system",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, "")
 
-	testJavaWithConfig(t, createTestConfig(testConfigInfo{
+	runTest(t, testConfigInfo{
 		libraryType:                "java_sdk_library",
 		fromPartition:              "vendor",
 		toPartition:                "product",
 		enforceVendorInterface:     true,
 		enforceProductInterface:    true,
 		enforceJavaSdkLibraryCheck: true,
-	}))
+	}, "")
 }
 
 func TestDefaults(t *testing.T) {
@@ -1829,7 +1844,14 @@
 }
 
 func TestJavaSdkLibrary(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+			"29": {"foo"},
+			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
+		}),
+	).RunTestWithBp(t, `
 		droiddoc_exported_dir {
 			name: "droiddoc-templates-sdk",
 			path: ".",
@@ -1906,68 +1928,51 @@
 		`)
 
 	// check the existence of the internal modules
-	ctx.ModuleForTests("foo", "android_common")
-	ctx.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
-	ctx.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
-	ctx.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
-	ctx.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
-	ctx.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
-	ctx.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
-	ctx.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
-	ctx.ModuleForTests("foo.api.public.28", "")
-	ctx.ModuleForTests("foo.api.system.28", "")
-	ctx.ModuleForTests("foo.api.test.28", "")
+	result.ModuleForTests("foo", "android_common")
+	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
+	result.ModuleForTests("foo.api.public.28", "")
+	result.ModuleForTests("foo.api.system.28", "")
+	result.ModuleForTests("foo.api.test.28", "")
 
-	bazJavac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
+	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
 	// tests if baz is actually linked to the stubs lib
-	if !strings.Contains(bazJavac.Args["classpath"], "foo.stubs.system.jar") {
-		t.Errorf("baz javac classpath %v does not contain %q", bazJavac.Args["classpath"],
-			"foo.stubs.system.jar")
-	}
+	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
 	// ... and not to the impl lib
-	if strings.Contains(bazJavac.Args["classpath"], "foo.jar") {
-		t.Errorf("baz javac classpath %v should not contain %q", bazJavac.Args["classpath"],
-			"foo.jar")
-	}
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
 	// test if baz is not linked to the system variant of foo
-	if strings.Contains(bazJavac.Args["classpath"], "foo.stubs.jar") {
-		t.Errorf("baz javac classpath %v should not contain %q", bazJavac.Args["classpath"],
-			"foo.stubs.jar")
-	}
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
 
-	bazTestJavac := ctx.ModuleForTests("baz-test", "android_common").Rule("javac")
+	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
 	// tests if baz-test is actually linked to the test stubs lib
-	if !strings.Contains(bazTestJavac.Args["classpath"], "foo.stubs.test.jar") {
-		t.Errorf("baz-test javac classpath %v does not contain %q", bazTestJavac.Args["classpath"],
-			"foo.stubs.test.jar")
-	}
+	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
 
-	baz29Javac := ctx.ModuleForTests("baz-29", "android_common").Rule("javac")
+	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
 	// tests if baz-29 is actually linked to the system 29 stubs lib
-	if !strings.Contains(baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar") {
-		t.Errorf("baz-29 javac classpath %v does not contain %q", baz29Javac.Args["classpath"],
-			"prebuilts/sdk/29/system/foo.jar")
-	}
+	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar")
 
-	bazModule30Javac := ctx.ModuleForTests("baz-module-30", "android_common").Rule("javac")
+	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
 	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
-	if !strings.Contains(bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar") {
-		t.Errorf("baz-module-30 javac classpath %v does not contain %q", bazModule30Javac.Args["classpath"],
-			"prebuilts/sdk/30/module-lib/foo.jar")
-	}
+	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar")
 
 	// test if baz has exported SDK lib names foo and bar to qux
-	qux := ctx.ModuleForTests("qux", "android_common")
+	qux := result.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
 		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
-		if w := []string{"foo", "bar", "fred", "quuz"}; !reflect.DeepEqual(w, sdkLibs) {
-			t.Errorf("qux should export %q but exports %q", w, sdkLibs)
-		}
+		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
 	}
 }
 
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "sdklib",
 			srcs: ["a.java"],
@@ -1987,20 +1992,23 @@
 		`)
 
 	for _, implName := range []string{"sdklib", "sdklib.impl"} {
-		implJavacCp := ctx.ModuleForTests(implName, "android_common").Rule("javac").Args["classpath"]
+		implJavacCp := result.ModuleForTests(implName, "android_common").Rule("javac").Args["classpath"]
 		if !strings.Contains(implJavacCp, "/foo.jar") || strings.Contains(implJavacCp, "/bar.jar") {
 			t.Errorf("%v javac classpath %v does not contain foo and not bar", implName, implJavacCp)
 		}
 	}
 	stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
-	stubsJavacCp := ctx.ModuleForTests(stubName, "android_common").Rule("javac").Args["classpath"]
+	stubsJavacCp := result.ModuleForTests(stubName, "android_common").Rule("javac").Args["classpath"]
 	if strings.Contains(stubsJavacCp, "/foo.jar") || !strings.Contains(stubsJavacCp, "/bar.jar") {
 		t.Errorf("stubs javac classpath %v does not contain bar and not foo", stubsJavacCp)
 	}
 }
 
 func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2018,14 +2026,17 @@
 		`)
 
 	// The bar library should depend on the stubs jar.
-	barLibrary := ctx.ModuleForTests("bar", "android_common").Rule("javac")
+	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
 	if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
 
 func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2043,7 +2054,13 @@
 }
 
 func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
-	testJavaError(t, `"foo" does not provide api scope system`, `
+	javaFixtureFactory.
+		Extend(
+			PrepareForTestWithJavaSdkLibraryFiles,
+			FixtureWithLastReleaseApis("foo"),
+		).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
+		RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2061,7 +2078,10 @@
 }
 
 func TestJavaSdkLibrary_Deps(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "sdklib",
 			srcs: ["a.java"],
@@ -2073,7 +2093,7 @@
 		}
 		`)
 
-	CheckModuleDependencies(t, ctx, "sdklib", "android_common", []string{
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
 		`dex2oatd`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
@@ -2083,7 +2103,7 @@
 }
 
 func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.RunTestWithBp(t, `
 		java_sdk_library_import {
 			name: "foo",
 			public: {
@@ -2116,63 +2136,74 @@
 		`
 
 	t.Run("stubs.source", func(t *testing.T) {
-		testJavaError(t, `stubs.source not available for api scope public`, bp+`
-		java_library {
-			name: "bar",
-			srcs: [":foo{.public.stubs.source}"],
-			java_resources: [
-				":foo{.public.api.txt}",
-				":foo{.public.removed-api.txt}",
-			],
-		}
-		`)
+		javaFixtureFactory.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: [":foo{.public.stubs.source}"],
+					java_resources: [
+						":foo{.public.api.txt}",
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
 	})
 
 	t.Run("api.txt", func(t *testing.T) {
-		testJavaError(t, `api.txt not available for api scope public`, bp+`
-		java_library {
-			name: "bar",
-			srcs: ["a.java"],
-			java_resources: [
-				":foo{.public.api.txt}",
-			],
-		}
-		`)
+		javaFixtureFactory.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.api.txt}",
+					],
+				}
+			`)
 	})
 
 	t.Run("removed-api.txt", func(t *testing.T) {
-		testJavaError(t, `removed-api.txt not available for api scope public`, bp+`
-		java_library {
-			name: "bar",
-			srcs: ["a.java"],
-			java_resources: [
-				":foo{.public.removed-api.txt}",
-			],
-		}
-		`)
+		javaFixtureFactory.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
 	})
 }
 
 func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
-	testJavaError(t, `module "foo": enabled api scope "system" depends on disabled scope "public"`, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			// Explicitly disable public to test the check that ensures the set of enabled
-			// scopes is consistent.
-			public: {
-				enabled: false,
-			},
-			system: {
-				enabled: true,
-			},
-		}
+	javaFixtureFactory.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java", "b.java"],
+				api_packages: ["foo"],
+				// Explicitly disable public to test the check that ensures the set of enabled
+				// scopes is consistent.
+				public: {
+					enabled: false,
+				},
+				system: {
+					enabled: true,
+				},
+			}
 		`)
 }
 
 func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java", "b.java"],
@@ -2186,7 +2217,10 @@
 }
 
 func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java", "b.java"],
@@ -2202,7 +2236,10 @@
 }
 
 func TestJavaSdkLibrary_SystemServer(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java", "b.java"],
@@ -2218,26 +2255,31 @@
 }
 
 func TestJavaSdkLibrary_MissingScope(t *testing.T) {
-	testJavaError(t, `requires api scope module-lib from foo but it only has \[\] available`, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			public: {
-				enabled: false,
-			},
-		}
+	javaFixtureFactory.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java"],
+				public: {
+					enabled: false,
+				},
+			}
 
-		java_library {
-			name: "baz",
-			srcs: ["a.java"],
-			libs: ["foo"],
-			sdk_version: "module_current",
-		}
+			java_library {
+				name: "baz",
+				srcs: ["a.java"],
+				libs: ["foo"],
+				sdk_version: "module_current",
+			}
 		`)
 }
 
 func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
-	testJava(t, `
+	javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2257,7 +2299,10 @@
 }
 
 func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := javaFixtureFactory.Extend(
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2277,7 +2322,7 @@
 		}
 		`)
 	// The baz library should depend on the system stubs jar.
-	bazLibrary := ctx.ModuleForTests("baz", "android_common").Rule("javac")
+	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
 	if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
@@ -2554,3 +2599,16 @@
 		t.Errorf("Unexpected test data - expected: %q, actual: %q", expected, actual)
 	}
 }
+
+func TestDefaultInstallable(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_test_host {
+			name: "foo"
+		}
+	`)
+
+	buildOS := android.BuildOs.String()
+	module := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
+	assertDeepEquals(t, "Default installable value should be true.", proptools.BoolPtr(true),
+		module.properties.Installable)
+}
diff --git a/java/kotlin.go b/java/kotlin.go
index 8067ad5..2960f81 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -64,7 +64,9 @@
 		// Insert a second rule to write out the list of resources to a file.
 		commonSrcsList := android.PathForModuleOut(ctx, "kotlinc_common_srcs.list")
 		rule := android.NewRuleBuilder(pctx, ctx)
-		rule.Command().Text("cp").FlagWithRspFileInputList("", commonSrcFiles).Output(commonSrcsList)
+		rule.Command().Text("cp").
+			FlagWithRspFileInputList("", commonSrcsList.ReplaceExtension(ctx, "rsp"), commonSrcFiles).
+			Output(commonSrcsList)
 		rule.Build("kotlin_common_srcs_list", "kotlin common_srcs list")
 		return android.OptionalPathForPath(commonSrcsList)
 	}
diff --git a/java/legacy_core_platform_api_usage.go b/java/legacy_core_platform_api_usage.go
index a6639a5..5949edd 100644
--- a/java/legacy_core_platform_api_usage.go
+++ b/java/legacy_core_platform_api_usage.go
@@ -83,6 +83,7 @@
 	"FrameworksNetTests",
 	"FrameworksServicesRoboTests",
 	"FrameworksServicesTests",
+	"FrameworksMockingServicesTests",
 	"FrameworksUtilTests",
 	"FrameworksWifiTests",
 	"hid",
@@ -160,7 +161,11 @@
 }
 
 func useLegacyCorePlatformApi(ctx android.EarlyModuleContext) bool {
-	_, found := legacyCorePlatformApiLookup[ctx.ModuleName()]
+	return useLegacyCorePlatformApiByName(ctx.ModuleName())
+}
+
+func useLegacyCorePlatformApiByName(name string) bool {
+	_, found := legacyCorePlatformApiLookup[name]
 	return found
 }
 
diff --git a/java/lint.go b/java/lint.go
index 9f677db..fccd1a5 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -220,7 +220,9 @@
 		// Insert a second rule to write out the list of resources to a file.
 		resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
 		resListRule := android.NewRuleBuilder(pctx, ctx)
-		resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
+		resListRule.Command().Text("cp").
+			FlagWithRspFileInputList("", resourcesList.ReplaceExtension(ctx, "rsp"), l.resources).
+			Output(resourcesList)
 		resListRule.Build("lint_resources_list", "lint resources list")
 		trackRSPDependency(l.resources, resourcesList)
 	}
@@ -241,7 +243,10 @@
 	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
 	// lint separately.
 	srcsList := android.PathForModuleOut(ctx, "lint", "srcs.list")
-	rule.Command().Text("cp").FlagWithRspFileInputList("", l.srcs).Output(srcsList)
+	srcsListRsp := android.PathForModuleOut(ctx, "lint-srcs.list.rsp")
+	rule.Command().Text("cp").
+		FlagWithRspFileInputList("", srcsListRsp, l.srcs).
+		Output(srcsList)
 	trackRSPDependency(l.srcs, srcsList)
 
 	cmd := rule.Command().
@@ -635,7 +640,7 @@
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputPath).
 		FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
-		FlagWithRspFileInputList("-r ", paths)
+		FlagWithRspFileInputList("-r ", outputPath.ReplaceExtension(ctx, "rsp"), paths)
 
 	rule.Build(outputPath.Base(), outputPath.Base())
 }
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 2c47b0a..3c43a8e 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -20,19 +20,22 @@
 )
 
 func init() {
-	android.RegisterSingletonType("platform_compat_config_singleton", platformCompatConfigSingletonFactory)
-	android.RegisterModuleType("platform_compat_config", PlatformCompatConfigFactory)
-	android.RegisterModuleType("global_compat_config", globalCompatConfigFactory)
+	registerPlatformCompatConfigBuildComponents(android.InitRegistrationContext)
 }
 
+func registerPlatformCompatConfigBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("platform_compat_config_singleton", platformCompatConfigSingletonFactory)
+	ctx.RegisterModuleType("platform_compat_config", PlatformCompatConfigFactory)
+	ctx.RegisterModuleType("prebuilt_platform_compat_config", prebuiltCompatConfigFactory)
+	ctx.RegisterModuleType("global_compat_config", globalCompatConfigFactory)
+}
+
+var PrepareForTestWithPlatformCompatConfig = android.FixtureRegisterWithContext(registerPlatformCompatConfigBuildComponents)
+
 func platformCompatConfigPath(ctx android.PathContext) android.OutputPath {
 	return android.PathForOutput(ctx, "compat_config", "merged_compat_config.xml")
 }
 
-type platformCompatConfigSingleton struct {
-	metadata android.Path
-}
-
 type platformCompatConfigProperties struct {
 	Src *string `android:"path"`
 }
@@ -46,7 +49,7 @@
 	metadataFile   android.OutputPath
 }
 
-func (p *platformCompatConfig) compatConfigMetadata() android.OutputPath {
+func (p *platformCompatConfig) compatConfigMetadata() android.Path {
 	return p.metadataFile
 }
 
@@ -58,52 +61,20 @@
 	return "compatconfig"
 }
 
+type platformCompatConfigMetadataProvider interface {
+	compatConfigMetadata() android.Path
+}
+
 type PlatformCompatConfigIntf interface {
 	android.Module
 
-	compatConfigMetadata() android.OutputPath
 	CompatConfig() android.OutputPath
 	// Sub dir under etc dir.
 	SubDir() string
 }
 
 var _ PlatformCompatConfigIntf = (*platformCompatConfig)(nil)
-
-// compat singleton rules
-func (p *platformCompatConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-
-	var compatConfigMetadata android.Paths
-
-	ctx.VisitAllModules(func(module android.Module) {
-		if c, ok := module.(PlatformCompatConfigIntf); ok {
-			metadata := c.compatConfigMetadata()
-			compatConfigMetadata = append(compatConfigMetadata, metadata)
-		}
-	})
-
-	if compatConfigMetadata == nil {
-		// nothing to do.
-		return
-	}
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	outputPath := platformCompatConfigPath(ctx)
-
-	rule.Command().
-		BuiltTool("process-compat-config").
-		FlagForEachInput("--xml ", compatConfigMetadata).
-		FlagWithOutput("--merged-config ", outputPath)
-
-	rule.Build("merged-compat-config", "Merge compat config")
-
-	p.metadata = outputPath
-}
-
-func (p *platformCompatConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if p.metadata != nil {
-		ctx.Strict("INTERNAL_PLATFORM_MERGED_COMPAT_CONFIG", p.metadata.String())
-	}
-}
+var _ platformCompatConfigMetadataProvider = (*platformCompatConfig)(nil)
 
 func (p *platformCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	rule := android.NewRuleBuilder(pctx, ctx)
@@ -139,17 +110,100 @@
 	}}
 }
 
-func platformCompatConfigSingletonFactory() android.Singleton {
-	return &platformCompatConfigSingleton{}
-}
-
 func PlatformCompatConfigFactory() android.Module {
 	module := &platformCompatConfig{}
 	module.AddProperties(&module.properties)
-	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
 
+// A prebuilt version of the platform compat config module.
+type prebuiltCompatConfigModule struct {
+	android.ModuleBase
+	android.SdkBase
+	prebuilt android.Prebuilt
+
+	properties prebuiltCompatConfigProperties
+
+	metadataFile android.Path
+}
+
+type prebuiltCompatConfigProperties struct {
+	Metadata *string `android:"path"`
+}
+
+func (module *prebuiltCompatConfigModule) Prebuilt() *android.Prebuilt {
+	return &module.prebuilt
+}
+
+func (module *prebuiltCompatConfigModule) Name() string {
+	return module.prebuilt.Name(module.ModuleBase.Name())
+}
+
+func (module *prebuiltCompatConfigModule) compatConfigMetadata() android.Path {
+	return module.metadataFile
+}
+
+var _ platformCompatConfigMetadataProvider = (*prebuiltCompatConfigModule)(nil)
+
+func (module *prebuiltCompatConfigModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	module.metadataFile = module.prebuilt.SingleSourcePath(ctx)
+}
+
+// A prebuilt version of platform_compat_config that provides the metadata.
+func prebuiltCompatConfigFactory() android.Module {
+	m := &prebuiltCompatConfigModule{}
+	m.AddProperties(&m.properties)
+	android.InitSingleSourcePrebuiltModule(m, &m.properties, "Metadata")
+	android.InitSdkAwareModule(m)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+// compat singleton rules
+type platformCompatConfigSingleton struct {
+	metadata android.Path
+}
+
+func (p *platformCompatConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+
+	var compatConfigMetadata android.Paths
+
+	ctx.VisitAllModules(func(module android.Module) {
+		if c, ok := module.(platformCompatConfigMetadataProvider); ok {
+			metadata := c.compatConfigMetadata()
+			compatConfigMetadata = append(compatConfigMetadata, metadata)
+		}
+	})
+
+	if compatConfigMetadata == nil {
+		// nothing to do.
+		return
+	}
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	outputPath := platformCompatConfigPath(ctx)
+
+	rule.Command().
+		BuiltTool("process-compat-config").
+		FlagForEachInput("--xml ", compatConfigMetadata).
+		FlagWithOutput("--merged-config ", outputPath)
+
+	rule.Build("merged-compat-config", "Merge compat config")
+
+	p.metadata = outputPath
+}
+
+func (p *platformCompatConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
+	if p.metadata != nil {
+		ctx.Strict("INTERNAL_PLATFORM_MERGED_COMPAT_CONFIG", p.metadata.String())
+	}
+}
+
+func platformCompatConfigSingletonFactory() android.Singleton {
+	return &platformCompatConfigSingleton{}
+}
+
 //============== merged_compat_config =================
 type globalCompatConfigProperties struct {
 	// name of the file into which the metadata will be copied.
diff --git a/java/platform_compat_config_test.go b/java/platform_compat_config_test.go
new file mode 100644
index 0000000..0c5d001
--- /dev/null
+++ b/java/platform_compat_config_test.go
@@ -0,0 +1,53 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestPlatformCompatConfig(t *testing.T) {
+	result := emptyFixtureFactory.RunTest(t,
+		PrepareForTestWithPlatformCompatConfig,
+		android.FixtureWithRootAndroidBp(`
+			platform_compat_config {
+				name: "myconfig2",
+			}
+			platform_compat_config {
+				name: "myconfig1",
+			}
+			platform_compat_config {
+				name: "myconfig3",
+			}
+		`),
+	)
+
+	checkMergedCompatConfigInputs(t, result, "myconfig",
+		"out/soong/.intermediates/myconfig1/myconfig1_meta.xml",
+		"out/soong/.intermediates/myconfig2/myconfig2_meta.xml",
+		"out/soong/.intermediates/myconfig3/myconfig3_meta.xml",
+	)
+}
+
+// Check that the merged file create by platform_compat_config_singleton has the correct inputs.
+func checkMergedCompatConfigInputs(t *testing.T, result *android.TestResult, message string, expectedPaths ...string) {
+	sourceGlobalCompatConfig := result.SingletonForTests("platform_compat_config_singleton")
+	allOutputs := sourceGlobalCompatConfig.AllOutputs()
+	android.AssertIntEquals(t, message+": output len", 1, len(allOutputs))
+	output := sourceGlobalCompatConfig.Output(allOutputs[0])
+	android.AssertPathsRelativeToTopEquals(t, message+": inputs", expectedPaths, output.Implicits)
+}
diff --git a/java/proto.go b/java/proto.go
index 652a4da..8731822 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -94,7 +94,7 @@
 	}
 }
 
-func protoFlags(ctx android.ModuleContext, j *CompilerProperties, p *android.ProtoProperties,
+func protoFlags(ctx android.ModuleContext, j *CommonProperties, p *android.ProtoProperties,
 	flags javaBuilderFlags) javaBuilderFlags {
 
 	flags.proto = android.GetProtoFlags(ctx, p)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index b03f90c..e1ca77d 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1681,7 +1681,7 @@
 
 var _ sdkLibraryComponentNamingScheme = (*defaultNamingScheme)(nil)
 
-func moduleStubLinkType(name string) (stub bool, ret linkType) {
+func moduleStubLinkType(name string) (stub bool, ret sdkLinkType) {
 	// This suffix-based approach is fragile and could potentially mis-trigger.
 	// TODO(b/155164730): Clean this up when modules no longer reference sdk_lib stubs directly.
 	if strings.HasSuffix(name, ".stubs.public") || strings.HasSuffix(name, "-stubs-publicapi") {
diff --git a/java/testing.go b/java/testing.go
index 4e1997e..896bcf8 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -61,27 +61,95 @@
 	PrepareForTestWithJavaDefaultModules,
 )
 
+// Prepare a fixture with the standard files required by a java_sdk_library module.
+var PrepareForTestWithJavaSdkLibraryFiles = android.FixtureMergeMockFs(javaSdkLibraryFiles)
+
+var javaSdkLibraryFiles = android.MockFS{
+	"api/current.txt":               nil,
+	"api/removed.txt":               nil,
+	"api/system-current.txt":        nil,
+	"api/system-removed.txt":        nil,
+	"api/test-current.txt":          nil,
+	"api/test-removed.txt":          nil,
+	"api/module-lib-current.txt":    nil,
+	"api/module-lib-removed.txt":    nil,
+	"api/system-server-current.txt": nil,
+	"api/system-server-removed.txt": nil,
+}
+
+// FixtureWithLastReleaseApis creates a preparer that creates prebuilt versions of the specified
+// modules for the `last` API release. By `last` it just means last in the list of supplied versions
+// and as this only provides one version it can be any value.
+//
+// This uses FixtureWithPrebuiltApis under the covers so the limitations of that apply to this.
+func FixtureWithLastReleaseApis(moduleNames ...string) android.FixturePreparer {
+	return FixtureWithPrebuiltApis(map[string][]string{
+		"30": moduleNames,
+	})
+}
+
+// PrepareForTestWithPrebuiltsOfCurrentApi is a preparer that creates prebuilt versions of the
+// standard modules for the current version.
+//
+// This uses FixtureWithPrebuiltApis under the covers so the limitations of that apply to this.
+var PrepareForTestWithPrebuiltsOfCurrentApi = FixtureWithPrebuiltApis(map[string][]string{
+	"current": {},
+	// Can't have current on its own as it adds a prebuilt_apis module but doesn't add any
+	// .txt files which causes the prebuilt_apis module to fail.
+	"30": {},
+})
+
+// FixtureWithPrebuiltApis creates a preparer that will define prebuilt api modules for the
+// specified releases and modules.
+//
+// The supplied map keys are the releases, e.g. current, 29, 30, etc. The values are a list of
+// modules for that release. Due to limitations in the prebuilt_apis module which this preparer
+// uses the set of releases must include at least one numbered release, i.e. it cannot just include
+// "current".
+//
+// This defines a file in the mock file system in a predefined location (prebuilts/sdk/Android.bp)
+// and so only one instance of this can be used in each fixture.
+func FixtureWithPrebuiltApis(release2Modules map[string][]string) android.FixturePreparer {
+	mockFS := android.MockFS{}
+	path := "prebuilts/sdk/Android.bp"
+
+	bp := fmt.Sprintf(`
+			prebuilt_apis {
+				name: "sdk",
+				api_dirs: ["%s"],
+				imports_sdk_version: "none",
+				imports_compile_dex: true,
+			}
+		`, strings.Join(android.SortedStringKeys(release2Modules), `", "`))
+
+	for release, modules := range release2Modules {
+		libs := append([]string{"android", "core-for-system-modules"}, modules...)
+		mockFS.Merge(prebuiltApisFilesForLibs([]string{release}, libs))
+	}
+	return android.GroupFixturePreparers(
+		// A temporary measure to discard the definitions provided by default by javaMockFS() to allow
+		// the changes that use this preparer to fix tests to be separated from the change to remove
+		// javaMockFS().
+		android.FixtureModifyMockFS(func(fs android.MockFS) {
+			for k, _ := range fs {
+				if strings.HasPrefix(k, "prebuilts/sdk/") {
+					delete(fs, k)
+				}
+			}
+		}),
+		android.FixtureAddTextFile(path, bp),
+		android.FixtureMergeMockFs(mockFS),
+	)
+}
+
 func javaMockFS() android.MockFS {
 	mockFS := android.MockFS{
-		"api/current.txt":        nil,
-		"api/removed.txt":        nil,
-		"api/system-current.txt": nil,
-		"api/system-removed.txt": nil,
-		"api/test-current.txt":   nil,
-		"api/test-removed.txt":   nil,
-
 		"prebuilts/sdk/tools/core-lambda-stubs.jar": nil,
 		"prebuilts/sdk/Android.bp":                  []byte(`prebuilt_apis { name: "sdk", api_dirs: ["14", "28", "30", "current"], imports_sdk_version: "none", imports_compile_dex:true,}`),
 
 		"bin.py": nil,
 		python.StubTemplateHost: []byte(`PYTHON_BINARY = '%interpreter%'
 		MAIN_FILE = '%main%'`),
-
-		// For java_sdk_library
-		"api/module-lib-current.txt":    nil,
-		"api/module-lib-removed.txt":    nil,
-		"api/system-server-current.txt": nil,
-		"api/system-server-removed.txt": nil,
 	}
 
 	levels := []string{"14", "28", "29", "30", "current"}
@@ -101,6 +169,7 @@
 	bp += GatherRequiredDepsForTest()
 
 	mockFS := javaMockFS()
+	mockFS.Merge(javaSdkLibraryFiles)
 
 	cc.GatherRequiredFilesForTest(mockFS)
 
diff --git a/python/Android.bp b/python/Android.bp
index b633f1e..e49fa6a 100644
--- a/python/Android.bp
+++ b/python/Android.bp
@@ -20,6 +20,7 @@
         "proto.go",
         "python.go",
         "test.go",
+        "testing.go",
     ],
     testSrcs: [
         "python_test.go",
diff --git a/python/binary.go b/python/binary.go
index 372b8a8..6061ad4 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -26,10 +26,14 @@
 )
 
 func init() {
-	android.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
+	registerPythonBinaryComponents(android.InitRegistrationContext)
 	android.RegisterBp2BuildMutator("python_binary_host", PythonBinaryBp2Build)
 }
 
+func registerPythonBinaryComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
+}
+
 type bazelPythonBinaryAttributes struct {
 	Main           string
 	Srcs           bazel.LabelList
diff --git a/python/library.go b/python/library.go
index b724d2b..9663b3c 100644
--- a/python/library.go
+++ b/python/library.go
@@ -21,8 +21,12 @@
 )
 
 func init() {
-	android.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
-	android.RegisterModuleType("python_library", PythonLibraryFactory)
+	registerPythonLibraryComponents(android.InitRegistrationContext)
+}
+
+func registerPythonLibraryComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
+	ctx.RegisterModuleType("python_library", PythonLibraryFactory)
 }
 
 func PythonLibraryHostFactory() android.Module {
diff --git a/python/python.go b/python/python.go
index a078c0b..4444a70 100644
--- a/python/python.go
+++ b/python/python.go
@@ -29,7 +29,11 @@
 )
 
 func init() {
-	android.PreDepsMutators(RegisterPythonPreDepsMutators)
+	registerPythonMutators(android.InitRegistrationContext)
+}
+
+func registerPythonMutators(ctx android.RegistrationContext) {
+	ctx.PreDepsMutators(RegisterPythonPreDepsMutators)
 }
 
 // Exported to support other packages using Python modules in tests.
diff --git a/python/python_test.go b/python/python_test.go
index 5c4efa7..6263c8a 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -15,21 +15,15 @@
 package python
 
 import (
-	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
-	"reflect"
-	"sort"
-	"strings"
+	"regexp"
 	"testing"
 
 	"android/soong/android"
 )
 
-var buildDir string
-
 type pyModule struct {
 	name          string
 	actualVersion string
@@ -56,7 +50,7 @@
 
 	data = []struct {
 		desc      string
-		mockFiles map[string][]byte
+		mockFiles android.MockFS
 
 		errors           []string
 		expectedBinaries []pyModule
@@ -64,7 +58,6 @@
 		{
 			desc: "module without any src files",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -79,7 +72,6 @@
 		{
 			desc: "module with bad src file ext",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -98,7 +90,6 @@
 		{
 			desc: "module with bad data file ext",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -121,7 +112,6 @@
 		{
 			desc: "module with bad pkg_path format",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -159,7 +149,6 @@
 		{
 			desc: "module with bad runfile src path format",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -187,7 +176,6 @@
 		{
 			desc: "module with duplicate runfile path",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_library_host {
 						name: "lib1",
@@ -207,21 +195,32 @@
 							"lib1",
 						],
 					}
+
+					python_binary_host {
+						name: "bin",
+						pkg_path: "e/",
+						srcs: [
+							"bin.py",
+						],
+						libs: [
+							"lib2",
+						],
+					}
 					`,
 				),
 				"dir/c/file1.py": nil,
 				"dir/file1.py":   nil,
+				"dir/bin.py":     nil,
 			},
 			errors: []string{
-				fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:9:6",
-					"lib2", "PY3", "a/b/c/file1.py", "lib2", "dir/file1.py",
+				fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6",
+					"bin", "PY3", "a/b/c/file1.py", "bin", "dir/file1.py",
 					"lib1", "dir/c/file1.py"),
 			},
 		},
 		{
 			desc: "module for testing dependencies",
 			mockFiles: map[string][]byte{
-				bpFile: []byte(`subdirs = ["dir"]`),
 				filepath.Join("dir", bpFile): []byte(
 					`python_defaults {
 						name: "default_lib",
@@ -314,10 +313,10 @@
 						"e/default_py3.py",
 						"e/file4.py",
 					},
-					srcsZip: "@prefix@/.intermediates/dir/bin/PY3/bin.py.srcszip",
+					srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip",
 					depsSrcsZips: []string{
-						"@prefix@/.intermediates/dir/lib5/PY3/lib5.py.srcszip",
-						"@prefix@/.intermediates/dir/lib6/PY3/lib6.py.srcszip",
+						"out/soong/.intermediates/dir/lib5/PY3/lib5.py.srcszip",
+						"out/soong/.intermediates/dir/lib6/PY3/lib6.py.srcszip",
 					},
 				},
 			},
@@ -327,60 +326,37 @@
 
 func TestPythonModule(t *testing.T) {
 	for _, d := range data {
+		if d.desc != "module with duplicate runfile path" {
+			continue
+		}
+		errorPatterns := make([]string, len(d.errors))
+		for i, s := range d.errors {
+			errorPatterns[i] = regexp.QuoteMeta(s)
+		}
+
 		t.Run(d.desc, func(t *testing.T) {
-			config := android.TestConfig(buildDir, nil, "", d.mockFiles)
-			ctx := android.NewTestContext(config)
-			ctx.PreDepsMutators(RegisterPythonPreDepsMutators)
-			ctx.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
-			ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
-			ctx.RegisterModuleType("python_defaults", defaultsFactory)
-			ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-			ctx.Register()
-			_, testErrs := ctx.ParseBlueprintsFiles(bpFile)
-			android.FailIfErrored(t, testErrs)
-			_, actErrs := ctx.PrepareBuildActions(config)
-			if len(actErrs) > 0 {
-				testErrs = append(testErrs, expectErrors(t, actErrs, d.errors)...)
-			} else {
-				for _, e := range d.expectedBinaries {
-					testErrs = append(testErrs,
-						expectModule(t, ctx, buildDir, e.name,
-							e.actualVersion,
-							e.srcsZip,
-							e.pyRunfiles,
-							e.depsSrcsZips)...)
-				}
+			result := emptyFixtureFactory.
+				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(errorPatterns)).
+				RunTest(t,
+					android.PrepareForTestWithDefaults,
+					PrepareForTestWithPythonBuildComponents,
+					d.mockFiles.AddToFixture(),
+				)
+
+			if len(result.Errs) > 0 {
+				return
 			}
-			android.FailIfErrored(t, testErrs)
+
+			for _, e := range d.expectedBinaries {
+				t.Run(e.name, func(t *testing.T) {
+					expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles, e.depsSrcsZips)
+				})
+			}
 		})
 	}
 }
 
-func expectErrors(t *testing.T, actErrs []error, expErrs []string) (testErrs []error) {
-	actErrStrs := []string{}
-	for _, v := range actErrs {
-		actErrStrs = append(actErrStrs, v.Error())
-	}
-	sort.Strings(actErrStrs)
-	if len(actErrStrs) != len(expErrs) {
-		t.Errorf("got (%d) errors, expected (%d) errors!", len(actErrStrs), len(expErrs))
-		for _, v := range actErrStrs {
-			testErrs = append(testErrs, errors.New(v))
-		}
-	} else {
-		sort.Strings(expErrs)
-		for i, v := range actErrStrs {
-			if !strings.Contains(v, expErrs[i]) {
-				testErrs = append(testErrs, errors.New(v))
-			}
-		}
-	}
-
-	return
-}
-
-func expectModule(t *testing.T, ctx *android.TestContext, buildDir, name, variant, expectedSrcsZip string,
-	expectedPyRunfiles, expectedDepsSrcsZips []string) (testErrs []error) {
+func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles, expectedDepsSrcsZips []string) {
 	module := ctx.ModuleForTests(name, variant)
 
 	base, baseOk := module.Module().(*Module)
@@ -393,56 +369,15 @@
 		actualPyRunfiles = append(actualPyRunfiles, path.dest)
 	}
 
-	if !reflect.DeepEqual(actualPyRunfiles, expectedPyRunfiles) {
-		testErrs = append(testErrs, errors.New(fmt.Sprintf(
-			`binary "%s" variant "%s" has unexpected pyRunfiles: %q! (expected: %q)`,
-			base.Name(),
-			base.properties.Actual_version,
-			actualPyRunfiles,
-			expectedPyRunfiles)))
-	}
+	android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles)
 
-	if base.srcsZip.String() != strings.Replace(expectedSrcsZip, "@prefix@", buildDir, 1) {
-		testErrs = append(testErrs, errors.New(fmt.Sprintf(
-			`binary "%s" variant "%s" has unexpected srcsZip: %q!`,
-			base.Name(),
-			base.properties.Actual_version,
-			base.srcsZip)))
-	}
+	android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip)
 
-	for i, _ := range expectedDepsSrcsZips {
-		expectedDepsSrcsZips[i] = strings.Replace(expectedDepsSrcsZips[i], "@prefix@", buildDir, 1)
-	}
-	if !reflect.DeepEqual(base.depsSrcsZips.Strings(), expectedDepsSrcsZips) {
-		testErrs = append(testErrs, errors.New(fmt.Sprintf(
-			`binary "%s" variant "%s" has unexpected depsSrcsZips: %q!`,
-			base.Name(),
-			base.properties.Actual_version,
-			base.depsSrcsZips)))
-	}
-
-	return
+	android.AssertPathsRelativeToTopEquals(t, "depsSrcsZips", expectedDepsSrcsZips, base.depsSrcsZips)
 }
 
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_python_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
+var emptyFixtureFactory = android.NewFixtureFactory(nil)
 
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
diff --git a/python/test.go b/python/test.go
index b7cd475..6713189 100644
--- a/python/test.go
+++ b/python/test.go
@@ -22,8 +22,12 @@
 // This file contains the module types for building Python test.
 
 func init() {
-	android.RegisterModuleType("python_test_host", PythonTestHostFactory)
-	android.RegisterModuleType("python_test", PythonTestFactory)
+	registerPythonTestComponents(android.InitRegistrationContext)
+}
+
+func registerPythonTestComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("python_test_host", PythonTestHostFactory)
+	ctx.RegisterModuleType("python_test", PythonTestFactory)
 }
 
 // Test option struct.
diff --git a/python/testing.go b/python/testing.go
new file mode 100644
index 0000000..ce1a5ab
--- /dev/null
+++ b/python/testing.go
@@ -0,0 +1,24 @@
+// Copyright 2021 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 python
+
+import "android/soong/android"
+
+var PrepareForTestWithPythonBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerPythonBinaryComponents),
+	android.FixtureRegisterWithContext(registerPythonLibraryComponents),
+	android.FixtureRegisterWithContext(registerPythonTestComponents),
+	android.FixtureRegisterWithContext(registerPythonMutators),
+)
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 56d660e..db69e23 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -29,7 +29,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r399163b"
+	bindgenClangVersion = "clang-r412851"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
diff --git a/rust/builder.go b/rust/builder.go
index 6326124..9d462d4 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -194,8 +194,7 @@
 	}
 
 	if len(deps.SrcDeps) > 0 {
-		genSubDir := "out/"
-		moduleGenDir := android.PathForModuleOut(ctx, genSubDir)
+		moduleGenDir := ctx.RustModule().compiler.CargoOutDir()
 		var outputs android.WritablePaths
 
 		for _, genSrc := range deps.SrcDeps {
@@ -208,7 +207,7 @@
 
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        cp,
-			Description: "cp " + moduleGenDir.Rel(),
+			Description: "cp " + moduleGenDir.Path().Rel(),
 			Outputs:     outputs,
 			Inputs:      deps.SrcDeps,
 			Args: map[string]string{
diff --git a/rust/compiler.go b/rust/compiler.go
index c26f208..98ad7ad 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -60,6 +60,7 @@
 	InstallInData                   = iota
 
 	incorrectSourcesError = "srcs can only contain one path for a rust file and source providers prefixed by \":\""
+	genSubDir             = "out/"
 )
 
 type BaseCompilerProperties struct {
@@ -154,6 +155,10 @@
 	distFile android.OptionalPath
 	// Stripped output file. If Valid(), this file will be installed instead of outputFile.
 	strippedOutputFile android.OptionalPath
+
+	// If a crate has a source-generated dependency, a copy of the source file
+	// will be available in cargoOutDir (equivalent to Cargo OUT_DIR).
+	cargoOutDir android.ModuleOutPath
 }
 
 func (compiler *baseCompiler) Disabled() bool {
@@ -243,6 +248,14 @@
 	panic(fmt.Errorf("baseCrater doesn't know how to crate things!"))
 }
 
+func (compiler *baseCompiler) initialize(ctx ModuleContext) {
+	compiler.cargoOutDir = android.PathForModuleOut(ctx, genSubDir)
+}
+
+func (compiler *baseCompiler) CargoOutDir() android.OptionalPath {
+	return android.OptionalPathForPath(compiler.cargoOutDir)
+}
+
 func (compiler *baseCompiler) isDependencyRoot() bool {
 	return false
 }
diff --git a/rust/project_json.go b/rust/project_json.go
index 32ce6f4..8d3d250 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -45,11 +45,12 @@
 }
 
 type rustProjectCrate struct {
-	DisplayName string           `json:"display_name"`
-	RootModule  string           `json:"root_module"`
-	Edition     string           `json:"edition,omitempty"`
-	Deps        []rustProjectDep `json:"deps"`
-	Cfgs        []string         `json:"cfgs"`
+	DisplayName string            `json:"display_name"`
+	RootModule  string            `json:"root_module"`
+	Edition     string            `json:"edition,omitempty"`
+	Deps        []rustProjectDep  `json:"deps"`
+	Cfgs        []string          `json:"cfgs"`
+	Env         map[string]string `json:"env"`
 }
 
 type rustProjectJson struct {
@@ -136,7 +137,7 @@
 		}
 	})
 	if !foundSource {
-		fmt.Errorf("No valid source for source provider found: %v\n", rModule)
+		ctx.Errorf("No valid source for source provider found: %v\n", rModule)
 	}
 	return sourceSrc, foundSource
 }
@@ -220,7 +221,7 @@
 func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module, comp *baseCompiler) (int, bool) {
 	rootModule, ok := crateSource(ctx, rModule, comp)
 	if !ok {
-		fmt.Errorf("Unable to find source for valid module: %v", rModule)
+		ctx.Errorf("Unable to find source for valid module: %v", rModule)
 		return 0, false
 	}
 
@@ -230,6 +231,11 @@
 		Edition:     comp.edition(),
 		Deps:        make([]rustProjectDep, 0),
 		Cfgs:        make([]string, 0),
+		Env:         make(map[string]string),
+	}
+
+	if comp.CargoOutDir().Valid() {
+		crate.Env["OUT_DIR"] = comp.CargoOutDir().String()
 	}
 
 	deps := make(map[string]int)
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index ba66215..289bcb8 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -190,8 +190,8 @@
 				}
 			}
 		}
-		// Check that liba depends on libbindings1
 		if strings.Contains(rootModule, "d/src/lib.rs") {
+			// Check that libd depends on libbindings1
 			found := false
 			for _, depName := range validateDependencies(t, crate) {
 				if depName == "bindings1" {
@@ -200,8 +200,17 @@
 				}
 			}
 			if !found {
-				t.Errorf("liba does not depend on libbindings1: %v", crate)
+				t.Errorf("libd does not depend on libbindings1: %v", crate)
 			}
+			// Check that OUT_DIR is populated.
+			env, ok := crate["env"].(map[string]interface{})
+			if !ok {
+				t.Errorf("libd does not have its environment variables set: %v", crate)
+			}
+			if _, ok = env["OUT_DIR"]; !ok {
+				t.Errorf("libd does not have its OUT_DIR set: %v", env)
+			}
+
 		}
 	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index dc23abb..8ebdb72 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -320,12 +320,16 @@
 }
 
 type compiler interface {
+	initialize(ctx ModuleContext)
 	compilerFlags(ctx ModuleContext, flags Flags) Flags
 	compilerProps() []interface{}
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path
 	compilerDeps(ctx DepsContext, deps Deps) Deps
 	crateName() string
 
+	// Output directory in which source-generated code from dependencies is
+	// copied. This is equivalent to Cargo's OUT_DIR variable.
+	CargoOutDir() android.OptionalPath
 	inData() bool
 	install(ctx ModuleContext)
 	relativeInstallPath() string
@@ -711,6 +715,7 @@
 	}
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
+		mod.compiler.initialize(ctx)
 		outputFile := mod.compiler.compile(ctx, flags, deps)
 
 		mod.outputFile = android.OptionalPathForPath(outputFile)
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 67460ba..2f44b20 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -46,7 +46,6 @@
 	"-C llvm-args=-sanitizer-coverage-trace-geps",
 	"-C llvm-args=-sanitizer-coverage-prune-blocks=0",
 	"-C llvm-args=-sanitizer-coverage-pc-table",
-	"-C link-dead-code=y",
 	"-Z sanitizer=address",
 
 	// Sancov breaks with lto
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 0eb1b76..973a675 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -19,6 +19,8 @@
 from __future__ import print_function
 
 import argparse
+import re
+import subprocess
 import sys
 from xml.dom import minidom
 
@@ -59,64 +61,44 @@
                       dest='extract_target_sdk_version',
                       action='store_true',
                       help='print the targetSdkVersion from the manifest')
+  parser.add_argument('--aapt',
+                      dest='aapt',
+                      help='path to aapt executable')
   parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file')
   parser.add_argument('input', help='input AndroidManifest.xml file')
   return parser.parse_args()
 
 
-def enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries, relax):
-  """Verify that the <uses-library> tags in the manifest match those provided by the build system.
+def enforce_uses_libraries(manifest, required, optional, relax, is_apk = False):
+  """Verify that the <uses-library> tags in the manifest match those provided
+  by the build system.
 
   Args:
-    doc: The XML document.
-    uses_libraries: The names of <uses-library> tags known to the build system
-    optional_uses_libraries: The names of <uses-library> tags with required:fals
-      known to the build system
-  Raises:
-    RuntimeError: Invalid manifest
-    ManifestMismatchError: Manifest does not match
+    manifest: manifest (either parsed XML or aapt dump of APK)
+    required: required libs known to the build system
+    optional: optional libs known to the build system
+    relax:    if true, suppress error on mismatch and just write it to file
+    is_apk:   if the manifest comes from an APK or an XML file
   """
+  if is_apk:
+    manifest_required, manifest_optional = extract_uses_libs_apk(manifest)
+  else:
+    manifest_required, manifest_optional = extract_uses_libs_xml(manifest)
 
-  manifest = parse_manifest(doc)
-  elems = get_children_with_tag(manifest, 'application')
-  application = elems[0] if len(elems) == 1 else None
-  if len(elems) > 1:
-    raise RuntimeError('found multiple <application> tags')
-  elif not elems:
-    if uses_libraries or optional_uses_libraries:
-      raise ManifestMismatchError('no <application> tag found')
-    return
+  if required is None:
+    required = []
 
-  return verify_uses_library(application, uses_libraries, optional_uses_libraries, relax)
-
-
-def verify_uses_library(application, uses_libraries, optional_uses_libraries, relax):
-  """Verify that the uses-library values known to the build system match the manifest.
-
-  Args:
-    application: the <application> tag in the manifest.
-    uses_libraries: the names of expected <uses-library> tags.
-    optional_uses_libraries: the names of expected <uses-library> tags with required="false".
-  Raises:
-    ManifestMismatchError: Manifest does not match
-  """
-
-  if uses_libraries is None:
-    uses_libraries = []
-
-  if optional_uses_libraries is None:
-    optional_uses_libraries = []
-
-  manifest_uses_libraries, manifest_optional_uses_libraries = parse_uses_library(application)
+  if optional is None:
+    optional = []
 
   err = []
-  if manifest_uses_libraries != uses_libraries:
+  if manifest_required != required:
     err.append('Expected required <uses-library> tags "%s", got "%s"' %
-               (', '.join(uses_libraries), ', '.join(manifest_uses_libraries)))
+               (', '.join(required), ', '.join(manifest_required)))
 
-  if manifest_optional_uses_libraries != optional_uses_libraries:
+  if manifest_optional != optional:
     err.append('Expected optional <uses-library> tags "%s", got "%s"' %
-               (', '.join(optional_uses_libraries), ', '.join(manifest_optional_uses_libraries)))
+               (', '.join(optional), ', '.join(manifest_optional)))
 
   if err:
     errmsg = '\n'.join(err)
@@ -126,19 +108,43 @@
 
   return None
 
-def parse_uses_library(application):
-  """Extract uses-library tags from the manifest.
 
-  Args:
-    application: the <application> tag in the manifest.
-  """
+def extract_uses_libs_apk(badging):
+  """Extract <uses-library> tags from the manifest of an APK."""
+
+  pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
+
+  required = []
+  optional = []
+  for match in re.finditer(pattern, badging):
+    libname = match.group(2)
+    if match.group(1) == None:
+      required.append(libname)
+    else:
+      optional.append(libname)
+
+  return first_unique_elements(required), first_unique_elements(optional)
+
+
+def extract_uses_libs_xml(xml):
+  """Extract <uses-library> tags from the manifest."""
+
+  manifest = parse_manifest(xml)
+  elems = get_children_with_tag(manifest, 'application')
+  application = elems[0] if len(elems) == 1 else None
+  if len(elems) > 1:
+    raise RuntimeError('found multiple <application> tags')
+  elif not elems:
+    if uses_libraries or optional_uses_libraries:
+      raise ManifestMismatchError('no <application> tag found')
+    return
 
   libs = get_children_with_tag(application, 'uses-library')
 
-  uses_libraries = [uses_library_name(x) for x in libs if uses_library_required(x)]
-  optional_uses_libraries = [uses_library_name(x) for x in libs if not uses_library_required(x)]
+  required = [uses_library_name(x) for x in libs if uses_library_required(x)]
+  optional = [uses_library_name(x) for x in libs if not uses_library_required(x)]
 
-  return first_unique_elements(uses_libraries), first_unique_elements(optional_uses_libraries)
+  return first_unique_elements(required), first_unique_elements(optional)
 
 
 def first_unique_elements(l):
@@ -167,16 +173,34 @@
   return (required.value == 'true') if required is not None else True
 
 
-def extract_target_sdk_version(doc):
+def extract_target_sdk_version(manifest, is_apk = False):
   """Returns the targetSdkVersion from the manifest.
 
   Args:
-    doc: The XML document.
-  Raises:
-    RuntimeError: invalid manifest
+    manifest: manifest (either parsed XML or aapt dump of APK)
+    is_apk:   if the manifest comes from an APK or an XML file
   """
+  if is_apk:
+    return extract_target_sdk_version_apk(manifest)
+  else:
+    return extract_target_sdk_version_xml(manifest)
 
-  manifest = parse_manifest(doc)
+
+def extract_target_sdk_version_apk(badging):
+  """Extract targetSdkVersion tags from the manifest of an APK."""
+
+  pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
+
+  for match in re.finditer(pattern, badging):
+    return match.group(1)
+
+  raise RuntimeError('cannot find targetSdkVersion in the manifest')
+
+
+def extract_target_sdk_version_xml(xml):
+  """Extract targetSdkVersion tags from the manifest."""
+
+  manifest = parse_manifest(xml)
 
   # Get or insert the uses-sdk element
   uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
@@ -203,14 +227,22 @@
   try:
     args = parse_args()
 
-    doc = minidom.parse(args.input)
+    # The input can be either an XML manifest or an APK, they are parsed and
+    # processed in different ways.
+    is_apk = args.input.endswith('.apk')
+    if is_apk:
+      aapt = args.aapt if args.aapt != None else "aapt"
+      manifest = subprocess.check_output([aapt, "dump", "badging", args.input])
+    else:
+      manifest = minidom.parse(args.input)
 
     if args.enforce_uses_libraries:
       # Check if the <uses-library> lists in the build system agree with those
       # in the manifest. Raise an exception on mismatch, unless the script was
       # passed a special parameter to suppress exceptions.
-      errmsg = enforce_uses_libraries(doc, args.uses_libraries,
-        args.optional_uses_libraries, args.enforce_uses_libraries_relax)
+      errmsg = enforce_uses_libraries(manifest, args.uses_libraries,
+        args.optional_uses_libraries, args.enforce_uses_libraries_relax,
+        is_apk)
 
       # Create a status file that is empty on success, or contains an error
       # message on failure. When exceptions are suppressed, dexpreopt command
@@ -221,11 +253,16 @@
             f.write("%s\n" % errmsg)
 
     if args.extract_target_sdk_version:
-      print(extract_target_sdk_version(doc))
+      print(extract_target_sdk_version(manifest, is_apk))
 
     if args.output:
+      # XML output is supposed to be written only when this script is invoked
+      # with XML input manifest, not with an APK.
+      if is_apk:
+        raise RuntimeError('cannot save APK manifest as XML')
+
       with open(args.output, 'wb') as f:
-        write_xml(f, doc)
+        write_xml(f, manifest)
 
   # pylint: disable=broad-except
   except Exception as err:
diff --git a/scripts/manifest_check_test.py b/scripts/manifest_check_test.py
index 56c2d9e..635ba9d 100755
--- a/scripts/manifest_check_test.py
+++ b/scripts/manifest_check_test.py
@@ -25,28 +25,38 @@
 sys.dont_write_bytecode = True
 
 
-def uses_library(name, attr=''):
+def uses_library_xml(name, attr=''):
   return '<uses-library android:name="%s"%s />' % (name, attr)
 
 
-def required(value):
+def required_xml(value):
   return ' android:required="%s"' % ('true' if value else 'false')
 
 
+def uses_library_apk(name, sfx=''):
+  return "uses-library%s:'%s'" % (sfx, name)
+
+
+def required_apk(value):
+  return '' if value else '-not-required'
+
+
 class EnforceUsesLibrariesTest(unittest.TestCase):
   """Unit tests for add_extract_native_libs function."""
 
-  def run_test(self, input_manifest, uses_libraries=None, optional_uses_libraries=None):
-    doc = minidom.parseString(input_manifest)
+  def run_test(self, xml, apk, uses_libraries=[], optional_uses_libraries=[]):
+    doc = minidom.parseString(xml)
     try:
       relax = False
       manifest_check.enforce_uses_libraries(doc, uses_libraries,
-        optional_uses_libraries, relax)
+        optional_uses_libraries, relax, is_apk=False)
+      manifest_check.enforce_uses_libraries(apk, uses_libraries,
+        optional_uses_libraries, relax, is_apk=True)
       return True
     except manifest_check.ManifestMismatchError:
       return False
 
-  manifest_tmpl = (
+  xml_tmpl = (
       '<?xml version="1.0" encoding="utf-8"?>\n'
       '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
       '    <application>\n'
@@ -54,115 +64,155 @@
       '    </application>\n'
       '</manifest>\n')
 
+  apk_tmpl = (
+      "package: name='com.google.android.something' versionCode='100'\n"
+      "sdkVersion:'29'\n"
+      "targetSdkVersion:'29'\n"
+      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n"
+      "%s\n"
+      "densities: '160' '240' '320' '480' '640' '65534")
+
   def test_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo'))
-    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    xml = self.xml_tmpl % (uses_library_xml('foo'))
+    apk = self.apk_tmpl % (uses_library_apk('foo'))
+    matches = self.run_test(xml, apk, uses_libraries=['foo'])
     self.assertTrue(matches)
 
   def test_uses_library_required(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo', required(True)))
-    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(True)))
+    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(True)))
+    matches = self.run_test(xml, apk, uses_libraries=['foo'])
     self.assertTrue(matches)
 
   def test_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
-    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
     self.assertTrue(matches)
 
   def test_expected_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
-    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+    matches = self.run_test(xml, apk, uses_libraries=['foo'])
     self.assertFalse(matches)
 
   def test_expected_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo'))
-    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    xml = self.xml_tmpl % (uses_library_xml('foo'))
+    apk = self.apk_tmpl % (uses_library_apk('foo'))
+    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
     self.assertFalse(matches)
 
   def test_missing_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('')
-    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    xml = self.xml_tmpl % ('')
+    apk = self.apk_tmpl % ('')
+    matches = self.run_test(xml, apk, uses_libraries=['foo'])
     self.assertFalse(matches)
 
   def test_missing_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('')
-    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    xml = self.xml_tmpl % ('')
+    apk = self.apk_tmpl % ('')
+    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
     self.assertFalse(matches)
 
   def test_extra_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo'))
-    matches = self.run_test(manifest_input)
+    xml = self.xml_tmpl % (uses_library_xml('foo'))
+    apk = self.apk_tmpl % (uses_library_xml('foo'))
+    matches = self.run_test(xml, apk)
     self.assertFalse(matches)
 
   def test_extra_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
-    matches = self.run_test(manifest_input)
+    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+    matches = self.run_test(xml, apk)
     self.assertFalse(matches)
 
   def test_multiple_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
-                                                      uses_library('bar')]))
-    matches = self.run_test(manifest_input, uses_libraries=['foo', 'bar'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
+                                      uses_library_xml('bar')]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
+                                      uses_library_apk('bar')]))
+    matches = self.run_test(xml, apk, uses_libraries=['foo', 'bar'])
     self.assertTrue(matches)
 
   def test_multiple_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
-                                                      uses_library('bar', required(False))]))
-    matches = self.run_test(manifest_input, optional_uses_libraries=['foo', 'bar'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
+                                      uses_library_xml('bar', required_xml(False))]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
+                                      uses_library_apk('bar', required_apk(False))]))
+    matches = self.run_test(xml, apk, optional_uses_libraries=['foo', 'bar'])
     self.assertTrue(matches)
 
   def test_order_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
-                                                      uses_library('bar')]))
-    matches = self.run_test(manifest_input, uses_libraries=['bar', 'foo'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
+                                      uses_library_xml('bar')]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
+                                      uses_library_apk('bar')]))
+    matches = self.run_test(xml, apk, uses_libraries=['bar', 'foo'])
     self.assertFalse(matches)
 
   def test_order_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
-                                                      uses_library('bar', required(False))]))
-    matches = self.run_test(manifest_input, optional_uses_libraries=['bar', 'foo'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
+                                      uses_library_xml('bar', required_xml(False))]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
+                                      uses_library_apk('bar', required_apk(False))]))
+    matches = self.run_test(xml, apk, optional_uses_libraries=['bar', 'foo'])
     self.assertFalse(matches)
 
   def test_duplicate_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
-                                                      uses_library('foo')]))
-    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
+                                      uses_library_xml('foo')]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
+                                      uses_library_apk('foo')]))
+    matches = self.run_test(xml, apk, uses_libraries=['foo'])
     self.assertTrue(matches)
 
   def test_duplicate_optional_uses_library(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
-                                                      uses_library('foo', required(False))]))
-    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
+                                      uses_library_xml('foo', required_xml(False))]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
+                                      uses_library_apk('foo', required_apk(False))]))
+    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
     self.assertTrue(matches)
 
   def test_mixed(self):
-    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
-                                                      uses_library('bar', required(False))]))
-    matches = self.run_test(manifest_input, uses_libraries=['foo'],
+    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
+                                      uses_library_xml('bar', required_xml(False))]))
+    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
+                                      uses_library_apk('bar', required_apk(False))]))
+    matches = self.run_test(xml, apk, uses_libraries=['foo'],
                             optional_uses_libraries=['bar'])
     self.assertTrue(matches)
 
 
 class ExtractTargetSdkVersionTest(unittest.TestCase):
-  def test_target_sdk_version(self):
-    manifest = (
-      '<?xml version="1.0" encoding="utf-8"?>\n'
-      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" />\n'
-      '</manifest>\n')
-    doc = minidom.parseString(manifest)
-    target_sdk_version = manifest_check.extract_target_sdk_version(doc)
-    self.assertEqual(target_sdk_version, '29')
+  def run_test(self, xml, apk, version):
+    doc = minidom.parseString(xml)
+    v = manifest_check.extract_target_sdk_version(doc, is_apk=False)
+    self.assertEqual(v, version)
+    v = manifest_check.extract_target_sdk_version(apk, is_apk=True)
+    self.assertEqual(v, version)
 
-  def test_min_sdk_version(self):
-    manifest = (
+  xml_tmpl = (
       '<?xml version="1.0" encoding="utf-8"?>\n'
       '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <uses-sdk android:minSdkVersion="28" />\n'
+      '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="%s" />\n'
       '</manifest>\n')
-    doc = minidom.parseString(manifest)
-    target_sdk_version = manifest_check.extract_target_sdk_version(doc)
-    self.assertEqual(target_sdk_version, '28')
+
+  apk_tmpl = (
+      "package: name='com.google.android.something' versionCode='100'\n"
+      "sdkVersion:'28'\n"
+      "targetSdkVersion:'%s'\n"
+      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n")
+
+  def test_targert_sdk_version_28(self):
+    xml = self.xml_tmpl % "28"
+    apk = self.apk_tmpl % "28"
+    self.run_test(xml, apk, "28")
+
+  def test_targert_sdk_version_29(self):
+    xml = self.xml_tmpl % "29"
+    apk = self.apk_tmpl % "29"
+    self.run_test(xml, apk, "29")
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/sdk/sdk.go b/sdk/sdk.go
index 2c84a2e..6ca8512 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -358,15 +358,36 @@
 
 // For dependencies from an in-development version of an SDK member to frozen versions of the same member
 // e.g. libfoo -> libfoo.mysdk.11 and libfoo.mysdk.12
+//
+// The dependency represented by this tag requires that for every APEX variant created for the
+// `from` module that an equivalent APEX variant is created for the 'to' module. This is because an
+// APEX that requires a specific version of an sdk (via the `uses_sdks` property will replace
+// dependencies on the unversioned sdk member with a dependency on the appropriate versioned sdk
+// member. In order for that to work the versioned sdk member needs to have a variant for that APEX.
+// As it is not known at the time that the APEX variants are created which specific APEX variants of
+// a versioned sdk members will be required it is necessary for the versioned sdk members to have
+// variants for any APEX that it could be used within.
+//
+// If the APEX selects a versioned sdk member then it will not have a dependency on the `from`
+// module at all so any dependencies of that module will not affect the APEX. However, if the APEX
+// selects the unversioned sdk member then it must exclude all the versioned sdk members. In no
+// situation would this dependency cause the `to` module to be added to the APEX hence why this tag
+// also excludes the `to` module from being added to the APEX contents.
 type sdkMemberVersionedDepTag struct {
 	dependencyTag
 	member  string
 	version string
 }
 
+func (t sdkMemberVersionedDepTag) AlwaysRequireApexVariant() bool {
+	return true
+}
+
 // Mark this tag so dependencies that use it are excluded from visibility enforcement.
 func (t sdkMemberVersionedDepTag) ExcludeFromVisibilityEnforcement() {}
 
+var _ android.AlwaysRequireApexVariantTag = sdkMemberVersionedDepTag{}
+
 // Step 1: create dependencies from an SDK module to its members.
 func memberMutator(mctx android.BottomUpMutatorContext) {
 	if s, ok := mctx.Module().(*sdk); ok {
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 05d8bdb..b7da95c 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -31,7 +31,7 @@
 		os.Exit(0)
 	}
 
-	runTestWithBuildDir(m)
+	os.Exit(m.Run())
 }
 
 func TestDepNotInRequiredSdks(t *testing.T) {
diff --git a/sdk/testing.go b/sdk/testing.go
index a5519f8..6df402c 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -16,8 +16,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
-	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -30,7 +28,7 @@
 )
 
 var sdkFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+	nil,
 	apex.PrepareForTestWithApexBuildComponents,
 	cc.PrepareForTestWithCcDefaultModules,
 	genrule.PrepareForTestWithGenRuleBuildComponents,
@@ -326,28 +324,3 @@
 	// The final output zip.
 	outputZip string
 }
-
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_sdk_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	_ = os.RemoveAll(buildDir)
-}
-
-func runTestWithBuildDir(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
-}
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 7a24168..5887b56 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -1,44 +1,20 @@
 package sh
 
 import (
-	"io/ioutil"
 	"os"
-	"path"
 	"path/filepath"
-	"reflect"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/cc"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_sh_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
 var shFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+	nil,
 	cc.PrepareForTestWithCcBuildComponents,
 	PrepareForTestWithShBuildComponents,
 	android.FixtureMergeMockFs(android.MockFS{
@@ -66,7 +42,7 @@
 }
 
 func TestShTestSubDir(t *testing.T) {
-	ctx, _ := testShBinary(t, `
+	ctx, config := testShBinary(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -78,16 +54,13 @@
 
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 
-	expectedPath := path.Join(buildDir,
-		"../target/product/test_device/data/nativetest64/foo_test")
+	expectedPath := "out/target/product/test_device/data/nativetest64/foo_test"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	if expectedPath != actualPath {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH expected: %q, actual: %q", expectedPath, actualPath)
-	}
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
 }
 
 func TestShTest(t *testing.T) {
-	ctx, _ := testShBinary(t, `
+	ctx, config := testShBinary(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -103,22 +76,17 @@
 
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 
-	expectedPath := path.Join(buildDir,
-		"../target/product/test_device/data/nativetest64/foo")
+	expectedPath := "out/target/product/test_device/data/nativetest64/foo"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	if expectedPath != actualPath {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH expected: %q, actual: %q", expectedPath, actualPath)
-	}
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
 
 	expectedData := []string{":testdata/data1", ":testdata/sub/data2"}
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
-	if !reflect.DeepEqual(expectedData, actualData) {
-		t.Errorf("Unexpected test data expected: %q, actual: %q", expectedData, actualData)
-	}
+	android.AssertDeepEquals(t, "LOCAL_TEST_DATA", expectedData, actualData)
 }
 
 func TestShTest_dataModules(t *testing.T) {
-	ctx, _ := testShBinary(t, `
+	ctx, config := testShBinary(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -157,22 +125,17 @@
 			libExt = ".dylib"
 		}
 		relocated := variant.Output("relocated/lib64/libbar" + libExt)
-		expectedInput := filepath.Join(buildDir, ".intermediates/libbar/"+arch+"_shared/libbar"+libExt)
-		if relocated.Input.String() != expectedInput {
-			t.Errorf("Unexpected relocation input, expected: %q, actual: %q",
-				expectedInput, relocated.Input.String())
-		}
+		expectedInput := "out/soong/.intermediates/libbar/" + arch + "_shared/libbar" + libExt
+		android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input)
 
 		mod := variant.Module().(*ShTest)
 		entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 		expectedData := []string{
-			filepath.Join(buildDir, ".intermediates/bar", arch, ":bar"),
-			filepath.Join(buildDir, ".intermediates/foo", arch, "relocated/:lib64/libbar"+libExt),
+			filepath.Join("out/soong/.intermediates/bar", arch, ":bar"),
+			filepath.Join("out/soong/.intermediates/foo", arch, "relocated/:lib64/libbar"+libExt),
 		}
 		actualData := entries.EntryMap["LOCAL_TEST_DATA"]
-		if !reflect.DeepEqual(expectedData, actualData) {
-			t.Errorf("Unexpected test data, expected: %q, actual: %q", expectedData, actualData)
-		}
+		android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
 	}
 }
 
@@ -197,7 +160,7 @@
 }
 
 func TestShTestHost_dataDeviceModules(t *testing.T) {
-	ctx, _ := testShBinary(t, `
+	ctx, config := testShBinary(t, `
 		sh_test_host {
 			name: "foo",
 			src: "test.sh",
@@ -227,21 +190,16 @@
 	variant := ctx.ModuleForTests("foo", buildOS+"_x86_64")
 
 	relocated := variant.Output("relocated/lib64/libbar.so")
-	expectedInput := filepath.Join(buildDir, ".intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
-	if relocated.Input.String() != expectedInput {
-		t.Errorf("Unexpected relocation input, expected: %q, actual: %q",
-			expectedInput, relocated.Input.String())
-	}
+	expectedInput := "out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so"
+	android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input)
 
 	mod := variant.Module().(*ShTest)
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 	expectedData := []string{
-		filepath.Join(buildDir, ".intermediates/bar/android_arm64_armv8-a/:bar"),
+		"out/soong/.intermediates/bar/android_arm64_armv8-a/:bar",
 		// libbar has been relocated, and so has a variant that matches the host arch.
-		filepath.Join(buildDir, ".intermediates/foo/"+buildOS+"_x86_64/relocated/:lib64/libbar.so"),
+		"out/soong/.intermediates/foo/" + buildOS + "_x86_64/relocated/:lib64/libbar.so",
 	}
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
-	if !reflect.DeepEqual(expectedData, actualData) {
-		t.Errorf("Unexpected test data, expected: %q, actual: %q", expectedData, actualData)
-	}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
 }
diff --git a/sysprop/Android.bp b/sysprop/Android.bp
index 540a8da..1d5eb31 100644
--- a/sysprop/Android.bp
+++ b/sysprop/Android.bp
@@ -14,6 +14,7 @@
     ],
     srcs: [
         "sysprop_library.go",
+        "testing.go",
     ],
     testSrcs: [
         "sysprop_test.go",
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 892a16c..f1c2d0d 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -193,7 +193,11 @@
 }
 
 func init() {
-	android.RegisterModuleType("sysprop_library", syspropLibraryFactory)
+	registerSyspropBuildComponents(android.InitRegistrationContext)
+}
+
+func registerSyspropBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("sysprop_library", syspropLibraryFactory)
 }
 
 func (m *syspropLibrary) Name() string {
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index fde41d6..cb1e362 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -15,72 +15,26 @@
 package sysprop
 
 import (
-	"reflect"
+	"os"
+	"strings"
+	"testing"
 
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/java"
 
-	"io/ioutil"
-	"os"
-	"strings"
-	"testing"
-
 	"github.com/google/blueprint/proptools"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_sysprop_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
-func testContext(config android.Config) *android.TestContext {
+var emptyFixtureFactory = android.NewFixtureFactory(nil)
 
-	ctx := android.NewTestArchContext(config)
-	java.RegisterRequiredBuildComponentsForTest(ctx)
-
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-
-	android.RegisterPrebuiltMutators(ctx)
-
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-
-	ctx.RegisterModuleType("sysprop_library", syspropLibraryFactory)
-
-	ctx.Register()
-
-	return ctx
-}
-
-func run(t *testing.T, ctx *android.TestContext, config android.Config) {
+func test(t *testing.T, bp string) *android.TestResult {
 	t.Helper()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-}
 
-func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
 	bp += `
 		cc_library {
 			name: "libbase",
@@ -126,9 +80,7 @@
 		}
 	`
 
-	bp += cc.GatherRequiredDepsForTest(android.Android)
-
-	mockFS := map[string][]byte{
+	mockFS := android.MockFS{
 		"a.java":                           nil,
 		"b.java":                           nil,
 		"c.java":                           nil,
@@ -172,31 +124,24 @@
 		"com/android2/OdmProperties.sysprop":         nil,
 	}
 
-	for k, v := range fs {
-		mockFS[k] = v
-	}
+	result := emptyFixtureFactory.RunTest(t,
+		cc.PrepareForTestWithCcDefaultModules,
+		java.PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithSyspropBuildComponents,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.DeviceSystemSdkVersions = []string{"28"}
+			variables.DeviceVndkVersion = proptools.StringPtr("current")
+			variables.Platform_vndk_version = proptools.StringPtr("VER")
+		}),
+		mockFS.AddToFixture(),
+		android.FixtureWithRootAndroidBp(bp),
+	)
 
-	config := java.TestConfig(buildDir, env, bp, mockFS)
-
-	config.TestProductVariables.DeviceSystemSdkVersions = []string{"28"}
-	config.TestProductVariables.DeviceVndkVersion = proptools.StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = proptools.StringPtr("VER")
-
-	return config
-
-}
-
-func test(t *testing.T, bp string) *android.TestContext {
-	t.Helper()
-	config := testConfig(nil, bp, nil)
-	ctx := testContext(config)
-	run(t, ctx, config)
-
-	return ctx
+	return result
 }
 
 func TestSyspropLibrary(t *testing.T) {
-	ctx := test(t, `
+	result := test(t, `
 		sysprop_library {
 			name: "sysprop-platform",
 			apex_available: ["//apex_available:platform"],
@@ -308,9 +253,9 @@
 		"android_vendor.VER_arm64_armv8-a_shared",
 		"android_vendor.VER_arm64_armv8-a_static",
 	} {
-		ctx.ModuleForTests("libsysprop-platform", variant)
-		ctx.ModuleForTests("libsysprop-vendor", variant)
-		ctx.ModuleForTests("libsysprop-odm", variant)
+		result.ModuleForTests("libsysprop-platform", variant)
+		result.ModuleForTests("libsysprop-vendor", variant)
+		result.ModuleForTests("libsysprop-odm", variant)
 	}
 
 	for _, variant := range []string{
@@ -319,21 +264,18 @@
 		"android_arm64_armv8-a_shared",
 		"android_arm64_armv8-a_static",
 	} {
-		library := ctx.ModuleForTests("libsysprop-platform", variant).Module().(*cc.Module)
+		library := result.ModuleForTests("libsysprop-platform", variant).Module().(*cc.Module)
 		expectedApexAvailableOnLibrary := []string{"//apex_available:platform"}
-		if !reflect.DeepEqual(library.ApexProperties.Apex_available, expectedApexAvailableOnLibrary) {
-			t.Errorf("apex available property on libsysprop-platform must be %#v, but was %#v.",
-				expectedApexAvailableOnLibrary, library.ApexProperties.Apex_available)
-		}
+		android.AssertDeepEquals(t, "apex available property on libsysprop-platform", expectedApexAvailableOnLibrary, library.ApexProperties.Apex_available)
 
 		// product variant of vendor-owned sysprop_library
-		ctx.ModuleForTests("libsysprop-vendor-on-product", variant)
+		result.ModuleForTests("libsysprop-vendor-on-product", variant)
 	}
 
-	ctx.ModuleForTests("sysprop-platform", "android_common")
-	ctx.ModuleForTests("sysprop-platform_public", "android_common")
-	ctx.ModuleForTests("sysprop-vendor", "android_common")
-	ctx.ModuleForTests("sysprop-vendor-on-product", "android_common")
+	result.ModuleForTests("sysprop-platform", "android_common")
+	result.ModuleForTests("sysprop-platform_public", "android_common")
+	result.ModuleForTests("sysprop-vendor", "android_common")
+	result.ModuleForTests("sysprop-vendor-on-product", "android_common")
 
 	// Check for exported includes
 	coreVariant := "android_arm64_armv8-a_static"
@@ -348,25 +290,19 @@
 	vendorInternalPath := "libsysprop-vendor/android_vendor.VER_arm64_armv8-a_static/gen/sysprop/include"
 	vendorPublicPath := "libsysprop-vendor-on-product/android_arm64_armv8-a_static/gen/sysprop/public/include"
 
-	platformClient := ctx.ModuleForTests("cc-client-platform", coreVariant)
+	platformClient := result.ModuleForTests("cc-client-platform", coreVariant)
 	platformFlags := platformClient.Rule("cc").Args["cFlags"]
 
 	// platform should use platform's internal header
-	if !strings.Contains(platformFlags, platformInternalPath) {
-		t.Errorf("flags for platform must contain %#v, but was %#v.",
-			platformInternalPath, platformFlags)
-	}
+	android.AssertStringDoesContain(t, "flags for platform", platformFlags, platformInternalPath)
 
-	platformStaticClient := ctx.ModuleForTests("cc-client-platform-static", coreVariant)
+	platformStaticClient := result.ModuleForTests("cc-client-platform-static", coreVariant)
 	platformStaticFlags := platformStaticClient.Rule("cc").Args["cFlags"]
 
 	// platform-static should use platform's internal header
-	if !strings.Contains(platformStaticFlags, platformInternalPath) {
-		t.Errorf("flags for platform-static must contain %#v, but was %#v.",
-			platformInternalPath, platformStaticFlags)
-	}
+	android.AssertStringDoesContain(t, "flags for platform-static", platformStaticFlags, platformInternalPath)
 
-	productClient := ctx.ModuleForTests("cc-client-product", coreVariant)
+	productClient := result.ModuleForTests("cc-client-product", coreVariant)
 	productFlags := productClient.Rule("cc").Args["cFlags"]
 
 	// Product should use platform's and vendor's public headers
@@ -376,7 +312,7 @@
 			platformPublicCorePath, vendorPublicPath, productFlags)
 	}
 
-	vendorClient := ctx.ModuleForTests("cc-client-vendor", vendorVariant)
+	vendorClient := result.ModuleForTests("cc-client-vendor", vendorVariant)
 	vendorFlags := vendorClient.Rule("cc").Args["cFlags"]
 
 	// Vendor should use platform's public header and vendor's internal header
@@ -387,15 +323,15 @@
 	}
 
 	// Java modules linking against system API should use public stub
-	javaSystemApiClient := ctx.ModuleForTests("java-platform", "android_common").Rule("javac")
-	syspropPlatformPublic := ctx.ModuleForTests("sysprop-platform_public", "android_common").Description("for turbine")
+	javaSystemApiClient := result.ModuleForTests("java-platform", "android_common").Rule("javac")
+	syspropPlatformPublic := result.ModuleForTests("sysprop-platform_public", "android_common").Description("for turbine")
 	if g, w := javaSystemApiClient.Implicits.Strings(), syspropPlatformPublic.Output.String(); !android.InList(w, g) {
 		t.Errorf("system api client should use public stub %q, got %q", w, g)
 	}
 }
 
 func TestApexAvailabilityIsForwarded(t *testing.T) {
-	ctx := test(t, `
+	result := test(t, `
 		sysprop_library {
 			name: "sysprop-platform",
 			apex_available: ["//apex_available:platform"],
@@ -407,23 +343,17 @@
 
 	expected := []string{"//apex_available:platform"}
 
-	ccModule := ctx.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	ccModule := result.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	propFromCc := ccModule.ApexProperties.Apex_available
-	if !reflect.DeepEqual(propFromCc, expected) {
-		t.Errorf("apex_available not forwarded to cc module. expected %#v, got %#v",
-			expected, propFromCc)
-	}
+	android.AssertDeepEquals(t, "apex_available forwarding to cc module", expected, propFromCc)
 
-	javaModule := ctx.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
+	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.ApexProperties.Apex_available
-	if !reflect.DeepEqual(propFromJava, expected) {
-		t.Errorf("apex_available not forwarded to java module. expected %#v, got %#v",
-			expected, propFromJava)
-	}
+	android.AssertDeepEquals(t, "apex_available forwarding to java module", expected, propFromJava)
 }
 
 func TestMinSdkVersionIsForwarded(t *testing.T) {
-	ctx := test(t, `
+	result := test(t, `
 		sysprop_library {
 			name: "sysprop-platform",
 			srcs: ["android/sysprop/PlatformProperties.sysprop"],
@@ -438,17 +368,11 @@
 		}
 	`)
 
-	ccModule := ctx.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	ccModule := result.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	propFromCc := proptools.String(ccModule.Properties.Min_sdk_version)
-	if propFromCc != "29" {
-		t.Errorf("min_sdk_version not forwarded to cc module. expected %#v, got %#v",
-			"29", propFromCc)
-	}
+	android.AssertStringEquals(t, "min_sdk_version forwarding to cc module", "29", propFromCc)
 
-	javaModule := ctx.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
+	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.MinSdkVersion()
-	if propFromJava != "30" {
-		t.Errorf("min_sdk_version not forwarded to java module. expected %#v, got %#v",
-			"30", propFromJava)
-	}
+	android.AssertStringEquals(t, "min_sdk_version forwarding to java module", "30", propFromJava)
 }
diff --git a/sysprop/testing.go b/sysprop/testing.go
new file mode 100644
index 0000000..3e14be1
--- /dev/null
+++ b/sysprop/testing.go
@@ -0,0 +1,19 @@
+// Copyright (C) 2021 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 sysprop
+
+import "android/soong/android"
+
+var PrepareForTestWithSyspropBuildComponents = android.FixtureRegisterWithContext(registerSyspropBuildComponents)
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 32b6eda..d17b464 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -32,6 +32,8 @@
     name: "soong-ui-build",
     pkgPath: "android/soong/ui/build",
     deps: [
+        "blueprint",
+        "blueprint-bootstrap",
         "soong-ui-build-paths",
         "soong-ui-logger",
         "soong-ui-metrics",
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index 81ce939..ec561d5 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -116,6 +116,7 @@
 			"RBE_exec_strategy",
 			"RBE_invocation_id",
 			"RBE_log_dir",
+			"RBE_num_retries_if_mismatched",
 			"RBE_platform",
 			"RBE_remote_accept_cache",
 			"RBE_remote_update_cache",
diff --git a/ui/build/build.go b/ui/build/build.go
index 215a6c8..3692f4f 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -218,6 +218,11 @@
 		what = what &^ BuildKati
 	}
 
+	if config.SkipNinja() {
+		ctx.Verboseln("Skipping Ninja as requested")
+		what = what &^ BuildNinja
+	}
+
 	if config.StartGoma() {
 		// Ensure start Goma compiler_proxy
 		startGoma(ctx, config)
@@ -290,7 +295,7 @@
 		}
 
 		// Run ninja
-		runNinja(ctx, config)
+		runNinjaForBuild(ctx, config)
 	}
 
 	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
diff --git a/ui/build/config.go b/ui/build/config.go
index 1152cd7..4816d1f 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -48,6 +48,7 @@
 	dist           bool
 	skipConfig     bool
 	skipKati       bool
+	skipNinja      bool
 	skipSoongTests bool
 
 	// From the product config
@@ -552,6 +553,8 @@
 		if arg == "--make-mode" {
 		} else if arg == "showcommands" {
 			c.verbose = true
+		} else if arg == "--skip-ninja" {
+			c.skipNinja = true
 		} else if arg == "--skip-make" {
 			c.skipConfig = true
 			c.skipKati = true
@@ -772,6 +775,10 @@
 	return c.skipKati
 }
 
+func (c *configImpl) SkipNinja() bool {
+	return c.skipNinja
+}
+
 func (c *configImpl) SkipConfig() bool {
 	return c.skipConfig
 }
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 7799766..5961c45 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -30,7 +30,7 @@
 // Constructs and runs the Ninja command line with a restricted set of
 // environment variables. It's important to restrict the environment Ninja runs
 // for hermeticity reasons, and to avoid spurious rebuilds.
-func runNinja(ctx Context, config Config) {
+func runNinjaForBuild(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
 	defer ctx.EndTrace()
 
@@ -145,6 +145,7 @@
 			"RBE_exec_strategy",
 			"RBE_invocation_id",
 			"RBE_log_dir",
+			"RBE_num_retries_if_mismatched",
 			"RBE_platform",
 			"RBE_remote_accept_cache",
 			"RBE_remote_update_cache",
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 884e957..9afcb88 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -23,6 +23,8 @@
 	"android/soong/shared"
 
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/bootstrap"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/google/blueprint/microfactory"
@@ -42,17 +44,78 @@
 
 // This uses Android.bp files and various tools to generate <builddir>/build.ninja.
 //
-// However, the execution of <builddir>/build.ninja happens later in build/soong/ui/build/build.go#Build()
+// However, the execution of <builddir>/build.ninja happens later in
+// build/soong/ui/build/build.go#Build()
 //
-// We want to rely on as few prebuilts as possible, so there is some bootstrapping here.
+// We want to rely on as few prebuilts as possible, so we need to bootstrap
+// Soong. The process is as follows:
 //
-// "Microfactory" is a tool for compiling Go code. We use it to build two other tools:
-// - minibp, used to generate build.ninja files. This is really build/blueprint/bootstrap/command.go#Main()
-// - bpglob, used during incremental builds to identify files in a glob that have changed
+// 1. We use "Microfactory", a simple tool to compile Go code, to build
+//    first itself, then soong_ui from soong_ui.bash. This binary contains
+//    parts of soong_build that are needed to build itself.
+// 2. This simplified version of soong_build then reads the Blueprint files
+//    that describe itself and emits .bootstrap/build.ninja that describes
+//    how to build its full version and use that to produce the final Ninja
+//    file Soong emits.
+// 3. soong_ui executes .bootstrap/build.ninja
 //
-// In reality, several build.ninja files are generated and/or used during the bootstrapping and build process.
-// See build/blueprint/bootstrap/doc.go for more information.
-//
+// (After this, Kati is executed to parse the Makefiles, but that's not part of
+// bootstrapping Soong)
+
+// A tiny struct used to tell Blueprint that it's in bootstrap mode. It would
+// probably be nicer to use a flag in bootstrap.Args instead.
+type BlueprintConfig struct {
+	srcDir           string
+	buildDir         string
+	ninjaBuildDir    string
+	debugCompilation bool
+}
+
+func (c BlueprintConfig) SrcDir() string {
+	return "."
+}
+
+func (c BlueprintConfig) BuildDir() string {
+	return c.buildDir
+}
+
+func (c BlueprintConfig) NinjaBuildDir() string {
+	return c.ninjaBuildDir
+}
+
+func (c BlueprintConfig) DebugCompilation() bool {
+	return c.debugCompilation
+}
+
+func bootstrapBlueprint(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
+	defer ctx.EndTrace()
+
+	var args bootstrap.Args
+
+	args.RunGoTests = !config.skipSoongTests
+	args.UseValidations = true // Use validations to depend on tests
+	args.BuildDir = config.SoongOutDir()
+	args.NinjaBuildDir = config.OutDir()
+	args.TopFile = "Android.bp"
+	args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
+	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
+	args.DepFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
+	args.GlobFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/soong-build-globs.ninja")
+	args.GeneratingPrimaryBuilder = true
+
+	blueprintCtx := blueprint.NewContext()
+	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
+	blueprintConfig := BlueprintConfig{
+		srcDir:           os.Getenv("TOP"),
+		buildDir:         config.SoongOutDir(),
+		ninjaBuildDir:    config.OutDir(),
+		debugCompilation: os.Getenv("SOONG_DELVE") != "",
+	}
+
+	bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
+}
+
 func runSoong(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSoong, "soong")
 	defer ctx.EndTrace()
@@ -63,33 +126,15 @@
 	// unused variables were changed?
 	envFile := filepath.Join(config.SoongOutDir(), "soong.environment.available")
 
-	// Use an anonymous inline function for tracing purposes (this pattern is used several times below).
-	func() {
-		ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
-		defer ctx.EndTrace()
-
-		// Use validations to depend on tests.
-		args := []string{"-n"}
-
-		if !config.skipSoongTests {
-			// Run tests.
-			args = append(args, "-t")
+	for _, n := range []string{".bootstrap", ".minibootstrap"} {
+		dir := filepath.Join(config.SoongOutDir(), n)
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			ctx.Fatalf("Cannot mkdir " + dir)
 		}
+	}
 
-		cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", args...)
-
-		cmd.Environment.Set("BLUEPRINTDIR", "./build/blueprint")
-		cmd.Environment.Set("BOOTSTRAP", "./build/blueprint/bootstrap.bash")
-		cmd.Environment.Set("BUILDDIR", config.SoongOutDir())
-		cmd.Environment.Set("GOROOT", "./"+filepath.Join("prebuilts/go", config.HostPrebuiltTag()))
-		cmd.Environment.Set("BLUEPRINT_LIST_FILE", filepath.Join(config.FileListDir(), "Android.bp.list"))
-		cmd.Environment.Set("NINJA_BUILDDIR", config.OutDir())
-		cmd.Environment.Set("SRCDIR", ".")
-		cmd.Environment.Set("TOPNAME", "Android.bp")
-		cmd.Sandbox = soongSandbox
-
-		cmd.RunAndPrintOrFatal()
-	}()
+	// This is done unconditionally, but does not take a measurable amount of time
+	bootstrapBlueprint(ctx, config)
 
 	soongBuildEnv := config.Environment().Copy()
 	soongBuildEnv.Set("TOP", os.Getenv("TOP"))
@@ -105,6 +150,11 @@
 	soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
 	soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir())
 
+	// For Soong bootstrapping tests
+	if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
+		soongBuildEnv.Set("ALLOW_MISSING_DEPENDENCIES", "true")
+	}
+
 	err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap())
 	if err != nil {
 		ctx.Fatalf("failed to write environment file %s: %s", envFile, err)
@@ -130,16 +180,6 @@
 	cfg.TrimPath = absPath(ctx, ".")
 
 	func() {
-		ctx.BeginTrace(metrics.RunSoong, "minibp")
-		defer ctx.EndTrace()
-
-		minibp := filepath.Join(config.SoongOutDir(), ".minibootstrap/minibp")
-		if _, err := microfactory.Build(&cfg, minibp, "github.com/google/blueprint/bootstrap/minibp"); err != nil {
-			ctx.Fatalln("Failed to build minibp:", err)
-		}
-	}()
-
-	func() {
 		ctx.BeginTrace(metrics.RunSoong, "bpglob")
 		defer ctx.EndTrace()
 
@@ -187,10 +227,6 @@
 		cmd.Sandbox = soongSandbox
 		cmd.RunAndStreamOrFatal()
 	}
-
-	// This build generates .bootstrap/build.ninja, which is used in the next step.
-	ninja("minibootstrap", ".minibootstrap/build.ninja")
-
 	// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
 	ninja("bootstrap", ".bootstrap/build.ninja")
 
diff --git a/xml/Android.bp b/xml/Android.bp
index a5e5f4c..1542930 100644
--- a/xml/Android.bp
+++ b/xml/Android.bp
@@ -13,6 +13,7 @@
         "soong-etc",
     ],
     srcs: [
+        "testing.go",
         "xml.go",
     ],
     testSrcs: [
diff --git a/xml/testing.go b/xml/testing.go
new file mode 100644
index 0000000..1d09f10
--- /dev/null
+++ b/xml/testing.go
@@ -0,0 +1,19 @@
+// 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.
+
+package xml
+
+import "android/soong/android"
+
+var PreparerForTestWithXmlBuildComponents = android.FixtureRegisterWithContext(registerXmlBuildComponents)
diff --git a/xml/xml.go b/xml/xml.go
index 8810ae4..c281078 100644
--- a/xml/xml.go
+++ b/xml/xml.go
@@ -53,10 +53,14 @@
 )
 
 func init() {
-	android.RegisterModuleType("prebuilt_etc_xml", PrebuiltEtcXmlFactory)
+	registerXmlBuildComponents(android.InitRegistrationContext)
 	pctx.HostBinToolVariable("XmlLintCmd", "xmllint")
 }
 
+func registerXmlBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("prebuilt_etc_xml", PrebuiltEtcXmlFactory)
+}
+
 type prebuiltEtcXmlProperties struct {
 	// Optional DTD that will be used to validate the xml file.
 	Schema *string `android:"path"`
diff --git a/xml/xml_test.go b/xml/xml_test.go
index 138503c..83ae51c 100644
--- a/xml/xml_test.go
+++ b/xml/xml_test.go
@@ -15,7 +15,6 @@
 package xml
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -23,62 +22,33 @@
 	"android/soong/etc"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_xml_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
-func testXml(t *testing.T, bp string) *android.TestContext {
-	fs := map[string][]byte{
+var emptyFixtureFactory = android.NewFixtureFactory(nil)
+
+func testXml(t *testing.T, bp string) *android.TestResult {
+	fs := android.MockFS{
 		"foo.xml": nil,
 		"foo.dtd": nil,
 		"bar.xml": nil,
 		"bar.xsd": nil,
 		"baz.xml": nil,
 	}
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("prebuilt_etc", etc.PrebuiltEtcFactory)
-	ctx.RegisterModuleType("prebuilt_etc_xml", PrebuiltEtcXmlFactory)
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
 
-	return ctx
-}
-
-func assertEqual(t *testing.T, name, expected, actual string) {
-	t.Helper()
-	if expected != actual {
-		t.Errorf(name+" expected %q != got %q", expected, actual)
-	}
+	return emptyFixtureFactory.RunTest(t,
+		android.PrepareForTestWithArchMutator,
+		etc.PrepareForTestWithPrebuiltEtc,
+		PreparerForTestWithXmlBuildComponents,
+		fs.AddToFixture(),
+		android.FixtureWithRootAndroidBp(bp),
+	)
 }
 
 // Minimal test
 func TestPrebuiltEtcXml(t *testing.T) {
-	ctx := testXml(t, `
+	result := testXml(t, `
 		prebuilt_etc_xml {
 			name: "foo.xml",
 			src: "foo.xml",
@@ -103,14 +73,14 @@
 		{rule: "xmllint-minimal", input: "baz.xml"},
 	} {
 		t.Run(tc.schemaType, func(t *testing.T) {
-			rule := ctx.ModuleForTests(tc.input, "android_arm64_armv8-a").Rule(tc.rule)
-			assertEqual(t, "input", tc.input, rule.Input.String())
+			rule := result.ModuleForTests(tc.input, "android_arm64_armv8-a").Rule(tc.rule)
+			android.AssertStringEquals(t, "input", tc.input, rule.Input.String())
 			if tc.schemaType != "" {
-				assertEqual(t, "schema", tc.schema, rule.Args[tc.schemaType])
+				android.AssertStringEquals(t, "schema", tc.schema, rule.Args[tc.schemaType])
 			}
 		})
 	}
 
-	m := ctx.ModuleForTests("foo.xml", "android_arm64_armv8-a").Module().(*prebuiltEtcXml)
-	assertEqual(t, "installDir", buildDir+"/target/product/test_device/system/etc", m.InstallDirPath().String())
+	m := result.ModuleForTests("foo.xml", "android_arm64_armv8-a").Module().(*prebuiltEtcXml)
+	android.AssertPathRelativeToTopEquals(t, "installDir", "out/soong/target/product/test_device/system/etc", m.InstallDirPath())
 }
diff --git a/zip/zip.go b/zip/zip.go
index f731329..088ed0d 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -406,6 +406,8 @@
 	buf := &bytes.Buffer{}
 	var out io.Writer = buf
 
+	var zipErr error
+
 	if !args.WriteIfChanged {
 		f, err := os.Create(args.OutputFilePath)
 		if err != nil {
@@ -414,7 +416,7 @@
 
 		defer f.Close()
 		defer func() {
-			if err != nil {
+			if zipErr != nil {
 				os.Remove(args.OutputFilePath)
 			}
 		}()
@@ -422,9 +424,9 @@
 		out = f
 	}
 
-	err := zipTo(args, out)
-	if err != nil {
-		return err
+	zipErr = zipTo(args, out)
+	if zipErr != nil {
+		return zipErr
 	}
 
 	if args.WriteIfChanged {