Merge "[pgo] Do not explicitly link profile runtime"
diff --git a/android/Android.bp b/android/Android.bp
index 2406321..4da0f4e 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -85,6 +85,7 @@
         "androidmk_test.go",
         "apex_test.go",
         "arch_test.go",
+        "bazel_test.go",
         "config_test.go",
         "csuite_config_test.go",
         "depset_test.go",
diff --git a/android/bazel.go b/android/bazel.go
index 683495b..9a14e70 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -32,7 +32,13 @@
 
 	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
 	// cause a conflict due to the duplicate targets if label is also set.
-	Bp2build_available bool
+	//
+	// This is a bool pointer to support tristates: true, false, not set.
+	//
+	// To opt-in a module, set bazel_module: { bp2build_available: true }
+	// To opt-out a module, set bazel_module: { bp2build_available: false }
+	// To defer the default setting for the directory, do not set the value.
+	Bp2build_available *bool
 }
 
 // Properties contains common module properties for Bazel migration purposes.
@@ -54,9 +60,9 @@
 	HasHandcraftedLabel() bool
 	HandcraftedLabel() string
 	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
-	ConvertWithBp2build() bool
+	ConvertWithBp2build(ctx BazelConversionPathContext) bool
 	GetBazelBuildFileContents(c Config, path, name string) (string, error)
-	ConvertedToBazel() bool
+	ConvertedToBazel(ctx BazelConversionPathContext) bool
 }
 
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@@ -91,15 +97,138 @@
 	if b.HasHandcraftedLabel() {
 		return b.HandcraftedLabel()
 	}
-	if b.ConvertWithBp2build() {
+	if b.ConvertWithBp2build(ctx) {
 		return bp2buildModuleLabel(ctx, module)
 	}
 	return "" // no label for unconverted module
 }
 
+// Configuration to decide if modules in a directory should default to true/false for bp2build_available
+type Bp2BuildConfig map[string]BazelConversionConfigEntry
+type BazelConversionConfigEntry int
+
+const (
+	// iota + 1 ensures that the int value is not 0 when used in the Bp2buildAllowlist map,
+	// which can also mean that the key doesn't exist in a lookup.
+
+	// all modules in this package and subpackages default to bp2build_available: true.
+	// allows modules to opt-out.
+	Bp2BuildDefaultTrueRecursively BazelConversionConfigEntry = iota + 1
+
+	// all modules in this package (not recursively) default to bp2build_available: false.
+	// allows modules to opt-in.
+	Bp2BuildDefaultFalse
+)
+
+var (
+	// Configure modules in these directories to enable bp2build_available: true or false by default.
+	bp2buildDefaultConfig = Bp2BuildConfig{
+		"bionic":                Bp2BuildDefaultTrueRecursively,
+		"system/core/libcutils": Bp2BuildDefaultTrueRecursively,
+		"system/logging/liblog": Bp2BuildDefaultTrueRecursively,
+	}
+
+	// Per-module denylist to always opt modules out.
+	bp2buildModuleDoNotConvert = map[string]bool{
+		"libBionicBenchmarksUtils":      true,
+		"libbionic_spawn_benchmark":     true,
+		"libc_jemalloc_wrapper":         true,
+		"libc_bootstrap":                true,
+		"libc_init_static":              true,
+		"libc_init_dynamic":             true,
+		"libc_tzcode":                   true,
+		"lib_dns":                       true,
+		"libc_freebsd":                  true,
+		"libc_freebsd_large_stack":      true,
+		"libc_netbsd":                   true,
+		"libc_openbsd_ndk":              true,
+		"libc_openbsd_large_stack":      true,
+		"libc_openbsd":                  true,
+		"libc_gdtoa":                    true,
+		"libc_fortify":                  true,
+		"libc_bionic":                   true,
+		"libc_bionic_ndk":               true,
+		"libc_bionic_systrace":          true,
+		"libc_pthread":                  true,
+		"libc_syscalls":                 true,
+		"libc_aeabi":                    true,
+		"libc_ndk":                      true,
+		"libc_nopthread":                true,
+		"libc_common":                   true,
+		"libc_static_dispatch":          true,
+		"libc_dynamic_dispatch":         true,
+		"libc_common_static":            true,
+		"libc_common_shared":            true,
+		"libc_unwind_static":            true,
+		"libc_nomalloc":                 true,
+		"libasync_safe":                 true,
+		"libc_malloc_debug_backtrace":   true,
+		"libsystemproperties":           true,
+		"libdl_static":                  true,
+		"liblinker_main":                true,
+		"liblinker_malloc":              true,
+		"liblinker_debuggerd_stub":      true,
+		"libbionic_tests_headers_posix": true,
+	}
+)
+
 // ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
-func (b *BazelModuleBase) ConvertWithBp2build() bool {
-	return b.bazelProperties.Bazel_module.Bp2build_available
+func (b *BazelModuleBase) ConvertWithBp2build(ctx BazelConversionPathContext) bool {
+	if bp2buildModuleDoNotConvert[ctx.Module().Name()] {
+		return false
+	}
+
+	// Ensure that the module type of this module has a bp2build converter. This
+	// prevents mixed builds from using auto-converted modules just by matching
+	// the package dir; it also has to have a bp2build mutator as well.
+	if ctx.Config().bp2buildModuleTypeConfig[ctx.ModuleType()] == false {
+		return false
+	}
+
+	packagePath := ctx.ModuleDir()
+	config := ctx.Config().bp2buildPackageConfig
+
+	// This is a tristate value: true, false, or unset.
+	propValue := b.bazelProperties.Bazel_module.Bp2build_available
+	if bp2buildDefaultTrueRecursively(packagePath, config) {
+		// Allow modules to explicitly opt-out.
+		return proptools.BoolDefault(propValue, true)
+	}
+
+	// Allow modules to explicitly opt-in.
+	return proptools.BoolDefault(propValue, false)
+}
+
+// bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
+// set of package prefixes where all modules must be converted. That is, if the
+// package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will
+// return true.
+//
+// However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry
+// exactly, this module will return false early.
+//
+// This function will also return false if the package doesn't match anything in
+// the config.
+func bp2buildDefaultTrueRecursively(packagePath string, config Bp2BuildConfig) bool {
+	ret := false
+
+	if config[packagePath] == Bp2BuildDefaultFalse {
+		return false
+	}
+
+	packagePrefix := ""
+	// e.g. for x/y/z, iterate over x, x/y, then x/y/z, taking the final value from the allowlist.
+	for _, part := range strings.Split(packagePath, "/") {
+		packagePrefix += part
+		if config[packagePrefix] == Bp2BuildDefaultTrueRecursively {
+			// package contains this prefix and this prefix should convert all modules
+			return true
+		}
+		// Continue to the next part of the package dir.
+		packagePrefix += "/"
+	}
+
+	return ret
 }
 
 // GetBazelBuildFileContents returns the file contents of a hand-crafted BUILD file if available or
@@ -126,6 +255,6 @@
 
 // ConvertedToBazel returns whether this module has been converted to Bazel, whether automatically
 // or manually
-func (b *BazelModuleBase) ConvertedToBazel() bool {
-	return b.ConvertWithBp2build() || b.HasHandcraftedLabel()
+func (b *BazelModuleBase) ConvertedToBazel(ctx BazelConversionPathContext) bool {
+	return b.ConvertWithBp2build(ctx) || b.HasHandcraftedLabel()
 }
diff --git a/android/bazel_test.go b/android/bazel_test.go
new file mode 100644
index 0000000..e5d8fbb
--- /dev/null
+++ b/android/bazel_test.go
@@ -0,0 +1,134 @@
+// 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 android
+
+import "testing"
+
+func TestConvertAllModulesInPackage(t *testing.T) {
+	testCases := []struct {
+		prefixes   Bp2BuildConfig
+		packageDir string
+	}{
+		{
+			prefixes: Bp2BuildConfig{
+				"a": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a/b": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a/b":   Bp2BuildDefaultTrueRecursively,
+				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultTrueRecursively,
+				"d/e/f": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultFalse,
+				"a/b":   Bp2BuildDefaultTrueRecursively,
+				"a/b/c": Bp2BuildDefaultFalse,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultTrueRecursively,
+				"a/b":   Bp2BuildDefaultFalse,
+				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a",
+		},
+	}
+
+	for _, test := range testCases {
+		if !bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes) {
+			t.Errorf("Expected to convert all modules in %s based on %v, but failed.", test.packageDir, test.prefixes)
+		}
+	}
+}
+
+func TestModuleOptIn(t *testing.T) {
+	testCases := []struct {
+		prefixes   Bp2BuildConfig
+		packageDir string
+	}{
+		{
+			prefixes: Bp2BuildConfig{
+				"a/b": Bp2BuildDefaultFalse,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":   Bp2BuildDefaultFalse,
+				"a/b": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a/b": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a", // opt-in by default
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultTrueRecursively,
+				"d/e/f": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "foo/bar",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultTrueRecursively,
+				"a/b":   Bp2BuildDefaultFalse,
+				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			},
+			packageDir: "a/b",
+		},
+		{
+			prefixes: Bp2BuildConfig{
+				"a":     Bp2BuildDefaultFalse,
+				"a/b":   Bp2BuildDefaultTrueRecursively,
+				"a/b/c": Bp2BuildDefaultFalse,
+			},
+			packageDir: "a",
+		},
+	}
+
+	for _, test := range testCases {
+		if bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes) {
+			t.Errorf("Expected to allow module opt-in in %s based on %v, but failed.", test.packageDir, test.prefixes)
+		}
+	}
+}
diff --git a/android/config.go b/android/config.go
index 0de8928..cfbc37f 100644
--- a/android/config.go
+++ b/android/config.go
@@ -140,6 +140,9 @@
 	fs         pathtools.FileSystem
 	mockBpList string
 
+	bp2buildPackageConfig    Bp2BuildConfig
+	bp2buildModuleTypeConfig map[string]bool
+
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
 	TestAllowNonExistentPaths bool
@@ -281,6 +284,8 @@
 
 	config.mockFileSystem(bp, fs)
 
+	config.bp2buildModuleTypeConfig = map[string]bool{}
+
 	return Config{config}
 }
 
@@ -452,6 +457,8 @@
 			Bool(config.productVariables.ClangCoverage))
 
 	config.BazelContext, err = NewBazelContext(config)
+	config.bp2buildPackageConfig = bp2buildDefaultConfig
+	config.bp2buildModuleTypeConfig = make(map[string]bool)
 
 	return Config{config}, err
 }
@@ -1005,6 +1012,10 @@
 	return ioutil.ReadFile(absolutePath(path.String()))
 }
 
+func (c *deviceConfig) WithDexpreopt() bool {
+	return c.config.productVariables.WithDexpreopt
+}
+
 func (c *config) FrameworksBaseDirExists(ctx PathContext) bool {
 	return ExistentPathForSource(ctx, "frameworks", "base", "Android.bp").Valid()
 }
diff --git a/android/env.go b/android/env.go
index 289d803..725a145 100644
--- a/android/env.go
+++ b/android/env.go
@@ -18,12 +18,16 @@
 	"android/soong/shared"
 )
 
-// This file supports dependencies on environment variables.  During build manifest generation,
-// any dependency on an environment variable is added to a list.  During the singleton phase
-// a JSON file is written containing the current value of all used environment variables.
-// The next time the top-level build script is run, it uses the soong_env executable to
-// compare the contents of the environment variables, rewriting the file if necessary to cause
-// a manifest regeneration.
+// This file supports dependencies on environment variables.  During build
+// manifest generation, any dependency on an environment variable is added to a
+// list.  At the end of the build, a JSON file called soong.environment.used is
+// written containing the current value of all used environment variables. The
+// next time the top-level build script is run, soong_ui parses the compare the
+// contents of the used environment variables, then, if they changed, deletes
+// soong.environment.used to cause a rebuild.
+//
+// The dependency of build.ninja on soong.environment.used is declared in
+// build.ninja.d
 
 var originalEnv map[string]string
 
@@ -34,30 +38,3 @@
 		panic(err)
 	}
 }
-
-func EnvSingleton() Singleton {
-	return &envSingleton{}
-}
-
-type envSingleton struct{}
-
-func (c *envSingleton) GenerateBuildActions(ctx SingletonContext) {
-	envDeps := ctx.Config().EnvDeps()
-
-	envFile := PathForOutput(ctx, "soong.environment.used")
-	if ctx.Failed() {
-		return
-	}
-
-	data, err := shared.EnvFileContents(envDeps)
-	if err != nil {
-		ctx.Errorf(err.Error())
-	}
-
-	err = WriteFileToOutputDir(envFile, data, 0666)
-	if err != nil {
-		ctx.Errorf(err.Error())
-	}
-
-	ctx.AddNinjaFileDeps(envFile.String())
-}
diff --git a/android/filegroup.go b/android/filegroup.go
index 2eb4741..abbb4d4 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -53,7 +53,7 @@
 
 func FilegroupBp2Build(ctx TopDownMutatorContext) {
 	fg, ok := ctx.Module().(*fileGroup)
-	if !ok || !fg.ConvertWithBp2build() {
+	if !ok || !fg.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/android/fixture.go b/android/fixture.go
index 6c9ea6b..8d62958 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -138,7 +138,7 @@
 // }
 //
 // func TestJavaStuff(t *testing.T) {
-//   result := android.GroupFixturePreparers(t,
+//   result := android.GroupFixturePreparers(
 //       prepareForJavaTest,
 //       android.FixtureWithRootAndroidBp(`java_library {....}`),
 //       android.MockFS{...}.AddToFixture(),
diff --git a/android/mutator.go b/android/mutator.go
index 9552aa1..9e99bee 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -226,7 +226,7 @@
 
 var bp2buildPreArchMutators = []RegisterMutatorFunc{}
 var bp2buildDepsMutators = []RegisterMutatorFunc{}
-var bp2buildMutators = []RegisterMutatorFunc{}
+var bp2buildMutators = map[string]RegisterMutatorFunc{}
 
 // RegisterBp2BuildMutator registers specially crafted mutators for
 // converting Blueprint/Android modules into special modules that can
@@ -237,7 +237,7 @@
 	f := func(ctx RegisterMutatorsContext) {
 		ctx.TopDown(moduleType, m)
 	}
-	bp2buildMutators = append(bp2buildMutators, f)
+	bp2buildMutators[moduleType] = f
 }
 
 // PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index b8ef0f5..de0197a 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -308,7 +308,8 @@
 					}
 				}),
 				test.fs.AddToFixture(),
-			).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+			).
+				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
 				RunTest(t)
 		})
 	}
diff --git a/android/paths.go b/android/paths.go
index f648c55..b3cf804 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -181,7 +181,7 @@
 	Path
 
 	// return the path to the build directory.
-	buildDir() string
+	getBuildDir() string
 
 	// the writablePath method doesn't directly do anything,
 	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
@@ -340,6 +340,7 @@
 
 	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
 	Module() Module
+	ModuleType() string
 	OtherModuleName(m blueprint.Module) string
 	OtherModuleDir(m blueprint.Module) string
 }
@@ -450,7 +451,7 @@
 	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
 	b, ok := module.(Bazelable)
 	// TODO(b/181155349): perhaps return an error here if the module can't be/isn't being converted
-	if !ok || !b.ConvertedToBazel() {
+	if !ok || !b.ConvertedToBazel(ctx) {
 		return bp2buildModuleLabel(ctx, module)
 	}
 	return b.GetBazelLabel(ctx, module)
@@ -511,6 +512,9 @@
 	if module == nil {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
+	if aModule, ok := module.(Module); ok && !aModule.Enabled() {
+		return nil, missingDependencyError{[]string{moduleName}}
+	}
 	if outProducer, ok := module.(OutputFileProducer); ok {
 		outputFiles, err := outProducer.OutputFiles(tag)
 		if err != nil {
@@ -931,9 +935,8 @@
 }
 
 type basePath struct {
-	path   string
-	config Config
-	rel    string
+	path string
+	rel  string
 }
 
 func (p basePath) Ext() string {
@@ -964,6 +967,9 @@
 // SourcePath is a Path representing a file path rooted from SrcDir
 type SourcePath struct {
 	basePath
+
+	// The sources root, i.e. Config.SrcDir()
+	srcDir string
 }
 
 var _ Path = SourcePath{}
@@ -977,7 +983,7 @@
 // code that is embedding ninja variables in paths
 func safePathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validateSafePath(pathComponents...)
-	ret := SourcePath{basePath{p, ctx.Config(), ""}}
+	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
 	if err != nil {
 		return ret, err
 	}
@@ -993,7 +999,7 @@
 // pathForSource creates a SourcePath from pathComponents, but does not check that it exists.
 func pathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validatePath(pathComponents...)
-	ret := SourcePath{basePath{p, ctx.Config(), ""}}
+	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
 	if err != nil {
 		return ret, err
 	}
@@ -1087,7 +1093,7 @@
 }
 
 func (p SourcePath) String() string {
-	return filepath.Join(p.config.srcDir, p.path)
+	return filepath.Join(p.srcDir, p.path)
 }
 
 // Join creates a new SourcePath with paths... joined with the current path. The
@@ -1119,7 +1125,7 @@
 		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
 		return OptionalPath{}
 	}
-	dir := filepath.Join(p.config.srcDir, p.path, relDir)
+	dir := filepath.Join(p.srcDir, p.path, relDir)
 	// Use Glob so that we are run again if the directory is added.
 	if pathtools.IsGlob(dir) {
 		ReportPathErrorf(ctx, "Path may not contain a glob: %s", dir)
@@ -1132,13 +1138,17 @@
 	if len(paths) == 0 {
 		return OptionalPath{}
 	}
-	relPath := Rel(ctx, p.config.srcDir, paths[0])
+	relPath := Rel(ctx, p.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
 }
 
 // OutputPath is a Path representing an intermediates file path rooted from the build directory
 type OutputPath struct {
 	basePath
+
+	// The soong build directory, i.e. Config.BuildDir()
+	buildDir string
+
 	fullPath string
 }
 
@@ -1153,8 +1163,8 @@
 	return p
 }
 
-func (p OutputPath) buildDir() string {
-	return p.config.buildDir
+func (p OutputPath) getBuildDir() string {
+	return p.buildDir
 }
 
 func (p OutputPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
@@ -1178,7 +1188,7 @@
 // Only use this function to construct paths for dependencies of the build
 // tool invocation.
 func pathForBuildToolDep(ctx PathContext, path string) toolDepPath {
-	return toolDepPath{basePath{path, ctx.Config(), ""}}
+	return toolDepPath{basePath{path, ""}}
 }
 
 // PathForOutput joins the provided paths and returns an OutputPath that is
@@ -1191,7 +1201,7 @@
 	}
 	fullPath := filepath.Join(ctx.Config().buildDir, path)
 	path = fullPath[len(fullPath)-len(path):]
-	return OutputPath{basePath{path, ctx.Config(), ""}, fullPath}
+	return OutputPath{basePath{path, ""}, ctx.Config().buildDir, fullPath}
 }
 
 // PathsForOutput returns Paths rooted from buildDir
@@ -1425,7 +1435,8 @@
 		reportPathError(ctx, err)
 	}
 
-	outputPath := OutputPath{basePath{"", ctx.Config(), ""},
+	outputPath := OutputPath{basePath{"", ""},
+		ctx.Config().buildDir,
 		ctx.Config().BazelContext.OutputBase()}
 
 	return BazelOutPath{
@@ -1519,6 +1530,9 @@
 type InstallPath struct {
 	basePath
 
+	// The soong build directory, i.e. Config.BuildDir()
+	buildDir string
+
 	// partitionDir is the part of the InstallPath that is automatically determined according to the context.
 	// For example, it is host/<os>-<arch> for host modules, and target/product/<device>/<partition> for device modules.
 	partitionDir string
@@ -1527,8 +1541,8 @@
 	makePath bool
 }
 
-func (p InstallPath) buildDir() string {
-	return p.config.buildDir
+func (p InstallPath) getBuildDir() string {
+	return p.buildDir
 }
 
 func (p InstallPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
@@ -1543,9 +1557,9 @@
 func (p InstallPath) String() string {
 	if p.makePath {
 		// Make path starts with out/ instead of out/soong.
-		return filepath.Join(p.config.buildDir, "../", p.path)
+		return filepath.Join(p.buildDir, "../", p.path)
 	} else {
-		return filepath.Join(p.config.buildDir, p.path)
+		return filepath.Join(p.buildDir, p.path)
 	}
 }
 
@@ -1554,9 +1568,9 @@
 // The ./soong is dropped if the install path is for Make.
 func (p InstallPath) PartitionDir() string {
 	if p.makePath {
-		return filepath.Join(p.config.buildDir, "../", p.partitionDir)
+		return filepath.Join(p.buildDir, "../", p.partitionDir)
 	} else {
-		return filepath.Join(p.config.buildDir, p.partitionDir)
+		return filepath.Join(p.buildDir, p.partitionDir)
 	}
 }
 
@@ -1639,7 +1653,8 @@
 	}
 
 	base := InstallPath{
-		basePath:     basePath{partionPath, ctx.Config(), ""},
+		basePath:     basePath{partionPath, ""},
+		buildDir:     ctx.Config().buildDir,
 		partitionDir: partionPath,
 		makePath:     false,
 	}
@@ -1649,7 +1664,8 @@
 
 func pathForNdkOrSdkInstall(ctx PathContext, prefix string, paths []string) InstallPath {
 	base := InstallPath{
-		basePath:     basePath{prefix, ctx.Config(), ""},
+		basePath:     basePath{prefix, ""},
+		buildDir:     ctx.Config().buildDir,
 		partitionDir: prefix,
 		makePath:     false,
 	}
@@ -1784,7 +1800,7 @@
 	if strings.ContainsAny(phony, "$/") {
 		ReportPathErrorf(ctx, "Phony target contains invalid character ($ or /): %s", phony)
 	}
-	return PhonyPath{basePath{phony, ctx.Config(), ""}}
+	return PhonyPath{basePath{phony, ""}}
 }
 
 type PhonyPath struct {
@@ -1793,8 +1809,9 @@
 
 func (p PhonyPath) writablePath() {}
 
-func (p PhonyPath) buildDir() string {
-	return p.config.buildDir
+func (p PhonyPath) getBuildDir() string {
+	// A phone path cannot contain any / so cannot be relative to the build directory.
+	return ""
 }
 
 func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
diff --git a/android/register.go b/android/register.go
index 900edfa..35469d4 100644
--- a/android/register.go
+++ b/android/register.go
@@ -174,7 +174,13 @@
 		t.register(ctx)
 	}
 
-	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators, bp2buildDepsMutators, bp2buildMutators)
+	bp2buildMutatorList := []RegisterMutatorFunc{}
+	for t, f := range bp2buildMutators {
+		ctx.config.bp2buildModuleTypeConfig[t] = true
+		bp2buildMutatorList = append(bp2buildMutatorList, f)
+	}
+
+	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators, bp2buildDepsMutators, bp2buildMutatorList)
 }
 
 // Register the pipeline of singletons, module types, and mutators for
@@ -206,7 +212,6 @@
 
 		// Register env and ninjadeps last so that they can track all used environment variables and
 		// Ninja file dependencies stored in the config.
-		singleton{false, "env", EnvSingleton},
 		singleton{false, "ninjadeps", ninjaDepsSingletonFactory},
 	)
 
diff --git a/android/testing.go b/android/testing.go
index f17de31..c077678 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -117,6 +117,11 @@
 	}),
 )
 
