Merge "Remove unnecessary pass in validatePath."
diff --git a/android/bazel.go b/android/bazel.go
index 10e9251..52f50c5 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -526,3 +526,15 @@
 
 	return "", errors.New("Main-Class is not found.")
 }
+
+func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath {
+	validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base())
+	ctx.Build(pctx, BuildParams{
+		Rule:        CpNoPreserveSymlink,
+		Description: "run validations " + outputFilePath.Base(),
+		Output:      validatedOutputFilePath,
+		Input:       outputFilePath,
+		Validations: validations,
+	})
+	return validatedOutputFilePath
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 123cc60..9ff6b52 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -29,6 +29,7 @@
 	"android/soong/android/allowlists"
 	"android/soong/bazel/cquery"
 	"android/soong/shared"
+	"android/soong/starlark_fmt"
 
 	"github.com/google/blueprint"
 
@@ -43,6 +44,27 @@
 		Description: "",
 		CommandDeps: []string{"${bazelBuildRunfilesTool}"},
 	}, "outDir")
+	allowedBazelEnvironmentVars = []string{
+		"ALLOW_LOCAL_TIDY_TRUE",
+		"DEFAULT_TIDY_HEADER_DIRS",
+		"TIDY_TIMEOUT",
+		"WITH_TIDY",
+		"WITH_TIDY_FLAGS",
+		"SKIP_ABI_CHECKS",
+		"UNSAFE_DISABLE_APEX_ALLOWED_DEPS_CHECK",
+		"AUTO_ZERO_INITIALIZE",
+		"AUTO_PATTERN_INITIALIZE",
+		"AUTO_UNINITIALIZE",
+		"USE_CCACHE",
+		"LLVM_NEXT",
+		"ALLOW_UNKNOWN_WARNING_OPTION",
+
+		// Overrides the version in the apex_manifest.json. The version is unique for
+		// each branch (internal, aosp, mainline releases, dessert releases).  This
+		// enables modules built on an older branch to be installed against a newer
+		// device for development purposes.
+		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION",
+	}
 )
 
 func init() {
@@ -165,7 +187,7 @@
 }
 
 type bazelRunner interface {
-	createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
+	createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
 	issueBazelCommand(bazelCmd *exec.Cmd) (output string, errorMessage string, error error)
 }
 
@@ -558,7 +580,7 @@
 	extraFlags []string
 }
 
-func (r *mockBazelRunner) createBazelCommand(_ *bazelPaths, _ bazel.RunName,
+func (r *mockBazelRunner) createBazelCommand(_ Config, _ *bazelPaths, _ bazel.RunName,
 	command bazelCommand, extraFlags ...string) *exec.Cmd {
 	r.commands = append(r.commands, command)
 	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
@@ -595,7 +617,7 @@
 	}
 }
 
-func (r *builtinBazelRunner) createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
+func (r *builtinBazelRunner) createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand,
 	extraFlags ...string) *exec.Cmd {
 	cmdFlags := []string{
 		"--output_base=" + absolutePath(paths.outputBase),
@@ -639,6 +661,13 @@
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1",
 	}
+	for _, envvar := range allowedBazelEnvironmentVars {
+		val := config.Getenv(envvar)
+		if val == "" {
+			continue
+		}
+		extraEnv = append(extraEnv, fmt.Sprintf("%s=%s", envvar, val))
+	}
 	bazelCmd.Env = append(os.Environ(), extraEnv...)
 
 	return bazelCmd
@@ -965,13 +994,13 @@
 		}
 	}
 	context.results = make(map[cqueryKey]string)
-	if err := context.runCquery(ctx); err != nil {
+	if err := context.runCquery(config, ctx); err != nil {
 		return err
 	}
 	if err := context.runAquery(config, ctx); err != nil {
 		return err
 	}
-	if err := context.generateBazelSymlinks(ctx); err != nil {
+	if err := context.generateBazelSymlinks(config, ctx); err != nil {
 		return err
 	}
 
@@ -980,7 +1009,7 @@
 	return nil
 }
 
-func (context *mixedBuildBazelContext) runCquery(ctx *Context) error {
+func (context *mixedBuildBazelContext) runCquery(config Config, ctx *Context) error {
 	if ctx != nil {
 		ctx.EventHandler.Begin("cquery")
 		defer ctx.EventHandler.End("cquery")
@@ -1007,7 +1036,7 @@
 		return err
 	}
 
-	cqueryCommandWithFlag := context.createBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
+	cqueryCommandWithFlag := context.createBazelCommand(config, context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
 		"--output=starlark", "--starlark:file="+absolutePath(cqueryFileRelpath))
 	cqueryOutput, cqueryErrorMessage, cqueryErr := context.issueBazelCommand(cqueryCommandWithFlag)
 	if cqueryErr != nil {
@@ -1072,7 +1101,7 @@
 			extraFlags = append(extraFlags, "--instrumentation_filter="+strings.Join(paths, ","))
 		}
 	}
-	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
+	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
 		extraFlags...))
 	if err != nil {
 		return err
@@ -1081,7 +1110,7 @@
 	return err
 }
 
-func (context *mixedBuildBazelContext) generateBazelSymlinks(ctx *Context) error {
+func (context *mixedBuildBazelContext) generateBazelSymlinks(config Config, ctx *Context) error {
 	if ctx != nil {
 		ctx.EventHandler.Begin("symlinks")
 		defer ctx.EventHandler.End("symlinks")
@@ -1089,7 +1118,7 @@
 	// Issue a build command of the phony root to generate symlink forests for dependencies of the
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
-	_, _, err := context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd))
+	_, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd))
 	return err
 }
 
@@ -1299,3 +1328,13 @@
 func bazelDepsetName(contentHash string) string {
 	return fmt.Sprintf("bazel_depset_%s", contentHash)
 }
+
+func EnvironmentVarsFile(config Config) string {
+	return fmt.Sprintf(bazel.GeneratedBazelFileWarning+`
+_env = %s
+
+env = _env
+`,
+		starlark_fmt.PrintStringList(allowedBazelEnvironmentVars, 0),
+	)
+}
diff --git a/android/defaults.go b/android/defaults.go
index 7906e94..925eafc 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -55,7 +55,7 @@
 	d.hook = hook
 }
 
-func (d *DefaultableModuleBase) callHookIfAvailable(ctx DefaultableHookContext) {
+func (d *DefaultableModuleBase) CallHookIfAvailable(ctx DefaultableHookContext) {
 	if d.hook != nil {
 		d.hook(ctx)
 	}
@@ -82,7 +82,7 @@
 	SetDefaultableHook(hook DefaultableHook)
 
 	// Call the hook if specified.
-	callHookIfAvailable(context DefaultableHookContext)
+	CallHookIfAvailable(context DefaultableHookContext)
 }
 
 type DefaultableModule interface {
@@ -630,6 +630,6 @@
 			defaultable.applyDefaults(ctx, defaultsList)
 		}
 
-		defaultable.callHookIfAvailable(ctx)
+		defaultable.CallHookIfAvailable(ctx)
 	}
 }
