Merge changes I9a08bbc0,I049d1bb9

* changes:
  Add validations to RuleBuilderCommand
  Fix bug in RuleBuilderCommand.OrderOnlys
diff --git a/android/bazel.go b/android/bazel.go
index 1f7f7e6..6e87d57 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -126,6 +126,42 @@
 )
 
 var (
+	// Do not write BUILD files for these directories
+	// NOTE: this is not recursive
+	bp2buildDoNotWriteBuildFileList = []string{
+		// Don't generate these BUILD files - because external BUILD files already exist
+		"external/boringssl",
+		"external/brotli",
+		"external/dagger2",
+		"external/flatbuffers",
+		"external/gflags",
+		"external/google-fruit",
+		"external/grpc-grpc",
+		"external/grpc-grpc/test/core/util",
+		"external/grpc-grpc/test/cpp/common",
+		"external/grpc-grpc/third_party/address_sorting",
+		"external/nanopb-c",
+		"external/nos/host/generic",
+		"external/nos/host/generic/libnos",
+		"external/nos/host/generic/libnos/generator",
+		"external/nos/host/generic/libnos_datagram",
+		"external/nos/host/generic/libnos_transport",
+		"external/nos/host/generic/nugget/proto",
+		"external/perfetto",
+		"external/protobuf",
+		"external/rust/cxx",
+		"external/rust/cxx/demo",
+		"external/ruy",
+		"external/tensorflow",
+		"external/tensorflow/tensorflow/lite",
+		"external/tensorflow/tensorflow/lite/java",
+		"external/tensorflow/tensorflow/lite/kernels",
+		"external/tflite-support",
+		"external/tinyalsa_new",
+		"external/wycheproof",
+		"external/libyuv",
+	}
+
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
 	bp2buildDefaultConfig = Bp2BuildConfig{
 		"bionic":                Bp2BuildDefaultTrueRecursively,
@@ -190,11 +226,16 @@
 	}
 
 	// Used for quicker lookups
-	bp2buildModuleDoNotConvert = map[string]bool{}
-	mixedBuildsDisabled        = map[string]bool{}
+	bp2buildDoNotWriteBuildFile = map[string]bool{}
+	bp2buildModuleDoNotConvert  = map[string]bool{}
+	mixedBuildsDisabled         = map[string]bool{}
 )
 
 func init() {
+	for _, moduleName := range bp2buildDoNotWriteBuildFileList {
+		bp2buildDoNotWriteBuildFile[moduleName] = true
+	}
+
 	for _, moduleName := range bp2buildModuleDoNotConvertList {
 		bp2buildModuleDoNotConvert[moduleName] = true
 	}
@@ -204,6 +245,14 @@
 	}
 }
 
+func ShouldWriteBuildFileForDir(dir string) bool {
+	if _, ok := bp2buildDoNotWriteBuildFile[dir]; ok {
+		return false
+	} else {
+		return true
+	}
+}
+
 // MixedBuildsEnabled checks that a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
 func (b *BazelModuleBase) MixedBuildsEnabled(ctx BazelConversionPathContext) bool {
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index 85f701f..cb25fee 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -11,7 +11,7 @@
 	label := "//foo:bar"
 	arch := Arm64
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "cquery", expression: "kind(rule, deps(//:buildroot))"}: `@sourceroot//foo:bar|arm64>>out/foo/bar.txt`,
+		bazelCommand{command: "cquery", expression: "kind(rule, deps(@soong_injection//:buildroot))"}: `//foo:bar|arm64>>out/foo/bar.txt`,
 	})
 	g, ok := bazelContext.GetOutputFiles(label, arch)
 	if ok {
@@ -35,19 +35,19 @@
 	if err != nil {
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
-	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "main.bzl")); os.IsNotExist(err) {
+	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "main.bzl")); os.IsNotExist(err) {
 		t.Errorf("Expected main.bzl to exist, but it does not")
 	} else if err != nil {
 		t.Errorf("Unexpected error stating main.bzl %s", err)
 	}
 
-	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "BUILD.bazel")); os.IsNotExist(err) {
+	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "BUILD.bazel")); os.IsNotExist(err) {
 		t.Errorf("Expected BUILD.bazel to exist, but it does not")
 	} else if err != nil {
 		t.Errorf("Unexpected error stating BUILD.bazel %s", err)
 	}
 
-	if _, err := os.Stat(filepath.Join(baseDir, "bazel", "WORKSPACE.bazel")); os.IsNotExist(err) {
+	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "WORKSPACE.bazel")); os.IsNotExist(err) {
 		t.Errorf("Expected WORKSPACE.bazel to exist, but it does not")
 	} else if err != nil {
 		t.Errorf("Unexpected error stating WORKSPACE.bazel %s", err)
@@ -56,7 +56,7 @@
 
 func TestInvokeBazelPopulatesBuildStatements(t *testing.T) {
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "aquery", expression: "deps(//:buildroot)"}: `
+		bazelCommand{command: "aquery", expression: "deps(@soong_injection//:buildroot)"}: `
 {
   "artifacts": [{
     "id": 1,
@@ -105,7 +105,7 @@
 		outputBase:   "outputbase",
 		workspaceDir: "workspace_dir",
 	}
-	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(//:buildroot)"}
+	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(@soong_injection//:buildroot)"}
 	if _, exists := bazelCommandResults[aqueryCommand]; !exists {
 		bazelCommandResults[aqueryCommand] = "{}\n"
 	}
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index 13f4949..9727cc7 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -91,7 +91,7 @@
 		}
 		if m, t := SrcIsModuleWithTag(module); m != "" {
 			l := getOtherModuleLabel(ctx, m, t)
-			l.Bp_text = bpText
+			l.OriginalModuleName = bpText
 			labels.Includes = append(labels.Includes, l)
 		} else {
 			ctx.ModuleErrorf("%q, is not a module reference", module)
@@ -156,8 +156,8 @@
 func transformSubpackagePath(ctx BazelConversionPathContext, path bazel.Label) bazel.Label {
 	var newPath bazel.Label
 
-	// Don't transform Bp_text
-	newPath.Bp_text = path.Bp_text
+	// Don't transform OriginalModuleName
+	newPath.OriginalModuleName = path.OriginalModuleName
 
 	if strings.HasPrefix(path.Label, "//") {
 		// Assume absolute labels are already correct (e.g. //path/to/some/package:foo.h)
@@ -247,7 +247,7 @@
 		if m, tag := SrcIsModuleWithTag(p); m != "" {
 			l := getOtherModuleLabel(ctx, m, tag)
 			if !InList(l.Label, expandedExcludes) {
-				l.Bp_text = fmt.Sprintf(":%s", m)
+				l.OriginalModuleName = fmt.Sprintf(":%s", m)
 				labels.Includes = append(labels.Includes, l)
 			}
 		} else {
diff --git a/android/filegroup.go b/android/filegroup.go
index 2f13ab8..fc6850e 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -105,9 +105,34 @@
 	return module
 }
 
-func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
-	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
+func (fg *fileGroup) generateBazelBuildActions(ctx ModuleContext) bool {
+	if !fg.MixedBuildsEnabled(ctx) {
+		return false
+	}
 
+	bazelCtx := ctx.Config().BazelContext
+	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), ctx.Arch().ArchType)
+	if !ok {
+		return false
+	}
+
+	bazelOuts := make(Paths, 0, len(filePaths))
+	for _, p := range filePaths {
+		src := PathForBazelOut(ctx, p)
+		bazelOuts = append(bazelOuts, src)
+	}
+
+	fg.srcs = bazelOuts
+
+	return true
+}
+
+func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if fg.generateBazelBuildActions(ctx) {
+		return
+	}
+
+	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
 	if fg.properties.Path != nil {
 		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
 	}
diff --git a/android/fixture.go b/android/fixture.go
index 5fc668a..fd051a7 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -339,6 +339,15 @@
 	})
 }
 
+// PrepareForDebug_DO_NOT_SUBMIT puts the fixture into debug which will cause it to output its
+// state before running the test.
+//
+// This must only be added temporarily to a test for local debugging and must be removed from the
+// test before submitting.
+var PrepareForDebug_DO_NOT_SUBMIT = newSimpleFixturePreparer(func(fixture *fixture) {
+	fixture.debug = true
+})
+
 // GroupFixturePreparers creates a composite FixturePreparer that is equivalent to applying each of
 // the supplied FixturePreparer instances in order.
 //
@@ -708,6 +717,9 @@
 
 	// The error handler used to check the errors, if any, that are reported.
 	errorHandler FixtureErrorHandler
+
+	// Debug mode status
+	debug bool
 }
 
 func (f *fixture) Config() Config {
@@ -725,6 +737,11 @@
 func (f *fixture) RunTest() *TestResult {
 	f.t.Helper()
 
+	// If in debug mode output the state of the fixture before running the test.
+	if f.debug {
+		f.outputDebugState()
+	}
+
 	ctx := f.ctx
 
 	// Do not use the fixture's mockFS to initialize the config's mock file system if it has been
@@ -769,6 +786,39 @@
 	return result
 }
 
+func (f *fixture) outputDebugState() {
+	fmt.Printf("Begin Fixture State for %s\n", f.t.Name())
+	if len(f.config.env) == 0 {
+		fmt.Printf("  Fixture Env is empty\n")
+	} else {
+		fmt.Printf("  Begin Env\n")
+		for k, v := range f.config.env {
+			fmt.Printf("  - %s=%s\n", k, v)
+		}
+		fmt.Printf("  End Env\n")
+	}
+	if len(f.mockFS) == 0 {
+		fmt.Printf("  Mock FS is empty\n")
+	} else {
+		fmt.Printf("  Begin Mock FS Contents\n")
+		for p, c := range f.mockFS {
+			if c == nil {
+				fmt.Printf("\n  - %s: nil\n", p)
+			} else {
+				contents := string(c)
+				separator := "    ========================================================================"
+				fmt.Printf("  - %s\n%s\n", p, separator)
+				for i, line := range strings.Split(contents, "\n") {
+					fmt.Printf("      %6d:    %s\n", i+1, line)
+				}
+				fmt.Printf("%s\n", separator)
+			}
+		}
+		fmt.Printf("  End Mock FS Contents\n")
+	}
+	fmt.Printf("End Fixture State for %s\n", f.t.Name())
+}
+
 // NormalizePathForTesting removes the test invocation specific build directory from the supplied
 // path.
 //
diff --git a/apex/apex.go b/apex/apex.go
index f5e6fa9..6f02c47 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -92,12 +92,6 @@
 
 	Multilib apexMultilibProperties
 
-	// List of boot images that are embedded inside this APEX bundle.
-	//
-	// deprecated: Use Bootclasspath_fragments
-	// TODO(b/177892522): Remove after has been replaced by Bootclasspath_fragments
-	Boot_images []string
-
 	// List of bootclasspath fragments that are embedded inside this APEX bundle.
 	Bootclasspath_fragments []string
 
@@ -573,7 +567,7 @@
 	certificateTag  = dependencyTag{name: "certificate"}
 	executableTag   = dependencyTag{name: "executable", payload: true}
 	fsTag           = dependencyTag{name: "filesystem", payload: true}
-	bootImageTag    = dependencyTag{name: "bootImage", payload: true, sourceOnly: true}
+	bcpfTag         = dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true}
 	compatConfigTag = dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
 	javaLibTag      = dependencyTag{name: "javaLib", payload: true}
 	jniLibTag       = dependencyTag{name: "jniLib", payload: true}
@@ -753,8 +747,7 @@
 
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
-	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Boot_images...)
-	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Bootclasspath_fragments...)
+	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
@@ -1700,10 +1693,10 @@
 				} else {
 					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
 				}
