Merge "Expose "full" dexpreopt.config"
diff --git a/android/Android.bp b/android/Android.bp
index 62d5e20..6124654 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -85,9 +85,11 @@
         "androidmk_test.go",
         "apex_test.go",
         "arch_test.go",
+        "bazel_handler_test.go",
         "bazel_test.go",
         "config_test.go",
         "csuite_config_test.go",
+        "defaults_test.go",
         "depset_test.go",
         "deptag_test.go",
         "expand_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 618e4be..590eceb 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -550,7 +550,9 @@
 		if !amod.InRamdisk() && !amod.InVendorRamdisk() {
 			a.AddPaths("LOCAL_FULL_INIT_RC", amod.initRcPaths)
 		}
-		a.AddStrings("LOCAL_VINTF_FRAGMENTS", amod.commonProperties.Vintf_fragments...)
+		if len(amod.vintfFragmentsPaths) > 0 {
+			a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", amod.vintfFragmentsPaths)
+		}
 		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary))
 		if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
 			a.SetString("LOCAL_VENDOR_MODULE", "true")
diff --git a/android/bazel.go b/android/bazel.go
index 260f1e5..6800c91 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,
@@ -134,7 +170,7 @@
 		"system/logging/liblog": Bp2BuildDefaultTrueRecursively,
 	}
 
-	// Per-module denylist to always opt modules out.
+	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
 		"libBionicBenchmarksUtils",      // ruperts@, cc_library_static, 'map' file not found
 		"libbionic_spawn_benchmark",     // ruperts@, cc_library_static, depends on //system/libbase
@@ -144,8 +180,6 @@
 		"libc_init_dynamic",             // ruperts@, cc_library_static, 'private/bionic_defs.h' file not found
 		"libc_tzcode",                   // ruperts@, cc_library_static, error: expected expression
 		"libc_netbsd",                   // ruperts@, cc_library_static, 'engine.c' file not found
-		"libc_openbsd_large_stack",      // ruperts@, cc_library_static, 'android/log.h' file not found
-		"libc_openbsd",                  // ruperts@, cc_library_static, 'android/log.h' file not found
 		"libc_fortify",                  // ruperts@, cc_library_static, 'private/bionic_fortify.h' file not found
 		"libc_bionic",                   // ruperts@, cc_library_static, 'private/bionic_asm.h' file not found
 		"libc_bionic_ndk",               // ruperts@, cc_library_static, depends on //bionic/libc/system_properties
@@ -167,9 +201,11 @@
 		"liblinker_malloc",              // ruperts@, cc_library_static, depends on //system/logging/liblog:liblog
 		"liblinker_debuggerd_stub",      // ruperts@, cc_library_static, depends on //system/libbase
 		"libbionic_tests_headers_posix", // ruperts@, cc_library_static, 'complex.h' file not found
-		"libc_dns",                      // ruperts@, cc_library_static, 'android/log.h' file not found
+		"libc_dns",                      // ruperts@, cc_library_static, 'private/android_filesystem_config.h' file not found
 		"libc_static_dispatch",          // eakammer@, cc_library_static, 'private/bionic_asm.h' file not found
 		"libc_dynamic_dispatch",         // eakammer@, cc_library_static, 'private/bionic_ifuncs.h' file not found
+		"note_memtag_heap_async",        // jingwen@, cc_library_static, 'private/bionic_asm.h' file not found (arm64)
+		"note_memtag_heap_sync",         // jingwen@, cc_library_static, 'private/bionic_asm.h' file not found (arm64)
 
 		// List of all full_cc_libraries in //bionic, with their immediate failures
 		"libc",              // jingwen@, cc_library, depends on //external/gwp_asan
@@ -179,21 +215,53 @@
 		"libm",              // jingwen@, cc_library, fatal error: 'freebsd-compat.h' file not found
 		"libseccomp_policy", // jingwen@, cc_library, fatal error: 'seccomp_policy.h' file not found
 		"libstdc++",         // jingwen@, cc_library, depends on //external/gwp_asan
+	}
 
-		// For mixed builds specifically
-		"note_memtag_heap_async", // jingwen@, cc_library_static, OK for bp2build but features.h includes not found for mixed builds (b/185079815)
-		"note_memtag_heap_sync",  // jingwen@, cc_library_static, OK for bp2build but features.h includes not found for mixed builds (b/185079815)
-		"libc_gdtoa",             // ruperts@, cc_library_static, OK for bp2build but undefined symbol: __strtorQ for mixed builds
+	// Per-module denylist to opt modules out of mixed builds. Such modules will
+	// still be generated via bp2build.
+	mixedBuildsDisabledList = []string{
+		"libc_gdtoa",   // ruperts@, cc_library_static, OK for bp2build but undefined symbol: __strtorQ for mixed builds
+		"libc_openbsd", // ruperts@, cc_library_static, OK for bp2build but error: duplicate symbol: strcpy for mixed builds
 	}
 
 	// Used for quicker lookups
-	bp2buildModuleDoNotConvert = 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
 	}
+
+	for _, moduleName := range mixedBuildsDisabledList {
+		mixedBuildsDisabled[moduleName] = true
+	}
+}
+
+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 {
+	if !ctx.Config().BazelContext.BazelEnabled() {
+		return false
+	}
+	if len(b.GetBazelLabel(ctx, ctx.Module())) == 0 {
+		return false
+	}
+	return !mixedBuildsDisabled[ctx.Module().Name()]
 }
 
 // ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 8d561d2..4598995 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -321,9 +321,8 @@
 // the invocation returned an error code.
 func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
 	extraFlags ...string) (string, string, error) {
-	cmdFlags := []string{"--output_base=" + paths.outputBase, command.command}
+	cmdFlags := []string{"--output_base=" + absolutePath(paths.outputBase), command.command}
 	cmdFlags = append(cmdFlags, command.expression)
-	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+paths.intermediatesDir())
 	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(paths, runName))
 
 	// Set default platforms to canonicalized values for mixed builds requests.
@@ -334,20 +333,20 @@
 	// The actual platform values here may be overridden by configuration
 	// transitions from the buildroot.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:android_x86_64")))
+		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_x86_64"))
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--extra_toolchains=%s", canonicalizeLabel("//prebuilts/clang/host/linux-x86:all")))
+		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"))
 	// This should be parameterized on the host OS, but let's restrict to linux
 	// to keep things simple for now.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--host_platform=%s", canonicalizeLabel("//build/bazel/platforms:linux_x86_64")))
+		fmt.Sprintf("--host_platform=%s", "//build/bazel/platforms:linux_x86_64"))
 
 	// Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
 	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
 	cmdFlags = append(cmdFlags, extraFlags...)
 
 	bazelCmd := exec.Command(paths.bazelPath, cmdFlags...)
-	bazelCmd.Dir = paths.workspaceDir
+	bazelCmd.Dir = absolutePath(paths.syntheticWorkspaceDir())
 	bazelCmd.Env = append(os.Environ(), "HOME="+paths.homeDir, pwdPrefix(),
 		// Disables local host detection of gcc; toolchain information is defined
 		// explicitly in BUILD files.
@@ -363,31 +362,6 @@
 	}
 }
 
-// Returns the string contents of a workspace file that should be output
-// adjacent to the main bzl file and build file.
-// This workspace file allows, via local_repository rule, sourcetree-level
-// BUILD targets to be referenced via @sourceroot.
-func (context *bazelContext) workspaceFileContents() []byte {
-	formatString := `
-# This file is generated by soong_build. Do not edit.
-local_repository(
-    name = "sourceroot",
-    path = "%[1]s",
-)
-
-local_repository(
-    name = "rules_cc",
-    path = "%[1]s/build/bazel/rules_cc",
-)
-
-local_repository(
-    name = "bazel_skylib",
-    path = "%[1]s/build/bazel/bazel_skylib",
-)
-`
-	return []byte(fmt.Sprintf(formatString, context.paths.workspaceDir))
-}
-
 func (context *bazelContext) mainBzlFileContents() []byte {
 	// TODO(cparsons): Define configuration transitions programmatically based
 	// on available archs.
@@ -398,7 +372,7 @@
 
 def _config_node_transition_impl(settings, attr):
     return {
-        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:android_%s" % attr.arch,
+        "//command_line_option:platforms": "@//build/bazel/platforms:android_%s" % attr.arch,
     }
 
 _config_node_transition = transition(
@@ -447,18 +421,6 @@
 	return []byte(contents)
 }
 
-// Returns a "canonicalized" corresponding to the given sourcetree-level label.
-// This abstraction is required because a sourcetree label such as //foo/bar:baz
-// must be referenced via the local repository prefix, such as
-// @sourceroot//foo/bar:baz.
-func canonicalizeLabel(label string) string {
-	if strings.HasPrefix(label, "//") {
-		return "@sourceroot" + label
-	} else {
-		return "@sourceroot//" + label
-	}
-}
-
 func (context *bazelContext) mainBuildFileContents() []byte {
 	// TODO(cparsons): Map label to attribute programmatically; don't use hard-coded
 	// architecture mapping.
@@ -487,7 +449,7 @@
 
 	labelsByArch := map[string][]string{}
 	for val, _ := range context.requests {
-		labelString := fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label))
+		labelString := fmt.Sprintf("\"@%s\"", val.label)
 		archString := getArchString(val)
 		labelsByArch[archString] = append(labelsByArch[archString], labelString)
 	}
@@ -593,12 +555,24 @@
 		mainSwitchSection))
 }
 
-// Returns a workspace-relative path containing build-related metadata required
-// for interfacing with Bazel. Example: out/soong/bazel.
+// Returns a path containing build-related metadata required for interfacing
+// with Bazel. Example: out/soong/bazel.
 func (p *bazelPaths) intermediatesDir() string {
 	return filepath.Join(p.buildDir, "bazel")
 }
 
+// Returns the path where the contents of the @soong_injection repository live.
+// It is used by Soong to tell Bazel things it cannot over the command line.
+func (p *bazelPaths) injectedFilesDir() string {
+	return filepath.Join(p.buildDir, "soong_injection")
+}
+
+// Returns the path of the synthetic Bazel workspace that contains a symlink
+// forest composed the whole source tree and BUILD files generated by bp2build.
+func (p *bazelPaths) syntheticWorkspaceDir() string {
+	return filepath.Join(p.buildDir, "workspace")
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *bazelContext) InvokeBazel() error {
@@ -608,47 +582,47 @@
 	var cqueryErr string
 	var err error
 
-	intermediatesDirPath := absolutePath(context.paths.intermediatesDir())
-	if _, err := os.Stat(intermediatesDirPath); os.IsNotExist(err) {
-		err = os.Mkdir(intermediatesDirPath, 0777)
+	soongInjectionPath := absolutePath(context.paths.injectedFilesDir())
+	if _, err := os.Stat(soongInjectionPath); os.IsNotExist(err) {
+		err = os.Mkdir(soongInjectionPath, 0777)
 	}
-
 	if err != nil {
 		return err
 	}
+
+	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666)
+	if err != nil {
+		return err
+	}
+
 	err = ioutil.WriteFile(
-		filepath.Join(intermediatesDirPath, "main.bzl"),
+		filepath.Join(soongInjectionPath, "main.bzl"),
 		context.mainBzlFileContents(), 0666)
 	if err != nil {
 		return err
 	}
+
 	err = ioutil.WriteFile(
-		filepath.Join(intermediatesDirPath, "BUILD.bazel"),
+		filepath.Join(soongInjectionPath, "BUILD.bazel"),
 		context.mainBuildFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	cqueryFileRelpath := filepath.Join(context.paths.intermediatesDir(), "buildroot.cquery")
+	cqueryFileRelpath := filepath.Join(context.paths.injectedFilesDir(), "buildroot.cquery")
 	err = ioutil.WriteFile(
 		absolutePath(cqueryFileRelpath),
 		context.cqueryStarlarkFileContents(), 0666)
 	if err != nil {
 		return err
 	}
-	err = ioutil.WriteFile(
-		filepath.Join(intermediatesDirPath, "WORKSPACE.bazel"),
-		context.workspaceFileContents(), 0666)
-	if err != nil {
-		return err
-	}
-	buildrootLabel := "//:buildroot"
+	buildrootLabel := "@soong_injection//:buildroot"
 	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
 		context.paths,
 		bazel.CqueryBuildRootRunName,
 		bazelCommand{"cquery", fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
 		"--output=starlark",
-		"--starlark:file="+cqueryFileRelpath)
-	err = ioutil.WriteFile(filepath.Join(intermediatesDirPath, "cquery.out"),
+		"--starlark:file="+absolutePath(cqueryFileRelpath))
+	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"),
 		[]byte(cqueryOutput), 0666)
 	if err != nil {
 		return err
@@ -702,7 +676,7 @@
 	_, _, err = context.issueBazelCommand(
 		context.paths,
 		bazel.BazelBuildPhonyRootRunName,
-		bazelCommand{"build", "//:phonyroot"})
+		bazelCommand{"build", "@soong_injection//:phonyroot"})
 
 	if err != nil {
 		return err
@@ -781,7 +755,7 @@
 }
 
 func getCqueryId(key cqueryKey) string {
-	return canonicalizeLabel(key.label) + "|" + getArchString(key)
+	return key.label + "|" + getArchString(key)
 }
 
 func getArchString(key cqueryKey) string {
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/android/rule_builder.go b/android/rule_builder.go
index 06e82c8..2507c4c 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -283,6 +283,28 @@
 	return orderOnlyList
 }
 
+// Validations returns the list of paths that were passed to RuleBuilderCommand.Validation or
+// RuleBuilderCommand.Validations.  The list is sorted and duplicates removed.
+func (r *RuleBuilder) Validations() Paths {
+	validations := make(map[string]Path)
+	for _, c := range r.commands {
+		for _, validation := range c.validations {
+			validations[validation.String()] = validation
+		}
+	}
+
+	var validationList Paths
+	for _, validation := range validations {
+		validationList = append(validationList, validation)
+	}
+
+	sort.Slice(validationList, func(i, j int) bool {
+		return validationList[i].String() < validationList[j].String()
+	})
+
+	return validationList
+}
+
 func (r *RuleBuilder) outputSet() map[string]WritablePath {
 	outputs := make(map[string]WritablePath)
 	for _, c := range r.commands {
@@ -460,7 +482,6 @@
 		r.ctx.Build(pctx, BuildParams{
 			Rule:        ErrorRule,
 			Outputs:     r.Outputs(),
-			OrderOnly:   r.OrderOnlys(),
 			Description: desc,
 			Args: map[string]string{
 				"error": "missing dependencies: " + strings.Join(r.missingDeps, ", "),
@@ -707,6 +728,8 @@
 		}),
 		Inputs:          rspFileInputs,
 		Implicits:       inputs,
+		OrderOnly:       r.OrderOnlys(),
+		Validations:     r.Validations(),
 		Output:          output,
 		ImplicitOutputs: implicitOutputs,
 		SymlinkOutputs:  r.SymlinkOutputs(),
@@ -727,6 +750,7 @@
 	inputs         Paths
 	implicits      Paths
 	orderOnlys     Paths
+	validations    Paths
 	outputs        WritablePaths
 	symlinkOutputs WritablePaths
 	depFiles       WritablePaths
@@ -1061,6 +1085,20 @@
 	return c
 }
 
+// Validation adds the specified input path to the validation dependencies by
+// RuleBuilder.Validations without modifying the command line.
+func (c *RuleBuilderCommand) Validation(path Path) *RuleBuilderCommand {
+	c.validations = append(c.validations, path)
+	return c
+}
+
+// Validations adds the specified input paths to the validation dependencies by
+// RuleBuilder.Validations without modifying the command line.
+func (c *RuleBuilderCommand) Validations(paths Paths) *RuleBuilderCommand {
+	c.validations = append(c.validations, paths...)
+	return c
+}
+
 // Output adds the specified output path to the command line.  The path will also be added to the outputs returned by
 // RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index d2a7d8d..feee90f 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"crypto/sha256"
+	"encoding/hex"
 	"fmt"
 	"path/filepath"
 	"regexp"
@@ -320,6 +322,7 @@
 			Input(PathForSource(ctx, "Input")).
 			Output(PathForOutput(ctx, "module/Output")).
 			OrderOnly(PathForSource(ctx, "OrderOnly")).
+			Validation(PathForSource(ctx, "Validation")).
 			SymlinkOutput(PathForOutput(ctx, "module/SymlinkOutput")).
 			ImplicitSymlinkOutput(PathForOutput(ctx, "module/ImplicitSymlinkOutput")).
 			Text("Text").
@@ -331,6 +334,7 @@
 			Input(PathForSource(ctx, "input2")).
 			Output(PathForOutput(ctx, "module/output2")).
 			OrderOnlys(PathsForSource(ctx, []string{"OrderOnlys"})).
+			Validations(PathsForSource(ctx, []string{"Validations"})).
 			Tool(PathForSource(ctx, "tool2"))
 
 		// Test updates to the first command after the second command has been started
@@ -358,6 +362,7 @@
 		"module/DepFile", "module/depfile", "module/ImplicitDepFile", "module/depfile2"})
 	wantTools := PathsForSource(ctx, []string{"Tool", "tool2"})
 	wantOrderOnlys := PathsForSource(ctx, []string{"OrderOnly", "OrderOnlys"})
+	wantValidations := PathsForSource(ctx, []string{"Validation", "Validations"})
 	wantSymlinkOutputs := PathsForOutput(ctx, []string{
 		"module/ImplicitSymlinkOutput", "module/SymlinkOutput"})
 
@@ -385,6 +390,7 @@
 		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
 		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
@@ -414,6 +420,7 @@
 		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
 		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
@@ -443,6 +450,7 @@
 		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
 		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
@@ -472,6 +480,7 @@
 		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
 		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
 	})
@@ -497,6 +506,9 @@
 
 func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	in := PathsForSource(ctx, t.properties.Srcs)
+	implicit := PathForSource(ctx, "implicit")
+	orderOnly := PathForSource(ctx, "orderonly")
+	validation := PathForSource(ctx, "validation")
 	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
 	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
 	outDir := PathForModuleOut(ctx, "gen")
@@ -506,9 +518,9 @@
 	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
 
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat,
-		t.properties.Sbox, t.properties.Sbox_inputs, rspFile, rspFileContents,
-		rspFile2, rspFileContents2)
+	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, out, outDep, outDir,
+		manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs,
+		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
 type testRuleBuilderSingleton struct{}
@@ -518,7 +530,10 @@
 }
 
 func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
-	in := PathForSource(ctx, "bar")
+	in := PathsForSource(ctx, []string{"in"})
+	implicit := PathForSource(ctx, "implicit")
+	orderOnly := PathForSource(ctx, "orderonly")
+	validation := PathForSource(ctx, "validation")
 	out := PathForOutput(ctx, "singleton/gen/baz")
 	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
 	outDir := PathForOutput(ctx, "singleton/gen")
@@ -527,11 +542,14 @@
 	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
 	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
-	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false, false,
+
+	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, out, outDep, outDir,
+		manifestPath, true, false, false,
 		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath,
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path,
+	out, outDep, outDir, manifestPath WritablePath,
 	restat, sbox, sboxInputs bool,
 	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
 
@@ -547,6 +565,9 @@
 	rule.Command().
 		Tool(PathForSource(ctx, "cp")).
 		Inputs(in).
+		Implicit(implicit).
+		OrderOnly(orderOnly).
+		Validation(validation).
 		Output(out).
 		ImplicitDepFile(outDep).
 		FlagWithRspFileInputList("@", rspFile, rspFileContents).
@@ -566,24 +587,24 @@
 
 func TestRuleBuilder_Build(t *testing.T) {
 	fs := MockFS{
-		"bar": nil,
-		"cp":  nil,
+		"in": nil,
+		"cp": nil,
 	}
 
 	bp := `
 		rule_builder_test {
 			name: "foo",
-			srcs: ["bar"],
+			srcs: ["in"],
 			restat: true,
 		}
 		rule_builder_test {
 			name: "foo_sbox",
-			srcs: ["bar"],
+			srcs: ["in"],
 			sbox: true,
 		}
 		rule_builder_test {
 			name: "foo_sbox_inputs",
-			srcs: ["bar"],
+			srcs: ["in"],
 			sbox: true,
 			sbox_inputs: true,
 		}
@@ -614,11 +635,17 @@
 		wantInputs := []string{"rsp_in"}
 		AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings())
 
-		wantImplicits := append([]string{"bar"}, extraImplicits...)
+		wantImplicits := append([]string{"implicit", "in"}, extraImplicits...)
 		// The second rsp file and the files listed in it should be in implicits
 		wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2)
 		AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits)
 
+		wantOrderOnlys := []string{"orderonly"}
+		AssertPathsRelativeToTopEquals(t, "OrderOnly", wantOrderOnlys, params.OrderOnly)
+
+		wantValidations := []string{"validation"}
+		AssertPathsRelativeToTopEquals(t, "Validations", wantValidations, params.Validations)
+
 		wantRspFileContent := "$in"
 		AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent)
 
@@ -646,7 +673,7 @@
 		rspFile2 := "out/soong/.intermediates/foo/rsp2"
 		module := result.ModuleForTests("foo", "")
 		check(t, module.Rule("rule"), module.Output(rspFile2),
-			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
 			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 	t.Run("sbox", func(t *testing.T) {
@@ -688,7 +715,7 @@
 		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
 		singleton := result.SingletonForTests("rule_builder_test")
 		check(t, singleton.Rule("rule"), singleton.Output(rspFile2),
-			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
 			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 }
@@ -702,6 +729,11 @@
 	// the list of inputs changes because the command line or a dependency
 	// changes.
 
+	hashOf := func(s string) string {
+		sum := sha256.Sum256([]byte(s))
+		return hex.EncodeToString(sum[:])
+	}
+
 	bp := `
 			rule_builder_test {
 				name: "hash0",
@@ -727,14 +759,12 @@
 		expectedHash string
 	}{
 		{
-			name: "hash0",
-			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
-			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
+			name:         "hash0",
+			expectedHash: hashOf("implicit\nin1.txt\nin2.txt"),
 		},
 		{
-			name: "hash1",
-			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
-			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
+			name:         "hash1",
+			expectedHash: hashOf("implicit\nin1.txt\nin2.txt\nin3.txt"),
 		},
 	}
 
diff --git a/android/test_asserts.go b/android/test_asserts.go
index bfb88ab..edeb408 100644
--- a/android/test_asserts.go
+++ b/android/test_asserts.go
@@ -126,13 +126,44 @@
 	}
 }
 
+// AssertStringContainsEquals checks if the string contains or does not contain the substring, given
+// the value of the expected bool. If the expectation does not hold it reports an error prefixed with
+// the supplied message and including a reason for why it failed.
+func AssertStringContainsEquals(t *testing.T, message string, s string, substring string, expected bool) {
+	if expected {
+		AssertStringDoesContain(t, message, s, substring)
+	} else {
+		AssertStringDoesNotContain(t, message, s, substring)
+	}
+}
+
 // AssertStringListContains checks if the list of strings contains the expected string. If it does
 // not then it reports an error prefixed with the supplied message and including a reason for why it
 // failed.