diff --git a/android/defs.go b/android/defs.go
index 6e5bb05..18eed2d 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -58,6 +58,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy rule that doesn't preserve symlinks.
+	CpNoPreserveSymlink = pctx.AndroidStaticRule("CpNoPreserveSymlink",
+		blueprint.RuleParams{
+			Command:     "rm -f $out && cp $cpFlags $in $out$extraCmds",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A copy rule that only updates the output if it changed.
 	CpIfChanged = pctx.AndroidStaticRule("CpIfChanged",
 		blueprint.RuleParams{
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 12faf22..f66ae16 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -95,20 +95,6 @@
 		return moduleNames
 	}
 
-	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
-	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
-	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
-	// for the overriding VNDK APEXes.
-	symbolFilesNotNeeded := a.vndkApex && len(a.overridableProperties.Overrides) > 0
-	if symbolFilesNotNeeded && apexType != flattenedApex {
-		return moduleNames
-	}
-
-	// Avoid creating duplicate build rules for multi-installed APEXes.
-	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
-		return moduleNames
-	}
-
 	seenDataOutPaths := make(map[string]bool)
 
 	for _, fi := range a.filesInfo {
@@ -152,7 +138,7 @@
 			// /system/apex/<name>/{lib|framework|...}
 			modulePath = filepath.Join(a.installDir.String(), apexBundleName, fi.installDir)
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
-			if a.primaryApexType && !symbolFilesNotNeeded {
+			if a.primaryApexType {
 				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
 			}
 			android.AndroidMkEmitAssignList(w, "LOCAL_MODULE_SYMLINKS", fi.symlinks)
diff --git a/apex/apex.go b/apex/apex.go
index a9c8afc..3ad0495 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -149,16 +149,6 @@
 	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
 	Use_vndk_as_stable *bool
 
-	// Whether this is multi-installed APEX should skip installing symbol files.
-	// Multi-installed APEXes share the same apex_name and are installed at the same time.
-	// Default is false.
-	//
-	// Should be set to true for all multi-installed APEXes except the singular
-	// default version within the multi-installed group.
-	// Only the default version can install symbol files in $(PRODUCT_OUT}/apex,
-	// or else conflicting build rules may be created.
-	Multi_install_skip_symbol_files *bool
-
 	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
 	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
 	// container. When set to zip, contents are stored in a zip container directly. This type is
@@ -1781,6 +1771,18 @@
 	return af
 }
 
+func apexFileForJavaModuleProfile(ctx android.BaseModuleContext, module javaModule) *apexFile {
+	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+		if profilePathOnHost := dexpreopter.ProfilePathOnHost(); profilePathOnHost != nil {
+			dirInApex := "javalib"
+			af := newApexFile(ctx, profilePathOnHost, module.BaseModuleName()+"-profile", dirInApex, etc, nil)
+			af.customStem = module.Stem() + ".jar.prof"
+			return &af
+		}
+	}
+	return nil
+}
+
 // androidApp is an interface to handle all app modules (android_app, android_app_import, etc.) in
 // the same way.
 type androidApp interface {
@@ -1964,6 +1966,11 @@
 	}
 	a.outputFile = a.outputApexFile
 
+	if len(outputs.TidyFiles) > 0 {
+		tidyFiles := android.PathsForBazelOut(ctx, outputs.TidyFiles)
+		a.outputFile = android.AttachValidationActions(ctx, a.outputFile, tidyFiles)
+	}
+
 	// TODO(b/257829940): These are used by the apex_keys_text singleton; would probably be a clearer
 	// interface if these were set in a provider rather than the module itself
 	a.publicKeyFile = android.PathForBazelOut(ctx, outputs.BundleKeyInfo[0])
@@ -2475,6 +2482,9 @@
 		case *java.Library, *java.SdkLibrary:
 			af := apexFileForJavaModule(ctx, child.(javaModule))
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			if profileAf := apexFileForJavaModuleProfile(ctx, child.(javaModule)); profileAf != nil {
+				vctx.filesInfo = append(vctx.filesInfo, *profileAf)
+			}
 			return true // track transitive dependencies
 		default:
 			ctx.PropertyErrorf("systemserverclasspath_fragments",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 4f300e7..e558fee 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -3806,11 +3806,9 @@
 		}`+vndkLibrariesTxtFiles("28", "current"))
 
 	assertApexName := func(expected, moduleName string) {
-		bundle := ctx.ModuleForTests(moduleName, "android_common_image").Module().(*apexBundle)
-		actual := proptools.String(bundle.properties.Apex_name)
-		if !reflect.DeepEqual(actual, expected) {
-			t.Errorf("Got '%v', expected '%v'", actual, expected)
-		}
+		module := ctx.ModuleForTests(moduleName, "android_common_image")
+		apexManifestRule := module.Rule("apexManifestRule")
+		ensureContains(t, apexManifestRule.Args["opt"], "-v name "+expected)
 	}
 
 	assertApexName("com.android.vndk.v29", "com.android.vndk.current")
@@ -4136,9 +4134,6 @@
 	`)
 
 	module := ctx.ModuleForTests("myapex", "android_common_com.android.myapex_image")
-	apexManifestRule := module.Rule("apexManifestRule")
-	ensureContains(t, apexManifestRule.Args["opt"], "-v name com.android.myapex")
-
 	apexBundle := module.Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	name := apexBundle.BaseModuleName()
diff --git a/apex/builder.go b/apex/builder.go
index 4aef3c1..49223a0 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -241,10 +241,11 @@
 	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
 	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
 
-	// APEX name can be overridden
+	// VNDK APEX name is determined at runtime, so update "name" in apex_manifest
 	optCommands := []string{}
-	if a.properties.Apex_name != nil {
-		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
+	if a.vndkApex {
+		apexName := vndkApexNamePrefix + a.vndkVersion(ctx.DeviceConfig())
+		optCommands = append(optCommands, "-v name "+apexName)
 	}
 
 	// Collect jniLibs. Notice that a.filesInfo is already sorted
@@ -454,19 +455,6 @@
 
 	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
 
-	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
-	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
-	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
-	// for the overriding VNDK APEXes.
-	if a.vndkApex && len(a.overridableProperties.Overrides) > 0 {
-		installSymbolFiles = false
-	}
-
-	// Avoid creating duplicate build rules for multi-installed APEXes.
-	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
-		installSymbolFiles = false
-
-	}
 	// set of dependency module:location mappings
 	installMapSet := make(map[string]bool)
 
@@ -1012,7 +1000,7 @@
 	if a.vndkApex {
 		overrideName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(vndkApexName)
 		if overridden {
-			return strings.Replace(*a.properties.Apex_name, vndkApexName, overrideName, 1)
+			return overrideName + ".v" + a.vndkVersion(ctx.DeviceConfig())
 		}
 		return ""
 	}
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index d037664..c404a2e 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -31,7 +31,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
-		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo", "myapex:bar"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -57,10 +57,23 @@
 			],
 		}
 
+		java_library {
+			name: "bar",
+			srcs: ["c.java"],
+			installable: true,
+			dex_preopt: {
+				profile: "bar-art-profile",
+			},
+			apex_available: [
+				"myapex",
+			],
+		}
+
 		systemserverclasspath_fragment {
 			name: "mysystemserverclasspathfragment",
 			contents: [
 				"foo",
+				"bar",
 			],
 			apex_available: [
 				"myapex",
@@ -71,6 +84,8 @@
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"etc/classpaths/systemserverclasspath.pb",
 		"javalib/foo.jar",
+		"javalib/bar.jar",
+		"javalib/bar.jar.prof",
 	})
 
 	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
@@ -236,7 +251,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
-		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo", "myapex:bar"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -262,10 +277,23 @@
 			],
 		}
 
+		java_library {
+			name: "bar",
+			srcs: ["c.java"],
+			dex_preopt: {
+				profile: "bar-art-profile",
+			},
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
 		systemserverclasspath_fragment {
 			name: "mysystemserverclasspathfragment",
 			standalone_contents: [
 				"foo",
+				"bar",
 			],
 			apex_available: [
 				"myapex",
@@ -276,6 +304,8 @@
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"etc/classpaths/systemserverclasspath.pb",
 		"javalib/foo.jar",
+		"javalib/bar.jar",
+		"javalib/bar.jar.prof",
 	})
 }
 
diff --git a/apex/vndk.go b/apex/vndk.go
index 80560cf..c0be753 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -65,10 +65,6 @@
 		}
 
 		vndkVersion := ab.vndkVersion(mctx.DeviceConfig())
-
-		// Ensure VNDK APEX mount point is formatted as com.android.vndk.v###
-		ab.properties.Apex_name = proptools.StringPtr(vndkApexNamePrefix + vndkVersion)
-
 		apiLevel, err := android.ApiLevelFromUser(mctx, vndkVersion)
 		if err != nil {
 			mctx.PropertyErrorf("vndk_version", "%s", err.Error())
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 6654191..0c8247a 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -180,7 +180,7 @@
 tidy_files = []
 clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
 if clang_tidy_info:
-  tidy_files = [v.path for v in clang_tidy_info.tidy_files.to_list()]
+  tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
 
 abi_diff_files = []
 abi_diff_info = p.get("//build/bazel/rules/abi:abi_dump.bzl%AbiDiffInfo")
@@ -207,7 +207,7 @@
     "Headers": headers,
     "RootStaticArchives": rootStaticArchives,
     "RootDynamicLibraries": rootSharedLibraries,
-    "TidyFiles": tidy_files,
+    "TidyFiles": [t for t in tidy_files],
     "TocFile": toc_file,
     "UnstrippedOutput": unstripped,
     "AbiDiffFiles": abi_diff_files,
@@ -261,6 +261,11 @@
 if not mk_info:
   fail("%s did not provide ApexMkInfo" % id_string)
 
+tidy_files = []
+clang_tidy_info = providers(target).get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
+if clang_tidy_info:
+    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
+
 return json_encode({
     "signed_output": info.signed_output.path,
     "signed_compressed_output": signed_compressed_output,
@@ -276,6 +281,7 @@
     "bundle_file": info.base_with_config_zip.path,
     "installed_files": info.installed_files.path,
     "make_modules_to_install": mk_info.make_modules_to_install,
+    "tidy_files": [t for t in tidy_files],
 })`
 }
 
@@ -294,6 +300,7 @@
 	BackingLibs            string   `json:"backing_libs"`
 	BundleFile             string   `json:"bundle_file"`
 	InstalledFiles         string   `json:"installed_files"`
+	TidyFiles              []string `json:"tidy_files"`
 
 	// From the ApexMkInfo provider
 	MakeModulesToInstall []string `json:"make_modules_to_install"`
@@ -338,12 +345,18 @@
     local_whole_static_libs = androidmk_info.local_whole_static_libs
     local_shared_libs = androidmk_info.local_shared_libs
 
+tidy_files = []
+clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
+if clang_tidy_info:
+    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
+
 return json_encode({
     "OutputFile":  output_path,
     "UnstrippedOutput": unstripped,
     "LocalStaticLibs": [l for l in local_static_libs],
     "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
     "LocalSharedLibs": [l for l in local_shared_libs],
+    "TidyFiles": [t for t in tidy_files],
 })
 `
 }
