Merge "Limit the number of the Java source files in a single compilation unit"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ff85661..317f5c4 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,6 @@
 [Builtin Hooks]
 gofmt = true
 bpfmt = true
+
+[Hook Scripts]
+do_not_use_DO_NOT_MERGE = ${REPO_ROOT}/build/soong/scripts/check_do_not_merge.sh ${PREUPLOAD_COMMIT}
diff --git a/android/apex.go b/android/apex.go
index 6bb0751..81f8c86 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -739,6 +739,8 @@
 
 	d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
 	WriteFileRule(ctx, d.flatListPath, flatContent.String())
+
+	ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), d.fullListPath, d.flatListPath)
 }
 
 // TODO(b/158059172): remove minSdkVersion allowlist
@@ -757,7 +759,6 @@
 	"captiveportal-lib":              28,
 	"flatbuffer_headers":             30,
 	"framework-permission":           30,
-	"framework-statsd":               30,
 	"gemmlowp_headers":               30,
 	"ike-internals":                  30,
 	"kotlinx-coroutines-android":     28,
@@ -790,11 +791,6 @@
 	"libprotobuf-java-lite":          30,
 	"libprotoutil":                   30,
 	"libqemu_pipe":                   30,
-	"libstats_jni":                   30,
-	"libstatslog_statsd":             30,
-	"libstatsmetadata":               30,
-	"libstatspull":                   30,
-	"libstatssocket":                 30,
 	"libsync":                        30,
 	"libtextclassifier_hash_headers": 30,
 	"libtextclassifier_hash_static":  30,
@@ -807,9 +803,6 @@
 	"philox_random_headers":          30,
 	"philox_random":                  30,
 	"service-permission":             30,
-	"service-statsd":                 30,
-	"statsd-aidl-ndk_platform":       30,
-	"statsd":                         30,
 	"tensorflow_headers":             30,
 	"xz-java":                        29,
 })
diff --git a/android/arch.go b/android/arch.go
index b277381..6fb70c9 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1442,7 +1442,7 @@
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
-		{"arm64", "armv8-a", "", []string{"arm64-v8a"}},
+		{"arm64", "armv8-a-branchprot", "", []string{"arm64-v8a"}},
 		{"x86", "", "", []string{"x86"}},
 		{"x86_64", "", "", []string{"x86_64"}},
 	}
@@ -1609,13 +1609,15 @@
 		} else {
 			buildTargets = firstTarget(targets, "lib64", "lib32")
 		}
+	case "first_prefer32":
+		buildTargets = firstTarget(targets, "lib32", "lib64")
 	case "prefer32":
 		buildTargets = filterMultilibTargets(targets, "lib32")
 		if len(buildTargets) == 0 {
 			buildTargets = filterMultilibTargets(targets, "lib64")
 		}
 	default:
-		return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", or "prefer32" found %q`,
+		return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", "prefer32" or "first_prefer32" found %q`,
 			multilib)
 	}
 
diff --git a/android/arch_list.go b/android/arch_list.go
index 0c33b9d..d68a0d1 100644
--- a/android/arch_list.go
+++ b/android/arch_list.go
@@ -41,6 +41,7 @@
 	},
 	Arm64: {
 		"armv8_a",
+		"armv8_a_branchprot",
 		"armv8_2a",
 		"armv8-2a-dotprod",
 		"cortex-a53",
diff --git a/android/config.go b/android/config.go
index 6170f34..f0bba81 100644
--- a/android/config.go
+++ b/android/config.go
@@ -128,7 +128,7 @@
 
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
-	testAllowNonExistentPaths bool
+	TestAllowNonExistentPaths bool
 
 	// The list of files that when changed, must invalidate soong_build to
 	// regenerate build.ninja.
@@ -247,6 +247,7 @@
 			AAPTCharacteristics:               stringPtr("nosdcard"),
 			AAPTPrebuiltDPI:                   []string{"xhdpi", "xxhdpi"},
 			UncompressPrivAppDex:              boolPtr(true),
+			ShippingApiLevel:                  stringPtr("30"),
 		},
 
 		buildDir:     buildDir,
@@ -255,7 +256,7 @@
 
 		// Set testAllowNonExistentPaths so that test contexts don't need to specify every path
 		// passed to PathForSource or PathForModuleSrc.
-		testAllowNonExistentPaths: true,
+		TestAllowNonExistentPaths: true,
 
 		BazelContext: noopBazelContext{},
 	}