-func AssertStringListContains(t *testing.T, message string, list []string, expected string) {
+func AssertStringListContains(t *testing.T, message string, list []string, s string) {
 	t.Helper()
-	if !InList(expected, list) {
-		t.Errorf("%s: could not find %q within %q", message, expected, list)
+	if !InList(s, list) {
+		t.Errorf("%s: could not find %q within %q", message, s, list)
+	}
+}
+
+// AssertStringListDoesNotContain checks if the list of strings contains the expected string. If it does
+// then it reports an error prefixed with the supplied message and including a reason for why it failed.
+func AssertStringListDoesNotContain(t *testing.T, message string, list []string, s string) {
+	t.Helper()
+	if InList(s, list) {
+		t.Errorf("%s: unexpectedly found %q within %q", message, s, list)
+	}
+}
+
+// AssertStringContainsEquals checks if the string contains or does not contain the substring, given
+// the value of the expected bool. If the expectation does not hold it reports an error prefixed with
+// the supplied message and including a reason for why it failed.
+func AssertStringListContainsEquals(t *testing.T, message string, list []string, s string, expected bool) {
+	if expected {
+		AssertStringListContains(t, message, list, s)
+	} else {
+		AssertStringListDoesNotContain(t, message, list, s)
 	}
 }
 
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 9fc701d..06d3b54 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -284,7 +284,7 @@
 					// To install companion files (init_rc, vintf_fragments)
 					// Copy some common properties of apexBundle to apex_manifest
 					commonProperties := []string{
-						"LOCAL_FULL_INIT_RC", "LOCAL_VINTF_FRAGMENTS",
+						"LOCAL_FULL_INIT_RC", "LOCAL_FULL_VINTF_FRAGMENTS",
 					}
 					for _, name := range commonProperties {
 						if value, ok := apexAndroidMkData.Entries.EntryMap[name]; ok {
@@ -394,7 +394,7 @@
 				// Because apex writes .mk with Custom(), we need to write manually some common properties
 				// which are available via data.Entries
 				commonProperties := []string{
-					"LOCAL_FULL_INIT_RC", "LOCAL_VINTF_FRAGMENTS",
+					"LOCAL_FULL_INIT_RC", "LOCAL_FULL_VINTF_FRAGMENTS",
 					"LOCAL_PROPRIETARY_MODULE", "LOCAL_VENDOR_MODULE", "LOCAL_ODM_MODULE", "LOCAL_PRODUCT_MODULE", "LOCAL_SYSTEM_EXT_MODULE",
 					"LOCAL_MODULE_OWNER",
 				}
diff --git a/apex/apex.go b/apex/apex.go
index f5e6fa9..088a462 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
 
@@ -116,16 +110,6 @@
 	// List of filesystem images that are embedded inside this APEX bundle.
 	Filesystems []string
 
-	// Name of the apex_key module that provides the private key to sign this APEX bundle.
-	Key *string
-
-	// Specifies the certificate and the private key to sign the zip container of this APEX. If
-	// this is "foo", foo.x509.pem and foo.pk8 under PRODUCT_DEFAULT_DEV_CERTIFICATE are used
-	// as the certificate and the private key, respectively. If this is ":module", then the
-	// certificate and the private key are provided from the android_app_certificate module
-	// named "module".
-	Certificate *string
-
 	// The minimum SDK version that this APEX must support at minimum. This is usually set to
 	// the SDK version that the APEX was first introduced.
 	Min_sdk_version *string
@@ -305,6 +289,16 @@
 
 	// A txt file containing list of files that are allowed to be included in this APEX.
 	Allowed_files *string `android:"path"`
+
+	// Name of the apex_key module that provides the private key to sign this APEX bundle.
+	Key *string
+
+	// Specifies the certificate and the private key to sign the zip container of this APEX. If
+	// this is "foo", foo.x509.pem and foo.pk8 under PRODUCT_DEFAULT_DEV_CERTIFICATE are used
+	// as the certificate and the private key, respectively. If this is ":module", then the
+	// certificate and the private key are provided from the android_app_certificate module
+	// named "module".
+	Certificate *string
 }
 
 type apexBundle struct {
@@ -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...)
@@ -767,20 +760,6 @@
 		}
 	}
 
-	// Dependencies for signing
-	if String(a.properties.Key) == "" {
-		ctx.PropertyErrorf("key", "missing")
-		return
-	}
-	ctx.AddDependency(ctx.Module(), keyTag, String(a.properties.Key))
-
-	cert := android.SrcIsModule(a.getCertString(ctx))
-	if cert != "" {
-		ctx.AddDependency(ctx.Module(), certificateTag, cert)
-		// empty cert is not an error. Cert and private keys will be directly found under
-		// PRODUCT_DEFAULT_DEV_CERTIFICATE
-	}
-
 	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
 	// This field currently isn't used.
 	// TODO(jiyong): consider dropping this feature
@@ -804,6 +783,20 @@
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps...)
 	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.overridableProperties.Rros...)
+
+	// Dependencies for signing
+	if String(a.overridableProperties.Key) == "" {
+		ctx.PropertyErrorf("key", "missing")
+		return
+	}
+	ctx.AddDependency(ctx.Module(), keyTag, String(a.overridableProperties.Key))
+
+	cert := android.SrcIsModule(a.getCertString(ctx))
+	if cert != "" {
+		ctx.AddDependency(ctx.Module(), certificateTag, cert)
+		// empty cert is not an error. Cert and private keys will be directly found under
+		// PRODUCT_DEFAULT_DEV_CERTIFICATE
+	}
 }
 
 type ApexBundleInfo struct {
@@ -1299,7 +1292,7 @@
 	if overridden {
 		return ":" + certificate
 	}
-	return String(a.properties.Certificate)
+	return String(a.overridableProperties.Certificate)
 }
 
 // See the installable property
@@ -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)
 						}
@@ -1931,19 +1924,19 @@
 					// Rlib is statically linked, but it might have shared lib
 					// dependencies. Track them.
 					return true
-				} else if java.IsbootImageContentDepTag(depTag) {
-					// Add the contents of the boot image to the apex.
+				} else if java.IsBootclasspathFragmentContentDepTag(depTag) {
+					// 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 {
@@ -1956,7 +1949,7 @@
 		return false
 	})
 	if a.privateKeyFile == nil {
-		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.properties.Key))
+		ctx.PropertyErrorf("key", "private_key for %q could not be found", String(a.overridableProperties.Key))
 		return
 	}
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 977a954..c2378fc 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2755,7 +2755,7 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_VINTF_FRAGMENTS := fragment.xml\n")
+	ensureContains(t, androidMk, "LOCAL_FULL_VINTF_FRAGMENTS := fragment.xml\n")
 	ensureContains(t, androidMk, "LOCAL_FULL_INIT_RC := init.rc\n")
 }
 
@@ -5599,6 +5599,8 @@
 			overrides: ["unknownapex"],
 			logging_parent: "com.foo.bar",
 			package_name: "test.overridden.package",
+			key: "mynewapex.key",
+			certificate: ":myapex.certificate",
 		}
 
 		apex_key {
@@ -5607,6 +5609,17 @@
 			private_key: "testkey.pem",
 		}
 
+		apex_key {
+			name: "mynewapex.key",
+			public_key: "testkey2.avbpubkey",
+			private_key: "testkey2.pem",
+		}
+
+		android_app_certificate {
+			name: "myapex.certificate",
+			certificate: "testkey",
+		}
+
 		android_app {
 			name: "app",
 			srcs: ["foo/bar/MyClass.java"],
@@ -5651,6 +5664,10 @@
 
 	optFlags := apexRule.Args["opt_flags"]
 	ensureContains(t, optFlags, "--override_apk_package_name test.overridden.package")
+	ensureContains(t, optFlags, "--pubkey testkey2.avbpubkey")
+
+	signApkRule := module.Rule("signapk")
+	ensureEquals(t, signApkRule.Args["certificates"], "testkey.x509.pem testkey.pk8")
 
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	var builder strings.Builder
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
index d447d70..dab72f7 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,16 +209,32 @@
 		}
 
 		boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			image_name: "art",
 			apex_available: [
 				"com.android.art",
 			],
 		}
 
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		java_import {
+			name: "bar",
+			jars: ["bar.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
 		// 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 +263,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 +310,7 @@
 		}
 
 		prebuilt_boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			image_name: "art",
 			apex_available: [
 				"com.android.art",
@@ -308,23 +324,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 +370,7 @@
 		}
 
 		boot_image {
-			name: "mybootimage",
+			name: "mybootclasspathfragment",
 			contents: [
 				"foo",
 				"bar",
@@ -374,7 +390,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..da8841c 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
@@ -872,7 +871,7 @@
 		return a.containerCertificateFile, a.containerPrivateKeyFile
 	}
 
-	cert := String(a.properties.Certificate)
+	cert := String(a.overridableProperties.Certificate)
 	if cert == "" {
 		return ctx.Config().DefaultAppCertificate(ctx)
 	}
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 74830d3..52b1689 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -137,9 +137,12 @@
 	)
 
 	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
+		// The configured contents of BootJars.
 		"com.android.art:baz",
 		"com.android.art:quuz",
 		"platform:foo",
+
+		// The configured contents of UpdatableBootJars.
 		"myapex:bar",
 	})
 
@@ -149,11 +152,24 @@
 
 	// Make sure that the myplatform-bootclasspath has the correct dependencies.
 	CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
+		// The following are stubs.
+		`platform:android_stubs_current`,
+		`platform:android_system_stubs_current`,
+		`platform:android_test_stubs_current`,
+		`platform:legacy.core.platform.api.stubs`,
+
+		// Needed for generating the boot image.
 		`platform:dex2oatd`,
+
+		// The configured contents of BootJars.
 		`com.android.art:baz`,
 		`com.android.art:quuz`,
 		`platform:foo`,
+
+		// The configured contents of UpdatableBootJars.
 		`myapex:bar`,
+
+		// The fragments.
 		`com.android.art:art-bootclasspath-fragment`,
 	})
 }
diff --git a/apex/testing.go b/apex/testing.go
index 926125f..69bd73e 100644
--- a/apex/testing.go
+++ b/apex/testing.go
@@ -25,5 +25,7 @@
 		// Needed by apex.
 		"system/core/rootdir/etc/public.libraries.android.txt": nil,
 		"build/soong/scripts/gen_ndk_backedby_apex.sh":         nil,
+		// Needed by prebuilt_apex.
+		"build/soong/scripts/unpack-prebuilt-apex.sh": nil,
 	}.AddToFixture(),
 )
diff --git a/bazel/cquery/Android.bp b/bazel/cquery/Android.bp
index 3a71e9c..74f7721 100644
--- a/bazel/cquery/Android.bp
+++ b/bazel/cquery/Android.bp
@@ -11,4 +11,7 @@
     pluginFor: [
         "soong_build",
     ],
+    testSrcs: [
+        "request_type_test.go",
+    ],
 }
diff --git a/bazel/properties.go b/bazel/properties.go
index 4bb2391..48d9589 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.
@@ -109,7 +125,9 @@
 	}
 }
 