@@ -361,6 +374,7 @@
 	CcAndroidMkInfo
 	OutputFile       string
 	UnstrippedOutput string
+	TidyFiles        []string
 }
 
 // splitOrEmpty is a modification of strings.Split() that returns an empty list
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index c43fbd8..5b3e19f 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -58,6 +58,8 @@
 	files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
 	files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg)))
 
+	files = append(files, newFile("allowlists", GeneratedBuildFileName, ""))
+	files = append(files, newFile("allowlists", "env.bzl", android.EnvironmentVarsFile(cfg)))
 	// TODO(b/262781701): Create an alternate soong_build entrypoint for writing out these files only when requested
 	files = append(files, newFile("allowlists", "mixed_build_prod_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelProdMode), "\n")+"\n"))
 	files = append(files, newFile("allowlists", "mixed_build_staging_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelStagingMode), "\n")+"\n"))
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index b9c06bc..8c24093 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -153,6 +153,14 @@
 		},
 		{
 			dir:      "allowlists",
+			basename: GeneratedBuildFileName,
+		},
+		{
+			dir:      "allowlists",
+			basename: "env.bzl",
+		},
+		{
+			dir:      "allowlists",
 			basename: "mixed_build_prod_allowlist.txt",
 		},
 		{
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 37188f1..183eb12 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -15,9 +15,7 @@
 package bp2build
 
 import (
-	"errors"
 	"fmt"
-	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -53,59 +51,6 @@
 	symlinkCount atomic.Uint64
 }
 
-// A simple thread pool to limit concurrency on system calls.
-// Necessary because Go spawns a new OS-level thread for each blocking system
-// call. This means that if syscalls are too slow and there are too many of
-// them, the hard limit on OS-level threads can be exhausted.
-type syscallPool struct {
-	shutdownCh []chan<- struct{}
-	workCh     chan syscall
-}
-
-type syscall struct {
-	work func()
-	done chan<- struct{}
-}
-
-func createSyscallPool(count int) *syscallPool {
-	result := &syscallPool{
-		shutdownCh: make([]chan<- struct{}, count),
-		workCh:     make(chan syscall),
-	}
-
-	for i := 0; i < count; i++ {
-		shutdownCh := make(chan struct{})
-		result.shutdownCh[i] = shutdownCh
-		go result.worker(shutdownCh)
-	}
-
-	return result
-}
-
-func (p *syscallPool) do(work func()) {
-	doneCh := make(chan struct{})
-	p.workCh <- syscall{work, doneCh}
-	<-doneCh
-}
-
-func (p *syscallPool) shutdown() {
-	for _, ch := range p.shutdownCh {
-		ch <- struct{}{} // Blocks until the value is received
-	}
-}
-
-func (p *syscallPool) worker(shutdownCh <-chan struct{}) {
-	for {
-		select {
-		case <-shutdownCh:
-			return
-		case work := <-p.workCh:
-			work.work()
-			work.done <- struct{}{}
-		}
-	}
-}
-
 // Ensures that the node for the given path exists in the tree and returns it.
 func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
 	if path == "" {
@@ -217,12 +162,35 @@
 }
 
 // Creates a symbolic link at dst pointing to src
-func symlinkIntoForest(topdir, dst, src string) {
-	err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
-	if err != nil {
+func symlinkIntoForest(topdir, dst, src string) uint64 {
+	srcPath := shared.JoinPath(topdir, src)
+	dstPath := shared.JoinPath(topdir, dst)
+
+	// Check if a symlink already exists.
+	if dstInfo, err := os.Lstat(dstPath); err != nil {
+		if !os.IsNotExist(err) {
+			fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
+			os.Exit(1)
+		}
+	} else {
+		if dstInfo.Mode()&os.ModeSymlink != 0 {
+			// Assume that the link's target is correct, i.e. no manual tampering.
+			// E.g. OUT_DIR could have been previously used with a different source tree check-out!
+			return 0
+		} else {
+			if err := os.RemoveAll(dstPath); err != nil {
+				fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
+				os.Exit(1)
+			}
+		}
+	}
+
+	// Create symlink.
+	if err := os.Symlink(srcPath, dstPath); err != nil {
 		fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
 		os.Exit(1)
 	}
+	return 1
 }
 
 func isDir(path string, fi os.FileInfo) bool {
@@ -253,8 +221,9 @@
 	defer context.wg.Done()
 
 	if instructions != nil && instructions.excluded {
-		// This directory is not needed, bail out
-		return
+		// Excluded paths are skipped at the level of the non-excluded parent.
+		fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
+		os.Exit(1)
 	}
 
 	// We don't add buildFilesDir here because the bp2build files marker files is
@@ -272,6 +241,12 @@
 				renamingBuildFile = true
 				srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
 				delete(srcDirMap, "BUILD")
+				if instructions != nil {
+					if _, ok := instructions.children["BUILD"]; ok {
+						instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
+						delete(instructions.children, "BUILD")
+					}
+				}
 			}
 		}
 	}
@@ -288,17 +263,41 @@
 	// Tests read the error messages generated, so ensure their order is deterministic
 	sort.Strings(allEntries)
 
-	err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
-		os.Exit(1)
+	fullForestPath := shared.JoinPath(context.topdir, forestDir)
+	createForestDir := false
+	if fi, err := os.Lstat(fullForestPath); err != nil {
+		if os.IsNotExist(err) {
+			createForestDir = true
+		} else {
+			fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
+		}
+	} else if fi.Mode()&os.ModeDir == 0 {
+		if err := os.RemoveAll(fullForestPath); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
+			os.Exit(1)
+		}
+		createForestDir = true
 	}
-	context.mkdirCount.Add(1)
+	if createForestDir {
+		if err := os.MkdirAll(fullForestPath, 0777); err != nil {
+			fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
+			os.Exit(1)
+		}
+		context.mkdirCount.Add(1)
+	}
+
+	// Start with a list of items that already exist in the forest, and remove
+	// each element as it is processed in allEntries. Any remaining items in
+	// forestMapForDeletion must be removed. (This handles files which were
+	// removed since the previous forest generation).
+	forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
 
 	for _, f := range allEntries {
 		if f[0] == '.' {
 			continue // Ignore dotfiles
 		}
+		delete(forestMapForDeletion, f)
+		// todo add deletionCount metric
 
 		// The full paths of children in the input trees and in the output tree
 		forestChild := shared.JoinPath(forestDir, f)
@@ -309,13 +308,9 @@
 		buildFilesChild := shared.JoinPath(buildFilesDir, f)
 
 		// Descend in the instruction tree if it exists
-		var instructionsChild *instructionsNode = nil
+		var instructionsChild *instructionsNode
 		if instructions != nil {
-			if f == "BUILD.bazel" && renamingBuildFile {
-				instructionsChild = instructions.children["BUILD"]
-			} else {
-				instructionsChild = instructions.children[f]
-			}
+			instructionsChild = instructions.children[f]
 		}
 
 		srcChildEntry, sExists := srcDirMap[f]
@@ -323,8 +318,7 @@
 
 		if instructionsChild != nil && instructionsChild.excluded {
 			if bExists {
-				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
 			}
 			continue
 		}
@@ -340,8 +334,7 @@
 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
 			} else {
 				// Not in the source tree, symlink BUILD file
-				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
 			}
 		} else if !bExists {
 			if sDir && instructionsChild != nil {
@@ -351,8 +344,7 @@
 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
 			} else {
 				// Not in the build file tree, symlink source tree, carry on
-				symlinkIntoForest(context.topdir, forestChild, srcChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
 			}
 		} else if sDir && bDir {
 			// Both are directories. Descend.
@@ -365,8 +357,7 @@
 			// The Android.bp file that codegen used to produce `buildFilesChild` is
 			// already a dependency, we can ignore `buildFilesChild`.
 			context.depCh <- srcChild
-			err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
-			if err != nil {
+			if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
 				fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
 					srcBuildFile, generatedBuildFile, err)
 				os.Exit(1)
@@ -379,51 +370,27 @@
 			os.Exit(1)
 		}
 	}
-}
 
-func removeParallelRecursive(pool *syscallPool, path string, fi os.FileInfo, wg *sync.WaitGroup) {
-	defer wg.Done()
-
-	if fi.IsDir() {
-		children := readdirToMap(path)
-		childrenWg := &sync.WaitGroup{}
-		childrenWg.Add(len(children))
-
-		for child, childFi := range children {
-			go removeParallelRecursive(pool, shared.JoinPath(path, child), childFi, childrenWg)
+	// Remove all files in the forest that exist in neither the source
+	// tree nor the build files tree. (This handles files which were removed
+	// since the previous forest generation).
+	for f := range forestMapForDeletion {
+		var instructionsChild *instructionsNode
+		if instructions != nil {
+			instructionsChild = instructions.children[f]
 		}
 
-		childrenWg.Wait()
-	}
-
-	pool.do(func() {
-		if err := os.Remove(path); err != nil {
-			fmt.Fprintf(os.Stderr, "Cannot unlink '%s': %s\n", path, err)
+		if instructionsChild != nil && instructionsChild.excluded {
+			// This directory may be excluded because bazel writes to it under the
+			// forest root. Thus this path is intentionally left alone.
+			continue
+		}
+		forestChild := shared.JoinPath(context.topdir, forestDir, f)
+		if err := os.RemoveAll(forestChild); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
 			os.Exit(1)
 		}
-	})
-}
-
-func removeParallel(path string) {
-	fi, err := os.Lstat(path)
-	if err != nil {
-		if errors.Is(err, fs.ErrNotExist) {
-			return
-		}
-
-		fmt.Fprintf(os.Stderr, "Cannot lstat '%s': %s\n", path, err)
-		os.Exit(1)
 	}
-
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	// Random guess as to the best number of syscalls to run in parallel
-	pool := createSyscallPool(100)
-	removeParallelRecursive(pool, path, fi, wg)
-	pool.shutdown()
-
-	wg.Wait()
 }
 
 // PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
@@ -439,8 +406,6 @@
 		symlinkCount: atomic.Uint64{},
 	}
 
-	removeParallel(shared.JoinPath(topdir, forest))
-
 	instructions := instructionsFromExcludePathList(exclude)
 	go func() {
 		context.wg.Add(1)
diff --git a/cc/binary.go b/cc/binary.go
index 2f8ee7f..a04b174 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -588,7 +588,11 @@
 		return
 	}
 
-	outputFilePath := android.PathForBazelOut(ctx, info.OutputFile)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, info.OutputFile)
+	if len(info.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, info.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*binaryDecorator).unstrippedOutputFile = android.PathForBazelOut(ctx, info.UnstrippedOutput)
 
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 43aff5c..e0b5b5d 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -55,6 +55,47 @@
 	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
 }
 