@@ -964,13 +965,7 @@
 // More info: https://source.android.com/devices/architecture/rros
 func (c *config) EnforceRROForModule(name string) bool {
 	enforceList := c.productVariables.EnforceRROTargets
-	// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
-	exemptedList := c.productVariables.EnforceRROExemptedTargets
-	if len(exemptedList) > 0 {
-		if InList(name, exemptedList) {
-			return false
-		}
-	}
+
 	if len(enforceList) > 0 {
 		if InList("*", enforceList) {
 			return true
@@ -979,11 +974,6 @@
 	}
 	return false
 }
-
-func (c *config) EnforceRROExemptedForModule(name string) bool {
-	return InList(name, c.productVariables.EnforceRROExemptedTargets)
-}
-
 func (c *config) EnforceRROExcludedOverlay(path string) bool {
 	excluded := c.productVariables.EnforceRROExcludedOverlays
 	if len(excluded) > 0 {
@@ -1441,6 +1431,18 @@
 	return c.config.productVariables.RecoverySnapshotModules
 }
 
+func (c *deviceConfig) ShippingApiLevel() ApiLevel {
+	if c.config.productVariables.ShippingApiLevel == nil {
+		return NoneApiLevel
+	}
+	apiLevel, _ := strconv.Atoi(*c.config.productVariables.ShippingApiLevel)
+	return uncheckedFinalApiLevel(apiLevel)
+}
+
+func (c *deviceConfig) BuildBrokenVendorPropertyNamespace() bool {
+	return c.config.productVariables.BuildBrokenVendorPropertyNamespace
+}
+
 // The ConfiguredJarList struct provides methods for handling a list of (apex, jar) pairs.
 // Such lists are used in the build system for things like bootclasspath jars or system server jars.
 // The apex part is either an apex name, or a special names "platform" or "system_ext". Jar is a
diff --git a/android/paths.go b/android/paths.go
index c3fa61a..ada4da6 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -280,7 +280,8 @@
 	return false
 }
 
-// PathsForSource returns Paths rooted from SrcDir
+// PathsForSource returns Paths rooted from SrcDir, *not* rooted from the module's local source
+// directory
 func PathsForSource(ctx PathContext, paths []string) Paths {
 	ret := make(Paths, len(paths))
 	for i, path := range paths {
@@ -289,9 +290,9 @@
 	return ret
 }
 
-// ExistentPathsForSources returns a list of Paths rooted from SrcDir that are
-// found in the tree. If any are not found, they are omitted from the list,
-// and dependencies are added so that we're re-run when they are added.
+// ExistentPathsForSources returns a list of Paths rooted from SrcDir, *not* rooted from the
+// module's local source directory, that are found in the tree. If any are not found, they are
+// omitted from the list, and dependencies are added so that we're re-run when they are added.
 func ExistentPathsForSources(ctx PathContext, paths []string) Paths {
 	ret := make(Paths, 0, len(paths))
 	for _, path := range paths {
@@ -395,6 +396,9 @@
 // `android:"path"` so that dependencies on other modules will have already been handled by the
 // path_properties mutator.
 func expandSrcsForBazel(ctx BazelConversionPathContext, paths, expandedExcludes []string) bazel.LabelList {
+	if paths == nil {
+		return bazel.LabelList{}
+	}
 	labels := bazel.LabelList{
 		Includes: []bazel.Label{},
 	}
@@ -582,7 +586,7 @@
 		p := pathForModuleSrc(ctx, sPath)
 		if exists, _, err := ctx.Config().fs.Exists(p.String()); err != nil {
 			ReportPathErrorf(ctx, "%s: %s", p, err.Error())
-		} else if !exists && !ctx.Config().testAllowNonExistentPaths {
+		} else if !exists && !ctx.Config().TestAllowNonExistentPaths {
 			ReportPathErrorf(ctx, "module source path %q does not exist", p)
 		}
 
@@ -1018,15 +1022,16 @@
 		}
 	} else if exists, _, err := ctx.Config().fs.Exists(path.String()); err != nil {
 		ReportPathErrorf(ctx, "%s: %s", path, err.Error())
-	} else if !exists && !ctx.Config().testAllowNonExistentPaths {
+	} else if !exists && !ctx.Config().TestAllowNonExistentPaths {
 		ReportPathErrorf(ctx, "source path %q does not exist", path)
 	}
 	return path
 }
 
-// ExistentPathForSource returns an OptionalPath with the SourcePath if the
-// path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added
-// so that the ninja file will be regenerated if the state of the path changes.
+// ExistentPathForSource returns an OptionalPath with the SourcePath, rooted from SrcDir, *not*
+// rooted from the module's local source directory, if the path exists, or an empty OptionalPath if
+// it doesn't exist. Dependencies are added so that the ninja file will be regenerated if the state
+// of the path changes.
 func ExistentPathForSource(ctx PathContext, pathComponents ...string) OptionalPath {
 	path, err := pathForSource(ctx, pathComponents...)
 	if err != nil {
diff --git a/android/util.go b/android/util.go
index 0f940fa..506f8f7 100644
--- a/android/util.go
+++ b/android/util.go
@@ -137,6 +137,16 @@
 	return false
 }
 
+// Returns true if any string in the given list has the given substring.
+func SubstringInList(list []string, substr string) bool {
+	for _, s := range list {
+		if strings.Contains(s, substr) {
+			return true
+		}
+	}
+	return false
+}
+
 // Returns true if any string in the given list has the given prefix.
 func PrefixInList(list []string, prefix string) bool {
 	for _, s := range list {
diff --git a/android/variable.go b/android/variable.go
index e76d683..dd000ad 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -199,11 +199,9 @@
 	CrossHostArch          *string `json:",omitempty"`
 	CrossHostSecondaryArch *string `json:",omitempty"`
 
-	DeviceResourceOverlays  []string `json:",omitempty"`
-	ProductResourceOverlays []string `json:",omitempty"`
-	EnforceRROTargets       []string `json:",omitempty"`
-	// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
-	EnforceRROExemptedTargets  []string `json:",omitempty"`
+	DeviceResourceOverlays     []string `json:",omitempty"`
+	ProductResourceOverlays    []string `json:",omitempty"`
+	EnforceRROTargets          []string `json:",omitempty"`
 	EnforceRROExcludedOverlays []string `json:",omitempty"`
 
 	AAPTCharacteristics *string  `json:",omitempty"`
@@ -367,6 +365,10 @@
 	BoardMoveRecoveryResourcesToVendorBoot *bool `json:",omitempty"`
 
 	PrebuiltHiddenApiDir *string `json:",omitempty"`
+
+	ShippingApiLevel *string `json:",omitempty"`
+
+	BuildBrokenVendorPropertyNamespace bool `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index aee3fc4..72394db 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -176,12 +176,15 @@
 ExtServices-core(minSdkVersion:current)
 flatbuffer_headers(minSdkVersion:(no version))
 fmtlib(minSdkVersion:29)
+fmtlib_ndk(minSdkVersion:29)
+framework-mediaprovider(minSdkVersion:30)
 framework-permission(minSdkVersion:30)
 framework-permission(minSdkVersion:current)
 framework-permission-s(minSdkVersion:30)
 framework-permission-s-shared(minSdkVersion:30)
 framework-sdkextensions(minSdkVersion:30)
 framework-sdkextensions(minSdkVersion:current)
+framework-statsd(minSdkVersion:30)
 framework-statsd(minSdkVersion:current)
 framework-tethering(minSdkVersion:30)
 framework-tethering(minSdkVersion:current)
@@ -199,6 +202,7 @@
 InProcessTethering(minSdkVersion:current)
 ipmemorystore-aidl-interfaces-java(minSdkVersion:29)
 ipmemorystore-aidl-interfaces-unstable-java(minSdkVersion:29)
+ipmemorystore-aidl-interfaces-V10-java(minSdkVersion:29)
 ipmemorystore-aidl-interfaces-V11-java(minSdkVersion:29)
 jni_headers(minSdkVersion:29)
 jsr305(minSdkVersion:14)
@@ -239,6 +243,7 @@
 libbacktrace_sys.rust_sysroot(minSdkVersion:29)
 libbase(minSdkVersion:29)
 libbase_headers(minSdkVersion:29)
+libbase_ndk(minSdkVersion:29)
 libbinder_headers(minSdkVersion:29)
 libbinder_headers_platform_shared(minSdkVersion:29)
 libbinderthreadstateutils(minSdkVersion:29)
@@ -314,6 +319,8 @@
 libfmq(minSdkVersion:29)
 libfmq-base(minSdkVersion:29)
 libFraunhoferAAC(minSdkVersion:29)
+libfuse(minSdkVersion:30)
+libfuse_jni(minSdkVersion:30)
 libgav1(minSdkVersion:29)
 libgcc(minSdkVersion:(no version))
 libgcc_stripped(minSdkVersion:(no version))
@@ -425,11 +432,15 @@
 libstagefright_mpeg2extractor(minSdkVersion:29)
 libstagefright_mpeg2support_nocrypto(minSdkVersion:29)
 libstats_jni(minSdkVersion:(no version))
+libstats_jni(minSdkVersion:30)
 libstatslog_resolv(minSdkVersion:29)
 libstatslog_statsd(minSdkVersion:(no version))
+libstatslog_statsd(minSdkVersion:30)
 libstatspull(minSdkVersion:(no version))
+libstatspull(minSdkVersion:30)
 libstatspush_compat(minSdkVersion:29)
 libstatssocket(minSdkVersion:(no version))
+libstatssocket(minSdkVersion:30)
 libstatssocket_headers(minSdkVersion:29)
 libstd(minSdkVersion:29)
 libsystem_headers(minSdkVersion:apex_inherit)
@@ -463,9 +474,11 @@
 libzstd(minSdkVersion:(no version))
 media_ndk_headers(minSdkVersion:29)
 media_plugin_headers(minSdkVersion:29)
+MediaProvider(minSdkVersion:30)
 mediaswcodec(minSdkVersion:29)
 metrics-constants-protos(minSdkVersion:29)
 modules-utils-build(minSdkVersion:29)
+modules-utils-build_system(minSdkVersion:29)
 modules-utils-os(minSdkVersion:30)
 ndk_crtbegin_so.19(minSdkVersion:(no version))
 ndk_crtbegin_so.21(minSdkVersion:(no version))
@@ -482,7 +495,9 @@
 net-utils-framework-common(minSdkVersion:current)
 netd-client(minSdkVersion:29)
 netd_aidl_interface-java(minSdkVersion:29)
+netd_aidl_interface-lateststable-java(minSdkVersion:29)
 netd_aidl_interface-unstable-java(minSdkVersion:29)
+netd_aidl_interface-V5-java(minSdkVersion:29)
 netd_aidl_interface-V6-java(minSdkVersion:29)
 netd_event_listener_interface-java(minSdkVersion:29)
 netd_event_listener_interface-ndk_platform(minSdkVersion:29)
@@ -592,6 +607,7 @@
 service-permission(minSdkVersion:30)
 service-permission(minSdkVersion:current)
 service-permission-shared(minSdkVersion:30)
+service-statsd(minSdkVersion:30)
 service-statsd(minSdkVersion:current)
 SettingsLibActionBarShadow(minSdkVersion:21)
 SettingsLibAppPreference(minSdkVersion:21)
@@ -605,7 +621,9 @@
 SettingsLibUtils(minSdkVersion:21)
 stats_proto(minSdkVersion:29)
 statsd(minSdkVersion:(no version))
+statsd(minSdkVersion:30)
 statsd-aidl-ndk_platform(minSdkVersion:(no version))
+statsd-aidl-ndk_platform(minSdkVersion:30)
 statsprotos(minSdkVersion:29)
 tensorflow_headers(minSdkVersion:(no version))
 Tethering(minSdkVersion:30)
diff --git a/apex/apex.go b/apex/apex.go
index cfeac72..662bbbd 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -208,6 +208,9 @@
 
 	// List of native tests that are embedded inside this APEX.
 	Tests []string
+
+	// List of filesystem images that are embedded inside this APEX bundle.
+	Filesystems []string
 }
 
 type apexMultilibProperties struct {
@@ -580,6 +583,7 @@
 	ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
 	ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
 	ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
+	ctx.AddFarVariationDependencies(target.Variations(), fsTag, nativeModules.Filesystems...)
 }
 
 func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@@ -2843,14 +2847,6 @@
 	//
 	// Module separator
 	//
-	m["com.android.sdkext"] = []string{
-		"fmtlib_ndk",
-		"libbase_ndk",
-		"libprotobuf-cpp-lite-ndk",
-	}
-	//
-	// Module separator
-	//
 	m["com.android.os.statsd"] = []string{
 		"libstatssocket",
 	}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 181946b..bbc4b93 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2039,7 +2039,7 @@
 		},
 		{
 			name:          "Updatable apex with non-stable transitive dep",
-			expectedError: "compiles against Android API, but dependency \"transitive-jar\" is compiling against non-public Android API.",
+			expectedError: "compiles against Android API, but dependency \"transitive-jar\" is compiling against private API.",
 			bp: `
 				apex {
 					name: "myapex",
diff --git a/bloaty/Android.bp b/bloaty/Android.bp
new file mode 100644
index 0000000..b1f1e39
--- /dev/null
+++ b/bloaty/Android.bp
@@ -0,0 +1,40 @@
+bootstrap_go_package {
+    name: "soong-bloaty",
+    pkgPath: "android/soong/bloaty",
+    deps: [
+        "blueprint",
+        "soong-android",
+    ],
+    srcs: [
+        "bloaty.go",
+    ],
+    pluginFor: ["soong_build"],
+}
+
+python_test_host {
+    name: "bloaty_merger_test",
+    srcs: [
+        "bloaty_merger_test.py",
+        "bloaty_merger.py",
+        "file_sections.proto",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+    },
+    libs: [
+        "pyfakefs",
+        "ninja_rsp",
+    ],
+}
+
+python_binary_host {
+    name: "bloaty_merger",
+    srcs: [
+        "bloaty_merger.py",
+        "file_sections.proto",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+    },
+    libs: ["ninja_rsp"],
+}
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
new file mode 100644
index 0000000..0bff8aa
--- /dev/null
+++ b/bloaty/bloaty.go
@@ -0,0 +1,92 @@
+// 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 bloaty implements a singleton that measures binary (e.g. ELF
+// executable, shared library or Rust rlib) section sizes at build time.
+package bloaty
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+const bloatyDescriptorExt = "bloaty.csv"
+
+var (
+	fileSizeMeasurerKey blueprint.ProviderKey
+	pctx                = android.NewPackageContext("android/soong/bloaty")
+
+	// bloaty is used to measure a binary section sizes.
+	bloaty = pctx.AndroidStaticRule("bloaty",
+		blueprint.RuleParams{
+			Command:     "${bloaty} -n 0 --csv ${in} > ${out}",
+			CommandDeps: []string{"${bloaty}"},
+		})
+
+	// The bloaty merger script is used to combine the outputs from bloaty
+	// into a single protobuf.
+	bloatyMerger = pctx.AndroidStaticRule("bloatyMerger",
+		blueprint.RuleParams{
+			Command:        "${bloatyMerger} ${out}.lst ${out}",
+			CommandDeps:    []string{"${bloatyMerger}"},
+			Rspfile:        "${out}.lst",
+			RspfileContent: "${in}",
+		})
+)
+
+func init() {
+	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
+	pctx.SourcePathVariable("bloaty", "prebuilts/build-tools/${hostPrebuiltTag}/bin/bloaty")
+	pctx.HostBinToolVariable("bloatyMerger", "bloaty_merger")
+	android.RegisterSingletonType("file_metrics", fileSizesSingleton)
+	fileSizeMeasurerKey = blueprint.NewProvider(android.ModuleOutPath{})
+}
+
+// MeasureSizeForPath should be called by binary producers (e.g. in builder.go).
+func MeasureSizeForPath(ctx android.ModuleContext, filePath android.WritablePath) {
+	ctx.SetProvider(fileSizeMeasurerKey, filePath)
+}
+
+type sizesSingleton struct{}
+
+func fileSizesSingleton() android.Singleton {
+	return &sizesSingleton{}
+}
+
+func (singleton *sizesSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var deps android.Paths
+	// Visit all modules. If the size provider give us a binary path to measure,
+	// create the rule to measure it.
+	ctx.VisitAllModules(func(m android.Module) {
+		if !ctx.ModuleHasProvider(m, fileSizeMeasurerKey) {
+			return
+		}
+		filePath := ctx.ModuleProvider(m, fileSizeMeasurerKey).(android.ModuleOutPath)
+		sizeFile := filePath.ReplaceExtension(ctx, bloatyDescriptorExt)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        bloaty,
+			Description: "bloaty " + filePath.Rel(),
+			Input:       filePath,
+			Output:      sizeFile,
+		})
+		deps = append(deps, sizeFile)
+	})
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   bloatyMerger,
+		Inputs: android.SortedUniquePaths(deps),
+		Output: android.PathForOutput(ctx, "binary_sizes.pb"),
+	})
+}
diff --git a/bloaty/bloaty_merger.py b/bloaty/bloaty_merger.py
new file mode 100644
index 0000000..c873fb8
--- /dev/null
+++ b/bloaty/bloaty_merger.py
@@ -0,0 +1,79 @@
+# 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.
+"""Bloaty CSV Merger
+
+Merges a list of .csv files from Bloaty into a protobuf.  It takes the list as
+a first argument and the output as second. For instance:
+
+    $ bloaty_merger binary_sizes.lst binary_sizes.pb
+
+"""
+
+import argparse
+import csv
+
+import ninja_rsp
+
+import file_sections_pb2
+
+BLOATY_EXTENSION = ".bloaty.csv"
+
+def parse_csv(path):
+  """Parses a Bloaty-generated CSV file into a protobuf.
+
+  Args:
+    path: The filepath to the CSV file, relative to $ANDROID_TOP.
+
+  Returns:
+    A file_sections_pb2.File if the file was found; None otherwise.
+  """
+  file_proto = None
+  with open(path, newline='') as csv_file:
+    file_proto = file_sections_pb2.File()
+    if path.endswith(BLOATY_EXTENSION):
+      file_proto.path = path[:-len(BLOATY_EXTENSION)]
+    section_reader = csv.DictReader(csv_file)
+    for row in section_reader:
+      section = file_proto.sections.add()
+      section.name = row["sections"]
+      section.vm_size = int(row["vmsize"])
+      section.file_size = int(row["filesize"])
+  return file_proto
+
+def create_file_size_metrics(input_list, output_proto):
+  """Creates a FileSizeMetrics proto from a list of CSV files.
+
+  Args:
+    input_list: The path to the file which contains the list of CSV files. Each
+        filepath is separated by a space.
+    output_proto: The path for the output protobuf.
+  """
+  metrics = file_sections_pb2.FileSizeMetrics()
+  reader = ninja_rsp.NinjaRspFileReader(input_list)
+  for csv_path in reader:
+    file_proto = parse_csv(csv_path)
+    if file_proto:
+      metrics.files.append(file_proto)
+  with open(output_proto, "wb") as output:
+    output.write(metrics.SerializeToString())
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("input_list_file", help="List of bloaty csv files.")
+  parser.add_argument("output_proto", help="Output proto.")
+  args = parser.parse_args()
+  create_file_size_metrics(args.input_list_file, args.output_proto)
+
+if __name__ == '__main__':
+  main()
diff --git a/bloaty/bloaty_merger_test.py b/bloaty/bloaty_merger_test.py
new file mode 100644
index 0000000..0e3641d
--- /dev/null
+++ b/bloaty/bloaty_merger_test.py
@@ -0,0 +1,65 @@
+# 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.
+import unittest
+
+from pyfakefs import fake_filesystem_unittest
+
+import bloaty_merger
+import file_sections_pb2
+
+
+class BloatyMergerTestCase(fake_filesystem_unittest.TestCase):
+  def setUp(self):
+    self.setUpPyfakefs()
+
+  def test_parse_csv(self):
+    csv_content = "sections,vmsize,filesize\nsection1,2,3\n"
+    self.fs.create_file("file1.bloaty.csv", contents=csv_content)
+    pb = bloaty_merger.parse_csv("file1.bloaty.csv")
+    self.assertEqual(pb.path, "file1")
+    self.assertEqual(len(pb.sections), 1)
+    s = pb.sections[0]
+    self.assertEqual(s.name, "section1")
+    self.assertEqual(s.vm_size, 2)
+    self.assertEqual(s.file_size, 3)
+
+  def test_missing_file(self):
+    with self.assertRaises(FileNotFoundError):
+      bloaty_merger.parse_csv("missing.bloaty.csv")
+
+  def test_malformed_csv(self):
+    csv_content = "header1,heaVder2,header3\n4,5,6\n"
+    self.fs.create_file("file1.bloaty.csv", contents=csv_content)
+    with self.assertRaises(KeyError):
+      bloaty_merger.parse_csv("file1.bloaty.csv")
+
+  def test_create_file_metrics(self):
+    file_list = "file1.bloaty.csv file2.bloaty.csv"
+    file1_content = "sections,vmsize,filesize\nsection1,2,3\nsection2,7,8"
+    file2_content = "sections,vmsize,filesize\nsection1,4,5\n"
+
+    self.fs.create_file("files.lst", contents=file_list)
+    self.fs.create_file("file1.bloaty.csv", contents=file1_content)
+    self.fs.create_file("file2.bloaty.csv", contents=file2_content)
+
+    bloaty_merger.create_file_size_metrics("files.lst", "output.pb")
+
+    metrics = file_sections_pb2.FileSizeMetrics()
+    with open("output.pb", "rb") as output:
+      metrics.ParseFromString(output.read())
+
+
+if __name__ == '__main__':
+  suite = unittest.TestLoader().loadTestsFromTestCase(BloatyMergerTestCase)
+  unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/bloaty/file_sections.proto b/bloaty/file_sections.proto
new file mode 100644
index 0000000..34a32db
--- /dev/null
+++ b/bloaty/file_sections.proto
@@ -0,0 +1,39 @@
+// 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.
+syntax = "proto2";
+
+package file_sections;
+
+message SectionDescriptior {
+  // Name of the section (e.g. .rodata)
+  optional string name = 1;
+
+  // Size of that section as part of the file.
+  optional uint64 file_size = 2;
+
+  // Size of that section when loaded in memory.
+  optional uint64 vm_size = 3;
+}
+
+message File {
+  // Relative path from $OUT_DIR.
+  optional string path = 1;
+
+  // File sections.
+  repeated SectionDescriptior sections = 2;
+}
+
+message FileSizeMetrics {
+  repeated File files = 1;
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index fdac88d..7d748ec 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -11,17 +11,22 @@
         "build_conversion.go",
         "bzl_conversion.go",
         "conversion.go",
+        "metrics.go",
     ],
     deps: [
         "soong-android",
         "soong-bazel",
+        "soong-cc",
         "soong-genrule",
         "soong-sh",
     ],
     testSrcs: [
         "build_conversion_test.go",
         "bzl_conversion_test.go",
+        "cc_library_headers_conversion_test.go",
+        "cc_object_conversion_test.go",
         "conversion_test.go",
+        "sh_conversion_test.go",
         "testing.go",
     ],
     pluginFor: [
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index b89d0a0..7169d7e 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -22,13 +22,13 @@
 
 // The Bazel bp2build code generator is responsible for writing .bzl files that are equivalent to
 // Android.bp files that are capable of being built with Bazel.
-func Codegen(ctx CodegenContext) {
+func Codegen(ctx CodegenContext) CodegenMetrics {
 	outputDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(outputDir)
 
 	ruleShims := CreateRuleShims(android.ModuleTypeFactories())
 
-	buildToTargets := GenerateBazelTargets(ctx.Context(), ctx.mode)
+	buildToTargets, metrics := GenerateBazelTargets(ctx)
 
 	filesToWrite := CreateBazelFiles(ruleShims, buildToTargets, ctx.mode)
 	for _, f := range filesToWrite {
@@ -36,6 +36,8 @@
 			fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err)
 		}
 	}
+
+	return metrics
 }
 
 func writeFile(outputDir android.OutputPath, ctx android.PathContext, f BazelFile) error {
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 7ffcfa4..fcad686 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -105,6 +105,10 @@
 	mode    CodegenMode
 }
 
+func (c *CodegenContext) Mode() CodegenMode {
+	return c.mode
+}
+
 // CodegenMode is an enum to differentiate code-generation modes.
 type CodegenMode int
 
@@ -160,33 +164,54 @@
 	return attributes
 }
 
-func GenerateBazelTargets(ctx bpToBuildContext, codegenMode CodegenMode) map[string]BazelTargets {
+func GenerateBazelTargets(ctx CodegenContext) (map[string]BazelTargets, CodegenMetrics) {
 	buildFileToTargets := make(map[string]BazelTargets)
-	ctx.VisitAllModules(func(m blueprint.Module) {
-		dir := ctx.ModuleDir(m)
+
+	// Simple metrics tracking for bp2build
+	totalModuleCount := 0
+	ruleClassCount := make(map[string]int)
+
+	bpCtx := ctx.Context()
+	bpCtx.VisitAllModules(func(m blueprint.Module) {
+		dir := bpCtx.ModuleDir(m)
 		var t BazelTarget
 
-		switch codegenMode {
+		switch ctx.Mode() {
 		case Bp2Build:
 			if _, ok := m.(android.BazelTargetModule); !ok {
+				// Only include regular Soong modules (non-BazelTargetModules) into the total count.
+				totalModuleCount += 1
 				return
 			}
-			t = generateBazelTarget(ctx, m)
+			t = generateBazelTarget(bpCtx, m)
+			ruleClassCount[t.ruleClass] += 1
 		case QueryView:
 			// Blocklist certain module types from being generated.
-			if canonicalizeModuleType(ctx.ModuleType(m)) == "package" {
+			if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
 				// package module name contain slashes, and thus cannot
 				// be mapped cleanly to a bazel label.
 				return
 			}
-			t = generateSoongModuleTarget(ctx, m)
+			t = generateSoongModuleTarget(bpCtx, m)
 		default:
-			panic(fmt.Errorf("Unknown code-generation mode: %s", codegenMode))
+			panic(fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
 		}
 
 		buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
 	})
-	return buildFileToTargets
+
+	metrics := CodegenMetrics{
+		TotalModuleCount: totalModuleCount,
+		RuleClassCount:   ruleClassCount,
+	}
+
+	return buildFileToTargets, metrics
+}
+
+// Helper method for tests to easily access the targets in a dir.
+func GenerateBazelTargetsForDir(codegenCtx CodegenContext, dir string) BazelTargets {
+	bazelTargetsMap, _ := GenerateBazelTargets(codegenCtx)
+	return bazelTargetsMap[dir]
 }
 
 // Helper method to trim quotes around strings.
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index df554a0..fce6433 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -17,7 +17,6 @@
 import (
 	"android/soong/android"
 	"android/soong/genrule"
-	"android/soong/sh"
 	"strings"
 	"testing"
 )
@@ -195,6 +194,7 @@
 	for _, testCase := range testCases {
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
+
 		ctx.RegisterModuleType("custom", customModuleFactory)
 		ctx.Register()
 
@@ -203,7 +203,8 @@
 		_, errs = ctx.PrepareBuildActions(config)
 		android.FailIfErrored(t, errs)
 
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, QueryView)[dir]
+		codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
 		if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
 			t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
 		}
@@ -246,6 +247,7 @@
 	for _, testCase := range testCases {
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
+
 		ctx.RegisterModuleType("custom", customModuleFactory)
 		ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutator)
 		ctx.RegisterForBazelConversion()
@@ -259,7 +261,9 @@
 			continue
 		}
 
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, Bp2Build)[dir]
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
+
 		if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
 			t.Errorf("Expected %d bazel target, got %d", expectedCount, actualCount)
 		} else {
@@ -358,12 +362,6 @@
 					ruleClass: "genrule",
 					// Note: no bzlLoadLocation for native rules
 				},
-				BazelTarget{
-					name:      "sh_binary_target",
-					ruleClass: "sh_binary",
-					// Note: no bzlLoadLocation for native rules
-					// TODO(ruperts): Could open source the existing, experimental Starlark sh_ rules?
-				},
 			},
 			expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary")
 load("//build/bazel/rules:java.bzl", "java_binary")`,
@@ -422,7 +420,8 @@
 		_, errs = ctx.ResolveDependencies(config)
 		android.FailIfErrored(t, errs)
 
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, Bp2Build)[dir]
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
 		if actualCount := len(bazelTargets); actualCount != testCase.expectedBazelTargetCount {
 			t.Fatalf("Expected %d bazel target, got %d", testCase.expectedBazelTargetCount, actualCount)
 		}
@@ -476,6 +475,21 @@
 		dir                                string
 	}{
 		{
+			description:                        "filegroup with does not specify srcs",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			bp: `filegroup {
+    name: "fg_foo",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				`filegroup(
+    name = "fg_foo",
+)`,
+			},
+		},
+		{
 			description:                        "filegroup with no srcs",
 			moduleTypeUnderTest:                "filegroup",
 			moduleTypeUnderTestFactory:         android.FileGroupFactory,
@@ -860,23 +874,6 @@
 )`,
 			},
 		},
-		{
-			description:                        "sh_binary test",
-			moduleTypeUnderTest:                "sh_binary",
-			moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
-			moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
-			bp: `sh_binary {
-    name: "foo",
-    src: "foo.sh",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`sh_binary(
-    name = "foo",
-    srcs = [
-        "foo.sh",
-    ],
-)`},
-		},
 	}
 
 	dir := "."
