Merge "Correct type of dir function"
diff --git a/android/bazel.go b/android/bazel.go
index 7e2727c..e3fb0a6 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -357,6 +357,7 @@
 		"packages/services/Car/tests/SampleRearViewCamera":   Bp2BuildDefaultTrue,
 		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
 		"prebuilts/tools/common/m2":                          Bp2BuildDefaultTrue,
+		"prebuilts/sdk/tools/jetifier/jetifier-standalone":   Bp2BuildDefaultTrue,
 		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
 		"system/apex/proto":                                  Bp2BuildDefaultTrueRecursively,
 		"system/apex/libs":                                   Bp2BuildDefaultTrueRecursively,
@@ -393,8 +394,6 @@
 	// A module can either be in this list or its directory allowlisted entirely
 	// in bp2buildDefaultConfig, but not both at the same time.
 	bp2buildModuleAlwaysConvertList = []string{
-		"prebuilt_junit-params-assertj-core",
-
 		//external/avb
 		"avbtool",
 		"libavb",
@@ -417,11 +416,22 @@
 
 		//system/extras/verity/fec
 		"fec",
+
+		//packages/apps/Car/libs/car-ui-lib/car-ui-androidx
+		// genrule dependencies for java_imports
+		"car-ui-androidx-annotation-nodeps",
+		"car-ui-androidx-collection-nodeps",
+		"car-ui-androidx-core-common-nodeps",
+		"car-ui-androidx-lifecycle-common-nodeps",
+		"car-ui-androidx-constraintlayout-solver-nodeps",
 	}
 
-	// Per-module-type allowlist to always opt modules in of both bp2build and mixed builds
+	// Per-module-type allowlist to always opt modules in to both bp2build and mixed builds
 	// when they have the same type as one listed.
-	bp2buildModuleTypeAlwaysConvertList = []string{}
+	bp2buildModuleTypeAlwaysConvertList = []string{
+		"java_import",
+		"java_import_host",
+	}
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
@@ -485,18 +495,12 @@
 		"libprotobuf-java-full",            // b/210751803, we don't handle path property for filegroups
 		"host-libprotobuf-java-full",       // b/210751803, we don't handle path property for filegroups
 		"libprotobuf-java-util-full",       // b/210751803, we don't handle path property for filegroups
+		"apex_manifest_proto_java",         // b/210751803, depends on libprotobuf-java-full
+		"conscrypt",                        // b/210751803, we don't handle path property for filegroups
+		"conscrypt-for-host",               // b/210751803, we don't handle path property for filegroups
 
-		"conscrypt",          // b/210751803, we don't handle path property for filegroups
-		"conscrypt-for-host", // b/210751803, we don't handle path property for filegroups
-
-		"host-libprotobuf-java-lite",   // b/217236083, java_library cannot have deps without srcs
-		"host-libprotobuf-java-micro",  // b/217236083, java_library cannot have deps without srcs
-		"host-libprotobuf-java-nano",   // b/217236083, java_library cannot have deps without srcs
-		"error_prone_core",             // b/217236083, java_library cannot have deps without srcs
-		"bouncycastle-host",            // b/217236083, java_library cannot have deps without srcs
-		"mockito-robolectric-prebuilt", // b/217236083, java_library cannot have deps without srcs
-
-		"apex_manifest_proto_java", // b/215230097, we don't handle .proto files in java_library srcs attribute
+		"libprotobuf-java-nano",      // b/220869005, depends on non-public_current SDK
+		"host-libprotobuf-java-nano", // b/220869005, depends on libprotobuf-java-nano
 
 		"libc_musl_sysroot_bionic_arch_headers", // b/218405924, depends on soong_zip
 		"libc_musl_sysroot_bionic_headers",      // b/218405924, depends on soong_zip and generates duplicate srcs
@@ -509,6 +513,9 @@
 
 		"brotli-fuzzer-corpus", // b/202015218: outputs are in location incompatible with bazel genrule handling.
 
+		// python modules
+		"analyze_bcpf", // depends on bpmodify a blueprint_go_binary.
+
 		// b/203369847: multiple genrules in the same package creating the same file
 		// //development/sdk/...
 		"platform_tools_properties",
@@ -563,9 +570,9 @@
 		"art-script",     // depends on unconverted modules: dalvikvm, dex2oat
 		"dex2oat-script", // depends on unconverted modules: dex2oat
 
-		"error_prone_checkerframework_dataflow_nullaway", // TODO(b/219908977): "Error in fail: deps not allowed without srcs; move to runtime_deps?"
-
-		"libprotobuf-java-nano", // b/220869005, depends on non-public_current SDK
+		"prebuilt_car-ui-androidx-core-common",         // b/224773339, genrule dependency creates an .aar, not a .jar
+		"prebuilt_platform-robolectric-4.4-prebuilt",   // aosp/1999250, needs .aar support in Jars
+		"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
 	}
 
 	// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/module.go b/android/module.go
index e190e8b..66a5f60 100644
--- a/android/module.go
+++ b/android/module.go
@@ -3163,6 +3163,7 @@
 		symlinkTarget:         "",
 		executable:            executable,
 		effectiveLicenseFiles: &licenseFiles,
+		partition:             fullInstallPath.partition,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -3280,6 +3281,7 @@
 		srcPath:          nil,
 		symlinkTarget:    relPath,
 		executable:       false,
+		partition:        fullInstallPath.partition,
 	})
 
 	return fullInstallPath
@@ -3320,6 +3322,7 @@
 		srcPath:          nil,
 		symlinkTarget:    absPath,
 		executable:       false,
+		partition:        fullInstallPath.partition,
 	})
 
 	return fullInstallPath
diff --git a/android/notices.go b/android/notices.go
index 194a734..2a4c17c 100644
--- a/android/notices.go
+++ b/android/notices.go
@@ -15,93 +15,9 @@
 package android
 
 import (
-	"path/filepath"
 	"strings"
-
-	"github.com/google/blueprint"
 )
 
-func init() {
-	pctx.SourcePathVariable("merge_notices", "build/soong/scripts/mergenotice.py")
-	pctx.SourcePathVariable("generate_notice", "build/soong/scripts/generate-notice-files.py")
-
-	pctx.HostBinToolVariable("minigzip", "minigzip")
-}
-
-type NoticeOutputs struct {
-	Merged       OptionalPath
-	TxtOutput    OptionalPath
-	HtmlOutput   OptionalPath
-	HtmlGzOutput OptionalPath
-}
-
-var (
-	mergeNoticesRule = pctx.AndroidStaticRule("mergeNoticesRule", blueprint.RuleParams{
-		Command:     `${merge_notices} --output $out $in`,
-		CommandDeps: []string{"${merge_notices}"},
-		Description: "merge notice files into $out",
-	})
-
-	generateNoticeRule = pctx.AndroidStaticRule("generateNoticeRule", blueprint.RuleParams{
-		Command: `rm -rf $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
-			`mkdir -p $$(dirname $txtOut) $$(dirname $htmlOut)  $$(dirname $out) && ` +
-			`${generate_notice} --text-output $txtOut --html-output $htmlOut -t "$title" -s $inputDir && ` +
-			`${minigzip} -c $htmlOut > $out`,
-		CommandDeps: []string{"${generate_notice}", "${minigzip}"},
-		Description: "produce notice file $out",
-	}, "txtOut", "htmlOut", "title", "inputDir")
-)
-
-func MergeNotices(ctx ModuleContext, mergedNotice WritablePath, noticePaths []Path) {
-	ctx.Build(pctx, BuildParams{
-		Rule:        mergeNoticesRule,
-		Description: "merge notices",
-		Inputs:      noticePaths,
-		Output:      mergedNotice,
-	})
-}
-
-func BuildNoticeOutput(ctx ModuleContext, installPath InstallPath, installFilename string,
-	noticePaths []Path) NoticeOutputs {
-	// Merge all NOTICE files into one.
-	// TODO(jungjw): We should just produce a well-formatted NOTICE.html file in a single pass.
-	//
-	// generate-notice-files.py, which processes the merged NOTICE file, has somewhat strict rules
-	// about input NOTICE file paths.
-	// 1. Their relative paths to the src root become their NOTICE index titles. We want to use
-	// on-device paths as titles, and so output the merged NOTICE file the corresponding location.
-	// 2. They must end with .txt extension. Otherwise, they're ignored.
-	noticeRelPath := InstallPathToOnDevicePath(ctx, installPath.Join(ctx, installFilename+".txt"))
-	mergedNotice := PathForModuleOut(ctx, filepath.Join("NOTICE_FILES/src", noticeRelPath))
-	MergeNotices(ctx, mergedNotice, noticePaths)
-
-	// Transform the merged NOTICE file into a gzipped HTML file.
-	txtOuptut := PathForModuleOut(ctx, "NOTICE_txt", "NOTICE.txt")
-	htmlOutput := PathForModuleOut(ctx, "NOTICE_html", "NOTICE.html")
-	htmlGzOutput := PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
-	title := "Notices for " + ctx.ModuleName()
-	ctx.Build(pctx, BuildParams{
-		Rule:            generateNoticeRule,
-		Description:     "generate notice output",
-		Input:           mergedNotice,
-		Output:          htmlGzOutput,
-		ImplicitOutputs: WritablePaths{txtOuptut, htmlOutput},
-		Args: map[string]string{
-			"txtOut":   txtOuptut.String(),
-			"htmlOut":  htmlOutput.String(),
-			"title":    title,
-			"inputDir": PathForModuleOut(ctx, "NOTICE_FILES/src").String(),
-		},
-	})
-
-	return NoticeOutputs{
-		Merged:       OptionalPathForPath(mergedNotice),
-		TxtOutput:    OptionalPathForPath(txtOuptut),
-		HtmlOutput:   OptionalPathForPath(htmlOutput),
-		HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
-	}
-}
-
 // BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based on the module's
 // generated license metadata file.
 func BuildNoticeTextOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
@@ -112,5 +28,18 @@
 		FlagWithOutput("-o ", outputFile).
 		FlagWithDepFile("-d ", depsFile).
 		Input(ctx.Module().base().licenseMetadataFile)
-	rule.Build("container_notice", "container notice file")
+	rule.Build("text_notice", "container notice file")
+}
+
+// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based on the module's
+// generated license metadata file.
+func BuildNoticeHtmlOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
+	depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", "."))
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("htmlnotice").
+		FlagWithOutput("-o ", outputFile).
+		FlagWithDepFile("-d ", depsFile).
+		Input(ctx.Module().base().licenseMetadataFile)
+	rule.Build("html_notice", "container notice file")
 }
diff --git a/android/packaging.go b/android/packaging.go
index e3a0b54..ecd84a2 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -40,6 +40,8 @@
 	executable bool
 
 	effectiveLicenseFiles *Paths
+
+	partition string
 }
 
 // Get file name of installed package
@@ -67,6 +69,10 @@
 	return *p.effectiveLicenseFiles
 }
 
+func (p *PackagingSpec) Partition() string {
+	return p.partition
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
@@ -76,11 +82,14 @@
 	// be copied to a zip in CopyDepsToZip, `depTag` should implement PackagingItem marker interface.
 	AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
 
+	// GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
+	GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
+
 	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
 	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
 	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
 	// etc.) from the extracted files
-	CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) []string
+	CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) []string
 }
 
 // PackagingBase provides basic functionality for packaging dependencies. A module is expected to
@@ -211,7 +220,7 @@
 	}
 }
 
-// Returns transitive PackagingSpecs from deps
+// See PackageModule.GatherPackagingSpecs
 func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
 	ctx.VisitDirectDeps(func(child Module) {
@@ -229,10 +238,10 @@
 
 // CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into the specified directory.
-func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, m map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
+func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
 	seenDir := make(map[string]bool)
-	for _, k := range SortedStringKeys(m) {
-		ps := m[k]
+	for _, k := range SortedStringKeys(specs) {
+		ps := specs[k]
 		destPath := dir.Join(ctx, ps.relPathInPackage).String()
 		destDir := filepath.Dir(destPath)
 		entries = append(entries, ps.relPathInPackage)
@@ -254,14 +263,13 @@
 }
 
 // See PackageModule.CopyDepsToZip
-func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
-	m := p.GatherPackagingSpecs(ctx)
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) (entries []string) {
 	builder := NewRuleBuilder(pctx, ctx)
 
 	dir := PathForModuleOut(ctx, ".zip")
 	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
 	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
-	entries = p.CopySpecsToDir(ctx, builder, m, dir)
+	entries = p.CopySpecsToDir(ctx, builder, specs, dir)
 
 	builder.Command().
 		BuiltTool("soong_zip").
diff --git a/android/packaging_test.go b/android/packaging_test.go
index ff7446c..91ac1f3 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -95,7 +95,7 @@
 
 func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	zipFile := PathForModuleOut(ctx, "myzip.zip")
-	m.entries = m.CopyDepsToZip(ctx, zipFile)
+	m.entries = m.CopyDepsToZip(ctx, m.GatherPackagingSpecs(ctx), zipFile)
 }
 
 func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 059b4d7..e094a12 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -396,10 +396,6 @@
 				}
 				a.writeRequiredModules(w, moduleNames)
 
-				if a.mergedNotices.Merged.Valid() {
-					fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNotices.Merged.Path().String())
-				}
-
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 
 				if apexType == imageApex {
diff --git a/apex/apex.go b/apex/apex.go
index 6d8a67a..cb88f02 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -414,8 +414,8 @@
 	// Processed file_contexts files
 	fileContexts android.WritablePath
 
-	// Struct holding the merged notice file paths in different formats
-	mergedNotices android.NoticeOutputs
+	// Path to notice file in html.gz format.
+	htmlGzNotice android.WritablePath
 
 	// The built APEX file. This is the main product.
 	// Could be .apex or .capex
@@ -487,11 +487,10 @@
 // for each of the files in case when the APEX is flattened.
 type apexFile struct {
 	// buildFile is put in the installDir inside the APEX.
-	builtFile   android.Path
-	noticeFiles android.Paths
-	installDir  string
-	customStem  string
-	symlinks    []string // additional symlinks
+	builtFile  android.Path
+	installDir string
+	customStem string
+	symlinks   []string // additional symlinks
 
 	// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
 	// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
@@ -528,7 +527,6 @@
 		module:              module,
 	}
 	if module != nil {
-		ret.noticeFiles = module.NoticeFiles()
 		ret.moduleDir = ctx.OtherModuleDir(module)
 		ret.requiredModuleNames = module.RequiredModuleNames()
 		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 85bd595..ec815c4 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -591,15 +591,6 @@
 		t.Errorf("Could not find all expected symlinks! foo: %t, foo_link_64: %t. Command was %s", found_foo, found_foo_link_64, copyCmds)
 	}
 
-	mergeNoticesRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("mergeNoticesRule")
-	noticeInputs := mergeNoticesRule.Inputs.Strings()
-	if len(noticeInputs) != 3 {
-		t.Errorf("number of input notice files: expected = 3, actual = %q", len(noticeInputs))
-	}
-	ensureListContains(t, noticeInputs, "NOTICE")
-	ensureListContains(t, noticeInputs, "custom_notice")
-	ensureListContains(t, noticeInputs, "custom_notice_for_static_lib")
-
 	fullDepsInfo := strings.Split(ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("depsinfo/fulllist.txt").Args["content"], "\\n")
 	ensureListContains(t, fullDepsInfo, "  myjar(minSdkVersion:(no version)) <- myapex")
 	ensureListContains(t, fullDepsInfo, "  mylib2(minSdkVersion:(no version)) <- mylib")
diff --git a/apex/builder.go b/apex/builder.go
index 8c5f99b..50c8dd1 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -305,32 +305,6 @@
 	return output.OutputPath
 }
 
-// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
-// contributes to this APEX. The notice files are merged into a big notice file.
-func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
-	var noticeFiles android.Paths
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if externalDep {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-		noticeFiles = append(noticeFiles, to.NoticeFiles()...)
-		return true
-	})
-
-	// TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
-	for _, fi := range a.filesInfo {
-		noticeFiles = append(noticeFiles, fi.noticeFiles...)
-	}
-
-	if len(noticeFiles) == 0 {
-		return android.NoticeOutputs{}
-	}
-
-	return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
-}
-
 // buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
 // files included in this APEX is shown. The text file is dist'ed so that people can see what's
 // included in the APEX without actually downloading and extracting it.
@@ -642,12 +616,11 @@
 			optFlags = append(optFlags, "--logging_parent ", a.overridableProperties.Logging_parent)
 		}
 
-		a.mergedNotices = a.buildNoticeFiles(ctx, a.Name()+suffix)
-		if a.mergedNotices.HtmlGzOutput.Valid() {
-			// If there's a NOTICE file, embed it as an asset file in the APEX.
-			implicitInputs = append(implicitInputs, a.mergedNotices.HtmlGzOutput.Path())
-			optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.mergedNotices.HtmlGzOutput.String()))
-		}
+		// Create a NOTICE file, and embed it as an asset file in the APEX.
+		a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+		android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice)
+		implicitInputs = append(implicitInputs, a.htmlGzNotice)
+		optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.htmlGzNotice.String()))
 
 		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
 			// Apexes which are supposed to be installed in builtin dirs(/system, etc)
diff --git a/bazel/properties.go b/bazel/properties.go
index 1300a53..f956031 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -65,6 +65,14 @@
 	Excludes []Label
 }
 
+// MakeLabelList creates a LabelList from a list Label
+func MakeLabelList(labels []Label) LabelList {
+	return LabelList{
+		Includes: labels,
+		Excludes: nil,
+	}
+}
+
 func (ll *LabelList) Equals(other LabelList) bool {
 	if len(ll.Includes) != len(other.Includes) || len(ll.Excludes) != len(other.Excludes) {
 		return false
@@ -354,6 +362,15 @@
 	return keys
 }
 
+// MakeLabelAttribute turns a string into a LabelAttribute
+func MakeLabelAttribute(label string) *LabelAttribute {
+	return &LabelAttribute{
+		Value: &Label{
+			Label: label,
+		},
+	}
+}
+
 type configToBools map[string]bool
 
 func (ctb configToBools) setValue(config string, value *bool) {
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index b6095b2..3824586 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -51,7 +51,6 @@
 				"srcs":           `["app.java"]`,
 				"manifest":       `"AndroidManifest.xml"`,
 				"resource_files": `["res/res.png"]`,
-				"deps":           `["//prebuilts/sdk:public_current_android_sdk_java_import"]`,
 			}),
 		}})
 }
@@ -87,10 +86,7 @@
         "resb/res.png",
     ]`,
 				"custom_package": `"com.google"`,
-				"deps": `[
-        "//prebuilts/sdk:public_current_android_sdk_java_import",
-        ":static_lib_dep",
-    ]`,
+				"deps":           `[":static_lib_dep"]`,
 			}),
 		}})
 }