-func UniqueBazelLabels(originalLabels []Label) []Label {
+// UniqueSortedBazelLabels takes a []Label and deduplicates the labels, and returns
+// the slice in a sorted order.
+func UniqueSortedBazelLabels(originalLabels []Label) []Label {
 	uniqueLabelsSet := make(map[Label]bool)
 	for _, l := range originalLabels {
 		uniqueLabelsSet[l] = true
@@ -126,8 +144,8 @@
 
 func UniqueBazelLabelList(originalLabelList LabelList) LabelList {
 	var uniqueLabelList LabelList
-	uniqueLabelList.Includes = UniqueBazelLabels(originalLabelList.Includes)
-	uniqueLabelList.Excludes = UniqueBazelLabels(originalLabelList.Excludes)
+	uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
+	uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
 	return uniqueLabelList
 }
 
@@ -276,7 +294,7 @@
 	return LabelListAttribute{Value: UniqueBazelLabelList(value)}
 }
 
-// Append appends all values, including os and arch specific ones, from another
+// Append all values, including os and arch specific ones, from another
 // LabelListAttribute to this LabelListAttribute.
 func (attrs *LabelListAttribute) Append(other LabelListAttribute) {
 	for arch := range PlatformArchMap {
@@ -484,6 +502,26 @@
 	*v = value
 }
 
+// Append appends all values, including os and arch specific ones, from another
+// StringListAttribute to this StringListAttribute
+func (attrs *StringListAttribute) Append(other StringListAttribute) {
+	for arch := range PlatformArchMap {
+		this := attrs.GetValueForArch(arch)
+		that := other.GetValueForArch(arch)
+		this = append(this, that...)
+		attrs.SetValueForArch(arch, this)
+	}
+
+	for os := range PlatformOsMap {
+		this := attrs.GetValueForOS(os)
+		that := other.GetValueForOS(os)
+		this = append(this, that...)
+		attrs.SetValueForOS(os, this)
+	}
+
+	attrs.Value = append(attrs.Value, other.Value...)
+}
+
 // TryVariableSubstitution, replace string substitution formatting within each string in slice with
 // Starlark string.format compatible tag for productVariable.
 func TryVariableSubstitutions(slice []string, productVariable string) ([]string, bool) {
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 56840ef..229a4aa 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -39,7 +39,7 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualUniqueLabels := UniqueBazelLabels(tc.originalLabels)
+		actualUniqueLabels := UniqueSortedBazelLabels(tc.originalLabels)
 		if !reflect.DeepEqual(tc.expectedUniqueLabels, actualUniqueLabels) {
 			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabels, actualUniqueLabels)
 		}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index d2a8729..abd79f5 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -14,6 +14,7 @@
         "constants.go",
         "conversion.go",
         "metrics.go",
+        "symlink_forest.go",
     ],
     deps: [
         "soong-android",
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/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 783af2e..4b6e888 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -109,10 +109,22 @@
 `,
 			expectedBazelTargets: []string{`cc_library(
     name = "foo-lib",
-    copts = ["-Wall"],
+    copts = [
+        "-Wall",
+        "-I.",
+    ],
     deps = [":some-headers"],
-    hdrs = [
+    hdrs = ["foo-dir/a.h"],
+    includes = ["foo-dir"],
+    linkopts = ["-Wl,--exclude-libs=bar.a"] + select({
+        "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=baz.a"],
+        "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=qux.a"],
+        "//conditions:default": [],
+    }),
+    srcs = [
+        "impl.cpp",
         "header.h",
+        "foo-dir/a.h",
         "header.hh",
         "header.hpp",
         "header.hxx",
@@ -121,15 +133,7 @@
         "header.inc",
         "header.ipp",
         "header.h.generic",
-        "foo-dir/a.h",
-    ],
-    includes = ["foo-dir"],
-    linkopts = ["-Wl,--exclude-libs=bar.a"] + select({
-        "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=baz.a"],
-        "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=qux.a"],
-        "//conditions:default": [],
-    }),
-    srcs = ["impl.cpp"] + select({
+    ] + select({
         "//build/bazel/platforms/arch:x86": ["x86.cpp"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
@@ -190,14 +194,9 @@
         "-Wextra",
         "-Wunused",
         "-Werror",
+        "-I.",
     ],
     deps = [":libc_headers"],
-    hdrs = [
-        "linked_list.h",
-        "linker.h",
-        "linker_block_allocator.h",
-        "linker_cfi.h",
-    ],
     linkopts = [
         "-Wl,--exclude-libs=libgcc.a",
         "-Wl,--exclude-libs=libgcc_stripped.a",
@@ -210,7 +209,13 @@
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=libgcc_eh.a"],
         "//conditions:default": [],
     }),
-    srcs = ["ld_android.cpp"],
+    srcs = [
+        "ld_android.cpp",
+        "linked_list.h",
+        "linker.h",
+        "linker_block_allocator.h",
+        "linker_cfi.h",
+    ],
 )`},
 		},
 	}
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index c59241f..3180267 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -131,6 +131,7 @@
 }`,
 			expectedBazelTargets: []string{`cc_library_headers(
     name = "foo_headers",
+    copts = ["-I."],
     deps = [
         ":lib-1",
         ":lib-2",
@@ -157,6 +158,7 @@
     }),
 )`, `cc_library_headers(
     name = "lib-1",
+    copts = ["-I."],
     hdrs = [
         "lib-1/lib1a.h",
         "lib-1/lib1b.h",
@@ -164,6 +166,7 @@
     includes = ["lib-1"],
 )`, `cc_library_headers(
     name = "lib-2",
+    copts = ["-I."],
     hdrs = [
         "lib-2/lib2a.h",
         "lib-2/lib2b.h",
@@ -201,12 +204,16 @@
 }`,
 			expectedBazelTargets: []string{`cc_library_headers(
     name = "android-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "base-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "darwin-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "foo_headers",
+    copts = ["-I."],
     deps = [":base-lib"] + select({
         "//build/bazel/platforms/os:android": [":android-lib"],
         "//build/bazel/platforms/os:darwin": [":darwin-lib"],
@@ -218,12 +225,16 @@
     }),
 )`, `cc_library_headers(
     name = "fuchsia-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "linux-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "linux_bionic-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "windows-lib",
+    copts = ["-I."],
 )`},
 		},
 		{
@@ -244,10 +255,13 @@
 }`,
 			expectedBazelTargets: []string{`cc_library_headers(
     name = "android-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "exported-lib",
+    copts = ["-I."],
 )`, `cc_library_headers(
     name = "foo_headers",
+    copts = ["-I."],
     deps = select({
         "//build/bazel/platforms/os:android": [
             ":android-lib",
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 7e72a8b..00325fb 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -119,71 +119,70 @@
 cc_library_static {
     name: "static_lib_1",
     srcs: ["static_lib_1.cc"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_static {
     name: "static_lib_2",
     srcs: ["static_lib_2.cc"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_static {
     name: "whole_static_lib_1",
     srcs: ["whole_static_lib_1.cc"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_static {
     name: "whole_static_lib_2",
     srcs: ["whole_static_lib_2.cc"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_static {
     name: "foo_static",
     srcs: [
         "foo_static1.cc",
-	"foo_static2.cc",
+        "foo_static2.cc",
     ],
     cflags: [
         "-Dflag1",
-	"-Dflag2"
+        "-Dflag2"
     ],
     static_libs: [
         "static_lib_1",
-	"static_lib_2"
+        "static_lib_2"
     ],
     whole_static_libs: [
         "whole_static_lib_1",
-	"whole_static_lib_2"
+        "whole_static_lib_2"
     ],
     include_dirs: [
-	"include_dir_1",
-	"include_dir_2",
+        "include_dir_1",
+        "include_dir_2",
     ],
     local_include_dirs: [
         "local_include_dir_1",
-	"local_include_dir_2",
+        "local_include_dir_2",
     ],
     export_include_dirs: [
-	"export_include_dir_1",
-	"export_include_dir_2"
+    "export_include_dir_1",
+    "export_include_dir_2"
     ],
     header_libs: [
         "header_lib_1",
-	"header_lib_2"
+        "header_lib_2"
     ],
 
     // TODO: Also support export_header_lib_headers
-
-    bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Dflag1",
         "-Dflag2",
+        "-Iinclude_dir_1",
+        "-Iinclude_dir_2",
+        "-Ilocal_include_dir_1",
+        "-Ilocal_include_dir_2",
+        "-I.",
     ],
     deps = [
         ":header_lib_1",
@@ -194,8 +193,6 @@
         ":whole_static_lib_2",
     ],
     hdrs = [
-        "implicit_include_1.h",
-        "implicit_include_2.h",
         "export_include_dir_1/export_include_dir_1_a.h",
         "export_include_dir_1/export_include_dir_1_b.h",
         "export_include_dir_2/export_include_dir_2_a.h",
@@ -204,16 +201,17 @@
     includes = [
         "export_include_dir_1",
         "export_include_dir_2",
-        "include_dir_1",
-        "include_dir_2",
-        "local_include_dir_1",
-        "local_include_dir_2",
-        ".",
     ],
     linkstatic = True,
     srcs = [
         "foo_static1.cc",
         "foo_static2.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
         "include_dir_1/include_dir_1_a.h",
         "include_dir_1/include_dir_1_b.h",
         "include_dir_2/include_dir_2_a.h",
@@ -222,60 +220,90 @@
         "local_include_dir_1/local_include_dir_1_b.h",
         "local_include_dir_2/local_include_dir_2_a.h",
         "local_include_dir_2/local_include_dir_2_b.h",
-        "implicit_include_1.h",
-        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "static_lib_1",
-    hdrs = [
-        "implicit_include_1.h",
-        "implicit_include_2.h",
-    ],
-    includes = ["."],
+    copts = ["-I."],
     linkstatic = True,
     srcs = [
         "static_lib_1.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
+        "include_dir_1/include_dir_1_a.h",
+        "include_dir_1/include_dir_1_b.h",
+        "include_dir_2/include_dir_2_a.h",
+        "include_dir_2/include_dir_2_b.h",
+        "local_include_dir_1/local_include_dir_1_a.h",
+        "local_include_dir_1/local_include_dir_1_b.h",
+        "local_include_dir_2/local_include_dir_2_a.h",
+        "local_include_dir_2/local_include_dir_2_b.h",
     ],
 )`, `cc_library_static(
     name = "static_lib_2",
-    hdrs = [
-        "implicit_include_1.h",
-        "implicit_include_2.h",
-    ],
-    includes = ["."],
+    copts = ["-I."],
     linkstatic = True,
     srcs = [
         "static_lib_2.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
+        "include_dir_1/include_dir_1_a.h",
+        "include_dir_1/include_dir_1_b.h",
+        "include_dir_2/include_dir_2_a.h",
+        "include_dir_2/include_dir_2_b.h",
+        "local_include_dir_1/local_include_dir_1_a.h",
+        "local_include_dir_1/local_include_dir_1_b.h",
+        "local_include_dir_2/local_include_dir_2_a.h",
+        "local_include_dir_2/local_include_dir_2_b.h",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_1",
-    hdrs = [
-        "implicit_include_1.h",
-        "implicit_include_2.h",
-    ],
-    includes = ["."],
+    copts = ["-I."],
     linkstatic = True,
     srcs = [
         "whole_static_lib_1.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
+        "include_dir_1/include_dir_1_a.h",
+        "include_dir_1/include_dir_1_b.h",
+        "include_dir_2/include_dir_2_a.h",
+        "include_dir_2/include_dir_2_b.h",
+        "local_include_dir_1/local_include_dir_1_a.h",
+        "local_include_dir_1/local_include_dir_1_b.h",
+        "local_include_dir_2/local_include_dir_2_a.h",
+        "local_include_dir_2/local_include_dir_2_b.h",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_2",
-    hdrs = [
-        "implicit_include_1.h",
-        "implicit_include_2.h",
-    ],
-    includes = ["."],
+    copts = ["-I."],
     linkstatic = True,
     srcs = [
         "whole_static_lib_2.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
+        "include_dir_1/include_dir_1_a.h",
+        "include_dir_1/include_dir_1_b.h",
+        "include_dir_2/include_dir_2_a.h",
+        "include_dir_2/include_dir_2_b.h",
+        "local_include_dir_1/local_include_dir_1_a.h",
+        "local_include_dir_1/local_include_dir_1_b.h",
+        "local_include_dir_2/local_include_dir_2_a.h",
+        "local_include_dir_2/local_include_dir_2_b.h",
     ],
 )`},
 		},
@@ -306,14 +334,12 @@
     include_dirs: [
 	"subpackage",
     ],
-
-    bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    includes = [
-        "subpackage",
-        ".",
+    copts = [
+        "-Isubpackage",
+        "-I.",
     ],
     linkstatic = True,
     srcs = [
@@ -326,6 +352,299 @@
     ],
 )`},
 		},
+		{
+			description:                        "cc_library_static export include dir",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			filesystem: map[string]string{
+				// subpackage with subdirectory
+				"subpackage/Android.bp":                         "",
+				"subpackage/subpackage_header.h":                "",
+				"subpackage/subdirectory/subdirectory_header.h": "",
+			},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    export_include_dirs: ["subpackage"],
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = ["-I."],
+    hdrs = [
+        "//subpackage:subdirectory/subdirectory_header.h",
+        "//subpackage:subpackage_header.h",
+    ],
+    includes = ["subpackage"],
+    linkstatic = True,
+    srcs = [
+        "//subpackage:subpackage_header.h",
+        "//subpackage:subdirectory/subdirectory_header.h",
+    ],
+)`},
+		},
+		{
+			description:                        "cc_library_static export system include dir",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			filesystem: map[string]string{
+				// subpackage with subdirectory
+				"subpackage/Android.bp":                         "",
+				"subpackage/subpackage_header.h":                "",
+				"subpackage/subdirectory/subdirectory_header.h": "",
+			},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    export_system_include_dirs: ["subpackage"],
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = ["-I."],
+    hdrs = [
+        "//subpackage:subdirectory/subdirectory_header.h",
+        "//subpackage:subpackage_header.h",
+    ],
+    includes = ["subpackage"],
+    linkstatic = True,
+    srcs = [
+        "//subpackage:subpackage_header.h",
+        "//subpackage:subdirectory/subdirectory_header.h",
+    ],
+)`},
+		},
+		{
+			description:                        "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			dir:                                "subpackage",
+			filesystem: map[string]string{
+				// subpackage with subdirectory
+				"subpackage/Android.bp": `
+cc_library_static {
+    name: "foo_static",
+    // include_dirs are workspace/root relative
+    include_dirs: [
+        "subpackage/subsubpackage",
+        "subpackage2",
+        "subpackage3/subsubpackage"
+    ],
+    local_include_dirs: ["subsubpackage2"], // module dir relative
+    export_include_dirs: ["./exported_subsubpackage"], // module dir relative
+    include_build_directory: true,
+    bazel_module: { bp2build_available: true },
+}`,
+				"subpackage/subsubpackage/header.h":          "",
+				"subpackage/subsubpackage2/header.h":         "",
+				"subpackage/exported_subsubpackage/header.h": "",
+				"subpackage2/header.h":                       "",
+				"subpackage3/subsubpackage/header.h":         "",
+			},
+			bp: soongCcLibraryStaticPreamble,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-Isubpackage/subsubpackage",
+        "-Isubpackage2",
+        "-Isubpackage3/subsubpackage",
+        "-Isubpackage/subsubpackage2",
+        "-Isubpackage",
+    ],
+    hdrs = ["exported_subsubpackage/header.h"],
+    includes = ["./exported_subsubpackage"],
+    linkstatic = True,
+    srcs = [
+        "exported_subsubpackage/header.h",
+        "subsubpackage/header.h",
+        "subsubpackage2/header.h",
+    ],
+)`},
+		},
+		{
+			description:                        "cc_library_static include_build_directory disabled",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			filesystem: map[string]string{
+				// subpackage with subdirectory
+				"subpackage/Android.bp":                         "",
+				"subpackage/subpackage_header.h":                "",
+				"subpackage/subdirectory/subdirectory_header.h": "",
+			},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
+    local_include_dirs: ["subpackage2"],
+    include_build_directory: false,
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-Isubpackage",
+        "-Isubpackage2",
+    ],
+    linkstatic = True,
+)`},
+		},
+		{
+			description:                        "cc_library_static include_build_directory enabled",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			filesystem: map[string]string{
+				// subpackage with subdirectory
+				"subpackage/Android.bp":                         "",
+				"subpackage/subpackage_header.h":                "",
+				"subpackage2/Android.bp":                        "",
+				"subpackage2/subpackage2_header.h":              "",
+				"subpackage/subdirectory/subdirectory_header.h": "",
+			},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
+    local_include_dirs: ["subpackage2"],
+    include_build_directory: true,
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-Isubpackage",
+        "-Isubpackage2",
+        "-I.",
+    ],
+    linkstatic = True,
+    srcs = [
+        "//subpackage:subpackage_header.h",
+        "//subpackage:subdirectory/subdirectory_header.h",
+        "//subpackage2:subpackage2_header.h",
+    ],
+)`},
+		},
+		{
+			description:                        "cc_library_static arch-specific static_libs",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+			filesystem:                         map[string]string{},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static { name: "static_dep" }
+cc_library_static { name: "static_dep2" }
+cc_library_static {
+    name: "foo_static",
+    arch: { arm64: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = ["-I."],
+    deps = select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":static_dep",
+            ":static_dep2",
+        ],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep",
+    copts = ["-I."],
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep2",
+    copts = ["-I."],
+    linkstatic = True,
+)`},
+		},
+		{
+			description:                        "cc_library_static os-specific static_libs",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+			filesystem:                         map[string]string{},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static { name: "static_dep" }
+cc_library_static { name: "static_dep2" }
+cc_library_static {
+    name: "foo_static",
+    target: { android: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = ["-I."],
+    deps = select({
+        "//build/bazel/platforms/os:android": [
+            ":static_dep",
+            ":static_dep2",
+        ],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep",
+    copts = ["-I."],
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep2",
+    copts = ["-I."],
+    linkstatic = True,
+)`},
+		},
+		{
+			description:                        "cc_library_static base, arch and os-specific static_libs",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+			filesystem:                         map[string]string{},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_static { name: "static_dep" }
+cc_library_static { name: "static_dep2" }
+cc_library_static { name: "static_dep3" }
+cc_library_static { name: "static_dep4" }
+cc_library_static {
+    name: "foo_static",
+    static_libs: ["static_dep"],
+    whole_static_libs: ["static_dep2"],
+    target: { android: { static_libs: ["static_dep3"] } },
+    arch: { arm64: { static_libs: ["static_dep4"] } },
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = ["-I."],
+    deps = [
+        ":static_dep",
+        ":static_dep2",
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": [":static_dep4"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":static_dep3"],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep",
+    copts = ["-I."],
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep2",
+    copts = ["-I."],
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep3",
+    copts = ["-I."],
+    linkstatic = True,
+)`, `cc_library_static(
+    name = "static_dep4",
+    copts = ["-I."],
+    linkstatic = True,
+)`},
+		},
 	}
 
 	dir := "."
@@ -352,6 +671,7 @@
 			ctx.DepsBp2BuildMutators(m)
 		}
 		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterBp2BuildConfig(bp2buildConfig)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index a9d24ac..d00a1cb 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -55,8 +55,6 @@
         "a/b/*.c"
     ],
     exclude_srcs: ["a/b/exclude.c"],
-
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{`cc_object(
@@ -66,16 +64,14 @@
         "-Wno-gcc-compat",
         "-Wall",
         "-Werror",
+        "-Iinclude",
+        "-I.",
     ],
-    hdrs = [
+    srcs = [
+        "a/b/c.c",
         "a/b/bar.h",
         "a/b/foo.h",
     ],
-    local_include_dirs = [
-        "include",
-        ".",
-    ],
-    srcs = ["a/b/c.c"],
 )`,
 			},
 		},
@@ -93,7 +89,6 @@
     ],
 
     defaults: ["foo_defaults"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_defaults {
@@ -117,10 +112,8 @@
         "-Wall",
         "-Werror",
         "-fno-addrsig",
-    ],
-    local_include_dirs = [
-        "include",
-        ".",
+        "-Iinclude",
+        "-I.",
     ],
     srcs = ["a/b/c.c"],
 )`,
@@ -139,27 +132,27 @@
     name: "foo",
     srcs: ["a/b/c.c"],
     objs: ["bar"],
-
-    bazel_module: { bp2build_available: true },
 }
 
 cc_object {
     name: "bar",
     srcs: ["x/y/z.c"],
-
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{`cc_object(
     name = "bar",
-    copts = ["-fno-addrsig"],
-    local_include_dirs = ["."],
+    copts = [
+        "-fno-addrsig",
+        "-I.",
+    ],
     srcs = ["x/y/z.c"],
 )`, `cc_object(
     name = "foo",
-    copts = ["-fno-addrsig"],
+    copts = [
+        "-fno-addrsig",
+        "-I.",
+    ],
     deps = [":bar"],
-    local_include_dirs = ["."],
     srcs = ["a/b/c.c"],
 )`,
 			},
@@ -177,8 +170,6 @@
     name: "foo",
     srcs: ["a/b/c.c"],
     include_build_directory: false,
-
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{`cc_object(
@@ -201,8 +192,6 @@
             asflags: ["-DPLATFORM_SDK_VERSION=%d"],
         },
     },
-
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{`cc_object(
@@ -233,6 +222,7 @@
 
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
 		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterBp2BuildConfig(bp2buildConfig)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
@@ -290,17 +280,18 @@
             srcs: ["arch/arm/file.S"], // label list
         },
     },
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    copts = ["-fno-addrsig"] + select({
+    copts = [
+        "-fno-addrsig",
+        "-I.",
+    ] + select({
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//conditions:default": [],
     }),
-    local_include_dirs = ["."],
     srcs = ["a.cpp"] + select({
         "//build/bazel/platforms/arch:arm": ["arch/arm/file.S"],
         "//conditions:default": [],
@@ -334,20 +325,21 @@
             cflags: ["-Wall"],
         },
     },
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    copts = ["-fno-addrsig"] + select({
+    copts = [
+        "-fno-addrsig",
+        "-I.",
+    ] + select({
         "//build/bazel/platforms/arch:arm": ["-Wall"],
         "//build/bazel/platforms/arch:arm64": ["-Wall"],
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//build/bazel/platforms/arch:x86_64": ["-fPIC"],
         "//conditions:default": [],
     }),
-    local_include_dirs = ["."],
     srcs = ["base.cpp"] + select({
         "//build/bazel/platforms/arch:arm": ["arm.cpp"],
         "//build/bazel/platforms/arch:arm64": ["arm64.cpp"],
@@ -377,19 +369,20 @@
             cflags: ["-Wall"],
         },
     },
-    bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    copts = ["-fno-addrsig"] + select({
+    copts = [
+        "-fno-addrsig",
+        "-I.",
+    ] + select({
         "//build/bazel/platforms/os:android": ["-fPIC"],
         "//build/bazel/platforms/os:darwin": ["-Wall"],
         "//build/bazel/platforms/os:windows": ["-fPIC"],
         "//conditions:default": [],
     }),
-    local_include_dirs = ["."],
     srcs = ["base.cpp"],
 )`,
 			},
@@ -409,6 +402,7 @@
 
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
 		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterBp2BuildConfig(bp2buildConfig)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 97729df..b9ffc04 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -110,7 +110,11 @@
 		if err != nil {
 			return "", err
 		}
-		selects += s + ",\n"
+		// s could still be an empty string, e.g. unset slices of structs with
+		// length of 0.
+		if s != "" {
+			selects += s + ",\n"
+		}
 	}
 
 	if len(selects) == 0 {
@@ -137,6 +141,9 @@
 	if err != nil {
 		return "", err
 	}
+	if v == "" {
+		return "", nil
+	}
 	s += fmt.Sprintf("\"%s\": %s", key, v)
 	return s, nil
 }
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 6b47cd1..d67ab3d 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 mode == Bp2Build && !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/conversion_test.go b/bp2build/conversion_test.go
index 9fd6817..262a488 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -19,14 +19,14 @@
 	"testing"
 )
 
-type filepath struct {
+type bazelFilepath struct {
 	dir      string
 	basename string
 }
 
 func TestCreateBazelFiles_QueryView_AddsTopLevelFiles(t *testing.T) {
 	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, QueryView)
-	expectedFilePaths := []filepath{
+	expectedFilePaths := []bazelFilepath{
 		{
 			dir:      "",
 			basename: "BUILD",
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
new file mode 100644
index 0000000..15a6335
--- /dev/null
+++ b/bp2build/symlink_forest.go
@@ -0,0 +1,199 @@
+package bp2build
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"android/soong/shared"
+)
+
+// A tree structure that describes what to do at each directory in the created
+// symlink tree. Currently it is used to enumerate which files/directories
+// should be excluded from symlinking. Each instance of "node" represents a file
+// or a directory. If excluded is true, then that file/directory should be
+// excluded from symlinking. Otherwise, the node is not excluded, but one of its
+// descendants is (otherwise the node in question would not exist)
+type node struct {
+	name     string
+	excluded bool // If false, this is just an intermediate node
+	children map[string]*node
+}
+
+// Ensures that the a node for the given path exists in the tree and returns it.
+func ensureNodeExists(root *node, path string) *node {
+	if path == "" {
+		return root
+	}
+
+	if path[len(path)-1] == '/' {
+		path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
+	}
+
+	dir, base := filepath.Split(path)
+
+	// First compute the parent node...
+	dn := ensureNodeExists(root, dir)
+
+	// then create the requested node as its direct child, if needed.
+	if child, ok := dn.children[base]; ok {
+		return child
+	} else {
+		dn.children[base] = &node{base, false, make(map[string]*node)}
+		return dn.children[base]
+	}
+}
+
+// Turns a list of paths to be excluded into a tree made of "node" objects where
+// the specified paths are marked as excluded.
+func treeFromExcludePathList(paths []string) *node {
+	result := &node{"", false, make(map[string]*node)}
+
+	for _, p := range paths {
+		ensureNodeExists(result, p).excluded = true
+	}
+
+	return result
+}
+
+// Calls readdir() and returns it as a map from the basename of the files in dir
+// to os.FileInfo.
+func readdirToMap(dir string) map[string]os.FileInfo {
+	entryList, err := ioutil.ReadDir(dir)
+	result := make(map[string]os.FileInfo)
+
+	if err != nil {
+		if os.IsNotExist(err) {
+			// It's okay if a directory doesn't exist; it just means that one of the
+			// trees to be merged contains parts the other doesn't
+			return result
+		} else {
+			fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
+			os.Exit(1)
+		}
+	}
+
+	for _, fi := range entryList {
+		result[fi.Name()] = fi
+	}
+
+	return result
+}
+
+// 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 {
+		fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
+		os.Exit(1)
+	}
+}
+
+// Recursively plants a symlink forest at forestDir. The symlink tree will
+// 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, okay *bool) {
+	if exclude != nil && exclude.excluded {
+		// This directory is not needed, bail out
+		return
+	}
+
+	*acc = append(*acc, srcDir)
+	srcDirMap := readdirToMap(shared.JoinPath(topdir, srcDir))
+	buildFilesMap := readdirToMap(shared.JoinPath(topdir, buildFilesDir))
+
+	allEntries := make(map[string]bool)
+	for n, _ := range srcDirMap {
+		allEntries[n] = true
+	}
+
+	for n, _ := range buildFilesMap {
+		allEntries[n] = true
+	}
+
+	err := os.MkdirAll(shared.JoinPath(topdir, forestDir), 0777)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
+		os.Exit(1)
+	}
+
+	for f, _ := range allEntries {
+		if f[0] == '.' {
+			continue // Ignore dotfiles
+		}
+
+		// The full paths of children in the input trees and in the output tree
+		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 excludeChild *node
+		if exclude == nil {
+			excludeChild = nil
+		} else {
+			excludeChild = exclude.children[f]
+		}
+
+		srcChildEntry, sExists := srcDirMap[f]
+		buildFilesChildEntry, bExists := buildFilesMap[f]
+		excluded := excludeChild != nil && excludeChild.excluded
+
+		if excluded {
+			continue
+		}
+
+		if !sExists {
+			if buildFilesChildEntry.IsDir() && excludeChild != nil {
+				// Not in the source tree, but we have to exclude something from under
+				// this subtree, so descend
+				plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
+			} else {
+				// Not in the source tree, symlink BUILD file
+				symlinkIntoForest(topdir, forestChild, buildFilesChild)
+			}
+		} else if !bExists {
+			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, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
+			} else {
+				// Not in the build file tree, symlink source tree, carry on
+				symlinkIntoForest(topdir, forestChild, srcChild)
+			}
+		} else if srcChildEntry.IsDir() && buildFilesChildEntry.IsDir() {
+			// Both are directories. Descend.
+			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 exactly one is a directory\n",
+				srcChild, buildFilesChild)
+			*okay = false
+		}
+	}
+}
+
+// Creates a symlink forest by merging the directory tree at "buildFiles" and
+// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
+// under srcDir on which readdir() had to be called to produce the symlink
+// forest.
+func PlantSymlinkForest(topdir string, forest string, buildFiles string, srcDir string, exclude []string) []string {
+	deps := make([]string, 0)
+	os.RemoveAll(shared.JoinPath(topdir, forest))
+	excludeTree := treeFromExcludePathList(exclude)
+	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/bp2build.go b/cc/bp2build.go
index 0bca30a..79304a5 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -16,7 +16,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel"
-	"strings"
+	"path/filepath"
 )
 
 // bp2build functions and helpers for converting cc_* modules to Bazel.
@@ -53,6 +53,18 @@
 		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
 			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
 			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
+			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
+			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
+		}
+	}
+
+	for _, p := range module.GetArchProperties(&BaseLinkerProperties{}) {
+		// arch specific linker props
+		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
+			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
+			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
+			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
+			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
 		}
 	}
 
@@ -61,51 +73,80 @@
 
 // Convenience struct to hold all attributes parsed from compiler properties.
 type compilerAttributes struct {
-	copts bazel.StringListAttribute
-	srcs  bazel.LabelListAttribute
-	hdrs  bazel.LabelListAttribute
+	copts    bazel.StringListAttribute
+	srcs     bazel.LabelListAttribute
+	includes bazel.StringListAttribute
 }
 
 // bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
 func bp2BuildParseCompilerProps(ctx android.TopDownMutatorContext, module *Module) compilerAttributes {
-	var hdrs, srcs bazel.LabelListAttribute
+	var localHdrs, srcs bazel.LabelListAttribute
 	var copts bazel.StringListAttribute
 
-	hdrsAndSrcs := func(baseCompilerProps *BaseCompilerProperties) (bazel.LabelList, bazel.LabelList) {
-		srcsList := android.BazelLabelForModuleSrcExcludes(
-			ctx, baseCompilerProps.Srcs, baseCompilerProps.Exclude_srcs)
-		hdrsList := android.BazelLabelForModuleSrc(ctx, srcsList.LooseHdrsGlobs(headerExts))
-		return hdrsList, srcsList
+	// Creates the -I flag for a directory, while making the directory relative
+	// to the exec root for Bazel to work.
+	includeFlag := func(dir string) string {
+		// filepath.Join canonicalizes the path, i.e. it takes care of . or .. elements.
+		return "-I" + filepath.Join(ctx.ModuleDir(), dir)
+	}
+
+	// Parse the list of srcs, excluding files from exclude_srcs.
+	parseSrcs := func(baseCompilerProps *BaseCompilerProperties) bazel.LabelList {
+		return android.BazelLabelForModuleSrcExcludes(ctx, baseCompilerProps.Srcs, baseCompilerProps.Exclude_srcs)
+	}
+
+	// Parse the list of module-relative include directories (-I).
+	parseLocalIncludeDirs := func(baseCompilerProps *BaseCompilerProperties) []string {
+		// include_dirs are root-relative, not module-relative.
+		includeDirs := bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
+		return append(includeDirs, baseCompilerProps.Local_include_dirs...)
+	}
+
+	// Parse the list of copts.
+	parseCopts := func(baseCompilerProps *BaseCompilerProperties) []string {
+		copts := append([]string{}, baseCompilerProps.Cflags...)
+		for _, dir := range parseLocalIncludeDirs(baseCompilerProps) {
+			copts = append(copts, includeFlag(dir))
+		}
+		return copts
 	}
 
 	for _, props := range module.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			hdrs.Value, srcs.Value = hdrsAndSrcs(baseCompilerProps)
-			copts.Value = baseCompilerProps.Cflags
+			srcs.Value = parseSrcs(baseCompilerProps)
+			copts.Value = parseCopts(baseCompilerProps)
 			break
 		}
 	}
 
+	if c, ok := module.compiler.(*baseCompiler); ok && c.includeBuildDirectory() {
+		copts.Value = append(copts.Value, includeFlag("."))
+		localHdrs.Value = bp2BuildListHeadersInDir(ctx, ".")
+	} else if c, ok := module.compiler.(*libraryDecorator); ok && c.includeBuildDirectory() {
+		copts.Value = append(copts.Value, includeFlag("."))
+		localHdrs.Value = bp2BuildListHeadersInDir(ctx, ".")
+	}
+
 	for arch, props := range module.GetArchProperties(&BaseCompilerProperties{}) {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			hdrsList, srcsList := hdrsAndSrcs(baseCompilerProps)
-			hdrs.SetValueForArch(arch.Name, bazel.SubtractBazelLabelList(hdrsList, hdrs.Value))
-			srcs.SetValueForArch(arch.Name, srcsList)
-			copts.SetValueForArch(arch.Name, baseCompilerProps.Cflags)
+			srcsList := parseSrcs(baseCompilerProps)
+			srcs.SetValueForArch(arch.Name, bazel.SubtractBazelLabelList(srcsList, srcs.Value))
+			copts.SetValueForArch(arch.Name, parseCopts(baseCompilerProps))
 		}
 	}
 
 	for os, props := range module.GetTargetProperties(&BaseCompilerProperties{}) {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			hdrsList, srcsList := hdrsAndSrcs(baseCompilerProps)
-			hdrs.SetValueForOS(os.Name, bazel.SubtractBazelLabelList(hdrsList, hdrs.Value))
-			srcs.SetValueForOS(os.Name, srcsList)
-			copts.SetValueForOS(os.Name, baseCompilerProps.Cflags)
+			srcsList := parseSrcs(baseCompilerProps)
+			srcs.SetValueForOS(os.Name, bazel.SubtractBazelLabelList(srcsList, srcs.Value))
+			copts.SetValueForOS(os.Name, parseCopts(baseCompilerProps))
 		}
 	}
 
+	// Combine local, non-exported hdrs into srcs
+	srcs.Append(localHdrs)
+
 	return compilerAttributes{
-		hdrs:  hdrs,
 		srcs:  srcs,
 		copts: copts,
 	}
@@ -120,7 +161,6 @@
 // bp2BuildParseLinkerProps creates a label list attribute containing the header library deps of a module, including
 // configurable attribute values.
 func bp2BuildParseLinkerProps(ctx android.TopDownMutatorContext, module *Module) linkerAttributes {
-
 	var deps bazel.LabelListAttribute
 	var linkopts bazel.StringListAttribute
 
@@ -128,8 +168,10 @@
 		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
 			libs := baseLinkerProps.Header_libs
 			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			deps = bazel.MakeLabelListAttribute(
-				android.BazelLabelForModuleDeps(ctx, android.SortedUniqueStrings(libs)))
+			libs = append(libs, baseLinkerProps.Static_libs...)
+			libs = append(libs, baseLinkerProps.Whole_static_libs...)
+			libs = android.SortedUniqueStrings(libs)
+			deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, libs))
 			linkopts.Value = baseLinkerProps.Ldflags
 			break
 		}
@@ -139,6 +181,8 @@
 		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
 			libs := baseLinkerProps.Header_libs
 			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