@@ -913,7 +910,9 @@
 		if testCase.dir != "" {
 			checkDir = testCase.dir
 		}
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, Bp2Build)[checkDir]
+
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, checkDir)
 		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
 			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
 		} else {
@@ -1127,7 +1126,8 @@
 		_, errs = ctx.ResolveDependencies(config)
 		android.FailIfErrored(t, errs)
 
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, Bp2Build)[dir]
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
 		if actualCount := len(bazelTargets); actualCount != 1 {
 			t.Fatalf("%s: Expected 1 bazel target, got %d", testCase.description, actualCount)
 		}
@@ -1214,7 +1214,8 @@
 		_, errs = ctx.ResolveDependencies(config)
 		android.FailIfErrored(t, errs)
 
-		bazelTargets := GenerateBazelTargets(ctx.Context.Context, Bp2Build)[dir]
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
 		if actualCount := len(bazelTargets); actualCount != testCase.expectedCount {
 			t.Fatalf("%s: Expected %d bazel target, got %d", testCase.description, testCase.expectedCount, actualCount)
 		}
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
new file mode 100644
index 0000000..b2d8941
--- /dev/null
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -0,0 +1,222 @@
+// 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 bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"strings"
+	"testing"
+)
+
+const (
+	// See cc/testing.go for more context
+	soongCcLibraryPreamble = `
+cc_defaults {
+	name: "linux_bionic_supported",
+}
+
+toolchain_library {
+	name: "libclang_rt.builtins-x86_64-android",
+	defaults: ["linux_bionic_supported"],
+	vendor_available: true,
+	vendor_ramdisk_available: true,
+	product_available: true,
+	recovery_available: true,
+	native_bridge_supported: true,
+	src: "",
+}
+
+toolchain_library {
+	name: "libatomic",
+	defaults: ["linux_bionic_supported"],
+	vendor_available: true,
+	vendor_ramdisk_available: true,
+	product_available: true,
+	recovery_available: true,
+	native_bridge_supported: true,
+	src: "",
+}`
+)
+
+func TestCcLibraryHeadersLoadStatement(t *testing.T) {
+	testCases := []struct {
+		bazelTargets           BazelTargets
+		expectedLoadStatements string
+	}{
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:      "cc_library_headers_target",
+					ruleClass: "cc_library_headers",
+					// Note: no bzlLoadLocation for native rules
+				},
+			},
+			expectedLoadStatements: ``,
+		},
+	}
+
+	for _, testCase := range testCases {
+		actual := testCase.bazelTargets.LoadStatements()
+		expected := testCase.expectedLoadStatements
+		if actual != expected {
+			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
+		}
+	}
+
+}
+
+func TestCcLibraryHeadersBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		preArchMutators                    []android.RegisterMutatorFunc
+		depsMutators                       []android.RegisterMutatorFunc
+		bp                                 string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+		dir                                string
+	}{
+		{
+			description:                        "cc_library_headers test",
+			moduleTypeUnderTest:                "cc_library_headers",
+			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+			filesystem: map[string]string{
+				"lib-1/lib1a.h": "",
+				"lib-1/lib1b.h": "",
+				"lib-2/lib2a.h": "",
+				"lib-2/lib2b.h": "",
+				"dir-1/dir1a.h": "",
+				"dir-1/dir1b.h": "",
+				"dir-2/dir2a.h": "",
+				"dir-2/dir2b.h": "",
+			},
+			bp: soongCcLibraryPreamble + `
+cc_library_headers {
+    name: "lib-1",
+    export_include_dirs: ["lib-1"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_headers {
+    name: "lib-2",
+    export_include_dirs: ["lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_headers {
+    name: "foo_headers",
+    export_include_dirs: ["dir-1", "dir-2"],
+    header_libs: ["lib-1", "lib-2"],
+    export_header_lib_headers: ["lib-1", "lib-2"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`cc_library_headers(
+    name = "foo_headers",
+    deps = [
+        ":lib-1",
+        ":lib-2",
+    ],
+    hdrs = [
+        "dir-1/dir1a.h",
+        "dir-1/dir1b.h",
+        "dir-2/dir2a.h",
+        "dir-2/dir2b.h",
+    ],
+    includes = [
+        "dir-1",
+        "dir-2",
+    ],
+)`, `cc_library_headers(
+    name = "lib-1",
+    hdrs = [
+        "lib-1/lib1a.h",
+        "lib-1/lib1b.h",
+    ],
+    includes = [
+        "lib-1",
+    ],
+)`, `cc_library_headers(
+    name = "lib-2",
+    hdrs = [
+        "lib-2/lib2a.h",
+        "lib-2/lib2b.h",
+    ],
+    includes = [
+        "lib-2",
+    ],
+)`},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
+		ctx := android.NewTestContext(config)
+
+		cc.RegisterCCBuildComponents(ctx)
+		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		for _, m := range testCase.depsMutators {
+			ctx.DepsBp2BuildMutators(m)
+		}
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		checkDir := dir
+		if testCase.dir != "" {
+			checkDir = testCase.dir
+		}
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, checkDir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
new file mode 100644
index 0000000..816edb8
--- /dev/null
+++ b/bp2build/cc_object_conversion_test.go
@@ -0,0 +1,187 @@
+// 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 bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestCcObjectBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		blueprint                          string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+	}{
+		{
+			description:                        "simple cc_object generates cc_object with include header dep",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			filesystem: map[string]string{
+				"a/b/foo.h": "",
+				"a/b/bar.h": "",
+				"a/b/c.c":   "",
+			},
+			blueprint: `cc_object {
+    name: "foo",
+    local_include_dirs: ["include"],
+    cflags: [
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+    ],
+    srcs: [
+        "a/b/*.h",
+        "a/b/c.c"
+    ],
+
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{`cc_object(
+    name = "foo",
+    copts = [
+        "-fno-addrsig",
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+    ],
+    local_include_dirs = [
+        "include",
+    ],
+    srcs = [
+        "a/b/bar.h",
+        "a/b/foo.h",
+        "a/b/c.c",
+    ],
+)`,
+			},
+		},
+		{
+			description:                        "simple cc_object with defaults",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			blueprint: `cc_object {
+    name: "foo",
+    local_include_dirs: ["include"],
+    srcs: [
+        "a/b/*.h",
+        "a/b/c.c"
+    ],
+
+    defaults: ["foo_defaults"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_defaults {
+    name: "foo_defaults",
+    defaults: ["foo_bar_defaults"],
+	// TODO(b/178130668): handle configurable attributes that depend on the platform
+    arch: {
+        x86: {
+            cflags: ["-fPIC"],
+        },
+        x86_64: {
+            cflags: ["-fPIC"],
+        },
+    },
+}
+
+cc_defaults {
+    name: "foo_bar_defaults",
+    cflags: [
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+    ],
+}
+`,
+			expectedBazelTargets: []string{`cc_object(
+    name = "foo",
+    copts = [
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+        "-fno-addrsig",
+    ],
+    local_include_dirs = [
+        "include",
+    ],
+    srcs = [
+        "a/b/c.c",
+    ],
+)`,
+			},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
+		ctx := android.NewTestContext(config)
+		// Always register cc_defaults module factory
+		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, dir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			fmt.Println(bazelTargets)
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
new file mode 100644
index 0000000..916129f
--- /dev/null
+++ b/bp2build/metrics.go
@@ -0,0 +1,30 @@
+package bp2build
+
+import (
+	"android/soong/android"
+	"fmt"
+)
+
+// Simple metrics struct to collect information about a Blueprint to BUILD
+// conversion process.
+type CodegenMetrics struct {
+	// Total number of Soong/Blueprint modules
+	TotalModuleCount int
+
+	// Counts of generated Bazel targets per Bazel rule class
+	RuleClassCount map[string]int
+}
+
+// Print the codegen metrics to stdout.
+func (metrics CodegenMetrics) Print() {
+	generatedTargetCount := 0
+	for _, ruleClass := range android.SortedStringKeys(metrics.RuleClassCount) {
+		count := metrics.RuleClassCount[ruleClass]
+		fmt.Printf("[bp2build] %s: %d targets\n", ruleClass, count)
+		generatedTargetCount += count
+	}
+	fmt.Printf(
+		"[bp2build] Generated %d total BUILD targets from %d Android.bp modules.\n",
+		generatedTargetCount,
+		metrics.TotalModuleCount)
+}
diff --git a/bp2build/sh_conversion_test.go b/bp2build/sh_conversion_test.go
new file mode 100644
index 0000000..3e15dfc
--- /dev/null
+++ b/bp2build/sh_conversion_test.go
@@ -0,0 +1,135 @@
+// 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 bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/sh"
+	"strings"
+	"testing"
+)
+
+func TestShBinaryLoadStatement(t *testing.T) {
+	testCases := []struct {
+		bazelTargets           BazelTargets
+		expectedLoadStatements string
+	}{
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:      "sh_binary_target",
+					ruleClass: "sh_binary",
+					// Note: no bzlLoadLocation for native rules
+					// TODO(ruperts): Could open source the existing, experimental Starlark sh_ rules?
+				},
+			},
+			expectedLoadStatements: ``,
+		},
+	}
+
+	for _, testCase := range testCases {
+		actual := testCase.bazelTargets.LoadStatements()
+		expected := testCase.expectedLoadStatements
+		if actual != expected {
+			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
+		}
+	}
+
+}
+
+func TestShBinaryBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		preArchMutators                    []android.RegisterMutatorFunc
+		depsMutators                       []android.RegisterMutatorFunc
+		bp                                 string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+		dir                                string
+	}{
+		{
+			description:                        "sh_binary test",
+			moduleTypeUnderTest:                "sh_binary",
+			moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
+			moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
+			bp: `sh_binary {
+    name: "foo",
+    src: "foo.sh",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`sh_binary(
+    name = "foo",
+    srcs = [
+        "foo.sh",
+    ],
+)`},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
+		ctx := android.NewTestContext(config)
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		for _, m := range testCase.depsMutators {
+			ctx.DepsBp2BuildMutators(m)
+		}
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		checkDir := dir
+		if testCase.dir != "" {
+			checkDir = testCase.dir
+		}
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := GenerateBazelTargetsForDir(codegenCtx, checkDir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/cc/cc.go b/cc/cc.go
index 7f59158..6c1469f 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -28,6 +28,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/cc/config"
 	"android/soong/genrule"
 )
@@ -364,6 +365,8 @@
 	// can depend on libraries that are not exported by the APEXes and use private symbols
 	// from the exported libraries.
 	Test_for []string
+
+	bazel.Properties
 }
 
 type VendorProperties struct {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index b1c1b2e..942e397 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -20,6 +20,7 @@
 	"os"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -3910,3 +3911,294 @@
 	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_nodiag", variant), Async)
 	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_diag", variant), Sync)
 }
+
+func TestIncludeDirsExporting(t *testing.T) {
+
+	// Trim spaces from the beginning, end and immediately after any newline characters. Leaves
+	// embedded newline characters alone.
+	trimIndentingSpaces := func(s string) string {
+		return strings.TrimSpace(regexp.MustCompile("(^|\n)\\s+").ReplaceAllString(s, "$1"))
+	}
+
+	checkPaths := func(t *testing.T, message string, expected string, paths android.Paths) {
+		t.Helper()
+		expected = trimIndentingSpaces(expected)
+		actual := trimIndentingSpaces(strings.Join(android.FirstUniqueStrings(android.NormalizePathsForTesting(paths)), "\n"))
+		if expected != actual {
+			t.Errorf("%s: expected:\n%s\n actual:\n%s\n", message, expected, actual)
+		}
+	}
+
+	type exportedChecker func(t *testing.T, name string, exported FlagExporterInfo)
+
+	checkIncludeDirs := func(t *testing.T, ctx *android.TestContext, module android.Module, checkers ...exportedChecker) {
+		t.Helper()
+		exported := ctx.ModuleProvider(module, FlagExporterInfoProvider).(FlagExporterInfo)
+		name := module.Name()
+
+		for _, checker := range checkers {
+			checker(t, name, exported)
+		}
+	}
+
+	expectedIncludeDirs := func(expectedPaths string) exportedChecker {
+		return func(t *testing.T, name string, exported FlagExporterInfo) {
+			t.Helper()
+			checkPaths(t, fmt.Sprintf("%s: include dirs", name), expectedPaths, exported.IncludeDirs)
+		}
+	}
+
+	expectedSystemIncludeDirs := func(expectedPaths string) exportedChecker {
+		return func(t *testing.T, name string, exported FlagExporterInfo) {
+			t.Helper()
+			checkPaths(t, fmt.Sprintf("%s: system include dirs", name), expectedPaths, exported.SystemIncludeDirs)
+		}
+	}
+
+	expectedGeneratedHeaders := func(expectedPaths string) exportedChecker {
+		return func(t *testing.T, name string, exported FlagExporterInfo) {
+			t.Helper()
+			checkPaths(t, fmt.Sprintf("%s: generated headers", name), expectedPaths, exported.GeneratedHeaders)
+		}
+	}
+
+	expectedOrderOnlyDeps := func(expectedPaths string) exportedChecker {
+		return func(t *testing.T, name string, exported FlagExporterInfo) {
+			t.Helper()
+			checkPaths(t, fmt.Sprintf("%s: order only deps", name), expectedPaths, exported.Deps)
+		}
+	}
+
+	genRuleModules := `
+		genrule {
+			name: "genrule_foo",
+			cmd: "generate-foo",
+			out: [
+				"generated_headers/foo/generated_header.h",
+			],
+			export_include_dirs: [
+				"generated_headers",
+			],
+		}
+
+		genrule {
+			name: "genrule_bar",
+			cmd: "generate-bar",
+			out: [
+				"generated_headers/bar/generated_header.h",
+			],
+			export_include_dirs: [
+				"generated_headers",
+			],
+		}
+	`
+
+	t.Run("ensure exported include dirs are not automatically re-exported from shared_libs", func(t *testing.T) {
+		ctx := testCc(t, genRuleModules+`
+		cc_library {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			export_include_dirs: ["foo/standard"],
+			export_system_include_dirs: ["foo/system"],
+			generated_headers: ["genrule_foo"],
+			export_generated_headers: ["genrule_foo"],
+		}
+
+		cc_library {
+			name: "libbar",
+			srcs: ["bar.c"],
+			shared_libs: ["libfoo"],
+			export_include_dirs: ["bar/standard"],
+			export_system_include_dirs: ["bar/system"],
+			generated_headers: ["genrule_bar"],
+			export_generated_headers: ["genrule_bar"],
+		}
+		`)
+		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, foo,
+			expectedIncludeDirs(`
+				foo/standard
+				.intermediates/genrule_foo/gen/generated_headers
+			`),
+			expectedSystemIncludeDirs(`foo/system`),
+			expectedGeneratedHeaders(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
+			expectedOrderOnlyDeps(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
+		)
+
+		bar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, bar,
+			expectedIncludeDirs(`
+				bar/standard
+				.intermediates/genrule_bar/gen/generated_headers
+			`),
+			expectedSystemIncludeDirs(`bar/system`),
+			expectedGeneratedHeaders(`.intermediates/genrule_bar/gen/generated_headers/bar/generated_header.h`),
+			expectedOrderOnlyDeps(`.intermediates/genrule_bar/gen/generated_headers/bar/generated_header.h`),
+		)
+	})
+
+	t.Run("ensure exported include dirs are automatically re-exported from whole_static_libs", func(t *testing.T) {
+		ctx := testCc(t, genRuleModules+`
+		cc_library {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			export_include_dirs: ["foo/standard"],
+			export_system_include_dirs: ["foo/system"],
+			generated_headers: ["genrule_foo"],
+			export_generated_headers: ["genrule_foo"],
+		}
+
+		cc_library {
+			name: "libbar",
+			srcs: ["bar.c"],
+			whole_static_libs: ["libfoo"],
+			export_include_dirs: ["bar/standard"],
+			export_system_include_dirs: ["bar/system"],
+			generated_headers: ["genrule_bar"],
+			export_generated_headers: ["genrule_bar"],
+		}
+		`)
+		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, foo,
+			expectedIncludeDirs(`
+				foo/standard
+				.intermediates/genrule_foo/gen/generated_headers
+			`),
+			expectedSystemIncludeDirs(`foo/system`),
+			expectedGeneratedHeaders(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
+			expectedOrderOnlyDeps(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
+		)
+
+		bar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, bar,
+			expectedIncludeDirs(`
+				bar/standard
+				foo/standard
+				.intermediates/genrule_foo/gen/generated_headers
+				.intermediates/genrule_bar/gen/generated_headers
+			`),
+			expectedSystemIncludeDirs(`
+				bar/system
+				foo/system
+			`),
+			expectedGeneratedHeaders(`
+				.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h
+				.intermediates/genrule_bar/gen/generated_headers/bar/generated_header.h
+			`),
+			expectedOrderOnlyDeps(`
+				.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h
+				.intermediates/genrule_bar/gen/generated_headers/bar/generated_header.h
+			`),
+		)
+	})
+
+	// TODO: fix this test as it exports all generated headers.
+	t.Run("ensure only aidl headers are exported", func(t *testing.T) {
+		ctx := testCc(t, genRuleModules+`
+		cc_library_shared {
+			name: "libfoo",
+			srcs: [
+				"foo.c",
+				"b.aidl",
+				"a.proto",
+			],
+			aidl: {
+				export_aidl_headers: true,
+			}
+		}
+		`)
+		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, foo,
+			expectedIncludeDirs(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl
+			`),
+			expectedSystemIncludeDirs(``),
+			expectedGeneratedHeaders(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+			expectedOrderOnlyDeps(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+		)
+	})
+
+	// TODO: fix this test as it exports all generated headers.
+	t.Run("ensure only proto headers are exported", func(t *testing.T) {
+		ctx := testCc(t, genRuleModules+`
+		cc_library_shared {
+			name: "libfoo",
+			srcs: [
+				"foo.c",
+				"b.aidl",
+				"a.proto",
+			],
+			proto: {
+				export_proto_headers: true,
+			}
+		}
+		`)
+		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, foo,
+			expectedIncludeDirs(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto
+			`),
+			expectedSystemIncludeDirs(``),
+			expectedGeneratedHeaders(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+			expectedOrderOnlyDeps(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+		)
+	})
+
+	// TODO: fix this test as it exports all generated headers including public and non-public.
+	t.Run("ensure only non-public sysprop headers are exported", func(t *testing.T) {
+		ctx := testCc(t, genRuleModules+`
+		cc_library_shared {
+			name: "libfoo",
+			srcs: [
+				"foo.c",
+				"a.sysprop",
+				"b.aidl",
+				"a.proto",
+			],
+		}
+		`)
+		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		checkIncludeDirs(t, ctx, foo,
+			expectedIncludeDirs(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/include
+			`),
+			expectedSystemIncludeDirs(``),
+			expectedGeneratedHeaders(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/include/a.sysprop.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/public/include/a.sysprop.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+			expectedOrderOnlyDeps(`
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/include/a.sysprop.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/public/include/a.sysprop.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/b.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bnb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl/Bpb.h
+				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto/a.pb.h
+			`),
+		)
+	})
+}
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index e6024aa..d083d2a 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -31,6 +31,10 @@
 		"armv8-a": []string{
 			"-march=armv8-a",
 		},