-			case bootImageTag:
+			case bcpfTag:
 				{
 					if _, ok := child.(*java.BootImageModule); !ok {
-						ctx.PropertyErrorf("boot_images", "%q is not a boot_image module", depName)
+						ctx.PropertyErrorf("bootclasspath_fragments", "%q is not a boot_image module", depName)
 						return false
 					}
 					bootImageInfo := ctx.OtherModuleProvider(child, java.BootImageInfoProvider).(java.BootImageInfo)
@@ -1711,7 +1704,7 @@
 						dirInApex := filepath.Join("javalib", arch.String())
 						for _, f := range files {
 							androidMkModuleName := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-							// TODO(b/177892522) - consider passing in the boot image module here instead of nil
+							// TODO(b/177892522) - consider passing in the bootclasspath fragment module here instead of nil
 							af := newApexFile(ctx, f, androidMkModuleName, dirInApex, etc, nil)
 							filesInfo = append(filesInfo, af)
 						}
@@ -1932,18 +1925,18 @@
 					// dependencies. Track them.
 					return true
 				} else if java.IsbootImageContentDepTag(depTag) {
-					// Add the contents of the boot image to the apex.
+					// Add the contents of the bootclasspath fragment to the apex.
 					switch child.(type) {
 					case *java.Library, *java.SdkLibrary:
 						af := apexFileForJavaModule(ctx, child.(javaModule))
 						if !af.ok() {
-							ctx.PropertyErrorf("boot_images", "boot image content %q is not configured to be compiled into dex", depName)
+							ctx.PropertyErrorf("bootclasspath_fragments", "bootclasspath_fragment content %q is not configured to be compiled into dex", depName)
 							return false
 						}
 						filesInfo = append(filesInfo, af)
 						return true // track transitive dependencies
 					default:
-						ctx.PropertyErrorf("boot_images", "boot image content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
+						ctx.PropertyErrorf("bootclasspath_fragments", "bootclasspath_fragment content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
 					}
 
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
index d447d70..e18c2ea 100644
--- a/apex/boot_image_test.go
+++ b/apex/boot_image_test.go
@@ -25,7 +25,7 @@
 // Contains tests for boot_image logic from java/boot_image.go as the ART boot image requires
 // modules from the ART apex.
 
-var prepareForTestWithBootImage = android.GroupFixturePreparers(
+var prepareForTestWithBootclasspathFragment = android.GroupFixturePreparers(
 	java.PrepareForTestWithDexpreopt,
 	PrepareForTestWithApexBuildComponents,
 )
@@ -37,9 +37,9 @@
 	"system/sepolicy/apex/com.android.art-file_contexts": nil,
 })
 
-func TestBootImages(t *testing.T) {
+func TestBootclasspathFragments(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithBootImage,
+		prepareForTestWithBootclasspathFragment,
 		// Configure some libraries in the art and framework boot images.
 		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "platform:foo", "platform:bar"),
 		prepareForTestWithArtApex,
@@ -90,23 +90,23 @@
 			srcs: ["b.java"],
 		}
 
-		boot_image {
-			name: "art-boot-image",
+		bootclasspath_fragment {
+			name: "art-bootclasspath-fragment",
 			image_name: "art",
 			apex_available: [
 				"com.android.art",
 			],
 		}
 
-		boot_image {
-			name: "framework-boot-image",
+		bootclasspath_fragment {
+			name: "framework-bootclasspath-fragment",
 			image_name: "boot",
 		}
 `,
 	)
 
-	// Make sure that the framework-boot-image is using the correct configuration.
-	checkBootImage(t, result, "framework-boot-image", "platform:foo,platform:bar", `
+	// Make sure that the framework-bootclasspath-fragment is using the correct configuration.
+	checkBootclasspathFragment(t, result, "framework-bootclasspath-fragment", "platform:foo,platform:bar", `
 test_device/dex_bootjars/android/system/framework/arm/boot-foo.art
 test_device/dex_bootjars/android/system/framework/arm/boot-foo.oat
 test_device/dex_bootjars/android/system/framework/arm/boot-foo.vdex
@@ -121,8 +121,8 @@
 test_device/dex_bootjars/android/system/framework/arm64/boot-bar.vdex
 `)
 
-	// Make sure that the art-boot-image is using the correct configuration.
-	checkBootImage(t, result, "art-boot-image", "com.android.art:baz,com.android.art:quuz", `
+	// Make sure that the art-bootclasspath-fragment is using the correct configuration.
+	checkBootclasspathFragment(t, result, "art-bootclasspath-fragment", "com.android.art:baz,com.android.art:quuz", `
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.art
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.oat
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.vdex
@@ -138,7 +138,7 @@
 `)
 }
 
-func checkBootImage(t *testing.T, result *android.TestResult, moduleName string, expectedConfiguredModules string, expectedBootImageFiles string) {
+func checkBootclasspathFragment(t *testing.T, result *android.TestResult, moduleName string, expectedConfiguredModules string, expectedBootclasspathFragmentFiles string) {
 	t.Helper()
 
 	bootImage := result.ModuleForTests(moduleName, "android_common").Module().(*java.BootImageModule)
@@ -158,12 +158,12 @@
 		}
 	}
 
-	android.AssertTrimmedStringEquals(t, "invalid paths for "+moduleName, expectedBootImageFiles, strings.Join(allPaths, "\n"))
+	android.AssertTrimmedStringEquals(t, "invalid paths for "+moduleName, expectedBootclasspathFragmentFiles, strings.Join(allPaths, "\n"))
 }
 
-func TestBootImageInArtApex(t *testing.T) {
+func TestBootclasspathFragmentInArtApex(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithBootImage,
+		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
 
 		// Configure some libraries in the art boot image.
@@ -172,11 +172,11 @@
 		apex {
 			name: "com.android.art",
 			key: "com.android.art.key",
-			boot_images: [
-				"mybootimage",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
 			],
 			// bar (like foo) should be transitively included in this apex because it is part of the
-			// mybootimage boot_image. However, it is kept here to ensure that the apex dedups the files
+			// mybootclasspathfragment boot_image. However, it is kept here to ensure that the apex dedups the files
 			// correctly.
 			java_libs: [
 				"bar",
@@ -209,7 +209,7 @@
 		}
 
 		boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			image_name: "art",
 			apex_available: [
 				"com.android.art",
@@ -218,7 +218,7 @@
 
 		// Make sure that a preferred prebuilt doesn't affect the apex.
 		prebuilt_boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			image_name: "art",
 			prefer: true,
 			apex_available: [
@@ -247,13 +247,13 @@
 	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
 		`bar`,
 		`com.android.art.key`,
-		`mybootimage`,
+		`mybootclasspathfragment`,
 	})
 }
 
-func TestBootImageInPrebuiltArtApex(t *testing.T) {
+func TestBootclasspathFragmentInPrebuiltArtApex(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithBootImage,
+		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
 
 		android.FixtureMergeMockFs(android.MockFS{
@@ -294,7 +294,7 @@
 		}
 
 		prebuilt_boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			image_name: "art",
 			apex_available: [
 				"com.android.art",
@@ -308,23 +308,23 @@
 		`prebuilt_foo`,
 	})
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootimage", "android_common", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common", []string{
 		`dex2oatd`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 	})
 }
 
-func TestBootImageContentsNoName(t *testing.T) {
+func TestBootclasspathFragmentContentsNoName(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithBootImage,
+		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithMyapex,
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			boot_images: [
-				"mybootimage",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
 			],
 			updatable: false,
 		}
@@ -354,7 +354,7 @@
 		}
 
 		boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			contents: [
 				"foo",
 				"bar",
@@ -374,7 +374,7 @@
 
 	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		`myapex.key`,
-		`mybootimage`,
+		`mybootclasspathfragment`,
 	})
 }
 
diff --git a/apex/builder.go b/apex/builder.go
index e59dc96..b382a53 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -518,8 +518,7 @@
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
 	// Figure out if need to compress apex.
-	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex
-
+	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps()
 	if apexType == imageApex {
 		////////////////////////////////////////////////////////////////////////////////////
 		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
diff --git a/bazel/properties.go b/bazel/properties.go
index 4bb2391..5d3299b 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -35,11 +35,27 @@
 
 var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
 
-// Label is used to represent a Bazel compatible Label. Also stores the original bp text to support
-// string replacement.
+// Label is used to represent a Bazel compatible Label. Also stores the original
+// bp text to support string replacement.
 type Label struct {
-	Bp_text string
-	Label   string
+	// The string representation of a Bazel target label. This can be a relative
+	// or fully qualified label. These labels are used for generating BUILD
+	// files with bp2build.
+	Label string
+
+	// The original Soong/Blueprint module name that the label was derived from.
+	// This is used for replacing references to the original name with the new
+	// label, for example in genrule cmds.
+	//
+	// While there is a reversible 1:1 mapping from the module name to Bazel
+	// label with bp2build that could make computing the original module name
+	// from the label automatic, it is not the case for handcrafted targets,
+	// where modules can have a custom label mapping through the { bazel_module:
+	// { label: <label> } } property.
+	//
+	// With handcrafted labels, those modules don't go through bp2build
+	// conversion, but relies on handcrafted targets in the source tree.
+	OriginalModuleName string
 }
 
 // LabelList is used to represent a list of Bazel labels.
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 007d6d8..f1bf648 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -28,7 +28,7 @@
 	outputDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(outputDir)
 
-	buildToTargets, metrics := GenerateBazelTargets(ctx)
+	buildToTargets, metrics := GenerateBazelTargets(ctx, true)
 
 	filesToWrite := CreateBazelFiles(nil, buildToTargets, ctx.mode)
 
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index b7a2810..08790d1 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -176,7 +176,7 @@
 	return attributes
 }
 
-func GenerateBazelTargets(ctx *CodegenContext) (map[string]BazelTargets, CodegenMetrics) {
+func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (map[string]BazelTargets, CodegenMetrics) {
 	buildFileToTargets := make(map[string]BazelTargets)
 	buildFileToAppend := make(map[string]bool)
 
@@ -185,9 +185,13 @@
 		RuleClassCount: make(map[string]int),
 	}
 
+	dirs := make(map[string]bool)
+
 	bpCtx := ctx.Context()
 	bpCtx.VisitAllModules(func(m blueprint.Module) {
 		dir := bpCtx.ModuleDir(m)
+		dirs[dir] = true
+
 		var t BazelTarget
 
 		switch ctx.Mode() {
@@ -230,6 +234,17 @@
 
 		buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
 	})
+	if generateFilegroups {
+		// Add a filegroup target that exposes all sources in the subtree of this package
+		// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
+		for dir, _ := range dirs {
+			buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
+				name:      "bp2build_all_srcs",
+				content:   `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`,
+				ruleClass: "filegroup",
+			})
+		}
+	}
 
 	return buildFileToTargets, metrics
 }
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 6b47cd1..114b668 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -2,6 +2,7 @@
 
 import (
 	"android/soong/android"
+	"fmt"
 	"reflect"
 	"sort"
 	"strings"
@@ -48,6 +49,10 @@
 func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(buildToTargets))
 	for _, dir := range android.SortedStringKeys(buildToTargets) {
+		if !android.ShouldWriteBuildFileForDir(dir) {
+			fmt.Printf("[bp2build] Not writing generated BUILD file for dir: '%s'\n", dir)
+			continue
+		}
 		targets := buildToTargets[dir]
 		sort.Slice(targets, func(i, j int) bool {
 			// this will cover all bp2build generated targets
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 80ad3b6..15a6335 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -94,7 +94,7 @@
 // contain every file in buildFilesDir and srcDir excluding the files in
 // exclude. Collects every directory encountered during the traversal of srcDir
 // into acc.
-func plantSymlinkForestRecursive(topdir string, forestDir string, buildFilesDir string, srcDir string, exclude *node, acc *[]string) {
+func plantSymlinkForestRecursive(topdir string, forestDir string, buildFilesDir string, srcDir string, exclude *node, acc *[]string, okay *bool) {
 	if exclude != nil && exclude.excluded {
 		// This directory is not needed, bail out
 		return
@@ -125,53 +125,59 @@
 		}
 
 		// The full paths of children in the input trees and in the output tree
-		fp := shared.JoinPath(forestDir, f)
-		sp := shared.JoinPath(srcDir, f)
-		bp := shared.JoinPath(buildFilesDir, f)
+		forestChild := shared.JoinPath(forestDir, f)
+		srcChild := shared.JoinPath(srcDir, f)
+		buildFilesChild := shared.JoinPath(buildFilesDir, f)
 
 		// Descend in the exclusion tree, if there are any excludes left
-		var ce *node
+		var excludeChild *node
 		if exclude == nil {
-			ce = nil
+			excludeChild = nil
 		} else {
-			ce = exclude.children[f]
+			excludeChild = exclude.children[f]
 		}
 
-		sf, sExists := srcDirMap[f]
-		bf, bExists := buildFilesMap[f]
-		excluded := ce != nil && ce.excluded
+		srcChildEntry, sExists := srcDirMap[f]
+		buildFilesChildEntry, bExists := buildFilesMap[f]
+		excluded := excludeChild != nil && excludeChild.excluded
 
 		if excluded {
 			continue
 		}
 
 		if !sExists {
-			if bf.IsDir() && ce != nil {
+			if buildFilesChildEntry.IsDir() && excludeChild != nil {
 				// Not in the source tree, but we have to exclude something from under
 				// this subtree, so descend
-				plantSymlinkForestRecursive(topdir, fp, bp, sp, ce, acc)
+				plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
 			} else {
 				// Not in the source tree, symlink BUILD file
-				symlinkIntoForest(topdir, fp, bp)
+				symlinkIntoForest(topdir, forestChild, buildFilesChild)
 			}
 		} else if !bExists {
-			if sf.IsDir() && ce != nil {
+			if srcChildEntry.IsDir() && excludeChild != nil {
 				// Not in the build file tree, but we have to exclude something from
 				// under this subtree, so descend
-				plantSymlinkForestRecursive(topdir, fp, bp, sp, ce, acc)
+				plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
 			} else {
 				// Not in the build file tree, symlink source tree, carry on
-				symlinkIntoForest(topdir, fp, sp)
+				symlinkIntoForest(topdir, forestChild, srcChild)
 			}
-		} else if sf.IsDir() && bf.IsDir() {
+		} else if srcChildEntry.IsDir() && buildFilesChildEntry.IsDir() {
 			// Both are directories. Descend.
-			plantSymlinkForestRecursive(topdir, fp, bp, sp, ce, acc)
+			plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
+		} else if !srcChildEntry.IsDir() && !buildFilesChildEntry.IsDir() {
+			// Neither is a directory. Prioritize BUILD files generated by bp2build
+			// over any BUILD file imported into external/.
+			fmt.Fprintf(os.Stderr, "Both '%s' and '%s' exist, symlinking the former to '%s'\n",
+				buildFilesChild, srcChild, forestChild)
+			symlinkIntoForest(topdir, forestChild, buildFilesChild)
 		} else {
 			// Both exist and one is a file. This is an error.
 			fmt.Fprintf(os.Stderr,
-				"Conflict in workspace symlink tree creation: both '%s' and '%s' exist and at least one of them is a file\n",
-				sp, bp)
-			os.Exit(1)
+				"Conflict in workspace symlink tree creation: both '%s' and '%s' exist and exactly one is a directory\n",
+				srcChild, buildFilesChild)
+			*okay = false
 		}
 	}
 }
@@ -184,6 +190,10 @@
 	deps := make([]string, 0)
 	os.RemoveAll(shared.JoinPath(topdir, forest))
 	excludeTree := treeFromExcludePathList(exclude)
-	plantSymlinkForestRecursive(topdir, forest, buildFiles, srcDir, excludeTree, &deps)
+	okay := true
+	plantSymlinkForestRecursive(topdir, forest, buildFiles, srcDir, excludeTree, &deps, &okay)
+	if !okay {
+		os.Exit(1)
+	}
 	return deps
 }
diff --git a/bp2build/testing.go b/bp2build/testing.go
index ef3a78f..c4661ea 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -183,6 +183,7 @@
 
 // Helper method for tests to easily access the targets in a dir.
 func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) BazelTargets {
-	buildFileToTargets, _ := GenerateBazelTargets(codegenCtx)
+	// TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely
+	buildFileToTargets, _ := GenerateBazelTargets(codegenCtx, false)
 	return buildFileToTargets[dir]
 }
diff --git a/cc/builder.go b/cc/builder.go
index da8501c..ad7e1e6 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -968,7 +968,7 @@
 func transformBinaryPrefixSymbols(ctx android.ModuleContext, prefix string, inputFile android.Path,
 	flags builderFlags, outputFile android.WritablePath) {
 
-	objcopyCmd := gccCmd(flags.toolchain, "objcopy")
+	objcopyCmd := "${config.ClangBin}/llvm-objcopy"
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        prefixSymbols,
diff --git a/cc/makevars.go b/cc/makevars.go
index 48d5636..923472a 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -280,9 +280,9 @@
 		ctx.Strict(makePrefix+"STRIP", "${config.MacStripPath}")
 	} else {
 		ctx.Strict(makePrefix+"AR", "${config.ClangBin}/llvm-ar")
-		ctx.Strict(makePrefix+"READELF", gccCmd(toolchain, "readelf"))
-		ctx.Strict(makePrefix+"NM", gccCmd(toolchain, "nm"))
-		ctx.Strict(makePrefix+"STRIP", gccCmd(toolchain, "strip"))
+		ctx.Strict(makePrefix+"READELF", "${config.ClangBin}/llvm-readelf")
+		ctx.Strict(makePrefix+"NM", "${config.ClangBin}/llvm-nm")
+		ctx.Strict(makePrefix+"STRIP", "${config.ClangBin}/llvm-strip")
 	}
 
 	if target.Os.Class == android.Device {
diff --git a/cc/stub_library.go b/cc/stub_library.go
index 76d236c..81c8be7 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -30,7 +30,7 @@
 }
 
 // Check if the module defines stub, or itself is stub
-func isStubTarget(m *Module) bool {
+func IsStubTarget(m *Module) bool {
 	if m.IsStubs() || m.HasStubsVariants() {
 		return true
 	}
@@ -61,7 +61,7 @@
 	// Visit all generated soong modules and store stub library file names.
 	ctx.VisitAllModules(func(module android.Module) {
 		if m, ok := module.(*Module); ok {
-			if isStubTarget(m) {
+			if IsStubTarget(m) {
 				if name := getInstalledFileName(m); name != "" {
 					s.stubLibraryMap[name] = true
 				}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index acf1ac1..044689e 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -175,7 +175,7 @@
 }
 
 func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
-	bazelConversionRequested := configuration.IsEnvTrue("GENERATE_BAZEL_FILES") || bp2buildMarker != ""
+	bazelConversionRequested := bp2buildMarker != ""
 	mixedModeBuild := configuration.BazelContext.BazelEnabled()
 	generateQueryView := bazelQueryViewDir != ""
 	jsonModuleFile := configuration.Getenv("SOONG_DUMP_JSON_MODULE_GRAPH")
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index edc8a42..e2ce772 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -27,7 +27,7 @@
 
 	// Ignore metrics reporting for queryview, since queryview is already a full-repo
 	// conversion and can use data from bazel query directly.
-	buildToTargets, _ := bp2build.GenerateBazelTargets(ctx)
+	buildToTargets, _ := bp2build.GenerateBazelTargets(ctx, true)
 
 	filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets, bp2build.QueryView)
 	for _, f := range filesToWrite {
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 3cdaa64..38684d3 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -9,11 +9,13 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-linkerconfig",
     ],
     srcs: [
         "bootimg.go",
         "filesystem.go",
         "logical_partition.go",
+        "system_image.go",
         "vbmeta.go",
         "testing.go",
     ],
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index cf98717..b2caa51 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -31,6 +31,7 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("android_filesystem", filesystemFactory)
+	ctx.RegisterModuleType("android_system_image", systemImageFactory)
 }
 
 type filesystem struct {
@@ -39,6 +40,9 @@
 
 	properties filesystemProperties
 
+	// Function that builds extra files under the root directory and returns the files
+	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
+
 	output     android.OutputPath
 	installDir android.InstallPath
 }
@@ -87,10 +91,14 @@
 // partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
 func filesystemFactory() android.Module {
 	module := &filesystem{}
+	initFilesystemModule(module)
+	return module
+}
+
+func initFilesystemModule(module *filesystem) {
 	module.AddProperties(&module.properties)
 	android.InitPackageModule(module)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-	return module
 }
 
 var dependencyTag = struct {
@@ -148,7 +156,7 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 }
 
-// root zip will contain stuffs like dirs or symlinks.
+// root zip will contain extra files/dirs that are not from the `deps` property.
 func (f *filesystem) buildRootZip(ctx android.ModuleContext) android.OutputPath {
 	rootDir := android.PathForModuleGen(ctx, "root").OutputPath
 	builder := android.NewRuleBuilder(pctx, ctx)
@@ -182,15 +190,34 @@
 		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
 	}
 
-	zipOut := android.PathForModuleGen(ctx, "root.zip").OutputPath
+	// create extra files if there's any
+	rootForExtraFiles := android.PathForModuleGen(ctx, "root-extra").OutputPath
+	var extraFiles android.OutputPaths
+	if f.buildExtraFiles != nil {
+		extraFiles = f.buildExtraFiles(ctx, rootForExtraFiles)
+		for _, f := range extraFiles {
+			rel, _ := filepath.Rel(rootForExtraFiles.String(), f.String())
+			if strings.HasPrefix(rel, "..") {
+				panic(fmt.Errorf("%q is not under %q\n", f, rootForExtraFiles))
+			}
+		}
+	}
 
-	builder.Command().
-		BuiltTool("soong_zip").
-		FlagWithOutput("-o ", zipOut).
+	// Zip them all
+	zipOut := android.PathForModuleGen(ctx, "root.zip").OutputPath
+	zipCommand := builder.Command().BuiltTool("soong_zip")
+	zipCommand.FlagWithOutput("-o ", zipOut).
 		FlagWithArg("-C ", rootDir.String()).
 		Flag("-L 0"). // no compression because this will be unzipped soon
 		FlagWithArg("-D ", rootDir.String()).
 		Flag("-d") // include empty directories
+	if len(extraFiles) > 0 {
+		zipCommand.FlagWithArg("-C ", rootForExtraFiles.String())
+		for _, f := range extraFiles {
+			zipCommand.FlagWithInput("-f ", f)
+		}
+	}
+
 	builder.Command().Text("rm -rf").Text(rootDir.String())
 
 	builder.Build("zip_root", fmt.Sprintf("zipping root contents for %s", ctx.ModuleName()))
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 880b177..e78fdff 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 func TestMain(m *testing.M) {
@@ -27,6 +28,7 @@
 
 var fixture = android.GroupFixturePreparers(
 	android.PrepareForIntegrationTestWithAndroid,
+	cc.PrepareForIntegrationTestWithCc,
 	PrepareForTestWithFilesystemBuildComponents,
 )
 
@@ -40,3 +42,35 @@
 	// produces "myfilesystem.img"
 	result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
 }
+
+func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+	        android_system_image {
+			name: "myfilesystem",
+			deps: [
+				"libfoo",
+                                "libbar",
+			],
+			linker_config_src: "linker.config.json",
+		}
+
+		cc_library {
+			name: "libfoo",
+			stubs: {
+				symbol_file: "libfoo.map.txt",
+			},
+		}
+
+		cc_library {
+			name: "libbar",
+		}
+	`)
+
+	module := result.ModuleForTests("myfilesystem", "android_common")
+	output := module.Output("system/etc/linker.config.pb")
+
+	android.AssertStringDoesContain(t, "linker.config.pb should have libfoo",
+		output.RuleParams.Command, "libfoo.so")
+	android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
+		output.RuleParams.Command, "libbar.so")
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
new file mode 100644
index 0000000..a7c9143
--- /dev/null
+++ b/filesystem/system_image.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package filesystem
+
+import (
+	"android/soong/android"
+	"android/soong/linkerconfig"
+)
+
+type systemImage struct {
+	filesystem
+
+	properties systemImageProperties
+}
+
+type systemImageProperties struct {
+	// Path to the input linker config json file.
+	Linker_config_src *string
+}
+
+// android_system_image is a specialization of android_filesystem for the 'system' partition.
+// Currently, the only difference is the inclusion of linker.config.pb file which specifies
+// the provided and the required libraries to and from APEXes.
+func systemImageFactory() android.Module {
+	module := &systemImage{}
+	module.AddProperties(&module.properties)
+	module.filesystem.buildExtraFiles = module.buildExtraFiles
+	initFilesystemModule(&module.filesystem)
+	return module
+}
+
+func (s *systemImage) buildExtraFiles(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths {
+	lc := s.buildLinkerConfigFile(ctx, root)
+	// Add more files if needed
+	return []android.OutputPath{lc}
+}
+
+func (s *systemImage) buildLinkerConfigFile(ctx android.ModuleContext, root android.OutputPath) android.OutputPath {
+	input := android.PathForModuleSrc(ctx, android.String(s.properties.Linker_config_src))
+	output := root.Join(ctx, "system", "etc", "linker.config.pb")
+	var otherModules []android.Module
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		// Don't track direct dependencies that aren't not packaged
+		if parent == s {
+			if pi, ok := ctx.OtherModuleDependencyTag(child).(android.PackagingItem); !ok || !pi.IsPackagingItem() {
+				return false
+			}
+		}
+		otherModules = append(otherModules, child)
+		return true
+	})
+	builder := android.NewRuleBuilder(pctx, ctx)
+	linkerconfig.BuildLinkerConfig(ctx, builder, input, otherModules, output)
+	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
+	return output
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 3a9aecc..77dae75 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -856,8 +856,8 @@
 			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
 		}
 		for _, l := range allReplacements.Includes {
-			bpLoc := fmt.Sprintf("$(location %s)", l.Bp_text)
-			bpLocs := fmt.Sprintf("$(locations %s)", l.Bp_text)
+			bpLoc := fmt.Sprintf("$(location %s)", l.OriginalModuleName)
+			bpLocs := fmt.Sprintf("$(locations %s)", l.OriginalModuleName)
 			bazelLoc := fmt.Sprintf("$(location %s)", l.Label)
 			bazelLocs := fmt.Sprintf("$(locations %s)", l.Label)
 			cmd = strings.Replace(cmd, bpLoc, bazelLoc, -1)
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 3ecb977..e575085 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -156,16 +156,24 @@
 
 		// A source module that has been replaced by a prebuilt can never be the primary module.
 		if module.IsReplacedByPrebuilt() {
-			ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
-				if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
-					primary = false
-				} else {
-					ctx.ModuleErrorf(
-						"hiddenapi has determined that the source module %q should be ignored as it has been"+
-							" replaced by the prebuilt module %q but unfortunately it does not provide a"+
-							" suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
-				}
-			})
+			if ctx.HasProvider(android.ApexInfoProvider) {
+				// The source module is in an APEX but the prebuilt module on which it depends is not in an
+				// APEX and so is not the one that will actually be used for hidden API processing. That
+				// means it is not possible to check to see if it is a suitable replacement so just assume
+				// that it is.
+				primary = false
+			} else {
+				ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
+					if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
+						primary = false
+					} else {
+						ctx.ModuleErrorf(
+							"hiddenapi has determined that the source module %q should be ignored as it has been"+
+								" replaced by the prebuilt module %q but unfortunately it does not provide a"+
+								" suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
+					}
+				})
+			}
 		}
 	}
 	h.primary = primary
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index e5dba33..ebf541d 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -64,7 +64,7 @@
 func (p *HiddenAPIFlagFileProperties) hiddenAPIFlagFileInfo(ctx android.ModuleContext) hiddenAPIFlagFileInfo {
 	info := hiddenAPIFlagFileInfo{categoryToPaths: map[*hiddenAPIFlagFileCategory]android.Paths{}}
 	for _, category := range hiddenAPIFlagFileCategories {
-		paths := android.PathsForModuleSrc(ctx, category.propertyAccessor(p))
+		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
 		info.categoryToPaths[category] = paths
 	}
 	return info
@@ -74,9 +74,9 @@
 	// propertyName is the name of the property for this category.
 	propertyName string
 
-	// propertyAccessor retrieves the value of the property for this category from the set of
+	// propertyValueReader retrieves the value of the property for this category from the set of
 	// properties.
-	propertyAccessor func(properties *HiddenAPIFlagFileProperties) []string
+	propertyValueReader func(properties *HiddenAPIFlagFileProperties) []string
 
 	// commandMutator adds the appropriate command line options for this category to the supplied
 	// command
@@ -87,7 +87,7 @@
 	// See HiddenAPIFlagFileProperties.Unsupported
 	{
 		propertyName: "unsupported",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -97,7 +97,7 @@
 	// See HiddenAPIFlagFileProperties.Removed
 	{
 		propertyName: "removed",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Removed
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -107,7 +107,7 @@
 	// See HiddenAPIFlagFileProperties.Max_target_r_low_priority
 	{
 		propertyName: "max_target_r_low_priority",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_r_low_priority
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -117,7 +117,7 @@
 	// See HiddenAPIFlagFileProperties.Max_target_q
 	{
 		propertyName: "max_target_q",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_q
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -127,7 +127,7 @@
 	// See HiddenAPIFlagFileProperties.Max_target_p
 	{
 		propertyName: "max_target_p",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_p
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -137,7 +137,7 @@
 	// See HiddenAPIFlagFileProperties.Max_target_o_low_priority
 	{
 		propertyName: "max_target_o_low_priority",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_o_low_priority
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -147,7 +147,7 @@
 	// See HiddenAPIFlagFileProperties.Blocked
 	{
 		propertyName: "blocked",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Blocked
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
@@ -157,7 +157,7 @@
 	// See HiddenAPIFlagFileProperties.Unsupported_packages
 	{
 		propertyName: "unsupported_packages",
-		propertyAccessor: func(properties *HiddenAPIFlagFileProperties) []string {
+		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported_packages
 		},
 		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
diff --git a/java/lint.go b/java/lint.go
index aa308e6..a77daa8 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -200,7 +200,7 @@
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
 
 	cmd := rule.Command().
-		BuiltTool("lint-project-xml").
+		BuiltTool("lint_project_xml").
 		FlagWithOutput("--project_out ", projectXMLPath).
 		FlagWithOutput("--config_out ", configXMLPath).
 		FlagWithArg("--name ", ctx.ModuleName())
@@ -279,6 +279,19 @@
 	return manifestPath
 }
 
+func (l *linter) getBaselineFilepath(ctx android.ModuleContext) android.OptionalPath {
+	var lintBaseline android.OptionalPath
+	if lintFilename := proptools.StringDefault(l.properties.Lint.Baseline_filename, "lint-baseline.xml"); lintFilename != "" {
+		if String(l.properties.Lint.Baseline_filename) != "" {
+			// if manually specified, we require the file to exist
+			lintBaseline = android.OptionalPathForPath(android.PathForModuleSrc(ctx, lintFilename))
+		} else {
+			lintBaseline = android.ExistentPathForSource(ctx, ctx.ModuleDir(), lintFilename)
+		}
+	}
+	return lintBaseline
+}
+
 func (l *linter) lint(ctx android.ModuleContext) {
 	if !l.enabled() {
 		return
@@ -381,17 +394,9 @@
 		cmd.FlagWithArg("--check ", checkOnly)
 	}
 
-	if lintFilename := proptools.StringDefault(l.properties.Lint.Baseline_filename, "lint-baseline.xml"); lintFilename != "" {
-		var lintBaseline android.OptionalPath
-		if String(l.properties.Lint.Baseline_filename) != "" {
-			// if manually specified, we require the file to exist
-			lintBaseline = android.OptionalPathForPath(android.PathForModuleSrc(ctx, lintFilename))
-		} else {
-			lintBaseline = android.ExistentPathForSource(ctx, ctx.ModuleDir(), lintFilename)
-		}
-		if lintBaseline.Valid() {
-			cmd.FlagWithInput("--baseline ", lintBaseline.Path())
-		}
+	lintBaseline := l.getBaselineFilepath(ctx)
+	if lintBaseline.Valid() {
+		cmd.FlagWithInput("--baseline ", lintBaseline.Path())
 	}
 
 	cmd.Text("|| (").Text("if [ -e").Input(text).Text("]; then cat").Input(text).Text("; fi; exit 7)")
diff --git a/linkerconfig/Android.bp b/linkerconfig/Android.bp
index 9161f0e..76a6325 100644
--- a/linkerconfig/Android.bp
+++ b/linkerconfig/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-cc",
         "soong-etc",
     ],
     srcs: [
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 241cac6..8d0ad7c 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -15,11 +15,15 @@
 package linkerconfig
 
 import (
-	"android/soong/android"
-	"android/soong/etc"
 	"fmt"
+	"sort"
+	"strings"
 
 	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/etc"
 )
 
 var (
@@ -81,24 +85,59 @@
 }
 
 func (l *linkerConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	inputFile := android.PathForModuleSrc(ctx, android.String(l.properties.Src))
-	l.outputFilePath = android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
-	l.installDirPath = android.PathForModuleInstall(ctx, "etc")
-	linkerConfigRule := android.NewRuleBuilder(pctx, ctx)
-	linkerConfigRule.Command().
-		BuiltTool("conv_linker_config").
-		Flag("proto").
-		FlagWithInput("-s ", inputFile).
-		FlagWithOutput("-o ", l.outputFilePath)
-	linkerConfigRule.Build("conv_linker_config",
-		"Generate linker config protobuf "+l.outputFilePath.String())
+	input := android.PathForModuleSrc(ctx, android.String(l.properties.Src))
+	output := android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
 
+	builder := android.NewRuleBuilder(pctx, ctx)
+	BuildLinkerConfig(ctx, builder, input, nil, output)
+	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
+
+	l.outputFilePath = output
+	l.installDirPath = android.PathForModuleInstall(ctx, "etc")
 	if !proptools.BoolDefault(l.properties.Installable, true) {
 		l.SkipInstall()
 	}
 	ctx.InstallFile(l.installDirPath, l.outputFilePath.Base(), l.outputFilePath)
 }
 
+func BuildLinkerConfig(ctx android.ModuleContext, builder *android.RuleBuilder,
+	input android.Path, otherModules []android.Module, output android.OutputPath) {
+
+	// First, convert the input json to protobuf format
+	interimOutput := android.PathForModuleOut(ctx, "temp.pb")
+	builder.Command().
+		BuiltTool("conv_linker_config").
+		Flag("proto").
+		FlagWithInput("-s ", input).
+		FlagWithOutput("-o ", interimOutput)
+
+	// Secondly, if there's provideLibs gathered from otherModules, append them
+	var provideLibs []string
+	for _, m := range otherModules {
+		if c, ok := m.(*cc.Module); ok && cc.IsStubTarget(c) {
+			for _, ps := range c.PackagingSpecs() {
+				provideLibs = append(provideLibs, ps.FileName())
+			}
+		}
+	}
+	provideLibs = android.FirstUniqueStrings(provideLibs)
+	sort.Strings(provideLibs)
+	if len(provideLibs) > 0 {
+		builder.Command().
+			BuiltTool("conv_linker_config").
+			Flag("append").
+			FlagWithInput("-s ", interimOutput).
+			FlagWithOutput("-o ", output).
+			FlagWithArg("--key ", "provideLibs").
+			FlagWithArg("--value ", proptools.ShellEscapeIncludingSpaces(strings.Join(provideLibs, " ")))
+	} else {
+		// If nothing to add, just cp to the final output
+		builder.Command().Text("cp").Input(interimOutput).Output(output)
+	}
+	builder.Temporary(interimOutput)
+	builder.DeleteTemporaryFiles()
+}
+
 // linker_config generates protobuf file from json file. This protobuf file will be used from
 // linkerconfig while generating ld.config.txt. Format of this file can be found from
 // https://android.googlesource.com/platform/system/linkerconfig/+/master/README.md
diff --git a/python/tests/Android.bp b/python/tests/Android.bp
index 0e8eef6..a656859 100644
--- a/python/tests/Android.bp
+++ b/python/tests/Android.bp
@@ -23,7 +23,10 @@
         "par_test.py",
         "testpkg/par_test.py",
     ],
-
+    // Is not implemented as a python unittest
+    test_options: {
+        unit_test: false,
+    },
     version: {
         py2: {
             enabled: true,
@@ -43,7 +46,10 @@
         "par_test.py",
         "testpkg/par_test.py",
     ],
-
+    // Is not implemented as a python unittest
+    test_options: {
+        unit_test: false,
+    },
     version: {
         py3: {
             embedded_launcher: true,
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 8edb7c9..ba0ab93 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -79,7 +79,7 @@
 	// binary must expect arguments in a similar fashion to bindgen, e.g.
 	//
 	// "my_bindgen [flags] wrapper_header.h -o [output_path] -- [clang flags]"
-	Custom_bindgen string `android:"path"`
+	Custom_bindgen string
 }
 
 type bindgenDecorator struct {
diff --git a/rust/compiler.go b/rust/compiler.go
index bfc23b2..a3f02c0 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -77,10 +77,10 @@
 	Lints *string
 
 	// flags to pass to rustc. To enable configuration options or features, use the "cfgs" or "features" properties.
-	Flags []string `android:"path,arch_variant"`
+	Flags []string `android:"arch_variant"`
 
 	// flags to pass to the linker
-	Ld_flags []string `android:"path,arch_variant"`
+	Ld_flags []string `android:"arch_variant"`
 
 	// list of rust rlib crate dependencies
 	Rlibs []string `android:"arch_variant"`
diff --git a/scripts/Android.bp b/scripts/Android.bp
index c54b2bc..b0a8669 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -219,14 +219,25 @@
 }
 
 python_binary_host {
-    name: "lint-project-xml",
-    main: "lint-project-xml.py",
+    name: "lint_project_xml",
+    main: "lint_project_xml.py",
     srcs: [
-        "lint-project-xml.py",
+        "lint_project_xml.py",
     ],
     libs: ["ninja_rsp"],
 }
 
+python_test_host {
+    name: "lint_project_xml_test",
+    main: "lint_project_xml_test.py",
+    srcs: [
+        "lint_project_xml_test.py",
+        "lint_project_xml.py",
+    ],
+    libs: ["ninja_rsp"],
+    test_suites: ["general-tests"],
+}
+
 python_binary_host {
     name: "gen-kotlin-build-file.py",
     main: "gen-kotlin-build-file.py",
diff --git a/scripts/lint-project-xml.py b/scripts/lint_project_xml.py
similarity index 85%
rename from scripts/lint-project-xml.py
rename to scripts/lint_project_xml.py
index f1ef85d..74aebc1 100755
--- a/scripts/lint-project-xml.py
+++ b/scripts/lint_project_xml.py
@@ -18,6 +18,7 @@
 """This file generates project.xml and lint.xml files used to drive the Android Lint CLI tool."""
 
 import argparse
+from xml.dom import minidom
 
 from ninja_rsp import NinjaRspFileReader
 
@@ -73,6 +74,8 @@
                       help='file containing the module\'s manifest.')
   parser.add_argument('--merged_manifest', dest='merged_manifest',
                       help='file containing merged manifest for the module and its dependencies.')
+  parser.add_argument('--baseline', dest='baseline_path',
+                      help='file containing baseline lint issues.')
   parser.add_argument('--library', dest='library', action='store_true',
                       help='mark the module as a library.')
   parser.add_argument('--test', dest='test', action='store_true',
@@ -90,6 +93,8 @@
                      help='treat a lint issue as a warning.')
   group.add_argument('--disable_check', dest='checks', action=check_action('ignore'), default=[],
                      help='disable a lint issue.')
+  group.add_argument('--disallowed_issues', dest='disallowed_issues', default=[],
+                     help='lint issues disallowed in the baseline file')
   return parser.parse_args()
 
 
@@ -134,10 +139,30 @@
   f.write("</lint>\n")
 
 
+def check_baseline_for_disallowed_issues(baseline, forced_checks):
+  issues_element = baseline.documentElement
+  if issues_element.tagName != 'issues':
+    raise RuntimeError('expected issues tag at root')
+  issues = issues_element.getElementsByTagName('issue')
+  disallwed = set()
+  for issue in issues:
+    id = issue.getAttribute('id')
+    if id in forced_checks:
+      disallwed.add(id)
+  return disallwed
+
+
 def main():
   """Program entry point."""
   args = parse_args()
 
+  if args.baseline_path:
+    baseline = minidom.parse(args.baseline_path)
+    diallowed_issues = check_baseline_for_disallowed_issues(baseline, args.disallowed_issues)
+    if bool(diallowed_issues):
+      raise RuntimeError('disallowed issues %s found in lint baseline file %s for module %s'
+                         % (diallowed_issues, args.baseline_path, args.name))
+
   if args.project_out:
     with open(args.project_out, 'w') as f:
       write_project_xml(f, args)
diff --git a/scripts/lint_project_xml_test.py b/scripts/lint_project_xml_test.py
new file mode 100644
index 0000000..344691d
--- /dev/null
+++ b/scripts/lint_project_xml_test.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Unit tests for lint_project_xml.py."""
+
+import unittest
+from xml.dom import minidom
+
+import lint_project_xml
+
+
+class CheckBaselineForDisallowedIssuesTest(unittest.TestCase):
+  """Unit tests for check_baseline_for_disallowed_issues function."""
+
+  baseline_xml = minidom.parseString(
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">\n'
+      '    <issue id="foo" message="foo is evil" errorLine1="foo()">\n'
+      '        <location file="a/b/c.java" line="3" column="10"/>\n'
+      '    </issue>\n'
+      '    <issue id="bar" message="bar is known to be evil" errorLine1="bar()">\n'
+      '        <location file="a/b/c.java" line="5" column="12"/>\n'
+      '    </issue>\n'
+      '    <issue id="baz" message="baz may be evil" errorLine1="a = baz()">\n'
+      '        <location file="a/b/c.java" line="10" column="10"/>\n'
+      '    </issue>\n'
+      '    <issue id="foo" message="foo is evil" errorLine1="b = foo()">\n'
+      '        <location file="a/d/e.java" line="100" column="4"/>\n'
+      '    </issue>\n'
+      '</issues>\n')
+
+  def test_check_baseline_for_disallowed_issues(self):
+    disallowed_issues = lint_project_xml.check_baseline_for_disallowed_issues(self.baseline_xml, ["foo", "bar", "qux"])
+    self.assertEqual({"foo", "bar"}, disallowed_issues)
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/sdk/update.go b/sdk/update.go
index 522a888..da76fb4 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -103,7 +103,7 @@
 
 	rb.Command().
 		Implicits(implicits).
-		Text("echo").Text(proptools.ShellEscape(content)).
+		Text("echo -n").Text(proptools.ShellEscape(content)).
 		// convert \\n to \n
 		Text("| sed 's/\\\\n/\\n/g' >").Output(gf.path)
 	rb.Command().
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 8da398a..3f51114 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -1,5 +1,7 @@
 #!/bin/bash -eu
 
+set -o pipefail
+
 # This test exercises the bootstrapping process of the build system
 # in a source tree that only contains enough files for Bazel and Soong to work.
 
@@ -482,14 +484,14 @@
   fi
 }
 
-function test_integrated_bp2build_smoke {
+function test_bp2build_smoke {
   setup
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   [[ -e out/soong/.bootstrap/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
 }
 
-function test_integrated_bp2build_add_android_bp {
+function test_bp2build_add_android_bp {
   setup
 
   mkdir -p a
@@ -502,7 +504,7 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   [[ -e out/soong/bp2build/a/BUILD ]] || fail "a/BUILD not created"
   [[ -L out/soong/workspace/a/BUILD ]] || fail "a/BUILD not symlinked"
 
@@ -516,18 +518,18 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   [[ -e out/soong/bp2build/b/BUILD ]] || fail "a/BUILD not created"
   [[ -L out/soong/workspace/b/BUILD ]] || fail "a/BUILD not symlinked"
 }
 
-function test_integrated_bp2build_null_build {
+function test_bp2build_null_build {
   setup
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   local mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   local mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   if [[ "$mtime1" != "$mtime2" ]]; then
@@ -535,7 +537,7 @@
   fi
 }
 
-function test_integrated_bp2build_add_to_glob {
+function test_bp2build_add_to_glob {
   setup
 
   mkdir -p a
@@ -548,11 +550,11 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   grep -q a1.txt out/soong/bp2build/a/BUILD || fail "a1.txt not in BUILD file"
 
   touch a/a2.txt
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   grep -q a2.txt out/soong/bp2build/a/BUILD || fail "a2.txt not in BUILD file"
 }
 
@@ -564,7 +566,7 @@
   fi
 }
 
-function test_integrated_bp2build_bazel_workspace_structure {
+function test_bp2build_bazel_workspace_structure {
   setup
 
   mkdir -p a/b
@@ -578,7 +580,7 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
   [[ -d out/soong/workspace/a/b ]] || fail "module directory not a directory"
   [[ -L out/soong/workspace/a/b/BUILD ]] || fail "BUILD file not symlinked"
@@ -589,7 +591,7 @@
   [[ ! -e out/soong/workspace/out ]] || fail "out directory symlinked"
 }
 
-function test_integrated_bp2build_bazel_workspace_add_file {
+function test_bp2build_bazel_workspace_add_file {
   setup
 
   mkdir -p a
@@ -602,18 +604,69 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
 
   touch a/a2.txt  # No reference in the .bp file needed
-  INTEGRATED_BP2BUILD=1 run_soong
+  GENERATE_BAZEL_FILES=1 run_soong
   [[ -L out/soong/workspace/a/a2.txt ]] || fail "a/a2.txt not symlinked"
 }
 
+function test_bp2build_build_file_precedence {
+  setup
+
+  mkdir -p a
+  touch a/a.txt
+  touch a/BUILD
+  cat > a/Android.bp <<EOF
+filegroup {
+  name: "a",
+  srcs: ["a.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  GENERATE_BAZEL_FILES=1 run_soong
+  [[ -L out/soong/workspace/a/BUILD ]] || fail "BUILD file not symlinked"
+  [[ "$(readlink -f out/soong/workspace/a/BUILD)" =~ bp2build/a/BUILD$ ]] \
+    || fail "BUILD files symlinked to the wrong place"
+}
+
+function test_bp2build_reports_multiple_errors {
+  setup
+
+  mkdir -p a/BUILD
+  touch a/a.txt
+  cat > a/Android.bp <<EOF
+filegroup {
+  name: "a",
+  srcs: ["a.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  mkdir -p b/BUILD
+  touch b/b.txt
+  cat > b/Android.bp <<EOF
+filegroup {
+  name: "b",
+  srcs: ["b.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  if GENERATE_BAZEL_FILES=1 run_soong >& "$MOCK_TOP/errors"; then
+    fail "Build should have failed"
+  fi
+
+  grep -q "a/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for a/BUILD not found"
+  grep -q "b/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for b/BUILD not found"
+}
+
 test_smoke
 test_null_build
 test_null_build_after_docs
 test_soong_build_rebuilt_if_blueprint_changes
-# test_glob_noop_incremental  # Currently failing
+test_glob_noop_incremental
 test_add_file_to_glob
 test_add_android_bp
 test_change_android_bp
@@ -622,9 +675,11 @@
 test_glob_during_bootstrapping
 test_soong_build_rerun_iff_environment_changes
 test_dump_json_module_graph
-test_integrated_bp2build_smoke
-test_integrated_bp2build_null_build
-test_integrated_bp2build_add_android_bp
-test_integrated_bp2build_add_to_glob
-test_integrated_bp2build_bazel_workspace_structure
-test_integrated_bp2build_bazel_workspace_add_file
+test_bp2build_smoke
+test_bp2build_null_build
+test_bp2build_add_android_bp
+test_bp2build_add_to_glob
+test_bp2build_bazel_workspace_structure
+test_bp2build_bazel_workspace_add_file
+test_bp2build_build_file_precedence
+test_bp2build_reports_multiple_errors
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
new file mode 100755
index 0000000..082cd06
--- /dev/null
+++ b/tests/bp2build_bazel_test.sh
@@ -0,0 +1,75 @@
+#!/bin/bash -eu
+
+set -o pipefail
+
+# Test that bp2build and Bazel can play nicely together
+
+source "$(dirname "$0")/lib.sh"
+
+function test_bp2build_generates_all_buildfiles {
+  setup
+  create_mock_bazel
+
+  mkdir -p foo/convertible_soong_module
+  cat > foo/convertible_soong_module/Android.bp <<'EOF'
+genrule {
+    name: "the_answer",
+    cmd: "echo '42' > $(out)",
+    out: [
+        "the_answer.txt",
+    ],
+    bazel_module: {
+        bp2build_available: true,
+    },
+  }
+EOF
+
+  mkdir -p foo/unconvertible_soong_module
+  cat > foo/unconvertible_soong_module/Android.bp <<'EOF'
+genrule {
+    name: "not_the_answer",
+    cmd: "echo '43' > $(out)",
+    out: [
+        "not_the_answer.txt",
+    ],
+    bazel_module: {
+        bp2build_available: false,
+    },
+  }
+EOF
+
+  run_bp2build
+
+  if [[ ! -f "./out/soong/workspace/foo/convertible_soong_module/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/convertible_soong_module/BUILD was not generated"
+  fi
+
+  if [[ ! -f "./out/soong/workspace/foo/unconvertible_soong_module/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/unconvertible_soong_module/BUILD was not generated"
+  fi
+
+  if ! grep "the_answer" "./out/soong/workspace/foo/convertible_soong_module/BUILD"; then
+    fail "missing BUILD target the_answer in convertible_soong_module/BUILD"
+  fi
+
+  if grep "not_the_answer" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
+    fail "found unexpected BUILD target not_the_answer in unconvertible_soong_module/BUILD"
+  fi
+
+  if ! grep "filegroup" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
+    fail "missing filegroup in unconvertible_soong_module/BUILD"
+  fi
+
+  # NOTE: We don't actually use the extra BUILD file for anything here
+  run_bazel build --package_path=out/soong/workspace //foo/...
+
+  local the_answer_file="bazel-out/k8-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
+  if [[ ! -f "${the_answer_file}" ]]; then
+    fail "Expected '${the_answer_file}' to be generated, but was missing"
+  fi
+  if ! grep 42 "${the_answer_file}"; then
+    fail "Expected to find 42 in '${the_answer_file}'"
+  fi
+}
+
+test_bp2build_generates_all_buildfiles
diff --git a/tests/lib.sh b/tests/lib.sh
index 3795dfc..e561a3d 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -1,5 +1,7 @@
 #!/bin/bash -eu
 
+set -o pipefail
+
 HARDWIRED_MOCK_TOP=
 # Uncomment this to be able to view the source tree after a test is run
 # HARDWIRED_MOCK_TOP=/tmp/td
@@ -102,7 +104,25 @@
 }
 
 function run_soong() {
-  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests
+  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests "$@"
+}
+
+function create_mock_bazel() {
+  copy_directory build/bazel
+
+  symlink_directory prebuilts/bazel
+  symlink_directory prebuilts/jdk
+
+  symlink_file WORKSPACE
+  symlink_file tools/bazel
+}
+
+run_bazel() {
+  tools/bazel "$@"
+}
+
+run_bp2build() {
+  GENERATE_BAZEL_FILES=true build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests nothing
 }
 
 info "Starting Soong integration test suite $(basename $0)"
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
index 7dbafea..80774bf 100755
--- a/tests/mixed_mode_test.sh
+++ b/tests/mixed_mode_test.sh
@@ -1,5 +1,7 @@
 #!/bin/bash -eu
 
+set -o pipefail
+
 # This test exercises mixed builds where Soong and Bazel cooperate in building
 # Android.
 #
@@ -8,21 +10,11 @@
 
 source "$(dirname "$0")/lib.sh"
 
-function create_mock_bazel() {
-  copy_directory build/bazel
-
-  symlink_directory prebuilts/bazel
-  symlink_directory prebuilts/jdk
-
-  symlink_file WORKSPACE
-  symlink_file tools/bazel
-}
-
 function test_bazel_smoke {
   setup
   create_mock_bazel
 
-  tools/bazel info
+  run_bazel info
 }
 
 test_bazel_smoke
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 76b324b..8399573 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -1,6 +1,8 @@
 #!/bin/bash -eu
 
+set -o pipefail
+
 TOP="$(readlink -f "$(dirname "$0")"/../../..)"
 "$TOP/build/soong/tests/bootstrap_test.sh"
 "$TOP/build/soong/tests/mixed_mode_test.sh"
-
+"$TOP/build/soong/tests/bp2build_bazel_test.sh"
diff --git a/ui/build/build.go b/ui/build/build.go
index 3692f4f..c2ad057 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -258,8 +258,8 @@
 		// Run Soong
 		runSoong(ctx, config)
 
-		if config.Environment().IsEnvTrue("GENERATE_BAZEL_FILES") {
-			// Return early, if we're using Soong as the bp2build converter.
+		if config.bazelBuildMode() == generateBuildFiles {
+			// Return early, if we're using Soong as solely the generator of BUILD files.
 			return
 		}
 	}
diff --git a/ui/build/config.go b/ui/build/config.go
index 4816d1f..1d1f71f 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -93,6 +93,21 @@
 	BUILD_MODULES
 )
 
+type bazelBuildMode int
+
+// Bazel-related build modes.
+const (
+	// Don't use bazel at all.
+	noBazel bazelBuildMode = iota
+
+	// Only generate build files (in a subdirectory of the out directory) and exit.
+	generateBuildFiles
+
+	// Generate synthetic build files and incorporate these files into a build which
+	// partially uses Bazel. Build metadata may come from Android.bp or BUILD files.
+	mixedBuild
+)
+
 // checkTopDir validates that the current directory is at the root directory of the source tree.
 func checkTopDir(ctx Context) {
 	if _, err := os.Stat(srcDirFileCheck); err != nil {
@@ -897,6 +912,16 @@
 	return c.useBazel
 }
 
+func (c *configImpl) bazelBuildMode() bazelBuildMode {
+	if c.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
+		return mixedBuild
+	} else if c.Environment().IsEnvTrue("GENERATE_BAZEL_FILES") {
+		return generateBuildFiles
+	} else {
+		return noBazel
+	}
+}
+
 func (c *configImpl) StartRBE() bool {
 	if !c.UseRBE() {
 		return false
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 7e94b25..a41dbe1 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -206,7 +206,8 @@
 		}
 	}
 
-	integratedBp2Build := config.Environment().IsEnvTrue("INTEGRATED_BP2BUILD")
+	buildMode := config.bazelBuildMode()
+	integratedBp2Build := (buildMode == mixedBuild) || (buildMode == generateBuildFiles)
 
 	// This is done unconditionally, but does not take a measurable amount of time
 	bootstrapBlueprint(ctx, config, integratedBp2Build)
@@ -312,7 +313,7 @@
 
 func shouldCollectBuildSoongMetrics(config Config) bool {
 	// Do not collect metrics protobuf if the soong_build binary ran as the bp2build converter.
-	return config.Environment().IsFalse("GENERATE_BAZEL_FILES")
+	return config.bazelBuildMode() != generateBuildFiles
 }
 
 func loadSoongBuildMetrics(ctx Context, config Config) *soong_metrics_proto.SoongBuildMetrics {