+			libs = append(libs, baseLinkerProps.Static_libs...)
+			libs = append(libs, baseLinkerProps.Whole_static_libs...)
 			libs = android.SortedUniqueStrings(libs)
 			deps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, libs))
 			linkopts.SetValueForArch(arch.Name, baseLinkerProps.Ldflags)
@@ -149,6 +193,8 @@
 		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
 			libs := baseLinkerProps.Header_libs
 			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
+			libs = append(libs, baseLinkerProps.Static_libs...)
+			libs = append(libs, baseLinkerProps.Whole_static_libs...)
 			libs = android.SortedUniqueStrings(libs)
 			deps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
 			linkopts.SetValueForOS(os.Name, baseLinkerProps.Ldflags)
@@ -162,28 +208,44 @@
 }
 
 func bp2BuildListHeadersInDir(ctx android.TopDownMutatorContext, includeDir string) bazel.LabelList {
-	globs := bazel.GlobsInDir(includeDir, includeDir != ".", headerExts)
+	globs := bazel.GlobsInDir(includeDir, true, headerExts)
 	return android.BazelLabelForModuleSrc(ctx, globs)
 }
 
-// Bazel wants include paths to be relative to the module
-func bp2BuildMakePathsRelativeToModule(ctx android.TopDownMutatorContext, paths []string) []string {
+// Relativize a list of root-relative paths with respect to the module's
+// directory.
+//
+// include_dirs Soong prop are root-relative (b/183742505), but
+// local_include_dirs, export_include_dirs and export_system_include_dirs are
+// module dir relative. This function makes a list of paths entirely module dir
+// relative.
+//
+// For the `include` attribute, Bazel wants the paths to be relative to the
+// module.
+func bp2BuildMakePathsRelativeToModule(ctx android.BazelConversionPathContext, paths []string) []string {
 	var relativePaths []string
 	for _, path := range paths {
-		relativePath := strings.TrimPrefix(path, ctx.ModuleDir()+"/")
+		// Semantics of filepath.Rel: join(ModuleDir, rel(ModuleDir, path)) == path
+		relativePath, err := filepath.Rel(ctx.ModuleDir(), path)
+		if err != nil {
+			panic(err)
+		}
 		relativePaths = append(relativePaths, relativePath)
 	}
 	return relativePaths
 }
 
-// bp2BuildParseExportedIncludes creates a label list attribute contains the
-// exported included directories of a module.
+// bp2BuildParseExportedIncludes creates a string list attribute contains the
+// exported included directories of a module, and a label list attribute
+// containing the exported headers of a module.
 func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) (bazel.StringListAttribute, bazel.LabelListAttribute) {
 	libraryDecorator := module.linker.(*libraryDecorator)
 
+	// Export_system_include_dirs and export_include_dirs are already module dir
+	// relative, so they don't need to be relativized like include_dirs, which
+	// are root-relative.
 	includeDirs := libraryDecorator.flagExporter.Properties.Export_system_include_dirs
 	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
-	includeDirs = bp2BuildMakePathsRelativeToModule(ctx, includeDirs)
 	includeDirsAttribute := bazel.MakeStringListAttribute(includeDirs)
 
 	var headersAttribute bazel.LabelListAttribute
@@ -198,7 +260,6 @@
 		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
 			archIncludeDirs := flagExporterProperties.Export_system_include_dirs
 			archIncludeDirs = append(archIncludeDirs, flagExporterProperties.Export_include_dirs...)
-			archIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, archIncludeDirs)
 
 			// To avoid duplicate includes when base includes + arch includes are combined
 			archIncludeDirs = bazel.SubtractStrings(archIncludeDirs, includeDirs)
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/cc.go b/cc/cc.go
index 7f69d56..98df545 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1145,12 +1145,7 @@
 func (c *Module) isImplementationForLLNDKPublic() bool {
 	library, _ := c.library.(*libraryDecorator)
 	return library != nil && library.hasLLNDKStubs() &&
-		(!Bool(library.Properties.Llndk.Private) ||
-			// TODO(b/170784825): until the LLNDK properties are moved into the cc_library,
-			// the non-Vendor variants of the cc_library don't know if the corresponding
-			// llndk_library set private: true.  Since libft2 is the only private LLNDK
-			// library, hardcode it during the transition.
-			c.BaseModuleName() != "libft2")
+		!Bool(library.Properties.Llndk.Private)
 }
 
 // Returns true for LLNDK-private, VNDK-SP-private, and VNDK-core-private.
@@ -1641,7 +1636,7 @@
 func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
 	bazelModuleLabel := c.GetBazelLabel(actx, c)
 	bazelActionsUsed := false
-	if c.bazelHandler != nil && actx.Config().BazelContext.BazelEnabled() && len(bazelModuleLabel) > 0 {
+	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
 		bazelActionsUsed = c.bazelHandler.generateBazelBuildActions(actx, bazelModuleLabel)
 	}
 	return bazelActionsUsed
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 26d1f8e..c56643b 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2824,6 +2824,100 @@
 	checkEquals(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
 }
 
+func TestEmbeddedLlndkLibrary(t *testing.T) {
+	result := prepareForCcTest.RunTestWithBp(t, `
+	cc_library {
+		name: "libllndk",
+		stubs: { versions: ["1", "2"] },
+		llndk: {
+			symbol_file: "libllndk.map.txt",
+		},
+		export_include_dirs: ["include"],
+	}
+
+	cc_prebuilt_library_shared {
+		name: "libllndkprebuilt",
+		stubs: { versions: ["1", "2"] },
+		llndk: {
+			symbol_file: "libllndkprebuilt.map.txt",
+		},
+	}
+
+	cc_library {
+		name: "libllndk_with_external_headers",
+		stubs: { versions: ["1", "2"] },
+		llndk: {
+			symbol_file: "libllndk.map.txt",
+			export_llndk_headers: ["libexternal_llndk_headers"],
+		},
+		header_libs: ["libexternal_headers"],
+		export_header_lib_headers: ["libexternal_headers"],
+	}
+	cc_library_headers {
+		name: "libexternal_headers",
+		export_include_dirs: ["include"],
+		vendor_available: true,
+	}
+	cc_library_headers {
+		name: "libexternal_llndk_headers",
+		export_include_dirs: ["include_llndk"],
+		llndk: {
+			symbol_file: "libllndk.map.txt",
+		},
+		vendor_available: true,
+	}
+
+	cc_library {
+		name: "libllndk_with_override_headers",
+		stubs: { versions: ["1", "2"] },
+		llndk: {
+			symbol_file: "libllndk.map.txt",
+			override_export_include_dirs: ["include_llndk"],
+		},
+		export_include_dirs: ["include"],
+	}
+	`)
+	actual := result.ModuleVariantsForTests("libllndk")
+	for i := 0; i < len(actual); i++ {
+		if !strings.HasPrefix(actual[i], "android_vendor.29_") {
+			actual = append(actual[:i], actual[i+1:]...)
+			i--
+		}
+	}
+	expected := []string{
+		"android_vendor.29_arm64_armv8-a_shared_1",
+		"android_vendor.29_arm64_armv8-a_shared_2",
+		"android_vendor.29_arm64_armv8-a_shared_current",
+		"android_vendor.29_arm64_armv8-a_shared",
+		"android_vendor.29_arm_armv7-a-neon_shared_1",
+		"android_vendor.29_arm_armv7-a-neon_shared_2",
+		"android_vendor.29_arm_armv7-a-neon_shared_current",
+		"android_vendor.29_arm_armv7-a-neon_shared",
+	}
+	android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
+
+	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
+	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
+
+	params = result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared_1").Description("generate stub")
+	android.AssertSame(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
+
+	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
+		t.Helper()
+		m := result.ModuleForTests(module, variant).Module()
+		f := result.ModuleProvider(m, FlagExporterInfoProvider).(FlagExporterInfo)
+		android.AssertPathsRelativeToTopEquals(t, "exported include dirs for "+module+"["+variant+"]",
+			expectedDirs, f.IncludeDirs)
+	}
+
+	checkExportedIncludeDirs("libllndk", "android_arm64_armv8-a_shared", "include")
+	checkExportedIncludeDirs("libllndk", "android_vendor.29_arm64_armv8-a_shared", "include")
+	checkExportedIncludeDirs("libllndk_with_external_headers", "android_arm64_armv8-a_shared", "include")
+	checkExportedIncludeDirs("libllndk_with_external_headers", "android_vendor.29_arm64_armv8-a_shared", "include_llndk")
+	checkExportedIncludeDirs("libllndk_with_override_headers", "android_arm64_armv8-a_shared", "include")
+	checkExportedIncludeDirs("libllndk_with_override_headers", "android_vendor.29_arm64_armv8-a_shared", "include_llndk")
+}
+
 func TestLlndkHeaders(t *testing.T) {
 	ctx := testCc(t, `
 	llndk_headers {
@@ -3778,59 +3872,6 @@
 	android.AssertStringDoesContain(t, "min sdk version", cFlags, "-target aarch64-linux-android29")
 }
 
-func TestMinSdkVersionsOfCrtObjects(t *testing.T) {
-	ctx := testCc(t, `
-		cc_object {
-			name: "crt_foo",
-			srcs: ["foo.c"],
-			crt: true,
-			stl: "none",
-			min_sdk_version: "28",
-
-		}`)
-
-	arch := "android_arm64_armv8-a"
-	for _, v := range []string{"", "28", "29", "30", "current"} {
-		var variant string
-		if v == "" {
-			variant = arch
-		} else {
-			variant = arch + "_sdk_" + v
-		}
-		cflags := ctx.ModuleForTests("crt_foo", variant).Rule("cc").Args["cFlags"]
-		vNum := v
-		if v == "current" || v == "" {
-			vNum = "10000"
-		}
-		expected := "-target aarch64-linux-android" + vNum + " "
-		android.AssertStringDoesContain(t, "cflag", cflags, expected)
-	}
-}
-
-func TestUseCrtObjectOfCorrectVersion(t *testing.T) {
-	ctx := testCc(t, `
-		cc_binary {
-			name: "bin",
-			srcs: ["foo.c"],
-			stl: "none",
-			min_sdk_version: "29",
-			sdk_version: "current",
-		}
-		`)
-
-	// Sdk variant uses the crt object of the matching min_sdk_version
-	variant := "android_arm64_armv8-a_sdk"
-	crt := ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
-	android.AssertStringDoesContain(t, "crt dep of sdk variant", crt,
-		variant+"_29/crtbegin_dynamic.o")
-
-	// platform variant uses the crt object built for platform
-	variant = "android_arm64_armv8-a"
-	crt = ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
-	android.AssertStringDoesContain(t, "crt dep of platform variant", crt,
-		variant+"/crtbegin_dynamic.o")
-}
-
 type MemtagNoteType int
 
 const (
diff --git a/cc/library.go b/cc/library.go
index 2b0ee46..8acd7c7 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -260,11 +260,10 @@
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
 	linkerAttrs := bp2BuildParseLinkerProps(ctx, m)
 	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, m)
-	compilerAttrs.hdrs.Append(exportedIncludesHeaders)
 
 	attrs := &bazelCcLibraryAttributes{
 		Srcs:     compilerAttrs.srcs,
-		Hdrs:     compilerAttrs.hdrs,
+		Hdrs:     exportedIncludesHeaders,
 		Copts:    compilerAttrs.copts,
 		Linkopts: linkerAttrs.linkopts,
 		Deps:     linkerAttrs.deps,
@@ -404,8 +403,8 @@
 
 func (f *flagExporter) setProvider(ctx android.ModuleContext) {
 	ctx.SetProvider(FlagExporterInfoProvider, FlagExporterInfo{
-		IncludeDirs:       f.dirs,
-		SystemIncludeDirs: f.systemDirs,
+		IncludeDirs:       android.FirstUniquePaths(f.dirs),
+		SystemIncludeDirs: android.FirstUniquePaths(f.systemDirs),
 		Flags:             f.flags,
 		Deps:              f.deps,
 		GeneratedHeaders:  f.headers,
@@ -960,9 +959,8 @@
 	if ctx.IsLlndk() {
 		// LLNDK libraries ignore most of the properties on the cc_library and use the
 		// LLNDK-specific properties instead.
-		deps.HeaderLibs = append(deps.HeaderLibs, library.Properties.Llndk.Export_llndk_headers...)
-		deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders,
-			library.Properties.Llndk.Export_llndk_headers...)
+		deps.HeaderLibs = append([]string(nil), library.Properties.Llndk.Export_llndk_headers...)
+		deps.ReexportHeaderLibHeaders = append([]string(nil), library.Properties.Llndk.Export_llndk_headers...)
 		return deps
 	}
 
@@ -1407,6 +1405,12 @@
 			library.reexportDeps(timestampFiles...)
 		}
 
+		// override the module's export_include_dirs with llndk.override_export_include_dirs
+		// if it is set.
+		if override := library.Properties.Llndk.Override_export_include_dirs; override != nil {
+			library.flagExporter.Properties.Export_include_dirs = override
+		}
+
 		if Bool(library.Properties.Llndk.Export_headers_as_system) {
 			library.flagExporter.Properties.Export_system_include_dirs = append(
 				library.flagExporter.Properties.Export_system_include_dirs,
@@ -1668,6 +1672,13 @@
 
 // hasLLNDKStubs returns true if this cc_library module has a variant that will build LLNDK stubs.
 func (library *libraryDecorator) hasLLNDKStubs() bool {
+	return library.hasVestigialLLNDKLibrary() || String(library.Properties.Llndk.Symbol_file) != ""
+}
+
+// hasVestigialLLNDKLibrary returns true if this cc_library module has a corresponding llndk_library
+// module containing properties describing the LLNDK variant.
+// TODO(b/170784825): remove this once there are no more llndk_library modules.
+func (library *libraryDecorator) hasVestigialLLNDKLibrary() bool {
 	return String(library.Properties.Llndk_stubs) != ""
 }
 
@@ -2163,69 +2174,16 @@
 	}
 
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-
-	var includeDirs []string
-	var localIncludeDirs []string
-	for _, props := range module.compiler.compilerProps() {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			// TODO: these should be arch and os specific.
-			includeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
-			localIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Local_include_dirs)
-			break
-		}
-	}
-
-	// Soong implicitly includes headers from the module's directory.
-	// For Bazel builds to work we have to make these header includes explicit.
-	if module.compiler.(*libraryDecorator).includeBuildDirectory() {
-		localIncludeDirs = append(localIncludeDirs, ".")
-	}
-
-	// For Bazel, be more explicit about headers - list all header files in include dirs as srcs
-	for _, includeDir := range includeDirs {
-		compilerAttrs.srcs.Value.Append(bp2BuildListHeadersInDir(ctx, includeDir))
-	}
-	for _, localIncludeDir := range localIncludeDirs {
-		compilerAttrs.srcs.Value.Append(bp2BuildListHeadersInDir(ctx, localIncludeDir))
-	}
-
-	var staticLibs []string
-	var wholeStaticLibs []string
-	for _, props := range module.linker.linkerProps() {
-		// TODO: move this into bp2buildParseLinkerProps
-		if baseLinkerProperties, ok := props.(*BaseLinkerProperties); ok {
-			staticLibs = baseLinkerProperties.Static_libs
-			wholeStaticLibs = baseLinkerProperties.Whole_static_libs
-			break
-		}
-	}
-
-	// FIXME: Treat Static_libs and Whole_static_libs differently?
-	allDeps := staticLibs
-	allDeps = append(allDeps, wholeStaticLibs...)
-
-	depsLabels := android.BazelLabelForModuleDeps(ctx, allDeps)
-
-	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
-
-	// FIXME: Unify absolute vs relative paths
-	// FIXME: Use -I copts instead of setting includes= ?
-	allIncludes := exportedIncludes
-	allIncludes.Value = append(allIncludes.Value, includeDirs...)
-	allIncludes.Value = append(allIncludes.Value, localIncludeDirs...)
-
-	compilerAttrs.hdrs.Append(exportedIncludesHeaders)
-
 	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
-	depsLabels.Append(linkerAttrs.deps.Value)
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
 
 	attrs := &bazelCcLibraryStaticAttributes{
 		Copts:      compilerAttrs.copts,
 		Srcs:       compilerAttrs.srcs,
-		Deps:       bazel.MakeLabelListAttribute(depsLabels),
+		Deps:       linkerAttrs.deps,
 		Linkstatic: true,
-		Includes:   allIncludes,
-		Hdrs:       compilerAttrs.hdrs,
+		Includes:   exportedIncludes,
+		Hdrs:       exportedIncludesHeaders,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 076ce80..078242f 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -95,14 +95,14 @@
 		return
 	}
 
-	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
+	exportedIncludes, exportedHdrs := bp2BuildParseExportedIncludes(ctx, module)
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
 	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
 
 	attrs := &bazelCcLibraryHeadersAttributes{
 		Copts:    compilerAttrs.copts,
 		Includes: exportedIncludes,
-		Hdrs:     exportedIncludesHeaders,
+		Hdrs:     exportedHdrs,
 		Deps:     linkerAttrs.deps,
 	}
 
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index a46b31c..d05dbce 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -56,7 +56,12 @@
 	Unversioned *bool
 
 	// list of llndk headers to re-export include directories from.
-	Export_llndk_headers []string `android:"arch_variant"`
+	Export_llndk_headers []string
+
+	// list of directories relative to the Blueprints file that willbe added to the include path
+	// (using -I) for any module that links against the LLNDK variant of this module, replacing
+	// any that were listed outside the llndk clause.
+	Override_export_include_dirs []string
 
 	// whether this module can be directly depended upon by libs that are installed
 	// to /vendor and /product.
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/object.go b/cc/object.go
index 9bb279a..d8f1aba 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -112,12 +112,11 @@
 
 // For bp2build conversion.
 type bazelObjectAttributes struct {
-	Srcs               bazel.LabelListAttribute
-	Hdrs               bazel.LabelListAttribute
-	Deps               bazel.LabelListAttribute
-	Copts              bazel.StringListAttribute
-	Asflags            []string
-	Local_include_dirs []string
+	Srcs    bazel.LabelListAttribute
+	Hdrs    bazel.LabelListAttribute
+	Deps    bazel.LabelListAttribute
+	Copts   bazel.StringListAttribute
+	Asflags []string
 }
 
 type bazelObject struct {
@@ -158,18 +157,7 @@
 
 	// Set arch-specific configurable attributes
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	var localIncludeDirs []string
 	var asFlags []string
-	for _, props := range m.compiler.compilerProps() {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			localIncludeDirs = baseCompilerProps.Local_include_dirs
-			break
-		}
-	}
-
-	if c, ok := m.compiler.(*baseCompiler); ok && c.includeBuildDirectory() {
-		localIncludeDirs = append(localIncludeDirs, ".")
-	}
 
 	var deps bazel.LabelListAttribute
 	for _, props := range m.linker.linkerProps() {
@@ -197,12 +185,10 @@
 	// TODO(b/183595872) warn/error if we're not handling product variables
 
 	attrs := &bazelObjectAttributes{
-		Srcs:               compilerAttrs.srcs,
-		Hdrs:               compilerAttrs.hdrs,
-		Deps:               deps,
-		Copts:              compilerAttrs.copts,
-		Asflags:            asFlags,
-		Local_include_dirs: localIncludeDirs,
+		Srcs:    compilerAttrs.srcs,
+		Deps:    deps,
+		Copts:   compilerAttrs.copts,
+		Asflags: asFlags,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/object_test.go b/cc/object_test.go
index f82d544..0e5508a 100644
--- a/cc/object_test.go
+++ b/cc/object_test.go
@@ -19,6 +19,60 @@
 	"testing"
 )
 
+func TestMinSdkVersionsOfCrtObjects(t *testing.T) {
+	ctx := testCc(t, `
+		cc_object {
+			name: "crt_foo",
+			srcs: ["foo.c"],
+			crt: true,
+			stl: "none",
+			min_sdk_version: "28",
+
+		}`)
+
+	arch := "android_arm64_armv8-a"
+	for _, v := range []string{"", "28", "29", "30", "current"} {
+		var variant string
+		// platform variant
+		if v == "" {
+			variant = arch
+		} else {
+			variant = arch + "_sdk_" + v
+		}
+		cflags := ctx.ModuleForTests("crt_foo", variant).Rule("cc").Args["cFlags"]
+		vNum := v
+		if v == "current" || v == "" {
+			vNum = "10000"
+		}
+		expected := "-target aarch64-linux-android" + vNum + " "
+		android.AssertStringDoesContain(t, "cflag", cflags, expected)
+	}
+}
+
+func TestUseCrtObjectOfCorrectVersion(t *testing.T) {
+	ctx := testCc(t, `
+		cc_binary {
+			name: "bin",
+			srcs: ["foo.c"],
+			stl: "none",
+			min_sdk_version: "29",
+			sdk_version: "current",
+		}
+		`)
+
+	// Sdk variant uses the crt object of the matching min_sdk_version
+	variant := "android_arm64_armv8-a_sdk"
+	crt := ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	android.AssertStringDoesContain(t, "crt dep of sdk variant", crt,
+		variant+"_29/crtbegin_dynamic.o")
+
+	// platform variant uses the crt object built for platform
+	variant = "android_arm64_armv8-a"
+	crt = ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	android.AssertStringDoesContain(t, "crt dep of platform variant", crt,
+		variant+"/crtbegin_dynamic.o")
+}
+
 func TestLinkerScript(t *testing.T) {
 	t.Run("script", func(t *testing.T) {
 		testCc(t, `
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/cc/vndk.go b/cc/vndk.go
index 41f9fd3..e224e66 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -233,7 +233,7 @@
 type moduleListerFunc func(ctx android.SingletonContext) (moduleNames, fileNames []string)
 
 var (
-	llndkLibraries                = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsLLNDK && !isVestigialLLNDKModule(m) })
+	llndkLibraries                = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsLLNDK && !isVestigialLLNDKModule(m) && !m.Header() })
 	llndkLibrariesWithoutHWASAN   = vndkModuleListRemover(llndkLibraries, "libclang_rt.hwasan-")
 	vndkSPLibraries               = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKSP })
 	vndkCoreLibraries             = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKCore })
@@ -423,15 +423,24 @@
 	lib, isLib := m.linker.(*libraryDecorator)
 	prebuiltLib, isPrebuiltLib := m.linker.(*prebuiltLibraryLinker)
 