+		"armv8-a-branchprot": []string{
+			"-march=armv8-a",
+			"-mbranch-protection=standard",
+		},
 		"armv8-2a": []string{
 			"-march=armv8.2-a",
 		},
@@ -102,6 +106,7 @@
 	pctx.StaticVariable("Arm64ClangCppflags", strings.Join(ClangFilterUnknownCflags(arm64Cppflags), " "))
 
 	pctx.StaticVariable("Arm64ClangArmv8ACflags", strings.Join(arm64ArchVariantCflags["armv8-a"], " "))
+	pctx.StaticVariable("Arm64ClangArmv8ABranchProtCflags", strings.Join(arm64ArchVariantCflags["armv8-a-branchprot"], " "))
 	pctx.StaticVariable("Arm64ClangArmv82ACflags", strings.Join(arm64ArchVariantCflags["armv8-2a"], " "))
 	pctx.StaticVariable("Arm64ClangArmv82ADotprodCflags", strings.Join(arm64ArchVariantCflags["armv8-2a-dotprod"], " "))
 
@@ -124,6 +129,7 @@
 var (
 	arm64ClangArchVariantCflagsVar = map[string]string{
 		"armv8-a":  "${config.Arm64ClangArmv8ACflags}",
+		"armv8-a-branchprot": "${config.Arm64ClangArmv8ABranchProtCflags}",
 		"armv8-2a": "${config.Arm64ClangArmv82ACflags}",
 		"armv8-2a-dotprod": "${config.Arm64ClangArmv82ADotprodCflags}",
 	}
@@ -202,6 +208,7 @@
 func arm64ToolchainFactory(arch android.Arch) Toolchain {
 	switch arch.ArchVariant {
 	case "armv8-a":
+	case "armv8-a-branchprot":
 	case "armv8-2a":
 	case "armv8-2a-dotprod":
 		// Nothing extra for armv8-a/armv8-2a
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 71c7626..5e46d5a 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -150,7 +150,7 @@
 		"-Wunguarded-availability",
 		// This macro allows the bionic versioning.h to indirectly determine whether the
 		// option -Wunguarded-availability is on or not.
-		"-D__ANDROID_UNGUARDED_AVAILABILITY__",
+		"-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__",
 	}, " "))
 
 	pctx.StaticVariable("ClangExtraCppflags", strings.Join([]string{
diff --git a/cc/config/global.go b/cc/config/global.go
index ee41125..e60bb3d 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -144,8 +144,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r407598b"
-	ClangDefaultShortVersion = "12.0.2"
+	ClangDefaultVersion      = "clang-r412851"
+	ClangDefaultShortVersion = "12.0.3"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index 7cc9f43..7c20dd5 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -87,27 +87,11 @@
 		}, ",")
 	})
 