+// Prepares a test that disallows non-existent paths.
+var PrepareForTestDisallowNonExistentPaths = FixtureModifyConfig(func(config Config) {
+	config.TestAllowNonExistentPaths = false
+})
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
@@ -158,12 +163,17 @@
 	ctx.finalDeps = append(ctx.finalDeps, f)
 }
 
+func (ctx *TestContext) RegisterBp2BuildConfig(config Bp2BuildConfig) {
+	ctx.config.bp2buildPackageConfig = config
+}
+
 // RegisterBp2BuildMutator registers a BazelTargetModule mutator for converting a module
 // type to the equivalent Bazel target.
 func (ctx *TestContext) RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
 	f := func(ctx RegisterMutatorsContext) {
 		ctx.TopDown(moduleType, m)
 	}
+	ctx.config.bp2buildModuleTypeConfig[moduleType] = true
 	ctx.bp2buildMutators = append(ctx.bp2buildMutators, f)
 }
 
@@ -401,9 +411,6 @@
 	globalOrder.mutatorOrder.enforceOrdering(mutators)
 	mutators.registerAll(ctx.Context)
 
-	// Register the env singleton with this context before sorting.
-	ctx.RegisterSingletonType("env", EnvSingleton)
-
 	// Ensure that the singletons used in the test are in the same order as they are used at runtime.
 	globalOrder.singletonOrder.enforceOrdering(ctx.singletons)
 	ctx.singletons.registerAll(ctx.Context)
@@ -902,7 +909,7 @@
 	}
 	p := path.String()
 	if w, ok := path.(WritablePath); ok {
-		rel, err := filepath.Rel(w.buildDir(), p)
+		rel, err := filepath.Rel(w.getBuildDir(), p)
 		if err != nil {
 			panic(err)
 		}
@@ -937,7 +944,7 @@
 	}
 	p := path.String()
 	if w, ok := path.(WritablePath); ok {
-		buildDir := w.buildDir()
+		buildDir := w.getBuildDir()
 		return StringPathRelativeToTop(buildDir, p)
 	}
 	return p
diff --git a/android/variable.go b/android/variable.go
index 776a5c7..2ab51c7 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -341,6 +341,8 @@
 
 	DexpreoptGlobalConfig *string `json:",omitempty"`
 
+	WithDexpreopt bool `json:",omitempty"`
+
 	ManifestPackageNameOverrides []string `json:",omitempty"`
 	CertificateOverrides         []string `json:",omitempty"`
 	PackageNameOverrides         []string `json:",omitempty"`
diff --git a/apex/apex.go b/apex/apex.go
index a67fe1f..dca5595 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -29,7 +29,6 @@
 	"android/soong/android"
 	"android/soong/bpf"
 	"android/soong/cc"
-	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/java"
@@ -1573,9 +1572,6 @@
 		if dt, ok := depTag.(dependencyTag); ok && !dt.payload {
 			return false
 		}
-		if depTag == dexpreopt.Dex2oatDepTag {
-			return false
-		}
 
 		ai := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
 		externalDep := !android.InList(ctx.ModuleName(), ai.InApexes)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index b159660..e0cefa1 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -141,13 +141,12 @@
 		}
 	`),
 	android.FixtureMergeMockFs(android.MockFS{
-		"a.java":                                                      nil,
-		"PrebuiltAppFoo.apk":                                          nil,
-		"PrebuiltAppFooPriv.apk":                                      nil,
-		"build/make/target/product/security":                          nil,
-		"apex_manifest.json":                                          nil,
-		"AndroidManifest.xml":                                         nil,
-		"system/sepolicy/apex/myapex-file_contexts":                   nil,
+		"a.java":                                    nil,
+		"PrebuiltAppFoo.apk":                        nil,
+		"PrebuiltAppFooPriv.apk":                    nil,
+		"apex_manifest.json":                        nil,
+		"AndroidManifest.xml":                       nil,
+		"system/sepolicy/apex/myapex-file_contexts": nil,
 		"system/sepolicy/apex/myapex.updatable-file_contexts":         nil,
 		"system/sepolicy/apex/myapex2-file_contexts":                  nil,
 		"system/sepolicy/apex/otherapex-file_contexts":                nil,
diff --git a/bootstrap_test.sh b/bootstrap_test.sh
index 87f5e31..6c5338a 100755
--- a/bootstrap_test.sh
+++ b/bootstrap_test.sh
@@ -235,6 +235,86 @@
   grep -q my_little_library.py out/soong/build.ninja || fail "new file is not in output"
 }
 
+function test_soong_build_rerun_iff_environment_changes() {
+  setup
+
+  mkdir -p cherry
+  cat > cherry/Android.bp <<'EOF'
+bootstrap_go_package {
+  name: "cherry",
+  pkgPath: "android/soong/cherry",
+  deps: [
+    "blueprint",
+    "soong",
+    "soong-android",
+  ],
+  srcs: [
+    "cherry.go",
+  ],
+  pluginFor: ["soong_build"],
+}
+EOF
+
+  cat > cherry/cherry.go <<'EOF'
+package cherry
+
+import (
+  "android/soong/android"
+  "github.com/google/blueprint"
+)
+
+var (
+  pctx = android.NewPackageContext("cherry")
+)
+
+func init() {
+  android.RegisterSingletonType("cherry", CherrySingleton)
+}
+
+func CherrySingleton() android.Singleton {
+  return &cherrySingleton{}
+}
+
+type cherrySingleton struct{}
+
+func (p *cherrySingleton) GenerateBuildActions(ctx android.SingletonContext) {
+  cherryRule := ctx.Rule(pctx, "cherry",
+    blueprint.RuleParams{
+      Command: "echo CHERRY IS " + ctx.Config().Getenv("CHERRY") + " > ${out}",
+      CommandDeps: []string{},
+      Description: "Cherry",
+    })
+
+  outputFile := android.PathForOutput(ctx, "cherry", "cherry.txt")
+  var deps android.Paths
+
+  ctx.Build(pctx, android.BuildParams{
+    Rule: cherryRule,
+    Output: outputFile,
+    Inputs: deps,
+  })
+}
+EOF
+
+  export CHERRY=TASTY
+  run_soong
+  grep -q "CHERRY IS TASTY" out/soong/build.ninja \
+    || fail "first value of environment variable is not used"
+
+  export CHERRY=RED
+  run_soong
+  grep -q "CHERRY IS RED" out/soong/build.ninja \
+    || fail "second value of environment variable not used"
+  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  run_soong
+  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$mtime1" != "$mtime2" ]]; then
+    fail "Output Ninja file changed when environment variable did not"
+  fi
+
+}
+
 function test_add_file_to_soong_build() {
   setup
   run_soong
@@ -308,12 +388,28 @@
   grep -q "Make it so" out/soong/build.ninja || fail "New action not present"
 }
 
+function test_null_build_after_docs {
+  setup
+  run_soong
+  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  prebuilts/build-tools/linux-x86/bin/ninja -f out/soong/build.ninja soong_docs
+  run_soong
+  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+
+  if [[ "$mtime1" != "$mtime2" ]]; then
+    fail "Output Ninja file changed on null build"
+  fi
+}
+
 test_bazel_smoke
 test_smoke
 test_null_build
+test_null_build_after_docs
 test_soong_build_rebuilt_if_blueprint_changes
 test_add_file_to_glob
 test_add_android_bp
 test_change_android_bp
 test_delete_android_bp
 test_add_file_to_soong_build
+test_soong_build_rerun_iff_environment_changes
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 97a5137..007d6d8 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -18,44 +18,48 @@
 	"android/soong/android"
 	"fmt"
 	"os"
+	"strings"
 )
 
-// The Bazel bp2build code generator is responsible for writing .bzl files that are equivalent to
-// Android.bp files that are capable of being built with Bazel.
+// Codegen is the backend of bp2build. The code generator is responsible for
+// writing .bzl files that are equivalent to Android.bp files that are capable
+// of being built with Bazel.
 func Codegen(ctx *CodegenContext) CodegenMetrics {
 	outputDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(outputDir)
 
-	ruleShims := CreateRuleShims(android.ModuleTypeFactories())
-
 	buildToTargets, metrics := GenerateBazelTargets(ctx)
 
-	filesToWrite := CreateBazelFiles(ruleShims, buildToTargets, ctx.mode)
+	filesToWrite := CreateBazelFiles(nil, buildToTargets, ctx.mode)
+
+	generatedBuildFiles := []string{}
 	for _, f := range filesToWrite {
-		if err := writeFile(outputDir, ctx, f); err != nil {
+		p := getOrCreateOutputDir(outputDir, ctx, f.Dir).Join(ctx, f.Basename)
+		if err := writeFile(ctx, p, f.Contents); err != nil {
 			fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err)
 		}
+		// if these generated files are modified, regenerate on next run.
+		generatedBuildFiles = append(generatedBuildFiles, p.String())
 	}
 
+	// The MANIFEST file contains the full list of files generated by bp2build, excluding itself.
+	// Its purpose is for downstream tools to understand the set of files converted by bp2build.
+	manifestFile := outputDir.Join(ctx, "MANIFEST")
+	writeFile(ctx, manifestFile, strings.Join(generatedBuildFiles, "\n"))
+	generatedBuildFiles = append(generatedBuildFiles, manifestFile.String())
+
 	return metrics
 }
 
-func writeFile(outputDir android.OutputPath, ctx android.PathContext, f BazelFile) error {
-	return writeReadOnlyFile(ctx, getOutputPath(outputDir, ctx, f.Dir), f.Basename, f.Contents)
+// Get the output directory and create it if it doesn't exist.
+func getOrCreateOutputDir(outputDir android.OutputPath, ctx android.PathContext, dir string) android.OutputPath {
+	dirPath := outputDir.Join(ctx, dir)
+	android.CreateOutputDirIfNonexistent(dirPath, os.ModePerm)
+	return dirPath
 }
 
-func getOutputPath(outputDir android.OutputPath, ctx android.PathContext, dir string) android.OutputPath {
-	return outputDir.Join(ctx, dir)
-}
-
-// The auto-conversion directory should be read-only, sufficient for bazel query. The files
-// are not intended to be edited by end users.
-func writeReadOnlyFile(ctx android.PathContext, dir android.OutputPath, baseName, content string) error {
-	android.CreateOutputDirIfNonexistent(dir, os.ModePerm)
-	pathToFile := dir.Join(ctx, baseName)
-
-	// 0444 is read-only
-	err := android.WriteFileToOutputDir(pathToFile, []byte(content), 0444)
-
-	return err
+func writeFile(ctx android.PathContext, pathToFile android.OutputPath, content string) error {
+	// These files are made editable to allow users to modify and iterate on them
+	// in the source tree.
+	return android.WriteFileToOutputDir(pathToFile, []byte(content), 0644)
 }
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 89acbe9..b9b250a 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -412,7 +412,7 @@
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.RegisterBp2BuildMutator("custom_starlark", customBp2BuildMutatorFromStarlark)
+		ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutatorFromStarlark)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
@@ -1144,7 +1144,7 @@
 	}
 }
 
-func TestAllowlistingBp2buildTargets(t *testing.T) {
+func TestAllowlistingBp2buildTargetsExplicitly(t *testing.T) {
 	testCases := []struct {
 		moduleTypeUnderTest                string
 		moduleTypeUnderTestFactory         android.ModuleFactory
@@ -1222,6 +1222,124 @@
 	}
 }
 
+func TestAllowlistingBp2buildTargetsWithConfig(t *testing.T) {
+	testCases := []struct {
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator bp2buildMutator
+		expectedCount                      map[string]int
+		description                        string
+		bp2buildConfig                     android.Bp2BuildConfig
+		checkDir                           string
+		fs                                 map[string]string
+	}{
+		{
+			description:                        "test bp2build config package and subpackages config",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			expectedCount: map[string]int{
+				"migrated":                           1,
+				"migrated/but_not_really":            0,
+				"migrated/but_not_really/but_really": 1,
+				"not_migrated":                       0,
+				"also_not_migrated":                  0,
+			},
+			bp2buildConfig: android.Bp2BuildConfig{
+				"migrated":                android.Bp2BuildDefaultTrueRecursively,
+				"migrated/but_not_really": android.Bp2BuildDefaultFalse,
+				"not_migrated":            android.Bp2BuildDefaultFalse,
+			},
+			fs: map[string]string{
+				"migrated/Android.bp":                           `filegroup { name: "a" }`,
+				"migrated/but_not_really/Android.bp":            `filegroup { name: "b" }`,
+				"migrated/but_not_really/but_really/Android.bp": `filegroup { name: "c" }`,
+				"not_migrated/Android.bp":                       `filegroup { name: "d" }`,
+				"also_not_migrated/Android.bp":                  `filegroup { name: "e" }`,
+			},
+		},
+		{
+			description:                        "test bp2build config opt-in and opt-out",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			expectedCount: map[string]int{
+				"package-opt-in":             2,
+				"package-opt-in/subpackage":  0,
+				"package-opt-out":            1,
+				"package-opt-out/subpackage": 0,
+			},
+			bp2buildConfig: android.Bp2BuildConfig{
+				"package-opt-in":  android.Bp2BuildDefaultFalse,
+				"package-opt-out": android.Bp2BuildDefaultTrueRecursively,
+			},
+			fs: map[string]string{
+				"package-opt-in/Android.bp": `
+filegroup { name: "opt-in-a" }
+filegroup { name: "opt-in-b", bazel_module: { bp2build_available: true } }
+filegroup { name: "opt-in-c", bazel_module: { bp2build_available: true } }
+`,
+
+				"package-opt-in/subpackage/Android.bp": `
+filegroup { name: "opt-in-d" } // parent package not configured to DefaultTrueRecursively
+`,
+
+				"package-opt-out/Android.bp": `
+filegroup { name: "opt-out-a" }
+filegroup { name: "opt-out-b", bazel_module: { bp2build_available: false } }
+filegroup { name: "opt-out-c", bazel_module: { bp2build_available: false } }
+`,
+
+				"package-opt-out/subpackage/Android.bp": `
+filegroup { name: "opt-out-g", bazel_module: { bp2build_available: false } }
+filegroup { name: "opt-out-h", bazel_module: { bp2build_available: false } }
+`,
+			},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		fs := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.fs {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			fs[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, "", fs)
+		ctx := android.NewTestContext(config)
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterBp2BuildConfig(testCase.bp2buildConfig)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		android.FailIfErrored(t, errs)
+		_, errs = ctx.ResolveDependencies(config)
+		android.FailIfErrored(t, errs)
+
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+
+		// For each directory, test that the expected number of generated targets is correct.
+		for dir, expectedCount := range testCase.expectedCount {
+			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			if actualCount := len(bazelTargets); actualCount != expectedCount {
+				t.Fatalf(
+					"%s: Expected %d bazel target for %s package, got %d",
+					testCase.description,
+					expectedCount,
+					dir,
+					actualCount)
+			}
+
+		}
+	}
+}
+
 func TestCombineBuildFilesBp2buildTargets(t *testing.T) {
 	testCases := []struct {
 		description                        string
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 7877bb8..787222d 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -21,14 +21,15 @@
 	mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(ruleShims)+len(buildToTargets)+numAdditionalFiles)
 
-	// Write top level files: WORKSPACE and BUILD. These files are empty.
+	// Write top level files: WORKSPACE. These files are empty.
 	files = append(files, newFile("", "WORKSPACE", ""))
-	// Used to denote that the top level directory is a package.
-	files = append(files, newFile("", GeneratedBuildFileName, ""))
-
-	files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, ""))
 
 	if mode == QueryView {
+		// Used to denote that the top level directory is a package.
+		files = append(files, newFile("", GeneratedBuildFileName, ""))
+
+		files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, ""))
+
 		// These files are only used for queryview.
 		files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))
 
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index ec5f27e..a115ddc 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -89,16 +89,8 @@
 	expectedFilePaths := []filepath{
 		{
 			dir:      "",
-			basename: "BUILD",
-		},
-		{
-			dir:      "",
 			basename: "WORKSPACE",
 		},
-		{
-			dir:      bazelRulesSubDir,
-			basename: "BUILD",
-		},
 	}
 
 	assertFilecountsAreEqual(t, files, expectedFilePaths)
diff --git a/bp2build/testing.go b/bp2build/testing.go
index a15a4a5..ede8044 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -126,7 +126,7 @@
 
 func customBp2BuildMutator(ctx android.TopDownMutatorContext) {
 	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build() {
+		if !m.ConvertWithBp2build(ctx) {
 			return
 		}
 
@@ -147,7 +147,7 @@
 // module to target.
 func customBp2BuildMutatorFromStarlark(ctx android.TopDownMutatorContext) {
 	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build() {
+		if !m.ConvertWithBp2build(ctx) {
 			return
 		}
 
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index d083d2a..864fba1 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -46,7 +46,6 @@
 	arm64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
 		"-Wl,-z,separate-code",
-		"-Wl,--icf=safe",
 	}
 
 	arm64Lldflags = append(ClangFilterUnknownLldflags(arm64Ldflags),
@@ -128,10 +127,10 @@
 
 var (
 	arm64ClangArchVariantCflagsVar = map[string]string{
-		"armv8-a":  "${config.Arm64ClangArmv8ACflags}",
+		"armv8-a":            "${config.Arm64ClangArmv8ACflags}",
 		"armv8-a-branchprot": "${config.Arm64ClangArmv8ABranchProtCflags}",
-		"armv8-2a": "${config.Arm64ClangArmv82ACflags}",
-		"armv8-2a-dotprod": "${config.Arm64ClangArmv82ADotprodCflags}",
+		"armv8-2a":           "${config.Arm64ClangArmv82ACflags}",
+		"armv8-2a-dotprod":   "${config.Arm64ClangArmv82ADotprodCflags}",
 	}
 
 	arm64ClangCpuVariantCflagsVar = map[string]string{
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index f01c638..a402f8f 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -34,7 +34,6 @@
 	armCppflags = []string{}
 
 	armLdflags = []string{
-		"-Wl,--icf=safe",
 		"-Wl,--hash-style=gnu",
 		"-Wl,-m,armelf",
 	}
diff --git a/cc/config/global.go b/cc/config/global.go
index 7e80900..ed18300 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -97,6 +97,7 @@
 		"-Wl,--exclude-libs,libgcc_stripped.a",
 		"-Wl,--exclude-libs,libunwind_llvm.a",
 		"-Wl,--exclude-libs,libunwind.a",
+		"-Wl,--icf=safe",
 	}
 
 	deviceGlobalLldflags = append(ClangFilterUnknownLldflags(deviceGlobalLdflags),
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 115b775..0f4d8a6 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -86,7 +86,7 @@
 		return
 	}
 
-	if !module.ConvertWithBp2build() {
+	if !module.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/cc/object.go b/cc/object.go
index 6bea28b..664be8d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -131,7 +131,7 @@
 // Bazel equivalent target, plus any necessary include deps for the cc_object.
 func ObjectBp2Build(ctx android.TopDownMutatorContext) {
 	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build() {
+	if !ok || !m.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 11d3620..e2fc78c 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -17,11 +17,14 @@
 import (
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
 	"android/soong/shared"
+
 	"github.com/google/blueprint/bootstrap"
 
 	"android/soong/android"
@@ -95,11 +98,15 @@
 	android.InitSandbox(topDir)
 	android.InitEnvironment(shared.JoinPath(topDir, outDir, "soong.environment.available"))
 
+	usedVariablesFile := shared.JoinPath(outDir, "soong.environment.used")
 	// The top-level Blueprints file is passed as the first argument.
 	srcDir := filepath.Dir(flag.Arg(0))
 	var ctx *android.Context
 	configuration := newConfig(srcDir)
-	extraNinjaDeps := []string{configuration.ProductVariablesFileName}
+	extraNinjaDeps := []string{
+		configuration.ProductVariablesFileName,
+		shared.JoinPath(outDir, "soong.environment.used"),
+	}
 
 	if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
 		configuration.SetAllowMissingDependencies()
@@ -115,15 +122,12 @@
 		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
 	}
 
-	if bazelConversionRequested(configuration) {
+	bazelConversionRequested := bazelConversionRequested(configuration)
+	if bazelConversionRequested {
 		// Run the alternate pipeline of bp2build mutators and singleton to convert Blueprint to BUILD files
 		// before everything else.
-		runBp2Build(srcDir, configuration)
-		// Short-circuit and return.
-		return
-	}
-
-	if configuration.BazelContext.BazelEnabled() {
+		runBp2Build(srcDir, configuration, extraNinjaDeps)
+	} else if configuration.BazelContext.BazelEnabled() {
 		// Bazel-enabled mode. Soong runs in two passes.
 		// First pass: Analyze the build tree, but only store all bazel commands
 		// needed to correctly evaluate the tree in the second pass.
@@ -151,7 +155,7 @@
 	}
 
 	// Convert the Soong module graph into Bazel BUILD files.
-	if bazelQueryViewDir != "" {
+	if !bazelConversionRequested && bazelQueryViewDir != "" {
 		// Run the code-generation phase to convert BazelTargetModules to BUILD files.
 		codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
 		absoluteQueryViewDir := shared.JoinPath(topDir, bazelQueryViewDir)
@@ -161,7 +165,7 @@
 		}
 	}
 
-	if docFile != "" {
+	if !bazelConversionRequested && docFile != "" {
 		if err := writeDocs(ctx, configuration, docFile); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
 			os.Exit(1)
@@ -170,7 +174,7 @@
 
 	// TODO(ccross): make this a command line argument.  Requires plumbing through blueprint
 	//  to affect the command line of the primary builder.
-	if shouldPrepareBuildActions(configuration) {
+	if !bazelConversionRequested && shouldPrepareBuildActions(configuration) {
 		metricsFile := filepath.Join(bootstrap.CmdlineBuildDir(), "soong_build_metrics.pb")
 		err := android.WriteMetrics(configuration, metricsFile)
 		if err != nil {
@@ -178,12 +182,43 @@
 			os.Exit(1)
 		}
 	}
+
+	if docFile == "" {
+		// Let's not overwrite the used variables file when generating
+		// documentation
+		writeUsedVariablesFile(shared.JoinPath(topDir, usedVariablesFile), configuration)
+	}
+}
+
+func writeUsedVariablesFile(path string, configuration android.Config) {
+	data, err := shared.EnvFileContents(configuration.EnvDeps())
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error writing used variables file %s: %s\n", path, err)
+		os.Exit(1)
+	}
+
+	err = ioutil.WriteFile(path, data, 0666)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error writing used variables file %s: %s\n", path, err)
+		os.Exit(1)
+	}
+
+	// Touch the output Ninja file so that it's not older than the file we just
+	// wrote. We can't write the environment file earlier because one an access
+	// new environment variables while writing it.
+	outputNinjaFile := shared.JoinPath(topDir, bootstrap.CmdlineOutFile())
+	currentTime := time.Now().Local()
+	err = os.Chtimes(outputNinjaFile, currentTime, currentTime)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error touching output file %s: %s\n", outputNinjaFile, err)
+		os.Exit(1)
+	}
 }
 
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
-func runBp2Build(srcDir string, configuration android.Config) {
+func runBp2Build(srcDir string, configuration android.Config, extraNinjaDeps []string) {
 	// Register an alternate set of singletons and mutators for bazel
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
@@ -198,11 +233,13 @@
 	// configurations or variables, since those will generate different BUILD
 	// files based on how the user has configured their tree.
 	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineModuleListFile())
-	extraNinjaDeps, err := bp2buildCtx.ListModulePaths(srcDir)
+	modulePaths, err := bp2buildCtx.ListModulePaths(srcDir)
 	if err != nil {
 		panic(err)
 	}
 
+	extraNinjaDeps = append(extraNinjaDeps, modulePaths...)
+
 	// Run the loading and analysis pipeline to prepare the graph of regular
 	// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 	// from the regular Modules.
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 888466a..02f1120 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -33,6 +33,8 @@
 
 	OnlyPreoptBootImageAndSystemServer bool // only preopt jars in the boot image or system server
 
+	PreoptWithUpdatableBcp bool // If updatable boot jars are included in dexpreopt or not.
+
 	UseArtImage bool // use the art image (use other boot class path dex files without image)
 
 	HasSystemOther        bool     // store odex files that match PatternsOnSystemOther on the system_other partition
@@ -352,9 +354,23 @@
 	}
 }
 
-var Dex2oatDepTag = struct {
+type dex2oatDependencyTag struct {
 	blueprint.BaseDependencyTag
-}{}
+}
+
+func (d dex2oatDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+func (d dex2oatDependencyTag) ExcludeFromApexContents() {
+}
+
+// Dex2oatDepTag represents the dependency onto the dex2oatd module. It is added to any module that
+// needs dexpreopting and so it makes no sense for it to be checked for visibility or included in
+// the apex.
+var Dex2oatDepTag = dex2oatDependencyTag{}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = Dex2oatDepTag
+var _ android.ExcludeFromApexContentsTag = Dex2oatDepTag
 
 // RegisterToolDeps adds the necessary dependencies to binary modules for tools
 // that are required later when Get(Cached)GlobalSoongConfig is called. It
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index 8e90295..c0ba5ca 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -15,39 +15,78 @@
 package dexpreopt
 
 import (
+	"fmt"
+
 	"android/soong/android"
 )
 
-type dummyToolBinary struct {
+type fakeToolBinary struct {
 	android.ModuleBase
 }
 
-func (m *dummyToolBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+func (m *fakeToolBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
 
-func (m *dummyToolBinary) HostToolPath() android.OptionalPath {
+func (m *fakeToolBinary) HostToolPath() android.OptionalPath {
 	return android.OptionalPathForPath(android.PathForTesting("dex2oat"))
 }
 
-func dummyToolBinaryFactory() android.Module {
-	module := &dummyToolBinary{}
+func fakeToolBinaryFactory() android.Module {
+	module := &fakeToolBinary{}
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
 }
 
 func RegisterToolModulesForTest(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("dummy_tool_binary", dummyToolBinaryFactory)
+	ctx.RegisterModuleType("fake_tool_binary", fakeToolBinaryFactory)
 }
 
 func BpToolModulesForTest() string {
 	return `