-	if m.UseVndk() && isLib && lib.hasLLNDKStubs() {
+	if m.UseVndk() && isLib && lib.hasVestigialLLNDKLibrary() {
 		llndk := mctx.AddVariationDependencies(nil, llndkStubDepTag, String(lib.Properties.Llndk_stubs))
 		mergeLLNDKToLib(llndk[0].(*Module), &lib.Properties.Llndk, &lib.flagExporter)
 	}
-	if m.UseVndk() && isPrebuiltLib && prebuiltLib.hasLLNDKStubs() {
+	if m.UseVndk() && isPrebuiltLib && prebuiltLib.hasVestigialLLNDKLibrary() {
 		llndk := mctx.AddVariationDependencies(nil, llndkStubDepTag, String(prebuiltLib.Properties.Llndk_stubs))
 		mergeLLNDKToLib(llndk[0].(*Module), &prebuiltLib.Properties.Llndk, &prebuiltLib.flagExporter)
 	}
 
+	if m.UseVndk() && isLib && lib.hasLLNDKStubs() && !lib.hasVestigialLLNDKLibrary() {
+		m.VendorProperties.IsLLNDK = true
+		m.VendorProperties.IsVNDKPrivate = Bool(lib.Properties.Llndk.Private)
+	}
+	if m.UseVndk() && isPrebuiltLib && prebuiltLib.hasLLNDKStubs() && !prebuiltLib.hasVestigialLLNDKLibrary() {
+		m.VendorProperties.IsLLNDK = true
+		m.VendorProperties.IsVNDKPrivate = Bool(prebuiltLib.Properties.Llndk.Private)
+	}
+
 	if (isLib && lib.buildShared()) || (isPrebuiltLib && prebuiltLib.buildShared()) {
 		if m.vndkdep != nil && m.vndkdep.isVndk() && !m.vndkdep.isVndkExt() {
 			processVndkLibrary(mctx, m)
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 7bd0868..f124e40 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -230,7 +230,7 @@
 	}
 
 	// Copy in any files specified by the manifest.
-	err = copyFiles(command.CopyBefore, "", tempDir)
+	err = copyFiles(command.CopyBefore, "", tempDir, false)
 	if err != nil {
 		return "", err
 	}
@@ -255,12 +255,11 @@
 		return "", err
 	}
 
-	commandDescription := rawCommand
-
 	cmd := exec.Command("bash", "-c", rawCommand)
+	buf := &bytes.Buffer{}
 	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
+	cmd.Stdout = buf
+	cmd.Stderr = buf
 
 	if command.GetChdir() {
 		cmd.Dir = tempDir
@@ -276,9 +275,29 @@
 	}
 	err = cmd.Run()
 
+	if err != nil {
+		// The command failed, do a best effort copy of output files out of the sandbox.  This is
+		// especially useful for linters with baselines that print an error message on failure
+		// with a command to copy the output lint errors to the new baseline.  Use a copy instead of
+		// a move to leave the sandbox intact for manual inspection
+		copyFiles(command.CopyAfter, tempDir, "", true)
+	}
+
+	// If the command  was executed but failed with an error, print a debugging message before
+	// the command's output so it doesn't scroll the real error message off the screen.
 	if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
-		return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
-	} else if err != nil {
+		fmt.Fprintf(os.Stderr,
+			"The failing command was run inside an sbox sandbox in temporary directory\n"+
+				"%s\n"+
+				"The failing command line was:\n"+
+				"%s\n",
+			tempDir, rawCommand)
+	}
+
+	// Write the command's combined stdout/stderr.
+	os.Stdout.Write(buf.Bytes())
+
+	if err != nil {
 		return "", err
 	}
 
@@ -290,7 +309,7 @@
 
 		// build error message
 		errorMessage := "mismatch between declared and actual outputs\n"
-		errorMessage += "in sbox command(" + commandDescription + ")\n\n"
+		errorMessage += "in sbox command(" + rawCommand + ")\n\n"
 		errorMessage += "in sandbox " + tempDir + ",\n"
 		errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
 		for _, missingOutputError := range missingOutputErrors {
@@ -351,12 +370,13 @@
 	return missingOutputErrors
 }
 
-// copyFiles copies files in or out of the sandbox.
-func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// copyFiles copies files in or out of the sandbox.  If allowFromNotExists is true then errors
+// caused by a from path not existing are ignored.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, allowFromNotExists bool) error {
 	for _, copyPair := range copies {
 		fromPath := joinPath(fromDir, copyPair.GetFrom())
 		toPath := joinPath(toDir, copyPair.GetTo())
-		err := copyOneFile(fromPath, toPath, copyPair.GetExecutable())
+		err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), allowFromNotExists)
 		if err != nil {
 			return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
 		}
@@ -364,8 +384,9 @@
 	return nil
 }
 