-	// Give warnings to header files only in selected directories.
-	// Do not give warnings to external or vendor header files, which contain too
-	// many warnings.
+	// To reduce duplicate warnings from the same header files,
+	// header-filter will contain only the module directory and
+	// those specified by DEFAULT_TIDY_HEADER_DIRS.
 	pctx.VariableFunc("TidyDefaultHeaderDirs", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS"); override != "" {
-			return override
-		}
-		return strings.Join([]string{
-			"art/",
-			"bionic/",
-			"bootable/",
-			"build/",
-			"cts/",
-			"dalvik/",
-			"developers/",
-			"development/",
-			"frameworks/",
-			"libcore/",
-			"libnativehelper/",
-			"system/",
-		}, "|")
+		return ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS")
 	})
 
 	// Use WTIH_TIDY_FLAGS to pass extra global default clang-tidy flags.
diff --git a/cc/fuzz.go b/cc/fuzz.go
index d7da5ab..9fe5b17 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -20,6 +20,8 @@
 	"sort"
 	"strings"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/cc/config"
 )
@@ -172,7 +174,7 @@
 // This function takes a module and determines if it is a unique shared library
 // that should be installed in the fuzz target output directories. This function
 // returns true, unless:
-//  - The module is not a shared library, or
+//  - The module is not an installable shared library, or
 //  - The module is a header, stub, or vendor-linked library, or
 //  - The module is a prebuilt and its source is available, or
 //  - The module is a versioned member of an SDK snapshot.
@@ -209,6 +211,11 @@
 		if _, isLLndkStubLibrary := ccLibrary.linker.(*stubDecorator); isLLndkStubLibrary {
 			return false
 		}
+		// Discard installable:false libraries because they are expected to be absent
+		// in runtime.
+		if !proptools.BoolDefault(ccLibrary.Properties.Installable, true) {
+			return false
+		}
 	}
 
 	// If the same library is present both as source and a prebuilt we must pick
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 8b3dbeb..e5a5557 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -14,13 +14,18 @@
 
 package cc
 
-import "android/soong/android"
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+)
 
 func init() {
 	RegisterLibraryHeadersBuildComponents(android.InitRegistrationContext)
 
 	// Register sdk member types.
 	android.RegisterSdkMemberType(headersLibrarySdkMemberType)
+
+	android.RegisterBp2BuildMutator("cc_library_headers", CcLibraryHeadersBp2Build)
 }
 
 var headersLibrarySdkMemberType = &librarySdkMemberType{
@@ -55,3 +60,86 @@
 	library.HeaderOnly()
 	return module.Init()
 }
+
+type bazelCcLibraryHeadersAttributes struct {
+	Hdrs     bazel.LabelList
+	Includes bazel.LabelList
+	Deps     bazel.LabelList
+}
+
+type bazelCcLibraryHeaders struct {
+	android.BazelTargetModuleBase
+	bazelCcLibraryHeadersAttributes
+}
+
+func BazelCcLibraryHeadersFactory() android.Module {
+	module := &bazelCcLibraryHeaders{}
+	module.AddProperties(&module.bazelCcLibraryHeadersAttributes)
+	android.InitBazelTargetModule(module)
+	return module
+}
+
+func CcLibraryHeadersBp2Build(ctx android.TopDownMutatorContext) {
+	module, ok := ctx.Module().(*Module)
+	if !ok {
+		// Not a cc module
+		return
+	}
+
+	if !module.Properties.Bazel_module.Bp2build_available {
+		return
+	}
+
+	lib, ok := module.linker.(*libraryDecorator)
+	if !ok {
+		// Not a cc_library module
+		return
+	}
+	if !lib.header() {
+		// Not a cc_library_headers module
+		return
+	}
+
+	// list of directories that will be added to the include path (using -I) for this
+	// module and any module that links against this module.
+	includeDirs := lib.flagExporter.Properties.Export_system_include_dirs
+	includeDirs = append(includeDirs, lib.flagExporter.Properties.Export_include_dirs...)
+	includeDirLabels := android.BazelLabelForModuleSrc(ctx, includeDirs)
+
+	var includeDirGlobs []string
+	for _, includeDir := range includeDirs {
+		includeDirGlobs = append(includeDirGlobs, includeDir+"/**/*.h")
+	}
+
+	headerLabels := android.BazelLabelForModuleSrc(ctx, includeDirGlobs)
+
+	// list of modules that should only provide headers for this module.
+	var headerLibs []string
+	for _, linkerProps := range lib.linkerProps() {
+		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
+			headerLibs = baseLinkerProps.Export_header_lib_headers
+			break
+		}
+	}
+	headerLibLabels := android.BazelLabelForModuleDeps(ctx, headerLibs)
+
+	attrs := &bazelCcLibraryHeadersAttributes{
+		Includes: includeDirLabels,
+		Hdrs:     headerLabels,
+		Deps:     headerLibLabels,
+	}
+
+	props := bazel.NewBazelTargetModuleProperties(
+		module.Name(),
+		"cc_library_headers",
+		"//build/bazel/rules:cc_library_headers.bzl",
+	)
+
+	ctx.CreateBazelTargetModule(BazelCcLibraryHeadersFactory, props, attrs)
+}
+
+func (m *bazelCcLibraryHeaders) Name() string {
+	return m.BaseModuleName()
+}
+
+func (m *bazelCcLibraryHeaders) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
diff --git a/cc/object.go b/cc/object.go
index 3ce7676..d92e110 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -18,6 +18,7 @@
 	"fmt"
 
 	"android/soong/android"
+	"android/soong/bazel"
 )
 
 //
@@ -27,6 +28,8 @@
 func init() {
 	android.RegisterModuleType("cc_object", ObjectFactory)
 	android.RegisterSdkMemberType(ccObjectSdkMemberType)
+
+	android.RegisterBp2BuildMutator("cc_object", ObjectBp2Build)
 }
 
 var ccObjectSdkMemberType = &librarySdkMemberType{
@@ -82,9 +85,80 @@
 	module.compiler.appendCflags([]string{"-fno-addrsig"})
 
 	module.sdkMemberTypes = []android.SdkMemberType{ccObjectSdkMemberType}
+
 	return module.Init()
 }
 
+// For bp2build conversion.
+type bazelObjectAttributes struct {
+	Srcs               bazel.LabelList
+	Copts              []string
+	Local_include_dirs []string
+}
+
+type bazelObject struct {
+	android.BazelTargetModuleBase
+	bazelObjectAttributes
+}
+
+func (m *bazelObject) Name() string {
+	return m.BaseModuleName()
+}
+
+func (m *bazelObject) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+func BazelObjectFactory() android.Module {
+	module := &bazelObject{}
+	module.AddProperties(&module.bazelObjectAttributes)
+	android.InitBazelTargetModule(module)
+	return module
+}
+
+// ObjectBp2Build is the bp2build converter from cc_object modules to the
+// Bazel equivalent target, plus any necessary include deps for the cc_object.
+func ObjectBp2Build(ctx android.TopDownMutatorContext) {
+	m, ok := ctx.Module().(*Module)
+	if !ok || !m.Properties.Bazel_module.Bp2build_available {
+		return
+	}
+
+	// a Module can be something other than a cc_object.
+	if ctx.ModuleType() != "cc_object" {
+		return
+	}
+
+	if m.compiler == nil {
+		// a cc_object must have access to the compiler decorator for its props.
+		ctx.ModuleErrorf("compiler must not be nil for a cc_object module")
+	}
+
+	var copts []string
+	var srcs []string
+	var localIncludeDirs []string
+	for _, props := range m.compiler.compilerProps() {
+		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+			copts = baseCompilerProps.Cflags
+			srcs = baseCompilerProps.Srcs
+			localIncludeDirs = baseCompilerProps.Local_include_dirs
+			break
+		}
+	}
+
+	attrs := &bazelObjectAttributes{
+		Srcs:               android.BazelLabelForModuleSrc(ctx, srcs),
+		Copts:              copts,
+		Local_include_dirs: localIncludeDirs,
+	}
+
+	props := bazel.NewBazelTargetModuleProperties(
+		m.Name(),
+		"cc_object",
+		"//build/bazel/rules:cc_object.bzl",
+	)
+
+	ctx.CreateBazelTargetModule(BazelObjectFactory, props, attrs)
+}
+
 func (object *objectLinker) appendLdflags(flags []string) {
 	panic(fmt.Errorf("appendLdflags on objectLinker not supported"))
 }