-		dummy_tool_binary {
+		fake_tool_binary {
 			name: "dex2oatd",
 		}
 	`
 }
 
-// Prepares a test fixture by enabling dexpreopt.
-var PrepareForTestWithDexpreopt = FixtureModifyGlobalConfig(func(*GlobalConfig) {})
+func CompatLibDefinitionsForTest() string {
+	bp := ""
+
+	// For class loader context and <uses-library> tests.
+	dexpreoptModules := []string{"android.test.runner"}
+	dexpreoptModules = append(dexpreoptModules, CompatUsesLibs...)
+	dexpreoptModules = append(dexpreoptModules, OptionalCompatUsesLibs...)
+
+	for _, extra := range dexpreoptModules {
+		bp += fmt.Sprintf(`
+			java_library {
+				name: "%s",
+				srcs: ["a.java"],
+				sdk_version: "none",
+				system_modules: "stable-core-platform-api-stubs-system-modules",
+				compile_dex: true,
+				installable: true,
+			}
+		`, extra)
+	}
+
+	return bp
+}
+
+var PrepareForTestWithDexpreoptCompatLibs = android.GroupFixturePreparers(
+	android.FixtureAddFile("defaults/dexpreopt/compat/a.java", nil),
+	android.FixtureAddTextFile("defaults/dexpreopt/compat/Android.bp", CompatLibDefinitionsForTest()),
+)
+
+var PrepareForTestWithFakeDex2oatd = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(RegisterToolModulesForTest),
+	android.FixtureAddTextFile("defaults/dexpreopt/Android.bp", BpToolModulesForTest()),
+)
+
+// Prepares a test fixture by enabling dexpreopt, registering the fake_tool_binary module type and
+// using that to define the `dex2oatd` module.
+var PrepareForTestByEnablingDexpreopt = android.GroupFixturePreparers(
+	FixtureModifyGlobalConfig(func(*GlobalConfig) {}),
+)
 
 // FixtureModifyGlobalConfig enables dexpreopt (unless modified by the mutator) and modifies the
 // configuration.
@@ -78,3 +117,17 @@
 		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
+
+// FixtureSetUpdatableBootJars sets the UpdatableBootJars property in the global config.
+func FixtureSetUpdatableBootJars(bootJars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.UpdatableBootJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
+
+// FixtureSetPreoptWithUpdatableBcp sets the PreoptWithUpdatableBcp property in the global config.
+func FixtureSetPreoptWithUpdatableBcp(value bool) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.PreoptWithUpdatableBcp = value
+	})
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index b43f28e..cb841ed 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -820,7 +820,7 @@
 
 func GenruleBp2Build(ctx android.TopDownMutatorContext) {
 	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build() {
+	if !ok || !m.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index bb17e8e..2ee456d 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -36,6 +36,7 @@
 	PrepareForTestWithGenRuleBuildComponents,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
 		ctx.RegisterModuleType("tool", toolFactory)
+		ctx.RegisterModuleType("output", outputProducerFactory)
 	}),
 	android.FixtureMergeMockFs(android.MockFS{
 		"tool":       nil,
@@ -653,6 +654,35 @@
 	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs)
 }
 
+func TestGenruleAllowMissingDependencies(t *testing.T) {
+	bp := `
+		output {
+			name: "disabled",
+			enabled: false,
+		}
+
+		genrule {
+			name: "gen",
+			srcs: [
+				":disabled",
+			],
+			out: ["out"],
+			cmd: "cat $(in) > $(out)",
+		}
+       `
+	result := prepareForGenRuleTest.Extend(
+		android.FixtureModifyConfigAndContext(
+			func(config android.Config, ctx *android.TestContext) {
+				config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
+				ctx.SetAllowMissingDependencies(true)
+			})).RunTestWithBp(t, bp)
+
+	gen := result.ModuleForTests("gen", "").Output("out")
+	if gen.Rule != android.ErrorRule {
+		t.Errorf("Expected missing dependency error rule for gen, got %q", gen.Rule.String())
+	}
+}
+
 func TestGenruleWithBazel(t *testing.T) {
 	bp := `
 		genrule {
@@ -697,3 +727,24 @@
 }
 
 var _ android.HostToolProvider = (*testTool)(nil)
+
+type testOutputProducer struct {
+	android.ModuleBase
+	outputFile android.Path
+}
+
+func outputProducerFactory() android.Module {
+	module := &testOutputProducer{}
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	return module
+}
+
+func (t *testOutputProducer) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
+}
+
+func (t *testOutputProducer) OutputFiles(tag string) (android.Paths, error) {
+	return android.Paths{t.outputFile}, nil
+}
+
+var _ android.OutputFileProducer = (*testOutputProducer)(nil)
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 3477956..5eaa77b 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -135,7 +135,8 @@
 }
 
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo-shared_library", "foo-no_shared_library"),
 	).RunTestWithBp(t, `
@@ -171,7 +172,7 @@
 }
 
 func TestImportSoongDexJar(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		java_import {
 			name: "my-java-import",
 			jars: ["a.jar"],
@@ -180,14 +181,10 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("my-java-import", "android_common").Module()
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
-	expectedSoongDexJar := buildDir + "/.intermediates/my-java-import/android_common/dex/my-java-import.jar"
+	mod := result.Module("my-java-import", "android_common")
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
+	expectedSoongDexJar := "out/soong/.intermediates/my-java-import/android_common/dex/my-java-import.jar"
 	actualSoongDexJar := entries.EntryMap["LOCAL_SOONG_DEX_JAR"]
 
-	if len(actualSoongDexJar) != 1 {
-		t.Errorf("LOCAL_SOONG_DEX_JAR incorrect len %d", len(actualSoongDexJar))
-	} else if actualSoongDexJar[0] != expectedSoongDexJar {
-		t.Errorf("LOCAL_SOONG_DEX_JAR mismatch, actual: %s, expected: %s", actualSoongDexJar[0], expectedSoongDexJar)
-	}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_SOONG_DEX_JAR", result.Config, []string{expectedSoongDexJar}, actualSoongDexJar)
 }
diff --git a/java/app_import_test.go b/java/app_import_test.go
index cae41d0..147ae45 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -252,14 +252,15 @@
 
 	jniRuleRe := regexp.MustCompile("^if \\(zipinfo (\\S+)")
 	for _, test := range testCases {
-		config := testAppConfig(nil, bp, nil)
-		config.TestProductVariables.AAPTPreferredConfig = test.aaptPreferredConfig
-		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-		ctx := testContext(config)
+		result := android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.AAPTPreferredConfig = test.aaptPreferredConfig
+				variables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
+			}),
+		).RunTestWithBp(t, bp)
 
-		run(t, ctx, config)
-
-		variant := ctx.ModuleForTests("foo", "android_common")
+		variant := result.ModuleForTests("foo", "android_common")
 		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
 		matches := jniRuleRe.FindStringSubmatch(jniRuleCommand)
 		if len(matches) != 2 {
@@ -456,12 +457,9 @@
 		t.Errorf("prebuilt framework-res is not preprocessed")
 	}
 
-	expectedInstallPath := buildDir + "/target/product/test_device/system/framework/framework-res.apk"
+	expectedInstallPath := "out/soong/target/product/test_device/system/framework/framework-res.apk"
 
-	if a.dexpreopter.installPath.String() != expectedInstallPath {
-		t.Errorf("prebuilt framework-res installed to incorrect location, actual: %s, expected: %s", a.dexpreopter.installPath, expectedInstallPath)
-
-	}
+	android.AssertPathRelativeToTopEquals(t, "prebuilt framework-res install location", expectedInstallPath, a.dexpreopter.installPath)
 
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 
diff --git a/java/app_set_test.go b/java/app_set_test.go
index ab55758..adaf71b 100644
--- a/java/app_set_test.go
+++ b/java/app_set_test.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"reflect"
 	"testing"
 
@@ -96,20 +97,24 @@
 	}
 
 	for _, test := range testCases {
-		config := testAppConfig(nil, bp, nil)
-		config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-		config.TestProductVariables.Platform_sdk_version = &test.sdkVersion
-		config.Targets[android.Android] = test.targets
-		ctx := testContext(config)
-		run(t, ctx, config)
+		ctx := android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
+				variables.Platform_sdk_version = &test.sdkVersion
+			}),
+			android.FixtureModifyConfig(func(config android.Config) {
+				config.Targets[android.Android] = test.targets
+			}),
+		).RunTestWithBp(t, bp)
+
 		module := ctx.ModuleForTests("foo", "android_common")
 		const packedSplitApks = "foo.zip"
 		params := module.Output(packedSplitApks)
 		for k, v := range test.expected {
-			if actual := params.Args[k]; actual != v {
-				t.Errorf("%s: bad build arg value for '%s': '%s', expected '%s'",
-					test.name, k, actual, v)
-			}
+			t.Run(test.name, func(t *testing.T) {
+				android.AssertStringEquals(t, fmt.Sprintf("arg value for `%s`", k), v, params.Args[k])
+			})
 		}
 	}
 }
diff --git a/java/app_test.go b/java/app_test.go
index 7168a96..825ad20 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -26,25 +26,18 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
+	"android/soong/genrule"
 )
 
-// testAppConfig is a legacy way of creating a test Config for testing java app modules.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func testAppConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
-	return testConfig(env, bp, fs)
-}
-
-// testApp runs tests using the javaFixtureFactory
+// testApp runs tests using the prepareForJavaTest
 //
 // See testJava for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testApp(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
-	result := javaFixtureFactory.RunTestWithBp(t, bp)
+	result := prepareForJavaTest.RunTestWithBp(t, bp)
 	return result.TestContext
 }
 
@@ -63,7 +56,8 @@
 
 	for _, moduleType := range []string{"android_app", "android_library"} {
 		t.Run(moduleType, func(t *testing.T) {
-			result := javaFixtureFactory.Extend(
+			result := android.GroupFixturePreparers(
+				prepareForJavaTest,
 				android.FixtureModifyMockFS(func(fs android.MockFS) {
 					for _, file := range resourceFiles {
 						fs[file] = nil
@@ -118,9 +112,9 @@
 	foo := ctx.ModuleForTests("foo", "android_common")
 
 	expectedOutputs := []string{
-		filepath.Join(buildDir, ".intermediates/foo/android_common/foo.apk"),
-		filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v4.apk"),
-		filepath.Join(buildDir, ".intermediates/foo/android_common/foo_v7_hdpi.apk"),
+		"out/soong/.intermediates/foo/android_common/foo.apk",
+		"out/soong/.intermediates/foo/android_common/foo_v4.apk",
+		"out/soong/.intermediates/foo/android_common/foo_v7_hdpi.apk",
 	}
 	for _, expectedOutput := range expectedOutputs {
 		foo.Output(expectedOutput)
@@ -130,9 +124,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	if g, w := outputFiles.Strings(), expectedOutputs; !reflect.DeepEqual(g, w) {
-		t.Errorf(`want OutputFiles("") = %q, got %q`, w, g)
-	}
+	android.AssertPathsRelativeToTopEquals(t, `OutputFiles("")`, expectedOutputs, outputFiles)
 }
 
 func TestPlatformAPIs(t *testing.T) {
@@ -374,8 +366,8 @@
 			if test.expectedError != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
 			}
-			javaFixtureFactory.
-				Extend(FixtureWithPrebuiltApis(map[string][]string{
+			android.GroupFixturePreparers(
+				prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{
 					"29": {"foo"},
 				})).
 				ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, test.bp)
@@ -446,7 +438,7 @@
 		"prebuilts/ndk/current/platforms/android-29/arch-arm/usr/lib/crtend_so.o":     nil,
 	}
 
-	ctx, _ := testJavaWithConfig(t, testConfig(nil, bp, fs))
+	ctx, _ := testJavaWithFS(t, bp, fs)
 
 	inputs := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").Description("link").Implicits
 	var crtbeginFound, crtendFound bool
@@ -547,7 +539,7 @@
 		},
 	}
 
-	fs := map[string][]byte{
+	fs := android.MockFS{
 		"res/res/values/strings.xml": nil,
 	}
 
@@ -561,11 +553,13 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			config := testConfig(nil, fmt.Sprintf(bp, testCase.prop), fs)
-			ctx := testContext(config)
-			run(t, ctx, config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithOverlayBuildComponents,
+				fs.AddToFixture(),
+			).RunTestWithBp(t, fmt.Sprintf(bp, testCase.prop))
 
-			module := ctx.ModuleForTests("foo", "android_common")
+			module := result.ModuleForTests("foo", "android_common")
 			resourceList := module.MaybeOutput("aapt2/res.list")
 
 			var resources []string
@@ -575,10 +569,7 @@
 				}
 			}
 
-			if !reflect.DeepEqual(resources, testCase.resources) {
-				t.Errorf("expected resource files %q, got %q",
-					testCase.resources, resources)
-			}
+			android.AssertDeepEquals(t, "resource files", testCase.resources, resources)
 		})
 	}
 }
@@ -624,9 +615,9 @@
 			name: "foo",
 			// lib1 has its own asset. lib3 doesn't have any, but provides lib4's transitively.
 			assetPackages: []string{
-				buildDir + "/.intermediates/foo/android_common/aapt2/package-res.apk",
-				buildDir + "/.intermediates/lib1/android_common/assets.zip",
-				buildDir + "/.intermediates/lib3/android_common/assets.zip",
+				"out/soong/.intermediates/foo/android_common/aapt2/package-res.apk",
+				"out/soong/.intermediates/lib1/android_common/assets.zip",
+				"out/soong/.intermediates/lib3/android_common/assets.zip",
 			},
 		},
 		{
@@ -639,8 +630,8 @@
 		{
 			name: "lib3",
 			assetPackages: []string{
-				buildDir + "/.intermediates/lib3/android_common/aapt2/package-res.apk",
-				buildDir + "/.intermediates/lib4/android_common/assets.zip",
+				"out/soong/.intermediates/lib3/android_common/aapt2/package-res.apk",
+				"out/soong/.intermediates/lib4/android_common/assets.zip",
 			},
 		},
 		{
@@ -661,24 +652,18 @@
 			} else {
 				aapt2link = m.Output("package-res.apk")
 			}
+			aapt2link = aapt2link.RelativeToTop()
 			aapt2Flags := aapt2link.Args["flags"]
 			if test.assetFlag != "" {
-				if !strings.Contains(aapt2Flags, test.assetFlag) {
-					t.Errorf("Can't find asset flag %q in aapt2 link flags %q", test.assetFlag, aapt2Flags)
-				}
+				android.AssertStringDoesContain(t, "asset flag", aapt2Flags, test.assetFlag)
 			} else {
-				if strings.Contains(aapt2Flags, " -A ") {
-					t.Errorf("aapt2 link flags %q contain unexpected asset flag", aapt2Flags)
-				}
+				android.AssertStringDoesNotContain(t, "aapt2 link flags", aapt2Flags, " -A ")
 			}
 
 			// Check asset merge rule.
 			if len(test.assetPackages) > 0 {
 				mergeAssets := m.Output("package-res.apk")
-				if !reflect.DeepEqual(test.assetPackages, mergeAssets.Inputs.Strings()) {
-					t.Errorf("Unexpected mergeAssets inputs: %v, expected: %v",
-						mergeAssets.Inputs.Strings(), test.assetPackages)
-				}
+				android.AssertPathsRelativeToTopEquals(t, "mergeAssets inputs", test.assetPackages, mergeAssets.Inputs)
 			}
 		})
 	}
@@ -750,9 +735,9 @@
 			},
 			overlayFiles: map[string][]string{
 				"foo": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib/android_common/package-res.apk",
+					"out/soong/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 					"device/vendor/blah/overlay/foo/res/values/strings.xml",
@@ -763,7 +748,7 @@
 					"device/vendor/blah/overlay/bar/res/values/strings.xml",
 				},
 				"lib": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
 					"lib/res/res/values/strings.xml",
 					"device/vendor/blah/overlay/lib/res/values/strings.xml",
 				},
@@ -785,9 +770,9 @@
 			},
 			overlayFiles: map[string][]string{
 				"foo": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib/android_common/package-res.apk",
+					"out/soong/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 				},
@@ -796,7 +781,7 @@
 					"device/vendor/blah/overlay/bar/res/values/strings.xml",
 				},
 				"lib": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
 					"lib/res/res/values/strings.xml",
 				},
 			},
@@ -827,15 +812,15 @@
 			},
 			overlayFiles: map[string][]string{
 				"foo": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib/android_common/package-res.apk",
-					buildDir + "/.intermediates/lib3/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib/android_common/package-res.apk",
+					"out/soong/.intermediates/lib3/android_common/package-res.apk",
 					"foo/res/res/values/strings.xml",
 					"device/vendor/blah/static_overlay/foo/res/values/strings.xml",
 				},
 				"bar": {"device/vendor/blah/static_overlay/bar/res/values/strings.xml"},
 				"lib": {
-					buildDir + "/.intermediates/lib2/android_common/package-res.apk",
+					"out/soong/.intermediates/lib2/android_common/package-res.apk",
 					"lib/res/res/values/strings.xml",
 				},
 			},
@@ -862,7 +847,7 @@
 		"product/vendor/blah/overlay",
 	}
 
-	fs := map[string][]byte{
+	fs := android.MockFS{
 		"foo/res/res/values/strings.xml":                               nil,
 		"bar/res/res/values/strings.xml":                               nil,
 		"lib/res/res/values/strings.xml":                               nil,
@@ -913,18 +898,21 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			config := testAppConfig(nil, bp, fs)
-			config.TestProductVariables.DeviceResourceOverlays = deviceResourceOverlays
-			config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
-			if testCase.enforceRROTargets != nil {
-				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
-			}
-			if testCase.enforceRROExcludedOverlays != nil {
-				config.TestProductVariables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays
-			}
-
-			ctx := testContext(config)
-			run(t, ctx, config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithOverlayBuildComponents,
+				fs.AddToFixture(),
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = deviceResourceOverlays
+					variables.ProductResourceOverlays = productResourceOverlays
+					if testCase.enforceRROTargets != nil {
+						variables.EnforceRROTargets = testCase.enforceRROTargets
+					}
+					if testCase.enforceRROExcludedOverlays != nil {
+						variables.EnforceRROExcludedOverlays = testCase.enforceRROExcludedOverlays
+					}
+				}),
+			).RunTestWithBp(t, bp)
 
 			resourceListToFiles := func(module android.TestingModule, list []string) (files []string) {
 				for _, o := range list {
@@ -942,14 +930,14 @@
 			}
 
 			getResources := func(moduleName string) (resourceFiles, overlayFiles, rroDirs []string) {
-				module := ctx.ModuleForTests(moduleName, "android_common")
+				module := result.ModuleForTests(moduleName, "android_common")
 				resourceList := module.MaybeOutput("aapt2/res.list")
 				if resourceList.Rule != nil {
-					resourceFiles = resourceListToFiles(module, resourceList.Inputs.Strings())
+					resourceFiles = resourceListToFiles(module, android.PathsRelativeToTop(resourceList.Inputs))
 				}
 				overlayList := module.MaybeOutput("aapt2/overlay.list")
 				if overlayList.Rule != nil {
-					overlayFiles = resourceListToFiles(module, overlayList.Inputs.Strings())
+					overlayFiles = resourceListToFiles(module, android.PathsRelativeToTop(overlayList.Inputs))
 				}
 
 				for _, d := range module.Module().(AndroidLibraryDependency).ExportedRRODirs() {
@@ -961,7 +949,7 @@
 					} else {
 						t.Fatalf("Unexpected overlayType %d", d.overlayType)
 					}
-					rroDirs = append(rroDirs, prefix+d.path.String())
+					rroDirs = append(rroDirs, prefix+android.PathRelativeToTop(d.path))
 				}
 
 				return resourceFiles, overlayFiles, rroDirs
@@ -1077,7 +1065,8 @@
 					%s
 				}`, moduleType, test.sdkVersion, platformApiProp)
 