-// copyOneFile copies a file.
-func copyOneFile(from string, to string, executable bool) error {
+// copyOneFile copies a file and its permissions.  If forceExecutable is true it adds u+x to the
+// permissions.  If allowFromNotExists is true it returns nil if the from path doesn't exist.
+func copyOneFile(from string, to string, forceExecutable, allowFromNotExists bool) error {
 	err := os.MkdirAll(filepath.Dir(to), 0777)
 	if err != nil {
 		return err
@@ -373,11 +394,14 @@
 
 	stat, err := os.Stat(from)
 	if err != nil {
+		if os.IsNotExist(err) && allowFromNotExists {
+			return nil
+		}
 		return err
 	}
 
 	perm := stat.Mode()
-	if executable {
+	if forceExecutable {
 		perm = perm | 0100 // u+x
 	}
 
@@ -454,7 +478,7 @@
 		to := applyPathMappings(rspFile.PathMappings, from)
 
 		// Copy the file into the sandbox.
-		err := copyOneFile(from, joinPath(toDir, to), false)
+		err := copyOneFile(from, joinPath(toDir, to), false, false)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index c9cab13..044689e 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -25,6 +25,7 @@
 
 	"android/soong/bp2build"
 	"android/soong/shared"
+
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/deptools"
 
@@ -174,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")
@@ -392,10 +393,39 @@
 	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bp2buildCtx.Context, configuration)
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
-	for _, globPath := range bp2buildCtx.Globs() {
-		ninjaDeps = append(ninjaDeps, globPath.FileListFile(configuration.BuildDir()))
+	ninjaDeps = append(ninjaDeps, bootstrap.GlobFileListFiles(configuration)...)
+
+	// Run the code-generation phase to convert BazelTargetModules to BUILD files
+	// and print conversion metrics to the user.
+	codegenContext := bp2build.NewCodegenContext(configuration, *bp2buildCtx, bp2build.Bp2Build)
+	metrics := bp2build.Codegen(codegenContext)
+
+	generatedRoot := shared.JoinPath(configuration.BuildDir(), "bp2build")
+	workspaceRoot := shared.JoinPath(configuration.BuildDir(), "workspace")
+
+	excludes := []string{
+		"bazel-bin",
+		"bazel-genfiles",
+		"bazel-out",
+		"bazel-testlogs",
+		"bazel-" + filepath.Base(topDir),
 	}
 
+	if bootstrap.CmdlineArgs.NinjaBuildDir[0] != '/' {
+		excludes = append(excludes, bootstrap.CmdlineArgs.NinjaBuildDir)
+	}
+
+	symlinkForestDeps := bp2build.PlantSymlinkForest(
+		topDir, workspaceRoot, generatedRoot, configuration.SrcDir(), excludes)
+
+	// Only report metrics when in bp2build mode. The metrics aren't relevant
+	// for queryview, since that's a total repo-wide conversion and there's a
+	// 1:1 mapping for each module.
+	metrics.Print()
+
+	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
+	ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
+
 	depFile := bp2buildMarker + ".d"
 	err = deptools.WriteDepFile(shared.JoinPath(topDir, depFile), bp2buildMarker, ninjaDeps)
 	if err != nil {
@@ -403,17 +433,6 @@
 		os.Exit(1)
 	}
 
-	// Run the code-generation phase to convert BazelTargetModules to BUILD files
-	// and print conversion metrics to the user.
-	codegenContext := bp2build.NewCodegenContext(configuration, *bp2buildCtx, bp2build.Bp2Build)
-	metrics := bp2build.Codegen(codegenContext)
-
-	// Only report metrics when in bp2build mode. The metrics aren't relevant
-	// for queryview, since that's a total repo-wide conversion and there's a
-	// 1:1 mapping for each module.
-	metrics.Print()
-
-	extraNinjaDeps = append(extraNinjaDeps, codegenContext.AdditionalNinjaDeps()...)
 	if bp2buildMarker != "" {
 		touch(shared.JoinPath(topDir, bp2buildMarker))
 	} else {
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/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index ad52b00..f76a205 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -544,13 +544,13 @@
 // Recursive helper for toJsonClassLoaderContext.
 func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) []*jsonClassLoaderContext {
 	jClcs := make([]*jsonClassLoaderContext, len(clcs))
-	for _, clc := range clcs {
-		jClcs = append(jClcs, &jsonClassLoaderContext{
+	for i, clc := range clcs {
+		jClcs[i] = &jsonClassLoaderContext{
 			Name:        clc.Name,
 			Host:        clc.Host.String(),
 			Device:      clc.Device,
 			Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts),
-		})
+		}
 	}
 	return jClcs
 }
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 86f7871..610a4c9 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -155,6 +155,29 @@
 	})
 }
 
+func TestCLCJson(t *testing.T) {
+	ctx := testContext()
+	m := make(ClassLoaderContextMap)
+	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	jsonCLC := toJsonClassLoaderContext(m)
+	restored := fromJsonClassLoaderContext(ctx, jsonCLC)
+	android.AssertIntEquals(t, "The size of the maps should be the same.", len(m), len(restored))
+	for k := range m {
+		a, _ := m[k]
+		b, ok := restored[k]
+		android.AssertBoolEquals(t, "The both maps should have the same keys.", ok, true)
+		android.AssertIntEquals(t, "The size of the elements should be the same.", len(a), len(b))
+		for i, elemA := range a {
+			before := fmt.Sprintf("%v", *elemA)
+			after := fmt.Sprintf("%v", *b[i])
+			android.AssertStringEquals(t, "The content should be the same.", before, after)
+		}
+	}
+}
+
 // Test that unknown library paths cause a validation error.
 func testCLCUnknownPath(t *testing.T, whichPath string) {
 	ctx := testContext()
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 e6a5ab9..77dae75 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -543,7 +543,7 @@
 
 	bazelModuleLabel := g.GetBazelLabel(ctx, g)
 	bazelActionsUsed := false
-	if ctx.Config().BazelContext.BazelEnabled() && len(bazelModuleLabel) > 0 {
+	if g.MixedBuildsEnabled(ctx) {
 		bazelActionsUsed = g.generateBazelBuildActions(ctx, bazelModuleLabel)
 	}
 	if !bazelActionsUsed {
@@ -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/Android.bp b/java/Android.bp
index 2a4b596..8e3e10d 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -79,9 +79,11 @@
         "droiddoc_test.go",
         "droidstubs_test.go",
         "hiddenapi_singleton_test.go",
+        "jacoco_test.go",
         "java_test.go",
         "jdeps_test.go",
         "kotlin_test.go",
+        "lint_test.go",
         "platform_bootclasspath_test.go",
         "platform_compat_config_test.go",
         "plugin_test.go",
diff --git a/java/boot_image.go b/java/boot_image.go
index 0c47976..d0862a9 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"android/soong/android"
@@ -53,7 +54,7 @@
 	ctx.RegisterModuleType("prebuilt_bootclasspath_fragment", prebuiltBootImageFactory)
 }
 
-type bootImageContentDependencyTag struct {
+type bootclasspathFragmentContentDependencyTag struct {
 	blueprint.BaseDependencyTag
 }
 
@@ -62,16 +63,22 @@
 // This is a temporary workaround to make it easier to migrate to boot image modules with proper
 // dependencies.
 // TODO(b/177892522): Remove this and add needed visibility.
-func (b bootImageContentDependencyTag) ExcludeFromVisibilityEnforcement() {
+func (b bootclasspathFragmentContentDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+// The bootclasspath_fragment contents must never depend on prebuilts.
+func (b bootclasspathFragmentContentDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
 }
 
 // The tag used for the dependency between the boot image module and its contents.
-var bootImageContentDepTag = bootImageContentDependencyTag{}
+var bootclasspathFragmentContentDepTag = bootclasspathFragmentContentDependencyTag{}
 
-var _ android.ExcludeFromVisibilityEnforcementTag = bootImageContentDepTag
+var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
+var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
 
-func IsbootImageContentDepTag(tag blueprint.DependencyTag) bool {
-	return tag == bootImageContentDepTag
+func IsBootclasspathFragmentContentDepTag(tag blueprint.DependencyTag) bool {
+	return tag == bootclasspathFragmentContentDepTag
 }
 
 type bootImageProperties struct {
@@ -84,6 +91,8 @@
 	//
 	// The order of this list matters as it is the order that is used in the bootclasspath.
 	Contents []string
+
+	Hidden_api HiddenAPIFlagFileProperties
 }
 
 type BootImageModule struct {
@@ -185,7 +194,7 @@
 
 func (b *BootImageModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
-	if tag == bootImageContentDepTag {
+	if IsBootclasspathFragmentContentDepTag(tag) {
 		// Boot image contents are automatically added to apex.
 		return true
 	}
@@ -200,8 +209,28 @@
 	return nil
 }
 
+// ComponentDepsMutator adds dependencies onto modules before any prebuilt modules without a
+// corresponding source module are renamed. This means that adding a dependency using a name without
+// a prebuilt_ prefix will always resolve to a source module and when using a name with that prefix
+// it will always resolve to a prebuilt module.
+func (b *BootImageModule) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
+	module := ctx.Module()
+	_, isSourceModule := module.(*BootImageModule)
+
+	for _, name := range b.properties.Contents {
+		// A bootclasspath_fragment must depend only on other source modules, while the
+		// prebuilt_bootclasspath_fragment must only depend on other prebuilt modules.
+		//
+		// TODO(b/177892522) - avoid special handling of jacocoagent.
+		if !isSourceModule && name != "jacocoagent" {
+			name = android.PrebuiltNameFromSource(name)
+		}
+		ctx.AddDependency(module, bootclasspathFragmentContentDepTag, name)
+	}
+
+}
+
 func (b *BootImageModule) DepsMutator(ctx android.BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), bootImageContentDepTag, b.properties.Contents...)
 
 	if SkipDexpreoptBootJars(ctx) {
 		return
@@ -213,6 +242,9 @@
 }
 
 func (b *BootImageModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Perform hidden API processing.
+	b.generateHiddenAPIBuildActions(ctx)
+
 	// Nothing to do if skipping the dexpreopt of boot image jars.
 	if SkipDexpreoptBootJars(ctx) {
 		return
@@ -253,6 +285,15 @@
 	return imageConfig
 }
 
+// generateHiddenAPIBuildActions generates all the hidden API related build rules.
+func (b *BootImageModule) generateHiddenAPIBuildActions(ctx android.ModuleContext) {
+	// Resolve the properties to paths.
+	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
+
+	// Store the information for use by platform_bootclasspath.
+	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
+}
+
 type bootImageMemberType struct {
 	android.SdkMemberTypeBase
 }
@@ -281,19 +322,58 @@
 type bootImageSdkMemberProperties struct {
 	android.SdkMemberPropertiesBase
 
+	// The image name
 	Image_name *string
+
+	// Contents of the bootclasspath fragment
+	Contents []string
+
+	// Flag files by *hiddenAPIFlagFileCategory
+	Flag_files_by_category map[*hiddenAPIFlagFileCategory]android.Paths
 }
 
 func (b *bootImageSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	module := variant.(*BootImageModule)
 
 	b.Image_name = module.properties.Image_name
+	if b.Image_name == nil {
+		// Only one of image_name or contents can be specified. However, if image_name is set then the
+		// contents property is updated to match the configuration used to create the corresponding
+		// boot image. Therefore, contents property is only copied if the image name is not specified.
+		b.Contents = module.properties.Contents
+	}
+
+	// Get the flag file information from the module.
+	mctx := ctx.SdkModuleContext()
+	flagFileInfo := mctx.OtherModuleProvider(module, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
+	b.Flag_files_by_category = flagFileInfo.categoryToPaths
 }
 
 func (b *bootImageSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
 	if b.Image_name != nil {
 		propertySet.AddProperty("image_name", *b.Image_name)
 	}
+
+	if len(b.Contents) > 0 {
+		propertySet.AddPropertyWithTag("contents", b.Contents, ctx.SnapshotBuilder().SdkMemberReferencePropertyTag(true))
+	}
+
+	builder := ctx.SnapshotBuilder()
+	if b.Flag_files_by_category != nil {
+		hiddenAPISet := propertySet.AddPropertySet("hidden_api")
+		for _, category := range hiddenAPIFlagFileCategories {
+			paths := b.Flag_files_by_category[category]
+			if len(paths) > 0 {
+				dests := []string{}
+				for _, p := range paths {
+					dest := filepath.Join("hiddenapi", p.Base())
+					builder.CopyToSnapshot(p, dest)
+					dests = append(dests, dest)
+				}
+				hiddenAPISet.AddProperty(category.propertyName, dests)
+			}
+		}
+	}
 }
 
 var _ android.SdkMemberType = (*bootImageMemberType)(nil)
diff --git a/java/droidstubs.go b/java/droidstubs.go
index a9e2749..2676f3d 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -516,9 +516,6 @@
 		d.apiLintTimestamp = android.PathForModuleOut(ctx, "metalava", "api_lint.timestamp")
 
 		// Note this string includes a special shell quote $' ... ', which decodes the "\n"s.
-		// However, because $' ... ' doesn't expand environmental variables, we can't just embed
-		// $PWD, so we have to terminate $'...', use "$PWD", then start $' ... ' again,
-		// which is why we have '"$PWD"$' in it.
 		//
 		// TODO: metalava also has a slightly different message hardcoded. Should we unify this
 		// message and metalava's one?
@@ -539,9 +536,9 @@
 			msg += fmt.Sprintf(``+
 				`2. You can update the baseline by executing the following\n`+
 				`   command:\n`+
-				`       cp \\\n`+
-				`       "'"$PWD"$'/%s" \\\n`+
-				`       "'"$PWD"$'/%s"\n`+
+				`       (cd $ANDROID_BUILD_TOP && cp \\\n`+
+				`       "%s" \\\n`+
+				`       "%s")\n`+
 				`   To submit the revised baseline.txt to the main Android\n`+
 				`   repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path())
 		} else {
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 7cf082b..8cc6f8f 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -16,10 +16,163 @@
 
 import (
 	"android/soong/android"
+	"github.com/google/blueprint"
 )
 
 // Contains support for processing hiddenAPI in a modular fashion.
 
+type hiddenAPIStubsDependencyTag struct {
+	blueprint.BaseDependencyTag
+	sdkKind android.SdkKind
+}
+
+func (b hiddenAPIStubsDependencyTag) ExcludeFromApexContents() {
+}
+
+func (b hiddenAPIStubsDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
+// Avoid having to make stubs content explicitly visible to dependent modules.
+//
+// This is a temporary workaround to make it easier to migrate to bootclasspath_fragment modules
+// with proper dependencies.
+// TODO(b/177892522): Remove this and add needed visibility.
+func (b hiddenAPIStubsDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = hiddenAPIStubsDependencyTag{}
+var _ android.ReplaceSourceWithPrebuilt = hiddenAPIStubsDependencyTag{}
+var _ android.ExcludeFromApexContentsTag = hiddenAPIStubsDependencyTag{}
+
+// hiddenAPIRelevantSdkKinds lists all the android.SdkKind instances that are needed by the hidden
+// API processing.
+var hiddenAPIRelevantSdkKinds = []android.SdkKind{
+	android.SdkPublic,
+	android.SdkSystem,
+	android.SdkTest,
+	android.SdkCorePlatform,
+}
+
+// hiddenAPIComputeMonolithicStubLibModules computes the set of module names that provide stubs
+// needed to produce the hidden API monolithic stub flags file.
+func hiddenAPIComputeMonolithicStubLibModules(config android.Config) map[android.SdkKind][]string {
+	var publicStubModules []string
+	var systemStubModules []string
+	var testStubModules []string
+	var corePlatformStubModules []string
+
+	if config.AlwaysUsePrebuiltSdks() {
+		// Build configuration mandates using prebuilt stub modules
+		publicStubModules = append(publicStubModules, "sdk_public_current_android")
+		systemStubModules = append(systemStubModules, "sdk_system_current_android")
+		testStubModules = append(testStubModules, "sdk_test_current_android")
+	} else {
+		// Use stub modules built from source
+		publicStubModules = append(publicStubModules, "android_stubs_current")
+		systemStubModules = append(systemStubModules, "android_system_stubs_current")
+		testStubModules = append(testStubModules, "android_test_stubs_current")
+	}
+	// We do not have prebuilts of the core platform api yet
+	corePlatformStubModules = append(corePlatformStubModules, "legacy.core.platform.api.stubs")
+
+	// Allow products to define their own stubs for custom product jars that apps can use.
+	publicStubModules = append(publicStubModules, config.ProductHiddenAPIStubs()...)
+	systemStubModules = append(systemStubModules, config.ProductHiddenAPIStubsSystem()...)
+	testStubModules = append(testStubModules, config.ProductHiddenAPIStubsTest()...)
+	if config.IsEnvTrue("EMMA_INSTRUMENT") {
+		publicStubModules = append(publicStubModules, "jacoco-stubs")
+	}
+
+	m := map[android.SdkKind][]string{}
+	m[android.SdkPublic] = publicStubModules
+	m[android.SdkSystem] = systemStubModules
+	m[android.SdkTest] = testStubModules
+	m[android.SdkCorePlatform] = corePlatformStubModules
+	return m
+}
+
+// hiddenAPIAddStubLibDependencies adds dependencies onto the modules specified in
+// sdkKindToStubLibModules. It adds them in a well known order and uses an SdkKind specific tag to
+// identify the source of the dependency.
+func hiddenAPIAddStubLibDependencies(ctx android.BottomUpMutatorContext, sdkKindToStubLibModules map[android.SdkKind][]string) {
+	module := ctx.Module()
+	for _, sdkKind := range hiddenAPIRelevantSdkKinds {
+		modules := sdkKindToStubLibModules[sdkKind]
+		ctx.AddDependency(module, hiddenAPIStubsDependencyTag{sdkKind: sdkKind}, modules...)
+	}
+}
+
+// hiddenAPIGatherStubLibDexJarPaths gathers the paths to the dex jars from the dependencies added
+// in hiddenAPIAddStubLibDependencies.
+func hiddenAPIGatherStubLibDexJarPaths(ctx android.ModuleContext) map[android.SdkKind]android.Paths {
+	m := map[android.SdkKind]android.Paths{}
+	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok {
+			kind := hiddenAPIStubsTag.sdkKind
+			dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module)
+			if dexJar != nil {
+				m[kind] = append(m[kind], dexJar)
+			}
+		}
+	})
+	return m
+}
+
+// hiddenAPIRetrieveDexJarBuildPath retrieves the DexJarBuildPath from the specified module, if
+// available, or reports an error.
+func hiddenAPIRetrieveDexJarBuildPath(ctx android.ModuleContext, module android.Module) android.Path {
+	if j, ok := module.(UsesLibraryDependency); ok {
+		dexJar := j.DexJarBuildPath()
+		if dexJar != nil {
+			return dexJar
+		}
+		ctx.ModuleErrorf("dependency %s does not provide a dex jar, consider setting compile_dex: true", module)
+	} else {
+		ctx.ModuleErrorf("dependency %s of module type %s does not support providing a dex jar", module, ctx.OtherModuleType(module))
+	}
+	return nil
+}
+
+var sdkKindToHiddenapiListOption = map[android.SdkKind]string{
+	android.SdkPublic:       "public-stub-classpath",
+	android.SdkSystem:       "system-stub-classpath",
+	android.SdkTest:         "test-stub-classpath",
+	android.SdkCorePlatform: "core-platform-stub-classpath",
+}
+
+// ruleToGenerateHiddenAPIStubFlagsFile creates a rule to create a hidden API stub flags file.
+//
+// The rule is initialized but not built so that the caller can modify it and select an appropriate
+// name.
+func ruleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, outputPath android.OutputPath, bootDexJars android.Paths, sdkKindToPathList map[android.SdkKind]android.Paths) *android.RuleBuilder {
+	// Singleton rule which applies hiddenapi on all boot class path dex files.
+	rule := android.NewRuleBuilder(pctx, ctx)
+
+	tempPath := tempPathForRestat(ctx, outputPath)
+
+	command := rule.Command().
+		Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")).
+		Text("list").
+		FlagForEachInput("--boot-dex=", bootDexJars)
+
+	// Iterate over the sdk kinds in a fixed order.
+	for _, sdkKind := range hiddenAPIRelevantSdkKinds {
+		paths := sdkKindToPathList[sdkKind]
+		if len(paths) > 0 {
+			option := sdkKindToHiddenapiListOption[sdkKind]
+			command.FlagWithInputList("--"+option+"=", paths, ":")
+		}
+	}
+
+	// Add the output path.
+	command.FlagWithOutput("--out-api-flags=", tempPath)
+
+	commitChangeForRestat(rule, tempPath, outputPath)
+	return rule
+}
+
 // HiddenAPIFlagFileProperties contains paths to the flag files that can be used to augment the
 // information obtained from annotations within the source code in order to create the complete set
 // of flags that should be applied to the dex implementation jars on the bootclasspath.
@@ -63,7 +216,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
@@ -73,9 +226,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
@@ -86,7 +239,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) {
@@ -96,7 +249,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) {
@@ -106,7 +259,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) {
@@ -116,7 +269,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) {
@@ -126,7 +279,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) {
@@ -136,7 +289,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) {
@@ -146,7 +299,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) {
@@ -156,7 +309,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) {
@@ -172,6 +325,14 @@
 	categoryToPaths map[*hiddenAPIFlagFileCategory]android.Paths
 }
 
+func (i *hiddenAPIFlagFileInfo) append(other hiddenAPIFlagFileInfo) {
+	for _, category := range hiddenAPIFlagFileCategories {
+		i.categoryToPaths[category] = append(i.categoryToPaths[category], other.categoryToPaths[category]...)
+	}
+}
+
+var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{})
+
 // ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
 // flags from all the modules, the stub flags, augmented with some additional configuration files.
 //
@@ -197,7 +358,7 @@
 // augmentationInfo is a struct containing paths to files that augment the information provided by
 // the moduleSpecificFlagsPaths.
 func ruleToGenerateHiddenApiFlags(ctx android.BuilderContext, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, augmentationInfo hiddenAPIFlagFileInfo) {
-	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
+	tempPath := tempPathForRestat(ctx, outputPath)
 	rule := android.NewRuleBuilder(pctx, ctx)
 	command := rule.Command().
 		BuiltTool("generate_hiddenapi_lists").
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index ed0b722..3cc88e6 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"strings"
+
 	"android/soong/android"
 )
 
@@ -125,8 +127,6 @@
 		return
 	}
 
-	stubFlagsRule(ctx)
-
 	// If there is a prebuilt hiddenapi dir, generate rules to use the
 	// files within. Generally, we build the hiddenapi files from source
 	// during the build, ensuring consistency. It's possible, in a split
@@ -158,109 +158,6 @@
 	ctx.Strict("INTERNAL_PLATFORM_HIDDENAPI_FLAGS", h.flags.String())
 }
 
-// stubFlagsRule creates the rule to build hiddenapi-stub-flags.txt out of dex jars from stub modules and boot image
-// modules.
-func stubFlagsRule(ctx android.SingletonContext) {
-	var publicStubModules []string
-	var systemStubModules []string
-	var testStubModules []string
-	var corePlatformStubModules []string
-
-	if ctx.Config().AlwaysUsePrebuiltSdks() {
-		// Build configuration mandates using prebuilt stub modules
-		publicStubModules = append(publicStubModules, "sdk_public_current_android")
-		systemStubModules = append(systemStubModules, "sdk_system_current_android")
-		testStubModules = append(testStubModules, "sdk_test_current_android")
-	} else {
-		// Use stub modules built from source
-		publicStubModules = append(publicStubModules, "android_stubs_current")
-		systemStubModules = append(systemStubModules, "android_system_stubs_current")
-		testStubModules = append(testStubModules, "android_test_stubs_current")
-	}
-	// We do not have prebuilts of the core platform api yet
-	corePlatformStubModules = append(corePlatformStubModules, "legacy.core.platform.api.stubs")
-
-	// Allow products to define their own stubs for custom product jars that apps can use.
-	publicStubModules = append(publicStubModules, ctx.Config().ProductHiddenAPIStubs()...)
-	systemStubModules = append(systemStubModules, ctx.Config().ProductHiddenAPIStubsSystem()...)
-	testStubModules = append(testStubModules, ctx.Config().ProductHiddenAPIStubsTest()...)
-	if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") {
-		publicStubModules = append(publicStubModules, "jacoco-stubs")
-	}
-
-	publicStubPaths := make(android.Paths, len(publicStubModules))
-	systemStubPaths := make(android.Paths, len(systemStubModules))
-	testStubPaths := make(android.Paths, len(testStubModules))
-	corePlatformStubPaths := make(android.Paths, len(corePlatformStubModules))
-
-	moduleListToPathList := map[*[]string]android.Paths{
-		&publicStubModules:       publicStubPaths,
-		&systemStubModules:       systemStubPaths,
-		&testStubModules:         testStubPaths,
-		&corePlatformStubModules: corePlatformStubPaths,
-	}
-
-	var bootDexJars android.Paths
-
-	ctx.VisitAllModules(func(module android.Module) {
-		// Collect dex jar paths for the modules listed above.
-		if j, ok := module.(UsesLibraryDependency); ok {
-			name := ctx.ModuleName(module)
-			for moduleList, pathList := range moduleListToPathList {
-				if i := android.IndexList(name, *moduleList); i != -1 {
-					pathList[i] = j.DexJarBuildPath()
-				}
-			}
-		}
-
-		// Collect dex jar paths for modules that had hiddenapi encode called on them.
-		if h, ok := module.(hiddenAPIIntf); ok {
-			if jar := h.bootDexJar(); jar != nil {
-				bootDexJars = append(bootDexJars, jar)
-			}
-		}
-	})
-
-	var missingDeps []string
-	// Ensure all modules were converted to paths
-	for moduleList, pathList := range moduleListToPathList {
-		for i := range pathList {
-			if pathList[i] == nil {
-				moduleName := (*moduleList)[i]
-				pathList[i] = android.PathForOutput(ctx, "missing/module", moduleName)
-				if ctx.Config().AllowMissingDependencies() {
-					missingDeps = append(missingDeps, moduleName)
-				} else {
-					ctx.Errorf("failed to find dex jar path for module %q",
-						moduleName)
-				}
-			}
-		}
-	}
-
-	// Singleton rule which applies hiddenapi on all boot class path dex files.
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	outputPath := hiddenAPISingletonPaths(ctx).stubFlags
-	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
-
-	rule.MissingDeps(missingDeps)
-
-	rule.Command().
-		Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")).
-		Text("list").
-		FlagForEachInput("--boot-dex=", bootDexJars).
-		FlagWithInputList("--public-stub-classpath=", publicStubPaths, ":").
-		FlagWithInputList("--system-stub-classpath=", systemStubPaths, ":").
-		FlagWithInputList("--test-stub-classpath=", testStubPaths, ":").
-		FlagWithInputList("--core-platform-stub-classpath=", corePlatformStubPaths, ":").
-		FlagWithOutput("--out-api-flags=", tempPath)
-
-	commitChangeForRestat(rule, tempPath, outputPath)
-
-	rule.Build("hiddenAPIStubFlagsFile", "hiddenapi stub flags")
-}
-
 // Checks to see whether the supplied module variant is in the list of boot jars.
 //
 // This is similar to logic in getBootImageJar() so any changes needed here are likely to be needed
@@ -348,6 +245,16 @@
 	return outputPath
 }
 
+// tempPathForRestat creates a path of the same type as the supplied type but with a name of
+// <path>.tmp.
+//
+// e.g. If path is an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv then this will return
+// an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv.tmp
+func tempPathForRestat(ctx android.PathContext, path android.WritablePath) android.WritablePath {
+	extWithoutLeadingDot := strings.TrimPrefix(path.Ext(), ".")
+	return path.ReplaceExtension(ctx, extWithoutLeadingDot+".tmp")
+}
+
 // commitChangeForRestat adds a command to a rule that updates outputPath from tempPath if they are different.  It
 // also marks the rule as restat and marks the tempPath as a temporary file that should not be considered an output of
 // the rule.
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index 5ea9a5b..3ab2277 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -23,12 +23,20 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// TODO(b/177892522): Move these tests into a more appropriate place.
+
 func fixtureSetPrebuiltHiddenApiDirProductVariable(prebuiltHiddenApiDir *string) android.FixturePreparer {
 	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 		variables.PrebuiltHiddenApiDir = prebuiltHiddenApiDir
 	})
 }
 
+var prepareForTestWithDefaultPlatformBootclasspath = android.FixtureAddTextFile("frameworks/base/boot/Android.bp", `
+	platform_bootclasspath {
+		name: "platform-bootclasspath",
+	}
+`)
+
 var hiddenApiFixtureFactory = android.GroupFixturePreparers(
 	prepareForJavaTest, PrepareForTestWithHiddenApiBuildComponents)
 
@@ -36,6 +44,7 @@
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
+		prepareForTestWithDefaultPlatformBootclasspath,
 	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -44,8 +53,8 @@
 		}
 	`)
 
-	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
@@ -59,6 +68,7 @@
 	android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
+		prepareForTestWithDefaultPlatformBootclasspath,
 	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorMessage)).
 		RunTestWithBp(t, `
 		java_library {
@@ -79,6 +89,7 @@
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
+		prepareForTestWithDefaultPlatformBootclasspath,
 	).RunTestWithBp(t, `
 		java_import {
 			name: "foo",
@@ -87,8 +98,8 @@
 	}
 	`)
 
-	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
@@ -97,6 +108,7 @@
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
+		prepareForTestWithDefaultPlatformBootclasspath,
 	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -112,8 +124,8 @@
 		}
 	`)
 
-	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 
@@ -125,6 +137,7 @@
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
+		prepareForTestWithDefaultPlatformBootclasspath,
 	).RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -140,8 +153,8 @@
 		}
 	`)
 
-	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 
@@ -184,13 +197,14 @@
 			result := android.GroupFixturePreparers(
 				hiddenApiFixtureFactory,
 				tc.preparer,
+				prepareForTestWithDefaultPlatformBootclasspath,
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(tc.unbundledBuild)
 				}),
 			).RunTest(t)
 
-			hiddenAPI := result.SingletonForTests("hiddenapi")
-			hiddenapiRule := hiddenAPI.Rule("hiddenapi")
+			hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+			hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
 			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)
 
diff --git a/java/java.go b/java/java.go
index ee4f2eb..adb0c56 100644
--- a/java/java.go
+++ b/java/java.go
@@ -27,6 +27,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/dexpreopt"
 	"android/soong/java/config"
 	"android/soong/tradefed"
@@ -708,6 +709,9 @@
 
 	// Test options.
 	Test_options TestOptions
+
+	// Names of modules containing JNI libraries that should be installed alongside the test.
+	Jni_libs []string
 }
 
 type hostTestProperties struct {
@@ -769,6 +773,13 @@
 		}
 	}
 
+	if len(j.testProperties.Jni_libs) > 0 {
+		for _, target := range ctx.MultiTargets() {
+			sharedLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+			ctx.AddFarVariationDependencies(sharedLibVariations, jniLibTag, j.testProperties.Jni_libs...)
+		}
+	}
+
 	j.deps(ctx)
 }
 
@@ -793,6 +804,29 @@
 		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
 	})
 
+	ctx.VisitDirectDepsWithTag(jniLibTag, func(dep android.Module) {
+		sharedLibInfo := ctx.OtherModuleProvider(dep, cc.SharedLibraryInfoProvider).(cc.SharedLibraryInfo)
+		if sharedLibInfo.SharedLibrary != nil {
+			// Copy to an intermediate output directory to append "lib[64]" to the path,
+			// so that it's compatible with the default rpath values.
+			var relPath string
+			if sharedLibInfo.Target.Arch.ArchType.Multilib == "lib64" {
+				relPath = filepath.Join("lib64", sharedLibInfo.SharedLibrary.Base())
+			} else {
+				relPath = filepath.Join("lib", sharedLibInfo.SharedLibrary.Base())
+			}
+			relocatedLib := android.PathForModuleOut(ctx, "relocated").Join(ctx, relPath)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  sharedLibInfo.SharedLibrary,
+				Output: relocatedLib,
+			})
+			j.data = append(j.data, relocatedLib)
+		} else {
+			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
+		}
+	})
+
 	j.Library.GenerateAndroidBuildActions(ctx)
 }
 
diff --git a/java/java_test.go b/java/java_test.go
index 0523458..1b8aec2 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -20,6 +20,7 @@
 	"path/filepath"
 	"reflect"
 	"regexp"
+	"runtime"
 	"strconv"
 	"strings"
 	"testing"
@@ -462,6 +463,38 @@
 	}
 }
 
+func TestTest(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_test_host {
+			name: "foo",
+			srcs: ["a.java"],
+			jni_libs: ["libjni"],
+		}
+
+		cc_library_shared {
+			name: "libjni",
+			host_supported: true,
+			device_supported: false,
+			stl: "none",
+		}
+	`)
+
+	buildOS := android.BuildOs.String()
+
+	foo := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
+
+	expected := "lib64/libjni.so"
+	if runtime.GOOS == "darwin" {
+		expected = "lib64/libjni.dylib"
+	}
+
+	fooTestData := foo.data
+	if len(fooTestData) != 1 || fooTestData[0].Rel() != expected {
+		t.Errorf(`expected foo test data relative path [%q], got %q`,
+			expected, fooTestData.Strings())
+	}
+}
+
 func TestHostBinaryNoJavaDebugInfoOverride(t *testing.T) {
 	bp := `
 		java_library {
@@ -1170,107 +1203,6 @@
 	}
 }
 
-func TestJavaLint(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
-		java_library {
-			name: "foo",
-			srcs: [
-				"a.java",
-				"b.java",
-				"c.java",
-			],
-			min_sdk_version: "29",
-			sdk_version: "system_current",
-		}
-       `, map[string][]byte{
-		"lint-baseline.xml": nil,
-	})
-
-	foo := ctx.ModuleForTests("foo", "android_common")
-
-	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
-	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml") {
-		t.Error("did not pass --baseline flag")
-	}
-}
-
-func TestJavaLintWithoutBaseline(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
-		java_library {
-			name: "foo",
-			srcs: [
-				"a.java",
-				"b.java",
-				"c.java",
-			],
-			min_sdk_version: "29",
-			sdk_version: "system_current",
-		}
-       `, map[string][]byte{})
-
-	foo := ctx.ModuleForTests("foo", "android_common")
-
-	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
-	if strings.Contains(*sboxProto.Commands[0].Command, "--baseline") {
-		t.Error("passed --baseline flag for non existent file")
-	}
-}
-
-func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
-	android.GroupFixturePreparers(
-		PrepareForTestWithJavaDefaultModules,
-		android.PrepareForTestDisallowNonExistentPaths,
-	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{`source path "mybaseline.xml" does not exist`})).
-		RunTestWithBp(t, `
-			java_library {
-				name: "foo",
-				srcs: [
-				],
-				min_sdk_version: "29",
-				sdk_version: "system_current",
-				lint: {
-					baseline_filename: "mybaseline.xml",
-				},
-			}
-	 `)
-}
-
-func TestJavaLintUsesCorrectBpConfig(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
-		java_library {
-			name: "foo",
-			srcs: [
-				"a.java",
-				"b.java",
-				"c.java",
-			],
-			min_sdk_version: "29",
-			sdk_version: "system_current",
-			lint: {
-				error_checks: ["SomeCheck"],
-				baseline_filename: "mybaseline.xml",
-			},
-		}
-       `, map[string][]byte{
-		"mybaseline.xml": nil,
-	})
-
-	foo := ctx.ModuleForTests("foo", "android_common")
-
-	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
-	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline mybaseline.xml") {
-		t.Error("did not use the correct file for baseline")
-	}
-
-	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check NewApi") {
-		t.Error("should check NewApi errors")
-	}
-
-	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check SomeCheck") {
-		t.Error("should combine NewApi errors with SomeCheck errors")
-	}
-}
-
 func TestGeneratedSources(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
@@ -1614,31 +1546,51 @@
 		java_sdk_library {
 			name: "sdklib",
 			srcs: ["a.java"],
-			impl_only_libs: ["foo"],
-			stub_only_libs: ["bar"],
+			libs: ["lib"],
+			static_libs: ["static-lib"],
+			impl_only_libs: ["impl-only-lib"],
+			stub_only_libs: ["stub-only-lib"],
+			stub_only_static_libs: ["stub-only-static-lib"],
 		}
-		java_library {
-			name: "foo",
+		java_defaults {
+			name: "defaults",
 			srcs: ["a.java"],
 			sdk_version: "current",
 		}
-		java_library {
-			name: "bar",
-			srcs: ["a.java"],
-			sdk_version: "current",
-		}
+		java_library { name: "lib", defaults: ["defaults"] }
+		java_library { name: "static-lib", defaults: ["defaults"] }
+		java_library { name: "impl-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
 		`)
-
-	for _, implName := range []string{"sdklib", "sdklib.impl"} {
-		implJavacCp := result.ModuleForTests(implName, "android_common").Rule("javac").Args["classpath"]
-		if !strings.Contains(implJavacCp, "/foo.jar") || strings.Contains(implJavacCp, "/bar.jar") {
-			t.Errorf("%v javac classpath %v does not contain foo and not bar", implName, implJavacCp)
-		}
+	var expectations = []struct {
+		lib               string
+		on_impl_classpath bool
+		on_stub_classpath bool
+		in_impl_combined  bool
+		in_stub_combined  bool
+	}{
+		{lib: "lib", on_impl_classpath: true},
+		{lib: "static-lib", in_impl_combined: true},
+		{lib: "impl-only-lib", on_impl_classpath: true},
+		{lib: "stub-only-lib", on_stub_classpath: true},
+		{lib: "stub-only-static-lib", in_stub_combined: true},
 	}
-	stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
-	stubsJavacCp := result.ModuleForTests(stubName, "android_common").Rule("javac").Args["classpath"]
-	if strings.Contains(stubsJavacCp, "/foo.jar") || !strings.Contains(stubsJavacCp, "/bar.jar") {
-		t.Errorf("stubs javac classpath %v does not contain bar and not foo", stubsJavacCp)
+	verify := func(sdklib, dep string, cp, combined bool) {
+		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
+		expected := cp || combined // Every combined jar is also on the classpath.
+		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
+
+		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
+		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
+		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
+	}
+	for _, expectation := range expectations {
+		verify("sdklib", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+
+		stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
+		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
 	}
 }
 
diff --git a/java/lint.go b/java/lint.go
index aa308e6..862c9b4 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -26,6 +26,10 @@
 	"android/soong/remoteexec"
 )
 
+// lint checks automatically enforced for modules that have different min_sdk_version than
+// sdk_version
+var updatabilityChecks = []string{"NewApi"}
+
 type LintProperties struct {
 	// Controls for running Android Lint on the module.
 	Lint struct {
@@ -53,6 +57,9 @@
 
 		// Name of the file that lint uses as the baseline. Defaults to "lint-baseline.xml".
 		Baseline_filename *string
+
+		// If true, baselining updatability lint checks (e.g. NewApi) is prohibited. Defaults to false.
+		Strict_updatability_linting *bool
 	}
 }
 
@@ -200,7 +207,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())
@@ -253,6 +260,13 @@
 	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
 	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
 
+	if BoolDefault(l.properties.Lint.Strict_updatability_linting, false) {
+		if baselinePath := l.getBaselineFilepath(ctx); baselinePath.Valid() {
+			cmd.FlagWithInput("--baseline ", baselinePath.Path())
+			cmd.FlagForEachArg("--disallowed_issues ", updatabilityChecks)
+		}
+	}
+
 	return lintPaths{
 		projectXML: projectXMLPath,
 		configXML:  configXMLPath,
@@ -279,13 +293,36 @@
 	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
 	}
 
 	if l.minSdkVersion != l.compileSdkVersion {
-		l.extraMainlineLintErrors = append(l.extraMainlineLintErrors, "NewApi")
+		l.extraMainlineLintErrors = append(l.extraMainlineLintErrors, updatabilityChecks...)
+		_, filtered := android.FilterList(l.properties.Lint.Warning_checks, updatabilityChecks)
+		if len(filtered) != 0 {
+			ctx.PropertyErrorf("lint.warning_checks",
+				"Can't treat %v checks as warnings if min_sdk_version is different from sdk_version.", filtered)
+		}
+		_, filtered = android.FilterList(l.properties.Lint.Disabled_checks, updatabilityChecks)
+		if len(filtered) != 0 {
+			ctx.PropertyErrorf("lint.disabled_checks",
+				"Can't disable %v checks if min_sdk_version is different from sdk_version.", filtered)
+		}
 	}
 
 	extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
@@ -381,17 +418,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/java/lint_test.go b/java/lint_test.go
new file mode 100644
index 0000000..a253df9
--- /dev/null
+++ b/java/lint_test.go
@@ -0,0 +1,204 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestJavaLint(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		java_library {
+			name: "foo",
+			srcs: [
+				"a.java",
+				"b.java",
+				"c.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "system_current",
+		}
+       `, map[string][]byte{
+		"lint-baseline.xml": nil,
+	})
+
+	foo := ctx.ModuleForTests("foo", "android_common")
+
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml") {
+		t.Error("did not pass --baseline flag")
+	}
+}
+
+func TestJavaLintWithoutBaseline(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		java_library {
+			name: "foo",
+			srcs: [
+				"a.java",
+				"b.java",
+				"c.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "system_current",
+		}
+       `, map[string][]byte{})
+
+	foo := ctx.ModuleForTests("foo", "android_common")
+
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
+	if strings.Contains(*sboxProto.Commands[0].Command, "--baseline") {
+		t.Error("passed --baseline flag for non existent file")
+	}
+}
+
+func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
+	android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestDisallowNonExistentPaths,
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{`source path "mybaseline.xml" does not exist`})).
+		RunTestWithBp(t, `
+			java_library {
+				name: "foo",
+				srcs: [
+				],
+				min_sdk_version: "29",
+				sdk_version: "system_current",
+				lint: {
+					baseline_filename: "mybaseline.xml",
+				},
+			}
+	 `)
+}
+
+func TestJavaLintUsesCorrectBpConfig(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		java_library {
+			name: "foo",
+			srcs: [
+				"a.java",
+				"b.java",
+				"c.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "system_current",
+			lint: {
+				error_checks: ["SomeCheck"],
+				baseline_filename: "mybaseline.xml",
+			},
+		}
+       `, map[string][]byte{
+		"mybaseline.xml": nil,
+	})
+
+	foo := ctx.ModuleForTests("foo", "android_common")
+
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline mybaseline.xml") {
+		t.Error("did not use the correct file for baseline")
+	}
+
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check NewApi") {
+		t.Error("should check NewApi errors")
+	}
+
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check SomeCheck") {
+		t.Error("should combine NewApi errors with SomeCheck errors")
+	}
+}
+
+func TestJavaLintBypassUpdatableChecks(t *testing.T) {
+	testCases := []struct {
+		name  string
+		bp    string
+		error string
+	}{
+		{
+			name: "warning_checks",
+			bp: `
+				java_library {
+					name: "foo",
+					srcs: [
+						"a.java",
+					],
+					min_sdk_version: "29",
+					sdk_version: "current",
+					lint: {
+						warning_checks: ["NewApi"],
+					},
+				}
+			`,
+			error: "lint.warning_checks: Can't treat \\[NewApi\\] checks as warnings if min_sdk_version is different from sdk_version.",
+		},
+		{
+			name: "disable_checks",
+			bp: `
+				java_library {
+					name: "foo",
+					srcs: [
+						"a.java",
+					],
+					min_sdk_version: "29",
+					sdk_version: "current",
+					lint: {
+						disabled_checks: ["NewApi"],
+					},
+				}
+			`,
+			error: "lint.disabled_checks: Can't disable \\[NewApi\\] checks if min_sdk_version is different from sdk_version.",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			errorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.error)
+			android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, testCase.bp)
+		})
+	}
+}
+
+func TestJavaLintStrictUpdatabilityLinting(t *testing.T) {
+	bp := `
+		java_library {
+			name: "foo",
+			srcs: [
+				"a.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "current",
+			lint: {
+				strict_updatability_linting: true,
+			},
+		}
+	`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules, fs.AddToFixture()).
+		RunTestWithBp(t, bp)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command,
+		"--baseline lint-baseline.xml --disallowed_issues NewApi") {
+		t.Error("did not restrict baselining NewApi")
+	}
+}
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index cb8ad68..568f5e4 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -140,6 +140,8 @@
 }
 
 func (b *platformBootclasspathModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	b.hiddenAPIDepsMutator(ctx)
+
 	if SkipDexpreoptBootJars(ctx) {
 		return
 	}
@@ -149,6 +151,16 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
+func (b *platformBootclasspathModule) hiddenAPIDepsMutator(ctx android.BottomUpMutatorContext) {
+	if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
+		return
+	}
+
+	// Add dependencies onto the stub lib modules.
+	sdkKindToStubLibModules := hiddenAPIComputeMonolithicStubLibModules(ctx.Config())
+	hiddenAPIAddStubLibDependencies(ctx, sdkKindToStubLibModules)
+}
+
 func platformBootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
 	m := ctx.Module()
 	if p, ok := m.(*platformBootclasspathModule); ok {
@@ -258,7 +270,7 @@
 		}
 	})
 
