Merge "Clean up java package use of python build components"
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 79d8cdd..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.
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/config.go b/android/config.go
index f4685a1..b6c862d 100644
--- a/android/config.go
+++ b/android/config.go
@@ -19,6 +19,7 @@
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -72,6 +73,10 @@
 	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
 }
@@ -278,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: {
@@ -1635,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/fixture.go b/android/fixture.go
index 928967d..6db43e2 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -381,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.
@@ -493,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.
 //
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/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/paths_test.go b/android/paths_test.go
index 3734ed2..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,24 @@
 		}
 	`
 
-	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)
-
-	foo := ctx.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
-
-	if g, w := foo.missingDeps, []string{"a", "b", "c"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want foo missing deps %q, got %q", w, g)
-	}
-
-	if g, w := foo.srcs, []string{}; !reflect.DeepEqual(g, w) {
-		t.Errorf("want foo srcs %q, got %q", w, g)
-	}
-
-	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)
-	}
+	AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps)
+	AssertArrayString(t, "bar srcs", []string{}, bar.srcs)
 }
 
 func TestPathRelativeToTop(t *testing.T) {
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 080e236..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"
@@ -361,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) {
@@ -402,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) {
@@ -440,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())
 	})
 }
 
@@ -524,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,
 	}
@@ -543,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)
 	})
@@ -605,18 +551,18 @@
 		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)
 	})
 }
@@ -666,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/test_asserts.go b/android/test_asserts.go
index 5100abb..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) {
diff --git a/android/variable.go b/android/variable.go
index a5e9ab4..08fa12c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -426,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/apex/apex.go b/apex/apex.go
index 3db20f4..429465d 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -549,24 +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}
-	compatConfigsTag = dependencyTag{name: "compatConfig", 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
@@ -741,7 +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, compatConfigsTag, a.properties.Compat_configs...)
+	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
 
 	if a.artApex {
 		// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
@@ -835,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
 		}
@@ -1743,7 +1767,7 @@
 				} else {
 					ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc module", depName)
 				}
-			case compatConfigsTag:
+			case compatConfigTag:
 				if compatConfig, ok := child.(java.PlatformCompatConfigIntf); ok {
 					filesInfo = append(filesInfo, apexFileForCompatConfig(ctx, compatConfig, depName))
 				} else {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 21cf5df..c01525b 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -6018,6 +6018,13 @@
 			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{
diff --git a/bootstrap_test.sh b/bootstrap_test.sh
index 68067ee..87f5e31 100755
--- a/bootstrap_test.sh
+++ b/bootstrap_test.sh
@@ -4,7 +4,7 @@
 # in a source tree that only contains enough files for Bazel and Soong to work.
 
 HARDWIRED_MOCK_TOP=
-# Uncomment this for to be able to view the source tree after a test is run
+# 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")"/../..)"
@@ -85,63 +85,6 @@
   export ALLOW_MISSING_DEPENDENCIES=true
 
   mkdir -p out/soong
-  # This is necessary because the empty soong.variables file written to satisfy
-  # Ninja would contain "BootJars: {}" instead of "BootJars: []" which cannot
-  # be parsed back
-  # TODO(b/182965747): Fix this.
-  cat > out/soong/soong.variables <<'EOF'
-{
-    "BuildNumberFile": "build_number.txt",
-    "Platform_version_name": "S",
-    "Platform_sdk_version": 30,
-    "Platform_sdk_codename": "S",
-    "Platform_sdk_final": false,
-    "Platform_version_active_codenames": [
-        "S"
-    ],
-    "Platform_vndk_version": "S",
-    "DeviceName": "generic_arm64",
-    "DeviceArch": "arm64",
-    "DeviceArchVariant": "armv8-a",
-    "DeviceCpuVariant": "generic",
-    "DeviceAbi": [
-        "arm64-v8a"
-    ],
-    "DeviceSecondaryArch": "arm",
-    "DeviceSecondaryArchVariant": "armv8-a",
-    "DeviceSecondaryCpuVariant": "generic",
-    "DeviceSecondaryAbi": [
-        "armeabi-v7a",
-        "armeabi"
-    ],
-    "HostArch": "x86_64",
-    "HostSecondaryArch": "x86",
-    "CrossHost": "windows",
-    "CrossHostArch": "x86",
-    "CrossHostSecondaryArch": "x86_64",
-    "AAPTCharacteristics": "nosdcard",
-    "AAPTConfig": [
-        "normal",
-        "large",
-        "xlarge",
-        "hdpi",
-        "xhdpi",
-        "xxhdpi"
-    ],
-    "AAPTPreferredConfig": "xhdpi",
-    "AAPTPrebuiltDPI": [
-        "xhdpi",
-        "xxhdpi"
-    ],
-    "Malloc_not_svelte": true,
-    "Malloc_zero_contents": true,
-    "Malloc_pattern_fill_contents": false,
-    "Safestack": false,
-    "BootJars": [],
-    "UpdatableBootJars": [],
-    "Native_coverage": null
-}
-EOF
 }
 
 function run_soong() {
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/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 372a610..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"
@@ -217,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
@@ -255,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/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/app_test.go b/java/app_test.go
index c189ee5..7168a96 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -370,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)
 		})
 	}
 }
@@ -984,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)
@@ -1002,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) {
@@ -1083,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)
 			})
 		}
 	}
@@ -1145,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)
 				})
 			}
 		}
@@ -2360,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.
@@ -2380,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
@@ -2394,9 +2401,7 @@
 		`--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