-				result := javaFixtureFactory.Extend(
+				result := android.GroupFixturePreparers(
+					prepareForJavaTest,
 					android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 						variables.Platform_sdk_version = &test.platformSdkInt
 						variables.Platform_sdk_codename = &test.platformSdkCodename
@@ -1145,7 +1134,8 @@
 						vendor: true,
 					}`, moduleType, sdkKind, test.sdkVersion)
 
-					result := javaFixtureFactory.Extend(
+					result := android.GroupFixturePreparers(
+						prepareForJavaTest,
 						android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 							variables.Platform_sdk_version = &test.platformSdkInt
 							variables.Platform_sdk_codename = &test.platformSdkCodename
@@ -1269,13 +1259,19 @@
 			}
 		`
 
-		config := testAppConfig(nil, bp, nil)
-		config.TestProductVariables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce)
+		errorHandler := android.FixtureExpectsNoErrors
 		if enforce {
-			testJavaErrorWithConfig(t, "sdk_version must have a value when the module is located at vendor or product", config)
-		} else {
-			testJavaWithConfig(t, config)
+			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern("sdk_version must have a value when the module is located at vendor or product")
 		}
+
+		android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce)
+			}),
+		).
+			ExtendWithErrorHandler(errorHandler).
+			RunTestWithBp(t, bp)
 	}
 }
 
@@ -1609,25 +1605,23 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			config := testAppConfig(nil, test.bp, nil)
-			if test.certificateOverride != "" {
-				config.TestProductVariables.CertificateOverrides = []string{test.certificateOverride}
-			}
-			ctx := testContext(config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					if test.certificateOverride != "" {
+						variables.CertificateOverrides = []string{test.certificateOverride}
+					}
+				}),
+			).RunTestWithBp(t, test.bp)
 
-			run(t, ctx, config)
-			foo := ctx.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests("foo", "android_common")
 
 			signapk := foo.Output("foo.apk")
 			signCertificateFlags := signapk.Args["certificates"]
-			if test.expectedCertificate != signCertificateFlags {
-				t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expectedCertificate, signCertificateFlags)
-			}
+			android.AssertStringEquals(t, "certificates flags", test.expectedCertificate, signCertificateFlags)
 
 			signFlags := signapk.Args["flags"]
-			if test.expectedLineage != signFlags {
-				t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expectedLineage, signFlags)
-			}
+			android.AssertStringEquals(t, "signing flags", test.expectedLineage, signFlags)
 		})
 	}
 }
@@ -1677,17 +1671,15 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			config := testAppConfig(nil, test.bp, nil)
-			ctx := testContext(config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+			).RunTestWithBp(t, test.bp)
 
-			run(t, ctx, config)
-			foo := ctx.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests("foo", "android_common")
 
 			signapk := foo.Output("foo.apk")
 			signFlags := signapk.Args["flags"]
-			if test.expected != signFlags {
-				t.Errorf("Incorrect signing flags, expected: %q, got: %q", test.expected, signFlags)
-			}
+			android.AssertStringEquals(t, "signing flags", test.expected, signFlags)
 		})
 	}
 }
@@ -1710,8 +1702,8 @@
 			`,
 			packageNameOverride: "",
 			expected: []string{
-				buildDir + "/.intermediates/foo/android_common/foo.apk",
-				buildDir + "/target/product/test_device/system/app/foo/foo.apk",
+				"out/soong/.intermediates/foo/android_common/foo.apk",
+				"out/soong/target/product/test_device/system/app/foo/foo.apk",
 			},
 		},
 		{
@@ -1726,27 +1718,31 @@
 			packageNameOverride: "foo:bar",
 			expected: []string{
 				// The package apk should be still be the original name for test dependencies.
-				buildDir + "/.intermediates/foo/android_common/bar.apk",
-				buildDir + "/target/product/test_device/system/app/bar/bar.apk",
+				"out/soong/.intermediates/foo/android_common/bar.apk",
+				"out/soong/target/product/test_device/system/app/bar/bar.apk",
 			},
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			config := testAppConfig(nil, test.bp, nil)
-			if test.packageNameOverride != "" {
-				config.TestProductVariables.PackageNameOverrides = []string{test.packageNameOverride}
-			}
-			ctx := testContext(config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					if test.packageNameOverride != "" {
+						variables.PackageNameOverrides = []string{test.packageNameOverride}
+					}
+				}),
+			).RunTestWithBp(t, test.bp)
 
-			run(t, ctx, config)
-			foo := ctx.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests("foo", "android_common")
+
+			outSoongDir := result.Config.BuildDir()
 
 			outputs := foo.AllOutputs()
 			outputMap := make(map[string]bool)
 			for _, o := range outputs {
-				outputMap[o] = true
+				outputMap[android.StringPathRelativeToTop(outSoongDir, o)] = true
 			}
 			for _, e := range test.expected {
 				if _, exist := outputMap[e]; !exist {
@@ -1771,13 +1767,15 @@
 			sdk_version: "current",
 		}
 		`
-	config := testAppConfig(nil, bp, nil)
-	config.TestProductVariables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"}
-	ctx := testContext(config)
 
-	run(t, ctx, config)
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.ManifestPackageNameOverrides = []string{"foo:org.dandroid.bp"}
+		}),
+	).RunTestWithBp(t, bp)
 
-	bar := ctx.ModuleForTests("bar", "android_common")
+	bar := result.ModuleForTests("bar", "android_common")
 	res := bar.Output("package-res.apk")
 	aapt2Flags := res.Args["flags"]
 	e := "--rename-instrumentation-target-package org.dandroid.bp"
@@ -1787,7 +1785,8 @@
 }
 
 func TestOverrideAndroidApp(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
+		t, `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -1862,7 +1861,7 @@
 			name:            "foo",
 			moduleName:      "foo",
 			variantName:     "android_common",
-			apkPath:         "/target/product/test_device/system/app/foo/foo.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/foo/foo.apk",
 			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			lineageFlag:     "",
 			overrides:       []string{"qux"},
@@ -1874,7 +1873,7 @@
 			name:            "foo",
 			moduleName:      "bar",
 			variantName:     "android_common_bar",
-			apkPath:         "/target/product/test_device/system/app/bar/bar.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/bar/bar.apk",
 			certFlag:        "cert/new_cert.x509.pem cert/new_cert.pk8",
 			lineageFlag:     "--lineage lineage.bin",
 			overrides:       []string{"qux", "foo"},
@@ -1886,7 +1885,7 @@
 			name:            "foo",
 			moduleName:      "baz",
 			variantName:     "android_common_baz",
-			apkPath:         "/target/product/test_device/system/app/baz/baz.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/baz/baz.apk",
 			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			lineageFlag:     "",
 			overrides:       []string{"qux", "foo"},
@@ -1898,7 +1897,7 @@
 			name:            "foo",
 			moduleName:      "baz_no_rename_resources",
 			variantName:     "android_common_baz_no_rename_resources",
-			apkPath:         "/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
 			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			lineageFlag:     "",
 			overrides:       []string{"qux", "foo"},
@@ -1910,7 +1909,7 @@
 			name:            "foo_no_rename_resources",
 			moduleName:      "baz_base_no_rename_resources",
 			variantName:     "android_common_baz_base_no_rename_resources",
-			apkPath:         "/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
 			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			lineageFlag:     "",
 			overrides:       []string{"qux", "foo_no_rename_resources"},
@@ -1922,7 +1921,7 @@
 			name:            "foo_no_rename_resources",
 			moduleName:      "baz_override_base_rename_resources",
 			variantName:     "android_common_baz_override_base_rename_resources",
-			apkPath:         "/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
+			apkPath:         "out/soong/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
 			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			lineageFlag:     "",
 			overrides:       []string{"qux", "foo_no_rename_resources"},
@@ -1932,48 +1931,27 @@
 		},
 	}
 	for _, expected := range expectedVariants {
-		variant := ctx.ModuleForTests(expected.name, expected.variantName)
+		variant := result.ModuleForTests(expected.name, expected.variantName)
 
 		// Check the final apk name
-		outputs := variant.AllOutputs()
-		expectedApkPath := buildDir + expected.apkPath
-		found := false
-		for _, o := range outputs {
-			if o == expectedApkPath {
-				found = true
-				break
-			}
-		}
-		if !found {
-			t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
-		}
+		variant.Output(expected.apkPath)
 
 		// Check the certificate paths
 		signapk := variant.Output(expected.moduleName + ".apk")
 		certFlag := signapk.Args["certificates"]
-		if expected.certFlag != certFlag {
-			t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.certFlag, certFlag)
-		}
+		android.AssertStringEquals(t, "certificates flags", expected.certFlag, certFlag)
 
 		// Check the lineage flags
 		lineageFlag := signapk.Args["flags"]
-		if expected.lineageFlag != lineageFlag {
-			t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected.lineageFlag, lineageFlag)
-		}
+		android.AssertStringEquals(t, "signing flags", expected.lineageFlag, lineageFlag)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidApp)
-		if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) {
-			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
-				expected.overrides, mod.appProperties.Overrides)
-		}
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.appProperties.Overrides)
 
 		// Test Overridable property: Logging_parent
 		logging_parent := mod.aapt.LoggingParent
-		if expected.logging_parent != logging_parent {
-			t.Errorf("Incorrect overrides property value for logging parent, expected: %q, got: %q",
-				expected.logging_parent, logging_parent)
-		}
+		android.AssertStringEquals(t, "overrides property value for logging parent", expected.logging_parent, logging_parent)
 
 		// Check the package renaming flag, if exists.
 		res := variant.Output("package-res.apk")
@@ -2015,15 +1993,15 @@
 		`)
 
 	// Verify baz, which depends on the overridden module foo, has the correct classpath javac arg.