diff --git a/cc/test.go b/cc/test.go
index 17ac534..9b77e45 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -87,14 +87,10 @@
 	// Add RunCommandTargetPreparer to stop framework before the test and start it after the test.
 	Disable_framework *bool
 
-	// Add MinApiLevelModuleController to auto generated test config. If the device property of
-	// "ro.product.first_api_level" < Test_min_api_level, then skip this module.
+	// Add ShippingApiLevelModuleController to auto generated test config. If the device properties
+	// for the shipping api level is less than the test_min_api_level, skip this module.
 	Test_min_api_level *int64
 
-	// Add MinApiLevelModuleController to auto generated test config. If the device property of
-	// "ro.build.version.sdk" < Test_min_sdk_version, then skip this module.
-	Test_min_sdk_version *int64
-
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -232,7 +228,6 @@
 type testDecorator struct {
 	Properties TestProperties
 	linker     *baseLinker
-	hod        android.HostOrDeviceSupported
 }
 
 func (test *testDecorator) gtest() bool {
@@ -376,9 +371,7 @@
 		}
 	})
 
-	var apiLevelProp string
 	var configs []tradefed.Config
-	var minLevel string
 	for _, module := range test.Properties.Test_mainline_modules {
 		configs = append(configs, tradefed.Option{Name: "config-descriptor:metadata", Key: "mainline-param", Value: module})
 	}
@@ -402,20 +395,10 @@
 	for _, tag := range test.Properties.Test_options.Test_suite_tag {
 		configs = append(configs, tradefed.Option{Name: "test-suite-tag", Value: tag})
 	}
-	if test.Properties.Test_min_api_level != nil && test.Properties.Test_min_sdk_version != nil {
-		ctx.PropertyErrorf("test_min_api_level", "'test_min_api_level' and 'test_min_sdk_version' should not be set at the same time.")
-	} else if test.Properties.Test_min_api_level != nil {
-		apiLevelProp = "ro.product.first_api_level"
-		minLevel = strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)
-	} else if test.Properties.Test_min_sdk_version != nil {
-		apiLevelProp = "ro.build.version.sdk"
-		minLevel = strconv.FormatInt(int64(*test.Properties.Test_min_sdk_version), 10)
-	}
-	if apiLevelProp != "" {
+	if test.Properties.Test_min_api_level != nil {
 		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: minLevel})
-		options = append(options, tradefed.Option{Name: "api-level-prop", Value: apiLevelProp})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.MinApiLevelModuleController", options})
+		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)})
+		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
 	}
 
 	test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
@@ -432,8 +415,7 @@
 		ctx.PropertyErrorf("no_named_install_directory", "Module install directory may only be disabled if relative_install_path is set")
 	}
 
-	// TODO(179092189): Clean up to use Ctx.Host() when generalizing to cc_test
-	if test.testDecorator.hod == android.HostSupported && test.gtest() && test.Properties.Test_options.Unit_test == nil {
+	if ctx.Host() && test.gtest() && test.Properties.Test_options.Unit_test == nil {
 		test.Properties.Test_options.Unit_test = proptools.BoolPtr(true)
 	}
 	test.binaryDecorator.baseInstaller.install(ctx, file)
@@ -447,7 +429,6 @@
 	test := &testBinary{
 		testDecorator: testDecorator{
 			linker: binary.baseLinker,
-			hod:    hod,
 		},
 		binaryDecorator: binary,
 		baseCompiler:    NewBaseCompiler(),
diff --git a/cc/tidy.go b/cc/tidy.go
index 972ad7b..251c67b 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -20,6 +20,7 @@
 
 	"github.com/google/blueprint/proptools"
 
+	"android/soong/android"
 	"android/soong/cc/config"
 )
 
@@ -75,9 +76,17 @@
 	}
 	esc := proptools.NinjaAndShellEscapeList
 	flags.TidyFlags = append(flags.TidyFlags, esc(tidy.Properties.Tidy_flags)...)
-	// If TidyFlags is empty, add default header filter.
-	if len(flags.TidyFlags) == 0 {
-		headerFilter := "-header-filter=\"(" + ctx.ModuleDir() + "|${config.TidyDefaultHeaderDirs})\""
+	// If TidyFlags does not contain -header-filter, add default header filter.
+	// Find the substring because the flag could also appear as --header-filter=...
+	// and with or without single or double quotes.
+	if !android.SubstringInList(flags.TidyFlags, "-header-filter=") {
+		defaultDirs := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS")
+		headerFilter := "-header-filter="
+		if defaultDirs == "" {
+			headerFilter += ctx.ModuleDir() + "/"
+		} else {
+			headerFilter += "\"(" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
+		}
 		flags.TidyFlags = append(flags.TidyFlags, headerFilter)
 	}
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index c17e23d..3a6feca 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -134,7 +134,9 @@
 
 	// Convert the Soong module graph into Bazel BUILD files.
 	if bazelQueryViewDir != "" {
-		if err := createBazelQueryView(ctx, bazelQueryViewDir); err != nil {
+		// Run the code-generation phase to convert BazelTargetModules to BUILD files.
+		codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
+		if err := createBazelQueryView(codegenContext, bazelQueryViewDir); err != nil {
 			fmt.Fprintf(os.Stderr, "%s", err)
 			os.Exit(1)
 		}
@@ -188,9 +190,15 @@
 	// from the regular Modules.
 	bootstrap.Main(bp2buildCtx.Context, configuration, extraNinjaDeps...)
 
-	// Run the code-generation phase to convert BazelTargetModules to BUILD files.
+	// 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)
-	bp2build.Codegen(codegenContext)
+	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()
 
 	// Workarounds to support running bp2build in a clean AOSP checkout with no
 	// prior builds, and exiting early as soon as the BUILD files get generated,
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index dc0b323..0a77d67 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -22,9 +22,12 @@
 	"path/filepath"
 )
 
-func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error {
+func createBazelQueryView(ctx bp2build.CodegenContext, bazelQueryViewDir string) error {
 	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
-	buildToTargets := bp2build.GenerateBazelTargets(*ctx, bp2build.QueryView)
+
+	// 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)
 
 	filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets, bp2build.QueryView)
 	for _, f := range filesToWrite {
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 850c8f9..e15324b 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -28,6 +28,8 @@
 // various `prebuilt_*` mutators.
 
 import (
+	"fmt"
+
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -208,6 +210,17 @@
 	return p.outputFilePath
 }
 
+var _ android.OutputFileProducer = (*PrebuiltEtc)(nil)
+
+func (p *PrebuiltEtc) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{p.outputFilePath}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
 func (p *PrebuiltEtc) SubDir() string {
 	if subDir := proptools.String(p.properties.Sub_dir); subDir != "" {
 		return subDir
@@ -269,11 +282,14 @@
 		Input:  p.sourceFilePath,
 	})
 
-	if p.Installable() {
-		installPath := ctx.InstallFile(p.installDirPath, p.outputFilePath.Base(), p.outputFilePath)
-		for _, sl := range p.properties.Symlinks {
-			ctx.InstallSymlink(p.installDirPath, sl, installPath)
-		}
+	if !p.Installable() {
+		p.SkipInstall()
+	}
+
+	// Call InstallFile even when uninstallable to make the module included in the package
+	installPath := ctx.InstallFile(p.installDirPath, p.outputFilePath.Base(), p.outputFilePath)
+	for _, sl := range p.properties.Symlinks {
+		ctx.InstallSymlink(p.installDirPath, sl, installPath)
 	}
 }
 
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 4bc1823..c90929a 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -38,6 +38,9 @@
 }
 
 type bootimgProperties struct {
+	// Set the name of the output. Defaults to <module_name>.img.
+	Stem *string
+
 	// Path to the linux kernel prebuilt file
 	Kernel_prebuilt *string `android:"arch_variant,path"`
 
@@ -96,7 +99,7 @@
 }
 
 func (b *bootimg) installFileName() string {
-	return b.BaseModuleName() + ".img"
+	return proptools.StringDefault(b.properties.Stem, b.BaseModuleName()+".img")
 }
 
 func (b *bootimg) partitionName() string {
@@ -110,6 +113,7 @@
 	} else {
 		// TODO(jiyong): fix this
 		ctx.PropertyErrorf("vendor_boot", "only vendor_boot:true is supported")
+		return
 	}
 
 	if proptools.Bool(b.properties.Use_avb) {
@@ -123,7 +127,8 @@
 }
 
 func (b *bootimg) buildVendorBootImage(ctx android.ModuleContext) android.OutputPath {
-	output := android.PathForModuleOut(ctx, "unsigned.img").OutputPath
+	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName()).OutputPath
+
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("mkbootimg")
 
@@ -182,21 +187,20 @@
 }
 
 func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.OutputPath) android.OutputPath {
-	signedImage := android.PathForModuleOut(ctx, "signed.img").OutputPath
+	output := android.PathForModuleOut(ctx, b.installFileName()).OutputPath
 	key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
 
 	builder := android.NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("cp").Input(unsignedImage).Output(signedImage)
+	builder.Command().Text("cp").Input(unsignedImage).Output(output)
 	builder.Command().
 		BuiltTool("avbtool").
 		Flag("add_hash_footer").
 		FlagWithArg("--partition_name ", b.partitionName()).
 		FlagWithInput("--key ", key).
-		FlagWithOutput("--image ", signedImage)
+		FlagWithOutput("--image ", output)
 
 	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
-
-	return signedImage
+	return output
 }
 
 var _ android.AndroidMkEntriesProvider = (*bootimg)(nil)
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 7658154..3bccde9 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"path/filepath"
+	"strings"
 
 	"android/soong/android"
 
@@ -37,6 +39,11 @@
 	installDir android.InstallPath
 }
 
+type symlinkDefinition struct {
+	Target *string
+	Name   *string
+}
+
 type filesystemProperties struct {
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
@@ -54,6 +61,16 @@
 
 	// file_contexts file to make image. Currently, only ext4 is supported.
 	File_contexts *string `android:"path"`
+
+	// Base directory relative to root, to which deps are installed, e.g. "system". Default is "."
+	// (root).
+	Base_dir *string
+
+	// Directories to be created under root. e.g. /dev, /proc, etc.
+	Dirs []string
+
+	// Symbolic links to be created under root with "ln -sf <target> <name>".
+	Symlinks []symlinkDefinition
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -124,16 +141,75 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 }
 
+// root zip will contain stuffs like dirs or symlinks.
+func (f *filesystem) buildRootZip(ctx android.ModuleContext) android.OutputPath {
+	rootDir := android.PathForModuleGen(ctx, "root").OutputPath
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("rm -rf").Text(rootDir.String())
+	builder.Command().Text("mkdir -p").Text(rootDir.String())
+
+	// create dirs and symlinks
+	for _, dir := range f.properties.Dirs {
+		// OutputPath.Join verifies dir
+		builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, dir).String())
+	}
+
+	for _, symlink := range f.properties.Symlinks {
+		name := strings.TrimSpace(proptools.String(symlink.Name))
+		target := strings.TrimSpace(proptools.String(symlink.Target))
+
+		if name == "" {
+			ctx.PropertyErrorf("symlinks", "Name can't be empty")
+			continue
+		}
+
+		if target == "" {
+			ctx.PropertyErrorf("symlinks", "Target can't be empty")
+			continue
+		}
+
+		// OutputPath.Join verifies name. don't need to verify target.
+		dst := rootDir.Join(ctx, name)
+
+		builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String()))
+		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
+	}
+
+	zipOut := android.PathForModuleGen(ctx, "root.zip").OutputPath
+
+	builder.Command().
+		BuiltTool("soong_zip").
+		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
+	builder.Command().Text("rm -rf").Text(rootDir.String())
+
+	builder.Build("zip_root", fmt.Sprintf("zipping root contents for %s", ctx.ModuleName()))
+	return zipOut
+}
+
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
-	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
-	f.CopyDepsToZip(ctx, zipFile)
+	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
+	f.CopyDepsToZip(ctx, depsZipFile)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
+	rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath
+	builder.Command().
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", depsZipFile).
+		FlagWithOutput("-o ", rebasedDepsZip).
+		Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase
 
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	builder := android.NewRuleBuilder(pctx, ctx)
+	rootZip := f.buildRootZip(ctx)
 	builder.Command().
 		BuiltTool("zipsync").
 		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
-		Input(zipFile)
+		Input(rootZip).
+		Input(rebasedDepsZip)
 
 	propFile, toolDeps := f.buildPropFile(ctx)
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
@@ -187,7 +263,7 @@
 	}
 
 	addStr("fs_type", fsTypeStr(f.fsType(ctx)))
-	addStr("mount_point", "system")
+	addStr("mount_point", "/")
 	addStr("use_dynamic_partition_size", "true")
 	addPath("ext_mkuserimg", ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs"))
 	// b/177813163 deps of the host tools have to be added. Remove this.
@@ -233,15 +309,25 @@
 		ctx.PropertyErrorf("file_contexts", "file_contexts is not supported for compressed cpio image.")
 	}
 