-	b.generateHiddenAPIBuildActions(ctx, b.configuredModules)
+	b.generateHiddenAPIBuildActions(ctx, b.configuredModules, b.fragments)
 
 	// Nothing to do if skipping the dexpreopt of boot image jars.
 	if SkipDexpreoptBootJars(ctx) {
@@ -286,7 +298,7 @@
 }
 
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
-func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module) {
+func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module, fragments []android.Module) {
 
 	// Save the paths to the monolithic files for retrieval via OutputFiles().
 	b.hiddenAPIFlagsCSV = hiddenAPISingletonPaths(ctx).flags
@@ -338,16 +350,39 @@
 		moduleSpecificFlagsPaths = append(moduleSpecificFlagsPaths, module.flagsCSV())
 	}
 
-	augmentationInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
+	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
+	for _, fragment := range fragments {
+		if ctx.OtherModuleHasProvider(fragment, hiddenAPIFlagFileInfoProvider) {
+			info := ctx.OtherModuleProvider(fragment, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
+			flagFileInfo.append(info)
+		}
+	}
+
+	// Store the information for testing.
+	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
 
 	outputPath := hiddenAPISingletonPaths(ctx).flags
 	baseFlagsPath := hiddenAPISingletonPaths(ctx).stubFlags
-	ruleToGenerateHiddenApiFlags(ctx, outputPath, baseFlagsPath, moduleSpecificFlagsPaths, augmentationInfo)
+	ruleToGenerateHiddenApiFlags(ctx, outputPath, baseFlagsPath, moduleSpecificFlagsPaths, flagFileInfo)
 
+	b.generateHiddenAPIStubFlagsRules(ctx, hiddenAPISupportingModules)
 	b.generateHiddenAPIIndexRules(ctx, hiddenAPISupportingModules)
 	b.generatedHiddenAPIMetadataRules(ctx, hiddenAPISupportingModules)
 }
 
+func (b *platformBootclasspathModule) generateHiddenAPIStubFlagsRules(ctx android.ModuleContext, modules []hiddenAPISupportingModule) {
+	bootDexJars := android.Paths{}
+	for _, module := range modules {
+		bootDexJars = append(bootDexJars, module.bootDexJar())
+	}
+
+	sdkKindToStubPaths := hiddenAPIGatherStubLibDexJarPaths(ctx)
+
+	outputPath := hiddenAPISingletonPaths(ctx).stubFlags
+	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, outputPath, bootDexJars, sdkKindToStubPaths)
+	rule.Build("platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags")
+}
+
 func (b *platformBootclasspathModule) generateHiddenAPIIndexRules(ctx android.ModuleContext, modules []hiddenAPISupportingModule) {
 	indexes := android.Paths{}
 	for _, module := range modules {
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index e51b049..955e387 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -132,6 +134,96 @@
 	})
 }
 
+func TestPlatformBootclasspath_Fragments(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{module:"bar-fragment"},
+				],
+				hidden_api: {
+					unsupported: [
+							"unsupported.txt",
+					],
+					removed: [
+							"removed.txt",
+					],
+					max_target_r_low_priority: [
+							"max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"max-target-q.txt",
+					],
+					max_target_p: [
+							"max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"blocked.txt",
+					],
+					unsupported_packages: [
+							"unsupported-packages.txt",
+					],
+				},
+			}
+
+			bootclasspath_fragment {
+				name: "bar-fragment",
+				contents: ["bar"],
+				hidden_api: {
+					unsupported: [
+							"bar-unsupported.txt",
+					],
+					removed: [
+							"bar-removed.txt",
+					],
+					max_target_r_low_priority: [
+							"bar-max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"bar-max-target-q.txt",
+					],
+					max_target_p: [
+							"bar-max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"bar-max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"bar-blocked.txt",
+					],
+					unsupported_packages: [
+							"bar-unsupported-packages.txt",
+					],
+				},
+			}
+
+			java_library {
+				name: "bar",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	pbcp := result.Module("platform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
+
+	for _, category := range hiddenAPIFlagFileCategories {
+		name := category.propertyName
+		message := fmt.Sprintf("category %s", name)
+		filename := strings.ReplaceAll(name, "_", "-")
+		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
+		android.AssertPathsRelativeToTopEquals(t, message, expected, info.categoryToPaths[category])
+	}
+}
+
 func TestPlatformBootclasspathVariant(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
diff --git a/java/sdk.go b/java/sdk.go
index d6e20a7..1c097d5 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -247,7 +247,7 @@
 	}
 
 	combinedAidl := sdkFrameworkAidlPath(ctx)
-	tempPath := combinedAidl.ReplaceExtension(ctx, "aidl.tmp")
+	tempPath := tempPathForRestat(ctx, combinedAidl)
 
 	rule := createFrameworkAidl(stubsModules, tempPath, ctx)
 
@@ -261,7 +261,7 @@
 	stubsModules := []string{"android_module_lib_stubs_current"}
 
 	combinedAidl := nonUpdatableFrameworkAidlPath(ctx)
-	tempPath := combinedAidl.ReplaceExtension(ctx, "aidl.tmp")
+	tempPath := tempPathForRestat(ctx, combinedAidl)
 
 	rule := createFrameworkAidl(stubsModules, tempPath, ctx)
 
@@ -270,7 +270,7 @@
 	rule.Build("framework_non_updatable_aidl", "generate framework_non_updatable.aidl")
 }
 
-func createFrameworkAidl(stubsModules []string, path android.OutputPath, ctx android.SingletonContext) *android.RuleBuilder {
+func createFrameworkAidl(stubsModules []string, path android.WritablePath, ctx android.SingletonContext) *android.RuleBuilder {
 	stubsJars := make([]android.Paths, len(stubsModules))
 
 	ctx.VisitAllModules(func(module android.Module) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e5ee397..05ce97a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -399,6 +399,9 @@
 	// List of Java libraries that will be in the classpath when building stubs
 	Stub_only_libs []string `android:"arch_variant"`
 
+	// List of Java libraries that will included in stub libraries
+	Stub_only_static_libs []string `android:"arch_variant"`
+
 	// list of package names that will be documented and publicized as API.
 	// This allows the API to be restricted to a subset of the source files provided.
 	// If this is unspecified then all the source files will be treated as being part
@@ -534,6 +537,11 @@
 	// This is not the implementation jar, it still only contains stubs.
 	stubsImplPath android.Paths
 
+	// The dex jar for the stubs.
+	//
+	// This is not the implementation jar, it still only contains stubs.
+	stubsDexJarPath android.Path
+
 	// The API specification file, e.g. system_current.txt.
 	currentApiFilePath android.OptionalPath
 
@@ -549,6 +557,9 @@
 		lib := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
 		paths.stubsHeaderPath = lib.HeaderJars
 		paths.stubsImplPath = lib.ImplementationJars
+
+		libDep := dep.(UsesLibraryDependency)
+		paths.stubsDexJarPath = libDep.DexJarBuildPath()
 		return nil
 	} else {
 		return fmt.Errorf("expected module that has JavaInfoProvider, e.g. java_library")
@@ -825,8 +836,22 @@
 		return PrebuiltJars(ctx, c.moduleBase.BaseModuleName(), sdkVersion)
 	}
 
+	paths := c.selectScopePaths(ctx, sdkVersion.Kind)
+	if paths == nil {
+		return nil
+	}
+
+	return paths.stubsHeaderPath
+}
+
+// selectScopePaths returns the *scopePaths appropriate for the specific kind.
+//
+// If the module does not support the specific kind then it will return the *scopePaths for the
+// closest kind which is a subset of the requested kind. e.g. if requesting android.SdkModule then
+// it will return *scopePaths for android.SdkSystem if available or android.SdkPublic of not.
+func (c *commonToSdkLibraryAndImport) selectScopePaths(ctx android.BaseModuleContext, kind android.SdkKind) *scopePaths {
 	var apiScope *apiScope
-	switch sdkVersion.Kind {
+	switch kind {
 	case android.SdkSystem:
 		apiScope = apiScopeSystem
 	case android.SdkModule:
@@ -851,7 +876,17 @@
 		return nil
 	}
 
-	return paths.stubsHeaderPath
+	return paths
+}
+
+// to satisfy SdkLibraryDependency interface
+func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path {
+	paths := c.selectScopePaths(ctx, kind)
+	if paths == nil {
+		return nil
+	}
+
+	return paths.stubsDexJarPath
 }
 
 func (c *commonToSdkLibraryAndImport) sdkComponentPropertiesForChildLibrary() interface{} {
@@ -944,6 +979,10 @@
 	// jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise
 	// they are identical to the corresponding header jars.
 	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths
+
+	// SdkApiStubDexJar returns the dex jar for the stubs. It is needed by the hiddenapi processing
+	// tool which processes dex files.
+	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path
 }
 
 type SdkLibrary struct {
@@ -1239,6 +1278,7 @@
 		System_modules *string
 		Patch_module   *string
 		Libs           []string
+		Static_libs    []string
 		Compile_dex    *bool
 		Java_version   *string
 		Openjdk9       struct {
@@ -1263,6 +1303,7 @@
 	props.Patch_module = module.properties.Patch_module
 	props.Installable = proptools.BoolPtr(false)
 	props.Libs = module.sdkLibraryProperties.Stub_only_libs
+	props.Static_libs = module.sdkLibraryProperties.Stub_only_static_libs
 	// The stub-annotations library contains special versions of the annotations
 	// with CLASS retention policy, so that they're kept.
 	if proptools.Bool(module.sdkLibraryProperties.Annotations_enabled) {
@@ -1781,6 +1822,9 @@
 	// List of shared java libs, common to all scopes, that this module has
 	// dependencies to
 	Libs []string
+
+	// If set to true, compile dex files for the stubs. Defaults to false.
+	Compile_dex *bool
 }
 
 type SdkLibraryImport struct {
@@ -1916,6 +1960,7 @@
 		Libs        []string
 		Jars        []string
 		Prefer      *bool
+		Compile_dex *bool
 	}{}
 	props.Name = proptools.StringPtr(module.stubsLibraryModuleName(apiScope))
 	props.Sdk_version = scopeProperties.Sdk_version
@@ -1927,6 +1972,9 @@
 	// The imports are preferred if the java_sdk_library_import is preferred.
 	props.Prefer = proptools.BoolPtr(module.prebuilt.Prefer())
 
+	// The imports need to be compiled to dex if the java_sdk_library_import requests it.
+	props.Compile_dex = module.properties.Compile_dex
+
 	mctx.CreateModule(ImportFactory, &props, module.sdkComponentPropertiesForChildLibrary())
 }
 
@@ -2348,6 +2396,9 @@
 	// otherwise.
 	Shared_library *bool
 
+	// True if the stub imports should produce dex jars.
+	Compile_dex *bool
+
 	// The paths to the doctag files to add to the prebuilt.
 	Doctag_paths android.Paths
 }
@@ -2389,6 +2440,7 @@
 	s.Libs = sdk.properties.Libs
 	s.Naming_scheme = sdk.commonSdkLibraryProperties.Naming_scheme
 	s.Shared_library = proptools.BoolPtr(sdk.sharedLibrary())
+	s.Compile_dex = sdk.dexProperties.Compile_dex
 	s.Doctag_paths = sdk.doctagPaths
 }
 
@@ -2399,6 +2451,9 @@
 	if s.Shared_library != nil {
 		propertySet.AddProperty("shared_library", *s.Shared_library)
 	}
+	if s.Compile_dex != nil {
+		propertySet.AddProperty("compile_dex", *s.Compile_dex)
+	}
 
 	for _, apiScope := range allApiScopes {
 		if properties, ok := s.Scopes[apiScope]; ok {
diff --git a/java/testing.go b/java/testing.go
index aee0710..08a71b8 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -200,6 +200,9 @@
 		}),
 		dexpreopt.FixtureSetBootJars(bootJars...),
 		dexpreopt.FixtureSetArtBootJars(artBootJars...),
+
+		// Add a fake dex2oatd module.
+		dexpreopt.PrepareForTestWithFakeDex2oatd,
 	)
 }
 
@@ -212,6 +215,9 @@
 			variables.UpdatableBootJars = android.CreateTestConfiguredJarList(bootJars)
 		}),
 		dexpreopt.FixtureSetUpdatableBootJars(bootJars...),
+
+		// Add a fake dex2oatd module.
+		dexpreopt.PrepareForTestWithFakeDex2oatd,
 	)
 }
 
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/builder.go b/python/builder.go
index dc2d1f1..7d7239c 100644
--- a/python/builder.go
+++ b/python/builder.go
@@ -45,7 +45,7 @@
 	hostPar = pctx.AndroidStaticRule("hostPar",
 		blueprint.RuleParams{
 			Command: `sed -e 's/%interpreter%/$interp/g' -e 's/%main%/$main/g' $template > $stub && ` +
-				`echo "#!/usr/bin/env python" >${out}.prefix &&` +
+				`echo "#!/usr/bin/env $interp" >${out}.prefix &&` +
 				`$mergeParCmd -p --prefix ${out}.prefix -pm $stub $out $srcsZips && ` +
 				`chmod +x $out && (rm -f $stub; rm -f ${out}.prefix)`,
 			CommandDeps: []string{"$mergeParCmd"},
diff --git a/python/scripts/stub_template_host.txt b/python/scripts/stub_template_host.txt
index a48a86f..138404b 100644
--- a/python/scripts/stub_template_host.txt
+++ b/python/scripts/stub_template_host.txt
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env '%interpreter%'
 
 import os
 import re
@@ -82,7 +82,7 @@
 
     sys.stdout.flush()
     retCode = subprocess.call(args)
-    exit(retCode)
+    sys.exit(retCode)
   except:
     raise
   finally:
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/Android.bp b/rust/Android.bp
index a6c4e07..f45404f 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -21,6 +21,7 @@
         "clippy.go",
         "compiler.go",
         "coverage.go",
+        "doc.go",
         "fuzz.go",
         "image.go",
         "library.go",
diff --git a/rust/binary.go b/rust/binary.go
index dfe8744..ffc0413 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -122,7 +122,7 @@
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
 
-	TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+	TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile)
 
 	if binary.stripper.NeedsStrip(ctx) {
 		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
diff --git a/rust/bindgen.go b/rust/bindgen.go
index bcc26b8..ba0ab93 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -29,7 +29,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r412851"
+	bindgenClangVersion = "clang-r416183b"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -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/builder.go b/rust/builder.go
index 208b734..1fcce38 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -44,6 +44,16 @@
 		},
 		"rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars")
 
+	_       = pctx.SourcePathVariable("rustdocCmd", "${config.RustBin}/rustdoc")
+	rustdoc = pctx.AndroidStaticRule("rustdoc",
+		blueprint.RuleParams{
+			Command: "rm -rf $outDir && " +
+				"$envVars $rustdocCmd $rustdocFlags $in -o $outDir && " +
+				"touch $out",
+			CommandDeps: []string{"$rustdocCmd"},
+		},
+		"rustdocFlags", "outDir", "envVars")
+
 	_            = pctx.SourcePathVariable("clippyCmd", "${config.RustBin}/clippy-driver")
 	clippyDriver = pctx.AndroidStaticRule("clippy",
 		blueprint.RuleParams{
@@ -85,37 +95,37 @@
 }
 
 func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, linkDirs []string) buildOutput {
+	outputFile android.WritablePath) buildOutput {
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
 
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin", linkDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin")
 }
 
 func TransformSrctoRlib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, linkDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib", linkDirs)
+	outputFile android.WritablePath) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib")
 }
 
 func TransformSrctoDylib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, linkDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib", linkDirs)
+	outputFile android.WritablePath) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib")
 }
 
 func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, linkDirs []string) buildOutput {
+	outputFile android.WritablePath) buildOutput {
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib", linkDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib")
 }
 
 func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, linkDirs []string) buildOutput {
+	outputFile android.WritablePath) buildOutput {
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib", linkDirs)
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib")
 }
 
 func TransformSrctoProcMacro(ctx ModuleContext, mainSrc android.Path, deps PathDeps,
-	flags Flags, outputFile android.WritablePath, linkDirs []string) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro", linkDirs)
+	flags Flags, outputFile android.WritablePath) buildOutput {
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro")
 }
 
 func rustLibsToPaths(libs RustLibraries) android.Paths {
@@ -126,26 +136,69 @@
 	return paths
 }
 
+func makeLibFlags(deps PathDeps) []string {
+	var libFlags []string
+
+	// Collect library/crate flags
+	for _, lib := range deps.RLibs {
+		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
+	}
+	for _, lib := range deps.DyLibs {
+		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
+	}
+	for _, proc_macro := range deps.ProcMacros {
+		libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String())
+	}
+
+	for _, path := range deps.linkDirs {
+		libFlags = append(libFlags, "-L "+path)
+	}
+
+	return libFlags
+}
+
+func rustEnvVars(ctx ModuleContext, deps PathDeps) []string {
+	var envVars []string
+
+	// libstd requires a specific environment variable to be set. This is
+	// not officially documented and may be removed in the future. See
+	// https://github.com/rust-lang/rust/blob/master/library/std/src/env.rs#L866.
+	if ctx.RustModule().CrateName() == "std" {
+		envVars = append(envVars, "STD_ENV_ARCH="+config.StdEnvArch[ctx.RustModule().Arch().ArchType])
+	}
+
+	if len(deps.SrcDeps) > 0 {
+		moduleGenDir := ctx.RustModule().compiler.CargoOutDir()
+		// We must calculate an absolute path for OUT_DIR since Rust's include! macro (which normally consumes this)
+		// assumes that paths are relative to the source file.
+		var outDirPrefix string
+		if !filepath.IsAbs(moduleGenDir.String()) {
+			// If OUT_DIR is not absolute, we use $$PWD to generate an absolute path (os.Getwd() returns '/')
+			outDirPrefix = "$$PWD/"
+		} else {
+			// If OUT_DIR is absolute, then moduleGenDir will be an absolute path, so we don't need to set this to anything.
+			outDirPrefix = ""
+		}
+		envVars = append(envVars, "OUT_DIR="+filepath.Join(outDirPrefix, moduleGenDir.String()))
+	}
+
+	return envVars
+}
+
 func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, crate_type string, linkDirs []string) buildOutput {
+	outputFile android.WritablePath, crate_type string) buildOutput {
 
 	var inputs android.Paths
 	var implicits android.Paths
-	var envVars []string
 	var output buildOutput
-	var libFlags, rustcFlags, linkFlags []string
+	var rustcFlags, linkFlags []string
 	var implicitOutputs android.WritablePaths
 
 	output.outputFile = outputFile
 	crateName := ctx.RustModule().CrateName()
 	targetTriple := ctx.toolchain().RustTriple()
 
-	// libstd requires a specific environment variable to be set. This is
-	// not officially documented and may be removed in the future. See
-	// https://github.com/rust-lang/rust/blob/master/library/std/src/env.rs#L866.
-	if crateName == "std" {
-		envVars = append(envVars, "STD_ENV_ARCH="+config.StdEnvArch[ctx.RustModule().Arch().ArchType])
-	}
+	envVars := rustEnvVars(ctx, deps)
 
 	inputs = append(inputs, main)
 
@@ -168,20 +221,7 @@
 	linkFlags = append(linkFlags, flags.GlobalLinkFlags...)
 	linkFlags = append(linkFlags, flags.LinkFlags...)
 
-	// Collect library/crate flags
-	for _, lib := range deps.RLibs {
-		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
-	}
-	for _, lib := range deps.DyLibs {
-		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
-	}
-	for _, proc_macro := range deps.ProcMacros {
-		libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String())
-	}
-
-	for _, path := range linkDirs {
-		libFlags = append(libFlags, "-L "+path)
-	}
+	libFlags := makeLibFlags(deps)
 
 	// Collect dependencies
 	implicits = append(implicits, rustLibsToPaths(deps.RLibs)...)
@@ -217,18 +257,6 @@
 			},
 		})
 		implicits = append(implicits, outputs.Paths()...)
-
-		// We must calculate an absolute path for OUT_DIR since Rust's include! macro (which normally consumes this)
-		// assumes that paths are relative to the source file.
-		var outDirPrefix string
-		if !filepath.IsAbs(moduleGenDir.String()) {
-			// If OUT_DIR is not absolute, we use $$PWD to generate an absolute path (os.Getwd() returns '/')
-			outDirPrefix = "$$PWD/"
-		} else {
-			// If OUT_DIR is absolute, then moduleGenDir will be an absolute path, so we don't need to set this to anything.
-			outDirPrefix = ""
-		}
-		envVars = append(envVars, "OUT_DIR="+filepath.Join(outDirPrefix, moduleGenDir.String()))
 	}
 
 	envVars = append(envVars, "ANDROID_RUST_VERSION="+config.RustDefaultVersion)
@@ -272,3 +300,41 @@
 
 	return output
 }