-	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
-	fooTurbine := filepath.Join(buildDir, ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar")
+	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac").RelativeToTop()
+	fooTurbine := "out/soong/.intermediates/foo/android_common/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], fooTurbine) {
 		t.Errorf("baz classpath %v does not contain %q", javac.Args["classpath"], fooTurbine)
 	}
 
 	// Verify qux, which depends on the overriding module bar, has the correct classpath javac arg.
-	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac")
-	barTurbine := filepath.Join(buildDir, ".intermediates", "foo", "android_common_bar", "turbine-combined", "foo.jar")
+	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac").RelativeToTop()
+	barTurbine := "out/soong/.intermediates/foo/android_common_bar/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], barTurbine) {
 		t.Errorf("qux classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
 	}
@@ -2089,18 +2067,7 @@
 		variant := ctx.ModuleForTests("foo_test", expected.variantName)
 
 		// Check the final apk name
-		outputs := variant.AllOutputs()
-		expectedApkPath := buildDir + expected.apkPath
-		found := false
-		for _, o := range outputs {
-			if o == expectedApkPath {
-				found = true
-				break
-			}
-		}
-		if !found {
-			t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
-		}
+		variant.Output("out/soong" + expected.apkPath)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidTest)
@@ -2110,8 +2077,8 @@
 		}
 
 		// Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides.
-		javac := variant.Rule("javac")
-		turbine := filepath.Join(buildDir, ".intermediates", "foo", expected.targetVariant, "turbine-combined", "foo.jar")
+		javac := variant.Rule("javac").RelativeToTop()
+		turbine := filepath.Join("out", "soong", ".intermediates", "foo", expected.targetVariant, "turbine-combined", "foo.jar")
 		if !strings.Contains(javac.Args["classpath"], turbine) {
 			t.Errorf("classpath %q does not contain %q", javac.Args["classpath"], turbine)
 		}
@@ -2167,7 +2134,7 @@
 			moduleName:  "bar_test",
 			variantName: "android_common",
 			expectedFlags: []string{
-				"--manifest " + buildDir + "/.intermediates/bar_test/android_common/manifest_fixer/AndroidManifest.xml",
+				"--manifest out/soong/.intermediates/bar_test/android_common/manifest_fixer/AndroidManifest.xml",
 				"--package-name com.android.bar.test",
 			},
 		},
@@ -2175,8 +2142,7 @@
 			moduleName:  "foo_test",
 			variantName: "android_common_baz_test",
 			expectedFlags: []string{
-				"--manifest " + buildDir +
-					"/.intermediates/foo_test/android_common_baz_test/manifest_fixer/AndroidManifest.xml",
+				"--manifest out/soong/.intermediates/foo_test/android_common_baz_test/manifest_fixer/AndroidManifest.xml",
 				"--package-name com.android.baz.test",
 				"--test-file-name baz_test.apk",
 			},
@@ -2185,7 +2151,7 @@
 
 	for _, test := range testCases {
 		variant := ctx.ModuleForTests(test.moduleName, test.variantName)
-		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml")
+		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml").RelativeToTop()
 
 		if len(test.expectedFlags) > 0 {
 			if params.Rule == nil {
@@ -2369,7 +2335,8 @@
 		}
 	`
 
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("runtime-library", "foo", "quuz", "qux", "bar", "fred"),
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2456,6 +2423,66 @@
 			`#PCL[/system/framework/android.test.mock.jar] `)
 }
 
+func TestDexpreoptBcp(t *testing.T) {
+	bp := `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			sdk_version: "current",
+		}
+
+		java_sdk_library {
+			name: "bar",
+			srcs: ["a.java"],
+			api_packages: ["bar"],
+			permitted_packages: ["bar"],
+			sdk_version: "current",
+		}
+
+		android_app {
+			name: "app",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+	`
+
+	testCases := []struct {
+		name   string
+		with   bool
+		expect string
+	}{
+		{
+			name:   "with updatable bcp",
+			with:   true,
+			expect: "/system/framework/foo.jar:/system/framework/bar.jar",
+		},
+		{
+			name:   "without updatable bcp",
+			with:   false,
+			expect: "/system/framework/foo.jar",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				prepareForJavaTest,
+				PrepareForTestWithJavaSdkLibraryFiles,
+				FixtureWithLastReleaseApis("runtime-library", "foo", "bar"),
+				dexpreopt.FixtureSetBootJars("platform:foo"),
+				dexpreopt.FixtureSetUpdatableBootJars("platform:bar"),
+				dexpreopt.FixtureSetPreoptWithUpdatableBcp(test.with),
+			).RunTestWithBp(t, bp)
+
+			app := result.ModuleForTests("app", "android_common")
+			cmd := app.Rule("dexpreopt").RuleParams.Command
+			bcp := " -Xbootclasspath-locations:" + test.expect + " " // space at the end matters
+			android.AssertStringDoesContain(t, "dexpreopt app bcp", cmd, bcp)
+		})
+	}
+}
+
 func TestCodelessApp(t *testing.T) {
 	testCases := []struct {
 		name   string
@@ -2533,7 +2560,17 @@
 }
 
 func TestEmbedNotice(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, cc.GatherRequiredDepsForTest(android.Android)+`
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		cc.PrepareForTestWithCcDefaultModules,
+		genrule.PrepareForTestWithGenRuleBuildComponents,
+		android.MockFS{
+			"APP_NOTICE":     nil,
+			"GENRULE_NOTICE": nil,
+			"LIB_NOTICE":     nil,
+			"TOOL_NOTICE":    nil,
+		}.AddToFixture(),
+	).RunTestWithBp(t, `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -2589,15 +2626,10 @@
 			srcs: ["b.java"],
 			notice: "TOOL_NOTICE",
 		}
-	`, map[string][]byte{
-		"APP_NOTICE":     nil,
-		"GENRULE_NOTICE": nil,
-		"LIB_NOTICE":     nil,
-		"TOOL_NOTICE":    nil,
-	})
+	`)
 
 	// foo has NOTICE files to process, and embed_notices is true.
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests("foo", "android_common")
 	// verify merge notices rule.
 	mergeNotices := foo.Rule("mergeNoticesRule")
 	noticeInputs := mergeNotices.Inputs.Strings()
@@ -2615,27 +2647,23 @@
 		t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
 	}
 	// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
-	res := foo.Output("package-res.apk")
+	res := foo.Output("package-res.apk").RelativeToTop()
 	aapt2Flags := res.Args["flags"]
-	e := "-A " + buildDir + "/.intermediates/foo/android_common/NOTICE"
-	if !strings.Contains(aapt2Flags, e) {
-		t.Errorf("asset dir flag for NOTICE, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
-	}
+	e := "-A out/soong/.intermediates/foo/android_common/NOTICE"
+	android.AssertStringDoesContain(t, "expected.apkPath", aapt2Flags, e)
 
 	// bar has NOTICE files to process, but embed_notices is not set.
-	bar := ctx.ModuleForTests("bar", "android_common")
-	res = bar.Output("package-res.apk")
+	bar := result.ModuleForTests("bar", "android_common")
+	res = bar.Output("package-res.apk").RelativeToTop()
 	aapt2Flags = res.Args["flags"]
-	e = "-A " + buildDir + "/.intermediates/bar/android_common/NOTICE"
-	if strings.Contains(aapt2Flags, e) {
-		t.Errorf("bar shouldn't have the asset dir flag for NOTICE: %q", e)
-	}
+	e = "-A out/soong/.intermediates/bar/android_common/NOTICE"
+	android.AssertStringDoesNotContain(t, "bar shouldn't have the asset dir flag for NOTICE", aapt2Flags, e)
 
 	// baz's embed_notice is true, but it doesn't have any NOTICE files.
-	baz := ctx.ModuleForTests("baz", "android_common")
+	baz := result.ModuleForTests("baz", "android_common")
 	res = baz.Output("package-res.apk")
 	aapt2Flags = res.Args["flags"]
-	e = "-A " + buildDir + "/.intermediates/baz/android_common/NOTICE"
+	e = "-A out/soong/.intermediates/baz/android_common/NOTICE"
 	if strings.Contains(aapt2Flags, e) {
 		t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e)
 	}
@@ -2718,7 +2746,8 @@
 	test := func(t *testing.T, bp string, want bool, unbundled bool) {
 		t.Helper()
 
-		result := javaFixtureFactory.Extend(
+		result := android.GroupFixturePreparers(
+			prepareForJavaTest,
 			PrepareForTestWithPrebuiltsOfCurrentApi,
 			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 				if unbundled {
diff --git a/java/boot_image.go b/java/boot_image.go
index 25a4f17..a14940d 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -96,10 +96,6 @@
 
 func (b *BootImageModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
-	if tag == dexpreopt.Dex2oatDepTag {
-		// The dex2oat tool is only needed for building and is not required in the apex.
-		return false
-	}
 	if android.IsMetaDependencyTag(tag) {
 		// Cross-cutting metadata dependencies are metadata.
 		return false
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index a2961c2..b4cf012 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -160,14 +160,17 @@
 
 	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
+
+	isSystemServerJar := inList(ctx.ModuleName(), global.SystemServerJars)
+
 	bootImage := defaultBootImageConfig(ctx)
-	dexFiles := bootImage.dexPathsDeps.Paths()
-	// The dex locations for all Android variants are identical.
-	dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps
 	if global.UseArtImage {
 		bootImage = artBootImageConfig(ctx)
 	}
 
+	// System server jars are an exception: they are dexpreopted without updatable bootclasspath.
+	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp && !isSystemServerJar)
+
 	targets := ctx.MultiTargets()
 	if len(targets) == 0 {
 		// assume this is a java library, dexpreopt for all arches for now
@@ -176,7 +179,7 @@
 				targets = append(targets, target)
 			}
 		}
-		if inList(ctx.ModuleName(), global.SystemServerJars) && !d.isSDKLibrary {
+		if isSystemServerJar && !d.isSDKLibrary {
 			// If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
 			targets = targets[:1]
 		}
@@ -237,7 +240,7 @@
 		DexPreoptImagesDeps:     imagesDeps,
 		DexPreoptImageLocations: imageLocations,
 
-		PreoptBootClassPathDexFiles:     dexFiles,
+		PreoptBootClassPathDexFiles:     dexFiles.Paths(),
 		PreoptBootClassPathDexLocations: dexLocations,
 
 		PreoptExtractedApk: false,
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index e94b20c..7137f33 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -213,7 +214,7 @@
 var artApexNames = []string{
 	"com.android.art",
 	"com.android.art.debug",
-	"com.android.art,testing",
+	"com.android.art.testing",
 	"com.google.android.art",
 	"com.google.android.art.debug",
 	"com.google.android.art.testing",
@@ -438,6 +439,8 @@
 	// Create boot image for the ART apex (build artifacts are accessed via the global boot image config).
 	d.otherImages = append(d.otherImages, buildBootImage(ctx, artBootImageConfig(ctx)))
 
+	copyUpdatableBootJars(ctx)
+
 	dumpOatRules(ctx, d.defaultBootImage)
 }
 
@@ -449,46 +452,61 @@
 // be needed there too.
 //
 // TODO(b/177892522): Avoid having to perform this type of check or if necessary dedup it.
-func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, module android.Module) (int, android.Path) {
+func getBootJar(ctx android.SingletonContext, bootjars android.ConfiguredJarList,
+	module android.Module, fromWhere string) (int, android.Path, *android.ApexInfo) {
+
 	name := ctx.ModuleName(module)
 
 	// Strip a prebuilt_ prefix so that this can access the dex jar from a prebuilt module.
 	name = android.RemoveOptionalPrebuiltPrefix(name)
 
 	// Ignore any module that is not listed in the boot image configuration.
-	index := image.modules.IndexOfJar(name)
+	index := bootjars.IndexOfJar(name)
 	if index == -1 {
-		return -1, nil
+		return -1, nil, nil
 	}
 
 	// It is an error if a module configured in the boot image does not support accessing the dex jar.
 	// This is safe because every module that has the same name has to have the same module type.
 	jar, hasJar := module.(interface{ DexJarBuildPath() android.Path })
 	if !hasJar {
-		ctx.Errorf("module %q configured in boot image %q does not support accessing dex jar", module, image.name)
-		return -1, nil
+		ctx.Errorf("module %q %sdoes not support accessing dex jar", module, fromWhere)
+		return -1, nil, nil
 	}
 
 	// It is also an error if the module is not an ApexModule.
 	if _, ok := module.(android.ApexModule); !ok {
-		ctx.Errorf("module %q configured in boot image %q does not support being added to an apex", module, image.name)
-		return -1, nil
+		ctx.Errorf("module %q %sdoes not support being added to an apex", module, fromWhere)
+		return -1, nil, nil
 	}
 
 	apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
 	// Now match the apex part of the boot image configuration.
-	requiredApex := image.modules.Apex(index)
+	requiredApex := bootjars.Apex(index)
 	if requiredApex == "platform" {
 		if len(apexInfo.InApexes) != 0 {
 			// A platform variant is required but this is for an apex so ignore it.
-			return -1, nil
+			return -1, nil, nil
 		}
 	} else if !apexInfo.InApexByBaseName(requiredApex) {
 		// An apex variant for a specific apex is required but this is the wrong apex.
+		return -1, nil, nil
+	}
+
+	return index, jar.DexJarBuildPath(), &apexInfo
+}
+
+// Inspect this module to see if it contains a bootclasspath dex jar from a given boot image.
+func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, module android.Module) (int, android.Path) {
+	fromImage := fmt.Sprintf("configured in boot image %q ", image.name)
+	index, jarPath, apexInfo := getBootJar(ctx, image.modules, module, fromImage)
+	if index == -1 {
 		return -1, nil
 	}
 
+	name := ctx.ModuleName(module)
+
 	// Check that this module satisfies any boot image specific constraints.
 	fromUpdatableApex := apexInfo.Updatable
 
@@ -525,39 +543,40 @@
 		panic("unknown boot image: " + image.name)
 	}
 
-	return index, jar.DexJarBuildPath()
+	return index, jarPath
 }
 
-// buildBootImage takes a bootImageConfig, creates rules to build it, and returns the image.
-func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootImageConfig {
-	// Collect dex jar paths for the boot image modules.
+// Generate commands that will copy boot jars to predefined paths in the global config.
+func findAndCopyBootJars(ctx android.SingletonContext, bootjars android.ConfiguredJarList,
+	jarPathsPredefined android.WritablePaths,
+	getBootJar func(module android.Module) (int, android.Path)) []string {
+
 	// This logic is tested in the apex package to avoid import cycle apex <-> java.
-	bootDexJars := make(android.Paths, image.modules.Len())
+	jarPaths := make(android.Paths, bootjars.Len())
 
 	ctx.VisitAllModules(func(module android.Module) {
 		if !isActiveModule(module) {
 			return
 		}
-
-		if i, j := getBootImageJar(ctx, image, module); i != -1 {
-			if existing := bootDexJars[i]; existing != nil {
-				ctx.Errorf("Multiple dex jars found for %s:%s - %s and %s",
-					image.modules.Apex(i), image.modules.Jar(i), existing, j)
+		if i, j := getBootJar(module); i != -1 {
+			if existing := jarPaths[i]; existing != nil {
+				ctx.Errorf("Multiple dex jars found for %s:%s - %q and %q",
+					bootjars.Apex(i), bootjars.Jar(i), existing, j)
 				return
 			}
-
-			bootDexJars[i] = j
+			jarPaths[i] = j
 		}
 	})
 
 	var missingDeps []string
 	// Ensure all modules were converted to paths
-	for i := range bootDexJars {
-		if bootDexJars[i] == nil {
-			m := image.modules.Jar(i)
+	for i := range jarPaths {
+		if jarPaths[i] == nil {
+			m := bootjars.Jar(i)
 			if ctx.Config().AllowMissingDependencies() {
 				missingDeps = append(missingDeps, m)
-				bootDexJars[i] = android.PathForOutput(ctx, "missing/module", m, "from/apex", image.modules.Apex(i))
+				jarPaths[i] = android.PathForOutput(ctx, "missing/module", m, "from/apex",
+					bootjars.Apex(i))
 			} else {
 				ctx.Errorf("failed to find a dex jar path for module '%s'"+
 					", note that some jars may be filtered out by module constraints", m)
@@ -569,14 +588,24 @@
 	// time, before the boot images are built (these paths are used in dexpreopt rule generation for
 	// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
 	// paths.
-	for i := range bootDexJars {
+	for i := range jarPaths {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
-			Input:  bootDexJars[i],
-			Output: image.dexPaths[i],
+			Input:  jarPaths[i],
+			Output: jarPathsPredefined[i],
 		})
 	}
 
+	return missingDeps
+}
+
+// buildBootImage takes a bootImageConfig, creates rules to build it, and returns the image.
+func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootImageConfig {
+	getBootJarFunc := func(module android.Module) (int, android.Path) {
+		return getBootImageJar(ctx, image, module)
+	}
+	missingDeps := findAndCopyBootJars(ctx, image.modules, image.dexPaths, getBootJarFunc)
+
 	profile := bootImageProfileRule(ctx, image, missingDeps)
 	bootFrameworkProfileRule(ctx, image, missingDeps)
 	updatableBcpPackagesRule(ctx, image, missingDeps)
@@ -603,6 +632,21 @@
 	return image
 }
 
+// Generate commands that will copy updatable boot jars to predefined paths in the global config.
+func copyUpdatableBootJars(ctx android.SingletonContext) {
+	config := GetUpdatableBootConfig(ctx)
+	getBootJarFunc := func(module android.Module) (int, android.Path) {
+		index, jar, _ := getBootJar(ctx, config.modules, module, "configured in updatable boot jars ")
+		return index, jar
+	}
+	missingDeps := findAndCopyBootJars(ctx, config.modules, config.dexPaths, getBootJarFunc)
+	// Ignoring missing dependencies here. Ideally they should be added to the dexpreopt rule, but
+	// that is not possible as this rule is created after dexpreopt rules (it's in a singleton
+	// context, and they are in a module context). The true fix is to add dependencies from the
+	// dexpreopted modules on updatable boot jars and avoid this copying altogether.
+	_ = missingDeps
+}
+
 // Generate boot image build rules for a specific target.
 func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant,
 	profile android.Path, missingDeps []string) android.WritablePaths {
@@ -970,8 +1014,11 @@
 	image := d.defaultBootImage
 	if image != nil {
 		ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String())
-		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPathsDeps.Strings(), " "))
-		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " "))
+
+		global := dexpreopt.GetGlobalConfig(ctx)
+		dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
+		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " "))
+		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " "))
 
 		var imageNames []string
 		// TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index aadf6ad..d78651d 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -43,22 +43,22 @@
 		}
 	`
 
-	result := javaFixtureFactory.
-		Extend(
-			PrepareForTestWithJavaSdkLibraryFiles,
-			FixtureWithLastReleaseApis("foo"),
-			dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar", "platform:baz"),
-		).RunTestWithBp(t, bp)
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar", "platform:baz"),
+	).RunTestWithBp(t, bp)
 
 	dexpreoptBootJars := result.SingletonForTests("dex_bootjars")
 	rule := dexpreoptBootJars.Output(ruleFile)
 
 	for i := range expectedInputs {
-		expectedInputs[i] = filepath.Join(buildDir, "test_device", expectedInputs[i])
+		expectedInputs[i] = filepath.Join("out/soong/test_device", expectedInputs[i])
 	}
 
 	for i := range expectedOutputs {
-		expectedOutputs[i] = filepath.Join(buildDir, "test_device", expectedOutputs[i])
+		expectedOutputs[i] = filepath.Join("out/soong/test_device", expectedOutputs[i])
 	}
 
 	inputs := rule.Implicits.Strings()
@@ -69,9 +69,9 @@
 	sort.Strings(outputs)
 	sort.Strings(expectedOutputs)
 
-	android.AssertDeepEquals(t, "inputs", expectedInputs, inputs)
+	android.AssertStringPathsRelativeToTopEquals(t, "inputs", result.Config, expectedInputs, inputs)
 
-	android.AssertDeepEquals(t, "outputs", expectedOutputs, outputs)
+	android.AssertStringPathsRelativeToTopEquals(t, "outputs", result.Config, expectedOutputs, outputs)
 }
 
 func TestDexpreoptBootJars(t *testing.T) {
@@ -107,7 +107,7 @@
 func TestDexpreoptBootZip(t *testing.T) {
 	ruleFile := "boot.zip"
 
-	ctx := android.PathContextForTesting(testConfig(nil, "", nil))
+	ctx := android.PathContextForTesting(android.TestArchConfig("", nil, "", nil))
 	expectedInputs := []string{}
 	for _, target := range ctx.Config().Targets[android.Android] {
 		for _, ext := range []string{".art", ".oat", ".vdex"} {
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 282e936..64b2656 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -176,6 +176,57 @@
 	})
 }
 
+// Updatable boot config allows to access build/install paths of updatable boot jars without going
+// through the usual trouble of registering dependencies on those modules and extracting build paths
+// from those dependencies.
+type updatableBootConfig struct {
+	// A list of updatable boot jars.
+	modules android.ConfiguredJarList
+
+	// A list of predefined build paths to updatable boot jars. They are configured very early,
+	// before the modules for these jars are processed and the actual paths are generated, and
+	// later on a singleton adds commands to copy actual jars to the predefined paths.
+	dexPaths android.WritablePaths
+
+	// A list of dex locations (a.k.a. on-device paths) to the boot jars.
+	dexLocations []string
+}
+
+var updatableBootConfigKey = android.NewOnceKey("updatableBootConfig")
+
+// Returns updatable boot config.
+func GetUpdatableBootConfig(ctx android.PathContext) updatableBootConfig {
+	return ctx.Config().Once(updatableBootConfigKey, func() interface{} {
+		updatableBootJars := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars
+
+		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars")
+		dexPaths := updatableBootJars.BuildPaths(ctx, dir)
+
+		dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android)
+
+		return updatableBootConfig{updatableBootJars, dexPaths, dexLocations}
+	}).(updatableBootConfig)
+}
+
+// Returns a list of paths and a list of locations for the boot jars used in dexpreopt (to be
+// passed in -Xbootclasspath and -Xbootclasspath-locations arguments for dex2oat).
+func bcpForDexpreopt(ctx android.PathContext, withUpdatable bool) (android.WritablePaths, []string) {
+	// Non-updatable boot jars (they are used both in the boot image and in dexpreopt).
+	bootImage := defaultBootImageConfig(ctx)
+	dexPaths := bootImage.dexPathsDeps
+	// The dex locations for all Android variants are identical.
+	dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps
+
+	if withUpdatable {
+		// Updatable boot jars (they are used only in dexpreopt, but not in the boot image).
+		updBootConfig := GetUpdatableBootConfig(ctx)
+		dexPaths = append(dexPaths, updBootConfig.dexPaths...)
+		dexLocations = append(dexLocations, updBootConfig.dexLocations...)
+	}
+
+	return dexPaths, dexLocations
+}
+
 var defaultBootclasspathKey = android.NewOnceKey("defaultBootclasspath")
 
 var copyOf = android.CopyOf
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index f17d436..d6c6a2d 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -35,7 +35,8 @@
 	})
 }
 
-var hiddenApiFixtureFactory = javaFixtureFactory.Extend(PrepareForTestWithHiddenApiBuildComponents)
+var hiddenApiFixtureFactory = android.GroupFixturePreparers(
+	prepareForJavaTest, PrepareForTestWithHiddenApiBuildComponents)
 
 func TestHiddenAPISingleton(t *testing.T) {
 	result := hiddenApiFixtureFactory.Extend(
@@ -49,8 +50,8 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
-	want := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
@@ -144,8 +145,8 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
-	want := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
@@ -168,11 +169,11 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
-	fromSourceJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 
-	prebuiltJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/dex/foo.jar"
+	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/dex/foo.jar"
 	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 }
 
@@ -195,11 +196,11 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
-	prebuiltJarArg := "--boot-dex=" + buildDir + "/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 
-	fromSourceJarArg := "--boot-dex=" + buildDir + "/.intermediates/foo/android_common/aligned/foo.jar"
+	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 }
 
@@ -243,7 +244,7 @@
 			).RunTest(t)
 
 			hiddenAPI := result.SingletonForTests("hiddenapi")
-			hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+			hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
 			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
 			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)
 
@@ -260,7 +261,7 @@
 }
 
 func generateDexedPath(subDir, dex, module string) string {
-	return fmt.Sprintf("%s/.intermediates/%s/android_common/%s/%s.jar", buildDir, subDir, dex, module)
+	return fmt.Sprintf("out/soong/.intermediates/%s/android_common/%s/%s.jar", subDir, dex, module)
 }
 
 func generateDexPath(moduleDir string, module string) string {
@@ -297,8 +298,8 @@
 	`)
 
 	expectedCpInput := prebuiltHiddenApiDir + "/hiddenapi-flags.csv"