@@ -129,7 +125,6 @@
     })`,
 				"manifest":       `"AndroidManifest.xml"`,
 				"resource_files": `["res/res.png"]`,
-				"deps":           `["//prebuilts/sdk:public_current_android_sdk_java_import"]`,
 			}),
 		}})
 }
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index 65136d9..4fc07e0 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -28,6 +28,7 @@
 	(&tc).moduleTypeUnderTestFactory = java.BinaryHostFactory
 	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
 		ctx.RegisterModuleType("cc_library_host_shared", cc.LibraryHostSharedFactory)
+		ctx.RegisterModuleType("java_library", java.LibraryFactory)
 	}, tc)
 }
 
@@ -67,3 +68,33 @@
 		},
 	})
 }
+
+func TestJavaBinaryHostRuntimeDeps(t *testing.T) {
+	runJavaBinaryHostTestCase(t, bp2buildTestCase{
+		description: "java_binary_host with srcs, exclude_srcs, jni_libs, javacflags, and manifest.",
+		filesystem:  fs,
+		blueprint: `java_binary_host {
+    name: "java-binary-host-1",
+    static_libs: ["java-dep-1"],
+    manifest: "test.mf",
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-dep-1",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
+				"main_class":   `"com.android.test.MainClass"`,
+				"runtime_deps": `[":java-dep-1"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_import_conversion_test.go b/bp2build/java_import_conversion_test.go
index 2f7211c..0b3191c 100644
--- a/bp2build/java_import_conversion_test.go
+++ b/bp2build/java_import_conversion_test.go
@@ -29,7 +29,7 @@
 func registerJavaImportModuleTypes(ctx android.RegistrationContext) {
 }
 
-func TestMinimalJavaImport(t *testing.T) {
+func TestJavaImportMinimal(t *testing.T) {
 	runJavaImportTestCase(t, bp2buildTestCase{
 		description:                "Java import - simple example",
 		moduleTypeUnderTest:        "java_import",
@@ -50,3 +50,36 @@
 			}),
 		}})
 }
+
+func TestJavaImportArchVariant(t *testing.T) {
+	runJavaImportTestCase(t, bp2buildTestCase{
+		description:                "Java import - simple example",
+		moduleTypeUnderTest:        "java_import",
+		moduleTypeUnderTestFactory: java.ImportFactory,
+		filesystem: map[string]string{
+			"import.jar": "",
+		},
+		blueprint: `
+java_import {
+        name: "example_import",
+		target: {
+			android: {
+				jars: ["android.jar"],
+			},
+			linux_glibc: {
+				jars: ["linux.jar"],
+			},
+		},
+        bazel_module: { bp2build_available: true },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_import", "example_import", attrNameToString{
+				"jars": `select({
+        "//build/bazel/platforms/os:android": ["android.jar"],
+        "//build/bazel/platforms/os:linux": ["linux.jar"],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 5c65ec2..2f6bce2 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -15,17 +15,22 @@
 package bp2build
 
 import (
+	"fmt"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/java"
 )
 
-func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+func runJavaLibraryTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
 	t.Helper()
 	(&tc).moduleTypeUnderTest = "java_library"
 	(&tc).moduleTypeUnderTestFactory = java.LibraryFactory
-	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+	runBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+	runJavaLibraryTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
 }
 
 func TestJavaLibrary(t *testing.T) {
@@ -55,3 +60,99 @@
 		},
 	})
 }
+
+func TestJavaLibraryConvertsStaticLibsToDepsAndExports(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    libs: ["java-lib-2"],
+    static_libs: ["java-lib-3"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-3",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[
+        ":java-lib-2",
+        ":java-lib-3",
+    ]`,
+				"exports": `[":java-lib-3"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryConvertsStaticLibsToExportsIfNoSrcs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"exports": `[":java-lib-2"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryFailsToConvertLibsWithNoSrcs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		expectedErr: fmt.Errorf("Module has direct dependencies but no sources. Bazel will not allow this."),
+		blueprint: `java_library {
+    name: "java-lib-1",
+    libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{},
+	})
+}
+
+func TestJavaLibraryPlugins(t *testing.T) {
+	runJavaLibraryTestCaseWithRegistrationCtxFunc(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    plugins: ["java-plugin-1"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_plugin {
+    name: "java-plugin-1",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"plugins": `[":java-plugin-1"]`,
+			}),
+		},
+	}, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_plugin", java.PluginFactory)
+	})
+}
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
index ff13bb0..c2a2182 100644
--- a/bp2build/java_plugin_conversion_test.go
+++ b/bp2build/java_plugin_conversion_test.go
@@ -70,3 +70,39 @@
 		},
 	})
 }
+
+func TestJavaPluginNoSrcs(t *testing.T) {
+	runJavaPluginTestCase(t, bp2buildTestCase{
+		description: "java_plugin without srcs converts (static) libs to deps",
+		blueprint: `java_plugin {
+    name: "java-plug-1",
+    libs: ["java-lib-1"],
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-1",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_plugin", "java-plug-1", attrNameToString{
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+				"deps": `[
+        ":java-lib-1",
+        ":java-lib-2",
+    ]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
index 61a398c..67f8044 100644
--- a/bp2build/java_proto_conversion_test.go
+++ b/bp2build/java_proto_conversion_test.go
@@ -89,7 +89,7 @@
 						"deps": `[":java-protos_proto"]`,
 					}),
 				makeBazelTarget("java_library", "java-protos", attrNameToString{
-					"deps": fmt.Sprintf(`[":%s"]`, javaLibraryName),
+					"exports": fmt.Sprintf(`[":%s"]`, javaLibraryName),
 				}),
 			},
 		})
@@ -98,7 +98,7 @@
 
 func TestJavaProtoDefault(t *testing.T) {
 	runJavaProtoTestCase(t, bp2buildTestCase{
-		description: "java_proto",
+		description: "java_library proto default",
 		blueprint: `java_library_static {
     name: "java-protos",
     srcs: ["a.proto"],
@@ -115,7 +115,7 @@
 					"deps": `[":java-protos_proto"]`,
 				}),
 			makeBazelTarget("java_library", "java-protos", attrNameToString{
-				"deps": `[":java-protos_java_proto_lite"]`,
+				"exports": `[":java-protos_java_proto_lite"]`,
 			}),
 		},
 	})
diff --git a/cc/config/global.go b/cc/config/global.go
index 8dda537..b8790c7 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -287,8 +287,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r450784"
-	ClangDefaultShortVersion = "14.0.3"
+	ClangDefaultVersion      = "clang-r450784b"
+	ClangDefaultShortVersion = "14.0.4"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index ce6836b..e1659d3 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -65,7 +65,6 @@
 
 	linuxMuslLdflags = []string{
 		"-nostdlib",
-		"-lgcc", "-lgcc_eh",
 		"--sysroot /dev/null",
 	}
 
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 3cf8b58..814fef6 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -480,8 +480,8 @@
 		s.Diag.Cfi = nil
 	}
 