+
+func Rustdoc(ctx ModuleContext, main android.Path, deps PathDeps,
+	flags Flags) android.ModuleOutPath {
+
+	rustdocFlags := append([]string{}, flags.RustdocFlags...)
+	rustdocFlags = append(rustdocFlags, "--sysroot=/dev/null")
+
+	targetTriple := ctx.toolchain().RustTriple()
+
+	// Collect rustc flags
+	if targetTriple != "" {
+		rustdocFlags = append(rustdocFlags, "--target="+targetTriple)
+	}
+
+	crateName := ctx.RustModule().CrateName()
+	if crateName != "" {
+		rustdocFlags = append(rustdocFlags, "--crate-name "+crateName)
+	}
+
+	rustdocFlags = append(rustdocFlags, makeLibFlags(deps)...)
+	docTimestampFile := android.PathForModuleOut(ctx, "rustdoc.timestamp")
+	docDir := android.PathForOutput(ctx, "rustdoc", ctx.ModuleName())
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        rustdoc,
+		Description: "rustdoc " + main.Rel(),
+		Output:      docTimestampFile,
+		Input:       main,
+		Implicit:    ctx.RustModule().unstrippedOutputFile.Path(),
+		Args: map[string]string{
+			"rustdocFlags": strings.Join(rustdocFlags, " "),
+			"outDir":       docDir.String(),
+			"envVars":      strings.Join(rustEnvVars(ctx, deps), " "),
+		},
+	})
+
+	return docTimestampFile
+}
diff --git a/rust/compiler.go b/rust/compiler.go
index bc034d7..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"`
@@ -239,7 +239,10 @@
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
 	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
 	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
 	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
+	flags.RustdocFlags = append(flags.RustdocFlags, "--edition="+compiler.edition())
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, ctx.toolchain().ToolchainRustFlags())
@@ -272,6 +275,12 @@
 	panic(fmt.Errorf("baseCrater doesn't know how to crate things!"))
 }
 
+func (compiler *baseCompiler) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+
+	return android.OptionalPath{}
+}
+
 func (compiler *baseCompiler) initialize(ctx ModuleContext) {
 	compiler.cargoOutDir = android.PathForModuleOut(ctx, genSubDir)
 }
diff --git a/rust/doc.go b/rust/doc.go
new file mode 100644
index 0000000..e7f1371
--- /dev/null
+++ b/rust/doc.go
@@ -0,0 +1,43 @@
+// Copyright 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 rust
+
+import (
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterSingletonType("rustdoc", RustdocSingleton)
+}
+
+func RustdocSingleton() android.Singleton {
+	return &rustdocSingleton{}
+}
+
+type rustdocSingleton struct{}
+
+func (n *rustdocSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	ctx.VisitAllModules(func(module android.Module) {
+		if !module.Enabled() {
+			return
+		}
+
+		if m, ok := module.(*Module); ok {
+			if m.docTimestampFile.Valid() {
+				ctx.Phony("rustdoc", m.docTimestampFile.Path())
+			}
+		}
+	})
+}
diff --git a/rust/library.go b/rust/library.go
index 052fb3a..1bdf83a 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -432,14 +432,10 @@
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	var outputFile android.ModuleOutPath
 	var fileName string
-	var srcPath android.Path
+	srcPath := library.srcPath(ctx, deps)
 
 	if library.sourceProvider != nil {
-		// Assume the first source from the source provider is the library entry point.
-		srcPath = library.sourceProvider.Srcs()[0]
 		deps.srcProviderFiles = append(deps.srcProviderFiles, library.sourceProvider.Srcs()...)
-	} else {
-		srcPath, _ = srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
 	}
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
@@ -457,22 +453,22 @@
 		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
 
-		TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+		TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.dylib() {
 		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
 
-		TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+		TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.static() {
 		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
 
-		TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+		TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile)
 	} else if library.shared() {
 		fileName = library.sharedLibFilename(ctx)
 		outputFile = android.PathForModuleOut(ctx, fileName)
 
-		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile)
 	}
 
 	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
@@ -514,6 +510,31 @@
 	return outputFile
 }
 
+func (library *libraryDecorator) srcPath(ctx ModuleContext, deps PathDeps) android.Path {
+	if library.sourceProvider != nil {
+		// Assume the first source from the source provider is the library entry point.
+		return library.sourceProvider.Srcs()[0]
+	} else {
+		path, _ := srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
+		return path
+	}
+}
+
+func (library *libraryDecorator) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+	// rustdoc has builtin support for documenting config specific information
+	// regardless of the actual config it was given
+	// (https://doc.rust-lang.org/rustdoc/advanced-features.html#cfgdoc-documenting-platform-specific-or-feature-specific-information),
+	// so we generate the rustdoc for only the primary module so that we have a
+	// single set of docs to refer to.
+	if ctx.Module() != ctx.PrimaryModule() {
+		return android.OptionalPath{}
+	}
+
+	return android.OptionalPathForPath(Rustdoc(ctx, library.srcPath(ctx, deps),
+		deps, flags))
+}
+
 func (library *libraryDecorator) getStem(ctx ModuleContext) string {
 	stem := library.baseCompiler.getStemWithoutSuffix(ctx)
 	validateLibraryStem(ctx, stem, library.crateName())
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 94fe1e5..49f3c0f 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -103,6 +103,12 @@
 	return srcPath
 }
 
+func (prebuilt *prebuiltLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+
+	return android.OptionalPath{}
+}
+
 func (prebuilt *prebuiltLibraryDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = prebuilt.baseCompiler.compilerDeps(ctx, deps)
 	return deps
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 115045a..4eead32 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -68,7 +68,7 @@
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
 	srcPath, _ := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
-	TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile, deps.linkDirs)
+	TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
 	return outputFile
 }
 
diff --git a/rust/rust.go b/rust/rust.go
index 9738b46..78a793d 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -58,6 +58,7 @@
 	RustFlags       []string // Flags that apply to rust
 	LinkFlags       []string // Flags that apply to linker
 	ClippyFlags     []string // Flags that apply to clippy-driver, during the linting
+	RustdocFlags    []string // Flags that apply to rustdoc
 	Toolchain       config.Toolchain
 	Coverage        bool
 	Clippy          bool
@@ -124,6 +125,7 @@
 	// as a library. The stripped output which is used for installation can be found via
 	// compiler.strippedOutputFile if it exists.
 	unstrippedOutputFile android.OptionalPath
+	docTimestampFile     android.OptionalPath
 
 	hideApexVariantFromMake bool
 }
@@ -355,10 +357,12 @@
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path
 	compilerDeps(ctx DepsContext, deps Deps) Deps
 	crateName() string
+	rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath
 
 	// Output directory in which source-generated code from dependencies is
 	// copied. This is equivalent to Cargo's OUT_DIR variable.
 	CargoOutDir() android.OptionalPath
+
 	inData() bool
 	install(ctx ModuleContext)
 	relativeInstallPath() string
@@ -755,6 +759,8 @@
 		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
 		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), mod.unstrippedOutputFile)
 
+		mod.docTimestampFile = mod.compiler.rustdoc(ctx, flags, deps)
+
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if mod.installable(apexInfo) {
 			mod.compiler.install(ctx)
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/hiddenapi/generate_hiddenapi_lists.py b/scripts/hiddenapi/generate_hiddenapi_lists.py
index 6816475..5ab93d1 100755
--- a/scripts/hiddenapi/generate_hiddenapi_lists.py
+++ b/scripts/hiddenapi/generate_hiddenapi_lists.py
@@ -332,7 +332,7 @@
 def main(argv):
     # Parse arguments.
     args = vars(get_args())
-    flagfiles = parse_ordered_flags(args['ordered_flags'])
+    flagfiles = parse_ordered_flags(args['ordered_flags'] or [])
 
     # Initialize API->flags dictionary.
     flags = FlagsDict()
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/Android.bp b/sdk/Android.bp
index 7b034e6..09a7286 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -20,7 +20,7 @@
         "update.go",
     ],
     testSrcs: [
-        "boot_image_sdk_test.go",
+        "bootclasspath_fragment_sdk_test.go",
         "bp_test.go",
         "cc_sdk_test.go",
         "compat_config_sdk_test.go",
diff --git a/sdk/boot_image_sdk_test.go b/sdk/boot_image_sdk_test.go
deleted file mode 100644
index 5a03e34..0000000
--- a/sdk/boot_image_sdk_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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 sdk
-
-import (
-	"testing"
-
-	"android/soong/android"
-)
-
-func TestSnapshotWithBootImage(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForSdkTestWithJava,
-		android.FixtureWithRootAndroidBp(`
-			sdk {
-				name: "mysdk",
-				boot_images: ["mybootimage"],
-			}
-
-			boot_image {
-				name: "mybootimage",
-				image_name: "art",
-			}
-		`),
-	).RunTest(t)
-
-	CheckSnapshot(t, result, "mysdk", "",
-		checkUnversionedAndroidBpContents(`
-// This is auto-generated. DO NOT EDIT.
-
-prebuilt_boot_image {
-    name: "mybootimage",
-    prefer: false,
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    image_name: "art",
-}
-`),
-		checkVersionedAndroidBpContents(`
-// This is auto-generated. DO NOT EDIT.
-
-prebuilt_boot_image {
-    name: "mysdk_mybootimage@current",
-    sdk_member_name: "mybootimage",
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    image_name: "art",
-}
-
-sdk_snapshot {
-    name: "mysdk@current",
-    visibility: ["//visibility:public"],
-    boot_images: ["mysdk_mybootimage@current"],
-}
-`),
-		checkAllCopyRules(""))
-}
-
-// Test that boot_image works with sdk.
-func TestBasicSdkWithBootImage(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForSdkTestWithApex,
-		prepareForSdkTestWithJava,
-		android.FixtureWithRootAndroidBp(`
-		sdk {
-			name: "mysdk",
-			boot_images: ["mybootimage"],
-		}
-
-		boot_image {
-			name: "mybootimage",
-			image_name: "art",
-			apex_available: ["myapex"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@1",
-			boot_images: ["mybootimage_mysdk_1"],
-		}
-
-		prebuilt_boot_image {
-			name: "mybootimage_mysdk_1",
-			sdk_member_name: "mybootimage",
-			prefer: false,
-			visibility: ["//visibility:public"],
-			apex_available: [
-				"myapex",
-			],
-			image_name: "art",
-		}
-	`),
-	).RunTest(t)
-}
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
new file mode 100644
index 0000000..0ce4351
--- /dev/null
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -0,0 +1,355 @@
+// 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 sdk
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func TestSnapshotWithBootclasspathFragment_ImageName(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		prepareForSdkTestWithApex,
+
+		// Some additional files needed for the art apex.
+		android.FixtureMergeMockFs(android.MockFS{
+			"com.android.art.avbpubkey":                          nil,
+			"com.android.art.pem":                                nil,
+			"system/sepolicy/apex/com.android.art-file_contexts": nil,
+		}),
+		java.FixtureConfigureBootJars("com.android.art:mybootlib"),
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				java_boot_libs: ["mybootlib"],
+			}
+
+			apex {
+				name: "com.android.art",
+				key: "com.android.art.key",
+				bootclasspath_fragments: [
+					"mybootclasspathfragment",
+				],
+				updatable: false,
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				image_name: "art",
+				apex_available: ["com.android.art"],
+			}
+
+			apex_key {
+				name: "com.android.art.key",
+				public_key: "com.android.art.avbpubkey",
+				private_key: "com.android.art.pem",
+			}
+
+			java_library {
+				name: "mybootlib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+				apex_available: ["com.android.art"],
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["com.android.art"],
+    image_name: "art",
+}
+
+java_import {
+    name: "mybootlib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["com.android.art"],
+    jars: ["java/mybootlib.jar"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mysdk_mybootclasspathfragment@current",
+    sdk_member_name: "mybootclasspathfragment",
+    visibility: ["//visibility:public"],
+    apex_available: ["com.android.art"],
+    image_name: "art",
+}
+
+java_import {
+    name: "mysdk_mybootlib@current",
+    sdk_member_name: "mybootlib",
+    visibility: ["//visibility:public"],
+    apex_available: ["com.android.art"],
+    jars: ["java/mybootlib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    bootclasspath_fragments: ["mysdk_mybootclasspathfragment@current"],
+    java_boot_libs: ["mysdk_mybootlib@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+`),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, android.GroupFixturePreparers(
+			android.FixtureAddTextFile("prebuilts/apex/Android.bp", `
+				prebuilt_apex {
+					name: "com.android.art",
+					src: "art.apex",
+					exported_java_libs: [
+						"mybootlib",
+					],
+				}
+			`),
+			android.FixtureAddFile("prebuilts/apex/art.apex", nil),
+		),
+		),
+	)
+}
+
+func TestSnapshotWithBootClasspathFragment_Contents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				java_boot_libs: ["mybootlib"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: ["mybootlib"],
+			}
+
+			java_library {
+				name: "mybootlib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    contents: ["mybootlib"],
+}
+
+java_import {
+    name: "mybootlib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/mybootlib.jar"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mysdk_mybootclasspathfragment@current",
+    sdk_member_name: "mybootclasspathfragment",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    contents: ["mysdk_mybootlib@current"],
+}
+
+java_import {
+    name: "mysdk_mybootlib@current",
+    sdk_member_name: "mybootlib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/mybootlib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    bootclasspath_fragments: ["mysdk_mybootclasspathfragment@current"],
+    java_boot_libs: ["mysdk_mybootlib@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+`))
+}
+
+// Test that bootclasspath_fragment works with sdk.
+func TestBasicSdkWithBootclasspathFragment(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForSdkTestWithApex,
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+		sdk {
+			name: "mysdk",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+		}
+
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			image_name: "art",
+			apex_available: ["myapex"],
+		}
+
+		sdk_snapshot {
+			name: "mysdk@1",
+			bootclasspath_fragments: ["mybootclasspathfragment_mysdk_1"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspathfragment_mysdk_1",
+			sdk_member_name: "mybootclasspathfragment",
+			prefer: false,
+			visibility: ["//visibility:public"],
+			apex_available: [
+				"myapex",
+			],
+			image_name: "art",
+		}
+	`),
+	).RunTest(t)
+}
+
+func TestSnapshotWithBootclasspathFragment_HiddenAPI(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.MockFS{
+			"my-blocked.txt":                   nil,
+			"my-max-target-o-low-priority.txt": nil,
+			"my-max-target-p.txt":              nil,
+			"my-max-target-q.txt":              nil,
+			"my-max-target-r-low-priority.txt": nil,
+			"my-removed.txt":                   nil,
+			"my-unsupported-packages.txt":      nil,
+			"my-unsupported.txt":               nil,
+		}.AddToFixture(),
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				java_boot_libs: ["mybootlib"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: ["mybootlib"],
+				hidden_api: {
+					unsupported: [
+							"my-unsupported.txt",
+					],
+					removed: [
+							"my-removed.txt",
+					],
+					max_target_r_low_priority: [
+							"my-max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"my-max-target-q.txt",
+					],
+					max_target_p: [
+							"my-max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"my-max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"my-blocked.txt",
+					],
+					unsupported_packages: [
+							"my-unsupported-packages.txt",
+					],
+				},
+			}
+
+			java_library {
+				name: "mybootlib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    contents: ["mybootlib"],
+    hidden_api: {
+        unsupported: ["hiddenapi/my-unsupported.txt"],
+        removed: ["hiddenapi/my-removed.txt"],
+        max_target_r_low_priority: ["hiddenapi/my-max-target-r-low-priority.txt"],
+        max_target_q: ["hiddenapi/my-max-target-q.txt"],
+        max_target_p: ["hiddenapi/my-max-target-p.txt"],
+        max_target_o_low_priority: ["hiddenapi/my-max-target-o-low-priority.txt"],
+        blocked: ["hiddenapi/my-blocked.txt"],
+        unsupported_packages: ["hiddenapi/my-unsupported-packages.txt"],
+    },
+}
+
+java_import {
+    name: "mybootlib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/mybootlib.jar"],
+}
+`),
+		checkAllCopyRules(`
+my-unsupported.txt -> hiddenapi/my-unsupported.txt
+my-removed.txt -> hiddenapi/my-removed.txt
+my-max-target-r-low-priority.txt -> hiddenapi/my-max-target-r-low-priority.txt
+my-max-target-q.txt -> hiddenapi/my-max-target-q.txt
+my-max-target-p.txt -> hiddenapi/my-max-target-p.txt
+my-max-target-o-low-priority.txt -> hiddenapi/my-max-target-o-low-priority.txt
+my-blocked.txt -> hiddenapi/my-blocked.txt
+my-unsupported-packages.txt -> hiddenapi/my-unsupported-packages.txt
+.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+`),
+	)
+}
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 54916d8..6016981 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -1089,6 +1089,80 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			compile_dex: true,
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    compile_dex: true,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+    system: {
+        jars: ["sdk_library/system/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
+        current_api: "sdk_library/system/myjavalib.txt",
+        removed_api: "sdk_library/system/myjavalib-removed.txt",
+        sdk_version: "system_current",
+    },
+}
+`),
+		snapshotTestChecker(checkSnapshotWithSourcePreferred, func(t *testing.T, result *android.TestResult) {
+			ctx := android.ModuleInstallPathContextForTesting(result.Config)
+			dexJarBuildPath := func(name string, kind android.SdkKind) string {
+				dep := result.Module(name, "android_common").(java.SdkLibraryDependency)
+				path := dep.SdkApiStubDexJar(ctx, kind)
+				return path.RelativeToTop().String()
+			}
+
+			dexJarPath := dexJarBuildPath("myjavalib", android.SdkPublic)
+			android.AssertStringEquals(t, "source dex public stubs jar build path", "out/soong/.intermediates/myjavalib.stubs/android_common/dex/myjavalib.stubs.jar", dexJarPath)
+
+			dexJarPath = dexJarBuildPath("myjavalib", android.SdkSystem)
+			systemDexJar := "out/soong/.intermediates/myjavalib.stubs.system/android_common/dex/myjavalib.stubs.system.jar"
+			android.AssertStringEquals(t, "source dex system stubs jar build path", systemDexJar, dexJarPath)
+
+			// This should fall back to system as module is not available.
+			dexJarPath = dexJarBuildPath("myjavalib", android.SdkModule)
+			android.AssertStringEquals(t, "source dex module stubs jar build path", systemDexJar, dexJarPath)
+
+			dexJarPath = dexJarBuildPath(android.PrebuiltNameFromSource("myjavalib"), android.SdkPublic)
+			android.AssertStringEquals(t, "prebuilt dex public stubs jar build path", "out/soong/.intermediates/snapshot/prebuilt_myjavalib.stubs/android_common/dex/myjavalib.stubs.jar", dexJarPath)
+		}),
+	)
+}
+
 func TestSnapshotWithJavaSdkLibrary_SdkVersion_None(t *testing.T) {
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
diff --git a/sdk/testing.go b/sdk/testing.go
index 9465e13..bf59aed 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -255,13 +255,14 @@
 
 	var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) {
 		customization := snapshotBuildInfo.snapshotTestCustomization(testConfig)
+		customizedPreparers := android.GroupFixturePreparers(customization.preparers...)
 
 		// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
 		//  files the snapshot needs are actually copied into the snapshot.
 
 		// Run the snapshot with the snapshot preparer and the extra preparer, which must come after as
 		// it may need to modify parts of the MockFS populated by the snapshot preparer.
-		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer).
+		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer, customizedPreparers).
 			ExtendWithErrorHandler(customization.errorHandler).
 			RunTest(t)
 
@@ -369,6 +370,15 @@
 
 type resultChecker func(t *testing.T, result *android.TestResult)
 
+// snapshotTestPreparer registers a preparer that will be used to customize the specified
+// snapshotTest.
+func snapshotTestPreparer(snapshotTest snapshotTest, preparer android.FixturePreparer) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		customization := info.snapshotTestCustomization(snapshotTest)
+		customization.preparers = append(customization.preparers, preparer)
+	}
+}
+
 // snapshotTestChecker registers a checker that will be run against the result of processing the
 // generated snapshot for the specified snapshotTest.
 func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker {
@@ -395,6 +405,9 @@
 
 // Encapsulates information provided by each test to customize a specific snapshotTest.
 type snapshotTestCustomization struct {
+	// Preparers that are used to customize the test fixture before running the test.
+	preparers []android.FixturePreparer
+
 	// Checkers that are run on the result of processing the preferred snapshot in a specific test
 	// case.
 	checkers []resultChecker
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 98e3b66..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.
 
@@ -126,7 +128,7 @@
 
   # This test needs to start from a clean build, but setup creates an
   # initialized tree that has already been built once.  Clear the out
-  # directory to start from scratch.
+  # directory to start from scratch (see b/185591972)
   rm -rf out
 
   mkdir -p a
@@ -140,7 +142,7 @@
   run_soong
   local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  local glob_deps_file=out/soong/.glob/a/__py.glob.d
+  local glob_deps_file=out/soong/.primary/globs/0.d
 
   if [ -e "$glob_deps_file" ]; then
     fail "Glob deps file unexpectedly written on first build"
@@ -482,15 +484,14 @@
   fi
 }
 
-function test_integrated_bp2build_smoke {
+function test_bp2build_smoke {
   setup
-  INTEGRATED_BP2BUILD=1 run_soong
-  if [[ ! -e out/soong/.bootstrap/bp2build_workspace_marker ]]; then
-    fail "bp2build marker file not created"
-  fi
+  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
@@ -503,10 +504,9 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
-  if [[ ! -e out/soong/bp2build/a/BUILD ]]; then
-    fail "a/BUILD not created";
-  fi
+  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"
 
   mkdir -p b
   touch b/b.txt
@@ -518,19 +518,18 @@
 }
 EOF
 
-  INTEGRATED_BP2BUILD=1 run_soong
-  if [[ ! -e out/soong/bp2build/b/BUILD ]]; then
-    fail "b/BUILD not created";
-  fi
+  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
@@ -538,7 +537,7 @@
   fi
 }
 
-function test_integrated_bp2build_add_to_glob {
+function test_bp2build_add_to_glob {
   setup
 
   mkdir -p a
@@ -551,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"
 }
 
@@ -567,6 +566,102 @@
   fi
 }
 
+function test_bp2build_bazel_workspace_structure {
+  setup
+
+  mkdir -p a/b
+  touch a/a.txt
+  touch a/b/b.txt
+  cat > a/b/Android.bp <<'EOF'
+filegroup {
+  name: "b",
+  srcs: ["b.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  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"
+  [[ "$(readlink -f out/soong/workspace/a/b/BUILD)" =~ bp2build/a/b/BUILD$ ]] \
+    || fail "BUILD files symlinked at the wrong place"
+  [[ -L out/soong/workspace/a/b/b.txt ]] || fail "a/b/b.txt not symlinked"
+  [[ -L out/soong/workspace/a/a.txt ]] || fail "a/b/a.txt not symlinked"
+  [[ ! -e out/soong/workspace/out ]] || fail "out directory symlinked"
+}
+
+function test_bp2build_bazel_workspace_add_file {
+  setup
+
+  mkdir -p a
+  touch a/a.txt
+  cat > a/Android.bp <<EOF
+filegroup {
+  name: "a",
+  srcs: ["a.txt"],
+  bazel_module: { bp2build_available: true },
+}
+EOF
+
+  GENERATE_BAZEL_FILES=1 run_soong
+
+  touch a/a2.txt  # No reference in the .bp file needed
+  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
@@ -580,6 +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_to_glob
+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 db24037..8399573 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -1,6 +1,8 @@
-#!/bin/bash
+#!/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 {