-	expectedCpOutput := buildDir + "/hiddenapi/hiddenapi-flags.csv"
-	expectedFlagsCsv := buildDir + "/hiddenapi/hiddenapi-flags.csv"
+	expectedCpOutput := "out/soong/hiddenapi/hiddenapi-flags.csv"
+	expectedFlagsCsv := "out/soong/hiddenapi/hiddenapi-flags.csv"
 
 	foo := result.ModuleForTests("foo", "android_common")
 
@@ -306,12 +307,12 @@
 	cpRule := hiddenAPI.Rule("Cp")
 	actualCpInput := cpRule.BuildParams.Input
 	actualCpOutput := cpRule.BuildParams.Output
-	encodeDexRule := foo.Rule("hiddenAPIEncodeDex")
+	encodeDexRule := foo.Rule("hiddenAPIEncodeDex").RelativeToTop()
 	actualFlagsCsv := encodeDexRule.BuildParams.Args["flagsCsv"]
 
-	android.AssertStringEquals(t, "hiddenapi cp rule input", expectedCpInput, actualCpInput.String())
+	android.AssertPathRelativeToTopEquals(t, "hiddenapi cp rule input", expectedCpInput, actualCpInput)
 
-	android.AssertStringEquals(t, "hiddenapi cp rule output", expectedCpOutput, actualCpOutput.String())
+	android.AssertPathRelativeToTopEquals(t, "hiddenapi cp rule output", expectedCpOutput, actualCpOutput)
 
 	android.AssertStringEquals(t, "hiddenapi encode dex rule flags csv", expectedFlagsCsv, actualFlagsCsv)
 }
diff --git a/java/java_test.go b/java/java_test.go
index d27a73d..9924be7 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -34,28 +33,6 @@
 	"android/soong/python"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_java_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
-// Legacy factory to use to create fixtures for tests in this package.
-//
-// deprecated: See prepareForJavaTest
-var javaFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
-	prepareForJavaTest,
-)
-
 // Legacy preparer used for running tests within the java package.
 //
 // This includes everything that was needed to run any test in the java package prior to the
@@ -74,77 +51,13 @@
 	PrepareForTestWithOverlayBuildComponents,
 	python.PrepareForTestWithPythonBuildComponents,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-		ctx.RegisterModuleType("java_plugin", PluginFactory)
-
 		ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
 	}),
-	dexpreopt.PrepareForTestWithDexpreopt,
+	PrepareForTestWithDexpreopt,
 )
 
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
-}
-
-// testConfig is a legacy way of creating a test Config for testing java modules.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
-	return TestConfig(buildDir, env, bp, fs)
-}
-
-// testContext is a legacy way of creating a TestContext for testing java modules.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func testContext(config android.Config) *android.TestContext {
-
-	ctx := android.NewTestArchContext(config)
-	RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.RegisterModuleType("java_plugin", PluginFactory)
-	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(android.RegisterComponentsMutator)
-
-	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
-	ctx.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
-	ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
-
-	android.RegisterPrebuiltMutators(ctx)
-
-	genrule.RegisterGenruleBuildComponents(ctx)
-
-	// Register module types and mutators from cc needed for JNI testing
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-
-	return ctx
-}
-
-// run is a legacy way of running tests of java modules.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func run(t *testing.T, ctx *android.TestContext, config android.Config) {
-	t.Helper()
-
-	pathCtx := android.PathContextForTesting(config)
-	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
-
-	ctx.Register()
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
+	os.Exit(m.Run())
 }
 
 // testJavaError is a legacy way of running tests of java modules that expect errors.
@@ -154,8 +67,8 @@
 // deprecated
 func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContext, android.Config) {
 	t.Helper()
-	result := javaFixtureFactory.
-		Extend(dexpreopt.PrepareForTestWithDexpreopt).
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest, dexpreopt.PrepareForTestByEnablingDexpreopt).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
 		RunTestWithBp(t, bp)
 	return result.TestContext, result.Config
@@ -172,84 +85,41 @@
 	// the fixture's config will be ignored when RunTestWithConfig replaces it.
 	pathCtx := android.PathContextForTesting(config)
 	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
-	result := javaFixtureFactory.
+	result := prepareForJavaTest.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
 		RunTestWithConfig(t, config)
 	return result.TestContext, result.Config
 }
 
-// runWithErrors is a legacy way of running tests of java modules that expect errors.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func runWithErrors(t *testing.T, ctx *android.TestContext, config android.Config, pattern string) {
-	ctx.Register()
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-	_, errs = ctx.PrepareBuildActions(config)
-	if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, pattern, errs)
-		return
-	}
-
-	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
-	return
-}
-
-// testJavaWithFS runs tests using the javaFixtureFactory
+// testJavaWithFS runs tests using the prepareForJavaTest
 //
 // See testJava for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testJavaWithFS(t *testing.T, bp string, fs android.MockFS) (*android.TestContext, android.Config) {
 	t.Helper()
-	result := javaFixtureFactory.Extend(fs.AddToFixture()).RunTestWithBp(t, bp)
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest, fs.AddToFixture()).RunTestWithBp(t, bp)
 	return result.TestContext, result.Config
 }
 
-// testJava runs tests using the javaFixtureFactory
+// testJava runs tests using the prepareForJavaTest
 //
-// Do not add any new usages of this, instead use the javaFixtureFactory directly as it makes it
+// Do not add any new usages of this, instead use the prepareForJavaTest directly as it makes it
 // much easier to customize the test behavior.
 //
 // If it is necessary to customize the behavior of an existing test that uses this then please first
-// convert the test to using javaFixtureFactory first and then in a following change add the
+// convert the test to using prepareForJavaTest first and then in a following change add the
 // appropriate fixture preparers. Keeping the conversion change separate makes it easy to verify
 // that it did not change the test behavior unexpectedly.
 //
 // deprecated
 func testJava(t *testing.T, bp string) (*android.TestContext, android.Config) {
 	t.Helper()
-	result := javaFixtureFactory.RunTestWithBp(t, bp)
+	result := prepareForJavaTest.RunTestWithBp(t, bp)
 	return result.TestContext, result.Config
 }
 
-// testJavaWithConfig runs tests using the javaFixtureFactory
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func testJavaWithConfig(t *testing.T, config android.Config) (*android.TestContext, android.Config) {
-	t.Helper()
-	result := javaFixtureFactory.RunTestWithConfig(t, config)
-	return result.TestContext, result.Config
-}
-
-func moduleToPath(name string) string {
-	switch {
-	case name == `""`:
-		return name
-	case strings.HasSuffix(name, ".jar"):
-		return name
-	default:
-		return filepath.Join(buildDir, ".intermediates", name, "android_common", "turbine-combined", name+".jar")
-	}
-}
-
 // defaultModuleToPath constructs a path to the turbine generate jar for a default test module that
 // is defined in PrepareForIntegrationTestWithJava
 func defaultModuleToPath(name string) string {
@@ -259,10 +129,19 @@
 	case strings.HasSuffix(name, ".jar"):
 		return name
 	default:
-		return filepath.Join(buildDir, ".intermediates", defaultJavaDir, name, "android_common", "turbine-combined", name+".jar")
+		return filepath.Join("out", "soong", ".intermediates", defaultJavaDir, name, "android_common", "turbine-combined", name+".jar")
 	}
 }
 
+// Test that the PrepareForTestWithJavaDefaultModules provides all the files that it uses by
+// running it in a fixture that requires all source files to exist.
+func TestPrepareForTestWithJavaDefaultModules(t *testing.T) {
+	android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestDisallowNonExistentPaths,
+	).RunTest(t)
+}
+
 func TestJavaLinkType(t *testing.T) {
 	testJava(t, `
 		java_library {
@@ -371,7 +250,7 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac").RelativeToTop()
 	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
@@ -379,8 +258,8 @@
 	}
 
 	baz := ctx.ModuleForTests("baz", "android_common").Rule("javac").Output.String()
-	barTurbine := filepath.Join(buildDir, ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
-	bazTurbine := filepath.Join(buildDir, ".intermediates", "baz", "android_common", "turbine-combined", "baz.jar")
+	barTurbine := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
+	bazTurbine := filepath.Join("out", "soong", ".intermediates", "baz", "android_common", "turbine-combined", "baz.jar")
 
 	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], barTurbine)
 
@@ -522,13 +401,19 @@
 			}
 		`
 
-		config := testConfig(nil, bp, nil)
-		config.TestProductVariables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce)
+		errorHandler := android.FixtureExpectsNoErrors
 		if enforce {
-			testJavaErrorWithConfig(t, "sdk_version must have a value when the module is located at vendor or product", config)
-		} else {
-			testJavaWithConfig(t, config)
+			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern("sdk_version must have a value when the module is located at vendor or product")
 		}
+
+		android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceProductPartitionInterface = proptools.BoolPtr(enforce)
+			}),
+		).
+			ExtendWithErrorHandler(errorHandler).
+			RunTestWithBp(t, bp)
 	}
 }
 
@@ -606,13 +491,16 @@
 			srcs: ["b.java"],
 		}
 	`
-	config := testConfig(nil, bp, nil)
-	config.TestProductVariables.MinimizeJavaDebugInfo = proptools.BoolPtr(true)
 
-	ctx, _ := testJavaWithConfig(t, config)
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.MinimizeJavaDebugInfo = proptools.BoolPtr(true)
+		}),
+	).RunTestWithBp(t, bp)
 
 	// first, check that the -g flag is added to target modules
-	targetLibrary := ctx.ModuleForTests("target_library", "android_common")
+	targetLibrary := result.ModuleForTests("target_library", "android_common")
 	targetJavaFlags := targetLibrary.Module().VariablesForTests()["javacFlags"]
 	if !strings.Contains(targetJavaFlags, "-g:source,lines") {
 		t.Errorf("target library javac flags %v should contain "+
@@ -621,7 +509,7 @@
 
 	// check that -g is not overridden for host modules
 	buildOS := android.BuildOs.String()
-	hostBinary := ctx.ModuleForTests("host_binary", buildOS+"_common")
+	hostBinary := result.ModuleForTests("host_binary", buildOS+"_common")
 	hostJavaFlags := hostBinary.Module().VariablesForTests()["javacFlags"]
 	if strings.Contains(hostJavaFlags, "-g:source,lines") {
 		t.Errorf("java_binary_host javac flags %v should not have "+
@@ -709,11 +597,9 @@
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, bazJar.String())
 	}
 
-	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath().String()
-	expectedDexJar := buildDir + "/.intermediates/baz/android_common/dex/baz.jar"
-	if bazDexJar != expectedDexJar {
-		t.Errorf("baz dex jar build path expected %q, got %q", expectedDexJar, bazDexJar)
-	}
+	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath()
+	expectedDexJar := "out/soong/.intermediates/baz/android_common/dex/baz.jar"
+	android.AssertPathRelativeToTopEquals(t, "baz dex jar build path", expectedDexJar, bazDexJar)
 
 	ctx.ModuleForTests("qux", "android_common").Rule("Cp")
 }
@@ -754,7 +640,7 @@
 }
 
 func TestJavaSdkLibraryImport(t *testing.T) {
-	result := javaFixtureFactory.RunTestWithBp(t, `
+	result := prepareForJavaTest.RunTestWithBp(t, `
 		java_library {
 			name: "foo",
 			srcs: ["a.java"],
@@ -808,7 +694,8 @@
 }
 
 func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("sdklib"),
 	).RunTestWithBp(t, `
@@ -850,7 +737,8 @@
 }
 
 func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("sdklib"),
 	).RunTestWithBp(t, `
@@ -952,12 +840,12 @@
 	}
 
 	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
-		t.Run(fmt.Sprintf("%#v", info), func(t *testing.T) {
+		t.Run(fmt.Sprintf("%v", info), func(t *testing.T) {
 			errorHandler := android.FixtureExpectsNoErrors
 			if expectedErrorPattern != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
 			}
-			javaFixtureFactory.ExtendWithErrorHandler(errorHandler).RunTest(t, createPreparer(info))
+			prepareForJavaTest.ExtendWithErrorHandler(errorHandler).RunTest(t, createPreparer(info))
 		})
 	}
 
@@ -1088,14 +976,14 @@
 		}
 		`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac").RelativeToTop()
 	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
 	}
 
-	barTurbine := filepath.Join(buildDir, ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
+	barTurbine := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
 	if !strings.Contains(javac.Args["classpath"], barTurbine) {
 		t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
 	}
@@ -1341,33 +1229,22 @@
 }
 
 func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
-	config := testConfig(
-		nil,
-		`
-		java_library {
-			name: "foo",
-			srcs: [
-			],
-			min_sdk_version: "29",
-			sdk_version: "system_current",
-			lint: {
-				baseline_filename: "mybaseline.xml",
-			},
-		}
-     `, map[string][]byte{
-			"build/soong/java/lint_defaults.txt":                   nil,
-			"prebuilts/cmdline-tools/tools/bin/lint":               nil,
-			"prebuilts/cmdline-tools/tools/lib/lint-classpath.jar": nil,
-			"framework/aidl":                     nil,
-			"a.java":                             nil,
-			"AndroidManifest.xml":                nil,
-			"build/make/target/product/security": nil,
-		})
-	config.TestAllowNonExistentPaths = false
-	testJavaErrorWithConfig(t,
-		"source path \"mybaseline.xml\" does not exist",
-		config,
-	)
+	android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestDisallowNonExistentPaths,
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{`source path "mybaseline.xml" does not exist`})).
+		RunTestWithBp(t, `
+			java_library {
+				name: "foo",
+				srcs: [
+				],
+				min_sdk_version: "29",
+				sdk_version: "system_current",
+				lint: {
+					baseline_filename: "mybaseline.xml",
+				},
+			}
+	 `)
 }
 
 func TestJavaLintUsesCorrectBpConfig(t *testing.T) {
@@ -1435,8 +1312,8 @@
 }
 
 func TestTurbine(t *testing.T) {
-	result := javaFixtureFactory.
-		Extend(FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}})).
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}})).
 		RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -1459,19 +1336,19 @@
 		}
 		`)
 
-	fooTurbine := result.ModuleForTests("foo", "android_common").Rule("turbine")
-	barTurbine := result.ModuleForTests("bar", "android_common").Rule("turbine")
-	barJavac := result.ModuleForTests("bar", "android_common").Rule("javac")
-	barTurbineCombined := result.ModuleForTests("bar", "android_common").Description("for turbine")
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
+	fooTurbine := result.ModuleForTests("foo", "android_common").Rule("turbine").RelativeToTop()
+	barTurbine := result.ModuleForTests("bar", "android_common").Rule("turbine").RelativeToTop()
+	barJavac := result.ModuleForTests("bar", "android_common").Rule("javac").RelativeToTop()
+	barTurbineCombined := result.ModuleForTests("bar", "android_common").Description("for turbine").RelativeToTop()
+	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac").RelativeToTop()
 
-	android.AssertArrayString(t, "foo inputs", []string{"a.java"}, fooTurbine.Inputs.Strings())
+	android.AssertPathsRelativeToTopEquals(t, "foo inputs", []string{"a.java"}, fooTurbine.Inputs)
 
-	fooHeaderJar := filepath.Join(buildDir, ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar")
-	barTurbineJar := filepath.Join(buildDir, ".intermediates", "bar", "android_common", "turbine", "bar.jar")
+	fooHeaderJar := filepath.Join("out", "soong", ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar")
+	barTurbineJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
 	android.AssertStringDoesContain(t, "bar turbine classpath", barTurbine.Args["classpath"], fooHeaderJar)
 	android.AssertStringDoesContain(t, "bar javac classpath", barJavac.Args["classpath"], fooHeaderJar)
-	android.AssertArrayString(t, "bar turbine combineJar", []string{barTurbineJar, fooHeaderJar}, barTurbineCombined.Inputs.Strings())
+	android.AssertPathsRelativeToTopEquals(t, "bar turbine combineJar", []string{barTurbineJar, fooHeaderJar}, barTurbineCombined.Inputs)
 	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "prebuilts/sdk/14/public/android.jar")
 }
 
@@ -1484,9 +1361,9 @@
 		}
 		`)
 