@@ -2405,9 +2410,7 @@
 		`--uses-library android.test.runner ` +
 		`--optional-uses-library bar ` +
 		`--optional-uses-library baz `
-	if !strings.Contains(verifyApkCmd, verifyApkArgs) {
-		t.Errorf("wanted %q in %q", verifyApkArgs, verifyApkCmd)
-	}
+	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
@@ -2418,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) {
@@ -2722,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/java_test.go b/java/java_test.go
index 24911b4..b68945f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -48,9 +48,10 @@
 	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,
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 4bfd4e2..3c43a8e 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -26,6 +26,7 @@
 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)
 }
 
@@ -35,10 +36,6 @@
 	return android.PathForOutput(ctx, "compat_config", "merged_compat_config.xml")
 }
 
-type platformCompatConfigSingleton struct {
-	metadata android.Path
-}
-
 type platformCompatConfigProperties struct {
 	Src *string `android:"path"`
 }
@@ -52,7 +49,7 @@
 	metadataFile   android.OutputPath
 }
 
-func (p *platformCompatConfig) compatConfigMetadata() android.OutputPath {
+func (p *platformCompatConfig) compatConfigMetadata() android.Path {
 	return p.metadataFile
 }
 
@@ -64,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)
@@ -145,10 +110,6 @@
 	}}
 }
 
-func platformCompatConfigSingletonFactory() android.Singleton {
-	return &platformCompatConfigSingleton{}
-}
-
 func PlatformCompatConfigFactory() android.Module {
 	module := &platformCompatConfig{}
 	module.AddProperties(&module.properties)
@@ -156,6 +117,93 @@
 	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/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/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/ui/build/soong.go b/ui/build/soong.go
index fee5723..9afcb88 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -65,9 +65,10 @@
 // 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
+	srcDir           string
+	buildDir         string
+	ninjaBuildDir    string
+	debugCompilation bool
 }
 
 func (c BlueprintConfig) SrcDir() string {
@@ -82,6 +83,10 @@
 	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()
@@ -102,9 +107,10 @@
 	blueprintCtx := blueprint.NewContext()
 	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 	blueprintConfig := BlueprintConfig{
-		srcDir:        os.Getenv("TOP"),
-		buildDir:      config.SoongOutDir(),
-		ninjaBuildDir: config.OutDir(),
+		srcDir:           os.Getenv("TOP"),
+		buildDir:         config.SoongOutDir(),
+		ninjaBuildDir:    config.OutDir(),
+		debugCompilation: os.Getenv("SOONG_DELVE") != "",
 	}
 
 	bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
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())
 }