+func TestCcBinaryWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_binary {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcBinary: map[string]cquery.CcUnstrippedInfo{
+			"//foo/bar:bar": cquery.CcUnstrippedInfo{
+				OutputFile:       "foo",
+				UnstrippedOutput: "foo.unstripped",
+				TidyFiles:        []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	binMod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module()
+	producer := binMod.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_binary outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm64_armv8-a/validated/foo"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	unStrippedFilePath := binMod.(*Module).UnstrippedOutputFile()
+	expectedUnStrippedFile := "outputbase/execroot/__main__/foo.unstripped"
+	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
+}
+
 func TestBinaryLinkerScripts(t *testing.T) {
 	t.Parallel()
 	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
diff --git a/cc/cc.go b/cc/cc.go
index c33c5e3..c81160d 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1066,6 +1066,31 @@
 	return false
 }
 
+func (c *Module) IsFuzzModule() bool {
+	if _, ok := c.compiler.(*fuzzBinary); ok {
+		return true
+	}
+	return false
+}
+
+func (c *Module) FuzzModuleStruct() fuzz.FuzzModule {
+	return c.FuzzModule
+}
+
+func (c *Module) FuzzPackagedModule() fuzz.FuzzPackagedModule {
+	if fuzzer, ok := c.compiler.(*fuzzBinary); ok {
+		return fuzzer.fuzzPackagedModule
+	}
+	panic(fmt.Errorf("FuzzPackagedModule called on non-fuzz module: %q", c.BaseModuleName()))
+}
+
+func (c *Module) FuzzSharedLibraries() android.Paths {
+	if fuzzer, ok := c.compiler.(*fuzzBinary); ok {
+		return fuzzer.sharedLibraries
+	}
+	panic(fmt.Errorf("FuzzSharedLibraries called on non-fuzz module: %q", c.BaseModuleName()))
+}
+
 func (c *Module) NonCcVariants() bool {
 	return false
 }
@@ -1888,12 +1913,6 @@
 
 func (c *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
 	bazelModuleLabel := c.getBazelModuleLabel(ctx)
-
-	bazelCtx := ctx.Config().BazelContext
-	if ccInfo, err := bazelCtx.GetCcInfo(bazelModuleLabel, android.GetConfigKey(ctx)); err == nil {
-		c.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
-	}
-
 	c.bazelHandler.ProcessBazelQueryResponse(ctx, bazelModuleLabel)
 
 	c.Properties.SubName = GetSubnameProperty(ctx, c)
diff --git a/cc/config/global.go b/cc/config/global.go
index 1aec190..3fd1b10 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -279,7 +279,6 @@
 
 		// http://b/145211477
 		"-Wno-pointer-compare",
-		// http://b/145211022
 		"-Wno-final-dtor-non-final-class",
 
 		// http://b/165945989
@@ -435,7 +434,7 @@
 	pctx.StaticVariable("ClangBin", "${ClangPath}/bin")
 
 	pctx.StaticVariableWithEnvOverride("ClangShortVersion", "LLVM_RELEASE_VERSION", ClangDefaultShortVersion)
-	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib64/clang/${ClangShortVersion}/lib/linux")
+	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib/clang/${ClangShortVersion}/lib/linux")
 
 	// These are tied to the version of LLVM directly in external/llvm, so they might trail the host prebuilts
 	// being used for the rest of the build process.
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 7113d87..7aa8b91 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -212,7 +212,7 @@
 	return true
 }
 
-func sharedLibraryInstallLocation(
+func SharedLibraryInstallLocation(
 	libraryPath android.Path, isHost bool, fuzzDir string, archString string) string {
 	installLocation := "$(PRODUCT_OUT)/data"
 	if isHost {
@@ -224,7 +224,7 @@
 }
 
 // Get the device-only shared library symbols install directory.
-func sharedLibrarySymbolsInstallLocation(libraryPath android.Path, fuzzDir string, archString string) string {
+func SharedLibrarySymbolsInstallLocation(libraryPath android.Path, fuzzDir string, archString string) string {
 	return filepath.Join("$(PRODUCT_OUT)/symbols/data/", fuzzDir, archString, "/lib/", libraryPath.Base())
 }
 
@@ -237,59 +237,64 @@
 		installBase, ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzzBin.binaryDecorator.baseInstaller.install(ctx, file)
 
-	fuzzBin.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzzBin.fuzzPackagedModule.FuzzProperties.Corpus)
-	builder := android.NewRuleBuilder(pctx, ctx)
-	intermediateDir := android.PathForModuleOut(ctx, "corpus")
-	for _, entry := range fuzzBin.fuzzPackagedModule.Corpus {
-		builder.Command().Text("cp").
-			Input(entry).
-			Output(intermediateDir.Join(ctx, entry.Base()))
-	}
-	builder.Build("copy_corpus", "copy corpus")
-	fuzzBin.fuzzPackagedModule.CorpusIntermediateDir = intermediateDir
-
-	fuzzBin.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzzBin.fuzzPackagedModule.FuzzProperties.Data)
-	builder = android.NewRuleBuilder(pctx, ctx)
-	intermediateDir = android.PathForModuleOut(ctx, "data")
-	for _, entry := range fuzzBin.fuzzPackagedModule.Data {
-		builder.Command().Text("cp").
-			Input(entry).
-			Output(intermediateDir.Join(ctx, entry.Rel()))
-	}
-	builder.Build("copy_data", "copy data")
-	fuzzBin.fuzzPackagedModule.DataIntermediateDir = intermediateDir
-
-	if fuzzBin.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
-		fuzzBin.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzzBin.fuzzPackagedModule.FuzzProperties.Dictionary)
-		if fuzzBin.fuzzPackagedModule.Dictionary.Ext() != ".dict" {
-			ctx.PropertyErrorf("dictionary",
-				"Fuzzer dictionary %q does not have '.dict' extension",
-				fuzzBin.fuzzPackagedModule.Dictionary.String())
-		}
-	}
-
-	if fuzzBin.fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
-		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
-		android.WriteFileRule(ctx, configPath, fuzzBin.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
-		fuzzBin.fuzzPackagedModule.Config = configPath
-	}
+	fuzzBin.fuzzPackagedModule = PackageFuzzModule(ctx, fuzzBin.fuzzPackagedModule, pctx)
 
 	// Grab the list of required shared libraries.
 	fuzzBin.sharedLibraries, _ = CollectAllSharedDependencies(ctx)
 
 	for _, lib := range fuzzBin.sharedLibraries {
 		fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
-			sharedLibraryInstallLocation(
+			SharedLibraryInstallLocation(
 				lib, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
 
 		// Also add the dependency on the shared library symbols dir.
 		if !ctx.Host() {
 			fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
-				sharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
+				SharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
 		}
 	}
 }
 
+func PackageFuzzModule(ctx android.ModuleContext, fuzzPackagedModule fuzz.FuzzPackagedModule, pctx android.PackageContext) fuzz.FuzzPackagedModule {
+	fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Corpus)
+	builder := android.NewRuleBuilder(pctx, ctx)
+	intermediateDir := android.PathForModuleOut(ctx, "corpus")
+	for _, entry := range fuzzPackagedModule.Corpus {
+		builder.Command().Text("cp").
+			Input(entry).
+			Output(intermediateDir.Join(ctx, entry.Base()))
+	}
+	builder.Build("copy_corpus", "copy corpus")
+	fuzzPackagedModule.CorpusIntermediateDir = intermediateDir
+
+	fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Data)
+	builder = android.NewRuleBuilder(pctx, ctx)
+	intermediateDir = android.PathForModuleOut(ctx, "data")
+	for _, entry := range fuzzPackagedModule.Data {
+		builder.Command().Text("cp").
+			Input(entry).
+			Output(intermediateDir.Join(ctx, entry.Rel()))
+	}
+	builder.Build("copy_data", "copy data")
+	fuzzPackagedModule.DataIntermediateDir = intermediateDir
+
+	if fuzzPackagedModule.FuzzProperties.Dictionary != nil {
+		fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzzPackagedModule.FuzzProperties.Dictionary)
+		if fuzzPackagedModule.Dictionary.Ext() != ".dict" {
+			ctx.PropertyErrorf("dictionary",
+				"Fuzzer dictionary %q does not have '.dict' extension",
+				fuzzPackagedModule.Dictionary.String())
+		}
+	}
+
+	if fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
+		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
+		android.WriteFileRule(ctx, configPath, fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
+		fuzzPackagedModule.Config = configPath
+	}
+	return fuzzPackagedModule
+}
+
 func NewFuzzer(hod android.HostOrDeviceSupported) *Module {
 	module, binary := newBinary(hod, false)
 	baseInstallerPath := "fuzz"
@@ -344,7 +349,7 @@
 
 // Responsible for generating GNU Make rules that package fuzz targets into
 // their architecture & target/host specific zip file.
-type ccFuzzPackager struct {
+type ccRustFuzzPackager struct {
 	fuzz.FuzzPackager
 	fuzzPackagingArchModules         string
 	fuzzTargetSharedDepsInstallPairs string
@@ -353,7 +358,7 @@
 
 func fuzzPackagingFactory() android.Singleton {
 
-	fuzzPackager := &ccFuzzPackager{
+	fuzzPackager := &ccRustFuzzPackager{
 		fuzzPackagingArchModules:         "SOONG_FUZZ_PACKAGING_ARCH_MODULES",
 		fuzzTargetSharedDepsInstallPairs: "FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS",
 		allFuzzTargetsName:               "ALL_FUZZ_TARGETS",
@@ -361,7 +366,7 @@
 	return fuzzPackager
 }
 
-func (s *ccFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
+func (s *ccRustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
 	// Map between each architecture + host/device combination, and the files that
 	// need to be packaged (in the tuple of {source file, destination folder in
 	// archive}).
@@ -376,19 +381,18 @@
 	sharedLibraryInstalled := make(map[string]bool)
 
 	ctx.VisitAllModules(func(module android.Module) {
-		ccModule, ok := module.(*Module)
-		if !ok || ccModule.Properties.PreventInstall {
+		ccModule, ok := module.(LinkableInterface)
+		if !ok || ccModule.PreventInstall() {
 			return
 		}
 
 		// Discard non-fuzz targets.
-		if ok := fuzz.IsValid(ccModule.FuzzModule); !ok {
+		if ok := fuzz.IsValid(ccModule.FuzzModuleStruct()); !ok {
 			return
 		}
 
 		sharedLibsInstallDirPrefix := "lib"
-		fuzzModule, ok := ccModule.compiler.(*fuzzBinary)
-		if !ok {
+		if !ccModule.IsFuzzModule() {
 			return
 		}
 
@@ -399,12 +403,12 @@
 
 		fpm := fuzz.FuzzPackagedModule{}
 		if ok {
-			fpm = fuzzModule.fuzzPackagedModule
+			fpm = ccModule.FuzzPackagedModule()
 		}
 
 		intermediatePath := "fuzz"
 
-		archString := ccModule.Arch().ArchType.String()
+		archString := ccModule.Target().Arch.ArchType.String()
 		archDir := android.PathForIntermediates(ctx, intermediatePath, hostOrTargetString, archString)
 		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
 
@@ -415,7 +419,7 @@
 		files = s.PackageArtifacts(ctx, module, fpm, archDir, builder)
 
 		// Package shared libraries
-		files = append(files, GetSharedLibsToZip(fuzzModule.sharedLibraries, ccModule, &s.FuzzPackager, archString, sharedLibsInstallDirPrefix, &sharedLibraryInstalled)...)
+		files = append(files, GetSharedLibsToZip(ccModule.FuzzSharedLibraries(), ccModule, &s.FuzzPackager, archString, sharedLibsInstallDirPrefix, &sharedLibraryInstalled)...)
 
 		// The executable.
 		files = append(files, fuzz.FileToZip{android.OutputFileForModule(ctx, ccModule, "unstripped"), ""})
@@ -429,7 +433,7 @@
 	s.CreateFuzzPackage(ctx, archDirs, fuzz.Cc, pctx)
 }
 
-func (s *ccFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+func (s *ccRustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
 	packages := s.Packages.Strings()
 	sort.Strings(packages)
 	sort.Strings(s.FuzzPackager.SharedLibInstallStrings)
@@ -460,7 +464,7 @@
 		// For each architecture-specific shared library dependency, we need to
 		// install it to the output directory. Setup the install destination here,
 		// which will be used by $(copy-many-files) in the Make backend.
-		installDestination := sharedLibraryInstallLocation(
+		installDestination := SharedLibraryInstallLocation(
 			library, module.Host(), fuzzDir, archString)
 		if (*sharedLibraryInstalled)[installDestination] {
 			continue
@@ -479,7 +483,7 @@
 		// we want symbolization tools (like `stack`) to be able to find the symbols
 		// in $ANDROID_PRODUCT_OUT/symbols automagically.
 		if !module.Host() {
-			symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, fuzzDir, archString)
+			symbolsInstallDestination := SharedLibrarySymbolsInstallLocation(library, fuzzDir, archString)
 			symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
 			s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
 				library.String()+":"+symbolsInstallDestination)
diff --git a/cc/library.go b/cc/library.go
index b644728..9421007 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -845,7 +845,11 @@
 		ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
 		return
 	}
-	outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, rootStaticArchives[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 
 	objPaths := ccInfo.CcObjectFiles
@@ -881,9 +885,13 @@
 		ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
 		return
 	}
-	outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
-	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, rootDynamicLibraries[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = android.PathForBazelOut(ctx, ccInfo.UnstrippedOutput)
 
 	var tocFile android.OptionalPath
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 6440ee2..32ea1d4 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -76,7 +76,11 @@
 		return
 	}
 
-	outputPath := android.PathForBazelOut(ctx, outputPaths[0])
+	var outputPath android.Path = android.PathForBazelOut(ctx, outputPaths[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
 	h.module.outputFile = android.OptionalPathForPath(outputPath)
 
 	// HeaderLibraryInfo is an empty struct to indicate to dependencies that this is a header library
diff --git a/cc/library_test.go b/cc/library_test.go
index dab5bb8..de3db99 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -308,6 +308,75 @@
 	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
 }
 
+func TestCcLibraryWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_library {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				Headers:              []string{"foo.h"},
+				RootDynamicLibraries: []string{"foo.so"},
+				UnstrippedOutput:     "foo_unstripped.so",
+			},
+			"//foo/bar:bar_bp2build_cc_library_static": cquery.CcInfo{
+				CcObjectFiles:      []string{"foo.o"},
+				Includes:           []string{"include"},
+				SystemIncludes:     []string{"system_include"},
+				Headers:            []string{"foo.h"},
+				RootStaticArchives: []string{"foo.a"},
+				TidyFiles:          []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	staticFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	outputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm_armv7-a-neon_static/validated/foo.a"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	flagExporter := ctx.ModuleProvider(staticFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	outputFiles, err = sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_library outputfiles %s", err)
+	}
+	expectedOutputFiles = []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	android.AssertStringEquals(t, "unstripped shared library", "outputbase/execroot/__main__/foo_unstripped.so", sharedFoo.(*Module).linker.unstrippedOutputFilePath().String())
+	flagExporter = ctx.ModuleProvider(sharedFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+}
+
 func TestLibraryVersionScript(t *testing.T) {
 	t.Parallel()
 	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
@@ -344,6 +413,59 @@
 
 }
 
+func TestCcLibrarySharedWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_library_shared {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				RootDynamicLibraries: []string{"foo.so"},
+				TocFile:              "foo.so.toc",
+				TidyFiles:            []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	producer := sharedFoo.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm_armv7-a-neon_shared/validated/foo.so"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	tocFilePath := sharedFoo.(*Module).Toc()
+	if !tocFilePath.Valid() {
+		t.Errorf("Invalid tocFilePath: %s", tocFilePath)
+	}
+	tocFile := tocFilePath.Path()
+	expectedToc := "outputbase/execroot/__main__/foo.so.toc"
+	android.AssertStringEquals(t, "toc file", expectedToc, tocFile.String())
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, sharedFoo)[0]
+	expectedFlags := []string{"-Ioutputbase/execroot/__main__/include", "-isystem outputbase/execroot/__main__/system_include"}
+	gotFlags := entries.EntryMap["LOCAL_EXPORT_CFLAGS"]
+	android.AssertDeepEquals(t, "androidmk exported cflags", expectedFlags, gotFlags)
+}
+
 func TestCcLibrarySharedWithBazel(t *testing.T) {
 	t.Parallel()
 	bp := `
diff --git a/cc/linkable.go b/cc/linkable.go
index 0522fc6..9578807 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -3,6 +3,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel/cquery"
+	"android/soong/fuzz"
 	"android/soong/snapshot"
 
 	"github.com/google/blueprint"
@@ -120,6 +121,17 @@
 	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
+	// IsFuzzModule returns true if this a *_fuzz module.
+	IsFuzzModule() bool
+
+	// FuzzPackagedModule returns the fuzz.FuzzPackagedModule for this module.
+	// Expects that IsFuzzModule returns true.
+	FuzzPackagedModule() fuzz.FuzzPackagedModule
+
+	// FuzzSharedLibraries returns the shared library dependencies for this module.
+	// Expects that IsFuzzModule returns true.
+	FuzzSharedLibraries() android.Paths
+
 	Device() bool
 	Host() bool
 
@@ -256,6 +268,9 @@
 
 	// Partition returns the partition string for this module.
 	Partition() string
+
+	// FuzzModule returns the fuzz.FuzzModule associated with the module.
+	FuzzModuleStruct() fuzz.FuzzModule
 }
 
 var (
diff --git a/cc/lto_test.go b/cc/lto_test.go
index fbd91be..ff2eddc 100644
--- a/cc/lto_test.go
+++ b/cc/lto_test.go
@@ -15,10 +15,11 @@
 package cc
 
 import (
-	"android/soong/android"
 	"strings"
 	"testing"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -177,3 +178,35 @@
 		t.Errorf("'baz' expected to have flags %q, but got %q", w, libFooCFlags)
 	}
 }
+
+func TestLtoDisabledButEnabledForArch(t *testing.T) {
+	t.Parallel()
+	bp := `
+	cc_library {
+		name: "libfoo",
+		srcs: ["foo.c"],
+		host_supported:true,
+		lto: {
+			never: true,
+		},
+		target: {
+			android: {
+				lto: {
+					never: false,
+					thin: true,
+				},
+			},
+		},
+	}`
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+	).RunTestWithBp(t, bp)
+
+	libFooWithLto := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+	libFooWithoutLto := result.ModuleForTests("libfoo", "linux_glibc_x86_64_shared").Rule("ld")
+
+	android.AssertStringDoesContain(t, "missing flag for LTO in variant that expects it",
+		libFooWithLto.Args["ldFlags"], "-flto=thin")
+	android.AssertStringDoesNotContain(t, "got flag for LTO in variant that doesn't expect it",
+		libFooWithoutLto.Args["ldFlags"], "-flto=thin")
+}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 9e62bf8..03a600a 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -488,13 +488,17 @@
 		return true
 	}
 
-	out := android.PathForBazelOut(ctx, staticLibs[0])
-	h.module.outputFile = android.OptionalPathForPath(out)
+	var outputPath android.Path = android.PathForBazelOut(ctx, staticLibs[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
 
-	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(out).Build()
+	h.module.outputFile = android.OptionalPathForPath(outputPath)
+
+	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(outputPath).Build()
 	ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
-		StaticLibrary: out,
-
+		StaticLibrary:                        outputPath,
 		TransitiveStaticLibrariesForOrdering: depSet,
 	})
 
@@ -518,21 +522,26 @@
 		return true
 	}
 
-	out := android.PathForBazelOut(ctx, sharedLibs[0])
-	h.module.outputFile = android.OptionalPathForPath(out)
+	var outputPath android.Path = android.PathForBazelOut(ctx, sharedLibs[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
+
+	h.module.outputFile = android.OptionalPathForPath(outputPath)
 
 	// FIXME(b/214600441): We don't yet strip prebuilt shared libraries
-	h.library.unstrippedOutputFile = out
+	h.library.unstrippedOutputFile = outputPath
 
 	var toc android.Path
 	if len(ccInfo.TocFile) > 0 {
 		toc = android.PathForBazelOut(ctx, ccInfo.TocFile)
 	} else {
-		toc = out // Just reuse `out` so ninja still gets an input but won't matter
+		toc = outputPath // Just reuse `out` so ninja still gets an input but won't matter
 	}
 
 	info := SharedLibraryInfo{
-		SharedLibrary:   out,
+		SharedLibrary:   outputPath,
 		TableOfContents: android.OptionalPathForPath(toc),
 		Target:          ctx.Target(),
 	}
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 95fa99e..405680c 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -443,6 +443,75 @@
 		expectedStaticOutputFiles, staticOutputFiles.Strings())
 }
 
+func TestPrebuiltLibraryWithBazelValidations(t *testing.T) {
+	const bp = `
+cc_prebuilt_library {
+	name: "foo",
+	shared: {
+		srcs: ["foo.so"],
+	},
+	static: {
+		srcs: ["foo.a"],
+	},
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	outBaseDir := "outputbase"
+	result := android.GroupFixturePreparers(
+		prepareForPrebuiltTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.BazelContext = android.MockBazelContext{
+				OutputBaseDir: outBaseDir,
+				LabelToCcInfo: map[string]cquery.CcInfo{
+					"//foo/bar:bar": cquery.CcInfo{
+						CcSharedLibraryFiles: []string{"foo.so"},
+						TidyFiles:            []string{"foo.c.tidy"},
+					},
+					"//foo/bar:bar_bp2build_cc_library_static": cquery.CcInfo{
+						CcStaticLibraryFiles: []string{"foo.a"},
+						TidyFiles:            []string{"foo.c.tidy"},
+					},
+				},
+			}
+		}),
+	).RunTestWithBp(t, bp)
+	sharedFoo := result.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+
+	expectedOutputFile := "out/soong/.intermediates/foo/android_arm_armv7-a-neon_shared/validated/foo.so"
+	sharedInfo := result.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t,
+		"prebuilt library shared target path did not exist or did not match expected. If the base path is what does not match, it is likely that Soong built this module instead of Bazel.",
+		expectedOutputFile, sharedInfo.SharedLibrary)
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{expectedOutputFile}
+	android.AssertPathsRelativeToTopEquals(t,
+		"prebuilt library shared target output files did not match expected.",
+		expectedOutputFiles, outputFiles)
+
+	staticFoo := result.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	staticInfo := result.ModuleProvider(staticFoo, StaticLibraryInfoProvider).(StaticLibraryInfo)
+	expectedStaticOutputFile := "out/soong/.intermediates/foo/android_arm_armv7-a-neon_static/validated/foo.a"
+	android.AssertPathRelativeToTopEquals(t,
+		"prebuilt library static target path did not exist or did not match expected. If the base path is what does not match, it is likely that Soong built this module instead of Bazel.",
+		expectedStaticOutputFile, staticInfo.StaticLibrary)
+
+	staticOutputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object staticOutputFiles %s", err)
+	}
+	expectedStaticOutputFiles := []string{expectedStaticOutputFile}
+	android.AssertPathsRelativeToTopEquals(t,
+		"prebuilt library static target output files did not match expected.",
+		expectedStaticOutputFiles, staticOutputFiles)
+}
+
 func TestPrebuiltLibraryWithBazelStaticDisabled(t *testing.T) {
 	const bp = `
 cc_prebuilt_library {
diff --git a/cc/test.go b/cc/test.go
index 5c4d548..27de06b 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -649,7 +649,11 @@
 		return
 	}
 
-	outputFilePath := android.PathForBazelOut(ctx, info.OutputFile)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, info.OutputFile)
+	if len(info.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, info.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*testBinary).unstrippedOutputFile = android.PathForBazelOut(ctx, info.UnstrippedOutput)
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 29a6f95..c005f7c 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -447,6 +447,7 @@
 		"bazel-genfiles",
 		"bazel-out",
 		"bazel-testlogs",
+		"bazel-workspace",
 		"bazel-" + filepath.Base(topDir),
 	}
 }
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 661bd5d..ec6670e 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -220,10 +220,10 @@
 
 	c.run(buildCtx, config, args)
 
-	defer met.Dump(soongMetricsFile)
 	if !config.SkipMetricsUpload() {
 		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, bazelProfileFile, bazelMetricsFile, metricsFiles...)
 	}
+	defer met.Dump(soongMetricsFile)
 
 }
 
diff --git a/compliance/OWNERS b/compliance/OWNERS
new file mode 100644
index 0000000..f52e201
--- /dev/null
+++ b/compliance/OWNERS
@@ -0,0 +1,8 @@
+# OSEP Build
+bbadour@google.com
+kanouche@google.com
+napier@google.com
+
+# Open Source Compliance Tools
+rtp@google.com
+austinyuan@google.com
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index e3404a5..a590c72 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -101,6 +101,10 @@
 }
 
 func dexpreoptDisabled(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) bool {
+	if ctx.Config().UnbundledBuild() {
+		return true
+	}
+
 	if contains(global.DisablePreoptModules, module.Name) {
 		return true
 	}
diff --git a/java/dex.go b/java/dex.go
index a8dd375..38b0881 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -42,7 +42,8 @@
 		// True if the module containing this has it set by default.
 		EnabledByDefault bool `blueprint:"mutated"`
 
-		// Whether to continue building even if warnings are emitted.  Defaults to true.
+		// Whether to continue building even if warnings are emitted.  Defaults to true unless bytecode
+		// optimizations are enabled, in which case warnings are not ignored for safety.
 		Ignore_warnings *bool
 
 		// If true, runs R8 in Proguard compatibility mode (default).
@@ -332,8 +333,9 @@
 	}
 
 	// TODO(b/180878971): missing classes should be added to the relevant builds.
-	// TODO(b/229727645): do not use true as default for Android platform builds.
-	if proptools.BoolDefault(opt.Ignore_warnings, true) {
+	// TODO(b/229727645): do not use true as default for unoptimized platform targets.
+	ignoreWarningsDefault := !Bool(opt.Optimize)
+	if proptools.BoolDefault(opt.Ignore_warnings, ignoreWarningsDefault) {
 		r8Flags = append(r8Flags, "-ignorewarnings")
 	}
 
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 77cbe9c..c4b0af4 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -27,6 +27,7 @@
 	dexpreoptDisabled(ctx android.BaseModuleContext) bool
 	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
 	AndroidMkEntriesForApex() []android.AndroidMkEntries
+	ProfilePathOnHost() android.Path
 }
 
 type dexpreopterInstall struct {
@@ -103,6 +104,9 @@
 	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
 	//   dexpreopt another partition).
 	configPath android.WritablePath
+
+	// The path to the profile on host.
+	profilePathOnHost android.Path
 }
 
 type DexpreoptProperties struct {
@@ -180,9 +184,8 @@
 
 	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
 	if isApexVariant(ctx) {
-		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
-		// building the entire system image.
-		if !isApexSystemServerJar || ctx.Config().UnbundledBuild() {
+		// Don't preopt APEX variant module unless the module is an APEX system server jar.
+		if !isApexSystemServerJar {
 			return true
 		}
 	} else {
@@ -368,21 +371,29 @@
 		installBase := filepath.Base(install.To)
 		arch := filepath.Base(installDir)
 		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+		isProfile := strings.HasSuffix(installBase, ".prof")
+
+		if isProfile {
+			d.profilePathOnHost = install.From
+		}
 
 		if isApexSystemServerJar {
-			// APEX variants of java libraries are hidden from Make, so their dexpreopt
-			// outputs need special handling. Currently, for APEX variants of java
-			// libraries, only those in the system server classpath are handled here.
-			// Preopting of boot classpath jars in the ART APEX are handled in
-			// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
-			// The installs will be handled by Make as sub-modules of the java library.
-			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
-				name:                arch + "-" + installBase,
-				moduleName:          moduleName(ctx),
-				outputPathOnHost:    install.From,
-				installDirOnDevice:  installPath,
-				installFileOnDevice: installBase,
-			})
+			// Profiles are handled separately because they are installed into the APEX.
+			if !isProfile {
+				// APEX variants of java libraries are hidden from Make, so their dexpreopt
+				// outputs need special handling. Currently, for APEX variants of java
+				// libraries, only those in the system server classpath are handled here.
+				// Preopting of boot classpath jars in the ART APEX are handled in
+				// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
+				// The installs will be handled by Make as sub-modules of the java library.
+				d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
+					name:                arch + "-" + installBase,
+					moduleName:          moduleName(ctx),
+					outputPathOnHost:    install.From,
+					installDirOnDevice:  installPath,
+					installFileOnDevice: installBase,
+				})
+			}
 		} else if !d.preventInstall {
 			ctx.InstallFile(installPath, installBase, install.From)
 		}
@@ -404,3 +415,7 @@
 	}
 	return entries
 }
+
+func (d *dexpreopter) ProfilePathOnHost() android.Path {
+	return d.profilePathOnHost
+}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index b872365..a2295f4 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1749,7 +1749,7 @@
 		}
 	}
 
-	mctx.CreateModule(DroidstubsFactory, &props)
+	mctx.CreateModule(DroidstubsFactory, &props).(*Droidstubs).CallHookIfAvailable(mctx)
 }
 
 func (module *SdkLibrary) compareAgainstLatestApi(apiScope *apiScope) bool {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 210bfc3..1d0c13d 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -120,6 +120,7 @@
 	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
 	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
 	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "")
 	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
 	result.ModuleForTests("foo.api.public.28", "")
 	result.ModuleForTests("foo.api.system.28", "")
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 32c746e..20e9919 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -205,8 +205,8 @@
 		})
 }
 
-func (fuzz *fuzzDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.SubAndroidMk(entries, fuzz.binaryDecorator)
+func (fuzz *fuzzDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
+	ctx.SubAndroidMk(ret, fuzz.binaryDecorator)
 
 	var fuzzFiles []string
 	for _, d := range fuzz.fuzzPackagedModule.Corpus {
@@ -229,11 +229,14 @@
 			filepath.Dir(fuzz.fuzzPackagedModule.Config.String())+":config.json")
 	}
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
+	ret.ExtraEntries = append(ret.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
 		entries *android.AndroidMkEntries) {
 		entries.SetBool("LOCAL_IS_FUZZ_TARGET", true)
 		if len(fuzzFiles) > 0 {
 			entries.AddStrings("LOCAL_TEST_DATA", fuzzFiles...)
 		}
+		if fuzz.installedSharedDeps != nil {
+			entries.AddStrings("LOCAL_FUZZ_INSTALLED_SHARED_DEPS", fuzz.installedSharedDeps...)
+		}
 	})
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index e81ec6b..878f896 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -52,7 +52,7 @@
 		if ctx.Config().UseHostMusl() {
 			return "musl/lib/"
 		} else {
-			return "lib64/"
+			return "lib/"
 		}
 	})
 	_ = pctx.SourcePathVariable("bindgenClang",
diff --git a/rust/config/x86_linux_bionic_host.go b/rust/config/x86_linux_bionic_host.go
index b1a2c17..79c40ce 100644
--- a/rust/config/x86_linux_bionic_host.go
+++ b/rust/config/x86_linux_bionic_host.go
@@ -21,7 +21,9 @@
 )
 
 var (
-	LinuxBionicRustFlags     = []string{}
+	LinuxBionicRustFlags = []string{
+		"-C panic=abort",
+	}
 	LinuxBionicRustLinkFlags = []string{
 		"-B${cc_config.ClangBin}",
 		"-fuse-ld=lld",
diff --git a/rust/fuzz.go b/rust/fuzz.go
index 6faf55c..d7e7ddf 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -16,8 +16,6 @@
 
 import (
 	"path/filepath"
-	"sort"
-	"strings"
 
 	"android/soong/android"
 	"android/soong/cc"
@@ -27,14 +25,14 @@
 
 func init() {
 	android.RegisterModuleType("rust_fuzz", RustFuzzFactory)
-	android.RegisterSingletonType("rust_fuzz_packaging", rustFuzzPackagingFactory)
 }
 
 type fuzzDecorator struct {
 	*binaryDecorator
 
-	fuzzPackagedModule fuzz.FuzzPackagedModule
-	sharedLibraries    android.Paths
+	fuzzPackagedModule  fuzz.FuzzPackagedModule
+	sharedLibraries     android.Paths
+	installedSharedDeps []string
 }
 
 var _ compiler = (*fuzzDecorator)(nil)
@@ -64,9 +62,14 @@
 	flags = fuzzer.binaryDecorator.compilerFlags(ctx, flags)
 
 	// `../lib` for installed fuzz targets (both host and device), and `./lib` for fuzz target packages.
-	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
 	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
 
+	if ctx.InstallInVendor() {
+		flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../../lib`)
+	} else {
+		flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
+
+	}
 	return flags
 }
 
@@ -88,10 +91,8 @@
 }
 
 func (fuzzer *fuzzDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
-	out := fuzzer.binaryDecorator.compile(ctx, flags, deps)
 
-	// Grab the list of required shared libraries.
-	fuzzer.sharedLibraries, _ = cc.CollectAllSharedDependencies(ctx)
+	out := fuzzer.binaryDecorator.compile(ctx, flags, deps)
 
 	return out
 }
@@ -104,83 +105,6 @@
 	return rlibAutoDep
 }
 
-// Responsible for generating GNU Make rules that package fuzz targets into
-// their architecture & target/host specific zip file.
-type rustFuzzPackager struct {
-	fuzz.FuzzPackager
-}
-
-func rustFuzzPackagingFactory() android.Singleton {
-	return &rustFuzzPackager{}
-}
-
-func (s *rustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
-	// Map between each architecture + host/device combination.
-	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
-
-	// List of individual fuzz targets.
-	s.FuzzTargets = make(map[string]bool)
-
-	// Map tracking whether each shared library has an install rule to avoid duplicate install rules from
-	// multiple fuzzers that depend on the same shared library.
-	sharedLibraryInstalled := make(map[string]bool)
-
-	ctx.VisitAllModules(func(module android.Module) {
-		// Discard non-fuzz targets.
-		rustModule, ok := module.(*Module)
-		if !ok {
-			return
-		}
-
-		if ok := fuzz.IsValid(rustModule.FuzzModule); !ok || rustModule.Properties.PreventInstall {
-			return
-		}
-
-		fuzzModule, ok := rustModule.compiler.(*fuzzDecorator)
-		if !ok {
-			return
-		}
-
-		hostOrTargetString := "target"
-		if rustModule.Host() {
-			hostOrTargetString = "host"
-		}
-
-		archString := rustModule.Arch().ArchType.String()
-		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
-		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
-
-		var files []fuzz.FileToZip
-		builder := android.NewRuleBuilder(pctx, ctx)
-
-		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
-		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
-
-		// The executable.
-		files = append(files, fuzz.FileToZip{rustModule.UnstrippedOutputFile(), ""})
-
-		// Package shared libraries
-		files = append(files, cc.GetSharedLibsToZip(fuzzModule.sharedLibraries, rustModule, &s.FuzzPackager, archString, "lib", &sharedLibraryInstalled)...)
-
-		archDirs[archOs], ok = s.BuildZipFile(ctx, module, fuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
-		if !ok {
-			return
-		}
-
-	})
-	s.CreateFuzzPackage(ctx, archDirs, fuzz.Rust, pctx)
-}
-
-func (s *rustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
-	packages := s.Packages.Strings()
-	sort.Strings(packages)
-
-	ctx.Strict("SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
-
-	// Preallocate the slice of fuzz targets to minimize memory allocations.
-	s.PreallocateSlice(ctx, "ALL_RUST_FUZZ_TARGETS")
-}
-
 func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
 	fuzz.binaryDecorator.baseCompiler.dir = filepath.Join(
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
@@ -188,13 +112,22 @@
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzz.binaryDecorator.baseCompiler.install(ctx)
 
-	if fuzz.fuzzPackagedModule.FuzzProperties.Corpus != nil {
-		fuzz.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Corpus)
-	}
-	if fuzz.fuzzPackagedModule.FuzzProperties.Data != nil {
-		fuzz.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Data)
-	}
-	if fuzz.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
-		fuzz.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzz.fuzzPackagedModule.FuzzProperties.Dictionary)
+	fuzz.fuzzPackagedModule = cc.PackageFuzzModule(ctx, fuzz.fuzzPackagedModule, pctx)
+
+	installBase := "fuzz"
+
+	// Grab the list of required shared libraries.
+	fuzz.sharedLibraries, _ = cc.CollectAllSharedDependencies(ctx)
+
+	for _, lib := range fuzz.sharedLibraries {
+		fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
+			cc.SharedLibraryInstallLocation(
+				lib, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
+
+		// Also add the dependency on the shared library symbols dir.
+		if !ctx.Host() {
+			fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
+				cc.SharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
+		}
 	}
 }
diff --git a/rust/rust.go b/rust/rust.go
index 28a300b..67e0d7c 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -208,6 +208,11 @@
 			}
 			return android.Paths{}, nil
 		}
+	case "unstripped":
+		if mod.compiler != nil {
+			return android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), nil
+		}
+		return nil, nil
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 	}
@@ -619,6 +624,31 @@
 	return false
 }
 