-	barHeaderJar := filepath.Join(buildDir, ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
+	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
 	for i := 0; i < 3; i++ {
-		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i))
+		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i)).RelativeToTop()
 		if !strings.Contains(barJavac.Args["classpath"], barHeaderJar) {
 			t.Errorf("bar javac classpath %v does not contain %q", barJavac.Args["classpath"], barHeaderJar)
 		}
@@ -1551,12 +1428,12 @@
 
 	barStubsOutput := barStubsOutputs[0]
 	barDoc := ctx.ModuleForTests("bar-doc", "android_common")
-	javaDoc := barDoc.Rule("javadoc")
+	javaDoc := barDoc.Rule("javadoc").RelativeToTop()
 	if g, w := javaDoc.Implicits.Strings(), barStubsOutput.String(); !inList(w, g) {
 		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
 	}
 
-	expected := "-sourcepath " + buildDir + "/.intermediates/bar-doc/android_common/srcjars "
+	expected := "-sourcepath out/soong/.intermediates/bar-doc/android_common/srcjars "
 	if !strings.Contains(javaDoc.RuleParams.Command, expected) {
 		t.Errorf("bar-doc command does not contain flag %q, but should\n%q", expected, javaDoc.RuleParams.Command)
 	}
@@ -1814,7 +1691,7 @@
 }
 
 func TestJavaLibrary(t *testing.T) {
-	config := testConfig(nil, "", map[string][]byte{
+	testJavaWithFS(t, "", map[string][]byte{
 		"libcore/Android.bp": []byte(`
 				java_library {
 						name: "core",
@@ -1826,14 +1703,12 @@
 					name: "core-jar",
 					srcs: [":core{.jar}"],
 				}
-`),
+		`),
 	})
-	ctx := testContext(config)
-	run(t, ctx, config)
 }
 
 func TestJavaImport(t *testing.T) {
-	config := testConfig(nil, "", map[string][]byte{
+	testJavaWithFS(t, "", map[string][]byte{
 		"libcore/Android.bp": []byte(`
 				java_import {
 						name: "core",
@@ -1844,14 +1719,13 @@
 					name: "core-jar",
 					srcs: [":core{.jar}"],
 				}
-`),
+		`),
 	})
-	ctx := testContext(config)
-	run(t, ctx, config)
 }
 
 func TestJavaSdkLibrary(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithPrebuiltApis(map[string][]string{
 			"28": {"foo"},
@@ -1976,7 +1850,8 @@
 }
 
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("sdklib"),
 	).RunTestWithBp(t, `
@@ -2012,7 +1887,8 @@
 }
 
 func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2040,7 +1916,8 @@
 }
 
 func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
-	javaFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2061,11 +1938,11 @@
 }
 
 func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
-	javaFixtureFactory.
-		Extend(
-			PrepareForTestWithJavaSdkLibraryFiles,
-			FixtureWithLastReleaseApis("foo"),
-		).
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
 		RunTestWithBp(t, `
 		java_sdk_library {
@@ -2085,7 +1962,8 @@
 }
 
 func TestJavaSdkLibrary_Deps(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("sdklib"),
 	).RunTestWithBp(t, `
@@ -2110,7 +1988,7 @@
 }
 
 func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
-	javaFixtureFactory.RunTestWithBp(t, `
+	prepareForJavaTest.RunTestWithBp(t, `
 		java_sdk_library_import {
 			name: "foo",
 			public: {
@@ -2143,7 +2021,7 @@
 		`
 
 	t.Run("stubs.source", func(t *testing.T) {
-		javaFixtureFactory.
+		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
 			RunTestWithBp(t, bp+`
 				java_library {
@@ -2158,7 +2036,7 @@
 	})
 
 	t.Run("api.txt", func(t *testing.T) {
-		javaFixtureFactory.
+		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
 			RunTestWithBp(t, bp+`
 				java_library {
@@ -2172,7 +2050,7 @@
 	})
 
 	t.Run("removed-api.txt", func(t *testing.T) {
-		javaFixtureFactory.
+		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
 			RunTestWithBp(t, bp+`
 				java_library {
@@ -2187,7 +2065,7 @@
 }
 
 func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
-	javaFixtureFactory.
+	prepareForJavaTest.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
 		RunTestWithBp(t, `
 			java_sdk_library {
@@ -2207,7 +2085,8 @@
 }
 
 func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
-	javaFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2224,7 +2103,8 @@
 }
 
 func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
-	javaFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2243,7 +2123,8 @@
 }
 
 func TestJavaSdkLibrary_SystemServer(t *testing.T) {
-	javaFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2262,7 +2143,7 @@
 }
 
 func TestJavaSdkLibrary_MissingScope(t *testing.T) {
-	javaFixtureFactory.
+	prepareForJavaTest.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
 		RunTestWithBp(t, `
 			java_sdk_library {
@@ -2283,7 +2164,8 @@
 }
 
 func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
-	javaFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2306,7 +2188,8 @@
 }
 
 func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
-	result := javaFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -2398,7 +2281,7 @@
 
 // TODO(jungjw): Consider making this more robust by ignoring path order.
 func checkPatchModuleFlag(t *testing.T, ctx *android.TestContext, moduleName string, expected string) {
-	variables := ctx.ModuleForTests(moduleName, "android_common").Module().VariablesForTests()
+	variables := ctx.ModuleForTests(moduleName, "android_common").VariablesForTestsRelativeToTop()
 	flags := strings.Split(variables["javacFlags"], " ")
 	got := ""
 	for _, flag := range flags {
@@ -2408,7 +2291,7 @@
 			break
 		}
 	}
-	if expected != got {
+	if expected != android.StringPathRelativeToTop(ctx.Config().BuildDir(), got) {
 		t.Errorf("Unexpected patch-module flag for module %q - expected %q, but got %q", moduleName, expected, got)
 	}
 }
@@ -2478,10 +2361,10 @@
 		ctx, _ := testJava(t, bp)
 
 		checkPatchModuleFlag(t, ctx, "foo", "")
-		expected := "java.base=.:" + buildDir
+		expected := "java.base=.:out/soong"
 		checkPatchModuleFlag(t, ctx, "bar", expected)
 		expected = "java.base=" + strings.Join([]string{
-			".", buildDir, "dir", "dir2", "nested", defaultModuleToPath("ext"), defaultModuleToPath("framework")}, ":")
+			".", "out/soong", "dir", "dir2", "nested", defaultModuleToPath("ext"), defaultModuleToPath("framework")}, ":")
 		checkPatchModuleFlag(t, ctx, "baz", expected)
 	})
 }
@@ -2600,11 +2483,9 @@
 
 	test := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
 	entries := android.AndroidMkEntriesForTest(t, ctx, test)[0]
-	expected := []string{buildDir + "/.intermediates/bin/" + buildOS + "_x86_64_PY3/bin:bin"}
+	expected := []string{"out/soong/.intermediates/bin/" + buildOS + "_x86_64_PY3/bin:bin"}
 	actual := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Unexpected test data - expected: %q, actual: %q", expected, actual)
-	}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_COMPATIBILITY_SUPPORT_FILES", ctx.Config(), expected, actual)
 }
 
 func TestDefaultInstallable(t *testing.T) {
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 77ef294..1c146a1 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -176,19 +176,22 @@
 		env := map[string]string{
 			"RUN_ERROR_PRONE": "true",
 		}
-		config := testConfig(env, bp, nil)
-		ctx, _ := testJavaWithConfig(t, config)
+
+		result := android.GroupFixturePreparers(
+			PrepareForTestWithJavaDefaultModules,
+			android.FixtureMergeEnv(env),
+		).RunTestWithBp(t, bp)
 
 		buildOS := android.BuildOs.String()
 
-		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
+		kapt := result.ModuleForTests("foo", "android_common").Rule("kapt")
 		//kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-		javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
-		errorprone := ctx.ModuleForTests("foo", "android_common").Description("errorprone")
+		javac := result.ModuleForTests("foo", "android_common").Description("javac")
+		errorprone := result.ModuleForTests("foo", "android_common").Description("errorprone")
 
-		bar := ctx.ModuleForTests("bar", buildOS+"_common").Description("javac").Output.String()
-		baz := ctx.ModuleForTests("baz", buildOS+"_common").Description("javac").Output.String()
-		myCheck := ctx.ModuleForTests("my_check", buildOS+"_common").Description("javac").Output.String()
+		bar := result.ModuleForTests("bar", buildOS+"_common").Description("javac").Output.String()
+		baz := result.ModuleForTests("baz", buildOS+"_common").Description("javac").Output.String()
+		myCheck := result.ModuleForTests("my_check", buildOS+"_common").Description("javac").Output.String()
 
 		// Test that the errorprone plugins are not passed to kapt
 		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
diff --git a/java/plugin.go b/java/plugin.go
index 947c286..297ac2c 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -17,7 +17,11 @@
 import "android/soong/android"
 
 func init() {
-	android.RegisterModuleType("java_plugin", PluginFactory)
+	registerJavaPluginBuildComponents(android.InitRegistrationContext)
+}
+
+func registerJavaPluginBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("java_plugin", PluginFactory)
 }
 
 // A java_plugin module describes a host java library that will be used by javac as an annotation processor.
diff --git a/java/rro_test.go b/java/rro_test.go
index 061d9d3..0a10d93 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -24,7 +24,7 @@
 )
 
 func TestRuntimeResourceOverlay(t *testing.T) {
-	fs := map[string][]byte{
+	fs := android.MockFS{
 		"baz/res/res/values/strings.xml": nil,
 		"bar/res/res/values/strings.xml": nil,
 	}
@@ -57,15 +57,18 @@
 			sdk_version: "current",
 			resource_dirs: ["baz/res"],
 		}
-		`
-	config := testAppConfig(nil, bp, fs)
-	ctx := testContext(config)
-	run(t, ctx, config)
+	`
 
-	m := ctx.ModuleForTests("foo", "android_common")
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithOverlayBuildComponents,
+		fs.AddToFixture(),
+	).RunTestWithBp(t, bp)
+
+	m := result.ModuleForTests("foo", "android_common")
 
 	// Check AAPT2 link flags.
-	aapt2Flags := m.Output("package-res.apk").Args["flags"]
+	aapt2Flags := m.Output("package-res.apk").RelativeToTop().Args["flags"]
 	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
 	absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " "))
 	if len(absentFlags) > 0 {
@@ -73,14 +76,14 @@
 	}
 
 	// Check overlay.list output for static_libs dependency.
-	overlayList := m.Output("aapt2/overlay.list").Inputs.Strings()
-	staticLibPackage := buildDir + "/.intermediates/bar/android_common/package-res.apk"
+	overlayList := android.PathsRelativeToTop(m.Output("aapt2/overlay.list").Inputs)
+	staticLibPackage := "out/soong/.intermediates/bar/android_common/package-res.apk"
 	if !inList(staticLibPackage, overlayList) {
 		t.Errorf("Stactic lib res package %q missing in overlay list: %q", staticLibPackage, overlayList)
 	}
 
 	// Check AAPT2 link flags for resource_libs dependency.
-	resourceLibFlag := "-I " + buildDir + "/.intermediates/baz/android_common/package-res.apk"
+	resourceLibFlag := "-I " + "out/soong/.intermediates/baz/android_common/package-res.apk"
 	if !strings.Contains(aapt2Flags, resourceLibFlag) {
 		t.Errorf("Resource lib flag %q missing in aapt2 link flags: %q", resourceLibFlag, aapt2Flags)
 	}
@@ -97,7 +100,7 @@
 	if expected != signingFlag {
 		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
 	}
-	androidMkEntries := android.AndroidMkEntriesForTest(t, ctx, m.Module())[0]
+	androidMkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0]
 	path := androidMkEntries.EntryMap["LOCAL_CERTIFICATE"]
 	expectedPath := []string{"build/make/target/product/security/platform.x509.pem"}
 	if !reflect.DeepEqual(path, expectedPath) {
@@ -106,19 +109,15 @@
 
 	// Check device location.
 	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{shared.JoinPath(buildDir, "../target/product/test_device/product/overlay")}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
+	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay")}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	// A themed module has a different device location
-	m = ctx.ModuleForTests("foo_themed", "android_common")
-	androidMkEntries = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0]
+	m = result.ModuleForTests("foo_themed", "android_common")
+	androidMkEntries = android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0]
 	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{shared.JoinPath(buildDir, "../target/product/test_device/product/overlay/faza")}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
+	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay/faza")}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	overrides := androidMkEntries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
 	expectedOverrides := []string{"foo"}
@@ -128,7 +127,7 @@
 }
 
 func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
-	ctx, _ := testJava(t, `
+	ctx, config := testJava(t, `
 		java_defaults {
 			name: "rro_defaults",
 			theme: "default_theme",
@@ -161,10 +160,8 @@
 
 	// Check device location.
 	path := android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath := []string{shared.JoinPath(buildDir, "../target/product/test_device/product/overlay/default_theme")}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %q, expected: %q", path, expectedPath)
-	}
+	expectedPath := []string{shared.JoinPath("out/target/product/test_device/product/overlay/default_theme")}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
 
 	//
 	// RRO module without defaults
@@ -180,10 +177,8 @@
 
 	// Check device location.
 	path = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{shared.JoinPath(buildDir, "../target/product/test_device/system/overlay")}
-	if !reflect.DeepEqual(path, expectedPath) {
-		t.Errorf("Unexpected LOCAL_MODULE_PATH value: %v, expected: %v", path, expectedPath)
-	}
+	expectedPath = []string{shared.JoinPath("out/target/product/test_device/system/overlay")}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
 }
 
 func TestOverrideRuntimeResourceOverlay(t *testing.T) {
@@ -214,7 +209,7 @@
 	}{
 		{
 			variantName:       "android_common",
-			apkPath:           "/target/product/test_device/product/overlay/foo_overlay.apk",
+			apkPath:           "out/soong/target/product/test_device/product/overlay/foo_overlay.apk",
 			overrides:         nil,
 			targetVariant:     "android_common",
 			packageFlag:       "",
@@ -222,7 +217,7 @@
 		},
 		{
 			variantName:       "android_common_bar_overlay",
-			apkPath:           "/target/product/test_device/product/overlay/bar_overlay.apk",
+			apkPath:           "out/soong/target/product/test_device/product/overlay/bar_overlay.apk",
 			overrides:         []string{"foo_overlay"},
 			targetVariant:     "android_common_bar",
 			packageFlag:       "com.android.bar.overlay",
@@ -233,18 +228,7 @@
 		variant := ctx.ModuleForTests("foo_overlay", expected.variantName)
 
 		// Check the final apk name
-		outputs := variant.AllOutputs()
-		expectedApkPath := buildDir + expected.apkPath
-		found := false
-		for _, o := range outputs {
-			if o == expectedApkPath {
-				found = true
-				break
-			}
-		}
-		if !found {
-			t.Errorf("Can't find %q in output files.\nAll outputs:%v", expectedApkPath, outputs)
-		}
+		variant.Output(expected.apkPath)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*RuntimeResourceOverlay)
@@ -298,7 +282,7 @@
 		"product/vendor/blah/overlay",
 	}
 
-	fs := map[string][]byte{
+	fs := android.MockFS{
 		"lib2/res/values/strings.xml":                             nil,
 		"product/vendor/blah/overlay/lib2/res/values/strings.xml": nil,
 	}
@@ -334,19 +318,22 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			config := testAppConfig(nil, bp, fs)
-			config.TestProductVariables.ProductResourceOverlays = productResourceOverlays
-			if testCase.enforceRROTargets != nil {
-				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
-			}
-
-			ctx := testContext(config)
-			run(t, ctx, config)
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithOverlayBuildComponents,
+				fs.AddToFixture(),
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.ProductResourceOverlays = productResourceOverlays
+					if testCase.enforceRROTargets != nil {
+						variables.EnforceRROTargets = testCase.enforceRROTargets
+					}
+				}),
+			).RunTestWithBp(t, bp)
 
 			modules := []string{"foo", "bar"}
 			for _, moduleName := range modules {
-				module := ctx.ModuleForTests(moduleName, "android_common")
-				mkEntries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
+				module := result.ModuleForTests(moduleName, "android_common")
+				mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0]
 				actualRRODirs := mkEntries.EntryMap["LOCAL_SOONG_PRODUCT_RRO_DIRS"]
 				if !reflect.DeepEqual(actualRRODirs, testCase.rroDirs[moduleName]) {
 					t.Errorf("exected %s LOCAL_SOONG_PRODUCT_RRO_DIRS entry: %v\ngot:%q",
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 37875a4..028c4fe 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"path/filepath"
-	"reflect"
 	"strings"
 	"testing"
 
@@ -98,7 +97,7 @@
 			bootclasspath:  []string{"android_stubs_current", "core-lambda-stubs"},
 			system:         "core-current-stubs-system-modules",
 			java9classpath: []string{"android_stubs_current"},
-			aidl:           "-p" + buildDir + "/framework.aidl",
+			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
 
@@ -107,7 +106,7 @@
 			bootclasspath:  []string{"android_system_stubs_current", "core-lambda-stubs"},
 			system:         "core-current-stubs-system-modules",
 			java9classpath: []string{"android_system_stubs_current"},
-			aidl:           "-p" + buildDir + "/framework.aidl",
+			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
 
@@ -135,7 +134,7 @@
 			bootclasspath:  []string{"android_test_stubs_current", "core-lambda-stubs"},
 			system:         "core-current-stubs-system-modules",
 			java9classpath: []string{"android_test_stubs_current"},
-			aidl:           "-p" + buildDir + "/framework.aidl",
+			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
 
@@ -222,7 +221,7 @@
 			bootclasspath:  []string{"android_module_lib_stubs_current", "core-lambda-stubs"},
 			system:         "core-current-stubs-system-modules",
 			java9classpath: []string{"android_module_lib_stubs_current"},
-			aidl:           "-p" + buildDir + "/framework_non_updatable.aidl",
+			aidl:           "-pout/soong/framework_non_updatable.aidl",
 		},
 		{
 			name:           "system_server_current",
@@ -230,7 +229,7 @@
 			bootclasspath:  []string{"android_system_server_stubs_current", "core-lambda-stubs"},
 			system:         "core-current-stubs-system-modules",
 			java9classpath: []string{"android_system_server_stubs_current"},
-			aidl:           "-p" + buildDir + "/framework.aidl",
+			aidl:           "-pout/soong/framework.aidl",
 		},
 	}
 
@@ -303,23 +302,23 @@
 				} else {
 					dir = defaultJavaDir
 				}
-				system = "--system=" + filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system")
+				system = "--system=" + filepath.Join("out", "soong", ".intermediates", dir, testcase.system, "android_common", "system")
 				// The module-relative parts of these paths are hardcoded in system_modules.go:
 				systemDeps = []string{
-					filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "lib", "modules"),
-					filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "lib", "jrt-fs.jar"),
-					filepath.Join(buildDir, ".intermediates", dir, testcase.system, "android_common", "system", "release"),
+					filepath.Join("out", "soong", ".intermediates", dir, testcase.system, "android_common", "system", "lib", "modules"),
+					filepath.Join("out", "soong", ".intermediates", dir, testcase.system, "android_common", "system", "lib", "jrt-fs.jar"),
+					filepath.Join("out", "soong", ".intermediates", dir, testcase.system, "android_common", "system", "release"),
 				}
 			}
 
 			checkClasspath := func(t *testing.T, result *android.TestResult, isJava8 bool) {
 				foo := result.ModuleForTests("foo", variant)
-				javac := foo.Rule("javac")
+				javac := foo.Rule("javac").RelativeToTop()
 				var deps []string
 
-				aidl := foo.MaybeRule("aidl")
+				aidl := foo.MaybeRule("aidl").RelativeToTop()
 				if aidl.Rule != nil {
-					deps = append(deps, aidl.Output.String())
+					deps = append(deps, android.PathRelativeToTop(aidl.Output))
 				}
 
 				got := javac.Args["bootClasspath"]
@@ -347,12 +346,11 @@
 					t.Errorf("classpath expected %q != got %q", expected, got)
 				}
 
-				if !reflect.DeepEqual(javac.Implicits.Strings(), deps) {
-					t.Errorf("implicits expected %q != got %q", deps, javac.Implicits.Strings())
-				}
+				android.AssertPathsRelativeToTopEquals(t, "implicits", deps, javac.Implicits)
 			}
 
-			fixtureFactory := javaFixtureFactory.Extend(
+			fixtureFactory := android.GroupFixturePreparers(
+				prepareForJavaTest,
 				FixtureWithPrebuiltApis(map[string][]string{
 					"29":      {},
 					"30":      {},
@@ -378,7 +376,7 @@
 				checkClasspath(t, result, true /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
+					aidl := result.ModuleForTests("foo", variant).Rule("aidl").RelativeToTop()
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -391,7 +389,7 @@
 				checkClasspath(t, result, false /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
+					aidl := result.ModuleForTests("foo", variant).Rule("aidl").RelativeToTop()
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
diff --git a/java/system_modules_test.go b/java/system_modules_test.go
index 3d9f398..7d8935a 100644
--- a/java/system_modules_test.go
+++ b/java/system_modules_test.go
@@ -50,7 +50,7 @@
 `)
 
 func TestJavaSystemModules(t *testing.T) {
-	result := javaFixtureFactory.RunTest(t, addSourceSystemModules)
+	result := prepareForJavaTest.RunTest(t, addSourceSystemModules)
 
 	// check the existence of the source module
 	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
@@ -77,7 +77,7 @@
 `)
 
 func TestJavaSystemModulesImport(t *testing.T) {
-	result := javaFixtureFactory.RunTest(t, addPrebuiltSystemModules)
+	result := prepareForJavaTest.RunTest(t, addPrebuiltSystemModules)
 
 	// check the existence of the renamed prebuilt module
 	prebuiltSystemModules := result.ModuleForTests("system-modules", "android_common")
@@ -89,7 +89,7 @@
 }
 
 func TestJavaSystemModulesMixSourceAndPrebuilt(t *testing.T) {
-	result := javaFixtureFactory.RunTest(t,
+	result := prepareForJavaTest.RunTest(t,
 		addSourceSystemModules,
 		addPrebuiltSystemModules,
 	)
diff --git a/java/testing.go b/java/testing.go
index b0290dc..221ceb1 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -43,15 +43,42 @@
 	// Make sure that mutators and module types, e.g. prebuilt mutators available.
 	android.PrepareForTestWithAndroidBuildComponents,
 	// Make java build components available to the test.
-	android.FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+	android.FixtureRegisterWithContext(registerRequiredBuildComponentsForTest),
+	android.FixtureRegisterWithContext(registerJavaPluginBuildComponents),
+	// Additional files needed in tests that disallow non-existent source files.
+	// This includes files that are needed by all, or at least most, instances of a java module type.
+	android.MockFS{
+		// Needed for linter used by java_library.
+		"build/soong/java/lint_defaults.txt": nil,
+		// Needed for apps that do not provide their own.
+		"build/make/target/product/security": nil,
+	}.AddToFixture(),
 )
 
 // Test fixture preparer that will define default java modules, e.g. standard prebuilt modules.
 var PrepareForTestWithJavaDefaultModules = android.GroupFixturePreparers(
 	// Make sure that all the module types used in the defaults are registered.
 	PrepareForTestWithJavaBuildComponents,
+	// Additional files needed when test disallows non-existent source.
+	android.MockFS{
+		// Needed for framework-res
+		defaultJavaDir + "/AndroidManifest.xml": nil,
+		// Needed for framework
+		defaultJavaDir + "/framework/aidl": nil,
+		// Needed for various deps defined in GatherRequiredDepsForTest()
+		defaultJavaDir + "/a.java": nil,
+	}.AddToFixture(),
 	// The java default module definitions.
-	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", GatherRequiredDepsForTest()),
+	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", gatherRequiredDepsForTest()),
+	// Add dexpreopt compat libs (android.test.base, etc.) and a fake dex2oatd module.
+	dexpreopt.PrepareForTestWithDexpreoptCompatLibs,
+	dexpreopt.PrepareForTestWithFakeDex2oatd,
+)
+
+// Provides everything needed by dexpreopt.
+var PrepareForTestWithDexpreopt = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModules,
+	dexpreopt.PrepareForTestByEnablingDexpreopt,
 )
 
 var PrepareForTestWithOverlayBuildComponents = android.FixtureRegisterWithContext(registerOverlayBuildComponents)
@@ -177,7 +204,22 @@
 //
 // In particular this must register all the components that are used in the `Android.bp` snippet
 // returned by GatherRequiredDepsForTest()
+//
+// deprecated: Use test fixtures instead, e.g. PrepareForTestWithJavaBuildComponents
 func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
+	registerRequiredBuildComponentsForTest(ctx)
+
+	// Make sure that any tool related module types needed by dexpreopt have been registered.
+	dexpreopt.RegisterToolModulesForTest(ctx)
+}
+
+// registerRequiredBuildComponentsForTest registers the build components used by
+// PrepareForTestWithJavaDefaultModules.
+//
+// As functionality is moved out of here into separate FixturePreparer instances they should also
+// be moved into GatherRequiredDepsForTest for use by tests that have not yet switched to use test
+// fixtures.
+func registerRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
 	RegisterAARBuildComponents(ctx)
 	RegisterAppBuildComponents(ctx)
 	RegisterAppImportBuildComponents(ctx)
@@ -192,15 +234,32 @@
 	RegisterSdkLibraryBuildComponents(ctx)
 	RegisterStubsBuildComponents(ctx)
 	RegisterSystemModulesBuildComponents(ctx)
-
-	// Make sure that any tool related module types needed by dexpreopt have been registered.
-	dexpreopt.RegisterToolModulesForTest(ctx)
 }
 
 // Gather the module definitions needed by tests that depend upon code from this package.
 //
 // Returns an `Android.bp` snippet that defines the modules that are needed by this package.
+//
+// deprecated: Use test fixtures instead, e.g. PrepareForTestWithJavaDefaultModules
 func GatherRequiredDepsForTest() string {
+	bp := gatherRequiredDepsForTest()
+
+	// For class loader context and <uses-library> tests.
+	bp += dexpreopt.CompatLibDefinitionsForTest()
+
+	// Make sure that any tools needed for dexpreopting are defined.
+	bp += dexpreopt.BpToolModulesForTest()
+
+	return bp
+}
+
+// gatherRequiredDepsForTest gathers the module definitions used by
+// PrepareForTestWithJavaDefaultModules.
+//
+// As functionality is moved out of here into separate FixturePreparer instances they should also
+// be moved into GatherRequiredDepsForTest for use by tests that have not yet switched to use test
+// fixtures.
+func gatherRequiredDepsForTest() string {
 	var bp string
 
 	extraModules := []string{
@@ -232,24 +291,6 @@
 		`, extra)
 	}
 
-	// For class loader context and <uses-library> tests.
-	dexpreoptModules := []string{"android.test.runner"}
-	dexpreoptModules = append(dexpreoptModules, dexpreopt.CompatUsesLibs...)
-	dexpreoptModules = append(dexpreoptModules, dexpreopt.OptionalCompatUsesLibs...)
-
-	for _, extra := range dexpreoptModules {
-		bp += fmt.Sprintf(`
-			java_library {
-				name: "%s",
-				srcs: ["a.java"],
-				sdk_version: "none",
-				system_modules: "stable-core-platform-api-stubs-system-modules",
-				compile_dex: true,
-				installable: true,
-			}
-		`, extra)
-	}
-
 	bp += `
 		java_library {
 			name: "framework",
@@ -286,9 +327,6 @@
 		`, extra)
 	}
 
-	// Make sure that any tools needed for dexpreopting are defined.
-	bp += dexpreopt.BpToolModulesForTest()
-
 	// Make sure that the dex_bootjars singleton module is instantiated for the tests.
 	bp += `
 		dex_bootjars {
diff --git a/python/binary.go b/python/binary.go
index 6061ad4..5b0f080 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -61,7 +61,7 @@
 
 func PythonBinaryBp2Build(ctx android.TopDownMutatorContext) {
 	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build() {
+	if !ok || !m.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/rust/binary.go b/rust/binary.go
index df48916..dfe8744 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -87,7 +87,7 @@
 	deps = binary.baseCompiler.compilerDeps(ctx, deps)
 
 	if ctx.toolchain().Bionic() {
-		deps = bionicDeps(deps, Bool(binary.Properties.Static_executable))
+		deps = bionicDeps(ctx, deps, Bool(binary.Properties.Static_executable))
 		if Bool(binary.Properties.Static_executable) {
 			deps.CrtBegin = "crtbegin_static"
 		} else {
diff --git a/rust/bindgen.go b/rust/bindgen.go
index db69e23..bcc26b8 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -260,7 +260,7 @@
 func (b *bindgenDecorator) SourceProviderDeps(ctx DepsContext, deps Deps) Deps {
 	deps = b.BaseSourceProvider.SourceProviderDeps(ctx, deps)
 	if ctx.toolchain().Bionic() {
-		deps = bionicDeps(deps, false)
+		deps = bionicDeps(ctx, deps, false)
 	}
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
diff --git a/rust/compiler.go b/rust/compiler.go
index 98ad7ad..200af90 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -281,7 +281,7 @@
 	return deps
 }
 
-func bionicDeps(deps Deps, static bool) Deps {
+func bionicDeps(ctx DepsContext, deps Deps, static bool) Deps {
 	bionicLibs := []string{}
 	bionicLibs = append(bionicLibs, "liblog")
 	bionicLibs = append(bionicLibs, "libc")
@@ -294,9 +294,9 @@
 		deps.SharedLibs = append(deps.SharedLibs, bionicLibs...)
 	}
 
-	//TODO(b/141331117) libstd requires libgcc on Android
-	deps.StaticLibs = append(deps.StaticLibs, "libgcc")
-
+	if libRuntimeBuiltins := config.BuiltinsRuntimeLibrary(ctx.toolchain()); libRuntimeBuiltins != "" {
+		deps.StaticLibs = append(deps.StaticLibs, libRuntimeBuiltins)
+	}
 	return deps
 }
 
diff --git a/rust/config/global.go b/rust/config/global.go
index 12f4972..9208ddb 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -53,6 +53,7 @@
 
 	deviceGlobalRustFlags = []string{
 		"-C panic=abort",
+		"-Z link-native-libraries=no",
 	}
 
 	deviceGlobalLinkFlags = []string{
@@ -62,7 +63,7 @@
 		// Override cc's --no-undefined-version to allow rustc's generated alloc functions
 		"-Wl,--undefined-version",
 
-		"-Bdynamic",
+		"-Wl,-Bdynamic",
 		"-nostdlib",
 		"-Wl,--pack-dyn-relocs=android+relr",
 		"-Wl,--use-android-relr-tags",
diff --git a/rust/config/toolchain.go b/rust/config/toolchain.go
index 9525c38..a769f12 100644
--- a/rust/config/toolchain.go
+++ b/rust/config/toolchain.go
@@ -112,6 +112,10 @@
 	return ""
 }
 
+func BuiltinsRuntimeLibrary(t Toolchain) string {
+	return LibclangRuntimeLibrary(t, "builtins")
+}
+
 func LibFuzzerRuntimeLibrary(t Toolchain) string {
 	return LibclangRuntimeLibrary(t, "fuzzer")
 }
diff --git a/rust/library.go b/rust/library.go
index 7ff13ec..71fe1f5 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -404,7 +404,7 @@
 	deps = library.baseCompiler.compilerDeps(ctx, deps)
 
 	if ctx.toolchain().Bionic() && (library.dylib() || library.shared()) {
-		deps = bionicDeps(deps, false)
+		deps = bionicDeps(ctx, deps, false)
 		deps.CrtBegin = "crtbegin_so"
 		deps.CrtEnd = "crtend_so"
 	}
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 2be3c9c..fa0eb3f 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -26,91 +26,17 @@
 	PrepareForTestWithSdkBuildComponents,
 )
 
-func testSdkWithJava(t *testing.T, bp string) *android.TestResult {
-	t.Helper()
-
-	fs := map[string][]byte{
-		"Test.java":              nil,
-		"resource.test":          nil,
-		"aidl/foo/bar/Test.aidl": nil,
-
-		// For java_import
-		"prebuilt.jar": nil,
-
-		// For java_sdk_library
-		"api/current.txt":                                   nil,
-		"api/removed.txt":                                   nil,
-		"api/system-current.txt":                            nil,
-		"api/system-removed.txt":                            nil,
-		"api/test-current.txt":                              nil,
-		"api/test-removed.txt":                              nil,
-		"api/module-lib-current.txt":                        nil,
-		"api/module-lib-removed.txt":                        nil,
-		"api/system-server-current.txt":                     nil,
-		"api/system-server-removed.txt":                     nil,
-		"build/soong/scripts/gen-java-current-api-files.sh": nil,
-		"docs/known_doctags":                                nil,
-		"100/public/api/myjavalib.txt":                      nil,
-		"100/public/api/myjavalib-removed.txt":              nil,
-		"100/system/api/myjavalib.txt":                      nil,
-		"100/system/api/myjavalib-removed.txt":              nil,
-		"100/module-lib/api/myjavalib.txt":                  nil,
-		"100/module-lib/api/myjavalib-removed.txt":          nil,
-		"100/system-server/api/myjavalib.txt":               nil,
-		"100/system-server/api/myjavalib-removed.txt":       nil,
-	}
-
-	// for java_sdk_library tests
-	bp = `
-java_system_modules_import {
-	name: "core-current-stubs-system-modules",
-}
-java_system_modules_import {
-	name: "stable-core-platform-api-stubs-system-modules",
-}
-java_import {
-	name: "stable.core.platform.api.stubs",
-}
-java_import {
-	name: "android_stubs_current",
-}
-java_import {
-	name: "android_system_stubs_current",
-}
-java_import {
-	name: "android_test_stubs_current",
-}
-java_import {
-	name: "android_module_lib_stubs_current",
-}
-java_import {
-	name: "android_system_server_stubs_current",
-}
-java_import {
-	name: "core-lambda-stubs", 
-	sdk_version: "none",
-}
-java_import {
-	name: "ext", 
-	sdk_version: "none",
-}
-java_import {
-	name: "framework", 
-	sdk_version: "none",
-}
-prebuilt_apis {
-	name: "sdk",
-	api_dirs: ["100"],
-}
-` + bp
-
-	return testSdkWithFs(t, bp, fs)
-}
+var prepareForSdkTestWithJavaSdkLibrary = android.GroupFixturePreparers(
+	prepareForSdkTestWithJava,
+	java.PrepareForTestWithJavaDefaultModules,
+	java.PrepareForTestWithJavaSdkLibraryFiles,
+	java.FixtureWithLastReleaseApis("myjavalib"),
+)
 
 // Contains tests for SDK members provided by the java package.
 
 func TestSdkDependsOnSourceEvenWhenPrebuiltPreferred(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_header_libs: ["sdkmember"],
@@ -161,7 +87,10 @@
 }
 
 func TestBasicSdkWithJavaLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		prepareForSdkTestWithApex,
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_header_libs: ["sdkmember"],
@@ -242,7 +171,10 @@
 }
 
 func TestSnapshotWithJavaHeaderLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_header_libs: ["myjavalib"],
@@ -296,7 +228,10 @@
 }
 
 func TestHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			device_supported: false,
@@ -358,7 +293,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			host_supported: true,
@@ -426,7 +361,10 @@
 }
 
 func TestSnapshotWithJavaImplLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			java_libs: ["myjavalib"],
@@ -481,7 +419,7 @@
 }
 
 func TestSnapshotWithJavaBootLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			java_boot_libs: ["myjavalib"],
@@ -535,7 +473,10 @@
 }
 
 func TestHostSnapshotWithJavaImplLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			device_supported: false,
@@ -597,7 +538,7 @@
 }
 
 func TestSnapshotWithJavaTest(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			java_tests: ["myjavatests"],
@@ -649,7 +590,7 @@
 }
 
 func TestHostSnapshotWithJavaTest(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			device_supported: false,
@@ -710,7 +651,7 @@
 }
 
 func TestSnapshotWithJavaSystemModules(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_header_libs: ["exported-system-module"],
@@ -808,7 +749,7 @@
 }
 
 func TestHostSnapshotWithJavaSystemModules(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			device_supported: false,
@@ -888,7 +829,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithOsSpecificMembers(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			host_supported: true,
@@ -1021,7 +962,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1125,7 +1066,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SdkVersion_None(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1191,7 +1132,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1260,7 +1201,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_ApiScopes(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1350,7 +1291,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_ModuleLib(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1461,7 +1402,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SystemServer(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1551,7 +1492,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_NamingScheme(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1623,7 +1564,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_DoctagFiles(t *testing.T) {
-	result := testSdkWithJava(t, `
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
diff --git a/sdk/testing.go b/sdk/testing.go
index d21f425..ba40f67 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -44,7 +44,6 @@
 	`),
 
 	android.FixtureMergeMockFs(map[string][]byte{
-		"build/make/target/product/security":           nil,
 		"apex_manifest.json":                           nil,
 		"system/sepolicy/apex/myapex-file_contexts":    nil,
 		"system/sepolicy/apex/myapex2-file_contexts":   nil,
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 49f4961..1ae557a 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -521,7 +521,7 @@
 
 func ShBinaryBp2Build(ctx android.TopDownMutatorContext) {
 	m, ok := ctx.Module().(*ShBinary)
-	if !ok || !m.ConvertWithBp2build() {
+	if !ok || !m.ConvertWithBp2build(ctx) {
 		return
 	}
 
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index 1fabd92..45ccd04 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -112,9 +112,15 @@
 
 func stopRBE(ctx Context, config Config) {
 	cmd := Command(ctx, config, "stopRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd), "-shutdown")
-	if output, err := cmd.CombinedOutput(); err != nil {
+	output, err := cmd.CombinedOutput()
+	if err != nil {
 		ctx.Fatalf("rbe bootstrap with shutdown failed with: %v\n%s\n", err, output)
 	}
+
+	if len(output) > 0 {
+		fmt.Fprintln(ctx.Writer, "")
+		fmt.Fprintln(ctx.Writer, fmt.Sprintf("%s", output))
+	}
 }
 
 // DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.