-	zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath
-	f.CopyDepsToZip(ctx, zipFile)
+	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
+	f.CopyDepsToZip(ctx, depsZipFile)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
+	rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath
+	builder.Command().
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", depsZipFile).
+		FlagWithOutput("-o ", rebasedDepsZip).
+		Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase
 
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	builder := android.NewRuleBuilder(pctx, ctx)
+	rootZip := f.buildRootZip(ctx)
 	builder.Command().
 		BuiltTool("zipsync").
 		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
-		Input(zipFile)
+		Input(rootZip).
+		Input(rebasedDepsZip)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	cmd := builder.Command().
diff --git a/java/aar.go b/java/aar.go
index ac7ae25..602d2c4 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -160,8 +160,8 @@
 func (a *aapt) IsRROEnforced(ctx android.BaseModuleContext) bool {
 	// True if RRO is enforced for this module or...
 	return ctx.Config().EnforceRROForModule(ctx.ModuleName()) ||
-		// if RRO is enforced for any of its dependents, and this module is not exempted.
-		(a.aaptProperties.RROEnforcedForDependent && !ctx.Config().EnforceRROExemptedForModule(ctx.ModuleName()))
+		// if RRO is enforced for any of its dependents.
+		a.aaptProperties.RROEnforcedForDependent
 }
 
 func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext,
@@ -443,16 +443,14 @@
 					assets = append(assets, aarDep.ExportedAssets().Path())
 				}
 
-				if !ctx.Config().EnforceRROExemptedForModule(ctx.ModuleName()) {
-				outer:
-					for _, d := range aarDep.ExportedRRODirs() {
-						for _, e := range staticRRODirs {
-							if d.path == e.path {
-								continue outer
-							}
+			outer:
+				for _, d := range aarDep.ExportedRRODirs() {
+					for _, e := range staticRRODirs {
+						if d.path == e.path {
+							continue outer
 						}
-						staticRRODirs = append(staticRRODirs, d)
 					}
+					staticRRODirs = append(staticRRODirs, d)
 				}
 			}
 		}
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index c315124..282e936 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -82,10 +82,6 @@
 		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
 		artModules := global.ArtApexJars
-		// With EMMA_INSTRUMENT_FRAMEWORK=true the Core libraries depend on jacoco.
-		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-			artModules = artModules.Append("com.android.art", "jacocoagent")
-		}
 		frameworkModules := global.BootJars.RemoveList(artModules)
 
 		artSubdir := "apex/art_boot_images/javalib"
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 1651c1c..f8e41c4 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -221,13 +221,19 @@
 		return
 	}
 
+	classesJars := android.Paths{classesJar}
+	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
+		javaInfo := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
+		classesJars = append(classesJars, javaInfo.ImplementationJars...)
+	})
+
 	stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags
 
 	flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
 		Description: "hiddenapi flags",
-		Input:       classesJar,
+		Inputs:      classesJars,
 		Output:      flagsCSV,
 		Implicit:    stubFlagsCSV,
 		Args: map[string]string{
@@ -241,7 +247,7 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
 		Description: "hiddenapi metadata",
-		Input:       classesJar,
+		Inputs:      classesJars,
 		Output:      metadataCSV,
 		Implicit:    stubFlagsCSV,
 		Args: map[string]string{
@@ -255,8 +261,9 @@
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		BuiltTool("merge_csv").
-		FlagWithInput("--zip_input=", classesJar).
-		FlagWithOutput("--output=", indexCSV)
+		Flag("--zip_input").
+		FlagWithOutput("--output=", indexCSV).
+		Inputs(classesJars)
 	rule.Build("merged-hiddenapi-index", "Merged Hidden API index")
 	h.indexCSVPath = indexCSV
 
@@ -335,3 +342,16 @@
 		TransformZipAlign(ctx, output, tmpOutput)
 	}
 }
+
+type hiddenApiAnnotationsDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+// Tag used to mark dependencies on java_library instances that contains Java source files whose
+// sole purpose is to provide additional hiddenapi annotations.
+var hiddenApiAnnotationsTag hiddenApiAnnotationsDependencyTag
+
+// Mark this tag so dependencies that use it are excluded from APEX contents.
+func (t hiddenApiAnnotationsDependencyTag) ExcludeFromApexContents() {}
+
+var _ android.ExcludeFromApexContentsTag = hiddenApiAnnotationsTag
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index df825bb..4670d03 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -82,6 +82,10 @@
 			name: "foo",
 			srcs: ["a.java"],
 			compile_dex: true,
+
+			hiddenapi_additional_annotations: [
+				"foo-hiddenapi-annotations",
+			],
 		}
 
 		java_library {
@@ -90,6 +94,12 @@
 			compile_dex: true,
 		}
 