+func (mod *Module) IsFuzzModule() bool {
+	if _, ok := mod.compiler.(*fuzzDecorator); ok {
+		return true
+	}
+	return false
+}
+
+func (mod *Module) FuzzModuleStruct() fuzz.FuzzModule {
+	return mod.FuzzModule
+}
+
+func (mod *Module) FuzzPackagedModule() fuzz.FuzzPackagedModule {
+	if fuzzer, ok := mod.compiler.(*fuzzDecorator); ok {
+		return fuzzer.fuzzPackagedModule
+	}
+	panic(fmt.Errorf("FuzzPackagedModule called on non-fuzz module: %q", mod.BaseModuleName()))
+}
+
+func (mod *Module) FuzzSharedLibraries() android.Paths {
+	if fuzzer, ok := mod.compiler.(*fuzzDecorator); ok {
+		return fuzzer.sharedLibraries
+	}
+	panic(fmt.Errorf("FuzzSharedLibraries called on non-fuzz module: %q", mod.BaseModuleName()))
+}
+
 func (mod *Module) UnstrippedOutputFile() android.Path {
 	if mod.compiler != nil {
 		return mod.compiler.unstrippedOutputFilePath()
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 6477dac..878b4a1 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -140,7 +140,7 @@
   # NOTE: We don't actually use the extra BUILD file for anything here
   run_bazel build --config=android --config=bp2build --config=ci //foo/...
 
-  local the_answer_file="$(find -L bazel-out -name the_answer.txt)"
+  local -r the_answer_file="$(find -L bazel-out -name the_answer.txt)"
   if [[ ! -f "${the_answer_file}" ]]; then
     fail "Expected the_answer.txt to be generated, but was missing"
   fi
@@ -156,6 +156,49 @@
   eval "${_save_trap}"
 }
 
+function test_bp2build_symlinks_files {
+  setup
+  mkdir -p foo
+  touch foo/BLANK1
+  touch foo/BLANK2
+  touch foo/F2D
+  touch foo/BUILD
+
+  run_soong bp2build
+
+  if [[ -e "./out/soong/workspace/foo/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/BUILD should be omitted"
+  fi
+  for file in BLANK1 BLANK2 F2D
+  do
+    if [[ ! -L "./out/soong/workspace/foo/$file" ]]; then
+      fail "./out/soong/workspace/foo/$file should exist"
+    fi
+  done
+  local -r BLANK1_BEFORE=$(stat -c %y "./out/soong/workspace/foo/BLANK1")
+
+  rm foo/BLANK2
+  rm foo/F2D
+  mkdir foo/F2D
+  touch foo/F2D/BUILD
+
+  run_soong bp2build
+
+  if [[ -e "./out/soong/workspace/foo/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/BUILD should be omitted"
+  fi
+  local -r BLANK1_AFTER=$(stat -c %y "./out/soong/workspace/foo/BLANK1")
+  if [[ "$BLANK1_AFTER" != "$BLANK1_BEFORE" ]]; then
+    fail "./out/soong/workspace/foo/BLANK1 should be untouched"
+  fi
+  if [[  -e "./out/soong/workspace/foo/BLANK2" ]]; then
+    fail "./out/soong/workspace/foo/BLANK2 should be removed"
+  fi
+  if [[ -L "./out/soong/workspace/foo/F2D" ]] || [[ ! -d "./out/soong/workspace/foo/F2D" ]]; then
+    fail "./out/soong/workspace/foo/F2D should be a dir"
+  fi
+}
+
 function test_cc_correctness {
   setup