-	// Disable sanitizers that depend on the UBSan runtime for windows/darwin/musl builds.
-	if !ctx.Os().Linux() || ctx.Os() == android.LinuxMusl {
+	// Disable sanitizers that depend on the UBSan runtime for windows/darwin builds.
+	if !ctx.Os().Linux() {
 		s.Cfi = nil
 		s.Diag.Cfi = nil
 		s.Misc_undefined = nil
@@ -490,6 +490,12 @@
 		s.Integer_overflow = nil
 	}
 
+	// Disable CFI for musl
+	if ctx.toolchain().Musl() {
+		s.Cfi = nil
+		s.Diag.Cfi = nil
+	}
+
 	// Also disable CFI for VNDK variants of components
 	if ctx.isVndk() && ctx.useVndk() {
 		if ctx.static() {
@@ -702,10 +708,10 @@
 		flags.Local.AsFlags = append(flags.Local.AsFlags, sanitizeArg)
 		flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)
 
-		if ctx.toolchain().Bionic() {
-			// Bionic sanitizer runtimes have already been added as dependencies so that
-			// the right variant of the runtime will be used (with the "-android"
-			// suffix), so don't let clang the runtime library.
+		if ctx.toolchain().Bionic() || ctx.toolchain().Musl() {
+			// Bionic and musl sanitizer runtimes have already been added as dependencies so that
+			// the right variant of the runtime will be used (with the "-android" or "-musl"
+			// suffixes), so don't let clang the runtime library.
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-link-runtime")
 		} else {
 			// Host sanitizers only link symbols in the final executable, so
@@ -1217,7 +1223,7 @@
 			addStaticDeps(config.BuiltinsRuntimeLibrary(toolchain))
 		}
 
-		if runtimeLibrary != "" && (toolchain.Bionic() || c.sanitize.Properties.UbsanRuntimeDep) {
+		if runtimeLibrary != "" && (toolchain.Bionic() || toolchain.Musl() || c.sanitize.Properties.UbsanRuntimeDep) {
 			// UBSan is supported on non-bionic linux host builds as well
 
 			// Adding dependency to the runtime library. We are using *FarVariation*
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 0796258..ccf9e9d 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -43,8 +43,14 @@
 	// Function that builds extra files under the root directory and returns the files
 	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
 
+	// Function that filters PackagingSpecs returned by PackagingBase.GatherPackagingSpecs()
+	filterPackagingSpecs func(specs map[string]android.PackagingSpec)
+
 	output     android.OutputPath
 	installDir android.InstallPath
+
+	// For testing. Keeps the result of CopyDepsToZip()
+	entries []string
 }
 
 type symlinkDefinition struct {
@@ -226,7 +232,7 @@
 
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
 	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.CopyDepsToZip(ctx, depsZipFile)
+	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -345,7 +351,7 @@
 	}
 
 	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.CopyDepsToZip(ctx, depsZipFile)
+	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -434,3 +440,14 @@
 	}
 	return nil
 }
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
+	specs := f.PackagingBase.GatherPackagingSpecs(ctx)
+	if f.filterPackagingSpecs != nil {
+		f.filterPackagingSpecs(specs)
+	}
+	return specs
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index e78fdff..cda06d9 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -45,11 +45,11 @@
 
 func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
 	result := fixture.RunTestWithBp(t, `
-	        android_system_image {
+		android_system_image {
 			name: "myfilesystem",
 			deps: [
 				"libfoo",
-                                "libbar",
+				"libbar",
 			],
 			linker_config_src: "linker.config.json",
 		}
@@ -74,3 +74,54 @@
 	android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
 		output.RuleParams.Command, "libbar.so")
 }
+
+func registerComponent(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("component", componentFactory)
+}
+
+func componentFactory() android.Module {
+	m := &component{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+type component struct {
+	android.ModuleBase
+	properties struct {
+		Install_copy_in_data []string
+	}
+}
+
+func (c *component) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	output := android.PathForModuleOut(ctx, c.Name())
+	dir := android.PathForModuleInstall(ctx, "components")
+	ctx.InstallFile(dir, c.Name(), output)
+
+	dataDir := android.PathForModuleInPartitionInstall(ctx, "data", "components")
+	for _, d := range c.properties.Install_copy_in_data {
+		ctx.InstallFile(dataDir, d, output)
+	}
+}
+
+func TestFileSystemGathersItemsOnlyInSystemPartition(t *testing.T) {
+	f := android.GroupFixturePreparers(fixture, android.FixtureRegisterWithContext(registerComponent))
+	result := f.RunTestWithBp(t, `
+		android_system_image {
+			name: "myfilesystem",
+			multilib: {
+				common: {
+					deps: ["foo"],
+				},
+			},
+			linker_config_src: "linker.config.json",
+		}
+		component {
+			name: "foo",
+			install_copy_in_data: ["bar"],
+		}
+	`)
+
+	module := result.ModuleForTests("myfilesystem", "android_common").Module().(*systemImage)
+	android.AssertDeepEquals(t, "entries should have foo only", []string{"components/foo"}, module.entries)
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 1d24d6d..75abf70 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -37,6 +37,7 @@
 	module := &systemImage{}
 	module.AddProperties(&module.properties)
 	module.filesystem.buildExtraFiles = module.buildExtraFiles
+	module.filesystem.filterPackagingSpecs = module.filterPackagingSpecs
 	initFilesystemModule(&module.filesystem)
 	return module
 }
@@ -53,7 +54,7 @@
 
 	// we need "Module"s for packaging items
 	var otherModules []android.Module
-	deps := s.GatherPackagingSpecs(ctx)
+	deps := s.gatherFilteredPackagingSpecs(ctx)
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		for _, ps := range child.PackagingSpecs() {
 			if _, ok := deps[ps.RelPathInPackage()]; ok {
@@ -68,3 +69,14 @@
 	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
 	return output
 }
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (s *systemImage) filterPackagingSpecs(specs map[string]android.PackagingSpec) {
+	for k, ps := range specs {
+		if ps.Partition() != "system" {
+			delete(specs, k)
+		}
+	}
+}
diff --git a/java/androidmk.go b/java/androidmk.go
index b930441..80b828d 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -409,22 +409,6 @@
 				entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
 			},
 		},
-		ExtraFooters: []android.AndroidMkExtraFootersFunc{
-			func(w io.Writer, name, prefix, moduleDir string) {
-				if app.noticeOutputs.Merged.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE")
-				}
-				if app.noticeOutputs.TxtOutput.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.TxtOutput.String(), app.installApkName+"_NOTICE.txt")
-				}
-				if app.noticeOutputs.HtmlOutput.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.HtmlOutput.String(), app.installApkName+"_NOTICE.html")
-				}
-			},
-		},
 	}}
 }
 
diff --git a/java/app.go b/java/app.go
index 96fd61a..5b1daa4 100755
--- a/java/app.go
+++ b/java/app.go
@@ -19,7 +19,6 @@
 
 import (
 	"path/filepath"
-	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -164,8 +163,6 @@
 
 	additionalAaptFlags []string
 
-	noticeOutputs android.NoticeOutputs
-
 	overriddenManifestPackageName string
 
 	android.ApexBundleDepsInfo
@@ -523,53 +520,6 @@
 	return jniSymbols
 }
 
-func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) {
-	// Collect NOTICE files from all dependencies.
-	seenModules := make(map[android.Module]bool)
-	noticePathSet := make(map[android.Path]bool)
-
-	ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
-		// Have we already seen this?
-		if _, ok := seenModules[child]; ok {
-			return false
-		}
-		seenModules[child] = true
-
-		// Skip host modules.
-		if child.Target().Os.Class == android.Host {
-			return false
-		}
-
-		paths := child.(android.Module).NoticeFiles()
-		if len(paths) > 0 {
-			for _, path := range paths {
-				noticePathSet[path] = true
-			}
-		}
-		return true
-	})
-
-	// If the app has one, add it too.
-	if len(a.NoticeFiles()) > 0 {
-		for _, path := range a.NoticeFiles() {
-			noticePathSet[path] = true
-		}
-	}
-
-	if len(noticePathSet) == 0 {
-		return
-	}
-	var noticePaths []android.Path
-	for path := range noticePathSet {
-		noticePaths = append(noticePaths, path)
-	}
-	sort.Slice(noticePaths, func(i, j int) bool {
-		return noticePaths[i].String() < noticePaths[j].String()
-	})
-
-	a.noticeOutputs = android.BuildNoticeOutput(ctx, a.installDir, a.installApkName+".apk", noticePaths)
-}
-
 // Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it
 // isn't a cert module reference. Also checks and enforces system cert restriction if applicable.
 func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate {
@@ -636,9 +586,10 @@
 	}
 	a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
 
-	a.noticeBuildActions(ctx)
+	noticeFile := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+	android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile)
 	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
-		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
+		a.aapt.noticeFile = android.OptionalPathForPath(noticeFile)
 	}
 
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
@@ -1450,7 +1401,8 @@
 }
 
 type bazelAndroidAppAttributes struct {
-	*javaLibraryAttributes
+	*javaCommonAttributes
+	Deps             bazel.LabelListAttribute
 	Manifest         bazel.Label
 	Custom_package   *string
 	Resource_files   bazel.LabelListAttribute
@@ -1460,7 +1412,16 @@
 
 // ConvertWithBp2build is used to convert android_app to Bazel.
 func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	libAttrs := a.convertLibraryAttrsBp2Build(ctx)
+	commonAttrs, depLabels := a.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	if !commonAttrs.Srcs.IsEmpty() {
+		deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+	} else if !deps.IsEmpty() || !depLabels.StaticDeps.IsEmpty() {
+		ctx.ModuleErrorf("android_app has dynamic or static dependencies but no sources." +
+			" Bazel does not allow direct dependencies without sources nor exported" +
+			" dependencies on android_binary rule.")
+	}
 
 	manifest := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 
@@ -1483,7 +1444,8 @@
 	}
 
 	attrs := &bazelAndroidAppAttributes{
-		libAttrs,
+		commonAttrs,
+		deps,
 		android.BazelLabelForModuleSrcSingle(ctx, manifest),
 		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
 		a.overridableAppProperties.Package_name,
diff --git a/java/app_test.go b/java/app_test.go
index 16bbec1..08baf54 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -27,7 +27,6 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
-	"android/soong/genrule"
 )
 
 // testApp runs tests using the prepareForJavaTest
@@ -2722,116 +2721,6 @@
 	}
 }
 
-func TestEmbedNotice(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		PrepareForTestWithJavaDefaultModules,
-		cc.PrepareForTestWithCcDefaultModules,
-		genrule.PrepareForTestWithGenRuleBuildComponents,
-		android.MockFS{
-			"APP_NOTICE":     nil,
-			"GENRULE_NOTICE": nil,
-			"LIB_NOTICE":     nil,
-			"TOOL_NOTICE":    nil,
-		}.AddToFixture(),
-	).RunTestWithBp(t, `
-		android_app {
-			name: "foo",
-			srcs: ["a.java"],
-			static_libs: ["javalib"],
-			jni_libs: ["libjni"],
-			notice: "APP_NOTICE",
-			embed_notices: true,
-			sdk_version: "current",
-		}
-
-		// No embed_notice flag
-		android_app {
-			name: "bar",
-			srcs: ["a.java"],
-			jni_libs: ["libjni"],
-			notice: "APP_NOTICE",
-			sdk_version: "current",
-		}
-
-		// No NOTICE files
-		android_app {
-			name: "baz",
-			srcs: ["a.java"],
-			embed_notices: true,
-			sdk_version: "current",
-		}
-
-		cc_library {
-			name: "libjni",
-			system_shared_libs: [],
-			stl: "none",
-			notice: "LIB_NOTICE",
-			sdk_version: "current",
-		}
-
-		java_library {
-			name: "javalib",
-			srcs: [
-				":gen",
-			],
-			sdk_version: "current",
-		}
-
-		genrule {
-			name: "gen",
-			tools: ["gentool"],
-			out: ["gen.java"],
-			notice: "GENRULE_NOTICE",
-		}
-
-		java_binary_host {
-			name: "gentool",
-			srcs: ["b.java"],
-			notice: "TOOL_NOTICE",
-		}
-	`)
-
-	// foo has NOTICE files to process, and embed_notices is true.
-	foo := result.ModuleForTests("foo", "android_common")
-	// verify merge notices rule.
-	mergeNotices := foo.Rule("mergeNoticesRule")
-	noticeInputs := mergeNotices.Inputs.Strings()
-	// TOOL_NOTICE should be excluded as it's a host module.
-	if len(mergeNotices.Inputs) != 3 {
-		t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs)
-	}
-	if !inList("APP_NOTICE", noticeInputs) {
-		t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	if !inList("LIB_NOTICE", noticeInputs) {
-		t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	if !inList("GENRULE_NOTICE", noticeInputs) {
-		t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
-	res := foo.Output("package-res.apk")
-	aapt2Flags := res.Args["flags"]
-	e := "-A out/soong/.intermediates/foo/android_common/NOTICE"
-	android.AssertStringDoesContain(t, "expected.apkPath", aapt2Flags, e)
-
-	// bar has NOTICE files to process, but embed_notices is not set.
-	bar := result.ModuleForTests("bar", "android_common")
-	res = bar.Output("package-res.apk")
-	aapt2Flags = res.Args["flags"]
-	e = "-A out/soong/.intermediates/bar/android_common/NOTICE"
-	android.AssertStringDoesNotContain(t, "bar shouldn't have the asset dir flag for NOTICE", aapt2Flags, e)
-
-	// baz's embed_notice is true, but it doesn't have any NOTICE files.
-	baz := result.ModuleForTests("baz", "android_common")
-	res = baz.Output("package-res.apk")
-	aapt2Flags = res.Args["flags"]
-	e = "-A out/soong/.intermediates/baz/android_common/NOTICE"
-	if strings.Contains(aapt2Flags, e) {
-		t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e)
-	}
-}
-
 func TestUncompressDex(t *testing.T) {
 	testCases := []struct {
 		name string
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 2921c3e..e7aeeb8 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -477,7 +477,7 @@
 		Flag("--format=v2").
 		FlagWithArg("--repeat-errors-max ", "10").
 		FlagWithArg("--hide ", "UnresolvedImport").
-		FlagWithArg("--hide ", "InvalidNullability").
+		FlagWithArg("--hide ", "InvalidNullabilityOverride").
 		// b/223382732
 		FlagWithArg("--hide ", "ChangedDefault")
 
diff --git a/java/java.go b/java/java.go
index ddef34d..ecbbc32 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1725,6 +1725,7 @@
 
 	android.InitPrebuiltModule(module, &module.properties.Jars)
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostSupported)
 	return module
 }
@@ -2023,13 +2024,24 @@
 	}
 }
 
-type javaLibraryAttributes struct {
+type javaCommonAttributes struct {
 	Srcs      bazel.LabelListAttribute
-	Deps      bazel.LabelListAttribute
+	Plugins   bazel.LabelListAttribute
 	Javacopts bazel.StringListAttribute
 }
 
-func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) *javaLibraryAttributes {
+type javaDependencyLabels struct {
+	// Dependencies which DO NOT contribute to the API visible to upstream dependencies.
+	Deps bazel.LabelListAttribute
+	// Dependencies which DO contribute to the API visible to upstream dependencies.
+	StaticDeps bazel.LabelListAttribute
+}
+
+// convertLibraryAttrsBp2Build converts a few shared attributes from java_* modules
+// and also separates dependencies into dynamic dependencies and static dependencies.
+// Each corresponding Bazel target type, can have a different method for handling
+// dynamic vs. static dependencies, and so these are returned to the calling function.
+func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) (*javaCommonAttributes, *javaDependencyLabels) {
 	var srcs bazel.LabelListAttribute
 	archVariantProps := m.GetArchVariantProperties(ctx, &CommonProperties{})
 	for axis, configToProps := range archVariantProps {
@@ -2048,40 +2060,71 @@
 		protoSrcPartition: android.ProtoSrcLabelPartition,
 	})
 
-	attrs := &javaLibraryAttributes{
+	commonAttrs := &javaCommonAttributes{
 		Srcs: srcPartitions[javaSrcPartition],
+		Plugins: bazel.MakeLabelListAttribute(
+			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
+		),
 	}
 
 	if m.properties.Javacflags != nil {
-		attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
+		commonAttrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
 	}
 
+	depLabels := &javaDependencyLabels{}
+
 	var deps bazel.LabelList
-	sdkVersion := m.SdkVersion(ctx)
-	if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
-		// TODO(b/220869005) remove forced dependency on current public android.jar
-		deps.Add(&bazel.Label{Label: "//prebuilts/sdk:public_current_android_sdk_java_import"})
-	}
 	if m.properties.Libs != nil {
 		deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
 	}
+
+	var staticDeps bazel.LabelList
 	if m.properties.Static_libs != nil {
-		//TODO(b/217236083) handle static libs similarly to Soong
-		deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
+		staticDeps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
 	}
 
-	protoDeps := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
-	if protoDeps != nil {
-		deps.Add(protoDeps)
-	}
+	protoDepLabel := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
+	// Soong does not differentiate between a java_library and the Bazel equivalent of
+	// a java_proto_library + proto_library pair. Instead, in Soong proto sources are
+	// listed directly in the srcs of a java_library, and the classes produced
+	// by protoc are included directly in the resulting JAR. Thus upstream dependencies
+	// that depend on a java_library with proto sources can link directly to the protobuf API,
+	// and so this should be a static dependency.
+	staticDeps.Add(protoDepLabel)
 
-	attrs.Deps = bazel.MakeLabelListAttribute(deps)
+	depLabels.Deps = bazel.MakeLabelListAttribute(deps)
+	depLabels.StaticDeps = bazel.MakeLabelListAttribute(staticDeps)
 
-	return attrs
+	return commonAttrs, depLabels
+}
+
+type javaLibraryAttributes struct {
+	*javaCommonAttributes
+	Deps    bazel.LabelListAttribute
+	Exports bazel.LabelListAttribute
 }
 
 func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
-	attrs := m.convertLibraryAttrsBp2Build(ctx)
+	commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	if !commonAttrs.Srcs.IsEmpty() {
+		deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+
+		sdkVersion := m.SdkVersion(ctx)
+		if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
+			// TODO(b/220869005) remove forced dependency on current public android.jar
+			deps.Add(bazel.MakeLabelAttribute("//prebuilts/sdk:public_current_android_sdk_java_import"))
+		}
+	} else if !depLabels.Deps.IsEmpty() {
+		ctx.ModuleErrorf("Module has direct dependencies but no sources. Bazel will not allow this.")
+	}
+
+	attrs := &javaLibraryAttributes{
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Exports:              depLabels.StaticDeps,
+	}
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "java_library",
@@ -2092,15 +2135,30 @@
 }
 
 type javaBinaryHostAttributes struct {
-	Srcs       bazel.LabelListAttribute
-	Deps       bazel.LabelListAttribute
-	Main_class string
-	Jvm_flags  bazel.StringListAttribute
-	Javacopts  bazel.StringListAttribute
+	*javaCommonAttributes
+	Deps         bazel.LabelListAttribute
+	Runtime_deps bazel.LabelListAttribute
+	Main_class   string
+	Jvm_flags    bazel.StringListAttribute
 }
 
 // JavaBinaryHostBp2Build is for java_binary_host bp2build.
 func javaBinaryHostBp2Build(ctx android.TopDownMutatorContext, m *Binary) {
+	commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	deps.Append(depLabels.StaticDeps)
+	if m.binaryProperties.Jni_libs != nil {
+		deps.Append(bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, m.binaryProperties.Jni_libs)))
+	}
+
+	var runtimeDeps bazel.LabelListAttribute
+	if commonAttrs.Srcs.IsEmpty() {
+		// if there are no sources, then the dependencies can only be used at runtime
+		runtimeDeps = deps
+		deps = bazel.LabelListAttribute{}
+	}
+
 	mainClass := ""
 	if m.binaryProperties.Main_class != nil {
 		mainClass = *m.binaryProperties.Main_class
@@ -2112,26 +2170,12 @@
 		}
 		mainClass = mainClassInManifest
 	}
-	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs))
+
 	attrs := &javaBinaryHostAttributes{
-		Srcs:       srcs,
-		Main_class: mainClass,
-	}
-
-	if m.properties.Javacflags != nil {
-		attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
-	}
-
-	// Attribute deps
-	deps := []string{}
-	if m.properties.Static_libs != nil {
-		deps = append(deps, m.properties.Static_libs...)
-	}
-	if m.binaryProperties.Jni_libs != nil {
-		deps = append(deps, m.binaryProperties.Jni_libs...)
-	}
-	if len(deps) > 0 {
-		attrs.Deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, deps))
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Runtime_deps:         runtimeDeps,
+		Main_class:           mainClass,
 	}
 
 	// Attribute jvm_flags
@@ -2174,8 +2218,16 @@
 
 // java_import bp2Build converter.
 func (i *Import) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	//TODO(b/209577426): Support multiple arch variants
-	jars := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, i.properties.Jars, []string(nil)))
+	var jars bazel.LabelListAttribute
+	archVariantProps := i.GetArchVariantProperties(ctx, &ImportProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*ImportProperties); ok {
+				archJars := android.BazelLabelForModuleSrcExcludes(ctx, archProps.Jars, []string(nil))
+				jars.SetSelectValue(axis, config, archJars)
+			}
+		}
+	}
 
 	attrs := &bazelJavaImportAttributes{
 		Jars: jars,
diff --git a/java/plugin.go b/java/plugin.go
index 4b174b9..123dbd4 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -58,27 +58,32 @@
 }
 
 type pluginAttributes struct {
-	*javaLibraryAttributes
-	Processor_class        *string
-	Target_compatible_with bazel.LabelListAttribute
+	*javaCommonAttributes
+	Deps            bazel.LabelListAttribute
+	Processor_class *string
 }
 
 // ConvertWithBp2build is used to convert android_app to Bazel.
 func (p *Plugin) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	libAttrs := p.convertLibraryAttrsBp2Build(ctx)
-	attrs := &pluginAttributes{
-		libAttrs,
-		nil,
-		bazel.LabelListAttribute{},
+	pluginName := p.Name()
+	commonAttrs, depLabels := p.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	deps.Append(depLabels.StaticDeps)
+
+	var processorClass *string
+	if p.pluginProperties.Processor_class != nil {
+		processorClass = p.pluginProperties.Processor_class
 	}
 
-	if p.pluginProperties.Processor_class != nil {
-		attrs.Processor_class = p.pluginProperties.Processor_class
+	attrs := &pluginAttributes{
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Processor_class:      processorClass,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class: "java_plugin",
 	}
-
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: p.Name()}, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: pluginName}, attrs)
 }
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index bc36b20..30700dd 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -26,6 +26,7 @@
 		"packages/modules/Virtualization",
 		"platform_testing/tests/codecoverage/native/rust",
 		"prebuilts/rust",
+		"system/core/debuggerd/rust",
 		"system/core/libstats/pull_rust",
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go
index 7608349..4d7c422 100644
--- a/rust/config/x86_linux_host.go
+++ b/rust/config/x86_linux_host.go
@@ -42,8 +42,6 @@
 		"-nodefaultlibs",
 		"-nostdlib",
 		"-Wl,--no-dynamic-linker",
-		// for unwind
-		"-lgcc", "-lgcc_eh",
 	}
 	linuxX86Rustflags   = []string{}
 	linuxX86Linkflags   = []string{}
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 6f17272..6cdd07d 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -22,6 +22,7 @@
 	android.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
 	android.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
+	android.RegisterModuleType("rust_prebuilt_proc_macro", PrebuiltProcMacroFactory)
 }
 
 type PrebuiltProperties struct {
@@ -38,8 +39,42 @@
 	Properties PrebuiltProperties
 }
 
+type prebuiltProcMacroDecorator struct {
+	android.Prebuilt
+
+	*procMacroDecorator
+	Properties PrebuiltProperties
+}
+
+func PrebuiltProcMacroFactory() android.Module {
+	module, _ := NewPrebuiltProcMacro(android.HostSupportedNoCross)
+	return module.Init()
+}
+
+type rustPrebuilt interface {
+	prebuiltSrcs() []string
+	prebuilt() *android.Prebuilt
+}
+
+func NewPrebuiltProcMacro(hod android.HostOrDeviceSupported) (*Module, *prebuiltProcMacroDecorator) {
+	module, library := NewProcMacro(hod)
+	prebuilt := &prebuiltProcMacroDecorator{
+		procMacroDecorator: library,
+	}
+	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
+	return module, prebuilt
+}
+
 var _ compiler = (*prebuiltLibraryDecorator)(nil)
 var _ exportedFlagsProducer = (*prebuiltLibraryDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltLibraryDecorator)(nil)
+
+var _ compiler = (*prebuiltProcMacroDecorator)(nil)
+var _ exportedFlagsProducer = (*prebuiltProcMacroDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltProcMacroDecorator)(nil)
 
 func PrebuiltLibraryFactory() android.Module {
 	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
@@ -56,7 +91,7 @@
 	return module.Init()
 }
 
-func addSrcSupplier(module android.PrebuiltInterface, prebuilt *prebuiltLibraryDecorator) {
+func addSrcSupplier(module android.PrebuiltInterface, prebuilt rustPrebuilt) {
 	srcsSupplier := func(_ android.BaseModuleContext, _ android.Module) []string {
 		return prebuilt.prebuiltSrcs()
 	}
@@ -152,3 +187,44 @@
 func (prebuilt *prebuiltLibraryDecorator) prebuilt() *android.Prebuilt {
 	return &prebuilt.Prebuilt
 }
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuiltSrcs() []string {
+	srcs := prebuilt.Properties.Srcs
+	return srcs
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuilt() *android.Prebuilt {
+	return &prebuilt.Prebuilt
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerProps() []interface{} {
+	return append(prebuilt.procMacroDecorator.compilerProps(),
+		&prebuilt.Properties)
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
+	prebuilt.flagExporter.setProvider(ctx)
+
+	srcPath, paths := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+	if len(paths) > 0 {
+		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
+	}
+	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
+	return srcPath
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+
+	return android.OptionalPath{}
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
+	deps = prebuilt.baseCompiler.compilerDeps(ctx, deps)
+	return deps
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) nativeCoverage() bool {
+	return false
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 974c096..f8a4bbd 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -33,6 +33,7 @@
 }
 
 type procMacroInterface interface {
+	ProcMacro() bool
 }
 
 var _ compiler = (*procMacroDecorator)(nil)
@@ -90,6 +91,10 @@
 	return rlibAutoDep
 }
 
+func (procMacro *procMacroDecorator) ProcMacro() bool {
+	return true
+}
+
 func (procMacro *procMacroDecorator) everInstallable() bool {
 	// Proc_macros are never installed
 	return false
diff --git a/rust/rust.go b/rust/rust.go
index 1c718a4..d627261 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -27,6 +27,7 @@
 	cc_config "android/soong/cc/config"
 	"android/soong/fuzz"
 	"android/soong/rust/config"
+	"android/soong/snapshot"
 )
 
 var pctx = android.NewPackageContext("android/soong/rust")
@@ -806,6 +807,13 @@
 	return mod.Properties.Installable
 }
 
+func (mod *Module) ProcMacro() bool {
+	if pm, ok := mod.compiler.(procMacroInterface); ok {
+		return pm.ProcMacro()
+	}
+	return false
+}
+
 func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if mod.cachedToolchain == nil {
 		mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -920,12 +928,13 @@
 		}
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
+		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) && !mod.ProcMacro() {
 			// If the module has been specifically configure to not be installed then
 			// hide from make as otherwise it will break when running inside make as the
 			// output path to install will not be specified. Not all uninstallable
 			// modules can be hidden from make as some are needed for resolving make
-			// side dependencies.
+			// side dependencies. In particular, proc-macros need to be captured in the
+			// host snapshot.
 			mod.HideFromMake()
 		} else if !mod.installable(apexInfo) {
 			mod.SkipInstall()
@@ -1046,7 +1055,7 @@
 }
 
 func (mod *Module) Prebuilt() *android.Prebuilt {
-	if p, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
+	if p, ok := mod.compiler.(rustPrebuilt); ok {
 		return p.prebuilt()
 	}
 	return nil
@@ -1501,6 +1510,7 @@
 }
 
 var _ android.HostToolProvider = (*Module)(nil)
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
 
 func (mod *Module) HostToolPath() android.OptionalPath {
 	if !mod.Host() {
@@ -1508,6 +1518,10 @@
 	}
 	if binary, ok := mod.compiler.(*binaryDecorator); ok {
 		return android.OptionalPathForPath(binary.baseCompiler.path)
+	} else if pm, ok := mod.compiler.(*procMacroDecorator); ok {
+		// Even though proc-macros aren't strictly "tools", since they target the compiler
+		// and act as compiler plugins, we treat them similarly.
+		return android.OptionalPathForPath(pm.baseCompiler.path)
 	}
 	return android.OptionalPath{}
 }
diff --git a/scripts/generate-notice-files.py b/scripts/generate-notice-files.py
deleted file mode 100755
index 1b4acfa..0000000
--- a/scripts/generate-notice-files.py
+++ /dev/null
@@ -1,272 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2012 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.
-"""
-Usage: generate-notice-files --text-output [plain text output file] \
-               --html-output [html output file] \
-               --xml-output [xml output file] \
-               -t [file title] -s [directory of notices]
-
-Generate the Android notice files, including both text and html files.
-
--h to display this usage message and exit.
-"""
-from collections import defaultdict
-import argparse
-import hashlib
-import itertools
-import os
-import os.path
-import re
-import struct
-import sys
-
-MD5_BLOCKSIZE = 1024 * 1024
-HTML_ESCAPE_TABLE = {
-    b"&": b"&amp;",
-    b'"': b"&quot;",
-    b"'": b"&apos;",
-    b">": b"&gt;",
-    b"<": b"&lt;",
-    }
-
-def md5sum(filename):
-    """Calculate an MD5 of the file given by FILENAME,
-    and return hex digest as a string.
-    Output should be compatible with md5sum command"""
-
-    f = open(filename, "rb")
-    sum = hashlib.md5()
-    while 1:
-        block = f.read(MD5_BLOCKSIZE)
-        if not block:
-            break
-        sum.update(block)
-    f.close()
-    return sum.hexdigest()
-
-
-def html_escape(text):
-    """Produce entities within text."""
-    # Using for i in text doesn't work since i will be an int, not a byte.
-    # There are multiple ways to solve this, but the most performant way
-    # to iterate over a byte array is to use unpack. Using the
-    # for i in range(len(text)) and using that to get a byte using array
-    # slices is twice as slow as this method.
-    return b"".join(HTML_ESCAPE_TABLE.get(i,i) for i in struct.unpack(str(len(text)) + 'c', text))
-
-HTML_OUTPUT_CSS=b"""
-<style type="text/css">
-body { padding: 0; font-family: sans-serif; }
-.same-license { background-color: #eeeeee; border-top: 20px solid white; padding: 10px; }
-.label { font-weight: bold; }
-.file-list { margin-left: 1em; color: blue; }
-</style>
-
-"""
-
-def combine_notice_files_html(file_hash, input_dir, output_filename):
-    """Combine notice files in FILE_HASH and output a HTML version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
-    # Set up a filename to row id table (anchors inside tables don't work in
-    # most browsers, but href's to table row ids do)
-    id_table = {}
-    id_count = 0
-    for value in file_hash:
-        for filename in value:
-             id_table[filename] = id_count
-        id_count += 1
-
-    # Open the output file, and output the header pieces
-    output_file = open(output_filename, "wb")
-
-    output_file.write(b"<html><head>\n")
-    output_file.write(HTML_OUTPUT_CSS)
-    output_file.write(b'</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">\n')
-
-    # Output our table of contents
-    output_file.write(b'<div class="toc">\n')
-    output_file.write(b"<ul>\n")
-
-    # Flatten the list of lists into a single list of filenames
-    sorted_filenames = sorted(itertools.chain.from_iterable(file_hash))
-
-    # Print out a nice table of contents
-    for filename in sorted_filenames:
-        stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        output_file.write(('<li><a href="#id%d">%s</a></li>\n' % (id_table.get(filename), stripped_filename)).encode())
-
-    output_file.write(b"</ul>\n")
-    output_file.write(b"</div><!-- table of contents -->\n")
-    # Output the individual notice file lists
-    output_file.write(b'<table cellpadding="0" cellspacing="0" border="0">\n')
-    for value in file_hash:
-        output_file.write(('<tr id="id%d"><td class="same-license">\n' % id_table.get(value[0])).encode())
-        output_file.write(b'<div class="label">Notices for file(s):</div>\n')
-        output_file.write(b'<div class="file-list">\n')
-        for filename in value:
-            output_file.write(("%s <br/>\n" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))).encode())
-        output_file.write(b"</div><!-- file-list -->\n\n")
-        output_file.write(b'<pre class="license-text">\n')
-        with open(value[0], "rb") as notice_file:
-            output_file.write(html_escape(notice_file.read()))
-        output_file.write(b"\n</pre><!-- license-text -->\n")
-        output_file.write(b"</td></tr><!-- same-license -->\n\n\n\n")
-
-    # Finish off the file output
-    output_file.write(b"</table>\n")
-    output_file.write(b"</body></html>\n")
-    output_file.close()
-
-def combine_notice_files_text(file_hash, input_dir, output_filename, file_title):
-    """Combine notice files in FILE_HASH and output a text version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-    output_file = open(output_filename, "wb")
-    output_file.write(file_title.encode())
-    output_file.write(b"\n")
-    for value in file_hash:
-        output_file.write(b"============================================================\n")
-        output_file.write(b"Notices for file(s):\n")
-        for filename in value:
-            output_file.write(SRC_DIR_STRIP_RE.sub(r"\1", filename).encode())
-            output_file.write(b"\n")
-        output_file.write(b"------------------------------------------------------------\n")
-        with open(value[0], "rb") as notice_file:
-            output_file.write(notice_file.read())
-            output_file.write(b"\n")
-    output_file.close()
-
-def combine_notice_files_xml(files_with_same_hash, input_dir, output_filename):
-    """Combine notice files in FILE_HASH and output a XML version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
-    # Set up a filename to row id table (anchors inside tables don't work in
-    # most browsers, but href's to table row ids do)
-    id_table = {}
-    for file_key, files in files_with_same_hash.items():
-        for filename in files:
-             id_table[filename] = file_key
-
-    # Open the output file, and output the header pieces
-    output_file = open(output_filename, "wb")
-
-    output_file.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
-    output_file.write(b"<licenses>\n")
-
-    # Flatten the list of lists into a single list of filenames
-    sorted_filenames = sorted(list(id_table))
-
-    # Print out a nice table of contents
-    for filename in sorted_filenames:
-        stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        output_file.write(('<file-name contentId="%s">%s</file-name>\n' % (id_table.get(filename), stripped_filename)).encode())
-    output_file.write(b"\n\n")
-
-    processed_file_keys = []
-    # Output the individual notice file lists
-    for filename in sorted_filenames:
-        file_key = id_table.get(filename)
-        if file_key in processed_file_keys:
-            continue
-        processed_file_keys.append(file_key)
-
-        output_file.write(('<file-content contentId="%s"><![CDATA[' % file_key).encode())
-        with open(filename, "rb") as notice_file:
-            output_file.write(html_escape(notice_file.read()))
-        output_file.write(b"]]></file-content>\n\n")
-
-    # Finish off the file output
-    output_file.write(b"</licenses>\n")
-    output_file.close()
-
-def get_args():
-    parser = argparse.ArgumentParser()
-    parser.add_argument(
-        '--text-output', required=True,
-        help='The text output file path.')
-    parser.add_argument(
-        '--html-output',
-        help='The html output file path.')
-    parser.add_argument(
-        '--xml-output',
-        help='The xml output file path.')
-    parser.add_argument(
-        '-t', '--title', required=True,
-        help='The file title.')
-    parser.add_argument(
-        '-s', '--source-dir', required=True,
-        help='The directory containing notices.')
-    parser.add_argument(
-        '-i', '--included-subdirs', action='append',
-        help='The sub directories which should be included.')
-    parser.add_argument(
-        '-e', '--excluded-subdirs', action='append',
-        help='The sub directories which should be excluded.')
-    return parser.parse_args()
-
-def main(argv):
-    args = get_args()
-
-    txt_output_file = args.text_output
-    html_output_file = args.html_output
-    xml_output_file = args.xml_output
-    file_title = args.title
-    included_subdirs = []
-    excluded_subdirs = []
-    if args.included_subdirs is not None:
-        included_subdirs = args.included_subdirs
-    if args.excluded_subdirs is not None:
-        excluded_subdirs = args.excluded_subdirs
-
-    # Find all the notice files and md5 them
-    input_dir = os.path.normpath(args.source_dir)
-    files_with_same_hash = defaultdict(list)
-    for root, dir, files in os.walk(input_dir):
-        for file in files:
-            matched = True
-            if len(included_subdirs) > 0:
-                matched = False
-                for subdir in included_subdirs:
-                    if (root == (input_dir + '/' + subdir) or
-                        root.startswith(input_dir + '/' + subdir + '/')):
-                        matched = True
-                        break
-            elif len(excluded_subdirs) > 0:
-                for subdir in excluded_subdirs:
-                    if (root == (input_dir + '/' + subdir) or
-                        root.startswith(input_dir + '/' + subdir + '/')):
-                        matched = False
-                        break
-            if matched and file.endswith(".txt"):
-                filename = os.path.join(root, file)
-                file_md5sum = md5sum(filename)
-                files_with_same_hash[file_md5sum].append(filename)
-
-    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(list(files_with_same_hash))]
-
-    combine_notice_files_text(filesets, input_dir, txt_output_file, file_title)
-
-    if html_output_file is not None:
-        combine_notice_files_html(filesets, input_dir, html_output_file)
-
-    if xml_output_file is not None:
-        combine_notice_files_xml(files_with_same_hash, input_dir, xml_output_file)
-
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index 8a47c5d..07878f9 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -19,6 +19,52 @@
 }
 
 python_binary_host {
+    name: "analyze_bcpf",
+    main: "analyze_bcpf.py",
+    srcs: ["analyze_bcpf.py"],
+    // Make sure that the bpmodify tool is built.
+    data: [":bpmodify"],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
+
+python_test_host {
+    name: "analyze_bcpf_test",
+    main: "analyze_bcpf_test.py",
+    srcs: [
+        "analyze_bcpf.py",
+        "analyze_bcpf_test.py",
+    ],
+    // Make sure that the bpmodify tool is built.
+    data: [":bpmodify"],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_binary_host {
     name: "merge_csv",
     main: "merge_csv.py",
     srcs: ["merge_csv.py"],
diff --git a/scripts/hiddenapi/analyze_bcpf.py b/scripts/hiddenapi/analyze_bcpf.py
new file mode 100644
index 0000000..1ad8d07
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf.py
@@ -0,0 +1,1336 @@
+#!/usr/bin/env -S python -u
+#
+# Copyright (C) 2022 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.
+"""Analyze bootclasspath_fragment usage."""
+import argparse
+import dataclasses
+import enum
+import json
+import logging
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import typing
+from enum import Enum
+
+import sys
+
+from signature_trie import signature_trie
+
+_STUB_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-stub-flags.txt"
+
+_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-flags.csv"
+
+_INCONSISTENT_FLAGS = "ERROR: Hidden API flags are inconsistent:"
+
+
+class BuildOperation:
+
+    def __init__(self, popen):
+        self.popen = popen
+        self.returncode = None
+
+    def lines(self):
+        """Return an iterator over the lines output by the build operation.
+
+        The lines have had any trailing white space, including the newline
+        stripped.
+        """
+        return newline_stripping_iter(self.popen.stdout.readline)
+
+    def wait(self, *args, **kwargs):
+        self.popen.wait(*args, **kwargs)
+        self.returncode = self.popen.returncode
+
+
+@dataclasses.dataclass()
+class FlagDiffs:
+    """Encapsulates differences in flags reported by the build"""
+
+    # Map from member signature to the (module flags, monolithic flags)
+    diffs: typing.Dict[str, typing.Tuple[str, str]]
+
+
+@dataclasses.dataclass()
+class ModuleInfo:
+    """Provides access to the generated module-info.json file.
+
+    This is used to find the location of the file within which specific modules
+    are defined.
+    """
+
+    modules: typing.Dict[str, typing.Dict[str, typing.Any]]
+
+    @staticmethod
+    def load(filename):
+        with open(filename, "r", encoding="utf8") as f:
+            j = json.load(f)
+            return ModuleInfo(j)
+
+    def _module(self, module_name):
+        """Find module by name in module-info.json file"""
+        if module_name in self.modules:
+            return self.modules[module_name]
+
+        raise Exception(f"Module {module_name} could not be found")
+
+    def module_path(self, module_name):
+        module = self._module(module_name)
+        # The "path" is actually a list of paths, one for each class of module
+        # but as the modules are all created from bp files if a module does
+        # create multiple classes of make modules they should all have the same
+        # path.
+        paths = module["path"]
+        unique_paths = set(paths)
+        if len(unique_paths) != 1:
+            raise Exception(f"Expected module '{module_name}' to have a "
+                            f"single unique path but found {unique_paths}")
+        return paths[0]
+
+
+def extract_indent(line):
+    return re.match(r"([ \t]*)", line).group(1)
+
+
+_SPECIAL_PLACEHOLDER: str = "SPECIAL_PLACEHOLDER"
+
+
+@dataclasses.dataclass
+class BpModifyRunner:
+
+    bpmodify_path: str
+
+    def add_values_to_property(self, property_name, values, module_name,
+                               bp_file):
+        cmd = [
+            self.bpmodify_path, "-a", values, "-property", property_name, "-m",
+            module_name, "-w", bp_file, bp_file
+        ]
+
+        logging.debug(" ".join(cmd))
+        subprocess.run(
+            cmd,
+            stderr=subprocess.STDOUT,
+            stdout=log_stream_for_subprocess(),
+            check=True)
+
+
+@dataclasses.dataclass
+class FileChange:
+    path: str
+
+    description: str
+
+    def __lt__(self, other):
+        return self.path < other.path
+
+
+class PropertyChangeAction(Enum):
+    """Allowable actions that are supported by HiddenApiPropertyChange."""
+
+    # New values are appended to any existing values.
+    APPEND = 1
+
+    # New values replace any existing values.
+    REPLACE = 2
+
+
+@dataclasses.dataclass
+class HiddenApiPropertyChange:
+
+    property_name: str
+
+    values: typing.List[str]
+
+    property_comment: str = ""
+
+    # The action that indicates how this change is applied.
+    action: PropertyChangeAction = PropertyChangeAction.APPEND
+
+    def snippet(self, indent):
+        snippet = "\n"
+        snippet += format_comment_as_text(self.property_comment, indent)
+        snippet += f"{indent}{self.property_name}: ["
+        if self.values:
+            snippet += "\n"
+            for value in self.values:
+                snippet += f'{indent}    "{value}",\n'
+            snippet += f"{indent}"
+        snippet += "],\n"
+        return snippet
+
+    def fix_bp_file(self, bcpf_bp_file, bcpf, bpmodify_runner: BpModifyRunner):
+        # Add an additional placeholder value to identify the modification that
+        # bpmodify makes.
+        bpmodify_values = [_SPECIAL_PLACEHOLDER]
+
+        if self.action == PropertyChangeAction.APPEND:
+            # If adding the values to the existing values then pass the new
+            # values to bpmodify.
+            bpmodify_values.extend(self.values)
+        elif self.action == PropertyChangeAction.REPLACE:
+            # If replacing the existing values then it is not possible to use
+            # bpmodify for that directly. It could be used twice to remove the
+            # existing property and then add a new one but that does not remove
+            # any related comments and loses the position of the existing
+            # property as the new property is always added to the end of the
+            # containing block.
+            #
+            # So, instead of passing the new values to bpmodify this this just
+            # adds an extra placeholder to force bpmodify to format the list
+            # across multiple lines to ensure a consistent structure for the
+            # code that removes all the existing values and adds the new ones.
+            #
+            # This placeholder has to be different to the other placeholder as
+            # bpmodify dedups values.
+            bpmodify_values.append(_SPECIAL_PLACEHOLDER + "_REPLACE")
+        else:
+            raise ValueError(f"unknown action {self.action}")
+
+        packages = ",".join(bpmodify_values)
+        bpmodify_runner.add_values_to_property(
+            f"hidden_api.{self.property_name}", packages, bcpf, bcpf_bp_file)
+
+        with open(bcpf_bp_file, "r", encoding="utf8") as tio:
+            lines = tio.readlines()
+            lines = [line.rstrip("\n") for line in lines]
+
+        if self.fixup_bpmodify_changes(bcpf_bp_file, lines):
+            with open(bcpf_bp_file, "w", encoding="utf8") as tio:
+                for line in lines:
+                    print(line, file=tio)
+
+    def fixup_bpmodify_changes(self, bcpf_bp_file, lines):
+        """Fixup the output of bpmodify.
+
+        The bpmodify tool does not support all the capabilities that this needs
+        so it is used to do what it can, including marking the place in the
+        Android.bp file where it makes its changes and then this gets passed a
+        list of lines from that file which it then modifies to complete the
+        change.
+
+        This analyzes the list of lines to find the indices of the significant
+        lines and then applies some changes. As those changes can insert and
+        delete lines (changing the indices of following lines) the changes are
+        generally done in reverse order starting from the end and working
+        towards the beginning. That ensures that the changes do not invalidate
+        the indices of following lines.
+        """
+
+        # Find the line containing the placeholder that has been inserted.
+        place_holder_index = -1
+        for i, line in enumerate(lines):
+            if _SPECIAL_PLACEHOLDER in line:
+                place_holder_index = i
+                break
+        if place_holder_index == -1:
+            logging.debug("Could not find %s in %s", _SPECIAL_PLACEHOLDER,
+                          bcpf_bp_file)
+            return False
+
+        # Remove the place holder. Do this before inserting the comment as that
+        # would change the location of the place holder in the list.
+        place_holder_line = lines[place_holder_index]
+        if place_holder_line.endswith("],"):
+            place_holder_line = place_holder_line.replace(
+                f'"{_SPECIAL_PLACEHOLDER}"', "")
+            lines[place_holder_index] = place_holder_line
+        else:
+            del lines[place_holder_index]
+
+        # Scan forward to the end of the property block to remove a blank line
+        # that bpmodify inserts.
+        end_property_array_index = -1
+        for i in range(place_holder_index, len(lines)):
+            line = lines[i]
+            if line.endswith("],"):
+                end_property_array_index = i
+                break
+        if end_property_array_index == -1:
+            logging.debug("Could not find end of property array in %s",
+                          bcpf_bp_file)
+            return False
+
+        # If bdmodify inserted a blank line afterwards then remove it.
+        if (not lines[end_property_array_index + 1] and
+                lines[end_property_array_index + 2].endswith("},")):
+            del lines[end_property_array_index + 1]
+
+        # Scan back to find the preceding property line.
+        property_line_index = -1
+        for i in range(place_holder_index, 0, -1):
+            line = lines[i]
+            if line.lstrip().startswith(f"{self.property_name}: ["):
+                property_line_index = i
+                break
+        if property_line_index == -1:
+            logging.debug("Could not find property line in %s", bcpf_bp_file)
+            return False
+
+        # If this change is replacing the existing values then they need to be
+        # removed and replaced with the new values. That will change the lines
+        # after the property but it is necessary to do here as the following
+        # code operates on earlier lines.
+        if self.action == PropertyChangeAction.REPLACE:
+            # This removes the existing values and replaces them with the new
+            # values.
+            indent = extract_indent(lines[property_line_index + 1])
+            insert = [f'{indent}"{x}",' for x in self.values]
+            lines[property_line_index + 1:end_property_array_index] = insert
+            if not self.values:
+                # If the property has no values then merge the ], onto the
+                # same line as the property name.
+                del lines[property_line_index + 1]
+                lines[property_line_index] = lines[property_line_index] + "],"
+
+        # Only insert a comment if the property does not already have a comment.
+        line_preceding_property = lines[(property_line_index - 1)]
+        if (self.property_comment and
+                not re.match("([ \t]+)// ", line_preceding_property)):
+            # Extract the indent from the property line and use it to format the
+            # comment.
+            indent = extract_indent(lines[property_line_index])
+            comment_lines = format_comment_as_lines(self.property_comment,
+                                                    indent)
+
+            # If the line before the comment is not blank then insert an extra
+            # blank line at the beginning of the comment.
+            if line_preceding_property:
+                comment_lines.insert(0, "")
+
+            # Insert the comment before the property.
+            lines[property_line_index:property_line_index] = comment_lines
+        return True
+
+
+@dataclasses.dataclass()
+class Result:
+    """Encapsulates the result of the analysis."""
+
+    # The diffs in the flags.
+    diffs: typing.Optional[FlagDiffs] = None
+
+    # The bootclasspath_fragment hidden API properties changes.
+    property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
+        default_factory=list)
+
+    # The list of file changes.
+    file_changes: typing.List[FileChange] = dataclasses.field(
+        default_factory=list)
+
+
+class ClassProvider(enum.Enum):
+    """The source of a class found during the hidden API processing"""
+    BCPF = "bcpf"
+    OTHER = "other"
+
+
+# A fake member to use when using the signature trie to compute the package
+# properties from hidden API flags. This is needed because while that
+# computation only cares about classes the trie expects a class to be an
+# interior node but without a member it makes the class a leaf node. That causes
+# problems when analyzing inner classes as the outer class is a leaf node for
+# its own entry but is used as an interior node for inner classes.
+_FAKE_MEMBER = ";->fake()V"
+
+
+@dataclasses.dataclass()
+class BcpfAnalyzer:
+    # Path to this tool.
+    tool_path: str
+
+    # Directory pointed to by ANDROID_BUILD_OUT
+    top_dir: str
+
+    # Directory pointed to by OUT_DIR of {top_dir}/out if that is not set.
+    out_dir: str
+
+    # Directory pointed to by ANDROID_PRODUCT_OUT.
+    product_out_dir: str
+
+    # The name of the bootclasspath_fragment module.
+    bcpf: str
+
+    # The name of the apex module containing {bcpf}, only used for
+    # informational purposes.
+    apex: str
+
+    # The name of the sdk module containing {bcpf}, only used for
+    # informational purposes.
+    sdk: str
+
+    # If true then this will attempt to automatically fix any issues that are
+    # found.
+    fix: bool = False
+
+    # All the signatures, loaded from all-flags.csv, initialized by
+    # load_all_flags().
+    _signatures: typing.Set[str] = dataclasses.field(default_factory=set)
+
+    # All the classes, loaded from all-flags.csv, initialized by
+    # load_all_flags().
+    _classes: typing.Set[str] = dataclasses.field(default_factory=set)
+
+    # Information loaded from module-info.json, initialized by
+    # load_module_info().
+    module_info: ModuleInfo = None
+
+    @staticmethod
+    def reformat_report_test(text):
+        return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
+
+    def report(self, text, **kwargs):
+        # Concatenate lines that are not separated by a blank line together to
+        # eliminate formatting applied to the supplied text to adhere to python
+        # line length limitations.
+        text = self.reformat_report_test(text)
+        logging.info("%s", text, **kwargs)
+
+    def run_command(self, cmd, *args, **kwargs):
+        cmd_line = " ".join(cmd)
+        logging.debug("Running %s", cmd_line)
+        subprocess.run(
+            cmd,
+            *args,
+            check=True,
+            cwd=self.top_dir,
+            stderr=subprocess.STDOUT,
+            stdout=log_stream_for_subprocess(),
+            text=True,
+            **kwargs)
+
+    @property
+    def signatures(self):
+        if not self._signatures:
+            raise Exception("signatures has not been initialized")
+        return self._signatures
+
+    @property
+    def classes(self):
+        if not self._classes:
+            raise Exception("classes has not been initialized")
+        return self._classes
+
+    def load_all_flags(self):
+        all_flags = self.find_bootclasspath_fragment_output_file(
+            "all-flags.csv")
+
+        # Extract the set of signatures and a separate set of classes produced
+        # by the bootclasspath_fragment.
+        with open(all_flags, "r", encoding="utf8") as f:
+            for line in newline_stripping_iter(f.readline):
+                signature = self.line_to_signature(line)
+                self._signatures.add(signature)
+                class_name = self.signature_to_class(signature)
+                self._classes.add(class_name)
+
+    def load_module_info(self):
+        module_info_file = os.path.join(self.product_out_dir,
+                                        "module-info.json")
+        self.report(f"""
+Making sure that {module_info_file} is up to date.
+""")
+        output = self.build_file_read_output(module_info_file)
+        lines = output.lines()
+        for line in lines:
+            logging.debug("%s", line)
+        output.wait(timeout=10)
+        if output.returncode:
+            raise Exception(f"Error building {module_info_file}")
+        abs_module_info_file = os.path.join(self.top_dir, module_info_file)
+        self.module_info = ModuleInfo.load(abs_module_info_file)
+
+    @staticmethod
+    def line_to_signature(line):
+        return line.split(",")[0]
+
+    @staticmethod
+    def signature_to_class(signature):
+        return signature.split(";->")[0]
+
+    @staticmethod
+    def to_parent_package(pkg_or_class):
+        return pkg_or_class.rsplit("/", 1)[0]
+
+    def module_path(self, module_name):
+        return self.module_info.module_path(module_name)
+
+    def module_out_dir(self, module_name):
+        module_path = self.module_path(module_name)
+        return os.path.join(self.out_dir, "soong/.intermediates", module_path,
+                            module_name)
+
+    def find_bootclasspath_fragment_output_file(self, basename, required=True):
+        # Find the output file of the bootclasspath_fragment with the specified
+        # base name.
+        found_file = ""
+        bcpf_out_dir = self.module_out_dir(self.bcpf)
+        for (dirpath, _, filenames) in os.walk(bcpf_out_dir):
+            for f in filenames:
+                if f == basename:
+                    found_file = os.path.join(dirpath, f)
+                    break
+        if not found_file and required:
+            raise Exception(f"Could not find {basename} in {bcpf_out_dir}")
+        return found_file
+
+    def analyze(self):
+        """Analyze a bootclasspath_fragment module.
+
+        Provides help in resolving any existing issues and provides
+        optimizations that can be applied.
+        """
+        self.report(f"Analyzing bootclasspath_fragment module {self.bcpf}")
+        self.report(f"""
+Run this tool to help initialize a bootclasspath_fragment module. Before you
+start make sure that:
+
+1. The current checkout is up to date.
+
+2. The environment has been initialized using lunch, e.g.
+   lunch aosp_arm64-userdebug
+
+3. You have added a bootclasspath_fragment module to the appropriate Android.bp
+file. Something like this:
+
+   bootclasspath_fragment {{
+     name: "{self.bcpf}",
+     contents: [
+       "...",
+     ],
+
+     // The bootclasspath_fragments that provide APIs on which this depends.
+     fragments: [
+       {{
+         apex: "com.android.art",
+         module: "art-bootclasspath-fragment",
+       }},
+     ],
+   }}
+
+4. You have added it to the platform_bootclasspath module in
+frameworks/base/boot/Android.bp. Something like this:
+
+   platform_bootclasspath {{
+     name: "platform-bootclasspath",
+     fragments: [
+       ...
+       {{
+         apex: "{self.apex}",
+         module: "{self.bcpf}",
+       }},
+     ],
+   }}
+
+5. You have added an sdk module. Something like this:
+
+   sdk {{
+     name: "{self.sdk}",
+     bootclasspath_fragments: ["{self.bcpf}"],
+   }}
+""")
+
+        # Make sure that the module-info.json file is up to date.
+        self.load_module_info()
+
+        self.report("""
+Cleaning potentially stale files.
+""")
+        # Remove the out/soong/hiddenapi files.
+        shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
+
+        # Remove any bootclasspath_fragment output files.
+        shutil.rmtree(self.module_out_dir(self.bcpf), ignore_errors=True)
+
+        self.build_monolithic_stubs_flags()
+
+        result = Result()
+
+        self.build_monolithic_flags(result)
+        self.analyze_hiddenapi_package_properties(result)
+        self.explain_how_to_check_signature_patterns()
+
+        # If there were any changes that need to be made to the Android.bp
+        # file then either apply or report them.
+        if result.property_changes:
+            bcpf_dir = self.module_info.module_path(self.bcpf)
+            bcpf_bp_file = os.path.join(self.top_dir, bcpf_dir, "Android.bp")
+            if self.fix:
+                tool_dir = os.path.dirname(self.tool_path)
+                bpmodify_path = os.path.join(tool_dir, "bpmodify")
+                bpmodify_runner = BpModifyRunner(bpmodify_path)
+                for property_change in result.property_changes:
+                    property_change.fix_bp_file(bcpf_bp_file, self.bcpf,
+                                                bpmodify_runner)
+
+                result.file_changes.append(
+                    self.new_file_change(
+                        bcpf_bp_file,
+                        f"Updated hidden_api properties of '{self.bcpf}'"))
+
+            else:
+                hiddenapi_snippet = ""
+                for property_change in result.property_changes:
+                    hiddenapi_snippet += property_change.snippet("        ")
+
+                # Remove leading and trailing blank lines.
+                hiddenapi_snippet = hiddenapi_snippet.strip("\n")
+
+                result.file_changes.append(
+                    self.new_file_change(
+                        bcpf_bp_file, f"""
+Add the following snippet into the {self.bcpf} bootclasspath_fragment module
+in the {bcpf_dir}/Android.bp file. If the hidden_api block already exists then
+merge these properties into it.
+
+    hidden_api: {{
+{hiddenapi_snippet}
+    }},
+"""))
+
+        if result.file_changes:
+            if self.fix:
+                file_change_message = """
+The following files were modified by this script:"""
+            else:
+                file_change_message = """
+The following modifications need to be made:"""
+
+            self.report(f"""
+{file_change_message}""")
+            result.file_changes.sort()
+            for file_change in result.file_changes:
+                self.report(f"""
+    {file_change.path}
+        {file_change.description}
+""".lstrip("\n"))
+
+            if not self.fix:
+                self.report("""
+Run the command again with the --fix option to automatically make the above
+changes.
+""".lstrip())
+
+    def new_file_change(self, file, description):
+        return FileChange(
+            path=os.path.relpath(file, self.top_dir), description=description)
+
+    def check_inconsistent_flag_lines(self, significant, module_line,
+                                      monolithic_line, separator_line):
+        if not (module_line.startswith("< ") and
+                monolithic_line.startswith("> ") and not separator_line):
+            # Something went wrong.
+            self.report(f"""Invalid build output detected:
+  module_line: "{module_line}"
+  monolithic_line: "{monolithic_line}"
+  separator_line: "{separator_line}"
+""")
+            sys.exit(1)
+
+        if significant:
+            logging.debug("%s", module_line)
+            logging.debug("%s", monolithic_line)
+            logging.debug("%s", separator_line)
+
+    def scan_inconsistent_flags_report(self, lines):
+        """Scans a hidden API flags report
+
+        The hidden API inconsistent flags report which looks something like
+        this.
+
+        < out/soong/.intermediates/.../filtered-stub-flags.csv
+        > out/soong/hiddenapi/hiddenapi-stub-flags.txt
+
+        < Landroid/compat/Compatibility;->clearOverrides()V
+        > Landroid/compat/Compatibility;->clearOverrides()V,core-platform-api
+
+        """
+
+        # The basic format of an entry in the inconsistent flags report is:
+        #   <module specific flag>
+        #   <monolithic flag>
+        #   <separator>
+        #
+        # Wrap the lines iterator in an iterator which returns a tuple
+        # consisting of the three separate lines.
+        triples = zip(lines, lines, lines)
+
+        module_line, monolithic_line, separator_line = next(triples)
+        significant = False
+        bcpf_dir = self.module_info.module_path(self.bcpf)
+        if os.path.join(bcpf_dir, self.bcpf) in module_line:
+            # These errors are related to the bcpf being analyzed so
+            # keep them.
+            significant = True
+        else:
+            self.report(f"Filtering out errors related to {module_line}")
+
+        self.check_inconsistent_flag_lines(significant, module_line,
+                                           monolithic_line, separator_line)
+
+        diffs = {}
+        for module_line, monolithic_line, separator_line in triples:
+            self.check_inconsistent_flag_lines(significant, module_line,
+                                               monolithic_line, "")
+
+            module_parts = module_line.removeprefix("< ").split(",")
+            module_signature = module_parts[0]
+            module_flags = module_parts[1:]
+
+            monolithic_parts = monolithic_line.removeprefix("> ").split(",")
+            monolithic_signature = monolithic_parts[0]
+            monolithic_flags = monolithic_parts[1:]
+
+            if module_signature != monolithic_signature:
+                # Something went wrong.
+                self.report(f"""Inconsistent signatures detected:
+  module_signature: "{module_signature}"
+  monolithic_signature: "{monolithic_signature}"
+""")
+                sys.exit(1)
+
+            diffs[module_signature] = (module_flags, monolithic_flags)
+
+            if separator_line:
+                # If the separator line is not blank then it is the end of the
+                # current report, and possibly the start of another.
+                return separator_line, diffs
+
+        return "", diffs
+
+    def build_file_read_output(self, filename):
+        # Make sure the filename is relative to top if possible as the build
+        # may be using relative paths as the target.
+        rel_filename = filename.removeprefix(self.top_dir)
+        cmd = ["build/soong/soong_ui.bash", "--make-mode", rel_filename]
+        cmd_line = " ".join(cmd)
+        logging.debug("%s", cmd_line)
+        # pylint: disable=consider-using-with
+        output = subprocess.Popen(
+            cmd,
+            cwd=self.top_dir,
+            stderr=subprocess.STDOUT,
+            stdout=subprocess.PIPE,
+            text=True,
+        )
+        return BuildOperation(popen=output)
+
+    def build_hiddenapi_flags(self, filename):
+        output = self.build_file_read_output(filename)
+
+        lines = output.lines()
+        diffs = None
+        for line in lines:
+            logging.debug("%s", line)
+            while line == _INCONSISTENT_FLAGS:
+                line, diffs = self.scan_inconsistent_flags_report(lines)
+
+        output.wait(timeout=10)
+        if output.returncode != 0:
+            logging.debug("Command failed with %s", output.returncode)
+        else:
+            logging.debug("Command succeeded")
+
+        return diffs
+
+    def build_monolithic_stubs_flags(self):
+        self.report(f"""
+Attempting to build {_STUB_FLAGS_FILE} to verify that the
+bootclasspath_fragment has the correct API stubs available...
+""")
+
+        # Build the hiddenapi-stubs-flags.txt file.
+        diffs = self.build_hiddenapi_flags(_STUB_FLAGS_FILE)
+        if diffs:
+            self.report(f"""
+There is a discrepancy between the stub API derived flags created by the
+bootclasspath_fragment and the platform_bootclasspath. See preceding error
+messages to see which flags are inconsistent. The inconsistencies can occur for
+a couple of reasons:
+
+If you are building against prebuilts of the Android SDK, e.g. by using
+TARGET_BUILD_APPS then the prebuilt versions of the APIs this
+bootclasspath_fragment depends upon are out of date and need updating. See
+go/update-prebuilts for help.
+
+Otherwise, this is happening because there are some stub APIs that are either
+provided by or used by the contents of the bootclasspath_fragment but which are
+not available to it. There are 4 ways to handle this:
+
+1. A java_sdk_library in the contents property will automatically make its stub
+   APIs available to the bootclasspath_fragment so nothing needs to be done.
+
+2. If the API provided by the bootclasspath_fragment is created by an api_only
+   java_sdk_library (or a java_library that compiles files generated by a
+   separate droidstubs module then it cannot be added to the contents and
+   instead must be added to the api.stubs property, e.g.
+
+   bootclasspath_fragment {{
+     name: "{self.bcpf}",
+     ...
+     api: {{
+       stubs: ["$MODULE-api-only"],"
+     }},
+   }}
+
+3. If the contents use APIs provided by another bootclasspath_fragment then
+   it needs to be added to the fragments property, e.g.
+
+   bootclasspath_fragment {{
+     name: "{self.bcpf}",
+     ...
+     // The bootclasspath_fragments that provide APIs on which this depends.
+     fragments: [
+       ...
+       {{
+         apex: "com.android.other",
+         module: "com.android.other-bootclasspath-fragment",
+       }},
+     ],
+   }}
+
+4. If the contents use APIs from a module that is not part of another
+   bootclasspath_fragment then it must be added to the additional_stubs
+   property, e.g.
+
+   bootclasspath_fragment {{
+     name: "{self.bcpf}",
+     ...
+     additional_stubs: ["android-non-updatable"],
+   }}
+
+   Like the api.stubs property these are typically java_sdk_library modules but
+   can be java_library too.
+
+   Note: The "android-non-updatable" is treated as if it was a java_sdk_library
+   which it is not at the moment but will be in future.
+""")
+
+        return diffs
+
+    def build_monolithic_flags(self, result):
+        self.report(f"""
+Attempting to build {_FLAGS_FILE} to verify that the
+bootclasspath_fragment has the correct hidden API flags...
+""")
+
+        # Build the hiddenapi-flags.csv file and extract any differences in
+        # the flags between this bootclasspath_fragment and the monolithic
+        # files.
+        result.diffs = self.build_hiddenapi_flags(_FLAGS_FILE)
+
+        # Load information from the bootclasspath_fragment's all-flags.csv file.
+        self.load_all_flags()
+
+        if result.diffs:
+            self.report(f"""
+There is a discrepancy between the hidden API flags created by the
+bootclasspath_fragment and the platform_bootclasspath. See preceding error
+messages to see which flags are inconsistent. The inconsistencies can occur for
+a couple of reasons:
+
+If you are building against prebuilts of this bootclasspath_fragment then the
+prebuilt version of the sdk snapshot (specifically the hidden API flag files)
+are inconsistent with the prebuilt version of the apex {self.apex}. Please
+ensure that they are both updated from the same build.
+
+1. There are custom hidden API flags specified in the one of the files in
+   frameworks/base/boot/hiddenapi which apply to the bootclasspath_fragment but
+   which are not supplied to the bootclasspath_fragment module.
+
+2. The bootclasspath_fragment specifies invalid "package_prefixes" or
+   "split_packages" properties that match packages and classes that it does not
+   provide.
+
+""")
+
+            # Check to see if there are any hiddenapi related properties that
+            # need to be added to the
+            self.report("""
+Checking custom hidden API flags....
+""")
+            self.check_frameworks_base_boot_hidden_api_files(result)
+
+    def report_hidden_api_flag_file_changes(self, result, property_name,
+                                            flags_file, rel_bcpf_flags_file,
+                                            bcpf_flags_file):
+        matched_signatures = set()
+        # Open the flags file to read the flags from.
+        with open(flags_file, "r", encoding="utf8") as f:
+            for signature in newline_stripping_iter(f.readline):
+                if signature in self.signatures:
+                    # The signature is provided by the bootclasspath_fragment so
+                    # it will need to be moved to the bootclasspath_fragment
+                    # specific file.
+                    matched_signatures.add(signature)
+
+        # If the bootclasspath_fragment specific flags file is not empty
+        # then it contains flags. That could either be new flags just moved
+        # from frameworks/base or previous contents of the file. In either
+        # case the file must not be removed.
+        if matched_signatures:
+            insert = textwrap.indent("\n".join(matched_signatures),
+                                     "            ")
+            result.file_changes.append(
+                self.new_file_change(
+                    flags_file, f"""Remove the following entries:
+{insert}
+"""))
+
+            result.file_changes.append(
+                self.new_file_change(
+                    bcpf_flags_file, f"""Add the following entries:
+{insert}
+"""))
+
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name=property_name,
+                    values=[rel_bcpf_flags_file],
+                ))
+
+    def fix_hidden_api_flag_files(self, result, property_name, flags_file,
+                                  rel_bcpf_flags_file, bcpf_flags_file):
+        # Read the file in frameworks/base/boot/hiddenapi/<file> copy any
+        # flags that relate to the bootclasspath_fragment into a local
+        # file in the hiddenapi subdirectory.
+        tmp_flags_file = flags_file + ".tmp"
+
+        # Make sure the directory containing the bootclasspath_fragment specific
+        # hidden api flags exists.
+        os.makedirs(os.path.dirname(bcpf_flags_file), exist_ok=True)
+
+        bcpf_flags_file_exists = os.path.exists(bcpf_flags_file)
+
+        matched_signatures = set()
+        # Open the flags file to read the flags from.
+        with open(flags_file, "r", encoding="utf8") as f:
+            # Open a temporary file to write the flags (minus any removed
+            # flags).
+            with open(tmp_flags_file, "w", encoding="utf8") as t:
+                # Open the bootclasspath_fragment file for append just in
+                # case it already exists.
+                with open(bcpf_flags_file, "a", encoding="utf8") as b:
+                    for line in iter(f.readline, ""):
+                        signature = line.rstrip()
+                        if signature in self.signatures:
+                            # The signature is provided by the
+                            # bootclasspath_fragment so write it to the new
+                            # bootclasspath_fragment specific file.
+                            print(line, file=b, end="")
+                            matched_signatures.add(signature)
+                        else:
+                            # The signature is NOT provided by the
+                            # bootclasspath_fragment. Copy it to the new
+                            # monolithic file.
+                            print(line, file=t, end="")
+
+        # If the bootclasspath_fragment specific flags file is not empty
+        # then it contains flags. That could either be new flags just moved
+        # from frameworks/base or previous contents of the file. In either
+        # case the file must not be removed.
+        if matched_signatures:
+            # There are custom flags related to the bootclasspath_fragment
+            # so replace the frameworks/base/boot/hiddenapi file with the
+            # file that does not contain those flags.
+            shutil.move(tmp_flags_file, flags_file)
+
+            result.file_changes.append(
+                self.new_file_change(flags_file,
+                                     f"Removed '{self.bcpf}' specific entries"))
+
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name=property_name,
+                    values=[rel_bcpf_flags_file],
+                ))
+
+            # Make sure that the files are sorted.
+            self.run_command([
+                "tools/platform-compat/hiddenapi/sort_api.sh",
+                bcpf_flags_file,
+            ])
+
+            if bcpf_flags_file_exists:
+                desc = f"Added '{self.bcpf}' specific entries"
+            else:
+                desc = f"Created with '{self.bcpf}' specific entries"
+            result.file_changes.append(
+                self.new_file_change(bcpf_flags_file, desc))
+        else:
+            # There are no custom flags related to the
+            # bootclasspath_fragment so clean up the working files.
+            os.remove(tmp_flags_file)
+            if not bcpf_flags_file_exists:
+                os.remove(bcpf_flags_file)
+
+    def check_frameworks_base_boot_hidden_api_files(self, result):
+        hiddenapi_dir = os.path.join(self.top_dir,
+                                     "frameworks/base/boot/hiddenapi")
+        for basename in sorted(os.listdir(hiddenapi_dir)):
+            if not (basename.startswith("hiddenapi-") and
+                    basename.endswith(".txt")):
+                continue
+
+            flags_file = os.path.join(hiddenapi_dir, basename)
+
+            logging.debug("Checking %s for flags related to %s", flags_file,
+                          self.bcpf)
+
+            # Map the file name in frameworks/base/boot/hiddenapi into a
+            # slightly more meaningful name for use by the
+            # bootclasspath_fragment.
+            if basename == "hiddenapi-max-target-o.txt":
+                basename = "hiddenapi-max-target-o-low-priority.txt"
+            elif basename == "hiddenapi-max-target-r-loprio.txt":
+                basename = "hiddenapi-max-target-r-low-priority.txt"
+
+            property_name = basename.removeprefix("hiddenapi-")
+            property_name = property_name.removesuffix(".txt")
+            property_name = property_name.replace("-", "_")
+
+            rel_bcpf_flags_file = f"hiddenapi/{basename}"
+            bcpf_dir = self.module_info.module_path(self.bcpf)
+            bcpf_flags_file = os.path.join(self.top_dir, bcpf_dir,
+                                           rel_bcpf_flags_file)
+
+            if self.fix:
+                self.fix_hidden_api_flag_files(result, property_name,
+                                               flags_file, rel_bcpf_flags_file,
+                                               bcpf_flags_file)
+            else:
+                self.report_hidden_api_flag_file_changes(
+                    result, property_name, flags_file, rel_bcpf_flags_file,
+                    bcpf_flags_file)
+
+    @staticmethod
+    def split_package_comment(split_packages):
+        if split_packages:
+            return textwrap.dedent("""
+                The following packages contain classes from other modules on the
+                bootclasspath. That means that the hidden API flags for this
+                module has to explicitly list every single class this module
+                provides in that package to differentiate them from the classes
+                provided by other modules. That can include private classes that
+                are not part of the API.
+            """).strip("\n")
+
+        return "This module does not contain any split packages."
+
+    @staticmethod
+    def package_prefixes_comment():
+        return textwrap.dedent("""
+            The following packages and all their subpackages currently only
+            contain classes from this bootclasspath_fragment. Listing a package
+            here won't prevent other bootclasspath modules from adding classes
+            in any of those packages but it will prevent them from adding those
+            classes into an API surface, e.g. public, system, etc.. Doing so
+            will result in a build failure due to inconsistent flags.
+        """).strip("\n")
+
+    def analyze_hiddenapi_package_properties(self, result):
+        split_packages, single_packages, package_prefixes = \
+            self.compute_hiddenapi_package_properties()
+
+        # TODO(b/202154151): Find those classes in split packages that are not
+        #  part of an API, i.e. are an internal implementation class, and so
+        #  can, and should, be safely moved out of the split packages.
+
+        result.property_changes.append(
+            HiddenApiPropertyChange(
+                property_name="split_packages",
+                values=split_packages,
+                property_comment=self.split_package_comment(split_packages),
+                action=PropertyChangeAction.REPLACE,
+            ))
+
+        if split_packages:
+            self.report(f"""
+bootclasspath_fragment {self.bcpf} contains classes in packages that also
+contain classes provided by other sources, those packages are called split
+packages. Split packages should be avoided where possible but are often
+unavoidable when modularizing existing code.
+
+The hidden api processing needs to know which packages are split (and conversely
+which are not) so that it can optimize the hidden API flags to remove
+unnecessary implementation details.
+""")
+
+        self.report("""
+By default (for backwards compatibility) the bootclasspath_fragment assumes that
+all packages are split unless one of the package_prefixes or split_packages
+properties are specified. While that is safe it is not optimal and can lead to
+unnecessary implementation details leaking into the hidden API flags. Adding an
+empty split_packages property allows the flags to be optimized and remove any
+unnecessary implementation details.
+""")
+
+        if single_packages:
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name="single_packages",
+                    values=single_packages,
+                    property_comment=textwrap.dedent("""
+                    The following packages currently only contain classes from
+                    this bootclasspath_fragment but some of their sub-packages
+                    contain classes from other bootclasspath modules. Packages
+                    should only be listed here when necessary for legacy
+                    purposes, new packages should match a package prefix.
+                """),
+                    action=PropertyChangeAction.REPLACE,
+                ))
+
+        if package_prefixes:
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name="package_prefixes",
+                    values=package_prefixes,
+                    property_comment=self.package_prefixes_comment(),
+                    action=PropertyChangeAction.REPLACE,
+                ))
+
+    def explain_how_to_check_signature_patterns(self):
+        signature_patterns_files = self.find_bootclasspath_fragment_output_file(
+            "signature-patterns.csv", required=False)
+        if signature_patterns_files:
+            signature_patterns_files = signature_patterns_files.removeprefix(
+                self.top_dir)
+
+            self.report(f"""
+The purpose of the hiddenapi split_packages and package_prefixes properties is
+to allow the removal of implementation details from the hidden API flags to
+reduce the coupling between sdk snapshots and the APEX runtime. It cannot
+eliminate that coupling completely though. Doing so may require changes to the
+code.
+
+This tool provides support for managing those properties but it cannot decide
+whether the set of package prefixes suggested is appropriate that needs the
+input of the developer.
+
+Please run the following command:
+    m {signature_patterns_files}
+
+And then check the '{signature_patterns_files}' for any mention of
+implementation classes and packages (i.e. those classes/packages that do not
+contain any part of an API surface, including the hidden API). If they are
+found then the code should ideally be moved to a package unique to this module
+that is contained within a package that is part of an API surface.
+
+The format of the file is a list of patterns:
+
+* Patterns for split packages will list every class in that package.
+
+* Patterns for package prefixes will end with .../**.
+
+* Patterns for packages which are not split but cannot use a package prefix
+because there are sub-packages which are provided by another module will end
+with .../*.
+""")
+
+    def compute_hiddenapi_package_properties(self):
+        trie = signature_trie()
+        # Populate the trie with the classes that are provided by the
+        # bootclasspath_fragment tagging them to make it clear where they
+        # are from.
+        sorted_classes = sorted(self.classes)
+        for class_name in sorted_classes:
+            trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
+
+        monolithic_classes = set()
+        abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
+        with open(abs_flags_file, "r", encoding="utf8") as f:
+            for line in iter(f.readline, ""):
+                signature = self.line_to_signature(line)
+                class_name = self.signature_to_class(signature)
+                if (class_name not in monolithic_classes and
+                        class_name not in self.classes):
+                    trie.add(
+                        class_name + _FAKE_MEMBER,
+                        ClassProvider.OTHER,
+                        only_if_matches=True)
+                    monolithic_classes.add(class_name)
+
+        split_packages = []
+        single_packages = []
+        package_prefixes = []
+        self.recurse_hiddenapi_packages_trie(trie, split_packages,
+                                             single_packages, package_prefixes)
+        return split_packages, single_packages, package_prefixes
+
+    def recurse_hiddenapi_packages_trie(self, node, split_packages,
+                                        single_packages, package_prefixes):
+        nodes = node.child_nodes()
+        if nodes:
+            for child in nodes:
+                # Ignore any non-package nodes.
+                if child.type != "package":
+                    continue
+
+                package = child.selector.replace("/", ".")
+
+                providers = set(child.get_matching_rows("**"))
+                if not providers:
+                    # The package and all its sub packages contain no
+                    # classes. This should never happen.
+                    pass
+                elif providers == {ClassProvider.BCPF}:
+                    # The package and all its sub packages only contain
+                    # classes provided by the bootclasspath_fragment.
+                    logging.debug("Package '%s.**' is not split", package)
+                    package_prefixes.append(package)
+                    # There is no point traversing into the sub packages.
+                    continue
+                elif providers == {ClassProvider.OTHER}:
+                    # The package and all its sub packages contain no
+                    # classes provided by the bootclasspath_fragment.
+                    # There is no point traversing into the sub packages.
+                    logging.debug("Package '%s.**' contains no classes from %s",
+                                  package, self.bcpf)
+                    continue
+                elif ClassProvider.BCPF in providers:
+                    # The package and all its sub packages contain classes
+                    # provided by the bootclasspath_fragment and other
+                    # sources.
+                    logging.debug(
+                        "Package '%s.**' contains classes from "
+                        "%s and other sources", package, self.bcpf)
+
+                providers = set(child.get_matching_rows("*"))
+                if not providers:
+                    # The package contains no classes.
+                    logging.debug("Package: %s contains no classes", package)
+                elif providers == {ClassProvider.BCPF}:
+                    # The package only contains classes provided by the
+                    # bootclasspath_fragment.
+                    logging.debug("Package '%s.*' is not split", package)
+                    single_packages.append(package)
+                elif providers == {ClassProvider.OTHER}:
+                    # The package contains no classes provided by the
+                    # bootclasspath_fragment. Child nodes make contain such
+                    # classes.
+                    logging.debug("Package '%s.*' contains no classes from %s",
+                                  package, self.bcpf)
+                elif ClassProvider.BCPF in providers:
+                    # The package contains classes provided by both the
+                    # bootclasspath_fragment and some other source.
+                    logging.debug("Package '%s.*' is split", package)
+                    split_packages.append(package)
+
+                self.recurse_hiddenapi_packages_trie(child, split_packages,
+                                                     single_packages,
+                                                     package_prefixes)
+
+
+def newline_stripping_iter(iterator):
+    """Return an iterator over the iterator that strips trailing white space."""
+    lines = iter(iterator, "")
+    lines = (line.rstrip() for line in lines)
+    return lines
+
+
+def format_comment_as_text(text, indent):
+    return "".join(
+        [f"{line}\n" for line in format_comment_as_lines(text, indent)])
+
+
+def format_comment_as_lines(text, indent):
+    lines = textwrap.wrap(text.strip("\n"), width=77 - len(indent))
+    lines = [f"{indent}// {line}" for line in lines]
+    return lines
+
+
+def log_stream_for_subprocess():
+    stream = subprocess.DEVNULL
+    for handler in logging.root.handlers:
+        if handler.level == logging.DEBUG:
+            if isinstance(handler, logging.StreamHandler):
+                stream = handler.stream
+    return stream
+
+
+def main(argv):
+    args_parser = argparse.ArgumentParser(
+        description="Analyze a bootclasspath_fragment module.")
+    args_parser.add_argument(
+        "--bcpf",
+        help="The bootclasspath_fragment module to analyze",
+        required=True,
+    )
+    args_parser.add_argument(
+        "--apex",
+        help="The apex module to which the bootclasspath_fragment belongs. It "
+        "is not strictly necessary at the moment but providing it will "
+        "allow this script to give more useful messages and it may be"
+        "required in future.",
+        default="SPECIFY-APEX-OPTION")
+    args_parser.add_argument(
+        "--sdk",
+        help="The sdk module to which the bootclasspath_fragment belongs. It "
+        "is not strictly necessary at the moment but providing it will "
+        "allow this script to give more useful messages and it may be"
+        "required in future.",
+        default="SPECIFY-SDK-OPTION")
+    args_parser.add_argument(
+        "--fix",
+        help="Attempt to fix any issues found automatically.",
+        action="store_true",
+        default=False)
+    args = args_parser.parse_args(argv[1:])
+    top_dir = os.environ["ANDROID_BUILD_TOP"] + "/"
+    out_dir = os.environ.get("OUT_DIR", os.path.join(top_dir, "out"))
+    product_out_dir = os.environ.get("ANDROID_PRODUCT_OUT", top_dir)
+    # Make product_out_dir relative to the top so it can be used as part of a
+    # build target.
+    product_out_dir = product_out_dir.removeprefix(top_dir)
+    log_fd, abs_log_file = tempfile.mkstemp(
+        suffix="_analyze_bcpf.log", text=True)
+
+    with os.fdopen(log_fd, "w") as log_file:
+        # Set up debug logging to the log file.
+        logging.basicConfig(
+            level=logging.DEBUG,
+            format="%(levelname)-8s %(message)s",
+            stream=log_file)
+
+        # define a Handler which writes INFO messages or higher to the
+        # sys.stdout with just the message.
+        console = logging.StreamHandler()
+        console.setLevel(logging.INFO)
+        console.setFormatter(logging.Formatter("%(message)s"))
+        # add the handler to the root logger
+        logging.getLogger("").addHandler(console)
+
+        print(f"Writing log to {abs_log_file}")
+        try:
+            analyzer = BcpfAnalyzer(
+                tool_path=argv[0],
+                top_dir=top_dir,
+                out_dir=out_dir,
+                product_out_dir=product_out_dir,
+                bcpf=args.bcpf,
+                apex=args.apex,
+                sdk=args.sdk,
+                fix=args.fix,
+            )
+            analyzer.analyze()
+        finally:
+            print(f"Log written to {abs_log_file}")
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/scripts/hiddenapi/analyze_bcpf_test.py b/scripts/hiddenapi/analyze_bcpf_test.py
new file mode 100644
index 0000000..650dd54
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf_test.py
@@ -0,0 +1,650 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Unit tests for analyzing bootclasspath_fragment modules."""
+import os.path
+import shutil
+import tempfile
+import unittest
+import unittest.mock
+
+import sys
+
+import analyze_bcpf as ab
+
+_FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi"
+_MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt"
+_MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt"
+_MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt"
+_MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt"
+
+_MULTI_LINE_COMMENT = """
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo,
+bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem
+tincidunt luctus.""".replace("\n", " ").strip()
+
+
+class FakeBuildOperation(ab.BuildOperation):
+
+    def __init__(self, lines, return_code):
+        ab.BuildOperation.__init__(self, None)
+        self._lines = lines
+        self.returncode = return_code
+
+    def lines(self):
+        return iter(self._lines)
+
+    def wait(self, *args, **kwargs):
+        return
+
+
+class TestAnalyzeBcpf(unittest.TestCase):
+
+    def setUp(self):
+        # Create a temporary directory
+        self.test_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        # Remove the directory after the test
+        shutil.rmtree(self.test_dir)
+
+    @staticmethod
+    def write_abs_file(abs_path, contents):
+        os.makedirs(os.path.dirname(abs_path), exist_ok=True)
+        with open(abs_path, "w", encoding="utf8") as f:
+            print(contents.removeprefix("\n"), file=f, end="")
+
+    def populate_fs(self, fs):
+        for path, contents in fs.items():
+            abs_path = os.path.join(self.test_dir, path)
+            self.write_abs_file(abs_path, contents)
+
+    def create_analyzer_for_test(self,
+                                 fs=None,
+                                 bcpf="bcpf",
+                                 apex="apex",
+                                 sdk="sdk",
+                                 fix=False):
+        if fs:
+            self.populate_fs(fs)
+
+        top_dir = self.test_dir
+        out_dir = os.path.join(self.test_dir, "out")
+        product_out_dir = "out/product"
+
+        bcpf_dir = f"{bcpf}-dir"
+        modules = {bcpf: {"path": [bcpf_dir]}}
+        module_info = ab.ModuleInfo(modules)
+
+        analyzer = ab.BcpfAnalyzer(
+            tool_path=os.path.join(out_dir, "bin"),
+            top_dir=top_dir,
+            out_dir=out_dir,
+            product_out_dir=product_out_dir,
+            bcpf=bcpf,
+            apex=apex,
+            sdk=sdk,
+            fix=fix,
+            module_info=module_info,
+        )
+        analyzer.load_all_flags()
+        return analyzer
+
+    def test_reformat_report_text(self):
+        lines = """
+99. An item in a numbered list
+that traverses multiple lines.
+
+   An indented example
+   that should not be reformatted.
+"""
+        reformatted = ab.BcpfAnalyzer.reformat_report_test(lines)
+        self.assertEqual(
+            """
+99. An item in a numbered list that traverses multiple lines.
+
+   An indented example
+   that should not be reformatted.
+""", reformatted)
+
+    def do_test_build_flags(self, fix):
+        lines = """
+ERROR: Hidden API flags are inconsistent:
+< out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv
+> out/soong/hiddenapi/hiddenapi-flags.csv
+
+< Lacme/test/Class;-><init>()V,blocked
+> Lacme/test/Class;-><init>()V,max-target-o
+
+< Lacme/test/Other;->getThing()Z,blocked
+> Lacme/test/Other;->getThing()Z,max-target-p
+
+< Lacme/test/Widget;-><init()V,blocked
+> Lacme/test/Widget;-><init()V,max-target-q
+
+< Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+> Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r
+16:37:32 ninja failed with: exit status 1
+""".strip().splitlines()
+        operation = FakeBuildOperation(lines=lines, return_code=1)
+
+        fs = {
+            _MAX_TARGET_O:
+                """
+Lacme/items/Magnet;->size:I
+Lacme/test/Class;-><init>()V
+""",
+            _MAX_TARGET_P:
+                """
+Lacme/items/Rocket;->size:I
+Lacme/test/Other;->getThing()Z
+""",
+            _MAX_TARGET_Q:
+                """
+Lacme/items/Rock;->size:I
+Lacme/test/Widget;-><init()V
+""",
+            _MAX_TARGET_R:
+                """
+Lacme/items/Lever;->size:I
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+                """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+""",
+            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+                """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+Lacme/test/Widget;-><init()V,blocked
+Lacme/test/Class;-><init>()V,blocked
+Lacme/test/Other;->getThing()Z,blocked
+""",
+        }
+
+        analyzer = self.create_analyzer_for_test(fs, fix=fix)
+
+        # Override the build_file_read_output() method to just return a fake
+        # build operation.
+        analyzer.build_file_read_output = unittest.mock.Mock(
+            return_value=operation)
+
+        # Override the run_command() method to do nothing.
+        analyzer.run_command = unittest.mock.Mock()
+
+        result = ab.Result()
+
+        analyzer.build_monolithic_flags(result)
+        expected_diffs = {
+            "Lacme/test/Gadget;->NAME:Ljava/lang/String;":
+                (["blocked"], ["lo-prio", "max-target-r"]),
+            "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]),
+            "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]),
+            "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"])
+        }
+        self.assertEqual(expected_diffs, result.diffs, msg="flag differences")
+
+        expected_property_changes = [
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_o_low_priority",
+                values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_p",
+                values=["hiddenapi/hiddenapi-max-target-p.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_q",
+                values=["hiddenapi/hiddenapi-max-target-q.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_r_low_priority",
+                values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"],
+                property_comment=""),
+        ]
+        self.assertEqual(
+            expected_property_changes,
+            result.property_changes,
+            msg="property changes")
+
+        return result
+
+    def test_build_flags_report(self):
+        result = self.do_test_build_flags(fix=False)
+
+        expected_file_changes = [
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-o-low-priority.txt",
+                description="""Add the following entries:
+            Lacme/test/Class;-><init>()V
+""",
+            ),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+                description="""Add the following entries:
+            Lacme/test/Other;->getThing()Z
+""",
+            ),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+                description="""Add the following entries:
+            Lacme/test/Widget;-><init()V
+"""),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-r-low-priority.txt",
+                description="""Add the following entries:
+            Lacme/test/Gadget;->NAME:Ljava/lang/String;
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-o.txt",
+                description="""Remove the following entries:
+            Lacme/test/Class;-><init>()V
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-p.txt",
+                description="""Remove the following entries:
+            Lacme/test/Other;->getThing()Z
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-q.txt",
+                description="""Remove the following entries:
+            Lacme/test/Widget;-><init()V
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-r-loprio.txt",
+                description="""Remove the following entries:
+            Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""")
+        ]
+        result.file_changes.sort()
+        self.assertEqual(
+            expected_file_changes, result.file_changes, msg="file_changes")
+
+    def test_build_flags_fix(self):
+        result = self.do_test_build_flags(fix=True)
+
+        expected_file_changes = [
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-o-low-priority.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+                description="Added 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-r-low-priority.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_O,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_P,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_Q,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_R,
+                description="Removed 'bcpf' specific entries")
+        ]
+
+        result.file_changes.sort()
+        self.assertEqual(
+            expected_file_changes, result.file_changes, msg="file_changes")
+
+        expected_file_contents = {
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-o-low-priority.txt":
+                """
+Lacme/test/Class;-><init>()V
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+                """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+Lacme/test/Other;->getThing()Z
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt":
+                """
+Lacme/test/Widget;-><init()V
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-r-low-priority.txt":
+                """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+            _MAX_TARGET_O:
+                """
+Lacme/items/Magnet;->size:I
+""",
+            _MAX_TARGET_P:
+                """
+Lacme/items/Rocket;->size:I
+""",
+            _MAX_TARGET_Q:
+                """
+Lacme/items/Rock;->size:I
+""",
+            _MAX_TARGET_R:
+                """
+Lacme/items/Lever;->size:I
+""",
+        }
+        for file_change in result.file_changes:
+            path = file_change.path
+            expected_contents = expected_file_contents[path].lstrip()
+            abs_path = os.path.join(self.test_dir, path)
+            with open(abs_path, "r", encoding="utf8") as tio:
+                contents = tio.read()
+                self.assertEqual(
+                    expected_contents, contents, msg=f"{path} contents")
+
+    def test_compute_hiddenapi_package_properties(self):
+        fs = {
+            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+                """
+La/b/C;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+""",
+            "out/soong/hiddenapi/hiddenapi-flags.csv":
+                """
+La/b/C;->m()V
+La/b/D;->m()V
+La/b/E;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+La/b/c/d/E;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+"""
+        }
+        analyzer = self.create_analyzer_for_test(fs)
+        analyzer.load_all_flags()
+
+        split_packages, single_packages, package_prefixes = \
+            analyzer.compute_hiddenapi_package_properties()
+        self.assertEqual(["a.b"], split_packages)
+        self.assertEqual(["a.b.c"], single_packages)
+        self.assertEqual(["b"], package_prefixes)
+
+
+class TestHiddenApiPropertyChange(unittest.TestCase):
+
+    def setUp(self):
+        # Create a temporary directory
+        self.test_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        # Remove the directory after the test
+        shutil.rmtree(self.test_dir)
+
+    def check_change_fix(self, change, bpmodify_output, expected):
+        file = os.path.join(self.test_dir, "Android.bp")
+
+        with open(file, "w", encoding="utf8") as tio:
+            tio.write(bpmodify_output.strip("\n"))
+
+        bpmodify_runner = ab.BpModifyRunner(
+            os.path.join(os.path.dirname(sys.argv[0]), "bpmodify"))
+        change.fix_bp_file(file, "bcpf", bpmodify_runner)
+
+        with open(file, "r", encoding="utf8") as tio:
+            contents = tio.read()
+            self.assertEqual(expected.lstrip("\n"), contents)
+
+    def check_change_snippet(self, change, expected):
+        snippet = change.snippet("        ")
+        self.assertEqual(expected, snippet)
+
+    def test_change_property_with_value_no_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["android.provider"],
+        )
+
+        self.check_change_snippet(
+            change, """
+        split_packages: [
+            "android.provider",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+    },
+}
+""")
+
+    def test_change_property_with_value_and_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["android.provider"],
+            property_comment=_MULTI_LINE_COMMENT,
+        )
+
+        self.check_change_snippet(
+            change, """
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "android.provider",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+
+        single_packages: [
+            "android.system",
+        ],
+
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "android.provider",
+        ],
+
+        single_packages: [
+            "android.system",
+        ],
+
+    },
+}
+""")
+
+    def test_set_property_with_value_and_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["another.provider", "other.system"],
+            property_comment=_MULTI_LINE_COMMENT,
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(
+            change, """
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+    },
+}
+""")
+
+    def test_set_property_with_no_value_or_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=[],
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(change, """
+        split_packages: [],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+        package_prefixes: ["android.provider"],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""")
+
+    def test_set_empty_property_with_no_value_or_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=[],
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(change, """
+        split_packages: [],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""")
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=3)
diff --git a/scripts/mergenotice.py b/scripts/mergenotice.py
deleted file mode 100755
index fe99073..0000000
--- a/scripts/mergenotice.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2019 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.
-#
-"""
-Merges input notice files to the output file while ignoring duplicated files
-This script shouldn't be confused with build/soong/scripts/generate-notice-files.py
-which is responsible for creating the final notice file for all artifacts
-installed. This script has rather limited scope; it is meant to create a merged
-notice file for a set of modules that are packaged together, e.g. in an APEX.
-The merged notice file does not reveal the individual files in the package.
-"""
-
-import sys
-import argparse
-
-def get_args():
-  parser = argparse.ArgumentParser(description='Merge notice files.')
-  parser.add_argument('--output', help='output file path.')
-  parser.add_argument('inputs', metavar='INPUT', nargs='+',
-                      help='input notice file')
-  return parser.parse_args()
-
-def main(argv):
-  args = get_args()
-
-  processed = set()
-  with open(args.output, 'w+') as output:
-    for input in args.inputs:
-      with open(input, 'r') as f:
-        data = f.read().strip()
-        if data not in processed:
-          processed.add(data)
-          output.write('%s\n\n' % data)
-
-if __name__ == '__main__':
-  main(sys.argv)
diff --git a/scripts/microfactory.bash b/scripts/microfactory.bash
index 5e702e0..192b38f 100644
--- a/scripts/microfactory.bash
+++ b/scripts/microfactory.bash
@@ -59,7 +59,7 @@
     BUILDDIR=$(getoutdir) \
       SRCDIR=${TOP} \
       BLUEPRINTDIR=${TOP}/build/blueprint \
-      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf" \
+      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path rbcrun=${TOP}/build/make/tools/rbcrun -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf -pkg-path go.starlark.net=${TOP}/external/starlark-go" \
       build_go $@
 }
 
diff --git a/scripts/rbc-run b/scripts/rbc-run
index b8a6c0c..8d93f0e 100755
--- a/scripts/rbc-run
+++ b/scripts/rbc-run
@@ -6,8 +6,8 @@
 set -eu
 
 declare -r output_root="${OUT_DIR:-out}"
-declare -r runner="${output_root}/soong/rbcrun"
-declare -r converter="${output_root}/soong/mk2rbc"
+declare -r runner="${output_root}/rbcrun"
+declare -r converter="${output_root}/mk2rbc"
 declare -r launcher="${output_root}/rbc/launcher.rbc"
 declare -r makefile_list="${output_root}/.module_paths/configuration.list"
 declare -r makefile="$1"
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
index 6b4e12b..b04657d 100644
--- a/snapshot/host_fake_snapshot.go
+++ b/snapshot/host_fake_snapshot.go
@@ -68,6 +68,12 @@
 	registerHostSnapshotComponents(android.InitRegistrationContext)
 }
 
+// Add prebuilt information to snapshot data
+type hostSnapshotFakeJsonFlags struct {
+	SnapshotJsonFlags
+	Prebuilt bool `json:",omitempty"`
+}
+
 func registerHostSnapshotComponents(ctx android.RegistrationContext) {
 	ctx.RegisterSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton)
 }
@@ -94,7 +100,9 @@
 	// Find all host binary modules add 'fake' versions to snapshot
 	var outputs android.Paths
 	seen := make(map[string]bool)
-	var jsonData []SnapshotJsonFlags
+	var jsonData []hostSnapshotFakeJsonFlags
+	prebuilts := make(map[string]bool)
+
 	ctx.VisitAllModules(func(module android.Module) {
 		if module.Target().Os != ctx.Config().BuildOSTarget.Os {
 			return
@@ -104,9 +112,10 @@
 		}
 
 		if android.IsModulePrebuilt(module) {
+			// Add non-prebuilt module name to map of prebuilts
+			prebuilts[android.RemoveOptionalPrebuiltPrefix(module.Name())] = true
 			return
 		}
-
 		if !module.Enabled() || module.IsHideFromMake() {
 			return
 		}
@@ -114,17 +123,23 @@
 		if !apexInfo.IsForPlatform() {
 			return
 		}
-		path := hostBinToolPath(module)
+		path := hostToolPath(module)
 		if path.Valid() && path.String() != "" {
 			outFile := filepath.Join(c.snapshotDir, path.String())
 			if !seen[outFile] {
 				seen[outFile] = true
 				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
-				jsonData = append(jsonData, *hostBinJsonDesc(module))
+				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(module), false})
 			}
 		}
 	})
-
+	// Update any module prebuilt information
+	for idx, _ := range jsonData {
+		if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
+			// Prebuilt exists for this module
+			jsonData[idx].Prebuilt = true
+		}
+	}
 	marsh, err := json.Marshal(jsonData)
 	if err != nil {
 		ctx.Errorf("host fake snapshot json marshal failure: %#v", err)
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index 09a382e..9793218 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"path/filepath"
 	"sort"
+	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -62,6 +63,11 @@
 	installDir android.InstallPath
 }
 
+type ProcMacro interface {
+	ProcMacro() bool
+	CrateName() string
+}
+
 func hostSnapshotFactory() android.Module {
 	module := &hostSnapshot{}
 	initHostToolsModule(module)
@@ -94,7 +100,7 @@
 
 	// Create JSON file based on the direct dependencies
 	ctx.VisitDirectDeps(func(dep android.Module) {
-		desc := hostBinJsonDesc(dep)
+		desc := hostJsonDesc(dep)
 		if desc != nil {
 			jsonData = append(jsonData, *desc)
 		}
@@ -145,7 +151,7 @@
 
 	f.installDir = android.PathForModuleInstall(ctx)
 
-	f.CopyDepsToZip(ctx, depsZipFile)
+	f.CopyDepsToZip(ctx, f.GatherPackagingSpecs(ctx), depsZipFile)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().
@@ -183,7 +189,7 @@
 }
 
 // Get host tools path and relative install string helpers
-func hostBinToolPath(m android.Module) android.OptionalPath {
+func hostToolPath(m android.Module) android.OptionalPath {
 	if provider, ok := m.(android.HostToolProvider); ok {
 		return provider.HostToolPath()
 	}
@@ -198,18 +204,30 @@
 	return outString
 }
 
-// Create JSON description for given module, only create descriptions for binary modueles which
-// provide a valid HostToolPath
-func hostBinJsonDesc(m android.Module) *SnapshotJsonFlags {
-	path := hostBinToolPath(m)
+// Create JSON description for given module, only create descriptions for binary modules
+// and rust_proc_macro modules which provide a valid HostToolPath
+func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+	path := hostToolPath(m)
 	relPath := hostRelativePathString(m)
+	procMacro := false
+	moduleStem := filepath.Base(path.String())
+	crateName := ""
+
+	if pm, ok := m.(ProcMacro); ok && pm.ProcMacro() {
+		procMacro = pm.ProcMacro()
+		moduleStem = strings.TrimSuffix(moduleStem, filepath.Ext(moduleStem))
+		crateName = pm.CrateName()
+	}
+
 	if path.Valid() && path.String() != "" {
 		return &SnapshotJsonFlags{
 			ModuleName:          m.Name(),
-			ModuleStemName:      filepath.Base(path.String()),
+			ModuleStemName:      moduleStem,
 			Filename:            path.String(),
 			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
 			RelativeInstallPath: relPath,
+			RustProcMacro:       procMacro,
+			CrateName:           crateName,
 		}
 	}
 	return nil
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index 79d3cf6..4a14f2e 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -114,6 +114,8 @@
 	RelativeInstallPath string `json:",omitempty"`
 	Filename            string `json:",omitempty"`
 	ModuleStemName      string `json:",omitempty"`
+	RustProcMacro       bool   `json:",omitempty"`
+	CrateName           string `json:",omitempty"`
 
 	// dependencies
 	Required []string `json:",omitempty"`
diff --git a/soong_ui.bash b/soong_ui.bash
index c1c236b..49c4b78 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -53,6 +53,8 @@
 source ${TOP}/build/soong/scripts/microfactory.bash
 
 soong_build_go soong_ui android/soong/cmd/soong_ui
+soong_build_go mk2rbc android/soong/mk2rbc/cmd
+soong_build_go rbcrun rbcrun/cmd
 
 cd ${TOP}
 exec "$(getoutdir)/soong_ui" "$@"
diff --git a/tests/lib.sh b/tests/lib.sh
index 55f9ab4..1bb2df9 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -83,12 +83,14 @@
 function create_mock_soong {
   copy_directory build/blueprint
   copy_directory build/soong
+  copy_directory build/make/tools/rbcrun
 
   symlink_directory prebuilts/go
   symlink_directory prebuilts/build-tools
   symlink_directory prebuilts/clang/host
   symlink_directory external/go-cmp
   symlink_directory external/golang-protobuf
+  symlink_directory external/starlark-go
 
   touch "$MOCK_TOP/Android.bp"
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 3f10f75..11311f9 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -262,12 +262,6 @@
 		"BUILD_BROKEN_USES_BUILD_STATIC_LIBRARY",
 	}, exportEnvVars...), BannerVars...)
 
-	// We need Roboleaf converter and runner in the mixed mode
-	runMicrofactory(ctx, config, "mk2rbc", "android/soong/mk2rbc/cmd",
-		map[string]string{"android/soong": "build/soong"})
-	runMicrofactory(ctx, config, "rbcrun", "rbcrun/cmd",
-		map[string]string{"go.starlark.net": "external/starlark-go", "rbcrun": "build/make/tools/rbcrun"})
-
 	makeVars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true, "")
 	if err != nil {
 		ctx.Fatalln("Error dumping make vars:", err)