Merge "Fix a typo in the definition of `varApexNames` in `java/dexpreopt_bootjars.go`."
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 e335de0..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
 }
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 babf48c..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)
@@ -934,9 +935,8 @@
 }
 
 type basePath struct {
-	path   string
-	config Config
-	rel    string
+	path string
+	rel  string
 }
 
 func (p basePath) Ext() string {
@@ -967,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{}
@@ -980,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
 	}
@@ -996,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
 	}
@@ -1090,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
@@ -1122,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)
@@ -1135,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
 }
 
@@ -1156,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 {
@@ -1181,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
@@ -1194,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
@@ -1428,7 +1435,8 @@
 		reportPathError(ctx, err)
 	}
 
-	outputPath := OutputPath{basePath{"", ctx.Config(), ""},
+	outputPath := OutputPath{basePath{"", ""},
+		ctx.Config().buildDir,
 		ctx.Config().BazelContext.OutputBase()}
 
 	return BazelOutPath{
@@ -1522,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
@@ -1530,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 {
@@ -1546,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)
 	}
 }
 
@@ -1557,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)
 	}
 }
 
@@ -1642,7 +1653,8 @@
 	}
 
 	base := InstallPath{
-		basePath:     basePath{partionPath, ctx.Config(), ""},
+		basePath:     basePath{partionPath, ""},
+		buildDir:     ctx.Config().buildDir,
 		partitionDir: partionPath,
 		makePath:     false,
 	}
@@ -1652,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,
 	}
@@ -1787,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 {
@@ -1796,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 aeaa6ff..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
diff --git a/android/testing.go b/android/testing.go
index 0dfe38c..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)
 }
 
@@ -899,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)
 		}
@@ -934,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/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/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 94efa4d..e2fc78c 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -21,8 +21,10 @@
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
 	"android/soong/shared"
+
 	"github.com/google/blueprint/bootstrap"
 
 	"android/soong/android"
@@ -191,13 +193,24 @@
 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", path, err)
+		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", path, err)
+		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)
 	}
 }
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 4801482..c0ba5ca 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -117,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/java/app_test.go b/java/app_test.go
index 2523533..825ad20 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -26,6 +26,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	"android/soong/genrule"
 )
 
@@ -2422,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
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 5565a05..7137f33 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -439,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)
 }
 
@@ -630,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 {
@@ -997,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_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/java_test.go b/java/java_test.go
index 31eeb6b..9924be7 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -133,6 +133,15 @@
 	}
 }
 
+// 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 {
@@ -831,7 +840,7 @@
 	}
 
 	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)
@@ -1220,33 +1229,22 @@
 }
 
 func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
-	config := TestConfig(t.TempDir(),
-		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) {
diff --git a/java/testing.go b/java/testing.go
index 295b8d0..221ceb1 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -45,12 +45,29 @@
 	// Make java build components available to the test.
 	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()),
 	// Add dexpreopt compat libs (android.test.base, etc.) and a fake dex2oatd module.
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/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
 	}