+		java_library {
+			name: "foo-hiddenapi-annotations",
+			srcs: ["a.java"],
+			compile_dex: true,
+		}
+
 		java_import {
 			name: "foo",
 			jars: ["a.jar"],
@@ -112,6 +122,15 @@
 .intermediates/foo/android_common/hiddenapi/index.csv
 `,
 		indexRule)
+
+	// Make sure that the foo-hiddenapi-annotations.jar is included in the inputs to the rules that
+	// creates the index.csv file.
+	foo := ctx.ModuleForTests("foo", "android_common")
+	indexParams := foo.Output("hiddenapi/index.csv")
+	CheckHiddenAPIRuleInputs(t, `
+.intermediates/foo-hiddenapi-annotations/android_common/javac/foo-hiddenapi-annotations.jar
+.intermediates/foo/android_common/javac/foo.jar
+`, indexParams)
 }
 
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
diff --git a/java/java.go b/java/java.go
index 338140b..dbfad02 100644
--- a/java/java.go
+++ b/java/java.go
@@ -298,6 +298,9 @@
 
 	// If true, package the kotlin stdlib into the jar.  Defaults to true.
 	Static_kotlin_stdlib *bool `android:"arch_variant"`
+
+	// A list of java_library instances that provide additional hiddenapi annotations for the library.
+	Hiddenapi_additional_annotations []string
 }
 
 type CompilerDeviceProperties struct {
@@ -840,6 +843,9 @@
 	libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...)
 	ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...)
 
+	// Add dependency on libraries that provide additional hidden api annotations.
+	ctx.AddVariationDependencies(nil, hiddenApiAnnotationsTag, j.properties.Hiddenapi_additional_annotations...)
+
 	if ctx.DeviceConfig().VndkVersion() != "" && ctx.Config().EnforceInterPartitionJavaSdkLibrary() {
 		// Require java_sdk_library at inter-partition java dependency to ensure stable
 		// interface between partitions. If inter-partition java_library dependency is detected,
@@ -1008,6 +1014,7 @@
 const (
 	// TODO(jiyong) rename these for better readability. Make the allowed
 	// and disallowed link types explicit
+	// order is important here. See rank()
 	javaCore linkType = iota
 	javaSdk
 	javaSystem
@@ -1016,6 +1023,31 @@
 	javaPlatform
 )
 
+func (lt linkType) String() string {
+	switch lt {
+	case javaCore:
+		return "core Java API"
+	case javaSdk:
+		return "Android API"
+	case javaSystem:
+		return "system API"
+	case javaModule:
+		return "module API"
+	case javaSystemServer:
+		return "system server API"
+	case javaPlatform:
+		return "private API"
+	default:
+		panic(fmt.Errorf("unrecognized linktype: %v", lt))
+	}
+}
+
+// rank determins the total order among linkTypes. A link type of rank A can link to another link
+// type of rank B only when B <= A
+func (lt linkType) rank() int {
+	return int(lt)
+}
+
 type linkTypeContext interface {
 	android.Module
 	getLinkType(name string) (ret linkType, stubs bool)
@@ -1075,44 +1107,13 @@
 		return
 	}
 	otherLinkType, _ := to.getLinkType(ctx.OtherModuleName(to))
-	commonMessage := " In order to fix this, consider adjusting sdk_version: OR platform_apis: " +
-		"property of the source or target module so that target module is built with the same " +
-		"or smaller API set when compared to the source."
 
-	switch myLinkType {
-	case javaCore:
-		if otherLinkType != javaCore {
-			ctx.ModuleErrorf("compiles against core Java API, but dependency %q is compiling against non-core Java APIs."+commonMessage,
-				ctx.OtherModuleName(to))
-		}
-		break
-	case javaSdk:
-		if otherLinkType != javaCore && otherLinkType != javaSdk {
-			ctx.ModuleErrorf("compiles against Android API, but dependency %q is compiling against non-public Android API."+commonMessage,
-				ctx.OtherModuleName(to))
-		}
-		break
-	case javaSystem:
-		if otherLinkType == javaPlatform || otherLinkType == javaModule || otherLinkType == javaSystemServer {
-			ctx.ModuleErrorf("compiles against system API, but dependency %q is compiling against private API."+commonMessage,
-				ctx.OtherModuleName(to))
-		}
-		break
-	case javaModule:
-		if otherLinkType == javaPlatform || otherLinkType == javaSystemServer {
-			ctx.ModuleErrorf("compiles against module API, but dependency %q is compiling against private API."+commonMessage,
-				ctx.OtherModuleName(to))
-		}
-		break
-	case javaSystemServer:
-		if otherLinkType == javaPlatform {
-			ctx.ModuleErrorf("compiles against system server API, but dependency %q is compiling against private API."+commonMessage,
-				ctx.OtherModuleName(to))
-		}
-		break
-	case javaPlatform:
-		// no restriction on link-type
-		break
+	if myLinkType.rank() < otherLinkType.rank() {
+		ctx.ModuleErrorf("compiles against %v, but dependency %q is compiling against %v. "+
+			"In order to fix this, consider adjusting sdk_version: OR platform_apis: "+
+			"property of the source or target module so that target module is built "+
+			"with the same or smaller API set when compared to the source.",
+			myLinkType, ctx.OtherModuleName(to), otherLinkType)
 	}
 }
 
@@ -2426,7 +2427,7 @@
 
 	// list of files or filegroup modules that provide data that should be installed alongside
 	// the test
-	Data []string `android:"path,arch_variant"`
+	Data []string `android:"path"`
 
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
diff --git a/java/java_test.go b/java/java_test.go
index e7776c3..0ef4db6 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -127,7 +127,6 @@
 	}
 
 	t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
-
 	return ctx, config
 }
 
@@ -1179,6 +1178,110 @@
 	}
 }
 
+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")
+	rule := foo.Rule("lint")
+
+	if !strings.Contains(rule.RuleParams.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")
+	rule := foo.Rule("lint")
+
+	if strings.Contains(rule.RuleParams.Command, "--baseline") {
+		t.Error("passed --baseline flag for non existent file")
+	}
+}
+
+func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
+	config := testConfig(
+		nil,
+		`
+		java_library {
+			name: "foo",
+			srcs: [
+			],
+			min_sdk_version: "29",
+			sdk_version: "system_current",
+			lint: {
+				baseline_filename: "mybaseline.xml",
+			},
+		}
+     `, map[string][]byte{
+			"build/soong/java/lint_defaults.txt":                   nil,
+			"prebuilts/cmdline-tools/tools/bin/lint":               nil,
+			"prebuilts/cmdline-tools/tools/lib/lint-classpath.jar": nil,
+			"framework/aidl":                     nil,
+			"a.java":                             nil,
+			"AndroidManifest.xml":                nil,
+			"build/make/target/product/security": nil,
+		})
+	config.TestAllowNonExistentPaths = false
+	testJavaErrorWithConfig(t,
+		"source path \"mybaseline.xml\" does not exist",
+		config,
+	)
+}
+
+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")
+	rule := foo.Rule("lint")
+
+	if !strings.Contains(rule.RuleParams.Command, "--baseline mybaseline.xml") {
+		t.Error("did not use the correct file for baseline")
+	}
+}
+
 func TestGeneratedSources(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
diff --git a/java/lint.go b/java/lint.go
index c9e0cdd..8272595 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -19,6 +19,8 @@
 	"sort"
 	"strings"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 )
 
@@ -46,6 +48,9 @@
 
 		// Modules that provide extra lint checks
 		Extra_check_modules []string
+
+		// Name of the file that lint uses as the baseline. Defaults to "lint-baseline.xml".
+		Baseline_filename *string
 	}
 }
 
@@ -344,6 +349,19 @@
 		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())
+		}
+	}
+
 	cmd.Text("|| (").Text("cat").Input(text).Text("; exit 7)").Text(")")
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index 1e90149..c91b321 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -106,14 +106,18 @@
 	mctx.CreateModule(ImportFactory, &props)
 }
 
-func createFilegroup(mctx android.LoadHookContext, name string, path string) {
-	filegroupProps := struct {
+func createApiModule(mctx android.LoadHookContext, name string, path string) {
+	genruleProps := struct {
 		Name *string
 		Srcs []string
+		Out  []string
+		Cmd  *string
 	}{}
-	filegroupProps.Name = proptools.StringPtr(name)
-	filegroupProps.Srcs = []string{path}
-	mctx.CreateModule(android.FileGroupFactory, &filegroupProps)
+	genruleProps.Name = proptools.StringPtr(name)
+	genruleProps.Srcs = []string{path}
+	genruleProps.Out = []string{name}
+	genruleProps.Cmd = proptools.StringPtr("cp $(in) $(out)")
+	mctx.CreateModule(genrule.GenRuleFactory, &genruleProps)
 }
 
 func createEmptyFile(mctx android.LoadHookContext, name string) {
@@ -205,16 +209,16 @@
 		path    string
 	}
 
-	// Create filegroups for all (<module>, <scope, <version>) triplets,
-	// and a "latest" filegroup variant for each (<module>, <scope>) pair
-	moduleName := func(module, scope, version string) string {
+	// Create modules for all (<module>, <scope, <version>) triplets,
+	// and a "latest" module variant for each (<module>, <scope>) pair
+	apiModuleName := func(module, scope, version string) string {
 		return module + ".api." + scope + "." + version
 	}
 	m := make(map[string]latestApiInfo)
 	for _, f := range files {
 		localPath := strings.TrimPrefix(f, mydir)
 		module, apiver, scope := parseApiFilePath(mctx, localPath)
-		createFilegroup(mctx, moduleName(module, scope, apiver), localPath)
+		createApiModule(mctx, apiModuleName(module, scope, apiver), localPath)
 
 		version, err := strconv.Atoi(apiver)
 		if err != nil {
@@ -239,8 +243,8 @@
 	// Sort the keys in order to make build.ninja stable
 	for _, k := range android.SortedStringKeys(m) {
 		info := m[k]
-		name := moduleName(info.module, info.scope, "latest")
-		createFilegroup(mctx, name, info.path)
+		name := apiModuleName(info.module, info.scope, "latest")
+		createApiModule(mctx, name, info.path)
 	}
 
 	// Create incompatibilities tracking files for all modules, if we have a "next" api.
@@ -258,14 +262,14 @@
 				referencedModule = "android"
 			}
 
-			createFilegroup(mctx, moduleName(referencedModule+"-incompatibilities", scope, "latest"), localPath)
+			createApiModule(mctx, apiModuleName(referencedModule+"-incompatibilities", scope, "latest"), localPath)
 
 			incompatibilities[referencedModule+"."+scope] = true
 		}
 		// Create empty incompatibilities files for remaining modules
 		for _, k := range android.SortedStringKeys(m) {
 			if _, ok := incompatibilities[k]; !ok {
-				createEmptyFile(mctx, moduleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
+				createEmptyFile(mctx, apiModuleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
 			}
 		}
 	}
@@ -279,10 +283,10 @@
 	}
 }
 
-// prebuilt_apis is a meta-module that generates filegroup modules for all
-// API txt files found under the directory where the Android.bp is located.
+// prebuilt_apis is a meta-module that generates modules for all API txt files
+// found under the directory where the Android.bp is located.
 // Specifically, an API file located at ./<ver>/<scope>/api/<module>.txt
-// generates a filegroup module named <module>-api.<scope>.<ver>.
+// generates a module named <module>-api.<scope>.<ver>.
 //
 // It also creates <module>-api.<scope>.latest for the latest <ver>.
 //
diff --git a/java/rro_test.go b/java/rro_test.go
index 345f2ee..0a2f848 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -263,47 +263,34 @@
 
 func TestEnforceRRO_propagatesToDependencies(t *testing.T) {
 	testCases := []struct {
-		name                    string
-		enforceRROTargets       []string
-		enforceRROExemptTargets []string
-		rroDirs                 map[string][]string
+		name              string
+		enforceRROTargets []string
+		rroDirs           map[string][]string
 	}{
 		{
-			name:                    "no RRO",
-			enforceRROTargets:       nil,
-			enforceRROExemptTargets: nil,
+			name:              "no RRO",
+			enforceRROTargets: nil,
 			rroDirs: map[string][]string{
 				"foo": nil,
 				"bar": nil,
 			},
 		},
 		{
-			name:                    "enforce RRO on all",
-			enforceRROTargets:       []string{"*"},
-			enforceRROExemptTargets: nil,
+			name:              "enforce RRO on all",
+			enforceRROTargets: []string{"*"},
 			rroDirs: map[string][]string{
 				"foo": {"product/vendor/blah/overlay/lib2/res"},
 				"bar": {"product/vendor/blah/overlay/lib2/res"},
 			},
 		},
 		{
-			name:                    "enforce RRO on foo",
-			enforceRROTargets:       []string{"foo"},
-			enforceRROExemptTargets: nil,
+			name:              "enforce RRO on foo",
+			enforceRROTargets: []string{"foo"},
 			rroDirs: map[string][]string{
 				"foo": {"product/vendor/blah/overlay/lib2/res"},
 				"bar": {"product/vendor/blah/overlay/lib2/res"},
 			},
 		},
-		{
-			name:                    "enforce RRO on foo, bar exempted",
-			enforceRROTargets:       []string{"foo"},
-			enforceRROExemptTargets: []string{"bar"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": nil,
-			},
-		},
 	}
 
 	productResourceOverlays := []string{
@@ -351,9 +338,6 @@
 			if testCase.enforceRROTargets != nil {
 				config.TestProductVariables.EnforceRROTargets = testCase.enforceRROTargets
 			}
-			if testCase.enforceRROExemptTargets != nil {
-				config.TestProductVariables.EnforceRROExemptedTargets = testCase.enforceRROExemptTargets
-			}
 
 			ctx := testContext(config)
 			run(t, ctx, config)
diff --git a/java/sdk.go b/java/sdk.go
index 7290711..74d5a81 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -641,14 +641,26 @@
 
 	if ctx.Config().PlatformSdkCodename() == "REL" {
 		cmd.Text("echo REL >").Output(out)
-	} else if !ctx.Config().AlwaysUsePrebuiltSdks() {
-		in, err := ctx.GlobWithDeps("frameworks/base/api/*current.txt", nil)
-		if err != nil {
-			ctx.Errorf("error globbing API files: %s", err)
+	} else if ctx.Config().FrameworksBaseDirExists(ctx) && !ctx.Config().AlwaysUsePrebuiltSdks() {
+		cmd.Text("cat")
+		apiTxtFileModules := []string{
+			"frameworks-base-api-current.txt",
+			"frameworks-base-api-system-current.txt",
+			"frameworks-base-api-module-lib-current.txt",
 		}
-
-		cmd.Text("cat").
-			Inputs(android.PathsForSource(ctx, in)).
+		count := 0
+		ctx.VisitAllModules(func(module android.Module) {
+			name := ctx.ModuleName(module)
+			if android.InList(name, apiTxtFileModules) {
+				cmd.Inputs(android.OutputFilesForModule(ctx, module, ""))
+				count++
+			}
+		})
+		if count != len(apiTxtFileModules) {
+			ctx.Errorf("Could not find all the expected API modules %v, found %d\n", apiTxtFileModules, count)
+			return
+		}
+		cmd.Input(android.PathForSource(ctx, "frameworks/base/services/api/current.txt")).
 			Text("| md5sum | cut -d' ' -f1 >").
 			Output(out)
 	} else {
diff --git a/rust/Android.bp b/rust/Android.bp
index 8b2aa30..a29c474 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -8,6 +8,7 @@
     deps: [
         "soong",
         "soong-android",
+        "soong-bloaty",
         "soong-cc",
         "soong-rust-config",
     ],
diff --git a/rust/builder.go b/rust/builder.go
index 56fe031..547d705 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -21,6 +21,7 @@
 	"github.com/google/blueprint"
 
 	"android/soong/android"
+	"android/soong/bloaty"
 	"android/soong/rust/config"
 )
 
@@ -249,6 +250,8 @@
 		implicits = append(implicits, clippyFile)
 	}
 
+	bloaty.MeasureSizeForPath(ctx, outputFile)
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:            rustc,
 		Description:     "rustc " + main.Rel(),
diff --git a/rust/config/arm64_device.go b/rust/config/arm64_device.go
index 5066428..186e571 100644
--- a/rust/config/arm64_device.go
+++ b/rust/config/arm64_device.go
@@ -26,9 +26,10 @@
 	Arm64LinkFlags            = []string{}
 
 	Arm64ArchVariantRustFlags = map[string][]string{
-		"armv8-a":          []string{},
-		"armv8-2a":         []string{},
-		"armv8-2a-dotprod": []string{},
+		"armv8-a":            []string{},
+		"armv8-a-branchprot": []string{},
+		"armv8-2a":           []string{},
+		"armv8-2a-dotprod":   []string{},
 	}
 )
 
diff --git a/rust/config/global.go b/rust/config/global.go
index 182dc6a..12f4972 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.49.0"
+	RustDefaultVersion = "1.50.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/config/lints.go b/rust/config/lints.go
index 06bb668..ac8165b 100644
--- a/rust/config/lints.go
+++ b/rust/config/lints.go
@@ -52,6 +52,7 @@
 	// deny.
 	defaultClippyLints = []string{
 		"-A clippy::type-complexity",
+		"-A clippy::unnecessary-wraps",
 	}
 
 	// Rust lints for vendor code.
diff --git a/scripts/Android.bp b/scripts/Android.bp
index b9163cc..310c959 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -209,13 +209,18 @@
     test_suites: ["general-tests"],
 }
 
+python_library_host {
+    name: "ninja_rsp",
+    srcs: ["ninja_rsp.py"],
+}
+
 python_binary_host {
     name: "lint-project-xml",
     main: "lint-project-xml.py",
     srcs: [
         "lint-project-xml.py",
-        "ninja_rsp.py",
     ],
+    libs: ["ninja_rsp"],
 }
 
 python_binary_host {
@@ -223,8 +228,8 @@
     main: "gen-kotlin-build-file.py",
     srcs: [
         "gen-kotlin-build-file.py",
-        "ninja_rsp.py",
     ],
+    libs: ["ninja_rsp"],
 }
 
 python_binary_host {
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index ac67438..b93c883 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -8,6 +8,7 @@
   com.android.art.testing
   com.android.conscrypt
   com.android.i18n
+  com.android.os.statsd
   com.android.runtime
   com.android.tzdata
 )
diff --git a/scripts/check_do_not_merge.sh b/scripts/check_do_not_merge.sh
new file mode 100755
index 0000000..ad6a0a9
--- /dev/null
+++ b/scripts/check_do_not_merge.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# 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.
+
+if git show -s --format=%s $1 | grep -qE '(DO NOT MERGE)|(RESTRICT AUTOMERGE)'; then
+    cat >&2 <<EOF
+DO NOT MERGE and RESTRICT AUTOMERGE very often lead to unintended results
+and are not allowed to be used in this project.
+Please use the Merged-In tag to be more explicit about where this change
+should merge to. Google-internal documentation exists at go/merged-in
+
+If this check is mis-triggering or you know Merged-In is incorrect in this
+situation you can bypass this check with \`repo upload --no-verify\`.
+EOF
+    exit 1
+fi
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index a669cad..af7e7fe 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 python_binary_host {
     name: "merge_csv",
     main: "merge_csv.py",
diff --git a/scripts/hiddenapi/merge_csv.py b/scripts/hiddenapi/merge_csv.py
index 6a5b0e1..5ad61b2 100755
--- a/scripts/hiddenapi/merge_csv.py
+++ b/scripts/hiddenapi/merge_csv.py
@@ -26,7 +26,8 @@
 args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
 args_parser.add_argument('--header', help='Comma separated field names; '
                                           'if missing determines the header from input files.')
-args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
+args_parser.add_argument('--zip_input', help='Treat files as ZIP archives containing CSV files to merge.',
+                         action="store_true")
 args_parser.add_argument('--output', help='Output file for merged CSV.',
                          default='-', type=argparse.FileType('w'))
 args_parser.add_argument('files', nargs=argparse.REMAINDER)
@@ -36,20 +37,16 @@
 def dict_reader(input):
     return csv.DictReader(input, delimiter=',', quotechar='|')
 
-
-if args.zip_input and len(args.files) > 0:
-    raise ValueError('Expecting either a single ZIP with CSV files'
-                     ' or a list of CSV files as input; not both.')
-
 csv_readers = []
-if len(args.files) > 0:
+if not(args.zip_input):
     for file in args.files:
         csv_readers.append(dict_reader(open(file, 'r')))
-elif args.zip_input:
-    with ZipFile(args.zip_input) as zip:
-        for entry in zip.namelist():
-            if entry.endswith('.uau'):
-                csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+else:
+    for file in args.files:
+        with ZipFile(file) as zip:
+            for entry in zip.namelist():
+                if entry.endswith('.uau'):
+                    csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
 
 headers = set()
 if args.header: