Merge changes from topic "sts-sdk" into sc-mainline-prod am: 4db0005a13

Original change: https://googleplex-android-review.googlesource.com/c/platform/build/+/19776213

Change-Id: I1830f6af17c2d559fb317bf181214c28155ba1e0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index ab2564e..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["build_make_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
-    name: "build_make_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-BSD",
-        "SPDX-license-identifier-CC-BY",
-        "SPDX-license-identifier-GPL",
-        "SPDX-license-identifier-GPL-2.0",
-        "SPDX-license-identifier-LGPL",
-        "SPDX-license-identifier-MIT",
-        "legacy_not_a_contribution",
-        "legacy_restricted",
-    ],
-    // large-scale-change unable to identify any license_text files
-}
diff --git a/Changes.md b/Changes.md
index 1ab005f..cabbed6 100644
--- a/Changes.md
+++ b/Changes.md
@@ -1,5 +1,36 @@
 # Build System Changes for Android.mk Writers
 
+## Genrule starts disallowing directory inputs
+
+To better specify the inputs to the build, we are restricting use of directories
+as inputs to genrules.
+
+To fix existing uses, change inputs to specify the inputs and update the command
+accordingly. For example:
+
+```
+genrule: {
+    name: "foo",
+    srcs: ["bar"],
+    cmd: "cp $(location bar)/*.xml $(gendir)",
+    ...
+}
+```
+
+would become
+
+```
+genrule: {
+    name: "foo",
+    srcs: ["bar/*.xml"],
+    cmd: "cp $(in) $(gendir)",
+    ...
+}
+```
+
+`BUILD_BROKEN_INPUT_DIR_MODULES` can be used to allowlist specific directories
+with genrules that have input directories.
+
 ## Dexpreopt starts enforcing `<uses-library>` checks (for Java modules)
 
 In order to construct correct class loader context for dexpreopt, build system
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 47fd53a..957da92 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -757,6 +757,22 @@
 # vendor-ramdisk renamed to vendor_ramdisk
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor-ramdisk)
 
+# Common R directory has been removed.
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/R)
+
+# Most of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+$(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
+
+# More of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+$(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
+
+# More of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+$(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
+
+# Last of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+# Don't use SOONG_HOST_OUT, it is now an alias for HOST_OUT.
+$(call add-clean-step, rm -rf $(OUT_DIR)/soong/host)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/METADATA b/METADATA
index 814cb00..44781a7 100644
--- a/METADATA
+++ b/METADATA
@@ -1,8 +1,8 @@
 third_party {
-  # would be NOTICE save for GPL in:
-  #   core/LINUX_KERNEL_COPYING
-  #   tools/droiddoc/templates-pdk/assets/jquery-1.6.2.min.js
-  #   tools/droiddoc/templates-pdk/assets/jquery-history.js
-  #   tools/droiddoc/templates-pdk/assets/jquery-resizable.min.js
+  license_note: "would be NOTICE save for GPL in:\n"
+  "   core/LINUX_KERNEL_COPYING\n"
+  "   tools/droiddoc/templates-pdk/assets/jquery-1.6.2.min.js\n"
+  "   tools/droiddoc/templates-pdk/assets/jquery-history.js\n"
+  "   tools/droiddoc/templates-pdk/assets/jquery-resizable.min.js"
   license_type: RESTRICTED
 }
diff --git a/common/math.mk b/common/math.mk
index ec15f88..0271ea8 100644
--- a/common/math.mk
+++ b/common/math.mk
@@ -121,14 +121,26 @@
   $(lastword $(filter $(1) $(2),$(__MATH_NUMBERS))))
 endef
 
+# Returns the lesser of $1 or $2.
+define math_min
+$(strip $(call _math_check_valid,$(1)) $(call _math_check_valid,$(2)) \
+  $(firstword $(filter $(1) $(2),$(__MATH_NUMBERS))))
+endef
+
 $(call math-expect-error,(call math_max),Argument missing)
 $(call math-expect-error,(call math_max,1),Argument missing)
 $(call math-expect-error,(call math_max,1 2,3),Multiple words in a single argument: 1 2)
+$(call math-expect-error,(call math_min,1,2 3),Multiple words in a single argument: 2 3)
 $(call math-expect,(call math_max,0,1),1)
 $(call math-expect,(call math_max,1,0),1)
 $(call math-expect,(call math_max,1,1),1)
 $(call math-expect,(call math_max,5,42),42)
 $(call math-expect,(call math_max,42,5),42)
+$(call math-expect,(call math_min,0,1),0)
+$(call math-expect,(call math_min,1,0),0)
+$(call math-expect,(call math_min,1,1),1)
+$(call math-expect,(call math_min,7,32),7)
+$(call math-expect,(call math_min,32,7),7)
 
 define math_gt_or_eq
 $(if $(filter $(1),$(call math_max,$(1),$(2))),true)
diff --git a/common/strings.mk b/common/strings.mk
index e560bf0..768d061 100644
--- a/common/strings.mk
+++ b/common/strings.mk
@@ -88,6 +88,24 @@
 endef
 
 ###########################################################
+## Read a colon-separated sublist out of a colon-separated
+## list of words.
+## This has similar behavior to the built-in function
+## $(wordlist s,e,str) except both the input and output
+## word lists are colon-separated.
+##
+## The individual words may not contain spaces.
+##
+## $(1): 1 based index start
+## $(2): 1 based index end (can be 0)
+## $(3): value of the form a:b:c...
+###########################################################
+
+define wordlist-colon
+$(subst $(space),:,$(wordlist $(1),$(2),$(subst :,$(space),$(3))))
+endef
+
+###########################################################
 ## Convert "a=b c= d e = f = g h=" into "a=b c=d e= f=g h="
 ##
 ## $(1): list to collapse
diff --git a/core/Makefile b/core/Makefile
index 2d56edb..72aa890 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4,6 +4,17 @@
 # intermedites-dir-for
 LOCAL_PATH := $(BUILD_SYSTEM)
 
+SYSTEM_NOTICE_DEPS :=
+VENDOR_NOTICE_DEPS :=
+UNMOUNTED_NOTICE_DEPS :=
+ODM_NOTICE_DEPS :=
+OEM_NOTICE_DEPS :=
+PRODUCT_NOTICE_DEPS :=
+SYSTEM_EXT_NOTICE_DEPS :=
+VENDOR_DLKM_NOTICE_DEPS :=
+ODM_DLKM_NOTICE_DEPS :=
+SYSTEM_DLKM_NOTICE_DEPS :=
+
 # -----------------------------------------------------------------
 # Define rules to copy PRODUCT_COPY_FILES defined by the product.
 # PRODUCT_COPY_FILES contains words like <source file>:<dest file>[:<owner>].
@@ -83,6 +94,8 @@
 $(pcf_ignored_file):
 	echo "$(PRIVATE_IGNORED)" | tr " " "\n" >$@
 
+$(call declare-0p-target,$(pcf_ignored_file))
+
 $(call dist-for-goals,droidcore-unbundled,$(pcf_ignored_file):logs/$(notdir $(pcf_ignored_file)))
 
 pcf_ignored_file :=
@@ -157,13 +170,17 @@
 	    echo "HTML_OUTPUT=$(ndk_doxygen_out)" \
 	) | doxygen -
 
+$(call declare-1p-target,$(ndk_doxygen_out)/index.html,)
+
 # Note: Not a part of the docs target because we don't have doxygen available.
 # You can run this target locally if you have doxygen installed.
 ndk-docs: $(ndk_doxygen_out)/index.html
 .PHONY: ndk-docs
 endif
 
+ifeq ($(HOST_OS),linux)
 $(call dist-for-goals,sdk,$(API_FINGERPRINT))
+endif
 
 INSTALLED_RECOVERYIMAGE_TARGET :=
 # Build recovery image if
@@ -206,10 +223,12 @@
 	@mkdir -p $(dir $@)
 	$(hide) grep -v "$(subst $(space),\|,$(strip \
 	            $(sdk_build_prop_remove)))" $< > $@.tmp
-	$(hide) for x in $(sdk_build_prop_remove); do \
+	$(hide) for x in $(strip $(sdk_build_prop_remove)); do \
 	            echo "$$x"generic >> $@.tmp; done
 	$(hide) mv $@.tmp $@
 
+$(call declare-0p-target,$(INSTALLED_SDK_BUILD_PROP_TARGET))
+
 # -----------------------------------------------------------------
 # declare recovery ramdisk files
 ifeq ($(BUILDING_RECOVERY_IMAGE),true)
@@ -320,11 +339,11 @@
 define build-image-kernel-modules-depmod
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: .KATI_IMPLICIT_OUTPUTS := $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.alias $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.softdep $(3)/$(DEPMOD_STAGING_SUBDIR)/$(5)
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: $(DEPMOD)
-$(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_MODULES := $(1)
+$(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_MODULES := $(strip $(1))
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_MOUNT_POINT := $(2)
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_MODULE_DIR := $(3)/$(DEPMOD_STAGING_SUBDIR)/$(2)/lib/modules/$(8)
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_STAGING_DIR := $(3)
-$(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_LOAD_MODULES := $(4)
+$(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_LOAD_MODULES := $(strip $(4))
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_LOAD_FILE := $(3)/$(DEPMOD_STAGING_SUBDIR)/$(5)
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_MODULE_ARCHIVE := $(6)
 $(3)/$(DEPMOD_STAGING_SUBDIR)/modules.dep: PRIVATE_OUTPUT_DIR := $(7)
@@ -487,6 +506,12 @@
   endif
 endif
 
+ifneq ($(BOARD_DO_NOT_STRIP_RECOVERY_MODULES),true)
+	RECOVERY_STRIPPED_MODULE_STAGING_DIR := $(call intermediates-dir-for,PACKAGING,depmod_recovery_stripped)
+else
+	RECOVERY_STRIPPED_MODULE_STAGING_DIR :=
+endif
+
 ifneq ($(BOARD_DO_NOT_STRIP_VENDOR_MODULES),true)
 	VENDOR_STRIPPED_MODULE_STAGING_DIR := $(call intermediates-dir-for,PACKAGING,depmod_vendor_stripped)
 else
@@ -499,9 +524,15 @@
   VENDOR_RAMDISK_STRIPPED_MODULE_STAGING_DIR :=
 endif
 
+ifneq ($(BOARD_DO_NOT_STRIP_VENDOR_KERNEL_RAMDISK_MODULES),true)
+  VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR := $(call intermediates-dir-for,PACKAGING,depmod_vendor_kernel_ramdisk_stripped)
+else
+  VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR :=
+endif
+
 BOARD_KERNEL_MODULE_DIRS += top
 $(foreach kmd,$(BOARD_KERNEL_MODULE_DIRS), \
-  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,RECOVERY,$(TARGET_RECOVERY_ROOT_OUT),,modules.load.recovery,,$(kmd))) \
+  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,RECOVERY,$(TARGET_RECOVERY_ROOT_OUT),,modules.load.recovery,$(RECOVERY_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
   $(eval vendor_ramdisk_fragment := $(KERNEL_MODULE_DIR_VENDOR_RAMDISK_FRAGMENT_$(kmd))) \
   $(if $(vendor_ramdisk_fragment), \
     $(eval output_dir := $(VENDOR_RAMDISK_FRAGMENT.$(vendor_ramdisk_fragment).STAGING_DIR)) \
@@ -510,6 +541,7 @@
     $(eval output_dir := $(TARGET_VENDOR_RAMDISK_OUT)) \
     $(eval result_var := ALL_DEFAULT_INSTALLED_MODULES)) \
   $(eval $(result_var) += $(call build-image-kernel-modules-dir,VENDOR_RAMDISK,$(output_dir),,modules.load,$(VENDOR_RAMDISK_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
+  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,VENDOR_KERNEL_RAMDISK,$(TARGET_VENDOR_KERNEL_RAMDISK_OUT),,modules.load,$(VENDOR_KERNEL_RAMDISK_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-ramdisk-recovery-load,$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,VENDOR,$(if $(filter true,$(BOARD_USES_VENDOR_DLKMIMAGE)),$(TARGET_OUT_VENDOR_DLKM),$(TARGET_OUT_VENDOR)),vendor,modules.load,$(VENDOR_STRIPPED_MODULE_STAGING_DIR),$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-charger-load,$(kmd))) \
@@ -567,9 +599,13 @@
 	    $(if $(PACKAGES.$(p).EXTERNAL_KEY),\
 	      $(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),EXTERNAL,,$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@),\
 	      $(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),$(PACKAGES.$(p).CERTIFICATE),$(PACKAGES.$(p).PRIVATE_KEY),$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@))))
+	$(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),\
+	  $(call _apkcerts_write_line,$(notdir $(basename $(FSVERITY_APK_OUT))),$(FSVERITY_APK_KEY_PATH).x509.pem,$(FSVERITY_APK_KEY_PATH).pk8,,system,$@))
 	# In case value of PACKAGES is empty.
 	$(hide) touch $@
 
+$(call declare-0p-target,$(APKCERTS_FILE))
+
 .PHONY: apkcerts-list
 apkcerts-list: $(APKCERTS_FILE)
 
@@ -586,6 +622,7 @@
 	@rm -f $@
 	@$(foreach s,$(STATS.MODULE_TYPE),echo "modules_type_make,$(s),$(words $(STATS.MODULE_TYPE.$(s)))" >>$@;)
 	@$(foreach s,$(STATS.SOONG_MODULE_TYPE),echo "modules_type_soong,$(s),$(STATS.SOONG_MODULE_TYPE.$(s))" >>$@;)
+$(call declare-1p-target,$(BUILD_SYSTEM_STATS),build)
 $(call dist-for-goals,droidcore-unbundled,$(BUILD_SYSTEM_STATS))
 
 # -----------------------------------------------------------------
@@ -606,35 +643,48 @@
 	@rm -f $@
 	@$(foreach s,$(SOONG_CONV),echo "$(s),$(SOONG_CONV.$(s).TYPE),$(sort $(SOONG_CONV.$(s).PROBLEMS)),$(sort $(filter-out $(SOONG_ALREADY_CONV),$(SOONG_CONV.$(s).DEPS))),$(sort $(SOONG_CONV.$(s).MAKEFILES)),$(sort $(SOONG_CONV.$(s).INSTALLED))" >>$@;)
 
+$(call declare-1p-target,$(SOONG_CONV_DATA),build)
+
 SOONG_TO_CONVERT_SCRIPT := build/make/tools/soong_to_convert.py
 SOONG_TO_CONVERT := $(PRODUCT_OUT)/soong_to_convert.txt
 $(SOONG_TO_CONVERT): $(SOONG_CONV_DATA) $(SOONG_TO_CONVERT_SCRIPT)
 	@rm -f $@
 	$(hide) $(SOONG_TO_CONVERT_SCRIPT) $< >$@
+$(call declare-1p-target,$(SOONG_TO_CONVERT),build)
 $(call dist-for-goals,droidcore-unbundled,$(SOONG_TO_CONVERT))
 
+$(PRODUCT_OUT)/product_packages.txt:
+	@rm -f $@
+	echo "" > $@
+	$(foreach x,$(PRODUCT_PACKAGES),echo $(x) >> $@$(newline))
+
 MK2BP_CATALOG_SCRIPT := build/make/tools/mk2bp_catalog.py
+PRODUCT_PACKAGES_TXT := $(PRODUCT_OUT)/product_packages.txt
 MK2BP_REMAINING_HTML := $(PRODUCT_OUT)/mk2bp_remaining.html
 $(MK2BP_REMAINING_HTML): PRIVATE_CODE_SEARCH_BASE_URL := "https://cs.android.com/android/platform/superproject/+/master:"
-$(MK2BP_REMAINING_HTML): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT)
+$(MK2BP_REMAINING_HTML): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT) $(PRODUCT_PACKAGES_TXT)
 	@rm -f $@
 	$(hide) $(MK2BP_CATALOG_SCRIPT) \
 		--device=$(TARGET_DEVICE) \
+		--product-packages=$(PRODUCT_PACKAGES_TXT) \
 		--title="Remaining Android.mk files for $(TARGET_DEVICE)-$(TARGET_BUILD_VARIANT)" \
 		--codesearch=$(PRIVATE_CODE_SEARCH_BASE_URL) \
-		--out_dir="$(OUT_DIR)" \
+		--out-dir="$(OUT_DIR)" \
 		--mode=html \
 		> $@
+$(call declare-1p-target,$(MK2BP_REMAINING_HTML),build)
 $(call dist-for-goals,droidcore-unbundled,$(MK2BP_REMAINING_HTML))
 
 MK2BP_REMAINING_CSV := $(PRODUCT_OUT)/mk2bp_remaining.csv
-$(MK2BP_REMAINING_CSV): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT)
+$(MK2BP_REMAINING_CSV): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT) $(PRODUCT_PACKAGES_TXT)
 	@rm -f $@
 	$(hide) $(MK2BP_CATALOG_SCRIPT) \
 		--device=$(TARGET_DEVICE) \
-		--out_dir="$(OUT_DIR)" \
+		--product-packages=$(PRODUCT_PACKAGES_TXT) \
+		--out-dir="$(OUT_DIR)" \
 		--mode=csv \
 		> $@
+$(call declare-1p-target,$(MK2BP_REMAINING_CSV))
 $(call dist-for-goals,droidcore-unbundled,$(MK2BP_REMAINING_CSV))
 
 # -----------------------------------------------------------------
@@ -647,12 +697,16 @@
 	echo "# Modules added default -Wall" >> $@
 	for m in $(sort $(SOONG_MODULES_ADDED_WALL) $(MODULES_ADDED_WALL)); do echo $$m >> $@; done
 
+$(call declare-0p-target,$(WALL_WERROR))
+
 $(call dist-for-goals,droidcore-unbundled,$(WALL_WERROR))
 
 # -----------------------------------------------------------------
 # C/C++ flag information for modules
 $(call dist-for-goals,droidcore-unbundled,$(SOONG_MODULES_CFLAG_ARTIFACTS))
 
+$(foreach a,$(SOONG_MODULES_CFLAG_ARTIFACTS),$(call declare-0p-target,$(call word-colon,1,$(a))))
+
 # -----------------------------------------------------------------
 # Modules missing profile files
 PGO_PROFILE_MISSING := $(PRODUCT_OUT)/pgo_profile_file_missing.txt
@@ -661,12 +715,15 @@
 	echo "# Modules missing PGO profile files" >> $@
 	for m in $(SOONG_MODULES_MISSING_PGO_PROFILE_FILE); do echo $$m >> $@; done
 
+$(call declare-0p-target,$(PGO_PROFILE_MISSING))
+
 $(call dist-for-goals,droidcore,$(PGO_PROFILE_MISSING))
 
 CERTIFICATE_VIOLATION_MODULES_FILENAME := $(PRODUCT_OUT)/certificate_violation_modules.txt
 $(CERTIFICATE_VIOLATION_MODULES_FILENAME):
 	rm -f $@
 	$(foreach m,$(sort $(CERTIFICATE_VIOLATION_MODULES)), echo $(m) >> $@;)
+$(call declare-0p-target,$(CERTIFICATE_VIOLATION_MODULES_FILENAME))
 $(call dist-for-goals,droidcore,$(CERTIFICATE_VIOLATION_MODULES_FILENAME))
 
 # -----------------------------------------------------------------
@@ -703,6 +760,8 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(MERGETAGS) -o $@ $(PRIVATE_SRC_FILES)
 
+$(call declare-0p-target,$(all_event_log_tags_file))
+
 # Include tags from all packages included in this product, plus all
 # tags that are part of the system (ie, not in a vendor/ or device/
 # directory).
@@ -719,10 +778,19 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(MERGETAGS) -o $@ -m $(PRIVATE_MERGED_FILE) $(PRIVATE_SRC_FILES)
 
+$(eval $(call declare-0p-target,$(event_log_tags_file)))
+
 event-log-tags: $(event_log_tags_file)
 
 ALL_DEFAULT_INSTALLED_MODULES += $(event_log_tags_file)
 
+# Initialize INSTALLED_FILES_OUTSIDE_IMAGES with the list of all device files,
+# files installed in images will be filtered out later.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out \
+  $(PRODUCT_OUT)/apex/% \
+  $(PRODUCT_OUT)/fake_packages/% \
+  $(PRODUCT_OUT)/testcases/%, \
+  $(filter $(PRODUCT_OUT)/%,$(ALL_DEFAULT_INSTALLED_MODULES)))
 
 # #################################################################
 # Targets for boot/OS images
@@ -755,21 +823,24 @@
 
 # -----------------------------------------------------------------
 # the root dir
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_ROOT_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_ROOT_FILES := $(filter $(TARGET_ROOT_OUT)/%, \
-	$(ALL_GENERATED_SOURCES) \
 	$(ALL_DEFAULT_INSTALLED_MODULES))
 
+
 INSTALLED_FILES_FILE_ROOT := $(PRODUCT_OUT)/installed-files-root.txt
 INSTALLED_FILES_JSON_ROOT := $(INSTALLED_FILES_FILE_ROOT:.txt=.json)
 $(INSTALLED_FILES_FILE_ROOT): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_ROOT)
 $(INSTALLED_FILES_FILE_ROOT) : $(INTERNAL_ROOT_FILES) $(FILESLIST) $(FILESLIST_UTIL)
 	@echo Installed file list: $@
+	mkdir -p $(TARGET_ROOT_OUT)
 	mkdir -p $(dir $@)
 	rm -f $@
 	$(FILESLIST) $(TARGET_ROOT_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
-$(call dist-for-goals, sdk win_sdk sdk_addon, $(INSTALLED_FILES_FILE_ROOT))
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ROOT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ROOT))
 
 #------------------------------------------------------------------
 # dtb
@@ -783,9 +854,9 @@
 
 # -----------------------------------------------------------------
 # the ramdisk
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_RAMDISK_IMAGE
 INTERNAL_RAMDISK_FILES := $(filter $(TARGET_RAMDISK_OUT)/%, \
-	$(ALL_GENERATED_SOURCES) \
 	$(ALL_DEFAULT_INSTALLED_MODULES))
 
 INSTALLED_FILES_FILE_RAMDISK := $(PRODUCT_OUT)/installed-files-ramdisk.txt
@@ -799,7 +870,9 @@
 	$(FILESLIST) $(TARGET_RAMDISK_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
-$(call dist-for-goals, sdk win_sdk sdk_addon, $(INSTALLED_FILES_FILE_RAMDISK))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_RAMDISK)))
+
 BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img
 
 ifeq ($(BOARD_RAMDISK_USE_LZ4),true)
@@ -815,10 +888,20 @@
 
 # We just build this directly to the install location.
 INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET)
+$(INSTALLED_RAMDISK_TARGET): PRIVATE_DIRS := debug_ramdisk dev metadata mnt proc second_stage_resources sys
 $(INSTALLED_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_RAMDISK_FILES) $(INSTALLED_FILES_FILE_RAMDISK) | $(COMPRESSION_COMMAND_DEPS)
 	$(call pretty,"Target ramdisk: $@")
+	$(hide) mkdir -p $(addprefix $(TARGET_RAMDISK_OUT)/,$(PRIVATE_DIRS))
+ifeq (true,$(BOARD_USES_GENERIC_KERNEL_IMAGE))
+	$(hide) mkdir -p $(addprefix $(TARGET_RAMDISK_OUT)/first_stage_ramdisk/,$(PRIVATE_DIRS))
+endif
 	$(hide) $(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_RAMDISK_OUT) | $(COMPRESSION_COMMAND) > $@
 
+$(call declare-1p-container,$(INSTALLED_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_RAMDISK_TARGET),$(INTERNAL_RAMDISK_FILE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_RAMDISK_TARGET)
+
 .PHONY: ramdisk-nodeps
 ramdisk-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
 	@echo "make $@: ignoring dependencies"
@@ -852,6 +935,7 @@
   my_apex_extracted_boot_image := $(ALL_MODULES.$(my_installed_prebuilt_gki_apex).EXTRACTED_BOOT_IMAGE)
   INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
   $(eval $(call copy-one-file,$(my_apex_extracted_boot_image),$(INSTALLED_BOOTIMAGE_TARGET)))
+  $(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",boot)
 
   INTERNAL_PREBUILT_BOOTIMAGE := $(my_apex_extracted_boot_image)
 
@@ -872,15 +956,29 @@
 # $1: boot image file name
 # $2: boot image variant (boot, boot-debug, boot-test-harness)
 define get-bootimage-partition-size
-  $(BOARD_$(call to-upper,$(subst .img,,$(subst $(2),kernel,$(notdir $(1)))))_BOOTIMAGE_PARTITION_SIZE)
+$(BOARD_$(call to-upper,$(subst .img,,$(subst $(2),kernel,$(notdir $(1)))))_BOOTIMAGE_PARTITION_SIZE)
+endef
+
+# $1: partition size
+define get-partition-size-argument
+  $(if $(1),--partition_size $(1),--dynamic_partition_size)
 endef
 
 ifneq ($(strip $(TARGET_NO_KERNEL)),true)
 INTERNAL_BOOTIMAGE_ARGS := \
 	$(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET))
 
-ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
-INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+INTERNAL_INIT_BOOT_IMAGE_ARGS :=
+
+# TODO(b/229701033): clean up BOARD_BUILD_GKI_BOOT_IMAGE_WITHOUT_RAMDISK.
+ifneq ($(BOARD_BUILD_GKI_BOOT_IMAGE_WITHOUT_RAMDISK),true)
+  ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+    ifneq ($(BUILDING_INIT_BOOT_IMAGE),true)
+      INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+    else
+      INTERNAL_INIT_BOOT_IMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+    endif
+  endif
 endif
 
 ifndef BUILDING_VENDOR_BOOT_IMAGE
@@ -922,25 +1020,50 @@
   endif
 endif # BUILDING_VENDOR_BOOT_IMAGE == "" && BOARD_USES_GENERIC_KERNEL_IMAGE != true
 
-INTERNAL_MKBOOTIMG_VERSION_ARGS := \
+ifdef BOARD_GKI_SIGNING_KEY_PATH
+  # GKI boot images will not set system version & SPL value in the header.
+  # They can be set by the device manufacturer in the AVB properties instead.
+  INTERNAL_MKBOOTIMG_VERSION_ARGS :=
+else
+  INTERNAL_MKBOOTIMG_VERSION_ARGS := \
     --os_version $(PLATFORM_VERSION_LAST_STABLE) \
     --os_patch_level $(PLATFORM_SECURITY_PATCH)
+endif # BOARD_GKI_SIGNING_KEY_PATH
 
+# $(1): image target to certify
+# $(2): out certificate target
+# $(3): image name
+# $(4): additional AVB arguments
+define generate_generic_boot_image_certificate
+  rm -rf "$(2)"
+  mkdir -p "$(dir $(2))"
+  $(GENERATE_GKI_CERTIFICATE) $(INTERNAL_GKI_CERTIFICATE_ARGS) \
+    --additional_avb_args "$(4)" \
+    --name "$(3)" --output "$(2)" "$(1)"
+endef
+
+INTERNAL_GKI_CERTIFICATE_ARGS :=
+INTERNAL_GKI_CERTIFICATE_DEPS :=
 ifdef BOARD_GKI_SIGNING_KEY_PATH
-ifndef BOARD_GKI_SIGNING_ALGORITHM
-$(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
-endif
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS := \
-    --gki_signing_key $(BOARD_GKI_SIGNING_KEY_PATH) \
-    --gki_signing_algorithm $(BOARD_GKI_SIGNING_ALGORITHM) \
-    --gki_signing_avbtool_path $(AVBTOOL)
-endif
+  ifndef BOARD_GKI_SIGNING_ALGORITHM
+    $(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
+  endif
 
-# Using double quote to pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string
-# to MKBOOTIMG, although it may contain multiple args.
-ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS += \
-    --gki_signing_signature_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+  INTERNAL_GKI_CERTIFICATE_ARGS := \
+    --key "$(BOARD_GKI_SIGNING_KEY_PATH)" \
+    --algorithm "$(BOARD_GKI_SIGNING_ALGORITHM)" \
+    --avbtool "$(AVBTOOL)"
+
+  # Quote and pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string argument.
+  ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
+    INTERNAL_GKI_CERTIFICATE_ARGS += --additional_avb_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+  endif
+
+  INTERNAL_GKI_CERTIFICATE_DEPS := \
+    $(GENERATE_GKI_CERTIFICATE) \
+    $(BOARD_GKI_SIGNING_KEY_PATH) \
+    $(AVBTOOL)
+
 endif
 
 # Define these only if we are building boot
@@ -957,22 +1080,36 @@
 
 # $1: boot image target
 define build_boot_board_avb_enabled
-  $(MKBOOTIMG) --kernel $(call bootimage-to-kernel,$(1)) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
-               $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(1)
+  $(eval kernel := $(call bootimage-to-kernel,$(1)))
+  $(MKBOOTIMG) --kernel $(kernel) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(1)
+  $(if $(BOARD_GKI_SIGNING_KEY_PATH), \
+    $(eval boot_signature := $(call intermediates-dir-for,PACKAGING,generic_boot)/$(notdir $(1)).boot_signature) \
+    $(eval kernel_signature := $(call intermediates-dir-for,PACKAGING,generic_kernel)/$(notdir $(kernel)).boot_signature) \
+    $(call generate_generic_boot_image_certificate,$(1),$(boot_signature),boot,$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)) $(newline) \
+    $(call generate_generic_boot_image_certificate,$(kernel),$(kernel_signature),generic_kernel,$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)) $(newline) \
+    cat $(kernel_signature) >> $(boot_signature) $(newline) \
+    $(call assert-max-image-size,$(boot_signature),16 << 10) $(newline) \
+    truncate -s $$(( 16 << 10 )) $(boot_signature) $(newline) \
+    cat "$(boot_signature)" >> $(1))
   $(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(call get-bootimage-partition-size,$(1),boot)))
   $(AVBTOOL) add_hash_footer \
           --image $(1) \
-          --partition_size $(call get-bootimage-partition-size,$(1),boot) \
+          $(call get-partition-size-argument,$(call get-bootimage-partition-size,$(1),boot)) \
           --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \
           $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
 endef
 
-$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_board_avb_enabled,$@)
 
+$(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",boot)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES) $(INTERNAL_GKI_CERTIFICATE_DEPS),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
-bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
 	@echo "make $@: ignoring dependencies"
 	$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET),$(call build_boot_board_avb_enabled,$(b)))
 
@@ -989,6 +1126,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_supports_boot_signer,$@)
 
+$(call declare-1p-container,$(INSTALLED_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG) $(BOOT_SIGNER)
 	@echo "make $@: ignoring dependencies"
@@ -1007,6 +1149,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_supports_vboot,$@)
 
+$(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",boot)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG) $(VBOOT_SIGNER) $(FUTILITY)
 	@echo "make $@: ignoring dependencies"
@@ -1024,6 +1171,11 @@
 	$(call pretty,"Target boot image: $@")
 	$(call build_boot_novboot,$@)
 
+$(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",boot)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_BOOTIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage-nodeps
 bootimage-nodeps: $(MKBOOTIMG)
 	@echo "make $@: ignoring dependencies"
@@ -1042,9 +1194,14 @@
 	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
 	$(AVBTOOL) add_hash_footer \
 	    --image $@ \
-	    --partition_size $(BOARD_BOOTIMAGE_PARTITION_SIZE) \
+	    $(call get-partition-size-argument,$(BOARD_BOOTIMAGE_PARTITION_SIZE)) \
 	    --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \
 	    $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",bool)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(INTERNAL_PREBUILT_BOOTIMAGE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
 else
 $(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_PREBUILT_BOOTIMAGE)
 	cp $(INTERNAL_PREBUILT_BOOTIMAGE) $@
@@ -1060,7 +1217,75 @@
 my_installed_prebuilt_gki_apex :=
 
 # -----------------------------------------------------------------
+#  init boot image
+ifeq ($(BUILDING_INIT_BOOT_IMAGE),true)
+
+INSTALLED_INIT_BOOT_IMAGE_TARGET := $(PRODUCT_OUT)/init_boot.img
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_RAMDISK_TARGET)
+
+ifdef BOARD_KERNEL_PAGESIZE
+  INTERNAL_INIT_BOOT_IMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
+endif
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
+	$(call pretty,"Target init_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_INIT_ARGS) --output "$@"
+	$(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
+	$(AVBTOOL) add_hash_footer \
+           --image $@ \
+	   $(call get-partition-size-argument,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)) \
+	   --partition_name init_boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
+	   $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),$(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE),$(PRODUCT_OUT)/:/)
+else
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET):
+	$(call pretty,"Target init_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_INIT_ARGS) --output $@
+	$(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
+
+$(call declare-1p-target,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+endif
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
+else # BUILDING_INIT_BOOT_IMAGE is not true
+
+ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+INTERNAL_PREBUILT_INIT_BOOT_IMAGE := $(BOARD_PREBUILT_INIT_BOOT_IMAGE)
+INSTALLED_INIT_BOOT_IMAGE_TARGET := $(PRODUCT_OUT)/init_boot.img
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
+	cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+	$(AVBTOOL) add_hash_footer \
+	    --image $@ \
+	    $(call get-partition-size-argument,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)) \
+	    --partition_name boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
+	    $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),$(INTERNAL_PREBUILT_INIT_BOOT_IMAGE),$(PRODUCT_OUT)/:/)
+else
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE)
+	cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+
+$(call declare-1p-target,$(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+endif # BOARD_AVB_ENABLE
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
+else # BOARD_PREBUILT_INIT_BOOT_IMAGE not defined
+INSTALLED_INIT_BOOT_IMAGE_TARGET :=
+endif # BOARD_PREBUILT_INIT_BOOT_IMAGE
+
+endif # BUILDING_INIT_BOOT_IMAGE is not true
+
+# -----------------------------------------------------------------
 # vendor boot image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_VENDOR_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
 
 ifeq ($(PRODUCT_SUPPORTS_VERITY),true)
@@ -1068,7 +1293,6 @@
 endif
 
 INTERNAL_VENDOR_RAMDISK_FILES := $(filter $(TARGET_VENDOR_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
 INTERNAL_VENDOR_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot)/vendor_ramdisk.cpio$(RAMDISK_EXT)
@@ -1085,12 +1309,15 @@
 $(INTERNAL_VENDOR_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
 	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_VENDOR_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
 
-ifeq (true,$(BOARD_BUILD_VENDOR_RAMDISK_IMAGE))
 INSTALLED_VENDOR_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor_ramdisk.img
 $(INSTALLED_VENDOR_RAMDISK_TARGET): $(INTERNAL_VENDOR_RAMDISK_TARGET)
-	$(call pretty,"Target vendor ramdisk: $@")
+	@echo "Target vendor ramdisk: $@"
 	$(copy-file-to-target)
-endif
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_RAMDISK_TARGET),$(INTERNAL_VENDOR_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_RAMDISK_TARGET)
 
 INSTALLED_FILES_FILE_VENDOR_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-ramdisk.txt
 INSTALLED_FILES_JSON_VENDOR_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_RAMDISK:.txt=.json)
@@ -1103,8 +1330,15 @@
 	$(FILESLIST) $(TARGET_VENDOR_RAMDISK_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_RAMDISK)))
+
 ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
-  INTERNAL_VENDOR_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  ifneq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
+    # If we have vendor_kernel_boot partition, we migrate dtb image to that image
+    # and allow dtb in vendor_boot to be empty.
+    INTERNAL_VENDOR_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  endif
 endif
 ifdef BOARD_KERNEL_BASE
   INTERNAL_VENDOR_BOOTIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
@@ -1175,7 +1409,7 @@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 	$(AVBTOOL) add_hash_footer \
            --image $@ \
-	   --partition_size $(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE) \
+	   $(call get-partition-size-argument,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE)) \
 	   --partition_name vendor_boot $(INTERNAL_AVB_VENDOR_BOOT_SIGNING_ARGS) \
 	   $(BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS)
 else
@@ -1184,9 +1418,83 @@
 	$(MKBOOTIMG) $(INTERNAL_VENDOR_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_ramdisk $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_ARGS) --vendor_boot $@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 endif
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DTB_IMAGE_TARGET) $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_TARGETS) $(INTERNAL_VENDOR_BOOTCONDIG_TARGET),$(PRODUCT_OUT)/:/)
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_BOOTIMAGE_TARGET)
 endif # BUILDING_VENDOR_BOOT_IMAGE
 
 # -----------------------------------------------------------------
+# vendor kernel boot image
+ifeq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
+
+INTERNAL_VENDOR_KERNEL_RAMDISK_FILES := $(filter $(TARGET_VENDOR_KERNEL_RAMDISK_OUT)/%, \
+    $(ALL_DEFAULT_INSTALLED_MODULES))
+
+INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_kernel_boot)/vendor_kernel_ramdisk.cpio$(RAMDISK_EXT)
+
+$(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_KERNEL_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_VENDOR_KERNEL_RAMDISK_OUT) | $(COMPRESSION_COMMAND) > $@
+
+INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor_kernel_ramdisk.img
+$(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET): $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+	@echo "Target vendor kernel ramdisk: $@"
+	$(copy-file-to-target)
+
+INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-kernel-ramdisk.txt
+INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK:.txt=.json)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK): $(INTERNAL_VENDOR_KERNEL_RAMDISK_FILES) $(FILESLIST) $(FILESLIST_UTIL)
+	@echo Installed file list: $@
+	mkdir -p $(dir $@)
+	rm -f $@
+	$(FILESLIST) $(TARGET_VENDOR_KERNEL_RAMDISK_OUT) > $(@:.txt=.json)
+	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK))
+
+INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS := --vendor_ramdisk $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/vendor_kernel_boot.img
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET)
+
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+  INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS += --dtb $(INSTALLED_DTBIMAGE_TARGET)
+  $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(INSTALLED_DTBIMAGE_TARGET)
+endif
+ifdef BOARD_KERNEL_PAGESIZE
+  INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
+endif
+
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET): $(AVBTOOL) $(BOARD_AVB_VENDOR_KERNEL_BOOTIMAGE_KEY_PATH)
+	$(call pretty,"Target vendor_kernel_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_boot $@
+	$(call assert-max-image-size,$@,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE))
+	$(AVBTOOL) add_hash_footer \
+	    --image $@ \
+	   $(call get-partition-size-argument,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE)) \
+	   --partition_name vendor_kernel_boot $(INTERNAL_AVB_VENDOR_KERNEL_BOOT_SIGNING_ARGS) \
+	   $(BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS)
+else
+$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):
+	$(call pretty,"Target vendor_kernel_boot image: $@")
+	$(MKBOOTIMG) $(INTERNAL_VENDOR_KERNEL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --vendor_boot $@
+	$(call assert-max-image-size,$@,$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE))
+endif
+$(call declare-1p-container,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
+ifdef BOARD_INCLUDE_DTB_IN_BOOTIMG
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),\
+    $(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET) $(INSTALLED_DTBIMAGE_TARGET),\
+    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):)
+else
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_KERNEL_RAMDISK_TARGET),$(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET):)
+endif
+endif # BUILDING_VENDOR_KERNEL_BOOT_IMAGE
+
+# -----------------------------------------------------------------
 # NOTICE files
 #
 # We are required to publish the licenses for all code under BSD, GPL and
@@ -1200,60 +1508,64 @@
 
 .PHONY: notice_files
 
-# Create the rule to combine the files into text and html/xml forms
-# $(1) - xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm|
-#        xml_excluded_vendor_product_odm_vendor_dlkm_odm_dlkm|
-#        xml_product|xml_odm|xml_system_ext|xml_system|xml_vendor_dlkm|
-#        xml_odm_dlkm|html
-# $(2) - Plain text output file
-# $(3) - HTML/XML output file
-# $(4) - File title
-# $(5) - Directory to use.  Notice files are all $(5)/src.  Other
-#		 directories in there will be used for scratch
-# $(6) - Dependencies for the output files
-# $(7) - Directories to exclude
+# Convert license metadata into xml notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
 #
-# The algorithm here is that we go collect a hash for each of the notice
-# files and write the names of the files that match that hash.  Then
-# to generate the real files, we go print out all of the files and their
-# hashes.
-#
-# These rules are fairly complex, so they depend on this makefile so if
-# it changes, they'll run again.
-#
-# TODO: We could clean this up so that we just record the locations of the
-# original notice files instead of making rules to copy them somwehere.
-# Then we could traverse that without quite as much bash drama.
-define combine-notice-files
-$(2): PRIVATE_MESSAGE := $(4)
-$(2): PRIVATE_DIR := $(5)
-$(2): .KATI_IMPLICIT_OUTPUTS := $(3)
-$(2): $(6) $(BUILD_SYSTEM)/Makefile build/make/tools/generate-notice-files.py
-	build/make/tools/generate-notice-files.py --text-output $(2) $(foreach xdir, $(7), -e $(xdir) )\
-	    $(if $(filter $(1),xml_excluded_vendor_product_odm_vendor_dlkm_odm_dlkm),-e vendor -e product -e system_ext -e odm -e vendor_dlkm -e odm_dlkm --xml-output, \
-	      $(if $(filter $(1),xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm),-e system -e product -e system_ext -e odm -e vendor_dlkm -e odm_dlkm --xml-output, \
-	        $(if $(filter $(1),xml_product),-i product --xml-output, \
-	          $(if $(filter $(1),xml_system_ext),-i system_ext --xml-output, \
-	            $(if $(filter $(1),xml_system),-i system --xml-output, \
-	              $(if $(filter $(1),xml_odm),-i odm --xml-output, \
-	                $(if $(filter $(1),xml_vendor_dlkm),-i vendor_dlkm --xml-output, \
-	                  $(if $(filter $(1),xml_odm_dlkm),-i odm_dlkm --xml-output, \
-	                    --html-output)))))))) $(3) \
-	    -t $$(PRIVATE_MESSAGE) $$(foreach dir,$$(sort $$(PRIVATE_DIR)), -s $$(dir)/src)
-notice_files: $(2) $(3)
+define xml-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): PRIVATE_DEPS := $(call corresponding-license-metadata,$(4))
+$(1): $(call corresponding-license-metadata,$(4)) $(XMLNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(XMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $$(PRIVATE_DEPS)
+
+notice_files: $(1)
 endef
 
+# Convert license metadata into text notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
+#
+define text-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): $(call corresponding-license-metadata,$(4)) $(TEXTNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(TEXTNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+
+notice_files: $(1)
+endef
+
+# Conversion license metadata into html notice file.
+# $(1) - Output target notice filename
+# $(2) - Product name
+# $(3) - File title
+# $(4) - License metadata file roots
+# $(5) - Prefixes to strip
+#
+define html-notice-rule
+$(1): PRIVATE_PRODUCT := $(2)
+$(1): PRIVATE_MESSAGE := $(3)
+$(1): $(call corresponding-license-metadata,$(4)) $(HTMLNOTICE) $(BUILD_SYSTEM)/Makefile
+	OUT_DIR=$(OUT_DIR) $(HTMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+
+notice_files: $(1)
+endef
+
+$(KATI_obsolete_var combine-notice-files, To create notice files use xml-notice-rule, html-notice-rule, or text-notice-rule.)
+
 # Notice file logic isn't relevant for TARGET_BUILD_APPS
 ifndef TARGET_BUILD_APPS
 
 # TODO These intermediate NOTICE.txt/NOTICE.html files should go into
 # TARGET_OUT_NOTICE_FILES now that the notice files are gathered from
 # the src subdirectory.
-target_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE.txt
-tools_notice_file_txt := $(HOST_OUT_INTERMEDIATES)/NOTICE.txt
-tools_notice_file_html := $(HOST_OUT_INTERMEDIATES)/NOTICE.html
 kernel_notice_file := $(TARGET_OUT_NOTICE_FILES)/src/kernel.txt
-winpthreads_notice_file := $(TARGET_OUT_NOTICE_FILES)/src/winpthreads.txt
 
 # Some targets get included under $(PRODUCT_OUT) for debug symbols or other
 # reasons--not to be flashed onto any device. Targets under these directories
@@ -1262,55 +1574,45 @@
 
 # TODO(b/69865032): Make PRODUCT_NOTICE_SPLIT the default behavior.
 ifneq ($(PRODUCT_NOTICE_SPLIT),true)
-target_notice_file_html := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html
+#target_notice_file_html := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html
 target_notice_file_html_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE.html.gz
 installed_notice_html_or_xml_gz := $(TARGET_OUT)/etc/NOTICE.html.gz
-$(eval $(call combine-notice-files, html, \
-	        $(target_notice_file_txt), \
-	        $(target_notice_file_html), \
-	        "Notices for files contained in the filesystem images in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(ALL_DEFAULT_INSTALLED_MODULES) $(kernel_notice_file), \
-	        $(exclude_target_dirs)))
-$(target_notice_file_html_gz): $(target_notice_file_html) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(installed_notice_html_or_xml_gz): $(target_notice_file_html_gz)
-	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_notice_file_html_gz))
+$(call declare-0p-target,$(installed_notice_html_or_xml_gz))
 else
-target_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml
+# target_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml
 target_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE.xml.gz
 installed_notice_html_or_xml_gz := $(TARGET_OUT)/etc/NOTICE.xml.gz
 
 target_vendor_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.txt
-target_vendor_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.xml
 target_vendor_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR.xml.gz
 installed_vendor_notice_xml_gz := $(TARGET_OUT_VENDOR)/etc/NOTICE.xml.gz
 
 target_product_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.txt
-target_product_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.xml
 target_product_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_PRODUCT.xml.gz
 installed_product_notice_xml_gz := $(TARGET_OUT_PRODUCT)/etc/NOTICE.xml.gz
 
 target_system_ext_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.txt
-target_system_ext_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.xml
 target_system_ext_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_EXT.xml.gz
 installed_system_ext_notice_xml_gz := $(TARGET_OUT_SYSTEM_EXT)/etc/NOTICE.xml.gz
 
 target_odm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.txt
-target_odm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.xml
 target_odm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM.xml.gz
 installed_odm_notice_xml_gz := $(TARGET_OUT_ODM)/etc/NOTICE.xml.gz
 
 target_vendor_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.txt
-target_vendor_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.xml
 target_vendor_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_VENDOR_DLKM.xml.gz
 installed_vendor_dlkm_notice_xml_gz := $(TARGET_OUT_VENDOR_DLKM)/etc/NOTICE.xml.gz
 
 target_odm_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.txt
-target_odm_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.xml
 target_odm_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.xml.gz
 installed_odm_dlkm_notice_xml_gz := $(TARGET_OUT_ODM_DLKM)/etc/NOTICE.xml.gz
 
+target_system_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.txt
+target_system_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.xml.gz
+installed_system_dlkm_notice_xml_gz := $(TARGET_OUT_SYSTEM_DLKM)/etc/NOTICE.xml.gz
+
 # Notice files are copied to TARGET_OUT_NOTICE_FILES as a side-effect of their module
 # being built. A notice xml file must depend on all modules that could potentially
 # install a license file relevant to it.
@@ -1331,13 +1633,15 @@
 license_modules_odm := $(filter $(TARGET_OUT_ODM)/%,$(license_modules))
 license_modules_vendor_dlkm := $(filter $(TARGET_OUT_VENDOR_DLKM)/%,$(license_modules))
 license_modules_odm_dlkm := $(filter $(TARGET_OUT_ODM_DLKM)/%,$(license_modules))
+license_modules_odm_dlkm := $(filter $(TARGET_OUT_SYSTEM_DLKM)/%,$(license_modules))
 license_modules_agg := $(license_modules_system) \
                        $(license_modules_vendor) \
                        $(license_modules_product) \
                        $(license_modules_system_ext) \
                        $(license_modules_odm) \
                        $(license_modules_vendor_dlkm) \
-                       $(license_modules_odm_dlkm)
+                       $(license_modules_odm_dlkm) \
+                       $(license_modules_system_dlkm)
 # targets used for debug symbols only and do not get copied to the device
 license_modules_symbols_only := $(filter $(PRODUCT_OUT)/apex/%,$(license_modules))
 
@@ -1380,113 +1684,78 @@
 system_notice_file_message := "Notices for files contained in the system filesystem image in this directory:"
 endif
 
-$(eval $(call combine-notice-files, $(system_xml_directories), \
-	        $(target_notice_file_txt), \
-	        $(target_notice_file_xml), \
-	        $(system_notice_file_message), \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_system), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_excluded_system_product_odm_vendor_dlkm_odm_dlkm, \
-	        $(target_vendor_notice_file_txt), \
-	        $(target_vendor_notice_file_xml), \
-	        "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_vendor), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_product, \
-	        $(target_product_notice_file_txt), \
-	        $(target_product_notice_file_xml), \
-	        "Notices for files contained in the product filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_product), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_system_ext, \
-	        $(target_system_ext_notice_file_txt), \
-	        $(target_system_ext_notice_file_xml), \
-	        "Notices for files contained in the system_ext filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_system_ext), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_odm, \
-	        $(target_odm_notice_file_txt), \
-	        $(target_odm_notice_file_xml), \
-	        "Notices for files contained in the odm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_odm), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_vendor_dlkm, \
-	        $(target_vendor_dlkm_notice_file_txt), \
-	        $(target_vendor_dlkm_notice_file_xml), \
-	        "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_vendor_dlkm), \
-	        $(exclude_target_dirs)))
-$(eval $(call combine-notice-files, xml_odm_dlkm, \
-	        $(target_odm_dlkm_notice_file_txt), \
-	        $(target_odm_dlkm_notice_file_xml), \
-	        "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
-	        $(TARGET_OUT_NOTICE_FILES), \
-	        $(license_modules_odm_dlkm), \
-	        $(exclude_target_dirs)))
-
-$(target_notice_file_xml_gz): $(target_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_vendor_notice_file_xml_gz): $(target_vendor_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_product_notice_file_xml_gz): $(target_product_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_system_ext_notice_file_xml_gz): $(target_system_ext_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_odm_notice_file_xml_gz): $(target_odm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_vendor_dlkm_notice_file_xml_gz): $(target_vendor_dlkm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(target_odm_dlkm_notice_file_xml_gz): $(target_odm_dlkm_notice_file_xml) | $(MINIGZIP)
-	$(hide) $(MINIGZIP) -9 < $< > $@
-$(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_vendor_notice_xml_gz): $(target_vendor_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_product_notice_xml_gz): $(target_product_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_system_ext_notice_xml_gz): $(target_system_ext_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_odm_notice_xml_gz): $(target_odm_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_vendor_dlkm_notice_xml_gz): $(target_vendor_dlkm_notice_file_xml_gz)
-	$(copy-file-to-target)
-$(installed_odm_dlkm_notice_xml_gz): $(target_odm_dlkm_notice_file_xml_gz)
-	$(copy-file-to-target)
-
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_product_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_ext_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_dlkm_notice_xml_gz)
-ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_dlkm_notice_xml_gz)
 endif # PRODUCT_NOTICE_SPLIT
 
 ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
 
-$(eval $(call combine-notice-files, html, \
-	        $(tools_notice_file_txt), \
-	        $(tools_notice_file_html), \
-	        "Notices for files contained in the tools directory:", \
-	        $(HOST_OUT_NOTICE_FILES), \
-	        $(ALL_DEFAULT_INSTALLED_MODULES) \
-	        $(winpthreads_notice_file), \
-	        $(exclude_target_dirs)))
+need_vendor_notice:=false
+ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
+   need_vendor_notice:=true
+endif
+
+ifdef BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+   need_vendor_notice:=true
+endif
+
+ifdef BUILDING_VENDOR_IMAGE
+   need_vendor_notice:=true
+endif
+
+ifeq (true,$(need_vendor_notice))
+ifneq (,$(installed_vendor_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_notice_xml_gz)
+endif
+endif
+
+need_vendor_notice:=
+
+ifdef BUILDING_ODM_IMAGE
+ifneq (,$(installed_odm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_notice_xml_gz)
+endif
+endif
+
+ifdef BUILDING_PRODUCT_IMAGE
+ifneq (,$(installed_product_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_product_notice_xml_gz)
+endif
+endif
+
+ifdef BUILDING_SYSTEM_EXT_IMAGE
+ifneq (,$(installed_system_ext_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_ext_notice_xml_gz)
+endif
+endif
+
+ifdef BUILDING_VENDOR_DLKM_IMAGE
+ifneq (,$(installed_vendor_dlkm_notice_xml_gz)
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_dlkm_notice_xml_gz)
+endif
+endif
+
+ifdef BUILDING_ODM_DLKM_IMAGE
+ifneq (,$(installed_odm_dlkm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_dlkm_notice_xml_gz)
+endif
+endif
+
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+ifneq (,$(installed_system_dlkm_notice_xml_gz))
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_dlkm_notice_xml_gz)
+endif
+endif
 
 endif  # TARGET_BUILD_APPS
 
-# The kernel isn't really a module, so to get its module file in there, we
-# make the target NOTICE files depend on this particular file too, which will
-# then be in the right directory for the find in combine-notice-files to work.
+# Presently none of the prebuilts etc. comply with policy to have a license text. Fake one here.
 $(eval $(call copy-one-file,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING,$(kernel_notice_file)))
 
-$(eval $(call copy-one-file,$(BUILD_SYSTEM)/WINPTHREADS_COPYING,$(winpthreads_notice_file)))
+ifneq (,$(strip $(INSTALLED_KERNEL_TARGET)))
+$(call declare-license-metadata,$(INSTALLED_KERNEL_TARGET),SPDX-license-identifier-GPL-2.0-only,restricted,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING,"Kernel",kernel)
+endif
+
+# No matter where it gets copied from, a copied linux kernel is licensed under "GPL 2.0 only"
+$(eval $(call declare-copy-files-license-metadata,,:kernel,SPDX-license-identifier-GPL-2.0-only,restricted,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING,kernel))
 
 
 # #################################################################
@@ -1531,6 +1800,8 @@
     $(MKE2FS_CONF) \
     $(MKEXTUSERIMG)
 
+$(call declare-1p-target,$(MKE2FS_CONF),system/extras)
+
 ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
 INTERNAL_USERIMAGES_DEPS += $(MKF2FSUSERIMG)
 endif
@@ -1543,8 +1814,14 @@
     $(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE) \
     $(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE) \
     $(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
+    $(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
   ,erofs),)
-INTERNAL_USERIMAGES_DEPS += $(MKEROFSUSERIMG)
+INTERNAL_USERIMAGES_DEPS += $(MKEROFS)
+ifeq ($(BOARD_EROFS_USE_LEGACY_COMPRESSION),true)
+BOARD_EROFS_COMPRESSOR ?= "lz4"
+else
+BOARD_EROFS_COMPRESSOR ?= "lz4hc,9"
+endif
 endif
 
 ifneq ($(filter \
@@ -1555,6 +1832,7 @@
     $(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE) \
     $(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE) \
     $(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
+    $(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
   ,squashfs),)
 INTERNAL_USERIMAGES_DEPS += $(MKSQUASHFSUSERIMG)
 endif
@@ -1590,31 +1868,54 @@
 
 endif # PRODUCT_USE_DYNAMIC_PARTITIONS
 
+# $(1) the partition name (eg system)
+# $(2) the image prop file
+define add-common-flags-to-image-props
+$(eval _var := $(call to-upper,$(1)))
+$(hide) echo "$(1)_selinux_fc=$(SELINUX_FC)" >> $(2)
+$(hide) echo "building_$(1)_image=$(BUILDING_$(_var)_IMAGE)" >> $(2)
+endef
+
+# $(1) the partition name (eg system)
+# $(2) the image prop file
+define add-common-ro-flags-to-image-props
+$(eval _var := $(call to-upper,$(1)))
+$(if $(BOARD_$(_var)IMAGE_EROFS_COMPRESSOR),$(hide) echo "$(1)_erofs_compressor=$(BOARD_$(_var)IMAGE_EROFS_COMPRESSOR)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_EROFS_PCLUSTER_SIZE),$(hide) echo "$(1)_erofs_pcluster_size=$(BOARD_$(_var)IMAGE_EROFS_PCLUSTER_SIZE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_EXTFS_INODE_COUNT),$(hide) echo "$(1)_extfs_inode_count=$(BOARD_$(_var)IMAGE_EXTFS_INODE_COUNT)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_EXTFS_RSV_PCT),$(hide) echo "$(1)_extfs_rsv_pct=$(BOARD_$(_var)IMAGE_EXTFS_RSV_PCT)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "$(1)_f2fs_sldc_flags=$(BOARD_$(_var)IMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "$(1)_f2fs_compress=$(BOARD_$(_var)IMAGE_FILE_SYSTEM_COMPRESS)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_FILE_SYSTEM_TYPE),$(hide) echo "$(1)_fs_type=$(BOARD_$(_var)IMAGE_FILE_SYSTEM_TYPE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_JOURNAL_SIZE),$(hide) echo "$(1)_journal_size=$(BOARD_$(_var)IMAGE_JOURNAL_SIZE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "$(1)_reserved_size=$(BOARD_$(_var)IMAGE_PARTITION_RESERVED_SIZE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_PARTITION_SIZE),$(hide) echo "$(1)_size=$(BOARD_$(_var)IMAGE_PARTITION_SIZE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "$(1)_squashfs_block_size=$(BOARD_$(_var)IMAGE_SQUASHFS_BLOCK_SIZE)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "$(1)_squashfs_compressor=$(BOARD_$(_var)IMAGE_SQUASHFS_COMPRESSOR)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "$(1)_squashfs_compressor_opt=$(BOARD_$(_var)IMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(2))
+$(if $(BOARD_$(_var)IMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "$(1)_squashfs_disable_4k_align=$(BOARD_$(_var)IMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(2))
+$(if $(PRODUCT_$(_var)_BASE_FS_PATH),$(hide) echo "$(1)_base_fs_file=$(PRODUCT_$(_var)_BASE_FS_PATH)" >> $(2))
+$(eval _size := $(BOARD_$(_var)IMAGE_PARTITION_SIZE))
+$(eval _reserved := $(BOARD_$(_var)IMAGE_PARTITION_RESERVED_SIZE))
+$(eval _headroom := $(PRODUCT_$(_var)_HEADROOM))
+$(if $(or $(_size), $(_reserved), $(_headroom)),,
+    $(hide) echo "$(1)_disable_sparse=true" >> $(2))
+$(call add-common-flags-to-image-props,$(1),$(2))
+endef
+
 # $(1): the path of the output dictionary file
-# $(2): a subset of "system vendor cache userdata product system_ext oem odm vendor_dlkm odm_dlkm"
+# $(2): a subset of "system vendor cache userdata product system_ext oem odm vendor_dlkm odm_dlkm system_dlkm"
 # $(3): additional "key=value" pairs to append to the dictionary file.
 define generate-image-prop-dictionary
 $(if $(filter $(2),system),\
-    $(if $(BOARD_SYSTEMIMAGE_PARTITION_SIZE),$(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(1))
     $(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),$(hide) echo "system_other_size=$(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "system_fs_type=$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "system_f2fs_compress=$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_COMPRESS)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "system_f2fs_sldc_flags=$(BOARD_SYSTEMIMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_EXTFS_INODE_COUNT),$(hide) echo "system_extfs_inode_count=$(BOARD_SYSTEMIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_EXTFS_RSV_PCT),$(hide) echo "system_extfs_rsv_pct=$(BOARD_SYSTEMIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_JOURNAL_SIZE),$(hide) echo "system_journal_size=$(BOARD_SYSTEMIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "system_squashfs_compressor=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "system_squashfs_compressor_opt=$(BOARD_SYSTEMIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "system_squashfs_block_size=$(BOARD_SYSTEMIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "system_squashfs_disable_4k_align=$(BOARD_SYSTEMIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(PRODUCT_SYSTEM_BASE_FS_PATH),$(hide) echo "system_base_fs_file=$(PRODUCT_SYSTEM_BASE_FS_PATH)" >> $(1))
     $(if $(PRODUCT_SYSTEM_HEADROOM),$(hide) echo "system_headroom=$(PRODUCT_SYSTEM_HEADROOM)" >> $(1))
-    $(if $(BOARD_SYSTEMIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "system_reserved_size=$(BOARD_SYSTEMIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "system_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_system_image=$(BUILDING_SYSTEM_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,system,$(1))
 )
 $(if $(filter $(2),system_other),\
     $(hide) echo "building_system_other_image=$(BUILDING_SYSTEM_OTHER_IMAGE)" >> $(1)
+    $(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),,
+        $(hide) echo "system_other_disable_sparse=true" >> $(1))
 )
 $(if $(filter $(2),userdata),\
     $(if $(BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "userdata_fs_type=$(BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
@@ -1622,116 +1923,40 @@
     $(if $(PRODUCT_FS_CASEFOLD),$(hide) echo "needs_casefold=$(PRODUCT_FS_CASEFOLD)" >> $(1))
     $(if $(PRODUCT_QUOTA_PROJID),$(hide) echo "needs_projid=$(PRODUCT_QUOTA_PROJID)" >> $(1))
     $(if $(PRODUCT_FS_COMPRESSION),$(hide) echo "needs_compress=$(PRODUCT_FS_COMPRESSION)" >> $(1))
-    $(hide) echo "userdata_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_userdata_image=$(BUILDING_USERDATA_IMAGE)" >> $(1)
+    $(call add-common-flags-to-image-props,userdata,$(1))
 )
 $(if $(filter $(2),cache),\
     $(if $(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "cache_fs_type=$(BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
     $(if $(BOARD_CACHEIMAGE_PARTITION_SIZE),$(hide) echo "cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)" >> $(1))
-    $(hide) echo "cache_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_cache_image=$(BUILDING_CACHE_IMAGE)" >> $(1)
+    $(call add-common-flags-to-image-props,cache,$(1))
 )
 $(if $(filter $(2),vendor),\
-    $(if $(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "vendor_fs_type=$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "vendor_f2fs_compress=$(BOARD_VENDORIMAGE_FILE_SYSTEM_COMPRESS)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "vendor_f2fs_sldc_flags=$(BOARD_VENDORIMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_EXTFS_INODE_COUNT),$(hide) echo "vendor_extfs_inode_count=$(BOARD_VENDORIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_EXTFS_RSV_PCT),$(hide) echo "vendor_extfs_rsv_pct=$(BOARD_VENDORIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_PARTITION_SIZE),$(hide) echo "vendor_size=$(BOARD_VENDORIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_JOURNAL_SIZE),$(hide) echo "vendor_journal_size=$(BOARD_VENDORIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "vendor_squashfs_compressor=$(BOARD_VENDORIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "vendor_squashfs_compressor_opt=$(BOARD_VENDORIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "vendor_squashfs_block_size=$(BOARD_VENDORIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "vendor_squashfs_disable_4k_align=$(BOARD_VENDORIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(PRODUCT_VENDOR_BASE_FS_PATH),$(hide) echo "vendor_base_fs_file=$(PRODUCT_VENDOR_BASE_FS_PATH)" >> $(1))
-    $(if $(BOARD_VENDORIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "vendor_reserved_size=$(BOARD_VENDORIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "vendor_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_vendor_image=$(BUILDING_VENDOR_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,vendor,$(1))
 )
 $(if $(filter $(2),product),\
-    $(if $(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "product_fs_type=$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "product_f2fs_compress=$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_COMPRESS)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "product_f2fs_sldc_flags=$(BOARD_PRODUCTIMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_EXTFS_INODE_COUNT),$(hide) echo "product_extfs_inode_count=$(BOARD_PRODUCTIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_EXTFS_RSV_PCT),$(hide) echo "product_extfs_rsv_pct=$(BOARD_PRODUCTIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_PARTITION_SIZE),$(hide) echo "product_size=$(BOARD_PRODUCTIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_JOURNAL_SIZE),$(hide) echo "product_journal_size=$(BOARD_PRODUCTIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "product_squashfs_compressor=$(BOARD_PRODUCTIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "product_squashfs_compressor_opt=$(BOARD_PRODUCTIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "product_squashfs_block_size=$(BOARD_PRODUCTIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "product_squashfs_disable_4k_align=$(BOARD_PRODUCTIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(PRODUCT_PRODUCT_BASE_FS_PATH),$(hide) echo "product_base_fs_file=$(PRODUCT_PRODUCT_BASE_FS_PATH)" >> $(1))
-    $(if $(BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "product_reserved_size=$(BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "product_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_product_image=$(BUILDING_PRODUCT_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,product,$(1))
 )
 $(if $(filter $(2),system_ext),\
-    $(if $(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "system_ext_fs_type=$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "system_ext_f2fs_compress=$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_COMPRESS)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "system_ext_f2fs_sldc_flags=$(BOARD_SYSTEM_EXTIMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_EXTFS_INODE_COUNT),$(hide) echo "system_ext_extfs_inode_count=$(BOARD_SYSTEM_EXTIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_EXTFS_RSV_PCT),$(hide) echo "system_ext_extfs_rsv_pct=$(BOARD_SYSTEM_EXTIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_PARTITION_SIZE),$(hide) echo "system_ext_size=$(BOARD_SYSTEM_EXTIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_JOURNAL_SIZE),$(hide) echo "system_ext_journal_size=$(BOARD_SYSTEM_EXTIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "system_ext_squashfs_compressor=$(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "system_ext_squashfs_compressor_opt=$(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "system_ext_squashfs_block_size=$(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "system_ext_squashfs_disable_4k_align=$(BOARD_SYSTEM_EXTIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(BOARD_SYSTEM_EXTIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "system_ext_reserved_size=$(BOARD_SYSTEM_EXTIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "system_ext_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_system_ext_image=$(BUILDING_SYSTEM_EXT_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,system_ext,$(1))
 )
 $(if $(filter $(2),odm),\
-    $(if $(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "odm_fs_type=$(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_EXTFS_INODE_COUNT),$(hide) echo "odm_extfs_inode_count=$(BOARD_ODMIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_EXTFS_RSV_PCT),$(hide) echo "odm_extfs_rsv_pct=$(BOARD_ODMIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_PARTITION_SIZE),$(hide) echo "odm_size=$(BOARD_ODMIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_JOURNAL_SIZE),$(hide) echo "odm_journal_size=$(BOARD_ODMIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "odm_squashfs_compressor=$(BOARD_ODMIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "odm_squashfs_compressor_opt=$(BOARD_ODMIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "odm_squashfs_block_size=$(BOARD_ODMIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "odm_squashfs_disable_4k_align=$(BOARD_ODMIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(PRODUCT_ODM_BASE_FS_PATH),$(hide) echo "odm_base_fs_file=$(PRODUCT_ODM_BASE_FS_PATH)" >> $(1))
-    $(if $(BOARD_ODMIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "odm_reserved_size=$(BOARD_ODMIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "odm_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_odm_image=$(BUILDING_ODM_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,odm,$(1))
 )
 $(if $(filter $(2),vendor_dlkm),\
-    $(if $(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "vendor_dlkm_fs_type=$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_COMPRESS),$(hide) echo "vendor_dlkm_f2fs_compress=$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_COMPRESS)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_F2FS_SLOAD_COMPRESS_FLAGS),$(hide) echo "vendor_dlkm_f2fs_sldc_flags=$(BOARD_VENDOR_DLKMIMAGE_F2FS_SLOAD_COMPRESS_FLAGS)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_EXTFS_INODE_COUNT),$(hide) echo "vendor_dlkm_extfs_inode_count=$(BOARD_VENDOR_DLKMIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_EXTFS_RSV_PCT),$(hide) echo "vendor_dlkm_extfs_rsv_pct=$(BOARD_VENDOR_DLKMIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_PARTITION_SIZE),$(hide) echo "vendor_dlkm_size=$(BOARD_VENDOR_DLKMIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_JOURNAL_SIZE),$(hide) echo "vendor_dlkm_journal_size=$(BOARD_VENDOR_DLKMIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "vendor_dlkm_squashfs_compressor=$(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "vendor_dlkm_squashfs_compressor_opt=$(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "vendor_dlkm_squashfs_block_size=$(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "vendor_dlkm_squashfs_disable_4k_align=$(BOARD_VENDOR_DLKMIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(BOARD_VENDOR_DLKMIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "vendor_dlkm_reserved_size=$(BOARD_VENDOR_DLKMIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "vendor_dlkm_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_vendor_dlkm_image=$(BUILDING_VENDOR_DLKM_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,vendor_dlkm,$(1))
 )
 $(if $(filter $(2),odm_dlkm),\
-    $(if $(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE),$(hide) echo "odm_dlkm_fs_type=$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_EXTFS_INODE_COUNT),$(hide) echo "odm_dlkm_extfs_inode_count=$(BOARD_ODM_DLKMIMAGE_EXTFS_INODE_COUNT)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_EXTFS_RSV_PCT),$(hide) echo "odm_dlkm_extfs_rsv_pct=$(BOARD_ODM_DLKMIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_PARTITION_SIZE),$(hide) echo "odm_dlkm_size=$(BOARD_ODM_DLKMIMAGE_PARTITION_SIZE)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_JOURNAL_SIZE),$(hide) echo "odm_dlkm_journal_size=$(BOARD_ODM_DLKMIMAGE_JOURNAL_SIZE)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_SQUASHFS_COMPRESSOR),$(hide) echo "odm_dlkm_squashfs_compressor=$(BOARD_ODM_DLKMIMAGE_SQUASHFS_COMPRESSOR)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_SQUASHFS_COMPRESSOR_OPT),$(hide) echo "odm_dlkm_squashfs_compressor_opt=$(BOARD_ODM_DLKMIMAGE_SQUASHFS_COMPRESSOR_OPT)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_SQUASHFS_BLOCK_SIZE),$(hide) echo "odm_dlkm_squashfs_block_size=$(BOARD_ODM_DLKMIMAGE_SQUASHFS_BLOCK_SIZE)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_SQUASHFS_DISABLE_4K_ALIGN),$(hide) echo "odm_dlkm_squashfs_disable_4k_align=$(BOARD_ODM_DLKMIMAGE_SQUASHFS_DISABLE_4K_ALIGN)" >> $(1))
-    $(if $(BOARD_ODM_DLKMIMAGE_PARTITION_RESERVED_SIZE),$(hide) echo "odm_dlkm_reserved_size=$(BOARD_ODM_DLKMIMAGE_PARTITION_RESERVED_SIZE)" >> $(1))
-    $(hide) echo "odm_dlkm_selinux_fc=$(SELINUX_FC)" >> $(1)
-    $(hide) echo "building_odm_dlkm_image=$(BUILDING_ODM_DLKM_IMAGE)" >> $(1)
+    $(call add-common-ro-flags-to-image-props,odm_dlkm,$(1))
+)
+$(if $(filter $(2),system_dlkm),\
+    $(call add-common-ro-flags-to-image-props,system_dlkm,$(1))
 )
 $(if $(filter $(2),oem),\
     $(if $(BOARD_OEMIMAGE_PARTITION_SIZE),$(hide) echo "oem_size=$(BOARD_OEMIMAGE_PARTITION_SIZE)" >> $(1))
     $(if $(BOARD_OEMIMAGE_JOURNAL_SIZE),$(hide) echo "oem_journal_size=$(BOARD_OEMIMAGE_JOURNAL_SIZE)" >> $(1))
     $(if $(BOARD_OEMIMAGE_EXTFS_INODE_COUNT),$(hide) echo "oem_extfs_inode_count=$(BOARD_OEMIMAGE_EXTFS_INODE_COUNT)" >> $(1))
     $(if $(BOARD_OEMIMAGE_EXTFS_RSV_PCT),$(hide) echo "oem_extfs_rsv_pct=$(BOARD_OEMIMAGE_EXTFS_RSV_PCT)" >> $(1))
-    $(hide) echo "oem_selinux_fc=$(SELINUX_FC)" >> $(1)
+    $(call add-common-flags-to-image-props,oem,$(1))
 )
 $(hide) echo "ext_mkuserimg=$(notdir $(MKEXTUSERIMG))" >> $(1)
 
@@ -1740,6 +1965,10 @@
 $(if $(INTERNAL_USERIMAGES_SPARSE_EROFS_FLAG),$(hide) echo "erofs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_EROFS_FLAG)" >> $(1))
 $(if $(INTERNAL_USERIMAGES_SPARSE_SQUASHFS_FLAG),$(hide) echo "squashfs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_SQUASHFS_FLAG)" >> $(1))
 $(if $(INTERNAL_USERIMAGES_SPARSE_F2FS_FLAG),$(hide) echo "f2fs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_F2FS_FLAG)" >> $(1))
+$(if $(BOARD_EROFS_COMPRESSOR),$(hide) echo "erofs_default_compressor=$(BOARD_EROFS_COMPRESSOR)" >> $(1))
+$(if $(BOARD_EROFS_PCLUSTER_SIZE),$(hide) echo "erofs_pcluster_size=$(BOARD_EROFS_PCLUSTER_SIZE)" >> $(1))
+$(if $(BOARD_EROFS_SHARE_DUP_BLOCKS),$(hide) echo "erofs_share_dup_blocks=$(BOARD_EROFS_SHARE_DUP_BLOCKS)" >> $(1))
+$(if $(BOARD_EROFS_USE_LEGACY_COMPRESSION),$(hide) echo "erofs_use_legacy_compression=$(BOARD_EROFS_USE_LEGACY_COMPRESSION)" >> $(1))
 $(if $(BOARD_EXT4_SHARE_DUP_BLOCKS),$(hide) echo "ext4_share_dup_blocks=$(BOARD_EXT4_SHARE_DUP_BLOCKS)" >> $(1))
 $(if $(BOARD_FLASH_LOGICAL_BLOCK_SIZE), $(hide) echo "flash_logical_block_size=$(BOARD_FLASH_LOGICAL_BLOCK_SIZE)" >> $(1))
 $(if $(BOARD_FLASH_ERASE_BLOCK_SIZE), $(hide) echo "flash_erase_block_size=$(BOARD_FLASH_ERASE_BLOCK_SIZE)" >> $(1))
@@ -1755,6 +1984,7 @@
 $(if $(PRODUCT_SYSTEM_EXT_VERITY_PARTITION),$(hide) echo "system_ext_verity_block_device=$(PRODUCT_SYSTEM_EXT_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCT_VENDOR_DLKM_VERITY_PARTITION),$(hide) echo "vendor_dlkm_verity_block_device=$(PRODUCT_VENDOR_DLKM_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCT_ODM_DLKM_VERITY_PARTITION),$(hide) echo "odm_dlkm_verity_block_device=$(PRODUCT_ODM_DLKM_VERITY_PARTITION)" >> $(1))
+$(if $(PRODUCT_SYSTEM_DLKM_VERITY_PARTITION),$(hide) echo "system_dlkm_verity_block_device=$(PRODUCT_SYSTEM_DLKM_VERITY_PARTITION)" >> $(1))
 $(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot=$(PRODUCT_SUPPORTS_VBOOT)" >> $(1))
 $(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_key=$(PRODUCT_VBOOT_SIGNING_KEY)" >> $(1))
 $(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_subkey=$(PRODUCT_VBOOT_SIGNING_SUBKEY)" >> $(1))
@@ -1819,10 +2049,20 @@
         $(hide) echo "avb_odm_dlkm_key_path=$(BOARD_AVB_ODM_DLKM_KEY_PATH)" >> $(1)
         $(hide) echo "avb_odm_dlkm_algorithm=$(BOARD_AVB_ODM_DLKM_ALGORITHM)" >> $(1)
         $(hide) echo "avb_odm_dlkm_rollback_index_location=$(BOARD_AVB_ODM_DLKM_ROLLBACK_INDEX_LOCATION)" >> $(1)))
+$(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_system_dlkm_hashtree_enable=$(BOARD_AVB_ENABLE)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),\
+    $(hide) echo "avb_system_dlkm_add_hashtree_footer_args=$(BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),\
+    $(if $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH),\
+        $(hide) echo "avb_system_dlkm_key_path=$(BOARD_AVB_SYSTEM_DLKM_KEY_PATH)" >> $(1)
+        $(hide) echo "avb_system_dlkm_algorithm=$(BOARD_AVB_SYSTEM_DLKM_ALGORITHM)" >> $(1)
+        $(hide) echo "avb_system_dlkm_rollback_index_location=$(BOARD_SYSTEM_SYSTEM_DLKM_ROLLBACK_INDEX_LOCATION)" >> $(1)))
 $(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
     $(hide) echo "recovery_as_boot=true" >> $(1))
 $(if $(filter true,$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),\
     $(hide) echo "system_root_image=true" >> $(1))
+$(if $(filter true,$(BOARD_BUILD_GKI_BOOT_IMAGE_WITHOUT_RAMDISK)),\
+    $(hide) echo "gki_boot_image_without_ramdisk=true" >> $(1))
 $(hide) echo "root_dir=$(TARGET_ROOT_OUT)" >> $(1)
 $(if $(filter true,$(PRODUCT_USE_DYNAMIC_PARTITION_SIZE)),\
     $(hide) echo "use_dynamic_partition_size=true" >> $(1))
@@ -1862,6 +2102,9 @@
 ifdef BUILDING_ODM_DLKM_IMAGE
   PROP_DICTIONARY_IMAGES += odm_dlkm
 endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+  PROP_DICTIONARY_IMAGES += system_dlkm
+endif
 define generate-userimage-prop-dictionary
   $(call generate-image-prop-dictionary,$(1),$(PROP_DICTIONARY_IMAGES),$(2))
 endef
@@ -1876,6 +2119,7 @@
 # Recovery image
 
 # Recovery image exists if we are building recovery, or building recovery as boot.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_RECOVERY_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_RECOVERY_IMAGE
 
 INTERNAL_RECOVERYIMAGE_FILES := $(filter $(TARGET_RECOVERY_OUT)/%, \
@@ -1902,17 +2146,24 @@
 	$(FILESLIST) $(TARGET_RECOVERY_ROOT_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_RECOVERY)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_RECOVERY)))
+
 recovery_sepolicy := \
     $(TARGET_RECOVERY_ROOT_OUT)/sepolicy \
     $(TARGET_RECOVERY_ROOT_OUT)/plat_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/plat_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/plat_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/system_ext_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/system_ext_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/system_ext_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/vendor_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/vendor_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/vendor_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/odm_file_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/odm_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/product_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/product_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/product_property_contexts
 
 # Passed into rsync from non-recovery root to recovery root, to avoid overwriting recovery-specific
@@ -2064,6 +2315,9 @@
 
 ifdef TARGET_RECOVERY_FSTAB
 recovery_fstab := $(TARGET_RECOVERY_FSTAB)
+else ifdef TARGET_RECOVERY_FSTAB_GENRULE
+# Specifies a soong genrule module that generates an fstab.
+recovery_fstab := $(call intermediates-dir-for,ETC,$(TARGET_RECOVERY_FSTAB_GENRULE))/$(TARGET_RECOVERY_FSTAB_GENRULE)
 else
 recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
 endif
@@ -2150,6 +2404,11 @@
 	$(hide) cat $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET) >> $@
 	$(call append-recovery-ui-properties,$(PRIVATE_RECOVERY_UI_PROPERTIES),$@)
 
+$(call declare-1p-target,$(INSTALLED_RECOVERY_BUILD_PROP_TARGET),build)
+$(call declare-license-deps,$(INSTALLED_RECOVERY_BUILD_PROP_TARGET),\
+    $(INSTALLED_BUILD_PROP_TARGET) $(INSTALLED_VENDOR_BUILD_PROP_TARGET) $(INSTALLED_ODM_BUILD_PROP_TARGET) \
+    $(INSTALLED_PRODUCT_BUILD_PROP_TARGET) $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET))
+
 # Only install boot/etc/build.prop to recovery image on recovery_as_boot.
 # On device with dedicated recovery partition, the file should come from the boot
 # ramdisk.
@@ -2157,6 +2416,9 @@
 INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET := $(TARGET_RECOVERY_ROOT_OUT)/$(RAMDISK_BUILD_PROP_REL_PATH)
 $(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET): $(INSTALLED_RAMDISK_BUILD_PROP_TARGET)
 	$(copy-file-to-target)
+
+$(call declare-1p-target,$(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET),build)
+$(call declare-license-deps,$(INSTALLED_RECOVERY_RAMDISK_BUILD_PROP_TARGET),$(INSTALLED_RAMDISK_BUILD_PROP_TARGET))
 endif
 
 INTERNAL_RECOVERYIMAGE_ARGS := --ramdisk $(recovery_ramdisk)
@@ -2242,7 +2504,7 @@
                  $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_RECOVERY_MKBOOTIMG_ARGS) \
                  --output $(1).unsigned, \
     $(MKBOOTIMG) $(if $(strip $(2)),--kernel $(strip $(2))) $(INTERNAL_RECOVERYIMAGE_ARGS) \
-                 $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) \
+                 $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
                  $(BOARD_RECOVERY_MKBOOTIMG_ARGS) --output $(1))
   $(if $(filter true,$(PRODUCT_SUPPORTS_BOOT_SIGNER)),\
     $(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
@@ -2257,8 +2519,8 @@
     $(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE))))
   $(if $(filter true,$(BOARD_AVB_ENABLE)), \
     $(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)), \
-      $(AVBTOOL) add_hash_footer --image $(1) --partition_size $(call get-bootimage-partition-size,$(1),boot) --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS),\
-      $(AVBTOOL) add_hash_footer --image $(1) --partition_size $(BOARD_RECOVERYIMAGE_PARTITION_SIZE) --partition_name recovery $(INTERNAL_AVB_RECOVERY_SIGNING_ARGS) $(BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS)))
+      $(AVBTOOL) add_hash_footer --image $(1) $(call get-partition-size-argument,$(call get-bootimage-partition-size,$(1),boot)) --partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS),\
+      $(AVBTOOL) add_hash_footer --image $(1) $(call get-partition-size-argument,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)) --partition_name recovery $(INTERNAL_AVB_RECOVERY_SIGNING_ARGS) $(BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS)))
 endef
 
 recoveryimage-deps := $(MKBOOTIMG) $(recovery_ramdisk) $(recovery_kernel)
@@ -2271,9 +2533,6 @@
 ifeq (true,$(BOARD_AVB_ENABLE))
   recoveryimage-deps += $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH)
 endif
-ifdef BOARD_GKI_SIGNING_KEY_PATH
-  recoveryimage-deps += $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
-endif
 ifdef BOARD_INCLUDE_RECOVERY_DTBO
   ifdef BOARD_PREBUILT_RECOVERY_DTBOIMAGE
     recoveryimage-deps += $(BOARD_PREBUILT_RECOVERY_DTBOIMAGE)
@@ -2293,6 +2552,11 @@
 $(INSTALLED_BOOTIMAGE_TARGET): $(recoveryimage-deps)
 	$(call pretty,"Target boot image from recovery: $@")
 	$(call build-recoveryimage-target, $@, $(PRODUCT_OUT)/$(subst .img,,$(subst boot,kernel,$(notdir $@))))
+
+$(call declare-container-license-metadata,$(INSTALLED_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",bool)
+$(call declare-container-license-deps,$(INSTALLED_BOOTIMAGE_TARGET),$(recoveryimage-deps),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BOOTIMAGE_TARGET)
 endif # BOARD_USES_RECOVERY_AS_BOOT
 
 $(INSTALLED_RECOVERYIMAGE_TARGET): $(recoveryimage-deps)
@@ -2306,6 +2570,12 @@
 	$(remove-timestamps-from-package)
 endif
 
+
+$(call declare-1p-container,$(INSTALLED_RECOVERYIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_RECOVERYIMAGE_TARGET),$(recoveryimage-deps),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_RECOVERYIMAGE_TARGET)
+
 .PHONY: recoveryimage-nodeps
 recoveryimage-nodeps:
 	@echo "make $@: ignoring dependencies"
@@ -2327,79 +2597,83 @@
 $(error MTD device is no longer supported and thus BOARD_NAND_SPARE_SIZE is deprecated.)
 endif
 
-ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+
+# -----------------------------------------------------------------
+# Build debug ramdisk and debug boot image.
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_DEBUG_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
+ifneq ($(BUILDING_DEBUG_BOOT_IMAGE)$(BUILDING_DEBUG_VENDOR_BOOT_IMAGE),)
+
+INTERNAL_DEBUG_RAMDISK_FILES := $(filter $(TARGET_DEBUG_RAMDISK_OUT)/%, \
+    $(ALL_DEFAULT_INSTALLED_MODULES))
+
+# Directories to be picked into the debug ramdisk.
+# As these directories are all merged into one single cpio archive, the order
+# matters. If there are multiple files with the same pathname, then the last one
+# wins.
+#
+# ramdisk-debug.img will merge the content from either ramdisk.img or
+# ramdisk-recovery.img, depending on whether BOARD_USES_RECOVERY_AS_BOOT
+# is set or not.
+ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+  INTERNAL_DEBUG_RAMDISK_SRC_DIRS := $(TARGET_RECOVERY_ROOT_OUT)
+  INTERNAL_DEBUG_RAMDISK_SRC_RAMDISK_TARGET := $(recovery_ramdisk)
+else  # BOARD_USES_RECOVERY_AS_BOOT == true
+  INTERNAL_DEBUG_RAMDISK_SRC_DIRS := $(TARGET_RAMDISK_OUT)
+  INTERNAL_DEBUG_RAMDISK_SRC_RAMDISK_TARGET := $(INSTALLED_RAMDISK_TARGET)
+endif # BOARD_USES_RECOVERY_AS_BOOT != true
+
+INTERNAL_DEBUG_RAMDISK_SRC_DIRS += $(TARGET_DEBUG_RAMDISK_OUT)
+INTERNAL_DEBUG_RAMDISK_SRC_DEPS := $(INTERNAL_DEBUG_RAMDISK_SRC_RAMDISK_TARGET) $(INTERNAL_DEBUG_RAMDISK_FILES)
+
+# INSTALLED_FILES_FILE_DEBUG_RAMDISK would ensure TARGET_DEBUG_RAMDISK_OUT is created.
+INSTALLED_FILES_FILE_DEBUG_RAMDISK := $(PRODUCT_OUT)/installed-files-ramdisk-debug.txt
+INSTALLED_FILES_JSON_DEBUG_RAMDISK := $(INSTALLED_FILES_FILE_DEBUG_RAMDISK:.txt=.json)
+$(INSTALLED_FILES_FILE_DEBUG_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_DEBUG_RAMDISK)
+$(INSTALLED_FILES_FILE_DEBUG_RAMDISK): $(INTERNAL_DEBUG_RAMDISK_SRC_DEPS)
+$(INSTALLED_FILES_FILE_DEBUG_RAMDISK): $(FILESLIST) $(FILESLIST_UTIL)
+	@echo "Installed file list: $@"
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@) $(TARGET_DEBUG_RAMDISK_OUT)
+	touch $(TARGET_DEBUG_RAMDISK_OUT)/force_debuggable
+	$(FILESLIST) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) > $(@:.txt=.json)
+	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_DEBUG_RAMDISK)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_DEBUG_RAMDISK)))
+
+ifdef BUILDING_DEBUG_BOOT_IMAGE
+
 # -----------------------------------------------------------------
 # the debug ramdisk, which is the original ramdisk plus additional
 # files: force_debuggable, adb_debug.prop and userdebug sepolicy.
 # When /force_debuggable is present, /init will load userdebug sepolicy
 # and property files to allow adb root, if the device is unlocked.
-
-ifdef BUILDING_RAMDISK_IMAGE
-BUILT_DEBUG_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk-debug.img
-INSTALLED_DEBUG_RAMDISK_TARGET := $(BUILT_DEBUG_RAMDISK_TARGET)
-
-INTERNAL_DEBUG_RAMDISK_FILES := $(filter $(TARGET_DEBUG_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
-    $(ALL_DEFAULT_INSTALLED_MODULES))
-
-INSTALLED_FILES_FILE_DEBUG_RAMDISK := $(PRODUCT_OUT)/installed-files-ramdisk-debug.txt
-INSTALLED_FILES_JSON_DEBUG_RAMDISK := $(INSTALLED_FILES_FILE_DEBUG_RAMDISK:.txt=.json)
-$(INSTALLED_FILES_FILE_DEBUG_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_DEBUG_RAMDISK)
-
-# ramdisk-debug.img will merge the content from either ramdisk.img or
-# ramdisk-recovery.img, depending on whether BOARD_USES_RECOVERY_AS_BOOT
-# is set or not.
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  $(INSTALLED_FILES_FILE_DEBUG_RAMDISK): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-  $(INSTALLED_FILES_FILE_DEBUG_RAMDISK): $(recovery_ramdisk)
-else
-  $(INSTALLED_FILES_FILE_DEBUG_RAMDISK): PRIVATE_ADDITIONAL_DIR := $(TARGET_RAMDISK_OUT)
-  $(INSTALLED_FILES_FILE_DEBUG_RAMDISK): $(INSTALLED_RAMDISK_TARGET)
-endif # BOARD_USES_RECOVERY_AS_BOOT
-
-$(INSTALLED_FILES_FILE_DEBUG_RAMDISK) : $(INTERNAL_DEBUG_RAMDISK_FILES) $(FILESLIST) $(FILESLIST_UTIL)
-	@echo Installed file list: $@
-	mkdir -p $(dir $@)
-	rm -f $@
-	$(FILESLIST) $(TARGET_DEBUG_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) > $(@:.txt=.json)
-	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
-
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  $(INSTALLED_DEBUG_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-  $(INSTALLED_DEBUG_RAMDISK_TARGET): $(recovery_ramdisk)
-else
-  $(INSTALLED_DEBUG_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RAMDISK_OUT)
-  $(INSTALLED_DEBUG_RAMDISK_TARGET): $(INSTALLED_RAMDISK_TARGET)
-endif # BOARD_USES_RECOVERY_AS_BOOT
+INSTALLED_DEBUG_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk-debug.img
 
 $(INSTALLED_DEBUG_RAMDISK_TARGET): $(INSTALLED_FILES_FILE_DEBUG_RAMDISK)
-$(INSTALLED_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_DEBUG_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
-	$(call pretty,"Target debug ramdisk: $@")
-	mkdir -p $(TARGET_DEBUG_RAMDISK_OUT)
-	touch $(TARGET_DEBUG_RAMDISK_OUT)/force_debuggable
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_DEBUG_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
+$(INSTALLED_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
+	@echo "Target debug ramdisk: $@"
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
+
+$(call declare-1p-container,$(INSTALLED_DEBUG_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_DEBUG_RAMDISK_TARGET),$(INSTALLED_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DEBUG_RAMDISK_TARGET)
 
 .PHONY: ramdisk_debug-nodeps
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  ramdisk_debug-nodeps: PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-else
-  ramdisk_debug-nodeps: PRIVATE_ADDITIONAL_DIR := $(TARGET_RAMDISK_OUT)
-endif # BOARD_USES_RECOVERY_AS_BOOT
 ramdisk_debug-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
-	echo "make $@: ignoring dependencies"
-	mkdir -p $(TARGET_DEBUG_RAMDISK_OUT)
-	touch $(TARGET_DEBUG_RAMDISK_OUT)/force_debuggable
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_DEBUG_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $(INSTALLED_DEBUG_RAMDISK_TARGET)
-
-endif # BUILDING_RAMDISK_IMAGE
+	@echo "make $@: ignoring dependencies"
+	$(hide) rm -f $(INSTALLED_DEBUG_RAMDISK_TARGET)
+	$(hide) mkdir -p $(dir $(INSTALLED_DEBUG_RAMDISK_TARGET)) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $(INSTALLED_DEBUG_RAMDISK_TARGET)
 
 # -----------------------------------------------------------------
 # the boot-debug.img, which is the kernel plus ramdisk-debug.img
 #
 # Note: it's intentional to skip signing for boot-debug.img, because it
 # can only be used if the device is unlocked with verification error.
-ifneq ($(INSTALLED_BOOTIMAGE_TARGET),)
-ifneq ($(strip $(TARGET_NO_KERNEL)),true)
 ifneq ($(strip $(BOARD_KERNEL_BINARIES)),)
   INSTALLED_DEBUG_BOOTIMAGE_TARGET := $(foreach k,$(subst kernel,boot-debug,$(BOARD_KERNEL_BINARIES)), \
          $(PRODUCT_OUT)/$(k).img)
@@ -2408,10 +2682,11 @@
 endif
 
 # Replace ramdisk.img in $(MKBOOTIMG) ARGS with ramdisk-debug.img to build boot-debug.img
+$(INSTALLED_DEBUG_BOOTIMAGE_TARGET): $(INSTALLED_DEBUG_RAMDISK_TARGET)
 ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  INTERNAL_DEBUG_BOOTIMAGE_ARGS := $(subst $(recovery_ramdisk),$(INSTALLED_DEBUG_RAMDISK_TARGET), $(INTERNAL_RECOVERYIMAGE_ARGS))
+  INTERNAL_DEBUG_BOOTIMAGE_ARGS := $(subst $(INTERNAL_DEBUG_RAMDISK_SRC_RAMDISK_TARGET),$(INSTALLED_DEBUG_RAMDISK_TARGET),$(INTERNAL_RECOVERYIMAGE_ARGS))
 else
-  INTERNAL_DEBUG_BOOTIMAGE_ARGS := $(subst $(INSTALLED_RAMDISK_TARGET),$(INSTALLED_DEBUG_RAMDISK_TARGET), $(INTERNAL_BOOTIMAGE_ARGS))
+  INTERNAL_DEBUG_BOOTIMAGE_ARGS := $(subst $(INTERNAL_DEBUG_RAMDISK_SRC_RAMDISK_TARGET),$(INSTALLED_DEBUG_RAMDISK_TARGET),$(INTERNAL_BOOTIMAGE_ARGS))
 endif
 
 # If boot.img is chained but boot-debug.img is not signed, libavb in bootloader
@@ -2430,7 +2705,7 @@
 $(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(call get-bootimage-partition-size,$(1),$(2))))
 $(AVBTOOL) add_hash_footer \
   --image $(1) \
-  --partition_size $(call get-bootimage-partition-size,$(1),$(2))\
+  $(call get-partition-size-argument,$(call get-bootimage-partition-size,$(1),$(2)))\
   --partition_name boot $(INTERNAL_AVB_BOOT_TEST_SIGNING_ARGS) \
   $(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
 $(call assert-max-image-size,$(1),$(call get-bootimage-partition-size,$(1),$(2)))
@@ -2440,69 +2715,84 @@
 define build-debug-bootimage-target
   $(MKBOOTIMG) --kernel $(PRODUCT_OUT)/$(subst .img,,$(subst boot-debug,kernel,$(notdir $(1)))) \
     $(INTERNAL_DEBUG_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
-    $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $1
+    $(BOARD_MKBOOTIMG_ARGS) --output $1
   $(if $(BOARD_AVB_BOOT_KEY_PATH),$(call test-key-sign-bootimage,$1,boot-debug))
 endef
 
 # Depends on original boot.img and ramdisk-debug.img, to build the new boot-debug.img
-$(INSTALLED_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_BOOTIMAGE_TARGET) $(INSTALLED_DEBUG_RAMDISK_TARGET) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+$(INSTALLED_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_BOOTIMAGE_TARGET) $(AVBTOOL)
 	$(call pretty,"Target boot debug image: $@")
 	$(call build-debug-bootimage-target, $@)
 
+$(call declare-container-license-metadata,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),SPDX-license-identifier-GPL-2.0-only SPDX-license-identifier-Apache-2.0,restricted notice,$(BUILD_SYSTEM)/LINUX_KERNEL_COPYING build/soong/licenses/LICENSE,"Boot Image",boot)
+$(call declare-container-license-deps,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(INSTALLED_BOOTIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage_debug-nodeps
-bootimage_debug-nodeps: $(MKBOOTIMG) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+bootimage_debug-nodeps: $(MKBOOTIMG) $(AVBTOOL)
 	echo "make $@: ignoring dependencies"
 	$(foreach b,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(call build-debug-bootimage-target,$b))
 
-endif # TARGET_NO_KERNEL
-endif # INSTALLED_BOOTIMAGE_TARGET
+endif # BUILDING_DEBUG_BOOT_IMAGE
 
-ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
-ifeq ($(BUILDING_RAMDISK_IMAGE),true)
 # -----------------------------------------------------------------
 # vendor debug ramdisk
 # Combines vendor ramdisk files and debug ramdisk files to build the vendor debug ramdisk.
-#
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
+ifdef BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+
 INTERNAL_VENDOR_DEBUG_RAMDISK_FILES := $(filter $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES))
 
-INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-ramdisk-debug.txt
-INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK:.txt=.json)
-$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK)
-$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DEBUG_RAMDISK_TARGET)
-$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) $(FILESLIST) $(FILESLIST_UTIL)
-	@echo Installed file list: $@
-	mkdir -p $(dir $@)
-	rm -f $@
-	mkdir -p $(TARGET_VENDOR_DEBUG_RAMDISK_OUT) # The dir might not be created if no modules are installed here.
-	$(FILESLIST) $(TARGET_VENDOR_RAMDISK_OUT) $(TARGET_DEBUG_RAMDISK_OUT) $(TARGET_VENDOR_DEBUG_RAMDISK_OUT) > $(@:.txt=.json)
-	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
-
-INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot-debug)/vendor_ramdisk-debug.cpio$(RAMDISK_EXT)
+# The debug vendor ramdisk combines vendor ramdisk and debug ramdisk.
+INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS := $(TARGET_VENDOR_RAMDISK_OUT)
+INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DEPS := $(INTERNAL_VENDOR_RAMDISK_TARGET)
 
 # Exclude recovery files in the default vendor ramdisk if including a standalone
 # recovery ramdisk in vendor_boot.
 ifeq (true,$(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT))
-ifneq (true,$(BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT))
-$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(INTERNAL_RECOVERY_RAMDISK_FILES_TIMESTAMP)
-$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-endif
-endif
+  ifneq (true,$(BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT))
+    INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS += $(TARGET_RECOVERY_ROOT_OUT)
+    INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DEPS += $(INTERNAL_RECOVERY_RAMDISK_FILES_TIMESTAMP)
+  endif # BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT != true
+endif # BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT == true
 
-# The vendor debug ramdisk combines vendor ramdisk and debug ramdisk.
-$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_DEBUG_RAMDISK_TARGET)
+INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS += $(TARGET_VENDOR_DEBUG_RAMDISK_OUT) $(TARGET_DEBUG_RAMDISK_OUT)
+INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DEPS += $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) $(INSTALLED_FILES_FILE_DEBUG_RAMDISK)
+
+# INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK would ensure TARGET_VENDOR_DEBUG_RAMDISK_OUT is created.
+INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK := $(PRODUCT_OUT)/installed-files-vendor-ramdisk-debug.txt
+INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK := $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK:.txt=.json)
+$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK)
+$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DEPS)
+$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK): $(FILESLIST) $(FILESLIST_UTIL)
+	@echo "Installed file list: $@"
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@) $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)
+	$(FILESLIST) $(INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS) > $(@:.txt=.json)
+	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK))
+
+INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot-debug)/vendor_ramdisk-debug.cpio$(RAMDISK_EXT)
+
 $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK)
-$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_VENDOR_DEBUG_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
-	mkdir -p $(TARGET_VENDOR_DEBUG_RAMDISK_OUT)
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_VENDOR_RAMDISK_OUT) $(TARGET_DEBUG_RAMDISK_OUT) $(TARGET_VENDOR_DEBUG_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
+$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET): $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
 
-ifeq (true,$(BOARD_BUILD_VENDOR_RAMDISK_IMAGE))
 INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor_ramdisk-debug.img
 $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET): $(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET)
-	$(call pretty,"Target vendor debug ramdisk: $@")
+	@echo "Target debug vendor ramdisk: $@"
 	$(copy-file-to-target)
-endif
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET),$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
 
 # -----------------------------------------------------------------
 # vendor_boot-debug.img.
@@ -2516,7 +2806,7 @@
 $(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE)))
 $(AVBTOOL) add_hash_footer \
   --image $(1) \
-  --partition_size $(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE) \
+  $(call get-partition-size-argument,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE)) \
   --partition_name vendor_boot $(INTERNAL_AVB_VENDOR_BOOT_TEST_SIGNING_ARGS) \
   $(BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS)
 $(call assert-max-image-size,$(1),$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
@@ -2534,76 +2824,76 @@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 	$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),$(call test-key-sign-vendor-bootimage,$@))
 
-endif # BUILDING_RAMDISK_IMAGE
-endif # BUILDING_VENDOR_BOOT_IMAGE
+$(call declare-1p-container,$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_DEBUG_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)
+
+endif # BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+
+# Appends a few test harness specific properties into the adb_debug.prop.
+ADDITIONAL_TEST_HARNESS_PROPERTIES := ro.audio.silent=1
+ADDITIONAL_TEST_HARNESS_PROPERTIES += ro.test_harness=1
+
+INTERNAL_DEBUG_RAMDISK_ADB_DEBUG_PROP_TARGET := $(strip $(filter $(TARGET_DEBUG_RAMDISK_OUT)/adb_debug.prop,$(INTERNAL_DEBUG_RAMDISK_FILES)))
+INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET := $(TARGET_TEST_HARNESS_RAMDISK_OUT)/adb_debug.prop
+$(INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET): $(INTERNAL_DEBUG_RAMDISK_ADB_DEBUG_PROP_TARGET)
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@)
+ifdef INTERNAL_DEBUG_RAMDISK_ADB_DEBUG_PROP_TARGET
+	$(hide) cp $(INTERNAL_DEBUG_RAMDISK_ADB_DEBUG_PROP_TARGET) $@
+endif
+	$(hide) echo "" >> $@
+	$(hide) echo "#" >> $@
+	$(hide) echo "# ADDITIONAL TEST HARNESS PROPERTIES" >> $@
+	$(hide) echo "#" >> $@
+	$(hide) $(foreach line,$(ADDITIONAL_TEST_HARNESS_PROPERTIES), \
+	          echo "$(line)" >> $@;)
+
+$(call declare-1p-target,$(INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET))
+
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_TEST_HARNESS_RAMDISK_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
+INTERNAL_TEST_HARNESS_RAMDISK_FILES := $(filter $(TARGET_TEST_HARNESS_RAMDISK_OUT)/%, \
+    $(INTERNAL_TEST_HARNESS_RAMDISK_ADB_DEBUG_PROP_TARGET) \
+    $(ALL_DEFAULT_INSTALLED_MODULES))
+
+# The order is important here. The test harness ramdisk staging directory has to
+# come last so that it can override the adb_debug.prop in the debug ramdisk
+# staging directory.
+INTERNAL_TEST_HARNESS_RAMDISK_SRC_DIRS := $(INTERNAL_DEBUG_RAMDISK_SRC_DIRS) $(TARGET_TEST_HARNESS_RAMDISK_OUT)
+INTERNAL_TEST_HARNESS_RAMDISK_SRC_DEPS := $(INSTALLED_FILES_FILE_DEBUG_RAMDISK) $(INTERNAL_TEST_HARNESS_RAMDISK_FILES)
+
+ifdef BUILDING_DEBUG_BOOT_IMAGE
 
 # -----------------------------------------------------------------
 # The test harness ramdisk, which is based off debug_ramdisk, plus a
 # few additional test-harness-specific properties in adb_debug.prop.
+INSTALLED_TEST_HARNESS_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk-test-harness.img
 
-ifdef BUILDING_RAMDISK_IMAGE
-BUILT_TEST_HARNESS_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk-test-harness.img
-INSTALLED_TEST_HARNESS_RAMDISK_TARGET := $(BUILT_TEST_HARNESS_RAMDISK_TARGET)
+$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DEPS)
+$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
+	@echo "Target test harness ramdisk: $@"
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
 
-# Appends a few test harness specific properties into the adb_debug.prop.
-TEST_HARNESS_PROP_TARGET := $(TARGET_TEST_HARNESS_RAMDISK_OUT)/adb_debug.prop
-ADDITIONAL_TEST_HARNESS_PROPERTIES := ro.audio.silent=1
-ADDITIONAL_TEST_HARNESS_PROPERTIES += ro.test_harness=1
+$(call declare-1p-container,$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET),$(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DEPS),$(PRODUCT_OUT)/:/)
 
-# $(1): a list of key=value pairs for additional property assignments
-# $(2): the target .prop file to append the properties from $(1)
-define append-test-harness-props
-  echo "#" >> $(2); \
-  echo "# ADDITIONAL TEST HARNESS_PROPERTIES" >> $(2); \
-  echo "#" >> $(2);
-  $(foreach line,$(1), echo "$(line)" >> $(2);)
-endef
-
-INTERNAL_TEST_HARNESS_RAMDISK_FILES := $(filter $(TARGET_TEST_HARNESS_RAMDISK_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
-    $(ALL_DEFAULT_INSTALLED_MODULES))
-
-# ramdisk-test-harness.img will merge the content from either ramdisk.img or
-# ramdisk-recovery.img, depending on whether BOARD_USES_RECOVERY_AS_BOOT is set
-# or not.
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-  $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(recovery_ramdisk)
-else
-  $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RAMDISK_OUT)
-  $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(INSTALLED_RAMDISK_TARGET)
-endif # BOARD_USES_RECOVERY_AS_BOOT
-
-# The test harness ramdisk will rsync the files from the debug ramdisk, then appends some props.
-$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(INSTALLED_DEBUG_RAMDISK_TARGET)
-$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET): $(MKBOOTFS) $(INTERNAL_TEST_HARNESS_RAMDISK_FILES) | $(COMPRESSION_COMMAND_DEPS)
-	$(call pretty,"Target test harness ramdisk: $@")
-	rsync --chmod=u+w -a $(TARGET_DEBUG_RAMDISK_OUT)/ $(TARGET_TEST_HARNESS_RAMDISK_OUT)
-	$(call append-test-harness-props,$(ADDITIONAL_TEST_HARNESS_PROPERTIES),$(TEST_HARNESS_PROP_TARGET))
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_TEST_HARNESS_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
 
 .PHONY: ramdisk_test_harness-nodeps
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  ramdisk_test_harness-nodeps: PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-else
-  ramdisk_test_harness-nodeps: PRIVATE_ADDITIONAL_DIR := $(TARGET_RAMDISK_OUT)
-endif # BOARD_USES_RECOVERY_AS_BOOT
 ramdisk_test_harness-nodeps: $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
-	echo "make $@: ignoring dependencies"
-	rsync --chmod=u+w -a $(TARGET_DEBUG_RAMDISK_OUT)/ $(TARGET_TEST_HARNESS_RAMDISK_OUT)
-	$(call append-test-harness-props,$(ADDITIONAL_TEST_HARNESS_PROPERTIES),$(TEST_HARNESS_PROP_TARGET))
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_TEST_HARNESS_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
-
-endif # BUILDING_RAMDISK_IMAGE
+	@echo "make $@: ignoring dependencies"
+	$(hide) rm -f $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
+	$(hide) mkdir -p $(dir $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)) $(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DIRS)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_TEST_HARNESS_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
 
 # -----------------------------------------------------------------
 # the boot-test-harness.img, which is the kernel plus ramdisk-test-harness.img
 #
 # Note: it's intentional to skip signing for boot-test-harness.img, because it
 # can only be used if the device is unlocked with verification error.
-ifneq ($(INSTALLED_BOOTIMAGE_TARGET),)
-ifneq ($(strip $(TARGET_NO_KERNEL)),true)
-
 ifneq ($(strip $(BOARD_KERNEL_BINARIES)),)
   INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET := $(foreach k,$(subst kernel,boot-test-harness,$(BOARD_KERNEL_BINARIES)), \
     $(PRODUCT_OUT)/$(k).img)
@@ -2612,6 +2902,7 @@
 endif
 
 # Replace ramdisk-debug.img in $(MKBOOTIMG) ARGS with ramdisk-test-harness.img to build boot-test-harness.img
+$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET): $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
 INTERNAL_TEST_HARNESS_BOOTIMAGE_ARGS := $(subst $(INSTALLED_DEBUG_RAMDISK_TARGET),$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET),$(INTERNAL_DEBUG_BOOTIMAGE_ARGS))
 
 # If boot.img is chained but boot-test-harness.img is not signed, libavb in bootloader
@@ -2626,46 +2917,55 @@
 define build-boot-test-harness-target
   $(MKBOOTIMG) --kernel $(PRODUCT_OUT)/$(subst .img,,$(subst boot-test-harness,kernel,$(notdir $(1)))) \
     $(INTERNAL_TEST_HARNESS_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
-    $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@
+    $(BOARD_MKBOOTIMG_ARGS) --output $@
   $(if $(BOARD_AVB_BOOT_KEY_PATH),$(call test-key-sign-bootimage,$@,boot-test-harness))
 endef
 
 # Build the new boot-test-harness.img, based on boot-debug.img and ramdisk-test-harness.img.
-$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) \
-$(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) $(AVBTOOL)
 	$(call pretty,"Target boot test harness image: $@")
 	$(call build-boot-test-harness-target,$@)
 
+$(call declare-1p-container,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET)
+
 .PHONY: bootimage_test_harness-nodeps
-bootimage_test_harness-nodeps: $(MKBOOTIMG) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+bootimage_test_harness-nodeps: $(MKBOOTIMG) $(AVBTOOL)
 	echo "make $@: ignoring dependencies"
 	$(foreach b,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),$(call build-boot-test-harness-target,$b))
 
-endif # TARGET_NO_KERNEL
-endif # INSTALLED_BOOTIMAGE_TARGET
-endif # BOARD_BUILD_SYSTEM_ROOT_IMAGE is not true
+endif # BUILDING_DEBUG_BOOT_IMAGE
 
-ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
-ifeq ($(BUILDING_RAMDISK_IMAGE),true)
 # -----------------------------------------------------------------
 # vendor test harness ramdisk, which is a vendor ramdisk combined with
 # a test harness ramdisk.
+ifdef BUILDING_DEBUG_VENDOR_BOOT_IMAGE
 
 INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET := $(call intermediates-dir-for,PACKAGING,vendor_boot-test-harness)/vendor_ramdisk-test-harness.cpio$(RAMDISK_EXT)
 
-# Exclude recovery files in the default vendor ramdisk if including a standalone
-# recovery ramdisk in vendor_boot.
-ifeq (true,$(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT))
-ifneq (true,$(BOARD_INCLUDE_RECOVERY_RAMDISK_IN_VENDOR_BOOT))
-$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET): $(INTERNAL_RECOVERY_RAMDISK_FILES_TIMESTAMP)
-$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET): PRIVATE_ADDITIONAL_DIR := $(TARGET_RECOVERY_ROOT_OUT)
-endif
-endif
+# The order is important here. The test harness ramdisk staging directory has to
+# come last so that it can override the adb_debug.prop in the debug ramdisk
+# staging directory.
+INTERNAL_TEST_HARNESS_VENDOR_RAMDISK_SRC_DIRS := $(INTERNAL_DEBUG_VENDOR_RAMDISK_SRC_DIRS) $(TARGET_TEST_HARNESS_RAMDISK_OUT)
+INTERNAL_TEST_HARNESS_VENDOR_RAMDISK_SRC_DEPS := $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) $(INTERNAL_TEST_HARNESS_RAMDISK_FILES)
 
-# The vendor test harness ramdisk combines vendor ramdisk and test harness ramdisk.
-$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET): $(INTERNAL_VENDOR_RAMDISK_TARGET) $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)
+$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET): $(INTERNAL_TEST_HARNESS_VENDOR_RAMDISK_SRC_DEPS)
 $(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET): $(MKBOOTFS) | $(COMPRESSION_COMMAND_DEPS)
-	$(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_VENDOR_RAMDISK_OUT) $(TARGET_TEST_HARNESS_RAMDISK_OUT) $(PRIVATE_ADDITIONAL_DIR) | $(COMPRESSION_COMMAND) > $@
+	$(hide) rm -f $@
+	$(hide) mkdir -p $(dir $@)
+	$(MKBOOTFS) -d $(TARGET_OUT) $(INTERNAL_TEST_HARNESS_VENDOR_RAMDISK_SRC_DIRS) | $(COMPRESSION_COMMAND) > $@
+
+INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET := $(PRODUCT_OUT)/vendor_ramdisk-test-harness.img
+$(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET): $(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET)
+	@echo "Target test harness vendor ramdisk: $@"
+	$(copy-file-to-target)
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET)
 
 # -----------------------------------------------------------------
 # vendor_boot-test-harness.img.
@@ -2684,8 +2984,15 @@
 	$(call assert-max-image-size,$@,$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE))
 	$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),$(call test-key-sign-vendor-bootimage,$@))
 
-endif # BUILDING_RAMDISK_IMAGE
-endif # BUILDING_VENDOR_BOOT_IMAGE
+$(call declare-1p-container,$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET),$(INTERNAL_VENDOR_TEST_HARNESS_RAMDISK_TARGET),$(PRODUCT_OUT)/:/)
+
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET)
+
+endif # BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+
+endif # BUILDING_DEBUG_BOOT_IMAGE || BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+
 
 # Creates a compatibility symlink between two partitions, e.g. /system/vendor to /vendor
 # $1: from location (e.g $(TARGET_OUT)/vendor)
@@ -2711,8 +3018,66 @@
 # -----------------------------------------------------------------
 # system image
 
+# FSVerity metadata generation
+# Generate fsverity metadata files (.fsv_meta) and build manifest
+# (system/etc/security/fsverity/BuildManifest.apk) BEFORE filtering systemimage files below
+ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
+
+# Generate fsv_meta
+fsverity-metadata-targets := $(sort $(filter \
+  $(TARGET_OUT)/framework/% \
+  $(TARGET_OUT)/etc/boot-image.prof \
+  $(TARGET_OUT)/etc/dirty-image-objects \
+  $(TARGET_OUT)/etc/preloaded-classes \
+  $(TARGET_OUT)/etc/classpaths/%.pb, \
+  $(ALL_DEFAULT_INSTALLED_MODULES)))
+
+define fsverity-generate-metadata
+$(1).fsv_meta: PRIVATE_SRC := $(1)
+$(1).fsv_meta: PRIVATE_FSVERITY := $(HOST_OUT_EXECUTABLES)/fsverity
+$(1).fsv_meta: $(HOST_OUT_EXECUTABLES)/fsverity_metadata_generator $(HOST_OUT_EXECUTABLES)/fsverity $(1)
+	$$< --fsverity-path $$(PRIVATE_FSVERITY) --signature none \
+	    --hash-alg sha256 --output $$@ $$(PRIVATE_SRC)
+endef
+
+$(foreach f,$(fsverity-metadata-targets),$(eval $(call fsverity-generate-metadata,$(f))))
+ALL_DEFAULT_INSTALLED_MODULES += $(addsuffix .fsv_meta,$(fsverity-metadata-targets))
+
+# Generate BuildManifest.apk
+FSVERITY_APK_KEY_PATH := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
+FSVERITY_APK_OUT := $(TARGET_OUT)/etc/security/fsverity/BuildManifest.apk
+FSVERITY_APK_MANIFEST_PATH := system/security/fsverity/AndroidManifest.xml
+$(FSVERITY_APK_OUT): PRIVATE_FSVERITY := $(HOST_OUT_EXECUTABLES)/fsverity
+$(FSVERITY_APK_OUT): PRIVATE_AAPT2 := $(HOST_OUT_EXECUTABLES)/aapt2
+$(FSVERITY_APK_OUT): PRIVATE_MIN_SDK_VERSION := $(DEFAULT_APP_TARGET_SDK)
+$(FSVERITY_APK_OUT): PRIVATE_VERSION_CODE := $(PLATFORM_SDK_VERSION)
+$(FSVERITY_APK_OUT): PRIVATE_VERSION_NAME := $(APPS_DEFAULT_VERSION_NAME)
+$(FSVERITY_APK_OUT): PRIVATE_APKSIGNER := $(HOST_OUT_EXECUTABLES)/apksigner
+$(FSVERITY_APK_OUT): PRIVATE_MANIFEST := $(FSVERITY_APK_MANIFEST_PATH)
+$(FSVERITY_APK_OUT): PRIVATE_FRAMEWORK_RES := $(call intermediates-dir-for,APPS,framework-res,,COMMON)/package-export.apk
+$(FSVERITY_APK_OUT): PRIVATE_KEY := $(FSVERITY_APK_KEY_PATH)
+$(FSVERITY_APK_OUT): PRIVATE_INPUTS := $(fsverity-metadata-targets)
+$(FSVERITY_APK_OUT): $(HOST_OUT_EXECUTABLES)/fsverity_manifest_generator \
+    $(HOST_OUT_EXECUTABLES)/fsverity $(HOST_OUT_EXECUTABLES)/aapt2 \
+    $(HOST_OUT_EXECUTABLES)/apksigner $(FSVERITY_APK_MANIFEST_PATH) \
+    $(FSVERITY_APK_KEY_PATH).x509.pem $(FSVERITY_APK_KEY_PATH).pk8 \
+    $(call intermediates-dir-for,APPS,framework-res,,COMMON)/package-export.apk \
+    $(fsverity-metadata-targets)
+	$< --fsverity-path $(PRIVATE_FSVERITY) --aapt2-path $(PRIVATE_AAPT2) \
+	    --min-sdk-version $(PRIVATE_MIN_SDK_VERSION) \
+	    --version-code $(PRIVATE_VERSION_CODE) \
+	    --version-name $(PRIVATE_VERSION_NAME) \
+	    --apksigner-path $(PRIVATE_APKSIGNER) --apk-key-path $(PRIVATE_KEY) \
+	    --apk-manifest-path $(PRIVATE_MANIFEST) --framework-res $(PRIVATE_FRAMEWORK_RES) \
+	    --output $@ \
+	    --base-dir $(PRODUCT_OUT) $(PRIVATE_INPUTS)
+
+ALL_DEFAULT_INSTALLED_MODULES += $(FSVERITY_APK_OUT)
+
+endif  # PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_SYSTEMIMAGE_FILES := $(sort $(filter $(TARGET_OUT)/%, \
-    $(ALL_GENERATED_SOURCES) \
     $(ALL_DEFAULT_INSTALLED_MODULES)))
 
 # Create symlink /system/vendor to /vendor if necessary.
@@ -2730,6 +3095,19 @@
   INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/system_ext,/system_ext,system_ext.img)
 endif
 
+# -----------------------------------------------------------------
+# system_dlkm partition image
+
+# Create symlinks for system_dlkm on devices with a system_dlkm partition:
+# /system/lib/modules -> /system_dlkm/lib/modules
+#
+# On devices with a system_dlkm partition,
+# - /system/lib/modules is a symlink to a directory that stores system DLKMs.
+# - The system_dlkm partition is mounted at /system_dlkm at runtime.
+ifdef BOARD_USES_SYSTEM_DLKMIMAGE
+  INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/lib/modules,/system_dlkm/lib/modules,system_dlkm.img)
+endif
+
 FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)
 
 # ASAN libraries in the system image - add dependency.
@@ -2748,10 +3126,15 @@
 # Install system linker configuration
 # Collect all available stub libraries installed in system and install with predefined linker configuration
 SYSTEM_LINKER_CONFIG := $(TARGET_OUT)/etc/linker.config.pb
-$(SYSTEM_LINKER_CONFIG) : $(INTERNAL_SYSTEMIMAGE_FILES) $(LINKER_CONFIG_PATH_system_linker_config) | conv_linker_config
-	$(HOST_OUT_EXECUTABLES)/conv_linker_config systemprovide --source $(LINKER_CONFIG_PATH_system_linker_config)\
+SYSTEM_LINKER_CONFIG_SOURCE := $(call intermediates-dir-for,ETC,system_linker_config)/system_linker_config
+$(SYSTEM_LINKER_CONFIG): PRIVATE_SYSTEM_LINKER_CONFIG_SOURCE := $(SYSTEM_LINKER_CONFIG_SOURCE)
+$(SYSTEM_LINKER_CONFIG) : $(INTERNAL_SYSTEMIMAGE_FILES) $(SYSTEM_LINKER_CONFIG_SOURCE) | conv_linker_config
+	$(HOST_OUT_EXECUTABLES)/conv_linker_config systemprovide --source $(PRIVATE_SYSTEM_LINKER_CONFIG_SOURCE) \
 	  --output $@ --value "$(STUB_LIBRARIES)" --system "$(TARGET_OUT)"
 
+$(call declare-1p-target,$(SYSTEM_LINKER_CONFIG),)
+$(call declare-license-deps,$(SYSTEM_LINKER_CONFIG),$(INTERNAL_SYSTEMIMAGE_FILES) $(SYSTEM_LINKER_CONFIG_SOURCE))
+
 FULL_SYSTEMIMAGE_DEPS += $(SYSTEM_LINKER_CONFIG)
 
 # installed file list
@@ -2769,11 +3152,12 @@
 	$(FILESLIST) $(TARGET_OUT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON)))
+
 .PHONY: installed-file-list
 installed-file-list: $(INSTALLED_FILES_FILE)
 
-$(call dist-for-goals, sdk win_sdk sdk_addon, $(INSTALLED_FILES_FILE))
-
 systemimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,systemimage)
 BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img
@@ -2798,6 +3182,9 @@
 $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
 	$(call build-systemimage-target,$@)
 
+$(call declare-1p-container,$(BUILT_SYSTEMIMAGE),system/extras)
+$(call declare-container-license-deps,$(BUILT_SYSTEMIMAGE),$(FULL_SYSTEMIMAGE_DEPS),$(PRODUCT_OUT)/:/)
+
 INSTALLED_SYSTEMIMAGE_TARGET := $(PRODUCT_OUT)/system.img
 SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
 
@@ -2839,8 +3226,13 @@
 	$(copy-file-to-target)
 	$(hide) $(call assert-max-image-size,$@,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
 
+$(call declare-1p-container,$(INSTALLED_SYSTEMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEMIMAGE_TARGET),$(BUILT_SYSTEMIMAGE),$(BUILT_SYSTEMIMAGE):/)
+
 systemimage: $(INSTALLED_SYSTEMIMAGE_TARGET)
 
+SYSTEM_NOTICE_DEPS += $(INSTALLED_SYSTEMIMAGE_TARGET)
+
 .PHONY: systemimage-nodeps snod
 systemimage-nodeps snod: $(filter-out systemimage-nodeps snod,$(MAKECMDGOALS)) \
 	            | $(INTERNAL_USERIMAGES_DEPS)
@@ -2868,6 +3260,7 @@
 
 # -----------------------------------------------------------------
 # data partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_DATA)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 INTERNAL_USERDATAIMAGE_FILES := \
     $(filter $(TARGET_OUT_DATA)/%,$(ALL_DEFAULT_INSTALLED_MODULES))
 
@@ -2896,6 +3289,11 @@
 $(INSTALLED_USERDATAIMAGE_TARGET): $(INSTALLED_USERDATAIMAGE_TARGET_DEPS)
 	$(build-userdataimage-target)
 
+$(call declare-1p-container,$(INSTALLED_USERDATAIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_USERDATAIMAGE_TARGET),$(INSTALLED_USERDATAIMAGE_TARGET_DEPS),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_USERDATAIMAGE_TARGET)
+
 .PHONY: userdataimage-nodeps
 userdataimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-userdataimage-target)
@@ -2943,6 +3341,11 @@
 $(INSTALLED_BPTIMAGE_TARGET): $(BPTTOOL) $(BOARD_BPT_INPUT_FILES)
 	$(build-bptimage-target)
 
+$(call declare-1p-container,$(INSTALLED_BPTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_BPTIMAGE_TARGET),$(BOARD_BPT_INPUT_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_BPTIMAGE_TARGET)
+
 .PHONY: bptimage-nodeps
 bptimage-nodeps:
 	$(build-bptimage-target)
@@ -2951,6 +3354,7 @@
 
 # -----------------------------------------------------------------
 # cache partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_CACHE)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_CACHE_IMAGE
 INTERNAL_CACHEIMAGE_FILES := \
     $(filter $(TARGET_OUT_CACHE)/%,$(ALL_DEFAULT_INSTALLED_MODULES))
@@ -2976,6 +3380,11 @@
 $(INSTALLED_CACHEIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES)
 	$(build-cacheimage-target)
 
+$(call declare-1p-container,$(INSTALLED_CACHEIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_CACHEIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_CACHEIMAGE_TARGET)
+
 .PHONY: cacheimage-nodeps
 cacheimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-cacheimage-target)
@@ -2987,6 +3396,7 @@
 
 # -----------------------------------------------------------------
 # system_other partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_OTHER)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_SYSTEM_OTHER_IMAGE
 ifeq ($(BOARD_USES_SYSTEM_OTHER_ODEX),true)
 # Marker file to identify that odex files are installed
@@ -2994,6 +3404,8 @@
 ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER)
 $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER):
 	$(hide) touch $@
+
+$(call declare-0p-target,$(INSTALLED_SYSTEM_OTHER_ODEX_MARKER))
 endif
 
 INTERNAL_SYSTEMOTHERIMAGE_FILES := \
@@ -3013,6 +3425,9 @@
 	$(FILESLIST) $(TARGET_OUT_SYSTEM_OTHER) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEMOTHER)))
+$(eval $(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEMOTHER)))
+
 # Determines partition size for system_other.img.
 ifeq ($(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS),true)
 ifneq ($(filter system,$(BOARD_SUPER_PARTITION_BLOCK_DEVICES)),)
@@ -3047,6 +3462,11 @@
 # Only create system_other when not building the second stage of a SANITIZE_LITE build.
 $(INSTALLED_SYSTEMOTHERIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEMOTHERIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEMOTHER)
 	$(build-systemotherimage-target)
+
+$(call declare-1p-container,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEMOTHERIMAGE_FILES),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
 endif
 
 .PHONY: systemotherimage-nodeps
@@ -3058,6 +3478,7 @@
 
 # -----------------------------------------------------------------
 # vendor partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_VENDOR)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_VENDOR_IMAGE
 INTERNAL_VENDORIMAGE_FILES := \
     $(filter $(TARGET_OUT_VENDOR)/%,\
@@ -3097,6 +3518,9 @@
 	$(FILESLIST) $(TARGET_OUT_VENDOR) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR))
+
 vendorimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,vendor)
 BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img
@@ -3121,6 +3545,11 @@
     $(RECOVERY_FROM_BOOT_PATCH)
 	$(build-vendorimage-target)
 
+VENDOR_NOTICE_DEPS += $(INSTALLED_VENDORIMAGE_TARGET)
+
+$(call declare-container-license-metadata,$(INSTALLED_VENDORIMAGE_TARGET),legacy_proprietary,proprietary,,"Vendor Image",vendor)
+$(call declare-container-license-deps,$(INSTALLED_VENDORIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_VENDORIMAGE_FILES) $(RECOVERY_FROM_BOOT_PATH),$(PRODUCT_OUT)/:/)
+
 .PHONY: vendorimage-nodeps vnod
 vendorimage-nodeps vnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-vendorimage-target)
@@ -3130,10 +3559,15 @@
 else ifdef BOARD_PREBUILT_VENDORIMAGE
 INSTALLED_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img
 $(eval $(call copy-one-file,$(BOARD_PREBUILT_VENDORIMAGE),$(INSTALLED_VENDORIMAGE_TARGET)))
+$(if $(strip $(ALL_TARGETS.$(INSTALLED_VENDORIMAGE_TARGET).META_LIC)),,\
+    $(if $(strip $(ALL_TARGETS.$(BOARD_PREBUILT_VENDORIMAGE).META_LIC)),\
+        $(eval ALL_TARGETS.$(INSTALLED_VENDORIMAGE_TARGET).META_LIC:=$(ALL_TARGETS.$(BOARD_PREBUILT_VENDORIMAGE).META_LIC)),\
+        $(call declare-license-metadata,$(INSTALLED_VENDORIMAGE_TARGET),legacy_proprietary,proprietary,,"Vendor Image",vendor)))
 endif
 
 # -----------------------------------------------------------------
 # product partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_PRODUCT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_PRODUCT_IMAGE
 INTERNAL_PRODUCTIMAGE_FILES := \
     $(filter $(TARGET_OUT_PRODUCT)/%,\
@@ -3149,6 +3583,9 @@
 	$(FILESLIST) $(TARGET_OUT_PRODUCT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_PRODUCT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_PRODUCT))
+
 productimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,product)
 BUILT_PRODUCTIMAGE_TARGET := $(PRODUCT_OUT)/product.img
@@ -3172,6 +3609,11 @@
     $(INSTALLED_FILES_FILE_PRODUCT)
 	$(build-productimage-target)
 
+PRODUCT_NOTICE_DEPS += $(INSTALLED_PRODUCTIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_PRODUCTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_PRODUCTIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_PRODUCTIMAGE_FILES) $(INSTALLED_FILES_FILE_PRODUCT),$(PRODUCT_OUT)/:/)
+
 .PHONY: productimage-nodeps pnod
 productimage-nodeps pnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-productimage-target)
@@ -3185,6 +3627,7 @@
 
 # -----------------------------------------------------------------
 # system_ext partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_EXT)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_SYSTEM_EXT_IMAGE
 INTERNAL_SYSTEM_EXTIMAGE_FILES := \
     $(filter $(TARGET_OUT_SYSTEM_EXT)/%,\
@@ -3200,6 +3643,9 @@
 	$(FILESLIST) $(TARGET_OUT_SYSTEM_EXT) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEM_EXT))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEM_EXT))
+
 system_extimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,system_ext)
 BUILT_SYSTEM_EXTIMAGE_TARGET := $(PRODUCT_OUT)/system_ext.img
@@ -3225,6 +3671,11 @@
     $(INSTALLED_FILES_FILE_SYSTEM_EXT)
 	$(build-system_extimage-target)
 
+SYSTEM_EXT_NOTICE_DEPS += $(INSTALLED_SYSTEM_EXTIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_SYSTEM_EXTIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEM_EXTIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEM_EXTIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEM_EXT),$(PRODUCT_OUT)/:/)
+
 .PHONY: systemextimage-nodeps senod
 systemextimage-nodeps senod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-system_extimage-target)
@@ -3238,6 +3689,7 @@
 
 # -----------------------------------------------------------------
 # odm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_ODM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_ODM_IMAGE
 INTERNAL_ODMIMAGE_FILES := \
     $(filter $(TARGET_OUT_ODM)/%,\
@@ -3271,6 +3723,9 @@
 	$(FILESLIST) $(TARGET_OUT_ODM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ODM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ODM))
+
 odmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,odm)
 BUILT_ODMIMAGE_TARGET := $(PRODUCT_OUT)/odm.img
@@ -3278,7 +3733,8 @@
   $(call pretty,"Target odm fs image: $(INSTALLED_ODMIMAGE_TARGET)")
   @mkdir -p $(TARGET_OUT_ODM)
   @mkdir -p $(odmimage_intermediates) && rm -rf $(odmimage_intermediates)/odm_image_info.txt
-  $(call generate-userimage-prop-dictionary, $(odmimage_intermediates)/odm_image_info.txt, skip_fsck=true)
+  $(call generate-image-prop-dictionary, $(odmimage_intermediates)/odm_image_info.txt, odm, \
+	  skip_fsck=true)
   PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
       $(BUILD_IMAGE) \
           $(TARGET_OUT_ODM) $(odmimage_intermediates)/odm_image_info.txt \
@@ -3294,6 +3750,11 @@
     $(INSTALLED_FILES_FILE_ODM)
 	$(build-odmimage-target)
 
+ODM_NOTICE_DEPS += $(INSTALLED_ODMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_ODMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_ODMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_ODMIMAGE_FILES) $(INSTALLED_FILES_FILE_ODM),$(PRODUCT_OUT)/:/)
+
 .PHONY: odmimage-nodeps onod
 odmimage-nodeps onod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-odmimage-target)
@@ -3307,6 +3768,7 @@
 
 # -----------------------------------------------------------------
 # vendor_dlkm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_VENDOR_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_VENDOR_DLKM_IMAGE
 INTERNAL_VENDOR_DLKMIMAGE_FILES := \
     $(filter $(TARGET_OUT_VENDOR_DLKM)/%,\
@@ -3322,6 +3784,9 @@
 	$(FILESLIST) $(TARGET_OUT_VENDOR_DLKM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_VENDOR_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_VENDOR_DLKM))
+
 vendor_dlkmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,vendor_dlkm)
 BUILT_VENDOR_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/vendor_dlkm.img
@@ -3329,7 +3794,8 @@
   $(call pretty,"Target vendor_dlkm fs image: $(INSTALLED_VENDOR_DLKMIMAGE_TARGET)")
   @mkdir -p $(TARGET_OUT_VENDOR_DLKM)
   @mkdir -p $(vendor_dlkmimage_intermediates) && rm -rf $(vendor_dlkmimage_intermediates)/vendor_dlkm_image_info.txt
-  $(call generate-userimage-prop-dictionary, $(vendor_dlkmimage_intermediates)/vendor_dlkm_image_info.txt, skip_fsck=true)
+  $(call generate-image-prop-dictionary, $(vendor_dlkmimage_intermediates)/vendor_dlkm_image_info.txt, \
+	  vendor_dlkm, skip_fsck=true)
   PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
       $(BUILD_IMAGE) \
           $(TARGET_OUT_VENDOR_DLKM) $(vendor_dlkmimage_intermediates)/vendor_dlkm_image_info.txt \
@@ -3345,6 +3811,11 @@
     $(INSTALLED_FILES_FILE_VENDOR_DLKM)
 	$(build-vendor_dlkmimage-target)
 
+VENDOR_DLKM_NOTICE_DEPS += $(INSTALLED_VENDOR_DLKMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_VENDOR_DLKMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_VENDOR_DLKMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_VENDOR_DLKMIMAGE_FILES) $(INSTALLED_FILES_FILE_VENDOR_DLKM),$(PRODUCT_OUT)/:/)
+
 .PHONY: vendor_dlkmimage-nodeps vdnod
 vendor_dlkmimage-nodeps vdnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-vendor_dlkmimage-target)
@@ -3358,6 +3829,7 @@
 
 # -----------------------------------------------------------------
 # odm_dlkm partition image
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_ODM_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
 ifdef BUILDING_ODM_DLKM_IMAGE
 INTERNAL_ODM_DLKMIMAGE_FILES := \
     $(filter $(TARGET_OUT_ODM_DLKM)/%,\
@@ -3373,6 +3845,9 @@
 	$(FILESLIST) $(TARGET_OUT_ODM_DLKM) > $(@:.txt=.json)
 	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
 
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_ODM_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_ODM_DLKM))
+
 odm_dlkmimage_intermediates := \
     $(call intermediates-dir-for,PACKAGING,odm_dlkm)
 BUILT_ODM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/odm_dlkm.img
@@ -3380,7 +3855,8 @@
   $(call pretty,"Target odm_dlkm fs image: $(INSTALLED_ODM_DLKMIMAGE_TARGET)")
   @mkdir -p $(TARGET_OUT_ODM_DLKM)
   @mkdir -p $(odm_dlkmimage_intermediates) && rm -rf $(odm_dlkmimage_intermediates)/odm_dlkm_image_info.txt
-  $(call generate-userimage-prop-dictionary, $(odm_dlkmimage_intermediates)/odm_dlkm_image_info.txt, skip_fsck=true)
+  $(call generate-image-prop-dictionary, $(odm_dlkmimage_intermediates)/odm_dlkm_image_info.txt, \
+	  odm_dlkm, skip_fsck=true)
   PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
       $(BUILD_IMAGE) \
           $(TARGET_OUT_ODM_DLKM) $(odm_dlkmimage_intermediates)/odm_dlkm_image_info.txt \
@@ -3396,6 +3872,11 @@
     $(INSTALLED_FILES_FILE_ODM_DLKM)
 	$(build-odm_dlkmimage-target)
 
+ODM_DLKM_NOTICE_DEPS += $(INSTALLED_ODM_DLKMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_ODM_DLKMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_ODM_DLKMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_ODM_DLKMIMAGE_FILES) $(INSTALLED_FILES_FILE_ODM_DLKM),$(PRODUCT_OUT)/:/)
+
 .PHONY: odm_dlkmimage-nodeps odnod
 odm_dlkmimage-nodeps odnod: | $(INTERNAL_USERIMAGES_DEPS)
 	$(build-odm_dlkmimage-target)
@@ -3407,6 +3888,69 @@
 $(eval $(call copy-one-file,$(BOARD_PREBUILT_ODM_DLKMIMAGE),$(INSTALLED_ODM_DLKMIMAGE_TARGET)))
 endif
 
+# -----------------------------------------------------------------
+# system_dlkm partition image
+
+INSTALLED_FILES_OUTSIDE_IMAGES := $(filter-out $(TARGET_OUT_SYSTEM_DLKM)/%, $(INSTALLED_FILES_OUTSIDE_IMAGES))
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+
+INTERNAL_SYSTEM_DLKMIMAGE_FILES := \
+    $(filter $(TARGET_OUT_SYSTEM_DLKM)/%,\
+      $(ALL_DEFAULT_INSTALLED_MODULES))
+
+INSTALLED_FILES_FILE_SYSTEM_DLKM := $(PRODUCT_OUT)/installed-files-system_dlkm.txt
+INSTALLED_FILES_JSON_SYSTEM_DLKM := $(INSTALLED_FILES_FILE_SYSTEM_DLKM:.txt=.json)
+$(INSTALLED_FILES_FILE_SYSTEM_DLKM): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_SYSTEM_DLKM)
+$(INSTALLED_FILES_FILE_SYSTEM_DLKM): $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) $(FILESLIST) $(FILESLIST_UTIL)
+	@echo Installed file list: $@
+	mkdir -p $(dir $@)
+	if [ -d "$(BOARD_SYSTEM_DLKM_SRC)" ]; then rsync -rupE $(BOARD_SYSTEM_DLKM_SRC)/ $(TARGET_OUT_SYSTEM_DLKM); fi
+	rm -f $@
+	$(FILESLIST) $(TARGET_OUT_SYSTEM_DLKM) > $(@:.txt=.json)
+	$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+$(call declare-0p-target,$(INSTALLED_FILES_FILE_SYSTEM_DLKM))
+$(call declare-0p-target,$(INSTALLED_FILES_JSON_SYSTEM_DLKM))
+
+system_dlkmimage_intermediates := \
+    $(call intermediates-dir-for,PACKAGING,system_dlkm)
+BUILT_SYSTEM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/system_dlkm.img
+define build-system_dlkmimage-target
+  $(call pretty,"Target system_dlkm fs image: $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)")
+  @mkdir -p $(TARGET_OUT_SYSTEM_DLKM)
+  @mkdir -p $(system_dlkmimage_intermediates) && rm -rf $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt
+  $(call generate-image-prop-dictionary, $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt, \
+	  system_dlkm, skip_fsck=true)
+  PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
+      $(BUILD_IMAGE) \
+          $(TARGET_OUT_SYSTEM_DLKM) $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt \
+          $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(TARGET_OUT)
+  $(call assert-max-image-size,$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE))
+endef
+
+# We just build this directly to the install location.
+INSTALLED_SYSTEM_DLKMIMAGE_TARGET := $(BUILT_SYSTEM_DLKMIMAGE_TARGET)
+$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET): \
+    $(INTERNAL_USERIMAGES_DEPS) \
+    $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) \
+    $(INSTALLED_FILES_FILE_SYSTEM_DLKM)
+	$(build-system_dlkmimage-target)
+
+SYSTEM_DLKM_NOTICE_DEPS += $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+
+$(call declare-1p-container,$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEM_DLKM),$(PRODUCT_OUT)/:/)
+
+.PHONY: system_dlkmimage-nodeps sdnod
+system_dlkmimage-nodeps sdnod: | $(INTERNAL_USERIMAGES_DEPS)
+	$(build-system_dlkmimage-target)
+
+sync: $(INTERNAL_SYSTEM_DLKMIMAGE_FILES)
+
+else ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+INSTALLED_SYSTEM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/system_dlkm.img
+$(eval $(call copy-one-file,$(BOARD_PREBUILT_SYSTEM_DLKMIMAGE),$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)))
+endif
 
 # -----------------------------------------------------------------
 # dtbo image
@@ -3418,9 +3962,14 @@
 	cp $(BOARD_PREBUILT_DTBOIMAGE) $@
 	$(AVBTOOL) add_hash_footer \
 	    --image $@ \
-	    --partition_size $(BOARD_DTBOIMG_PARTITION_SIZE) \
+	    $(call get-partition-size-argument,$(BOARD_DTBOIMG_PARTITION_SIZE)) \
 	    --partition_name dtbo $(INTERNAL_AVB_DTBO_SIGNING_ARGS) \
 	    $(BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_DTBOIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_DTBOIMAGE_TARGET),$(BOARD_PREBUILT_DTBOIMAGE),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_DTBOIMAGE_TARGET)
 else
 $(INSTALLED_DTBOIMAGE_TARGET): $(BOARD_PREBUILT_DTBOIMAGE)
 	cp $(BOARD_PREBUILT_DTBOIMAGE) $@
@@ -3430,30 +3979,50 @@
 
 # -----------------------------------------------------------------
 # Protected VM firmware image
-ifdef BOARD_PREBUILT_PVMFWIMAGE
+ifeq ($(BOARD_USES_PVMFWIMAGE),true)
 INSTALLED_PVMFWIMAGE_TARGET := $(PRODUCT_OUT)/pvmfw.img
+INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET := $(PRODUCT_OUT)/pvmfw_embedded.avbpubkey
+INTERNAL_PREBUILT_PVMFWIMAGE := packages/modules/Virtualization/pvmfw/pvmfw.img
+INTERNAL_PVMFW_EMBEDDED_AVBKEY := external/avb/test/data/testkey_rsa4096_pub.bin
 
-ifeq ($(BOARD_AVB_ENABLE),true)
-$(INSTALLED_PVMFWIMAGE_TARGET): $(BOARD_PREBUILT_PVMFWIMAGE) $(AVBTOOL) $(BOARD_AVB_PVMFW_KEY_PATH)
-	cp $(BOARD_PREBUILT_PVMFWIMAGE) $@
-	$(AVBTOOL) add_hash_footer \
-	    --image $@ \
-	    --partition_size $(BOARD_PVMFWIMG_PARTITION_SIZE) \
-	    --partition_name pvmfw $(INTERNAL_AVB_PVMFW_SIGNING_ARGS) \
-	    $(BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS)
+ifdef BOARD_PREBUILT_PVMFWIMAGE
+PREBUILT_PVMFWIMAGE_TARGET := $(BOARD_PREBUILT_PVMFWIMAGE)
 else
-$(INSTALLED_PVMFWIMAGE_TARGET): $(BOARD_PREBUILT_PVMFWIMAGE)
-	cp $(BOARD_PREBUILT_PVMFWIMAGE) $@
+PREBUILT_PVMFWIMAGE_TARGET := $(INTERNAL_PREBUILT_PVMFWIMAGE)
 endif
 
-endif # BOARD_PREBUILT_PVMFWIMAGE
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_PVMFWIMAGE_TARGET): $(PREBUILT_PVMFWIMAGE_TARGET) $(AVBTOOL) $(BOARD_AVB_PVMFW_KEY_PATH)
+	cp $< $@
+	$(AVBTOOL) add_hash_footer \
+	    --image $@ \
+	    $(call get-partition-size-argument,$(BOARD_PVMFWIMAGE_PARTITION_SIZE)) \
+	    --partition_name pvmfw $(INTERNAL_AVB_PVMFW_SIGNING_ARGS) \
+	    $(BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS)
+
+$(call declare-1p-container,$(INSTALLED_PVMFWIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_PVMFWIMAGE_TARGET),$(PREBUILT_PVMFWIMAGE_TARGET),$(PRODUCT_OUT)/:/)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_PVMFWIMAGE_TARGET)
+else
+$(eval $(call copy-one-file,$(PREBUILT_PVMFWIMAGE_TARGET),$(INSTALLED_PVMFWIMAGE_TARGET)))
+endif
+
+$(INSTALLED_PVMFWIMAGE_TARGET): $(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET)
+
+$(eval $(call copy-one-file,$(INTERNAL_PVMFW_EMBEDDED_AVBKEY),$(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET)))
+
+endif # BOARD_USES_PVMFWIMAGE
 
 # Returns a list of image targets corresponding to the given list of partitions. For example, it
 # returns "$(INSTALLED_PRODUCTIMAGE_TARGET)" for "product", or "$(INSTALLED_SYSTEMIMAGE_TARGET)
 # $(INSTALLED_VENDORIMAGE_TARGET)" for "system vendor".
 # (1): list of partitions like "system", "vendor" or "system product system_ext".
 define images-for-partitions
-$(strip $(foreach item,$(1),$(if $(filter $(item),system_other),$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(INSTALLED_$(call to-upper,$(item))IMAGE_TARGET))))
+$(strip $(foreach item,$(1),\
+  $(if $(filter $(item),system_other),$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),\
+    $(if $(filter $(item),init_boot),$(INSTALLED_INIT_BOOT_IMAGE_TARGET),\
+      $(INSTALLED_$(call to-upper,$(item))IMAGE_TARGET)))))
 endef
 
 # -----------------------------------------------------------------
@@ -3478,7 +4047,7 @@
           --image $(3) \
           --key $(BOARD_AVB_$(call to-upper,$(2))_KEY_PATH) \
           --algorithm $(BOARD_AVB_$(call to-upper,$(2))_ALGORITHM) \
-          --partition_size $(BOARD_AVB_$(call to-upper,$(2))_PARTITION_SIZE) \
+          $(call get-partition-size-argument,$(BOARD_AVB_$(call to-upper,$(2))_PARTITION_SIZE)) \
           --partition_name $(2) \
           $(INTERNAL_AVB_CUSTOMIMAGES_SIGNING_ARGS) \
           $(BOARD_AVB_$(call to-upper,$(2))_ADD_HASHTREE_FOOTER_ARGS)
@@ -3522,6 +4091,8 @@
 	@mkdir -p $(dir $@)
 	$(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_OTHER_KEY_PATH) --output $@
 
+$(eval $(call declare-0p-target,$(INSTALLED_PRODUCT_SYSTEM_OTHER_AVBKEY_TARGET),))
+
 ifndef BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX
 BOARD_AVB_SYSTEM_OTHER_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
 endif
@@ -3549,48 +4120,89 @@
 endif
 endif
 
-# Appends os version and security patch level as a AVB property descriptor
+# Appends os version as a AVB property descriptor.
+SYSTEM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system.os_version:$(SYSTEM_OS_VERSION)
 
+PRODUCT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_PRODUCT_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.product.os_version:$(PRODUCT_OS_VERSION)
+
+SYSTEM_EXT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_EXT_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_ext.os_version:$(SYSTEM_EXT_OS_VERSION)
+
+INIT_BOOT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.init_boot.os_version:$(INIT_BOOT_OS_VERSION)
+
+BOOT_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.boot.os_version:$(BOOT_OS_VERSION)
+
+VENDOR_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.vendor.os_version:$(VENDOR_OS_VERSION)
+
+ODM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.odm.os_version:$(ODM_OS_VERSION)
+
+VENDOR_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.vendor_dlkm.os_version:$(VENDOR_DLKM_OS_VERSION)
+
+ODM_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.odm_dlkm.os_version:$(ODM_DLKM_OS_VERSION)
+
+SYSTEM_DLKM_OS_VERSION ?= $(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_dlkm.os_version:$(SYSTEM_DLKM_OS_VERSION)
+
+# Appends fingerprint and security patch level as a AVB property descriptor.
 BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.system.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.system.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.system.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_PRODUCT_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.product.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.product.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.product.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_SYSTEM_EXT_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.system_ext.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.system_ext.os_version:$(PLATFORM_VERSION_LAST_STABLE) \
     --prop com.android.build.system_ext.security_patch:$(PLATFORM_SECURITY_PATCH)
 
 BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
-    --prop com.android.build.boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.boot.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
+
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.init_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.vendor_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
 
+BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.vendor_kernel_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
+
 BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.recovery.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.vendor.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.vendor.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.vendor.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.odm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.odm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.odm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.vendor_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.vendor_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.vendor_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
-    --prop com.android.build.odm_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
-    --prop com.android.build.odm_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+    --prop com.android.build.odm_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
+
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.dtbo.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
@@ -3599,11 +4211,21 @@
     --prop com.android.build.pvmfw.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
 
 # The following vendor- and odm-specific images needs explicit SPL set per board.
+# TODO(b/210875415) Is this security_patch property used? Should it be removed from
+# boot.img when there is no platform ramdisk included in it?
 ifdef BOOT_SECURITY_PATCH
 BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.boot.security_patch:$(BOOT_SECURITY_PATCH)
 endif
 
+ifdef INIT_BOOT_SECURITY_PATCH
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.init_boot.security_patch:$(INIT_BOOT_SECURITY_PATCH)
+else ifdef BOOT_SECURITY_PATCH
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+    --prop com.android.build.init_boot.security_patch:$(BOOT_SECURITY_PATCH)
+endif
+
 ifdef VENDOR_SECURITY_PATCH
 BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
     --prop com.android.build.vendor.security_patch:$(VENDOR_SECURITY_PATCH)
@@ -3624,13 +4246,20 @@
     --prop com.android.build.odm_dlkm.security_patch:$(ODM_DLKM_SECURITY_PATCH)
 endif
 
+ifdef SYSTEM_DLKM_SECURITY_PATCH
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+    --prop com.android.build.system_dlkm.security_patch:$(SYSTEM_DLKM_SECURITY_PATCH)
+endif
+
 ifdef PVMFW_SECURITY_PATCH
 BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS += \
     --prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
 endif
 
 BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
+INIT_BOOT_FOOTER_ARGS := BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS
 VENDOR_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS
+VENDOR_KERNEL_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS
 DTBO_FOOTER_ARGS := BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS
 PVMFW_FOOTER_ARGS := BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS
 SYSTEM_FOOTER_ARGS := BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS
@@ -3641,6 +4270,7 @@
 ODM_FOOTER_ARGS := BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS
 VENDOR_DLKM_FOOTER_ARGS := BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS
 ODM_DLKM_FOOTER_ARGS := BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS
+SYSTEM_DLKM_FOOTER_ARGS := BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS
 
 # Helper function that checks and sets required build variables for an AVB chained partition.
 # $(1): the partition to enable AVB chain, e.g., boot or system or vbmeta_system.
@@ -3713,11 +4343,21 @@
 $(eval $(call check-and-set-avb-args,boot))
 endif
 
+ifdef INSTALLED_INIT_BOOT_IMAGE_TARGET
+$(eval $(call check-and-set-avb-args,init_boot))
+endif
+
 ifdef INSTALLED_VENDOR_BOOTIMAGE_TARGET
 $(eval $(call check-and-set-avb-args,vendor_boot))
 endif
 
+ifdef INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET
+$(eval $(call check-and-set-avb-args,vendor_kernel_boot))
+endif
+
+ifdef INSTALLED_SYSTEMIMAGE_TARGET
 $(eval $(call check-and-set-avb-args,system))
+endif
 
 ifdef INSTALLED_VENDORIMAGE_TARGET
 $(eval $(call check-and-set-avb-args,vendor))
@@ -3743,6 +4383,10 @@
 $(eval $(call check-and-set-avb-args,odm_dlkm))
 endif
 
+ifdef INSTALLED_SYSTEM_DLKMIMAGE_TARGET
+$(eval $(call check-and-set-avb-args,system_dlkm))
+endif
+
 ifdef INSTALLED_DTBOIMAGE_TARGET
 $(eval $(call check-and-set-avb-args,dtbo))
 endif
@@ -3809,9 +4453,15 @@
   $(if $(BOARD_AVB_BOOT_KEY_PATH),\
     $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_BOOT_KEY_PATH) \
       --output $(1)/boot.avbpubkey)
+  $(if $(BOARD_AVB_INIT_BOOT_KEY_PATH),\
+    $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_INIT_BOOT_KEY_PATH) \
+      --output $(1)/init_boot.avbpubkey)
   $(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),\
     $(AVBTOOL) extract_public_key --key $(BOARD_AVB_VENDOR_BOOT_KEY_PATH) \
       --output $(1)/vendor_boot.avbpubkey)
+  $(if $(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH),\
+    $(AVBTOOL) extract_public_key --key $(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH) \
+      --output $(1)/vendor_kernel_boot.avbpubkey)
   $(if $(BOARD_AVB_SYSTEM_KEY_PATH),\
     $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_KEY_PATH) \
       --output $(1)/system.avbpubkey)
@@ -3833,6 +4483,9 @@
   $(if $(BOARD_AVB_ODM_DLKM_KEY_PATH),\
     $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_ODM_DLKM_KEY_PATH) \
       --output $(1)/odm_dlkm.avbpubkey)
+  $(if $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH),\
+    $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH) \
+      --output $(1)/system_dlkm.avbpubkey)
   $(if $(BOARD_AVB_DTBO_KEY_PATH),\
     $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_DTBO_KEY_PATH) \
       --output $(1)/dtbo.avbpubkey)
@@ -3880,6 +4533,10 @@
 	    $(call images-for-partitions,$(BOARD_AVB_VBMETA_SYSTEM)) \
 	    $(BOARD_AVB_VBMETA_SYSTEM_KEY_PATH)
 	$(call build-chained-vbmeta-image,vbmeta_system)
+
+$(call declare-1p-container,$(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET),)
+
+SYSTEM_NOTICE_DEPS += $(INSTALLED_VBMETA_SYSTEMIMAGE_TARGET)
 endif
 endif # BUILDING_SYSTEM_IMAGE
 
@@ -3890,6 +4547,10 @@
 	    $(call images-for-partitions,$(BOARD_AVB_VBMETA_VENDOR)) \
 	    $(BOARD_AVB_VBMETA_VENDOR_KEY_PATH)
 	$(call build-chained-vbmeta-image,vbmeta_vendor)
+
+$(call declare-1p-container,$(INSTALLED_VBMETA_VENDORIMAGE_TARGET),)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_VBMETA_VENDORIMAGE_TARGET)
 endif
 
 define build-vbmetaimage-target
@@ -3912,7 +4573,9 @@
 $(INSTALLED_VBMETAIMAGE_TARGET): \
 	    $(AVBTOOL) \
 	    $(INSTALLED_BOOTIMAGE_TARGET) \
+	    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
 	    $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
+	    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) \
 	    $(INSTALLED_SYSTEMIMAGE_TARGET) \
 	    $(INSTALLED_VENDORIMAGE_TARGET) \
 	    $(INSTALLED_PRODUCTIMAGE_TARGET) \
@@ -3920,6 +4583,7 @@
 	    $(INSTALLED_ODMIMAGE_TARGET) \
 	    $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
 	    $(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+	    $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
 	    $(INSTALLED_DTBOIMAGE_TARGET) \
 	    $(INSTALLED_PVMFWIMAGE_TARGET) \
 	    $(INSTALLED_CUSTOMIMAGES_TARGET) \
@@ -3931,6 +4595,10 @@
 	    $(BOARD_AVB_KEY_PATH)
 	$(build-vbmetaimage-target)
 
+$(call declare-1p-container,$(INSTALLED_VBMETAIMAGE_TARGET),)
+
+UNMOUNTED_NOTICE_DEPS += $(INSTALLED_VBMETAIMAGE_TARGET)
+
 .PHONY: vbmetaimage-nodeps
 vbmetaimage-nodeps: PRIVATE_AVB_VBMETA_SIGNING_ARGS := \
     --algorithm $(BOARD_AVB_ALGORITHM) --key $(BOARD_AVB_KEY_PATH)
@@ -3951,11 +4619,12 @@
     $(INTERNAL_ODMIMAGE_FILES) \
     $(INTERNAL_VENDOR_DLKMIMAGE_FILES) \
     $(INTERNAL_ODM_DLKMIMAGE_FILES) \
+    $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) \
 
 # -----------------------------------------------------------------
 # Check VINTF of build
 
-# Note: vendor_dlkm and odm_dlkm does not have VINTF files.
+# Note: vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
 ifeq (,$(TARGET_BUILD_UNBUNDLED))
 
 intermediates := $(call intermediates-dir-for,PACKAGING,check_vintf_all)
@@ -3992,6 +4661,7 @@
 check_vintf_all_deps += $(check_vintf_system_log)
 $(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps)
 	@( $< --check-one --dirmap /system:$(TARGET_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
+$(call declare-0p-target,$(check_vintf_system_log))
 check_vintf_system_log :=
 
 vintffm_log := $(intermediates)/vintffm.log
@@ -4000,6 +4670,8 @@
 	@( $< --check --dirmap /system:$(TARGET_OUT) \
 	  $(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
 
+$(call declare-0p-target,$(vintffm_log))
+
 endif # check_vintf_system_deps
 check_vintf_system_deps :=
 
@@ -4021,6 +4693,7 @@
 	  ( $< --check-one --dirmap /vendor:$(TARGET_OUT_VENDOR) \
 	       --property ro.boot.product.vendor.sku=$(filter-out EMPTY_VENDOR_SKU_PLACEHOLDER,$(vendor_sku)) \
 	       > $@ 2>&1 ) || ( cat $@ && exit 1 ); )
+$(call declare-0p-target,$(check_vintf_vendor_log))
 check_vintf_vendor_log :=
 endif # check_vintf_vendor_deps
 check_vintf_vendor_deps :=
@@ -4042,6 +4715,9 @@
 $(BUILT_KERNEL_VERSION_FILE):
 	echo $(BOARD_KERNEL_VERSION) > $@
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+$(call declare-0p-target,$(BUILT_KERNEL_VERSION_FILE))
+
 my_board_extracted_kernel := true
 endif # BOARD_KERNEL_VERSION
 endif # BOARD_KERNEL_CONFIG_FILE
@@ -4065,6 +4741,8 @@
 	  --output-configs $@ \
 	  --output-release $(BUILT_KERNEL_VERSION_FILE)
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+
 my_board_extracted_kernel := true
 endif # INSTALLED_KERNEL_TARGET
 endif # my_board_extracted_kernel
@@ -4084,6 +4762,8 @@
 	  --output-configs $@ \
 	  --output-release $(BUILT_KERNEL_VERSION_FILE)
 
+$(call declare-0p-target,$(BUILT_KERNEL_CONFIGS_FILE))
+
 my_board_extracted_kernel := true
 endif # INSTALLED_BOOTIMAGE_TARGET
 endif # my_board_extracted_kernel
@@ -4173,6 +4853,8 @@
 	       --property ro.boot.product.vendor.sku=$(filter-out EMPTY_VENDOR_SKU_PLACEHOLDER,$(vendor_sku)) \
 	       >> $@ 2>&1 ) || (cat $@ && exit 1); ))
 
+$(call declare-0p-target,$(check_vintf_compatible_log))
+
 check_vintf_compatible_log :=
 check_vintf_compatible_args :=
 check_vintf_compatible_deps :=
@@ -4236,6 +4918,8 @@
 	  $(call intermediates-dir-for,PACKAGING,check-all-partition-sizes)/misc_info.txt, \
 	  $@)
 
+$(call declare-0p-target,$(check_all_partition_sizes_log))
+
 .PHONY: check-all-partition-sizes
 check-all-partition-sizes: $(check_all_partition_sizes_log)
 
@@ -4305,7 +4989,6 @@
 INTERNAL_OTATOOLS_MODULES := \
   aapt2 \
   add_img_to_target_files \
-  aftltool \
   apksigner \
   append2simg \
   avbtool \
@@ -4328,8 +5011,10 @@
   e2fsdroid \
   fc_sort \
   fec \
+  fsck.erofs \
   fsck.f2fs \
   fs_config \
+  generate_gki_certificate \
   generate_verity_key \
   host_init_verifier \
   img2simg \
@@ -4349,11 +5034,11 @@
   mke2fs \
   mke2fs.conf \
   mkfs.erofs \
-  mkerofsimage.sh \
   mkf2fsuserimg.sh \
   mksquashfs \
   mksquashfsimage.sh \
   mkuserimg_mke2fs \
+  ota_extractor \
   ota_from_target_files \
   repack_bootimg \
   secilc \
@@ -4362,6 +5047,7 @@
   shflags \
   sign_apex \
   sign_target_files_apks \
+  sign_virt_apex \
   signapk \
   simg2img \
   sload_f2fs \
@@ -4373,6 +5059,7 @@
   verity_signer \
   verity_verifier \
   zipalign \
+  zucchini \
 
 # Additional tools to unpack and repack the apex file.
 INTERNAL_OTATOOLS_MODULES += \
@@ -4442,6 +5129,9 @@
 	cp $(SOONG_ZIP) $(ZIP2ZIP) $(MERGE_ZIPS) $(PRIVATE_ZIP_ROOT)/bin/
 	$(SOONG_ZIP) -o $@ -C $(PRIVATE_ZIP_ROOT) -D $(PRIVATE_ZIP_ROOT)
 
+$(call declare-1p-container,$(BUILT_OTATOOLS_PACKAGE),build)
+$(call declare-container-license-deps,$(INTERNAL_OTATOOLS_PACKAGE_FILES) $(INTERNAL_OTATOOLS_RELEASETOOLS),$(BUILT_OTATOOLS_PACKAGE):)
+
 .PHONY: otatools-package
 otatools-package: $(BUILT_OTATOOLS_PACKAGE)
 
@@ -4482,6 +5172,10 @@
 else
 	echo "boot_images=$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET),$(notdir $(b)))" >> $@
 endif
+ifneq ($(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+	$(hide) echo "init_boot=true" >> $@
+	$(hide) echo "init_boot_size=$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)" >> $@
+endif
 ifeq ($(BOARD_RAMDISK_USE_LZ4),true)
 	echo "lz4_ramdisks=true" >> $@
 endif
@@ -4489,6 +5183,10 @@
 	echo "vendor_boot=true" >> $@
 	echo "vendor_boot_size=$(BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE)" >> $@
 endif
+ifneq ($(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
+	echo "vendor_kernel_boot=true" >> $@
+	echo "vendor_kernel_boot_size=$(BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE)" >> $@
+endif
 ifeq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) echo "no_recovery=true" >> $@
 endif
@@ -4509,17 +5207,19 @@
 endif
 	$(hide) echo "tool_extensions=$(tool_extensions)" >> $@
 	$(hide) echo "default_system_dev_certificate=$(DEFAULT_SYSTEM_DEV_CERTIFICATE)" >> $@
+ifdef PRODUCT_EXTRA_OTA_KEYS
+	$(hide) echo "extra_ota_keys=$(PRODUCT_EXTRA_OTA_KEYS)" >> $@
+endif
 ifdef PRODUCT_EXTRA_RECOVERY_KEYS
 	$(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $@
 endif
 	$(hide) echo 'mkbootimg_args=$(BOARD_MKBOOTIMG_ARGS)' >> $@
 	$(hide) echo 'recovery_mkbootimg_args=$(BOARD_RECOVERY_MKBOOTIMG_ARGS)' >> $@
 	$(hide) echo 'mkbootimg_version_args=$(INTERNAL_MKBOOTIMG_VERSION_ARGS)' >> $@
+	$(hide) echo 'mkbootimg_init_args=$(BOARD_MKBOOTIMG_INIT_ARGS)' >> $@
 ifdef BOARD_GKI_SIGNING_KEY_PATH
 	$(hide) echo 'gki_signing_key_path=$(BOARD_GKI_SIGNING_KEY_PATH)' >> $@
 	$(hide) echo 'gki_signing_algorithm=$(BOARD_GKI_SIGNING_ALGORITHM)' >> $@
-endif
-ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
 	$(hide) echo 'gki_signing_signature_args=$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)' >> $@
 endif
 	$(hide) echo "multistage_support=1" >> $@
@@ -4555,12 +5255,24 @@
 	$(hide) echo "avb_boot_algorithm=$(BOARD_AVB_BOOT_ALGORITHM)" >> $@
 	$(hide) echo "avb_boot_rollback_index_location=$(BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
 endif # BOARD_AVB_BOOT_KEY_PATH
+	$(hide) echo "avb_init_boot_add_hash_footer_args=$(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
+ifdef BOARD_AVB_INIT_BOOT_KEY_PATH
+	$(hide) echo "avb_init_boot_key_path=$(BOARD_AVB_INIT_BOOT_KEY_PATH)" >> $@
+	$(hide) echo "avb_init_boot_algorithm=$(BOARD_AVB_INIT_BOOT_ALGORITHM)" >> $@
+	$(hide) echo "avb_init_boot_rollback_index_location=$(BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
+endif # BOARD_AVB_INIT_BOOT_KEY_PATH
 	echo "avb_vendor_boot_add_hash_footer_args=$(BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
 ifdef BOARD_AVB_VENDOR_BOOT_KEY_PATH
 	echo "avb_vendor_boot_key_path=$(BOARD_AVB_VENDOR_BOOT_KEY_PATH)" >> $@
 	echo "avb_vendor_boot_algorithm=$(BOARD_AVB_VENDOR_BOOT_ALGORITHM)" >> $@
 	echo "avb_vendor_boot_rollback_index_location=$(BOARD_AVB_VENDOR_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
 endif # BOARD_AVB_VENDOR_BOOT_KEY_PATH
+	echo "avb_vendor_kernel_boot_add_hash_footer_args=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
+ifdef BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
+	echo "avb_vendor_kernel_boot_key_path=$(BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH)" >> $@
+	echo "avb_vendor_kernel_boot_algorithm=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ALGORITHM)" >> $@
+	echo "avb_vendor_kernel_boot_rollback_index_location=$(BOARD_AVB_VENDOR_KERNEL_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
+endif # BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
 	$(hide) echo "avb_recovery_add_hash_footer_args=$(BOARD_AVB_RECOVERY_ADD_HASH_FOOTER_ARGS)" >> $@
 ifdef BOARD_AVB_RECOVERY_KEY_PATH
 	$(hide) echo "avb_recovery_key_path=$(BOARD_AVB_RECOVERY_KEY_PATH)" >> $@
@@ -4621,10 +5333,10 @@
 endif # BOARD_AVB_DTBO_KEY_PATH
 endif # BOARD_AVB_ENABLE
 endif # BOARD_PREBUILT_DTBOIMAGE
-ifdef BOARD_PREBUILT_PVMFWIMAGE
+ifeq ($(BOARD_USES_PVMFWIMAGE),true)
 	$(hide) echo "has_pvmfw=true" >> $@
 ifeq ($(BOARD_AVB_ENABLE),true)
-	$(hide) echo "pvmfw_size=$(BOARD_PVMFWIMG_PARTITION_SIZE)" >> $@
+	$(hide) echo "pvmfw_size=$(BOARD_PVMFWIMAGE_PARTITION_SIZE)" >> $@
 	$(hide) echo "avb_pvmfw_add_hash_footer_args=$(BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS)" >> $@
 ifdef BOARD_AVB_PVMFW_KEY_PATH
 	$(hide) echo "avb_pvmfw_key_path=$(BOARD_AVB_PVMFW_KEY_PATH)" >> $@
@@ -4632,7 +5344,7 @@
 	$(hide) echo "avb_pvmfw_rollback_index_location=$(BOARD_AVB_PVMFW_ROLLBACK_INDEX_LOCATION)" >> $@
 endif # BOARD_AVB_PVMFW_KEY_PATH
 endif # BOARD_AVB_ENABLE
-endif # BOARD_PREBUILT_PVMFWIMAGE
+endif # BOARD_USES_PVMFWIMAGE
 	$(call dump-dynamic-partitions-info,$@)
 	@# VINTF checks
 ifeq ($(PRODUCT_ENFORCE_VINTF_MANIFEST),true)
@@ -4659,6 +5371,14 @@
 ifneq ($(BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST),)
 	$(hide) echo "partial_ota_update_partitions_list=$(BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST)" >> $@
 endif
+ifeq ($(BUILDING_WITH_VSDK),true)
+	$(hide) echo "building_with_vsdk=true" >> $@
+endif
+ifeq ($(TARGET_FLATTEN_APEX),false)
+	$(hide) echo "target_flatten_apex=false" >> $@
+endif
+
+$(call declare-0p-target,$(INSTALLED_MISC_INFO_TARGET))
 
 .PHONY: misc_info
 misc_info: $(INSTALLED_MISC_INFO_TARGET)
@@ -4705,13 +5425,18 @@
 tool_extension := $(wildcard $(tool_extensions)/releasetools.py)
 $(BUILT_TARGET_FILES_PACKAGE): PRIVATE_TOOL_EXTENSION := $(tool_extension)
 
+updaer_dep :=
 ifeq ($(AB_OTA_UPDATER),true)
-updater_dep := system/update_engine/update_engine.conf
+updater_dep += system/update_engine/update_engine.conf
+$(call declare-1p-target,system/update_engine/update_engine.conf,system/update_engine)
+updater_dep += external/zucchini/version_info.h
+$(call declare-license-metadata,external/zucchini/version_info.h,legacy_notice,notice,external/zucchini/LICENSE,external/zucchini)
+updater_dep += $(HOST_OUT_SHARED_LIBRARIES)/liblz4.so
 endif
 
 # Build OTA tools if non-A/B is allowed
 ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
-updater_dep := $(built_ota_tools)
+updater_dep += $(built_ota_tools)
 endif
 
 $(BUILT_TARGET_FILES_PACKAGE): $(updater_dep)
@@ -4765,13 +5490,17 @@
 define filter-out-missing-odm_dlkm
 $(if $(INSTALLED_ODM_DLKMIMAGE_TARGET),$(1),$(filter-out odm_dlkm,$(1)))
 endef
-# Filter out vendor,vendor_dlkm,odm,odm_dlkm from the list for AOSP targets.
+define filter-out-missing-system_dlkm
+$(if $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(1),$(filter-out system_dlkm,$(1)))
+endef
+# Filter out vendor,vendor_dlkm,odm,odm_dlkm,system_dlkm from the list for AOSP targets.
 # $(1): list
 define filter-out-missing-partitions
 $(call filter-out-missing-vendor,\
   $(call filter-out-missing-vendor_dlkm,\
     $(call filter-out-missing-odm,\
-      $(call filter-out-missing-odm_dlkm,$(1)))))
+      $(call filter-out-missing-odm_dlkm,\
+        $(call filter-out-missing-system_dlkm,$(1))))))
 endef
 
 # Information related to dynamic partitions and virtual A/B. This information
@@ -4821,6 +5550,11 @@
     echo "virtual_ab=true" >> $(1))
   $(if $(filter true,$(PRODUCT_VIRTUAL_AB_COMPRESSION)), \
     echo "virtual_ab_compression=true" >> $(1))
+# This value controls the compression algorithm used for VABC
+# valid options are defined in system/core/fs_mgr/libsnapshot/cow_writer.cpp
+# e.g. "none", "gz", "brotli"
+  $(if $(PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD), \
+    echo "virtual_ab_compression_method=$(PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD)" >> $(1))
   $(if $(filter true,$(PRODUCT_VIRTUAL_AB_OTA_RETROFIT)), \
     echo "virtual_ab_retrofit=true" >> $(1))
 endef
@@ -4830,6 +5564,10 @@
 # image.
 ifdef BUILDING_SYSTEM_IMAGE
   $(BUILT_TARGET_FILES_PACKAGE): $(FULL_SYSTEMIMAGE_DEPS)
+else
+  # releasetools may need the system build.prop even when building a
+  # system-image-less product.
+  $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BUILD_PROP_TARGET)
 endif
 
 ifdef BUILDING_USERDATA_IMAGE
@@ -4843,6 +5581,7 @@
 ifdef BUILDING_VENDOR_BOOT_IMAGE
   $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_VENDOR_RAMDISK_FILES)
   $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_VENDOR_RAMDISK_FRAGMENT_TARGETS)
+  $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_VENDOR_BOOTCONFIG_TARGET)
   # The vendor ramdisk may be built from the recovery ramdisk.
   ifeq (true,$(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT))
     $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_RECOVERY_RAMDISK_FILES_TIMESTAMP)
@@ -4884,9 +5623,10 @@
   $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_SYSTEM_EXTIMAGE_TARGET)
 endif
 
-ifdef BUILDING_BOOT_IMAGE
+ifneq (,$(BUILDING_BOOT_IMAGE)$(BUILDING_INIT_BOOT_IMAGE))
   $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_RAMDISK_FILES)
-endif
+endif  # BUILDING_BOOT_IMAGE != "" || BUILDING_INIT_BOOT_IMAGE != ""
+
 ifneq (,$(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES)))
   $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BOOTIMAGE_TARGET)
 endif
@@ -4909,6 +5649,12 @@
   $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_ODM_DLKMIMAGE_TARGET)
 endif
 
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+  $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_SYSTEM_DLKMIMAGE_FILES)
+else ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+  $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+endif
+
 ifeq ($(BUILD_QEMU_IMAGES),true)
   MK_VBMETA_BOOT_KERNEL_CMDLINE_SH := device/generic/goldfish/tools/mk_vbmeta_boot_params.sh
   $(BUILT_TARGET_FILES_PACKAGE): $(MK_VBMETA_BOOT_KERNEL_CMDLINE_SH)
@@ -4927,6 +5673,7 @@
 	    $(INSTALLED_CACHEIMAGE_TARGET) \
 	    $(INSTALLED_DTBOIMAGE_TARGET) \
 	    $(INSTALLED_PVMFWIMAGE_TARGET) \
+	    $(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET) \
 	    $(INSTALLED_CUSTOMIMAGES_TARGET) \
 	    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
 	    $(INSTALLED_KERNEL_TARGET) \
@@ -4943,6 +5690,7 @@
 	    $(PRODUCT_ODM_BASE_FS_PATH) \
 	    $(PRODUCT_VENDOR_DLKM_BASE_FS_PATH) \
 	    $(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
+	    $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
 	    $(LPMAKE) \
 	    $(SELINUX_FC) \
 	    $(INSTALLED_MISC_INFO_TARGET) \
@@ -5044,8 +5792,10 @@
 	$(call package_files-copy-root, \
 	    $(TARGET_VENDOR_RAMDISK_OUT),$(zip_root)/VENDOR_BOOT/RAMDISK)
 ifdef INSTALLED_DTBIMAGE_TARGET
+ifneq ($(BUILDING_VENDOR_KERNEL_BOOT_IMAGE),true)
 	cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/VENDOR_BOOT/dtb
 endif
+endif # end of INSTALLED_DTBIMAGE_TARGET
 ifdef INTERNAL_VENDOR_BOOTCONFIG_TARGET
 	cp $(INTERNAL_VENDOR_BOOTCONFIG_TARGET) $(zip_root)/VENDOR_BOOT/vendor_bootconfig
 endif
@@ -5070,10 +5820,27 @@
 	  ))
 endif # INTERNAL_VENDOR_RAMDISK_FRAGMENTS != ""
 endif # INSTALLED_VENDOR_BOOTIMAGE_TARGET
+ifdef INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET
+	mkdir -p $(zip_root)/VENDOR_KERNEL_BOOT
+	$(call package_files-copy-root, \
+	    $(TARGET_VENDOR_KERNEL_RAMDISK_OUT),$(zip_root)/VENDOR_KERNEL_BOOT/RAMDISK)
+ifdef INSTALLED_DTBIMAGE_TARGET
+	cp $(INSTALLED_DTBIMAGE_TARGET) $(zip_root)/VENDOR_KERNEL_BOOT/dtb
+endif
+ifdef BOARD_KERNEL_PAGESIZE
+	echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/VENDOR_KERNEL_BOOT/pagesize
+endif
+endif # INSTALLED_VENDOR_BOOTIMAGE_TARGET
 ifdef BUILDING_SYSTEM_IMAGE
 	@# Contents of the system image
 	$(hide) $(call package_files-copy-root, \
 	    $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
+else ifdef INSTALLED_BUILD_PROP_TARGET
+	@# Copy the system build.prop even if not building a system image
+	@# because add_img_to_target_files may need it to build other partition
+	@# images.
+	$(hide) mkdir -p "$(zip_root)/SYSTEM"
+	$(hide) cp "$(INSTALLED_BUILD_PROP_TARGET)" "$(patsubst $(TARGET_OUT)/%,$(zip_root)/SYSTEM/%,$(INSTALLED_BUILD_PROP_TARGET))"
 endif
 ifdef BUILDING_USERDATA_IMAGE
 	@# Contents of the data image
@@ -5110,6 +5877,11 @@
 	$(hide) $(call package_files-copy-root, \
 	    $(TARGET_OUT_ODM_DLKM),$(zip_root)/ODM_DLKM)
 endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+	@# Contents of the system_dlkm image
+	$(hide) $(call package_files-copy-root, \
+	    $(TARGET_OUT_SYSTEM_DLKM),$(zip_root)/SYSTEM_DLKM)
+endif
 ifdef BUILDING_SYSTEM_OTHER_IMAGE
 	@# Contents of the system_other image
 	$(hide) $(call package_files-copy-root, \
@@ -5170,6 +5942,10 @@
 	$(hide) cp $(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
 	  $(zip_root)/META/$(notdir $(PRODUCT_ODM_DLKM_BASE_FS_PATH))
 endif
+ifneq ($(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH),)
+	$(hide) cp $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
+	  $(zip_root)/META/$(notdir $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH))
+endif
 ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH MKBOOTIMG=$(MKBOOTIMG) \
@@ -5179,10 +5955,12 @@
 ifeq ($(AB_OTA_UPDATER),true)
 	@# When using the A/B updater, include the updater config files in the zip.
 	$(hide) cp $(TOPDIR)system/update_engine/update_engine.conf $(zip_root)/META/update_engine_config.txt
-	$(hide) for part in $(AB_OTA_PARTITIONS); do \
+	$(hide) cp $(TOPDIR)external/zucchini/version_info.h $(zip_root)/META/zucchini_config.txt
+	$(hide) cp $(HOST_OUT_SHARED_LIBRARIES)/liblz4.so $(zip_root)/META/liblz4.so
+	$(hide) for part in $(sort $(AB_OTA_PARTITIONS)); do \
 	  echo "$${part}" >> $(zip_root)/META/ab_partitions.txt; \
 	done
-	$(hide) for conf in $(AB_OTA_POSTINSTALL_CONFIG); do \
+	$(hide) for conf in $(strip $(AB_OTA_POSTINSTALL_CONFIG)); do \
 	  echo "$${conf}" >> $(zip_root)/META/postinstall_config.txt; \
 	done
 ifdef OSRELEASED_DIRECTORY
@@ -5207,8 +5985,13 @@
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) $(zip_root)/IMAGES/
 endif
+ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+	$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
+	$(hide) cp $(INSTALLED_INIT_BOOT_IMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
+endif
+
 ifndef BOARD_PREBUILT_BOOTIMAGE
-ifneq (,$(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES)))
+ifneq (,$(strip $(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES))))
 ifdef INSTALLED_BOOTIMAGE_TARGET
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/IMAGES/
@@ -5230,14 +6013,19 @@
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_ODM_DLKMIMAGE_TARGET) $(zip_root)/IMAGES/
 endif
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+	$(hide) mkdir -p $(zip_root)/IMAGES
+	$(hide) cp $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(zip_root)/IMAGES/
+endif
 ifdef BOARD_PREBUILT_DTBOIMAGE
 	$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
 	$(hide) cp $(INSTALLED_DTBOIMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
 endif # BOARD_PREBUILT_DTBOIMAGE
-ifdef BOARD_PREBUILT_PVMFWIMAGE
+ifeq ($(BOARD_USES_PVMFWIMAGE),true)
 	$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
 	$(hide) cp $(INSTALLED_PVMFWIMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
-endif # BOARD_PREBUILT_PVMFWIMAGE
+	$(hide) cp $(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET) $(zip_root)/PREBUILT_IMAGES/
+endif
 ifdef BOARD_PREBUILT_BOOTLOADER
 	$(hide) mkdir -p $(zip_root)/IMAGES
 	$(hide) cp $(INSTALLED_BOOTLOADER_MODULE) $(zip_root)/IMAGES/
@@ -5275,12 +6063,22 @@
 ifdef BUILDING_ODM_DLKM_IMAGE
 	$(hide) $(call fs_config,$(zip_root)/ODM_DLKM,odm_dlkm/) > $(zip_root)/META/odm_dlkm_filesystem_config.txt
 endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+	$(hide) $(call fs_config,$(zip_root)/SYSTEM_DLKM,system_dlkm/) > $(zip_root)/META/system_dlkm_filesystem_config.txt
+endif
 	@# ROOT always contains the files for the root under normal boot.
 	$(hide) $(call fs_config,$(zip_root)/ROOT,) > $(zip_root)/META/root_filesystem_config.txt
 ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
 	@# BOOT/RAMDISK exists and contains the ramdisk for recovery if using BOARD_USES_RECOVERY_AS_BOOT.
 	$(hide) $(call fs_config,$(zip_root)/BOOT/RAMDISK,) > $(zip_root)/META/boot_filesystem_config.txt
 endif
+ifdef BUILDING_INIT_BOOT_IMAGE
+	$(hide) $(call package_files-copy-root, $(TARGET_RAMDISK_OUT),$(zip_root)/INIT_BOOT/RAMDISK)
+	$(hide) $(call fs_config,$(zip_root)/INIT_BOOT/RAMDISK,) > $(zip_root)/META/init_boot_filesystem_config.txt
+ifdef BOARD_KERNEL_PAGESIZE
+	$(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/INIT_BOOT/pagesize
+endif # BOARD_KERNEL_PAGESIZE
+endif # BUILDING_INIT_BOOT_IMAGE
 ifneq ($(INSTALLED_VENDOR_BOOTIMAGE_TARGET),)
 	$(call fs_config,$(zip_root)/VENDOR_BOOT/RAMDISK,) > $(zip_root)/META/vendor_boot_filesystem_config.txt
 endif
@@ -5320,6 +6118,41 @@
 .PHONY: target-files-package
 target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
 
+$(call declare-1p-container,$(BUILT_TARGET_FILES_PACKAGE),)
+$(call declare-container-license-deps,$(BUILT_TARGET_FILES_PACKAGE), $(INSTALLED_RADIOIMAGE_TARGET) \
+            $(INSTALLED_RECOVERYIMAGE_TARGET) \
+            $(INSTALLED_CACHEIMAGE_TARGET) \
+            $(INSTALLED_DTBOIMAGE_TARGET) \
+            $(INSTALLED_PVMFWIMAGE_TARGET) \
+            $(INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET) \
+            $(INSTALLED_CUSTOMIMAGES_TARGET) \
+            $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
+            $(INSTALLED_KERNEL_TARGET) \
+            $(INSTALLED_RAMDISK_TARGET) \
+            $(INSTALLED_DTBIMAGE_TARGET) \
+            $(INSTALLED_2NDBOOTLOADER_TARGET) \
+            $(BOARD_PREBUILT_DTBOIMAGE) \
+            $(BOARD_PREBUILT_RECOVERY_DTBOIMAGE) \
+            $(BOARD_RECOVERY_ACPIO) \
+            $(PRODUCT_SYSTEM_BASE_FS_PATH) \
+            $(PRODUCT_VENDOR_BASE_FS_PATH) \
+            $(PRODUCT_PRODUCT_BASE_FS_PATH) \
+            $(PRODUCT_SYSTEM_EXT_BASE_FS_PATH) \
+            $(PRODUCT_ODM_BASE_FS_PATH) \
+            $(PRODUCT_VENDOR_DLKM_BASE_FS_PATH) \
+            $(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
+            $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
+            $(LPMAKE) \
+            $(SELINUX_FC) \
+            $(INSTALLED_MISC_INFO_TARGET) \
+            $(APKCERTS_FILE) \
+            $(SOONG_APEX_KEYS_FILE) \
+            $(HOST_OUT_EXECUTABLES)/fs_config \
+            $(ADD_IMG_TO_TARGET_FILES) \
+            $(MAKE_RECOVERY_PATCH) \
+            $(BUILT_KERNEL_CONFIGS_FILE) \
+            $(BUILT_KERNEL_VERSION_FILE),$(BUILT_TARGET_FILES_PACKAGE):)
+
 $(call dist-for-goals, target-files-package, $(BUILT_TARGET_FILES_PACKAGE))
 
 # -----------------------------------------------------------------
@@ -5329,7 +6162,9 @@
 	@echo Package NDK sysroot...
 	$(hide) tar cjf $@ -C $(SOONG_OUT_DIR) ndk
 
+ifeq ($(HOST_OS),linux)
 $(call dist-for-goals,sdk,$(NDK_SYSROOT_TARGET))
+endif
 
 ifeq ($(build_ota_package),true)
 # -----------------------------------------------------------------
@@ -5357,12 +6192,17 @@
 INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
 INTERNAL_OTA_METADATA := $(PRODUCT_OUT)/ota_metadata
 
+$(call declare-0p-target,$(INTERNAL_OTA_METADATA))
+
 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
 $(INTERNAL_OTA_PACKAGE_TARGET): .KATI_IMPLICIT_OUTPUTS := $(INTERNAL_OTA_METADATA)
 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES)
 	@echo "Package OTA: $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --output_metadata_path $(INTERNAL_OTA_METADATA))
 
+$(call declare-1p-container,$(INTERNAL_OTA_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: otapackage
 otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
 
@@ -5378,6 +6218,9 @@
 	@echo "Package OTA (retrofit dynamic partitions): $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --retrofit_dynamic_partitions)
 
+$(call declare-1p-container,$(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: otardppackage
 
 otapackage otardppackage: $(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET)
@@ -5393,6 +6236,10 @@
 	@echo "Package partial OTA: $@"
 	$(call build-ota-package-target,$@,-k $(KEY_CERT_PAIR) --partial "$(BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST)")
 
+$(call declare-1p-container,$(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(OTA_FROM_TARGET_FILES) $(INTERNAL_OTATOOLS_FILES),$(PRODUCT_OUT)/:/)
+
+
 .PHONY: partialotapackage
 partialotapackage: $(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET)
 
@@ -5424,57 +6271,90 @@
 ifeq ($(BUILD_OS),linux)
 ifneq ($(DEX2OAT),)
 dexpreopt_tools_deps := $(DEXPREOPT_GEN_DEPS) $(DEXPREOPT_GEN) $(AAPT2)
+dexpreopt_tools_deps += $(HOST_OUT_EXECUTABLES)/dexdump
+dexpreopt_tools_deps += $(HOST_OUT_EXECUTABLES)/oatdump
 DEXPREOPT_TOOLS_ZIP := $(PRODUCT_OUT)/dexpreopt_tools.zip
 $(DEXPREOPT_TOOLS_ZIP): $(dexpreopt_tools_deps)
 $(DEXPREOPT_TOOLS_ZIP): PRIVATE_DEXPREOPT_TOOLS_DEPS := $(dexpreopt_tools_deps)
 $(DEXPREOPT_TOOLS_ZIP): $(SOONG_ZIP)
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(SOONG_ZIP) -d -o $@ -j $(addprefix -f ,$(PRIVATE_DEXPREOPT_TOOLS_DEPS)) -f $$(realpath $(DEX2OAT))
+$(call declare-1p-target,$(DEXPREOPT_TOOLS_ZIP),)
 endif # DEX2OAT is set
 endif # BUILD_OS == linux
 
 DEXPREOPT_CONFIG_ZIP := $(PRODUCT_OUT)/dexpreopt_config.zip
-$(DEXPREOPT_CONFIG_ZIP): $(FULL_SYSTEMIMAGE_DEPS) \
-	    $(INTERNAL_RAMDISK_FILES) \
-	    $(INTERNAL_USERDATAIMAGE_FILES) \
-	    $(INTERNAL_VENDORIMAGE_FILES) \
-	    $(INTERNAL_PRODUCTIMAGE_FILES) \
-	    $(INTERNAL_SYSTEM_EXTIMAGE_FILES) \
-	    $(DEX_PREOPT_CONFIG_FOR_MAKE) \
-	    $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE)
+
+$(DEXPREOPT_CONFIG_ZIP): $(INSTALLED_SYSTEMIMAGE_TARGET) \
+    $(INSTALLED_VENDORIMAGE_TARGET) \
+    $(INSTALLED_ODMIMAGE_TARGET) \
+    $(INSTALLED_PRODUCTIMAGE_TARGET) \
+
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(DEXPREOPT_CONFIG_ZIP): $(DEX_PREOPT_CONFIG_FOR_MAKE) \
+	  $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) \
+
+endif
 
 $(DEXPREOPT_CONFIG_ZIP): $(SOONG_ZIP)
 	$(hide) mkdir -p $(dir $@) $(PRODUCT_OUT)/dexpreopt_config
+
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
 ifneq (,$(DEX_PREOPT_CONFIG_FOR_MAKE))
 	$(hide) cp $(DEX_PREOPT_CONFIG_FOR_MAKE) $(PRODUCT_OUT)/dexpreopt_config
 endif
 ifneq (,$(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE))
 	$(hide) cp $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(PRODUCT_OUT)/dexpreopt_config
 endif
+endif #!TARGET_BUILD_UNBUNDLED
 	$(hide) $(SOONG_ZIP) -d -o $@ -C $(PRODUCT_OUT)/dexpreopt_config -D $(PRODUCT_OUT)/dexpreopt_config
 
+.PHONY: dexpreopt_config_zip
+dexpreopt_config_zip: $(DEXPREOPT_CONFIG_ZIP)
+
+$(call declare-1p-target,$(DEXPREOPT_CONFIG_ZIP),)
+
 # -----------------------------------------------------------------
 # A zip of the symbols directory.  Keep the full paths to make it
 # more obvious where these files came from.
+# Also produces a textproto containing mappings from elf IDs to symbols
+# filename, which will allow finding the appropriate symbols to deobfuscate
+# a stack trace frame.
 #
+
 name := $(TARGET_PRODUCT)
 ifeq ($(TARGET_BUILD_TYPE),debug)
   name := $(name)_debug
 endif
-name := $(name)-symbols-$(FILE_NAME_TAG)
 
-SYMBOLS_ZIP := $(PRODUCT_OUT)/$(name).zip
+# The path to the zip file containing binaries with symbols.
+SYMBOLS_ZIP := $(PRODUCT_OUT)/$(name)-symbols-$(FILE_NAME_TAG).zip
+# The path to a file containing mappings from elf IDs to filenames.
+SYMBOLS_MAPPING := $(PRODUCT_OUT)/$(name)-symbols-mapping-$(FILE_NAME_TAG).textproto
+.KATI_READONLY := SYMBOLS_ZIP SYMBOLS_MAPPING
 # For apps_only build we'll establish the dependency later in build/make/core/main.mk.
 ifeq (,$(TARGET_BUILD_UNBUNDLED))
 $(SYMBOLS_ZIP): $(INTERNAL_ALLIMAGES_FILES) $(updater_dep)
 endif
 $(SYMBOLS_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,symbols)/filelist
-$(SYMBOLS_ZIP): $(SOONG_ZIP)
+$(SYMBOLS_ZIP): PRIVATE_MAPPING_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,elf_symbol_mapping)
+$(SYMBOLS_ZIP): $(SOONG_ZIP) $(SYMBOLS_MAP)
 	@echo "Package symbols: $@"
 	$(hide) rm -rf $@ $(PRIVATE_LIST_FILE)
-	$(hide) mkdir -p $(dir $@) $(TARGET_OUT_UNSTRIPPED) $(dir $(PRIVATE_LIST_FILE))
+	$(hide) mkdir -p $(TARGET_OUT_UNSTRIPPED) $(dir $(PRIVATE_LIST_FILE)) $(PRIVATE_MAPPING_PACKAGING_DIR)
+	# Find all of the files in the symbols directory and zip them into the symbols zip.
 	$(hide) find -L $(TARGET_OUT_UNSTRIPPED) -type f | sort >$(PRIVATE_LIST_FILE)
 	$(hide) $(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(OUT_DIR)/.. -l $(PRIVATE_LIST_FILE)
+	# Find all of the files in the symbols mapping directory and merge them into the symbols mapping textproto.
+	$(hide) find -L $(PRIVATE_MAPPING_PACKAGING_DIR) -type f | sort >$(PRIVATE_LIST_FILE)
+	$(hide) $(SYMBOLS_MAP) -merge $(SYMBOLS_MAPPING) -ignore_missing_files @$(PRIVATE_LIST_FILE)
+$(SYMBOLS_ZIP): .KATI_IMPLICIT_OUTPUTS := $(SYMBOLS_MAPPING)
+
+$(call declare-1p-container,$(SYMBOLS_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(SYMBOLS_ZIP),$(INTERNAL_ALLIMAGES_FILES) $(updater_dep),$(PRODUCT_OUT)/:/)
+endif
+
 # -----------------------------------------------------------------
 # A zip of the coverage directory.
 #
@@ -5494,17 +6374,27 @@
 	$(hide) find $(TARGET_OUT_COVERAGE) | sort >$(PRIVATE_LIST_FILE)
 	$(hide) $(SOONG_ZIP) -d -o $@ -C $(TARGET_OUT_COVERAGE) -l $(PRIVATE_LIST_FILE)
 
+$(call declare-1p-container,$(COVERAGE_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(COVERAGE_ZIP),$(INTERNAL_ALLIMAGE_FILES),$(PRODUCT_OUT)/:/)
+endif
+
+SYSTEM_NOTICE_DEPS += $(COVERAGE_ZIP)
+
 #------------------------------------------------------------------
 # Export the LLVM profile data tool and dependencies for Clang coverage processing
 #
 ifeq (true,$(CLANG_COVERAGE))
   LLVM_PROFDATA := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-profdata
+  LLVM_COV := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/bin/llvm-cov
   LIBCXX := $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION)/lib64/libc++.so.1
-  PROFDATA_ZIP := $(PRODUCT_OUT)/llvm-profdata.zip
-  $(PROFDATA_ZIP): $(SOONG_ZIP)
-	$(hide) $(SOONG_ZIP) -d -o $@ -C $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION) -f $(LLVM_PROFDATA) -f $(LIBCXX)
+  # Use llvm-profdata.zip for backwards compatibility with tradefed code.
+  LLVM_COVERAGE_TOOLS_ZIP := $(PRODUCT_OUT)/llvm-profdata.zip
 
-  $(call dist-for-goals,droidcore-unbundled apps_only,$(PROFDATA_ZIP))
+  $(LLVM_COVERAGE_TOOLS_ZIP): $(SOONG_ZIP)
+	$(hide) $(SOONG_ZIP) -d -o $@ -C $(LLVM_PREBUILTS_BASE)/linux-x86/$(LLVM_PREBUILTS_VERSION) -f $(LLVM_PROFDATA) -f $(LIBCXX) -f $(LLVM_COV)
+
+  $(call dist-for-goals,droidcore-unbundled apps_only,$(LLVM_COVERAGE_TOOLS_ZIP))
 endif
 
 # -----------------------------------------------------------------
@@ -5559,17 +6449,40 @@
 
 #------------------------------------------------------------------
 # A zip of Proguard obfuscation dictionary files.
+# Also produces a textproto containing mappings from the hashes of the
+# dictionary contents (which are also stored in the dex files on the
+# devices) to the filename of the proguard dictionary, which will allow
+# finding the appropriate dictionary to deobfuscate a stack trace frame.
 #
+
+# The path to the zip file containing proguard dictionaries.
 PROGUARD_DICT_ZIP := $(PRODUCT_OUT)/$(TARGET_PRODUCT)-proguard-dict-$(FILE_NAME_TAG).zip
+# The path to the zip file containing mappings from dictionary hashes to filenames.
+PROGUARD_DICT_MAPPING := $(PRODUCT_OUT)/$(TARGET_PRODUCT)-proguard-dict-mapping-$(FILE_NAME_TAG).textproto
+.KATI_READONLY := PROGUARD_DICT_ZIP PROGUARD_DICT_MAPPING
 # For apps_only build we'll establish the dependency later in build/make/core/main.mk.
 ifeq (,$(TARGET_BUILD_UNBUNDLED))
 $(PROGUARD_DICT_ZIP): $(INTERNAL_ALLIMAGES_FILES) $(updater_dep)
 endif
 $(PROGUARD_DICT_ZIP): PRIVATE_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,proguard_dictionary)
-$(PROGUARD_DICT_ZIP): $(SOONG_ZIP)
+$(PROGUARD_DICT_ZIP): PRIVATE_MAPPING_PACKAGING_DIR := $(call intermediates-dir-for,PACKAGING,proguard_dictionary_mapping)
+$(PROGUARD_DICT_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,proguard_dictionary_filelist)/filelist
+$(PROGUARD_DICT_ZIP): $(SOONG_ZIP) $(SYMBOLS_MAP)
 	@echo "Packaging Proguard obfuscation dictionary files."
-	mkdir -p $(dir $@) $(PRIVATE_PACKAGING_DIR)
-	$(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(PRIVATE_PACKAGING_DIR) -P out/target/common/obj -D $(PRIVATE_PACKAGING_DIR)
+	rm -rf $@ $(PRIVATE_LIST_FILE)
+	mkdir -p $(PRIVATE_PACKAGING_DIR) $(PRIVATE_MAPPING_PACKAGING_DIR) $(dir $(PRIVATE_LIST_FILE))
+	# Zip all of the files in the proguard dictionary directory.
+	$(SOONG_ZIP) --ignore_missing_files -d -o $@ -C $(PRIVATE_PACKAGING_DIR) -D $(PRIVATE_PACKAGING_DIR)
+	# Find all of the files in the proguard dictionary mapping directory and merge them into the mapping textproto.
+	# Strip the PRIVATE_PACKAGING_DIR off the filenames to match soong_zip's -C argument.
+	$(hide) find -L $(PRIVATE_MAPPING_PACKAGING_DIR) -type f | sort >$(PRIVATE_LIST_FILE)
+	$(SYMBOLS_MAP) -merge $(PROGUARD_DICT_MAPPING) -strip_prefix $(PRIVATE_PACKAGING_DIR)/ -ignore_missing_files @$(PRIVATE_LIST_FILE)
+$(PROGUARD_DICT_ZIP): .KATI_IMPLICIT_OUTPUTS := $(PROGUARD_DICT_MAPPING)
+
+$(call declare-1p-container,$(PROGUARD_DICT_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(PROGUARD_DICT_ZIP),$(INTERNAL_ALLIMAGES_FILES) $(updater_dep),$(PRODUCT_OUT)/:/)
+endif
 
 #------------------------------------------------------------------
 # A zip of Proguard usage files.
@@ -5581,6 +6494,7 @@
     $(INSTALLED_SYSTEMIMAGE_TARGET) \
     $(INSTALLED_RAMDISK_TARGET) \
     $(INSTALLED_BOOTIMAGE_TARGET) \
+    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
     $(INSTALLED_USERDATAIMAGE_TARGET) \
     $(INSTALLED_VENDORIMAGE_TARGET) \
     $(INSTALLED_PRODUCTIMAGE_TARGET) \
@@ -5588,6 +6502,7 @@
     $(INSTALLED_ODMIMAGE_TARGET) \
     $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
     $(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
     $(updater_dep)
 endif
 $(PROGUARD_USAGE_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,proguard_usage.zip)/filelist
@@ -5598,6 +6513,23 @@
 	find $(PRIVATE_PACKAGING_DIR) -name proguard_usage.zip > $(PRIVATE_LIST_FILE)
 	$(MERGE_ZIPS) $@ @$(PRIVATE_LIST_FILE)
 
+$(call declare-1p-container,$(PROGUARD_USAGE_ZIP),)
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+$(call declare-container-license-deps,$(PROGUARD_USAGE_ZIP),$(INSTALLED_SYSTEMIMAGE_TARGET) \
+    $(INSTALLED_RAMDISK_TARGET) \
+    $(INSTALLED_BOOTIMAGE_TARGET) \
+    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
+    $(INSTALLED_USERDATAIMAGE_TARGET) \
+    $(INSTALLED_VENDORIMAGE_TARGET) \
+    $(INSTALLED_PRODUCTIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) \
+    $(INSTALLED_ODMIMAGE_TARGET) \
+    $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
+    $(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
+    $(updater_dep),$(PROGUARD_USAGE_ZIP):/)
+endif
+
 ifeq (true,$(PRODUCT_USE_DYNAMIC_PARTITIONS))
 
 # Dump variables used by build_super_image.py (for building super.img and super_empty.img).
@@ -5720,6 +6652,8 @@
 
 $(call dist-for-goals,dist_files,$(INSTALLED_SUPERIMAGE_EMPTY_TARGET))
 
+$(call declare-0p-target,$(INSTALLED_SUPERIMAGE_EMPTY_TARGET))
+
 endif # BUILDING_SUPER_EMPTY_IMAGE
 
 
@@ -5741,6 +6675,9 @@
 	        --additional IMAGES/VerifiedBootParams.textproto:VerifiedBootParams.textproto \
 	        $(BUILT_TARGET_FILES_PACKAGE) $@
 
+$(call declare-1p-container,$(INTERNAL_UPDATE_PACKAGE_TARGET),)
+$(call declare-container-license-deps,$(INTERNAL_UPDATE_PACKAGE_TARGET),$(BUILT_TARGET_FILES_PACKAGE) $(IMG_FROM_TARGET_FILES),$(PRODUCT_OUT)/:/)
+
 .PHONY: updatepackage
 updatepackage: $(INTERNAL_UPDATE_PACKAGE_TARGET)
 $(call dist-for-goals,updatepackage,$(INTERNAL_UPDATE_PACKAGE_TARGET))
@@ -5842,6 +6779,16 @@
 droidcore-unbundled: $(INSTALLED_QEMU_ODM_DLKMIMAGE)
 endif
 
+ifdef INSTALLED_SYSTEM_DLKMIMAGE_TARGET
+INSTALLED_QEMU_SYSTEM_DLKMIMAGE := $(PRODUCT_OUT)/system_dlkm-qemu.img
+$(INSTALLED_QEMU_SYSTEM_DLKMIMAGE): $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(MK_QEMU_IMAGE_SH) $(SGDISK_HOST)
+	@echo Create system_dlkm-qemu.img
+	(export SGDISK=$(SGDISK_HOST); $(MK_QEMU_IMAGE_SH) $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET))
+
+system_dlkmimage: $(INSTALLED_QEMU_SYSTEM_DLKMIMAGE)
+droidcore-unbundled: $(INSTALLED_QEMU_SYSTEM_DLKMIMAGE)
+endif
+
 QEMU_VERIFIED_BOOT_PARAMS := $(PRODUCT_OUT)/VerifiedBootParams.textproto
 $(QEMU_VERIFIED_BOOT_PARAMS): $(INSTALLED_VBMETAIMAGE_TARGET) $(INSTALLED_SYSTEMIMAGE_TARGET) \
     $(MK_VBMETA_BOOT_KERNEL_CMDLINE_SH) $(AVBTOOL)
@@ -5858,7 +6805,6 @@
 ifeq ($(BUILD_EMULATOR),true)
 INTERNAL_EMULATOR_PACKAGE_FILES += \
         $(HOST_OUT_EXECUTABLES)/emulator$(HOST_EXECUTABLE_SUFFIX) \
-        prebuilts/qemu-kernel/$(TARGET_ARCH)/kernel-qemu \
         $(INSTALLED_RAMDISK_TARGET) \
         $(INSTALLED_SYSTEMIMAGE_TARGET) \
         $(INSTALLED_USERDATAIMAGE_TARGET)
@@ -5877,6 +6823,8 @@
 # -----------------------------------------------------------------
 # The SDK
 
+ifneq ($(filter sdk,$(MAKECMDGOALS)),)
+
 # The SDK includes host-specific components, so it belongs under HOST_OUT.
 sdk_dir := $(HOST_OUT)/sdk/$(TARGET_PRODUCT)
 
@@ -5886,15 +6834,11 @@
 #     darwin-x86  --> android-sdk_12345_mac-x86
 #     windows-x86 --> android-sdk_12345_windows
 #
+ifneq ($(HOST_OS),linux)
+  $(error Building the monolithic SDK is only supported on Linux)
+endif
 sdk_name := android-sdk_$(FILE_NAME_TAG)
-ifeq ($(HOST_OS),darwin)
-  INTERNAL_SDK_HOST_OS_NAME := mac
-else
-  INTERNAL_SDK_HOST_OS_NAME := $(HOST_OS)
-endif
-ifneq ($(HOST_OS),windows)
-  INTERNAL_SDK_HOST_OS_NAME := $(INTERNAL_SDK_HOST_OS_NAME)-$(SDK_HOST_ARCH)
-endif
+INTERNAL_SDK_HOST_OS_NAME := linux-$(SDK_HOST_ARCH)
 sdk_name := $(sdk_name)_$(INTERNAL_SDK_HOST_OS_NAME)
 
 sdk_dep_file := $(sdk_dir)/sdk_deps.mk
@@ -5905,8 +6849,6 @@
 # if we don't have a real list, then use "everything"
 ifeq ($(strip $(ATREE_FILES)),)
 ATREE_FILES := \
-	$(ALL_DEFAULT_INSTALLED_MODULES) \
-	$(INSTALLED_RAMDISK_TARGET) \
 	$(ALL_DOCS) \
 	$(ALL_SDK_FILES)
 endif
@@ -5914,9 +6856,7 @@
 atree_dir := development/build
 
 
-sdk_atree_files := \
-	$(atree_dir)/sdk.exclude.atree \
-	$(atree_dir)/sdk-$(HOST_OS)-$(SDK_HOST_ARCH).atree
+sdk_atree_files := $(atree_dir)/sdk.exclude.atree
 
 # development/build/sdk-android-<abi>.atree is used to differentiate
 # between architecture models (e.g. ARMv5TE versus ARMv7) when copying
@@ -5932,30 +6872,14 @@
 sdk_atree_files += $(atree_dir)/sdk.atree
 endif
 
-include $(BUILD_SYSTEM)/sdk_font.mk
-
 deps := \
-	$(target_notice_file_txt) \
-	$(tools_notice_file_txt) \
 	$(OUT_DOCS)/offline-sdk-timestamp \
 	$(SDK_METADATA_FILES) \
-	$(SYMBOLS_ZIP) \
-	$(COVERAGE_ZIP) \
-	$(APPCOMPAT_ZIP) \
-	$(INSTALLED_SYSTEMIMAGE_TARGET) \
-	$(INSTALLED_QEMU_SYSTEMIMAGE) \
-	$(INSTALLED_QEMU_RAMDISKIMAGE) \
-	$(INSTALLED_QEMU_VENDORIMAGE) \
-	$(QEMU_VERIFIED_BOOT_PARAMS) \
-	$(INSTALLED_USERDATAIMAGE_TARGET) \
-	$(INSTALLED_RAMDISK_TARGET) \
-	$(INSTALLED_SDK_BUILD_PROP_TARGET) \
-	$(INSTALLED_BUILD_PROP_TARGET) \
+  $(INSTALLED_SDK_BUILD_PROP_TARGET) \
 	$(ATREE_FILES) \
 	$(sdk_atree_files) \
 	$(HOST_OUT_EXECUTABLES)/atree \
-	$(HOST_OUT_EXECUTABLES)/line_endings \
-	$(SDK_FONT_DEPS)
+	$(HOST_OUT_EXECUTABLES)/line_endings
 
 INTERNAL_SDK_TARGET := $(sdk_dir)/$(sdk_name).zip
 $(INTERNAL_SDK_TARGET): PRIVATE_NAME := $(sdk_name)
@@ -5970,7 +6894,7 @@
 $(INTERNAL_SDK_TARGET): $(deps)
 	@echo "Package SDK: $@"
 	$(hide) rm -rf $(PRIVATE_DIR) $@
-	$(hide) for f in $(target_gnu_MODULES); do \
+	$(hide) for f in $(strip $(target_gnu_MODULES)); do \
 	  if [ -f $$f ]; then \
 	    echo SDK: $(if $(SDK_GNU_ERROR),ERROR:,warning:) \
 	        including GNU target $$f >&2; \
@@ -5978,7 +6902,6 @@
 	  fi; \
 	done; \
 	if [ $$FAIL ]; then exit 1; fi
-	$(hide) echo $(notdir $(SDK_FONT_DEPS)) | tr " " "\n"  > $(SDK_FONT_TEMP)/fontsInSdk.txt
 	$(hide) ( \
 	    ATREE_STRIP="$(HOST_STRIP) -x" \
 	    $(HOST_OUT_EXECUTABLES)/atree \
@@ -5994,26 +6917,17 @@
 	        -v "TARGET_ARCH=$(TARGET_ARCH)" \
 	        -v "TARGET_CPU_ABI=$(TARGET_CPU_ABI)" \
 	        -v "DLL_EXTENSION=$(HOST_SHLIB_SUFFIX)" \
-	        -v "FONT_OUT=$(SDK_FONT_TEMP)" \
 	        -o $(PRIVATE_DIR) && \
-	    cp -f $(target_notice_file_txt) \
-	            $(PRIVATE_DIR)/system-images/android-$(PLATFORM_VERSION)/$(TARGET_CPU_ABI)/NOTICE.txt && \
-	    cp -f $(tools_notice_file_txt) $(PRIVATE_DIR)/platform-tools/NOTICE.txt && \
 	    HOST_OUT_EXECUTABLES=$(HOST_OUT_EXECUTABLES) HOST_OS=$(HOST_OS) \
 	        development/build/tools/sdk_clean.sh $(PRIVATE_DIR) && \
 	    chmod -R ug+rwX $(PRIVATE_DIR) && \
 	    cd $(dir $@) && zip -rqX $(notdir $@) $(PRIVATE_NAME) \
 	) || ( rm -rf $(PRIVATE_DIR) $@ && exit 44 )
 
-
-# Is a Windows SDK requested? If so, we need some definitions from here
-# in order to find the Linux SDK used to create the Windows one.
-MAIN_SDK_NAME := $(sdk_name)
 MAIN_SDK_DIR  := $(sdk_dir)
 MAIN_SDK_ZIP  := $(INTERNAL_SDK_TARGET)
-ifneq ($(filter win_sdk winsdk-tools,$(MAKECMDGOALS)),)
-include $(TOPDIR)development/build/tools/windows_sdk.mk
-endif
+
+endif # sdk in MAKECMDGOALS
 
 # -----------------------------------------------------------------
 # Findbugs
@@ -6083,6 +6997,22 @@
 haiku: $(SOONG_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_FUZZ_TARGETS)
 $(call dist-for-goals,haiku,$(SOONG_FUZZ_PACKAGING_ARCH_MODULES))
 
+.PHONY: haiku-java
+haiku-java: $(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_TARGETS)
+$(call dist-for-goals,haiku-java,$(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES))
+
 .PHONY: haiku-rust
 haiku-rust: $(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_RUST_FUZZ_TARGETS)
 $(call dist-for-goals,haiku-rust,$(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES))
+
+# -----------------------------------------------------------------
+# Extract platform fonts used in Layoutlib
+include $(BUILD_SYSTEM)/layoutlib_fonts.mk
+
+
+# -----------------------------------------------------------------
+# OS Licensing
+
+include $(BUILD_SYSTEM)/os_licensing.mk
+
+# When appending new code to this file, please insert above OS Licensing
diff --git a/core/OWNERS b/core/OWNERS
index 5456d4f..8794434 100644
--- a/core/OWNERS
+++ b/core/OWNERS
@@ -1,5 +1,5 @@
-per-file dex_preopt*.mk = ngeoffray@google.com,calin@google.com,mathewi@google.com,dbrazdil@google.com
-per-file verify_uses_libraries.sh = ngeoffray@google.com,calin@google.com,mathieuc@google.com
+per-file dex_preopt*.mk = ngeoffray@google.com,calin@google.com,mathewi@google.com,skvadrik@google.com
+per-file verify_uses_libraries.sh = ngeoffray@google.com,calin@google.com,skvadrik@google.com
 
 # For version updates
 per-file version_defaults.mk = aseaton@google.com,elisapascual@google.com,lubomir@google.com,pscovanner@google.com
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 39f0155..2880b28 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -27,69 +27,137 @@
 # Add variables to the namespace below:
 
 $(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64)
+$(call add_soong_config_var,ANDROID,IS_TARGET_MIXED_SEPOLICY)
+ifeq ($(IS_TARGET_MIXED_SEPOLICY),true)
+$(call add_soong_config_var_value,ANDROID,MIXED_SEPOLICY_VERSION,$(BOARD_SEPOLICY_VERS))
+endif
 $(call add_soong_config_var,ANDROID,BOARD_USES_ODMIMAGE)
+$(call add_soong_config_var,ANDROID,BOARD_USES_RECOVERY_AS_BOOT)
+$(call add_soong_config_var,ANDROID,BOARD_BUILD_SYSTEM_ROOT_IMAGE)
+$(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
 
-ifeq (,$(filter com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
+# Default behavior for the tree wrt building modules or using prebuilts. This
+# can always be overridden by setting the environment variable
+# MODULE_BUILD_FROM_SOURCE.
+BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := false
+
+ifneq ($(SANITIZE_TARGET)$(EMMA_INSTRUMENT_FRAMEWORK),)
+  # Always use sources when building the framework with Java coverage or
+  # sanitized builds as they both require purpose built prebuilts which we do
+  # not provide.
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+ifneq ($(CLANG_COVERAGE)$(NATIVE_COVERAGE_PATHS),)
+  # Always use sources when building with clang coverage and native coverage.
+  # It is possible that there are certain situations when building with coverage
+  # would work with prebuilts, e.g. when the coverage is not being applied to
+  # modules for which we provide prebuilts. Unfortunately, determining that
+  # would require embedding knowledge of which coverage paths affect which
+  # modules here. That would duplicate a lot of information, add yet another
+  # location  module authors have to update and complicate the logic here.
+  # For nowe we will just always build from sources when doing coverage builds.
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+# TODO(b/172063604): Remove once products no longer use dex2oat(d)s.
+# If the product uses dex2oats and/or dex2oatds then build from sources as
+# ART does not currently provide prebuilts of those tools.
+ifneq (,$(filter dex2oats dex2oatds,$(PRODUCT_HOST_PACKAGES)))
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+# ART does not provide linux_bionic variants needed for products that
+# set HOST_CROSS_OS=linux_bionic.
+ifeq (linux_bionic,${HOST_CROSS_OS})
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+# ART does not provide host side arm64 variants needed for products that
+# set HOST_CROSS_ARCH=arm64.
+ifeq (arm64,${HOST_CROSS_ARCH})
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+# TV based devices do not seem to work with prebuilts, so build from source
+# for now and fix in a follow up.
+ifneq (,$(filter tv,$(subst $(comma),$(space),${PRODUCT_CHARACTERISTICS})))
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+# ATV based devices do not seem to work with prebuilts, so build from source
+# for now and fix in a follow up.
+ifneq (,${PRODUCT_IS_ATV})
+  BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+endif
+
+ifneq (,$(MODULE_BUILD_FROM_SOURCE))
+  # Keep an explicit setting.
+else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS))$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
   # Prebuilt module SDKs require prebuilt modules to work, and currently
   # prebuilt modules are only provided for com.google.android.xxx. If we can't
   # find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
   # and disable prebuilt SDKs. In particular this applies to AOSP builds.
+  #
+  # However, docs/sdk/win_sdk/sdk_addon builds might not include com.google.android.xxx
+  # packages, so for those we respect the default behavior.
   MODULE_BUILD_FROM_SOURCE := true
+else ifneq (,$(PRODUCT_MODULE_BUILD_FROM_SOURCE))
+  # Let products override the branch default.
+  MODULE_BUILD_FROM_SOURCE := $(PRODUCT_MODULE_BUILD_FROM_SOURCE)
+else
+  MODULE_BUILD_FROM_SOURCE := $(BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE)
 endif
 
-# TODO(b/172480615): Remove when platform uses ART Module prebuilts by default.
-ifeq (,$(filter art_module,$(SOONG_CONFIG_NAMESPACES)))
-  $(call add_soong_config_namespace,art_module)
-  SOONG_CONFIG_art_module += source_build
-endif
-ifneq (,$(findstring .android.art,$(TARGET_BUILD_APPS)))
+ifneq (,$(ART_MODULE_BUILD_FROM_SOURCE))
+  # Keep an explicit setting.
+else ifneq (,$(findstring .android.art,$(TARGET_BUILD_APPS)))
   # Build ART modules from source if they are listed in TARGET_BUILD_APPS.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(filter-out modules_% mainline_modules_%,$(TARGET_PRODUCT)))
-  # Always build from source for the module targets. This ought to be covered by
-  # the TARGET_BUILD_APPS check above, but there are test builds that don't set it.
-  SOONG_CONFIG_art_module_source_build := true
-else ifdef MODULE_BUILD_FROM_SOURCE
-  # Build from source if other Mainline modules are.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(filter true,$(NATIVE_COVERAGE) $(CLANG_COVERAGE)))
-  # Always build ART APEXes from source in coverage builds since the prebuilts
-  # aren't built with instrumentation.
-  # TODO(b/172480617): Find another solution for this.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(SANITIZE_TARGET)$(SANITIZE_HOST))
-  # Prebuilts aren't built with sanitizers either.
-  SOONG_CONFIG_art_module_source_build := true
-  MODULE_BUILD_FROM_SOURCE := true
-else ifneq (,$(PRODUCT_FUCHSIA))
-  # Fuchsia picks out ART internal packages that aren't available in the
-  # prebuilt.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(filter x86 x86_64,$(HOST_CROSS_ARCH)))
-  # We currently only provide prebuilts for x86 on host. This skips prebuilts in
-  # cuttlefish builds for ARM servers.
-  SOONG_CONFIG_art_module_source_build := true
-else ifneq (,$(filter dex2oatds dex2oats,$(PRODUCT_HOST_PACKAGES)))
-  # Some products depend on host tools that aren't available as prebuilts.
-  SOONG_CONFIG_art_module_source_build := true
-else ifeq (,$(filter com.google.android.art,$(PRODUCT_PACKAGES)))
-  # TODO(b/192006406): There is currently no good way to control which prebuilt
-  # APEX (com.google.android.art or com.android.art) gets picked for deapexing
-  # to provide dex jars for hiddenapi and dexpreopting. Instead the AOSP APEX is
-  # completely disabled, and we build from source for AOSP products.
-  SOONG_CONFIG_art_module_source_build := true
+  ART_MODULE_BUILD_FROM_SOURCE := true
 else
-  # This sets the default for building ART APEXes from source rather than
-  # prebuilts (in packages/modules/ArtPrebuilt and prebuilt/module_sdk/art) in
-  # all other platform builds.
-  SOONG_CONFIG_art_module_source_build ?= false
+  # Do the same as other modules by default.
+  ART_MODULE_BUILD_FROM_SOURCE := $(MODULE_BUILD_FROM_SOURCE)
 endif
 
+$(call soong_config_set,art_module,source_build,$(ART_MODULE_BUILD_FROM_SOURCE))
+
+# Ensure that those mainline modules who have individually toggleable prebuilts
+# are controlled by the MODULE_BUILD_FROM_SOURCE environment variable by
+# default.
+INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES := \
+  bluetooth \
+  uwb \
+  wifi \
+
+$(foreach m, $(INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES),\
+  $(if $(call soong_config_get,$(m)_module,source_build),,\
+    $(call soong_config_set,$(m)_module,source_build,$(MODULE_BUILD_FROM_SOURCE))))
+
 # Apex build mode variables
 ifdef APEX_BUILD_FOR_PRE_S_DEVICES
 $(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static)
 endif
 
-ifdef MODULE_BUILD_FROM_SOURCE
+ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
 $(call add_soong_config_var_value,ANDROID,module_build_from_source,true)
 endif
+
+# Messaging app vars
+ifeq (eng,$(TARGET_BUILD_VARIANT))
+$(call soong_config_set,messaging,build_variant_eng,true)
+endif
+
+# TODO(b/203088572): Remove when Java optimizations enabled by default for
+# SystemUI.
+$(call add_soong_config_var,ANDROID,SYSTEMUI_OPTIMIZE_JAVA)
+# TODO(b/196084106): Remove when Java optimizations enabled by default for
+# system packages.
+$(call add_soong_config_var,ANDROID,SYSTEM_OPTIMIZE_JAVA)
+
+# Check for SupplementalApi module.
+ifeq ($(wildcard packages/modules/SupplementalApi),)
+$(call add_soong_config_var_value,ANDROID,include_nonpublic_framework_api,false)
+else
+$(call add_soong_config_var_value,ANDROID,include_nonpublic_framework_api,true)
+endif
+
diff --git a/core/app_prebuilt_internal.mk b/core/app_prebuilt_internal.mk
index 79639a8..4586f35 100644
--- a/core/app_prebuilt_internal.mk
+++ b/core/app_prebuilt_internal.mk
@@ -128,6 +128,9 @@
     LOCAL_CERTIFICATE := $(dir $(DEFAULT_SYSTEM_DEV_CERTIFICATE))$(LOCAL_CERTIFICATE)
   endif
 
+  # NOTE(ruperts): Consider moving the logic below out of a conditional,
+  # to avoid the possibility of silently ignoring user settings.
+
   PACKAGES.$(LOCAL_MODULE).PRIVATE_KEY := $(LOCAL_CERTIFICATE).pk8
   PACKAGES.$(LOCAL_MODULE).CERTIFICATE := $(LOCAL_CERTIFICATE).x509.pem
   PACKAGES := $(PACKAGES) $(LOCAL_MODULE)
@@ -142,6 +145,8 @@
 
   $(built_module): $(LOCAL_CERTIFICATE_LINEAGE)
   $(built_module): PRIVATE_CERTIFICATE_LINEAGE := $(LOCAL_CERTIFICATE_LINEAGE)
+
+  $(built_module): PRIVATE_ROTATION_MIN_SDK_VERSION := $(LOCAL_ROTATION_MIN_SDK_VERSION)
 endif
 
 ifneq ($(LOCAL_MODULE_STEM),)
diff --git a/core/artifact_path_requirements.mk b/core/artifact_path_requirements.mk
index ceaefa2..566b9f7 100644
--- a/core/artifact_path_requirements.mk
+++ b/core/artifact_path_requirements.mk
@@ -22,6 +22,10 @@
     $(TARGET_OUT_SYSTEM_OTHER)/%.art
 endif
 
+ifneq (,$(filter-out true false relaxed strict,$(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))$(filter-out 1 0,$(words $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))))
+  $(error PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS must be one of [true, false, relaxed, strict], found: $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS))
+endif
+
 all_offending_files :=
 $(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\
   $(eval requirements := $(PRODUCTS.$(makefile).ARTIFACT_PATH_REQUIREMENTS)) \
@@ -46,7 +50,7 @@
   $(eval allowed_patterns := $(call resolve-product-relative-paths,$(allowed))) \
   $(eval offending_files := $(filter-out $(allowed_patterns),$(files_in_requirement))) \
   $(eval enforcement := $(PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS)) \
-  $(if $(enforcement),\
+  $(if $(filter-out false,$(enforcement)),\
     $(call maybe-print-list-and-error,$(offending_files),\
       $(INTERNAL_PRODUCT) produces files inside $(makefile)s artifact path requirement. \
       $(PRODUCT_ARTIFACT_PATH_REQUIREMENT_HINT)) \
diff --git a/core/base_rules.mk b/core/base_rules.mk
index 1b7a279..7ea9b52 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -33,6 +33,9 @@
 endif
 $(call verify-module-name)
 
+my_test_data :=
+my_test_config :=
+
 LOCAL_IS_HOST_MODULE := $(strip $(LOCAL_IS_HOST_MODULE))
 ifdef LOCAL_IS_HOST_MODULE
   ifneq ($(LOCAL_IS_HOST_MODULE),true)
@@ -373,7 +376,13 @@
 
 LOCAL_BUILT_MODULE := $(intermediates)/$(my_built_module_stem)
 
-ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
+ifneq (,$(LOCAL_SOONG_INSTALLED_MODULE))
+  ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+    $(call pretty-error, LOCAL_SOONG_INSTALLED_MODULE can only be used from $(SOONG_ANDROID_MK))
+  endif
+  # Use the install path requested by Soong.
+  LOCAL_INSTALLED_MODULE := $(LOCAL_SOONG_INSTALLED_MODULE)
+else ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
   # Apk and its attachments reside in its own subdir.
   ifeq ($(LOCAL_MODULE_CLASS),APPS)
     # framework-res.apk doesn't like the additional layer.
@@ -507,91 +516,106 @@
 ## Module installation rule
 ###########################################################
 
-my_init_rc_installed :=
-my_init_rc_path :=
-my_init_rc_pairs :=
 my_installed_symlinks :=
-my_default_test_module :=
-ifeq ($(use_testcase_folder),true)
-arch_dir := $($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
-my_default_test_module := $($(my_prefix)OUT_TESTCASES)/$(LOCAL_MODULE)/$(arch_dir)/$(my_installed_module_stem)
-arch_dir :=
-endif
 
-ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
-ifneq ($(LOCAL_INSTALLED_MODULE),$(my_default_test_module))
-$(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
-$(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
+ifneq (,$(LOCAL_SOONG_INSTALLED_MODULE))
+  # Soong already generated the copy rule, but make the installed location depend on the Make
+  # copy of the intermediates for now, as some rules that collect intermediates may expect
+  # them to exist.
+  $(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
+
+  $(foreach symlink, $(LOCAL_SOONG_INSTALL_SYMLINKS), \
+    $(call declare-0p-target,$(symlink)))
+  $(my_all_targets) : | $(LOCAL_SOONG_INSTALL_SYMLINKS)
+else ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
+  $(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
+  $(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
 	@echo "Install: $@"
-ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+  ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
 	$(copy-file-or-link-to-new-target)
-else
+  else
 	$(copy-file-to-new-target)
-endif
+  endif
 	$(PRIVATE_POST_INSTALL_CMD)
-endif
 
-ifndef LOCAL_IS_HOST_MODULE
-# Rule to install the module's companion init.rc.
-ifneq ($(strip $(LOCAL_FULL_INIT_RC)),)
-my_init_rc := $(LOCAL_FULL_INIT_RC)
-else
-my_init_rc := $(foreach rc,$(LOCAL_INIT_RC_$(my_32_64_bit_suffix)) $(LOCAL_INIT_RC),$(LOCAL_PATH)/$(rc))
-endif
-ifneq ($(strip $(my_init_rc)),)
-# Make doesn't support recovery as an output partition, but some Soong modules installed in recovery
-# have init.rc files that need to be installed alongside them. Manually handle the case where the
-# output file is in the recovery partition.
-my_init_rc_path := $(if $(filter $(TARGET_RECOVERY_ROOT_OUT)/%,$(my_module_path)),$(TARGET_RECOVERY_ROOT_OUT)/system/etc,$(TARGET_OUT$(partition_tag)_ETC))
-my_init_rc_pairs := $(foreach rc,$(my_init_rc),$(rc):$(my_init_rc_path)/init/$(notdir $(rc)))
-my_init_rc_installed := $(foreach rc,$(my_init_rc_pairs),$(call word-colon,2,$(rc)))
+  # Rule to install the module's companion symlinks
+  my_installed_symlinks := $(addprefix $(my_module_path)/,$(LOCAL_MODULE_SYMLINKS) $(LOCAL_MODULE_SYMLINKS_$(my_32_64_bit_suffix)))
+  $(foreach symlink,$(my_installed_symlinks),\
+      $(call symlink-file,$(LOCAL_INSTALLED_MODULE),$(my_installed_module_stem),$(symlink))\
+      $(call declare-0p-target,$(symlink)))
 
-# Make sure we only set up the copy rules once, even if another arch variant
-# shares a common LOCAL_INIT_RC.
-my_init_rc_new_pairs := $(filter-out $(ALL_INIT_RC_INSTALLED_PAIRS),$(my_init_rc_pairs))
-my_init_rc_new_installed := $(call copy-many-init-script-files-checked,$(my_init_rc_new_pairs))
-ALL_INIT_RC_INSTALLED_PAIRS += $(my_init_rc_new_pairs)
-
-$(my_all_targets) : $(my_init_rc_installed)
-endif # my_init_rc
-endif # !LOCAL_IS_HOST_MODULE
-
-# Rule to install the module's companion symlinks
-my_installed_symlinks := $(addprefix $(my_module_path)/,$(LOCAL_MODULE_SYMLINKS) $(LOCAL_MODULE_SYMLINKS_$(my_32_64_bit_suffix)))
-$(foreach symlink,$(my_installed_symlinks),\
-    $(call symlink-file,$(LOCAL_INSTALLED_MODULE),$(my_installed_module_stem),$(symlink)))
-
-$(my_all_targets) : | $(my_installed_symlinks)
+  $(my_all_targets) : | $(my_installed_symlinks)
 
 endif # !LOCAL_UNINSTALLABLE_MODULE
 
 ###########################################################
-## VINTF manifest fragment goals
+## VINTF manifest fragment and init.rc goals
 ###########################################################
 
 my_vintf_installed:=
+my_vintf_path:=
 my_vintf_pairs:=
+my_init_rc_installed :=
+my_init_rc_path :=
+my_init_rc_pairs :=
 ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
-ifndef LOCAL_IS_HOST_MODULE
-ifneq ($(strip $(LOCAL_FULL_VINTF_FRAGMENTS)),)
-my_vintf_fragments := $(LOCAL_FULL_VINTF_FRAGMENTS)
-else
-my_vintf_fragments := $(foreach xml,$(LOCAL_VINTF_FRAGMENTS),$(LOCAL_PATH)/$(xml))
-endif
-ifneq ($(strip $(my_vintf_fragments)),)
+  ifndef LOCAL_IS_HOST_MODULE
+    # Rule to install the module's companion vintf fragments.
+    ifneq ($(strip $(LOCAL_FULL_VINTF_FRAGMENTS)),)
+      my_vintf_fragments := $(LOCAL_FULL_VINTF_FRAGMENTS)
+    else
+      my_vintf_fragments := $(foreach xml,$(LOCAL_VINTF_FRAGMENTS),$(LOCAL_PATH)/$(xml))
+    endif
+    ifneq ($(strip $(my_vintf_fragments)),)
+      # Make doesn't support recovery as an output partition, but some Soong modules installed in recovery
+      # have init.rc files that need to be installed alongside them. Manually handle the case where the
+      # output file is in the recovery partition.
+      my_vintf_path := $(if $(filter $(TARGET_RECOVERY_ROOT_OUT)/%,$(my_module_path)),$(TARGET_RECOVERY_ROOT_OUT)/system/etc,$(TARGET_OUT$(partition_tag)_ETC))
+      my_vintf_pairs := $(foreach xml,$(my_vintf_fragments),$(xml):$(my_vintf_path)/vintf/manifest/$(notdir $(xml)))
+      my_vintf_installed := $(foreach xml,$(my_vintf_pairs),$(call word-colon,2,$(xml)))
 
-my_vintf_pairs := $(foreach xml,$(my_vintf_fragments),$(xml):$(TARGET_OUT$(partition_tag)_ETC)/vintf/manifest/$(notdir $(xml)))
-my_vintf_installed := $(foreach xml,$(my_vintf_pairs),$(call word-colon,2,$(xml)))
+      # Only set up copy rules once, even if another arch variant shares it
+      my_vintf_new_pairs := $(filter-out $(ALL_VINTF_MANIFEST_FRAGMENTS_LIST),$(my_vintf_pairs))
+      my_vintf_new_installed := $(call copy-many-vintf-manifest-files-checked,$(my_vintf_new_pairs))
 
-# Only set up copy rules once, even if another arch variant shares it
-my_vintf_new_pairs := $(filter-out $(ALL_VINTF_MANIFEST_FRAGMENTS_LIST),$(my_vintf_pairs))
-my_vintf_new_installed := $(call copy-many-vintf-manifest-files-checked,$(my_vintf_new_pairs))
+      ALL_VINTF_MANIFEST_FRAGMENTS_LIST += $(my_vintf_new_pairs)
 
-ALL_VINTF_MANIFEST_FRAGMENTS_LIST += $(my_vintf_new_pairs)
+      $(my_all_targets) : $(my_vintf_new_installed)
+    endif # my_vintf_fragments
 
-$(my_all_targets) : $(my_vintf_new_installed)
-endif # LOCAL_VINTF_FRAGMENTS
-endif # !LOCAL_IS_HOST_MODULE
+    # Rule to install the module's companion init.rc.
+    ifneq ($(strip $(LOCAL_FULL_INIT_RC)),)
+      my_init_rc := $(LOCAL_FULL_INIT_RC)
+    else
+      my_init_rc := $(foreach rc,$(LOCAL_INIT_RC_$(my_32_64_bit_suffix)) $(LOCAL_INIT_RC),$(LOCAL_PATH)/$(rc))
+    endif
+    ifneq ($(strip $(my_init_rc)),)
+      # Make doesn't support recovery or ramdisk as an output partition,
+      # but some Soong modules installed in recovery or ramdisk
+      # have init.rc files that need to be installed alongside them.
+      # Manually handle the case where the
+      # output file is in the recovery or ramdisk partition.
+      ifneq (,$(filter $(TARGET_RECOVERY_ROOT_OUT)/%,$(my_module_path)))
+        my_init_rc_path := $(TARGET_RECOVERY_ROOT_OUT)/system/etc
+      else ifneq (,$(filter $(TARGET_RAMDISK_OUT)/%,$(my_module_path)))
+        my_init_rc_path := $(TARGET_RAMDISK_OUT)/system/etc
+      else
+        my_init_rc_path := $(TARGET_OUT$(partition_tag)_ETC)
+      endif
+      my_init_rc_pairs := $(foreach rc,$(my_init_rc),$(rc):$(my_init_rc_path)/init/$(notdir $(rc)))
+      my_init_rc_installed := $(foreach rc,$(my_init_rc_pairs),$(call word-colon,2,$(rc)))
+
+      # Make sure we only set up the copy rules once, even if another arch variant
+      # shares a common LOCAL_INIT_RC.
+      my_init_rc_new_pairs := $(filter-out $(ALL_INIT_RC_INSTALLED_PAIRS),$(my_init_rc_pairs))
+      my_init_rc_new_installed := $(call copy-many-init-script-files-checked,$(my_init_rc_new_pairs))
+
+      ALL_INIT_RC_INSTALLED_PAIRS += $(my_init_rc_new_pairs)
+
+      $(my_all_targets) : $(my_init_rc_installed)
+    endif # my_init_rc
+
+  endif # !LOCAL_IS_HOST_MODULE
 endif # !LOCAL_UNINSTALLABLE_MODULE
 
 ###########################################################
@@ -697,6 +721,11 @@
 endif
 ifdef LOCAL_MULTILIB
   multi_arch := true
+# These conditionals allow this functionality to be mimicked in Soong
+else ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+  ifeq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
+    multi_arch := true
+  endif
 endif
 
 ifdef multi_arch
@@ -718,8 +747,9 @@
 
 # The module itself.
 $(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
-  $(eval my_compat_dist_$(suite) := $(foreach dir, $(call compatibility_suite_dirs,$(suite),$(arch_dir)), \
-    $(LOCAL_BUILT_MODULE):$(dir)/$(my_installed_module_stem))) \
+  $(eval my_compat_dist_$(suite) := $(patsubst %:$(LOCAL_INSTALLED_MODULE),$(LOCAL_INSTALLED_MODULE):$(LOCAL_INSTALLED_MODULE),\
+    $(foreach dir, $(call compatibility_suite_dirs,$(suite),$(arch_dir)), \
+      $(LOCAL_BUILT_MODULE):$(dir)/$(my_installed_module_stem)))) \
   $(eval my_compat_dist_config_$(suite) := ))
 
 
@@ -861,6 +891,16 @@
 endif  # LOCAL_UNINSTALLABLE_MODULE
 endif  # LOCAL_COMPATIBILITY_SUITE
 
+my_supported_variant :=
+ifeq ($(my_host_cross),true)
+  my_supported_variant := HOST_CROSS
+else
+  ifdef LOCAL_IS_HOST_MODULE
+    my_supported_variant := HOST
+  else
+    my_supported_variant := DEVICE
+  endif
+endif
 ###########################################################
 ## Add test module to ALL_DISABLED_PRESUBMIT_TESTS if LOCAL_PRESUBMIT_DISABLED is set to true.
 ###########################################################
@@ -902,12 +942,38 @@
 ALL_MODULES.$(my_register_name).TARGET_BUILT := \
     $(ALL_MODULES.$(my_register_name).TARGET_BUILT) $(LOCAL_BUILT_MODULE)
 endif
-ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
-ALL_MODULES.$(my_register_name).INSTALLED := \
+ifneq (,$(LOCAL_SOONG_INSTALLED_MODULE))
+  # Store the list of paths to installed locations of files provided by this
+  # module.  Used as dependencies of the image packaging rules when the module
+  # is installed by the current product.
+  ALL_MODULES.$(my_register_name).INSTALLED := \
+    $(strip $(ALL_MODULES.$(my_register_name).INSTALLED) \
+      $(foreach f, $(LOCAL_SOONG_INSTALL_PAIRS),\
+        $(word 2,$(subst :,$(space),$(f)))) \
+      $(LOCAL_SOONG_INSTALL_SYMLINKS) \
+      $(my_init_rc_installed) \
+      $(my_installed_test_data) \
+      $(my_vintf_installed))
+  # Store the list of colon-separated pairs of the built and installed locations
+  # of files provided by this module.  Used by custom packaging rules like
+  # package-modules.mk that need to copy the built files to a custom install
+  # location during packaging.
+  #
+  # Translate copies from $(LOCAL_PREBUILT_MODULE_FILE) to $(LOCAL_BUILT_MODULE)
+  # so that package-modules.mk gets any transtive dependencies added to
+  # $(LOCAL_BUILT_MODULE), for example unstripped symbols files.
+  ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
+    $(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) \
+      $(patsubst $(LOCAL_PREBUILT_MODULE_FILE):%,$(LOCAL_BUILT_MODULE):%,$(LOCAL_SOONG_INSTALL_PAIRS)) \
+      $(my_init_rc_pairs) \
+      $(my_test_data_pairs) \
+      $(my_vintf_pairs))
+else ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
+  ALL_MODULES.$(my_register_name).INSTALLED := \
     $(strip $(ALL_MODULES.$(my_register_name).INSTALLED) \
     $(LOCAL_INSTALLED_MODULE) $(my_init_rc_installed) $(my_installed_symlinks) \
     $(my_installed_test_data) $(my_vintf_installed))
-ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
+  ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
     $(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) \
     $(LOCAL_BUILT_MODULE):$(LOCAL_INSTALLED_MODULE) \
     $(my_init_rc_pairs) $(my_test_data_pairs) $(my_vintf_pairs))
@@ -935,6 +1001,36 @@
 my_required_modules += $(LOCAL_REQUIRED_MODULES_$($(my_prefix)OS))
 endif
 
+ALL_MODULES.$(my_register_name).SHARED_LIBS := \
+    $(ALL_MODULES.$(my_register_name).SHARED_LIBS) $(LOCAL_SHARED_LIBRARIES)
+
+ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS := \
+    $(ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS) $(LOCAL_SYSTEM_SHARED_LIBRARIES)
+
+ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES := \
+    $(ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES) $(LOCAL_RUNTIME_LIBRARIES)
+
+ifdef LOCAL_TEST_DATA
+  # Export the list of targets that are handled as data inputs and required
+  # by tests at runtime. The LOCAL_TEST_DATA format is generated from below
+  # https://cs.android.com/android/platform/superproject/+/master:build/soong/android/androidmk.go;l=925-944;drc=master
+  # which format is like $(path):$(relative_file) but for module-info, only
+  # the string after ":" is needed.
+  ALL_MODULES.$(my_register_name).TEST_DATA := \
+    $(strip $(ALL_MODULES.$(my_register_name).TEST_DATA) \
+      $(foreach f, $(LOCAL_TEST_DATA),\
+        $(call word-colon,2,$(f))))
+endif
+
+ifdef LOCAL_TEST_DATA_BINS
+  ALL_MODULES.$(my_register_name).TEST_DATA_BINS := \
+    $(ALL_MODULES.$(my_register_name).TEST_DATA_BINS) $(LOCAL_TEST_DATA_BINS)
+endif
+
+ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS := \
+  $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS) \
+  $(filter-out $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS),$(my_supported_variant))
+
 ##########################################################################
 ## When compiling against the VNDK, add the .vendor or .product suffix to
 ## required modules.
@@ -1009,7 +1105,9 @@
 endif
 ALL_MODULES.$(my_register_name).FOR_HOST_CROSS := $(my_host_cross)
 ALL_MODULES.$(my_register_name).MODULE_NAME := $(LOCAL_MODULE)
-ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := $(LOCAL_COMPATIBILITY_SUITE)
+ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := \
+  $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES) \
+  $(filter-out $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES),$(LOCAL_COMPATIBILITY_SUITE))
 ALL_MODULES.$(my_register_name).TEST_CONFIG := $(test_config)
 ALL_MODULES.$(my_register_name).EXTRA_TEST_CONFIGS := $(LOCAL_EXTRA_FULL_TEST_CONFIGS)
 ALL_MODULES.$(my_register_name).TEST_MAINLINE_MODULES := $(LOCAL_TEST_MAINLINE_MODULES)
@@ -1026,7 +1124,7 @@
 ##########################################################
 # Track module-level dependencies.
 # Use $(LOCAL_MODULE) instead of $(my_register_name) to ignore module's bitness.
-ifdef RECORD_ALL_DEPS
+# (b/204397180) Unlock RECORD_ALL_DEPS was acknowledged reasonable for better Atest performance.
 ALL_DEPS.MODULES += $(LOCAL_MODULE)
 ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(sort \
   $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) \
@@ -1043,7 +1141,6 @@
 
 license_files := $(call find-parent-file,$(LOCAL_PATH),MODULE_LICENSE*)
 ALL_DEPS.$(LOCAL_MODULE).LICENSE := $(sort $(ALL_DEPS.$(LOCAL_MODULE).LICENSE) $(license_files))
-endif
 
 ###########################################################
 ## Take care of my_module_tags
diff --git a/core/binary.mk b/core/binary.mk
index cf47374..665270e 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -32,6 +32,12 @@
   endif
 endif
 
+# Third party code has additional no-override flags.
+is_third_party :=
+ifneq ($(filter external/% hardware/% vendor/%,$(LOCAL_PATH)),)
+  is_third_party := true
+endif
+
 my_soong_problems :=
 
 # The following LOCAL_ variables will be modified in this file.
@@ -48,6 +54,10 @@
 my_cppflags := $(LOCAL_CPPFLAGS)
 my_cflags_no_override := $(GLOBAL_CLANG_CFLAGS_NO_OVERRIDE)
 my_cppflags_no_override := $(GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE)
+ifdef is_third_party
+    my_cflags_no_override += $(GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE)
+    my_cppflags_no_override += $(GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE)
+endif
 my_ldflags := $(LOCAL_LDFLAGS)
 my_ldlibs := $(LOCAL_LDLIBS)
 my_asflags := $(LOCAL_ASFLAGS)
@@ -563,8 +573,6 @@
 # the dependencies.
 my_gen_src_files := $(filter %.c %$(LOCAL_CPP_EXTENSION) %.S %.s,$(my_generated_sources))
 
-ALL_GENERATED_SOURCES += $(my_generated_sources)
-
 ####################################################
 ## Compile RenderScript with reflected C++
 ####################################################
diff --git a/core/board_config.mk b/core/board_config.mk
index 1b08f9a..dc50a68 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -65,6 +65,7 @@
 # File system variables
 _board_strip_readonly_list += BOARD_FLASH_BLOCK_SIZE
 _board_strip_readonly_list += BOARD_BOOTIMAGE_PARTITION_SIZE
+_board_strip_readonly_list += BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE
 _board_strip_readonly_list += BOARD_RECOVERYIMAGE_PARTITION_SIZE
 _board_strip_readonly_list += BOARD_SYSTEMIMAGE_PARTITION_SIZE
 _board_strip_readonly_list += BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE
@@ -84,6 +85,9 @@
 _board_strip_readonly_list += BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE
 _board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_PARTITION_SIZE
 _board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+_board_strip_readonly_list += BOARD_PVMFWIMAGE_PARTITION_SIZE
 
 # Logical partitions related variables.
 _board_strip_readonly_list += BOARD_SYSTEMIMAGE_PARTITION_RESERVED_SIZE
@@ -91,6 +95,7 @@
 _board_strip_readonly_list += BOARD_ODMIMAGE_PARTITION_RESERVED_SIZE
 _board_strip_readonly_list += BOARD_VENDOR_DLKMIMAGE_PARTITION_RESERVED_SIZE
 _board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_PARTITION_RESERVED_SIZE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE
 _board_strip_readonly_list += BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE
 _board_strip_readonly_list += BOARD_SYSTEM_EXTIMAGE_PARTITION_RESERVED_SIZE
 _board_strip_readonly_list += BOARD_SUPER_PARTITION_SIZE
@@ -121,14 +126,59 @@
 _board_strip_readonly_list += BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT
 _board_strip_readonly_list += BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES
 
+# Prebuilt image variables
+_board_strip_readonly_list += BOARD_PREBUILT_INIT_BOOT_IMAGE
+
 # Defines the list of logical vendor ramdisk names to build or include in vendor_boot.
 _board_strip_readonly_list += BOARD_VENDOR_RAMDISK_FRAGMENTS
 
+# These are all variables used to build $(INSTALLED_MISC_INFO_TARGET)
+# in build/make/core/Makefile. Their values get used in command line
+# arguments, so they have to be stripped to make the ninja files stable.
+_board_strip_list :=
+_board_strip_list += BOARD_DTBOIMG_PARTITION_SIZE
+_board_strip_list += BOARD_AVB_DTBO_KEY_PATH
+_board_strip_list += BOARD_AVB_DTBO_ALGORITHM
+_board_strip_list += BOARD_AVB_DTBO_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_PVMFW_KEY_PATH
+_board_strip_list += BOARD_AVB_PVMFW_ALGORITHM
+_board_strip_list += BOARD_AVB_PVMFW_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST
+_board_strip_list += BOARD_BPT_DISK_SIZE
+_board_strip_list += BOARD_BPT_INPUT_FILES
+_board_strip_list += BOARD_BPT_MAKE_TABLE_ARGS
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_ALGORITHM
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_KEY_PATH
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_ALGORITHM
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_KEY_PATH
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM
+_board_strip_list += BOARD_AVB_RECOVERY_KEY_PATH
+_board_strip_list += BOARD_AVB_RECOVERY_ALGORITHM
+_board_strip_list += BOARD_AVB_RECOVERY_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_KEY_PATH
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_ALGORITHM
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_KEY_PATH
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_ALGORITHM
+_board_strip_list += BOARD_AVB_VENDOR_KERNEL_BOOT_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_GKI_SIGNING_SIGNATURE_ARGS
+_board_strip_list += BOARD_GKI_SIGNING_ALGORITHM
+_board_strip_list += BOARD_GKI_SIGNING_KEY_PATH
+_board_strip_list += BOARD_MKBOOTIMG_ARGS
+_board_strip_list += BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE
+_board_strip_list += BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
+_board_strip_list += ODM_MANIFEST_SKUS
+
+
 _build_broken_var_list := \
   BUILD_BROKEN_DUP_RULES \
   BUILD_BROKEN_DUP_SYSPROP \
   BUILD_BROKEN_ELF_PREBUILT_PRODUCT_COPY_FILES \
   BUILD_BROKEN_ENFORCE_SYSPROP_OWNER \
+  BUILD_BROKEN_INPUT_DIR_MODULES \
   BUILD_BROKEN_MISSING_REQUIRED_MODULES \
   BUILD_BROKEN_OUTSIDE_INCLUDE_DIRS \
   BUILD_BROKEN_PREBUILT_ELF_FILES \
@@ -184,7 +234,33 @@
   .KATI_READONLY := TARGET_DEVICE_DIR
 endif
 
+# TODO(colefaust) change this if to RBC_PRODUCT_CONFIG when
+# the board configuration is known to work on everything
+# the product config works on.
+ifndef RBC_BOARD_CONFIG
 include $(board_config_mk)
+else
+  $(shell mkdir -p $(OUT_DIR)/rbc)
+  $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_board_config.mk)
+
+  $(shell $(OUT_DIR)/mk2rbc \
+    --mode=write -r --outdir $(OUT_DIR)/rbc \
+    --boardlauncher=$(OUT_DIR)/rbc/boardlauncher.rbc \
+    --input_variables=$(OUT_DIR)/rbc/make_vars_pre_board_config.mk \
+    --makefile_list=$(OUT_DIR)/.module_paths/configuration.list \
+    $(board_config_mk))
+  ifneq ($(.SHELLSTATUS),0)
+    $(error board configuration converter failed: $(.SHELLSTATUS))
+  endif
+
+  $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_board_config_results.mk \
+    $(OUT_DIR)/rbcrun RBC_OUT="make" $(OUT_DIR)/rbc/boardlauncher.rbc)
+  ifneq ($(.SHELLSTATUS),0)
+    $(error board configuration runner failed: $(.SHELLSTATUS))
+  endif
+
+  include $(OUT_DIR)/rbc/rbc_board_config_results.mk
+endif
 
 ifneq (,$(and $(TARGET_ARCH),$(TARGET_ARCH_SUITE)))
   $(error $(board_config_mk) erroneously sets both TARGET_ARCH and TARGET_ARCH_SUITE)
@@ -204,6 +280,7 @@
 
 # Clean up and verify BoardConfig variables
 $(foreach var,$(_board_strip_readonly_list),$(eval $(var) := $$(strip $$($(var)))))
+$(foreach var,$(_board_strip_list),$(eval $(var) := $$(strip $$($(var)))))
 $(foreach var,$(_board_true_false_vars), \
   $(if $(filter-out true false,$($(var))), \
     $(error Valid values of $(var) are "true", "false", and "". Not "$($(var))")))
@@ -397,6 +474,25 @@
 endif
 .KATI_READONLY := BUILDING_BOOT_IMAGE
 
+# Are we building an init boot image
+BUILDING_INIT_BOOT_IMAGE :=
+ifeq ($(PRODUCT_BUILD_INIT_BOOT_IMAGE),)
+  ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+    BUILDING_INIT_BOOT_IMAGE :=
+  else ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+    BUILDING_INIT_BOOT_IMAGE :=
+  else ifdef BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE
+    BUILDING_INIT_BOOT_IMAGE := true
+  endif
+else ifeq ($(PRODUCT_BUILD_INIT_BOOT_IMAGE),true)
+  ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+    $(error PRODUCT_BUILD_INIT_BOOT_IMAGE is true, but so is BOARD_USES_RECOVERY_AS_BOOT. Use only one option.)
+  else
+    BUILDING_INIT_BOOT_IMAGE := true
+  endif
+endif
+.KATI_READONLY := BUILDING_INIT_BOOT_IMAGE
+
 # Are we building a recovery image
 BUILDING_RECOVERY_IMAGE :=
 ifeq ($(PRODUCT_BUILD_RECOVERY_IMAGE),)
@@ -428,6 +524,25 @@
 endif
 .KATI_READONLY := BUILDING_VENDOR_BOOT_IMAGE
 
+# Are we building a vendor kernel boot image
+BUILDING_VENDOR_KERNEL_BOOT_IMAGE :=
+ifeq ($(PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE),true)
+  ifneq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
+    $(error BUILDING_VENDOR_BOOT_IMAGE is required, but BUILDING_VENDOR_BOOT_IMAGE is not true)
+  endif
+  ifndef BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
+    $(error BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE is required when PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE is true)
+  endif
+  BUILDING_VENDOR_KERNEL_BOOT_IMAGE := true
+else ifeq ($(PRODUCT_BUILD_VENDOR_KERNEL),)
+  ifdef BOARD_VENDOR_KERNEL_BOOTIMAGE_PARTITION_SIZE
+    ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
+      BUILDING_VENDOR_KERNEL_BOOT_IMAGE := true
+    endif
+  endif
+endif # end of PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE
+.KATI_READONLY := BUILDING_VENDOR_KERNEL_BOOT_IMAGE
+
 # Are we building a ramdisk image
 BUILDING_RAMDISK_IMAGE := true
 ifeq ($(PRODUCT_BUILD_RAMDISK_IMAGE),)
@@ -439,6 +554,91 @@
 endif
 .KATI_READONLY := BUILDING_RAMDISK_IMAGE
 
+# Are we building a debug vendor_boot image
+BUILDING_DEBUG_VENDOR_BOOT_IMAGE :=
+# Can't build vendor_boot-debug.img if BOARD_BUILD_SYSTEM_ROOT_IMAGE is true,
+# because building debug vendor_boot image requires a ramdisk.
+ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+  ifeq ($(PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE is true, but so is BOARD_BUILD_SYSTEM_ROOT_IMAGE. \
+      Skip building the debug vendor_boot image.)
+  endif
+# Can't build vendor_boot-debug.img if we're not building a ramdisk.
+else ifndef BUILDING_RAMDISK_IMAGE
+  ifeq ($(PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE is true, but we're not building a ramdisk image. \
+      Skip building the debug vendor_boot image.)
+  endif
+# Can't build vendor_boot-debug.img if we're not building a vendor_boot.img.
+else ifndef BUILDING_VENDOR_BOOT_IMAGE
+  ifeq ($(PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE is true, but we're not building a vendor_boot image. \
+      Skip building the debug vendor_boot image.)
+  endif
+else
+  ifeq ($(PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE),)
+    BUILDING_DEBUG_VENDOR_BOOT_IMAGE := true
+  else ifeq ($(PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE),true)
+    BUILDING_DEBUG_VENDOR_BOOT_IMAGE := true
+  endif
+endif
+.KATI_READONLY := BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+
+_has_boot_img_artifact :=
+ifneq ($(strip $(TARGET_NO_KERNEL)),true)
+  ifdef BUILDING_BOOT_IMAGE
+    _has_boot_img_artifact := true
+  endif
+  # BUILDING_RECOVERY_IMAGE && BOARD_USES_RECOVERY_AS_BOOT implies that
+  # recovery is being built with the file name *boot.img*, which still counts
+  # as "building boot.img".
+  ifdef BUILDING_RECOVERY_IMAGE
+    ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+      _has_boot_img_artifact := true
+    endif
+  endif
+endif
+
+# Are we building a debug boot image
+BUILDING_DEBUG_BOOT_IMAGE :=
+# Can't build boot-debug.img if BOARD_BUILD_SYSTEM_ROOT_IMAGE is true,
+# because building debug boot image requires a ramdisk.
+ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
+  ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but so is BOARD_BUILD_SYSTEM_ROOT_IMAGE. \
+      Skip building the debug boot image.)
+  endif
+# Can't build boot-debug.img if we're not building a ramdisk.
+else ifndef BUILDING_RAMDISK_IMAGE
+  ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but we're not building a ramdisk image. \
+      Skip building the debug boot image.)
+  endif
+# Can't build boot-debug.img if we're not building a boot.img.
+else ifndef _has_boot_img_artifact
+  ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but we're not building a boot image. \
+      Skip building the debug boot image.)
+  endif
+else ifdef BUILDING_INIT_BOOT_IMAGE
+  ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+    $(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but we don't have a ramdisk in the boot image. \
+      Skip building the debug boot image.)
+  endif
+else
+  ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),)
+    BUILDING_DEBUG_BOOT_IMAGE := true
+    # Don't build boot-debug.img if we're already building vendor_boot-debug.img.
+    ifdef BUILDING_DEBUG_VENDOR_BOOT_IMAGE
+      BUILDING_DEBUG_BOOT_IMAGE :=
+    endif
+  else ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+    BUILDING_DEBUG_BOOT_IMAGE := true
+  endif
+endif
+.KATI_READONLY := BUILDING_DEBUG_BOOT_IMAGE
+_has_boot_img_artifact :=
+
 # Are we building a userdata image
 BUILDING_USERDATA_IMAGE :=
 ifeq ($(PRODUCT_BUILD_USERDATA_IMAGE),)
@@ -696,6 +896,58 @@
 .KATI_READONLY := BUILDING_ODM_DLKM_IMAGE
 
 ###########################################
+# Now we can substitute with the real value of TARGET_COPY_OUT_SYSTEM_DLKM
+ifeq ($(TARGET_COPY_OUT_SYSTEM_DLKM),$(_system_dlkm_path_placeholder))
+  TARGET_COPY_OUT_SYSTEM_DLKM := $(TARGET_COPY_OUT_SYSTEM)/system_dlkm
+else ifeq ($(filter system_dlkm system/system_dlkm,$(TARGET_COPY_OUT_SYSTEM_DLKM)),)
+  $(error TARGET_COPY_OUT_SYSTEM_DLKM must be either 'system_dlkm' or 'system/system_dlkm', seeing '$(TARGET_COPY_OUT_ODM_DLKM)'.)
+endif
+PRODUCT_COPY_FILES := $(subst $(_system_dlkm_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_DLKM),$(PRODUCT_COPY_FILES))
+
+BOARD_USES_SYSTEM_DLKMIMAGE :=
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+  BOARD_USES_SYSTEM_DLKMIMAGE := true
+endif
+ifdef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+  BOARD_USES_SYSTEM_DLKMIMAGE := true
+endif
+$(call check_image_config,system_dlkm)
+
+BUILDING_SYSTEM_DLKM_IMAGE :=
+ifeq ($(PRODUCT_BUILD_SYSTEM_DLKM_IMAGE),)
+  ifdef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+    BUILDING_SYSTEM_DLKM_IMAGE := true
+  endif
+else ifeq ($(PRODUCT_BUILD_SYSTEM_DLKM_IMAGE),true)
+  BUILDING_SYSTEM_DLKM_IMAGE := true
+  ifndef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+    $(error PRODUCT_BUILD_SYSTEM_DLKM_IMAGE set to true, but BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE not defined)
+  endif
+endif
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+  BUILDING_SYSTEM_DLKM_IMAGE :=
+endif
+.KATI_READONLY := BUILDING_SYSTEM_DLKM_IMAGE
+
+BOARD_USES_PVMFWIMAGE :=
+ifdef BOARD_PREBUILT_PVMFWIMAGE
+  BOARD_USES_PVMFWIMAGE := true
+endif
+ifeq ($(PRODUCT_BUILD_PVMFW_IMAGE),true)
+  BOARD_USES_PVMFWIMAGE := true
+endif
+.KATI_READONLY := BOARD_USES_PVMFWIMAGE
+
+BUILDING_PVMFW_IMAGE :=
+ifeq ($(PRODUCT_BUILD_PVMFW_IMAGE),true)
+  BUILDING_PVMFW_IMAGE := true
+endif
+ifdef BOARD_PREBUILT_PVMFWIMAGE
+  BUILDING_PVMFW_IMAGE :=
+endif
+.KATI_READONLY := BUILDING_PVMFW_IMAGE
+
+###########################################
 # Ensure consistency among TARGET_RECOVERY_UPDATER_LIBS, AB_OTA_UPDATER, and PRODUCT_OTA_FORCE_NON_AB_PACKAGE.
 TARGET_RECOVERY_UPDATER_LIBS ?=
 AB_OTA_UPDATER ?=
@@ -745,7 +997,7 @@
 
 ifdef BOARD_VNDK_VERSION
   ifeq ($(BOARD_VNDK_VERSION),$(PLATFORM_VNDK_VERSION))
-    $(error BOARD_VNDK_VERSION is equal to PLATFORM_VNDK_VERSION; use BOARD_VNDK_VERSION := current))
+    $(error BOARD_VNDK_VERSION is equal to PLATFORM_VNDK_VERSION; use BOARD_VNDK_VERSION := current)
   endif
   ifneq ($(BOARD_VNDK_VERSION),current)
     $(call check_vndk_version,$(BOARD_VNDK_VERSION))
@@ -766,8 +1018,8 @@
 endif
 
 ###########################################
-# APEXes are by default flattened, i.e. non-updatable.
-# It can be unflattened (and updatable) by inheriting from
+# APEXes are by default flattened, i.e. non-updatable, if not building unbundled
+# apps. It can be unflattened (and updatable) by inheriting from
 # updatable_apex.mk
 #
 # APEX flattening can also be forcibly enabled (resp. disabled) by
@@ -776,7 +1028,7 @@
 ifdef OVERRIDE_TARGET_FLATTEN_APEX
   TARGET_FLATTEN_APEX := $(OVERRIDE_TARGET_FLATTEN_APEX)
 else
-  ifeq (,$(TARGET_FLATTEN_APEX))
+  ifeq (,$(TARGET_BUILD_APPS)$(TARGET_FLATTEN_APEX))
     TARGET_FLATTEN_APEX := true
   endif
 endif
@@ -803,8 +1055,8 @@
     $(KATI_deprecated_var $(m),Please convert to Soong)))
 
 $(if $(filter true,$(BUILD_BROKEN_USES_BUILD_COPY_HEADERS)),\
-  $(KATI_deprecated_var BUILD_COPY_HEADERS,See $(CHANGES_URL)#copy_headers),\
-  $(KATI_obsolete_var BUILD_COPY_HEADERS,See $(CHANGES_URL)#copy_headers))
+  $(KATI_deprecated_var BUILD_COPY_HEADERS,See $(CHANGES_URL)\#copy_headers),\
+  $(KATI_obsolete_var BUILD_COPY_HEADERS,See $(CHANGES_URL)\#copy_headers))
 
 $(foreach m,$(filter-out BUILD_COPY_HEADERS,$(DEFAULT_ERROR_BUILD_MODULE_TYPES)),\
   $(if $(filter true,$(BUILD_BROKEN_USES_$(m))),\
diff --git a/core/build-system.html b/core/build-system.html
index b872909c..181e939 100644
--- a/core/build-system.html
+++ b/core/build-system.html
@@ -444,8 +444,12 @@
 <p>Unbundled build has several meanings by the context.
 Let me explain the meaning by the flags related to "unbundled build"</p>
 <h4>TARGET_BUILD_UNBUNDLED</h4>
-<p>The source tree might not have the full platform sources. It is always set if
-<code>TARGET_BUILD_APPS</code> or <code>TARGET_BUILD_UNBUNDLED_IMAGE</code> is set.</p>
+<p>
+    The source tree might not have the full platform sources. It turns on
+    <code>TARGET_BUILD_USE_PREBUILT_SDKS</code>, unless
+    <code>UNBUNDLED_BUILD_SDKS_FROM_SOURCE</code> is set. It is always set if
+    <code>TARGET_BUILD_APPS</code> or <code>TARGET_BUILD_UNBUNDLED_IMAGE</code> is set.
+</p>
 <h4>TARGET_BUILD_USE_PREBUILT_SDKS</h4>
 <p>It is an internal flag. If it is set, prebuilt SDKs are used, even if a module's
 <code>LOCAL_SDK_VERSION</code> is <code>current</code> (including <code>system_current</code>,
diff --git a/core/build_id.mk b/core/build_id.mk
index 63aa8fb..a489788 100644
--- a/core/build_id.mk
+++ b/core/build_id.mk
@@ -18,4 +18,4 @@
 # (like "CRB01").  It must be a single word, and is
 # capitalized by convention.
 
-BUILD_ID=M_2022_07
+BUILD_ID=TM
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 94a027c..b5b371c 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -42,6 +42,7 @@
 LOCAL_CLANG_LDFLAGS:=
 LOCAL_CLASSPATH:=
 LOCAL_COMPATIBILITY_SUITE:=
+LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY:=
 LOCAL_COMPATIBILITY_SUPPORT_FILES:=
 LOCAL_COMPRESSED_MODULE:=
 LOCAL_CONLYFLAGS:=
@@ -186,6 +187,7 @@
 LOCAL_MODULE_TAGS:=
 LOCAL_MODULE_TARGET_ARCH:=
 LOCAL_MODULE_TARGET_ARCH_WARN:=
+LOCAL_MODULE_TYPE:=
 LOCAL_MODULE_UNSUPPORTED_HOST_ARCH:=
 LOCAL_MODULE_UNSUPPORTED_HOST_ARCH_WARN:=
 LOCAL_MODULE_UNSUPPORTED_TARGET_ARCH:=
@@ -262,6 +264,8 @@
 LOCAL_RESOURCE_DIR:=
 LOCAL_RLIB_LIBRARIES:=
 LOCAL_RMTYPEDEFS:=
+LOCAL_ROTATION_MIN_SDK_VERSION:=
+LOCAL_RUNTIME_LIBRARIES:=
 LOCAL_RRO_THEME:=
 LOCAL_RTTI_FLAG:=
 LOCAL_SANITIZE:=
@@ -282,7 +286,11 @@
 LOCAL_SOONG_DEXPREOPT_CONFIG :=
 LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=
 LOCAL_SOONG_HEADER_JAR :=
+LOCAL_SOONG_INSTALL_PAIRS :=
+LOCAL_SOONG_INSTALL_SYMLINKS :=
+LOCAL_SOONG_INSTALLED_MODULE :=
 LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=
+LOCAL_SOONG_LICENSE_METADATA :=
 LOCAL_SOONG_LINK_TYPE :=
 LOCAL_SOONG_LINT_REPORTS :=
 LOCAL_SOONG_PROGUARD_DICT :=
@@ -310,6 +318,7 @@
 LOCAL_TARGET_REQUIRED_MODULES:=
 LOCAL_TEST_CONFIG:=
 LOCAL_TEST_DATA:=
+LOCAL_TEST_DATA_BINS:=
 LOCAL_TEST_MAINLINE_MODULES:=
 LOCAL_TEST_MODULE_TO_PROGUARD_WITH:=
 LOCAL_TIDY:=
@@ -352,6 +361,7 @@
 LOCAL_PACK_MODULE_RELOCATIONS_$(TARGET_ARCH):=
 LOCAL_PREBUILT_JNI_LIBS_$(TARGET_ARCH):=
 LOCAL_REQUIRED_MODULES_$(TARGET_ARCH):=
+LOCAL_RUNTIME_LIBRARIES_$(TARGET_ARCH):=
 LOCAL_SHARED_LIBRARIES_$(TARGET_ARCH):=
 LOCAL_SOONG_JNI_LIBS_$(TARGET_ARCH):=
 LOCAL_SOONG_JNI_LIBS_SYMBOLS:=
@@ -376,6 +386,7 @@
 LOCAL_PACK_MODULE_RELOCATIONS_$(TARGET_2ND_ARCH):=
 LOCAL_PREBUILT_JNI_LIBS_$(TARGET_2ND_ARCH):=
 LOCAL_REQUIRED_MODULES_$(TARGET_2ND_ARCH):=
+LOCAL_RUNTIME_LIBRARIES_$(TARGET_2ND_ARCH):=
 LOCAL_SHARED_LIBRARIES_$(TARGET_2ND_ARCH):=
 LOCAL_SOONG_JNI_LIBS_$(TARGET_2ND_ARCH):=
 LOCAL_SRC_FILES_EXCLUDE_$(TARGET_2ND_ARCH):=
@@ -397,6 +408,7 @@
 LOCAL_HEADER_LIBRARIES_$(HOST_ARCH):=
 LOCAL_LDFLAGS_$(HOST_ARCH):=
 LOCAL_REQUIRED_MODULES_$(HOST_ARCH):=
+LOCAL_RUNTIME_LIBRARIES_$(HOST_ARCH):=
 LOCAL_SHARED_LIBRARIES_$(HOST_ARCH):=
 LOCAL_SRC_FILES_EXCLUDE_$(HOST_ARCH):=
 LOCAL_SRC_FILES_$(HOST_ARCH):=
@@ -416,6 +428,7 @@
 LOCAL_HEADER_LIBRARIES_$(HOST_2ND_ARCH):=
 LOCAL_LDFLAGS_$(HOST_2ND_ARCH):=
 LOCAL_REQUIRED_MODULES_$(HOST_2ND_ARCH):=
+LOCAL_RUNTIME_LIBRARIES_$(HOST_2ND_ARCH):=
 LOCAL_SHARED_LIBRARIES_$(HOST_2ND_ARCH):=
 LOCAL_SRC_FILES_EXCLUDE_$(HOST_2ND_ARCH):=
 LOCAL_SRC_FILES_$(HOST_2ND_ARCH):=
@@ -432,6 +445,7 @@
 LOCAL_LDFLAGS_$(HOST_OS):=
 LOCAL_LDLIBS_$(HOST_OS):=
 LOCAL_REQUIRED_MODULES_$(HOST_OS):=
+LOCAL_RUNTIME_LIBRARIES_$(HOST_OS):=
 LOCAL_SHARED_LIBRARIES_$(HOST_OS):=
 LOCAL_SRC_FILES_$(HOST_OS):=
 LOCAL_STATIC_LIBRARIES_$(HOST_OS):=
@@ -473,6 +487,8 @@
 LOCAL_MODULE_STEM_64:=
 LOCAL_MODULE_SYMLINKS_32:=
 LOCAL_MODULE_SYMLINKS_64:=
+LOCAL_RUNTIME_LIBRARIES_32:=
+LOCAL_RUNTIME_LIBRARIES_64:=
 LOCAL_SHARED_LIBRARIES_32:=
 LOCAL_SHARED_LIBRARIES_64:=
 LOCAL_SRC_FILES_32:=
@@ -494,6 +510,8 @@
 full_android_manifest :=
 non_system_module :=
 
+module_license_metadata :=
+
 # Trim MAKEFILE_LIST so that $(call my-dir) doesn't need to
 # iterate over thousands of entries every time.
 # Leave the current makefile to make sure we don't break anything
diff --git a/core/combo/HOST_CROSS_linux_bionic-arm64.mk b/core/combo/HOST_CROSS_linux_bionic-arm64.mk
deleted file mode 100644
index df6865f..0000000
--- a/core/combo/HOST_CROSS_linux_bionic-arm64.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (C) 2020 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.
-#
-
-# Configuration for builds hosted on linux_arm-arm64
-# Included by combo/select.mk
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(call _gen_toc_command_for_elf,$(1),$(2))
-endef
diff --git a/core/combo/HOST_CROSS_windows-x86.mk b/core/combo/HOST_CROSS_windows-x86.mk
deleted file mode 100644
index d924901..0000000
--- a/core/combo/HOST_CROSS_windows-x86.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2006 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.
-#
-
-# Settings to use MinGW as a cross-compiler under Linux
-# Included by combo/select.make
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)OBJDUMP) -x $(1) | grep "^Name" | cut -f3 -d" " > $(2)
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)NM) -g -f p $(1) | cut -f1-2 -d" " >> $(2)
-endef
diff --git a/core/combo/HOST_CROSS_windows-x86_64.mk b/core/combo/HOST_CROSS_windows-x86_64.mk
deleted file mode 100644
index d924901..0000000
--- a/core/combo/HOST_CROSS_windows-x86_64.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2006 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.
-#
-
-# Settings to use MinGW as a cross-compiler under Linux
-# Included by combo/select.make
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)OBJDUMP) -x $(1) | grep "^Name" | cut -f3 -d" " > $(2)
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)NM) -g -f p $(1) | cut -f1-2 -d" " >> $(2)
-endef
diff --git a/core/combo/select.mk b/core/combo/select.mk
index 33c8e6d..9c7e69e 100644
--- a/core/combo/select.mk
+++ b/core/combo/select.mk
@@ -27,10 +27,19 @@
 combo_var_prefix := $(combo_2nd_arch_prefix)$(combo_target)
 
 # Set reasonable defaults for the various variables
+ifeq ($(combo_target),HOST_CROSS_)
+$(KATI_obsolete_var \
+  $(combo_var_prefix)GLOBAL_ARFLAGS \
+  $(combo_var_prefix)STATIC_LIB_SUFFIX \
+  $(combo_var_prefix)transform-shared-lib-to-toc \
+  ,HOST_CROSS builds are not supported in Make)
+else
 
-$(combo_var_prefix)GLOBAL_ARFLAGS := crsPD -format=gnu
+$(combo_var_prefix)GLOBAL_ARFLAGS := crsPD --format=gnu
 
 $(combo_var_prefix)STATIC_LIB_SUFFIX := .a
 
 # Now include the combo for this specific target.
 include $(BUILD_COMBOS)/$(combo_target)$(combo_os_arch).mk
+
+endif
diff --git a/core/config.mk b/core/config.mk
index 93c5db1..7f0e98e 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -159,6 +159,8 @@
 $(KATI_deprecated_var BOARD_PLAT_PRIVATE_SEPOLICY_DIR,Use SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS instead)
 $(KATI_obsolete_var TARGET_NO_VENDOR_BOOT,Use PRODUCT_BUILD_VENDOR_BOOT_IMAGE instead)
 $(KATI_obsolete_var PRODUCT_CHECK_ELF_FILES,Use BUILD_BROKEN_PREBUILT_ELF_FILES instead)
+$(KATI_obsolete_var ALL_GENERATED_SOURCES,ALL_GENERATED_SOURCES is no longer used)
+$(KATI_obsolete_var ALL_ORIGINAL_DYNAMIC_BINARIES,ALL_ORIGINAL_DYNAMIC_BINARIES is no longer used)
 
 # Used to force goals to build.  Only use for conditionally defined goals.
 .PHONY: FORCE
@@ -252,6 +254,10 @@
 # Initialize SOONG_CONFIG_NAMESPACES so that it isn't recursive.
 SOONG_CONFIG_NAMESPACES :=
 
+# TODO(asmundak): remove add_soong_config_namespace, add_soong_config_var,
+# and add_soong_config_var_value once all their usages are replaced with
+# soong_config_set/soong_config_append.
+
 # The add_soong_config_namespace function adds a namespace and initializes it
 # to be empty.
 # $1 is the namespace.
@@ -259,7 +265,7 @@
 
 define add_soong_config_namespace
 $(eval SOONG_CONFIG_NAMESPACES += $1) \
-$(eval SOONG_CONFIG_$1 :=)
+$(eval SOONG_CONFIG_$(strip $1) :=)
 endef
 
 # The add_soong_config_var function adds a a list of soong config variables to
@@ -268,8 +274,8 @@
 # $1 is the namespace. $2 is the list of variables.
 # Ex: $(call add_soong_config_var,acme,COOL_FEATURE_A COOL_FEATURE_B)
 define add_soong_config_var
-$(eval SOONG_CONFIG_$1 += $2) \
-$(foreach v,$2,$(eval SOONG_CONFIG_$1_$v := $($v)))
+$(eval SOONG_CONFIG_$(strip $1) += $2) \
+$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $($v)))
 endef
 
 # The add_soong_config_var_value function defines a make variable and also adds
@@ -282,6 +288,40 @@
 $(call add_soong_config_var,$1,$2)
 endef
 
+# Soong config namespace variables manipulation.
+#
+# internal utility to define a namespace and a variable in it.
+define soong_config_define_internal
+$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $1)) \
+$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $2))
+endef
+
+# soong_config_set defines the variable in the given Soong config namespace
+# and sets its value. If the namespace does not exist, it will be defined.
+# $1 is the namespace. $2 is the variable name. $3 is the variable value.
+# Ex: $(call soong_config_set,acme,COOL_FEATURE,true)
+define soong_config_set
+$(call soong_config_define_internal,$1,$2) \
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$3)
+endef
+
+# soong_config_append appends to the value of the variable in the given Soong
+# config namespace. If the variable does not exist, it will be defined. If the
+# namespace does not  exist, it will be defined.
+# $1 is the namespace, $2 is the variable name, $3 is the value
+define soong_config_append
+$(call soong_config_define_internal,$1,$2) \
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $3)
+endef
+
+# soong_config_append gets to the value of the variable in the given Soong
+# config namespace. If the namespace or variables does not exist, an
+# empty string will be returned.
+# $1 is the namespace, $2 is the variable name
+define soong_config_get
+$(SOONG_CONFIG_$(strip $1)_$(strip $2))
+endef
+
 # Set the extensions used for various packages
 COMMON_PACKAGE_SUFFIX := .zip
 COMMON_JAVA_PACKAGE_SUFFIX := .jar
@@ -445,13 +485,20 @@
 ifneq ($(filter true,$(SOONG_ALLOW_MISSING_DEPENDENCIES)),)
 ALLOW_MISSING_DEPENDENCIES := true
 endif
+# Mac builds default to ALLOW_MISSING_DEPENDENCIES, at least until the host
+# tools aren't enabled by default for Mac.
+ifeq ($(HOST_OS),darwin)
+  ALLOW_MISSING_DEPENDENCIES := true
+endif
 .KATI_READONLY := ALLOW_MISSING_DEPENDENCIES
 
 TARGET_BUILD_USE_PREBUILT_SDKS :=
 DISABLE_PREOPT :=
 ifneq (,$(TARGET_BUILD_APPS)$(TARGET_BUILD_UNBUNDLED_IMAGE))
   DISABLE_PREOPT := true
-  ifndef UNBUNDLED_BUILD_SDKS_FROM_SOURCE
+endif
+ifeq (true,$(TARGET_BUILD_UNBUNDLED))
+  ifneq (true,$(UNBUNDLED_BUILD_SDKS_FROM_SOURCE))
     TARGET_BUILD_USE_PREBUILT_SDKS := true
   endif
 endif
@@ -485,11 +532,8 @@
 #
 ifeq (,$(TARGET_BUILD_USE_PREBUILT_SDKS))
   AAPT := $(HOST_OUT_EXECUTABLES)/aapt
-  MAINDEXCLASSES := $(HOST_OUT_EXECUTABLES)/mainDexClasses
-
 else # TARGET_BUILD_USE_PREBUILT_SDKS
   AAPT := $(prebuilt_sdk_tools_bin)/aapt
-  MAINDEXCLASSES := $(prebuilt_sdk_tools)/mainDexClasses
 endif # TARGET_BUILD_USE_PREBUILT_SDKS
 
 ifeq (,$(TARGET_BUILD_USE_PREBUILT_SDKS))
@@ -507,14 +551,14 @@
 ACP := $(prebuilt_build_tools_bin)/acp
 CKATI := $(prebuilt_build_tools_bin)/ckati
 DEPMOD := $(HOST_OUT_EXECUTABLES)/depmod
-FILESLIST := $(SOONG_HOST_OUT_EXECUTABLES)/fileslist
+FILESLIST := $(HOST_OUT_EXECUTABLES)/fileslist
 FILESLIST_UTIL :=$= build/make/tools/fileslist_util.py
 HOST_INIT_VERIFIER := $(HOST_OUT_EXECUTABLES)/host_init_verifier
-XMLLINT := $(SOONG_HOST_OUT_EXECUTABLES)/xmllint
+XMLLINT := $(HOST_OUT_EXECUTABLES)/xmllint
 
 # SOONG_ZIP is exported by Soong, but needs to be defined early for
 # $OUT/dexpreopt.global.  It will be verified against the Soong version.
-SOONG_ZIP := $(SOONG_HOST_OUT_EXECUTABLES)/soong_zip
+SOONG_ZIP := $(HOST_OUT_EXECUTABLES)/soong_zip
 
 # ---------------------------------------------------------------
 # Generic tools.
@@ -537,6 +581,7 @@
 MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)
 MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)
 LZ4 := $(HOST_OUT_EXECUTABLES)/lz4$(HOST_EXECUTABLE_SUFFIX)
+GENERATE_GKI_CERTIFICATE := $(HOST_OUT_EXECUTABLES)/generate_gki_certificate$(HOST_EXECUTABLE_SUFFIX)
 ifeq (,$(strip $(BOARD_CUSTOM_MKBOOTIMG)))
 MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
 else
@@ -556,7 +601,7 @@
 FS_GET_STATS := $(HOST_OUT_EXECUTABLES)/fs_get_stats$(HOST_EXECUTABLE_SUFFIX)
 MKEXTUSERIMG := $(HOST_OUT_EXECUTABLES)/mkuserimg_mke2fs
 MKE2FS_CONF := system/extras/ext4_utils/mke2fs.conf
-MKEROFSUSERIMG := $(HOST_OUT_EXECUTABLES)/mkerofsimage.sh
+MKEROFS := $(HOST_OUT_EXECUTABLES)/mkfs.erofs
 MKSQUASHFSUSERIMG := $(HOST_OUT_EXECUTABLES)/mksquashfsimage.sh
 MKF2FSUSERIMG := $(HOST_OUT_EXECUTABLES)/mkf2fsuserimg.sh
 SIMG2IMG := $(HOST_OUT_EXECUTABLES)/simg2img$(HOST_EXECUTABLE_SUFFIX)
@@ -575,6 +620,7 @@
 OTA_FROM_TARGET_FILES := $(HOST_OUT_EXECUTABLES)/ota_from_target_files$(HOST_EXECUTABLE_SUFFIX)
 SPARSE_IMG := $(HOST_OUT_EXECUTABLES)/sparse_img$(HOST_EXECUTABLE_SUFFIX)
 CHECK_PARTITION_SIZES := $(HOST_OUT_EXECUTABLES)/check_partition_sizes$(HOST_EXECUTABLE_SUFFIX)
+SYMBOLS_MAP := $(HOST_OUT_EXECUTABLES)/symbols_map
 
 PROGUARD_HOME := external/proguard
 PROGUARD := $(PROGUARD_HOME)/bin/proguard.sh
@@ -603,7 +649,7 @@
 # Path to tools.jar
 HOST_JDK_TOOLS_JAR := $(ANDROID_JAVA8_HOME)/lib/tools.jar
 
-APICHECK_COMMAND := $(JAVA) -Xmx4g -jar $(APICHECK) --no-banner --compatible-output=no
+APICHECK_COMMAND := $(JAVA) -Xmx4g -jar $(APICHECK) --no-banner
 
 # Boolean variable determining if the allow list for compatible properties is enabled
 PRODUCT_COMPATIBLE_PROPERTY := true
@@ -722,8 +768,14 @@
 .KATI_READONLY := BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
 
 ifdef PRODUCT_SHIPPING_API_LEVEL
-  ifneq ($(call numbers_less_than,$(PRODUCT_SHIPPING_API_LEVEL),$(BOARD_SYSTEMSDK_VERSIONS)),)
-    $(error BOARD_SYSTEMSDK_VERSIONS ($(BOARD_SYSTEMSDK_VERSIONS)) must all be greater than or equal to PRODUCT_SHIPPING_API_LEVEL ($(PRODUCT_SHIPPING_API_LEVEL)))
+  board_api_level := $(firstword $(BOARD_API_LEVEL) $(BOARD_SHIPPING_API_LEVEL))
+  ifneq (,$(board_api_level))
+    min_systemsdk_version := $(call math_min,$(board_api_level),$(PRODUCT_SHIPPING_API_LEVEL))
+  else
+    min_systemsdk_version := $(PRODUCT_SHIPPING_API_LEVEL)
+  endif
+  ifneq ($(call numbers_less_than,$(min_systemsdk_version),$(BOARD_SYSTEMSDK_VERSIONS)),)
+    $(error BOARD_SYSTEMSDK_VERSIONS ($(BOARD_SYSTEMSDK_VERSIONS)) must all be greater than or equal to BOARD_API_LEVEL, BOARD_SHIPPING_API_LEVEL or PRODUCT_SHIPPING_API_LEVEL ($(min_systemsdk_version)))
   endif
   ifneq ($(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),28),)
     ifneq ($(TARGET_IS_64_BIT), true)
@@ -769,7 +821,7 @@
 # is made which breaks compatibility with the previous platform sepolicy version,
 # not just on every increase in PLATFORM_SDK_VERSION.  The minor version should
 # be reset to 0 on every bump of the PLATFORM_SDK_VERSION.
-sepolicy_major_vers := 31
+sepolicy_major_vers := 33
 sepolicy_minor_vers := 0
 
 ifneq ($(sepolicy_major_vers), $(PLATFORM_SDK_VERSION))
@@ -785,13 +837,30 @@
 sepolicy_major_vers :=
 sepolicy_minor_vers :=
 
+# BOARD_SEPOLICY_VERS must take the format "NN.m" and contain the sepolicy
+# version identifier corresponding to the sepolicy on which the non-platform
+# policy is to be based. If unspecified, this will build against the current
+# public platform policy in tree
+ifndef BOARD_SEPOLICY_VERS
+# The default platform policy version.
+BOARD_SEPOLICY_VERS := $(PLATFORM_SEPOLICY_VERSION)
+endif
+
+ifeq ($(BOARD_SEPOLICY_VERS),$(PLATFORM_SEPOLICY_VERSION))
+IS_TARGET_MIXED_SEPOLICY :=
+else
+IS_TARGET_MIXED_SEPOLICY := true
+endif
+
+.KATI_READONLY := IS_TARGET_MIXED_SEPOLICY
+
 # A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
 PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
-    26.0 \
-    27.0 \
     28.0 \
     29.0 \
     30.0 \
+    31.0 \
+    32.0 \
 
 .KATI_READONLY := \
     PLATFORM_SEPOLICY_COMPAT_VERSIONS \
@@ -864,6 +933,13 @@
 endif
 endif
 
+ifneq ($(BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE),)
+ifneq ($(BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE),)
+$(error Should not define BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE and \
+    BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE together)
+endif
+endif
+
 ifneq ($(BOARD_PRODUCTIMAGE_PARTITION_SIZE),)
 ifneq ($(BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE),)
 $(error Should not define BOARD_PRODUCTIMAGE_PARTITION_SIZE and \
@@ -899,7 +975,7 @@
 )
 
 # BOARD_*_PARTITION_LIST: a list of the following tokens
-valid_super_partition_list := system vendor product system_ext odm vendor_dlkm odm_dlkm
+valid_super_partition_list := system vendor product system_ext odm vendor_dlkm odm_dlkm system_dlkm
 $(foreach group,$(call to-upper,$(BOARD_SUPER_PARTITION_GROUPS)), \
     $(if $(filter-out $(valid_super_partition_list),$(BOARD_$(group)_PARTITION_LIST)), \
         $(error BOARD_$(group)_PARTITION_LIST contains invalid partition name \
@@ -998,6 +1074,14 @@
 BOARD_PREBUILT_HIDDENAPI_DIR ?=
 .KATI_READONLY := BOARD_PREBUILT_HIDDENAPI_DIR
 
+ifdef USE_HOST_MUSL
+  ifneq (,$(or $(BUILD_BROKEN_USES_BUILD_HOST_EXECUTABLE),\
+               $(BUILD_BROKEN_USES_BUILD_HOST_SHARED_LIBRARY),\
+               $(BUILD_BROKEN_USES_BUILD_HOST_STATIC_LIBRARY)))
+    $(error USE_HOST_MUSL can't be set when native host builds are enabled in Make with BUILD_BROKEN_USES_BUILD_HOST_*)
+  endif
+endif
+
 # ###############################################################
 # Set up final options.
 # ###############################################################
@@ -1149,10 +1233,27 @@
 endif
 .KATI_READONLY := GOMA_POOL RBE_POOL GOMA_OR_RBE_POOL
 
+JAVAC_NINJA_POOL :=
+R8_NINJA_POOL :=
+D8_NINJA_POOL :=
+
+ifneq ($(filter-out false,$(USE_RBE)),)
+  ifdef RBE_JAVAC
+    JAVAC_NINJA_POOL := $(RBE_POOL)
+  endif
+  ifdef RBE_R8
+    R8_NINJA_POOL := $(RBE_POOL)
+  endif
+  ifdef RBE_D8
+    D8_NINJA_POOL := $(RBE_POOL)
+  endif
+endif
+
+.KATI_READONLY := JAVAC_NINJA_POOL R8_NINJA_POOL D8_NINJA_POOL
+
 # These goals don't need to collect and include Android.mks/CleanSpec.mks
 # in the source tree.
-dont_bother_goals := out \
-    product-graph dump-products
+dont_bother_goals := out product-graph
 
 # Make ANDROID Soong config variables visible to Android.mk files, for
 # consistency with those defined in BoardConfig.mk files.
@@ -1168,7 +1269,4 @@
 DEFAULT_DATA_OUT_MODULES := ltp $(ltp_packages) $(kselftest_modules)
 .KATI_READONLY := DEFAULT_DATA_OUT_MODULES
 
-# Make RECORD_ALL_DEPS readonly.
-RECORD_ALL_DEPS :=$= $(filter true,$(RECORD_ALL_DEPS))
-
 include $(BUILD_SYSTEM)/dumpvar.mk
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 46f7f24..a0ff119 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -299,9 +299,6 @@
   my_sanitize := $(filter-out cfi,$(my_sanitize))
   my_cflags += -fno-lto
   my_ldflags += -fno-lto
-
-  # TODO(b/133876586): Disable experimental pass manager for fuzzer builds.
-  my_cflags += -fno-experimental-new-pass-manager
 endif
 
 ifneq ($(filter integer_overflow,$(my_sanitize)),)
diff --git a/core/definitions.mk b/core/definitions.mk
index c5fe76b..0c46de9 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -37,6 +37,10 @@
 # sub-variables.
 ALL_MODULES:=
 
+# The relative paths of the non-module targets in the system.
+ALL_NON_MODULES:=
+NON_MODULES_WITHOUT_LICENSE_METADATA:=
+
 # Full paths to targets that should be added to the "make droid"
 # set of installed targets.
 ALL_DEFAULT_INSTALLED_MODULES:=
@@ -55,16 +59,10 @@
 # its sub-variables.)
 ALL_MODULE_NAME_TAGS:=
 
-# Full path to all files that are made by some tool
-ALL_GENERATED_SOURCES:=
-
 # Full path to all asm, C, C++, lex and yacc generated C files.
 # These all have an order-only dependency on the copied headers
 ALL_C_CPP_ETC_OBJECTS:=
 
-# The list of dynamic binaries that haven't been stripped/compressed/etc.
-ALL_ORIGINAL_DYNAMIC_BINARIES:=
-
 # These files go into the SDK
 ALL_SDK_FILES:=
 
@@ -539,7 +537,7 @@
   $(eval _all_module_refs := \
     $(sort \
       $(foreach m,$(sort $(ALL_MODULES)), \
-        $(ALL_MODULES.$(m).NOTICE_DEPS) \
+        $(call word-colon,1,$(ALL_MODULES.$(m).NOTICE_DEPS)) \
       ) \
     ) \
   ) \
@@ -557,7 +555,7 @@
     $(eval ALL_MODULES.$(m).NOTICE_DEPS := \
       $(sort \
          $(foreach d,$(sort $(ALL_MODULES.$(m).NOTICE_DEPS)), \
-           $(_lookup.$(d)) \
+           $(foreach n,$(_lookup.$(call word-colon,1,$(d))),$(n):$(call wordlist-colon,2,9999,$(d))) \
         ) \
       ) \
     ) \
@@ -573,62 +571,357 @@
 endef
 
 ###########################################################
-## License metadata build rule for my_register_name $1
+# License metadata targets corresponding to targets in $(1)
+###########################################################
+define corresponding-license-metadata
+$(strip $(foreach target, $(sort $(1)), \
+  $(if $(strip $(ALL_MODULES.$(target).META_LIC)), \
+    $(ALL_MODULES.$(target).META_LIC), \
+    $(if $(strip $(ALL_TARGETS.$(target).META_LIC)), \
+      $(ALL_TARGETS.$(target).META_LIC), \
+      $(call append-path,$(call license-metadata-dir),$(patsubst $(OUT_DIR)%,out%,$(target).meta_lic))))))
+endef
+
+###########################################################
+## License metadata build rule for my_register_name $(1)
 ###########################################################
 define license-metadata-rule
-$(strip $(eval _dir := $(call license-metadata-dir)))
-$(strip $(eval _deps := $(sort $(filter-out $(_dir)/$(1).meta_lic,$(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS), $(_dir)/$(d).meta_lic)))))
-$(strip $(eval _notices := $(sort $(ALL_MODULES.$(1).NOTICES))))
-$(strip $(eval _tgts := $(sort $(ALL_MODULES.$(1).BUILT) $(ALL_MODULES.$(1).INSTALLED))))
-$(foreach b,$(_tgts),
-$(_dir)/$(b).meta_module ::
-	mkdir -p $$(dir $$@)
-	echo $(_dir)/$(1).meta_lic >> $$@
-	sort -u $$@ -o $$@
+$(foreach meta_lic, $(ALL_MODULES.$(1).DELAYED_META_LIC),$(call _license-metadata-rule,$(1),$(meta_lic)))
+endef
 
-)
-$(_dir)/$(1).meta_lic: PRIVATE_KINDS := $(sort $(ALL_MODULES.$(1).LICENSE_KINDS))
-$(_dir)/$(1).meta_lic: PRIVATE_CONDITIONS := $(sort $(ALL_MODULES.$(1).LICENSE_CONDITIONS))
-$(_dir)/$(1).meta_lic: PRIVATE_NOTICES := $(_notices)
-$(_dir)/$(1).meta_lic: PRIVATE_NOTICE_DEPS := $(_deps)
-$(_dir)/$(1).meta_lic: PRIVATE_TARGETS := $(_tgts)
-$(_dir)/$(1).meta_lic: PRIVATE_IS_CONTAINER := $(ALL_MODULES.$(1).IS_CONTAINER)
-$(_dir)/$(1).meta_lic: PRIVATE_PACKAGE_NAME := $(strip $(ALL_MODULES.$(1).LICENSE_PACKAGE_NAME))
-$(_dir)/$(1).meta_lic: PRIVATE_INSTALL_MAP := $(sort $(ALL_MODULES.$(1).LICENSE_INSTALL_MAP))
-$(_dir)/$(1).meta_lic : $(_deps) $(_notices) $(foreach b,$(_tgts), $(_dir)/$(b).meta_module) build/make/tools/build-license-metadata.sh
+$(KATI_obsolete_var notice-rule, This function has been removed)
+
+define _license-metadata-rule
+$(strip $(eval _srcs := $(strip $(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED)), $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT)), $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT), $(call word-colon,1,$d)))))))
+$(strip $(eval _deps := $(sort $(filter-out $(2)%,\
+   $(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS),\
+     $(addsuffix :$(call wordlist-colon,2,9999,$(d)), \
+       $(foreach dt,$(ALL_MODULES.$(d).BUILT) $(ALL_MODULES.$(d).INSTALLED),\
+         $(ALL_TARGETS.$(dt).META_LIC))))))))
+$(strip $(eval _notices := $(sort $(ALL_MODULES.$(1).NOTICES))))
+$(strip $(eval _tgts := $(sort $(ALL_MODULES.$(1).BUILT))))
+$(strip $(eval _inst := $(sort $(ALL_MODULES.$(1).INSTALLED))))
+$(strip $(eval _path := $(sort $(ALL_MODULES.$(1).PATH))))
+$(strip $(eval _map := $(strip $(foreach _m,$(sort $(ALL_MODULES.$(1).LICENSE_INSTALL_MAP)), \
+  $(eval _s := $(call word-colon,1,$(_m))) \
+  $(eval _d := $(call word-colon,2,$(_m))) \
+  $(eval _ns := $(if $(strip $(ALL_MODULES.$(_s).INSTALLED)),$(ALL_MODULES.$(_s).INSTALLED),$(if $(strip $(ALL_MODULES.$(_s).BUILT)),$(ALL_MODULES.$(_s).BUILT),$(_s)))) \
+  $(foreach ns,$(_ns),$(ns):$(_d) ) \
+))))
+
+$(2): PRIVATE_KINDS := $(sort $(ALL_MODULES.$(1).LICENSE_KINDS))
+$(2): PRIVATE_CONDITIONS := $(sort $(ALL_MODULES.$(1).LICENSE_CONDITIONS))
+$(2): PRIVATE_NOTICES := $(_notices)
+$(2): PRIVATE_NOTICE_DEPS := $(_deps)
+$(2): PRIVATE_SOURCES := $(_srcs)
+$(2): PRIVATE_TARGETS := $(_tgts)
+$(2): PRIVATE_INSTALLED := $(_inst)
+$(2): PRIVATE_PATH := $(_path)
+$(2): PRIVATE_IS_CONTAINER := $(ALL_MODULES.$(1).IS_CONTAINER)
+$(2): PRIVATE_PACKAGE_NAME := $(strip $(ALL_MODULES.$(1).LICENSE_PACKAGE_NAME))
+$(2): PRIVATE_INSTALL_MAP := $(_map)
+$(2): PRIVATE_MODULE_TYPE := $(ALL_MODULES.$(1).MODULE_TYPE)
+$(2): PRIVATE_MODULE_CLASS := $(ALL_MODULES.$(1).MODULE_CLASS)
+$(2): PRIVATE_INSTALL_MAP := $(_map)
+$(2): PRIVATE_ARGUMENT_FILE := $(call intermediates-dir-for,PACKAGING,notice)/$(2)/arguments
+$(2): $(BUILD_LICENSE_METADATA)
+$(2) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
-	build/make/tools/build-license-metadata.sh -k $$(PRIVATE_KINDS) -c $$(PRIVATE_CONDITIONS) -n $$(PRIVATE_NOTICES) -d $$(PRIVATE_NOTICE_DEPS) -m $$(PRIVATE_INSTALL_MAP) -t $$(PRIVATE_TARGETS) $$(if $$(PRIVATE_IS_CONTAINER),-is_container) -p $$(PRIVATE_PACKAGE_NAME) -o $$@
+	mkdir -p $$(dir $$(PRIVATE_ARGUMENT_FILE))
+	$$(call dump-words-to-file,\
+	    $$(addprefix -mt ,$$(PRIVATE_MODULE_TYPE))\
+	    $$(addprefix -mc ,$$(PRIVATE_MODULE_CLASS))\
+	    $$(addprefix -k ,$$(PRIVATE_KINDS))\
+	    $$(addprefix -c ,$$(PRIVATE_CONDITIONS))\
+	    $$(addprefix -n ,$$(PRIVATE_NOTICES))\
+	    $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS))\
+	    $$(addprefix -s ,$$(PRIVATE_SOURCES))\
+	    $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP))\
+	    $$(addprefix -t ,$$(PRIVATE_TARGETS))\
+	    $$(addprefix -i ,$$(PRIVATE_INSTALLED))\
+	    $$(addprefix -r ,$$(PRIVATE_PATH)),\
+	    $$(PRIVATE_ARGUMENT_FILE))
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
+	  $$(if $$(PRIVATE_IS_CONTAINER),-is_container) \
+	  -p '$$(PRIVATE_PACKAGE_NAME)' \
+	  @$$(PRIVATE_ARGUMENT_FILE) \
+	  -o $$@
+endef
 
-.PHONY: $(1).meta_lic
-$(1).meta_lic : $(_dir)/$(1).meta_lic
 
-$(strip $(eval _mifs := $(sort $(ALL_MODULES.$(1).MODULE_INSTALLED_FILENAMES))))
-$(strip $(eval _infs := $(sort $(ALL_MODULES.$(1).INSTALLED_NOTICE_FILE))))
+###########################################################
+## License metadata build rule for non-module target $(1)
+###########################################################
+define non-module-license-metadata-rule
+$(strip $(eval _dir := $(call license-metadata-dir)))
+$(strip $(eval _tgt := $(strip $(1))))
+$(strip $(eval _meta := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(_tgt).meta_lic))))
+$(strip $(eval _deps := $(sort $(filter-out 0p: :,$(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)),$(ALL_TARGETS.$(call word-colon,1,$(d)).META_LIC):$(call wordlist-colon,2,9999,$(d)))))))
+$(strip $(eval _notices := $(sort $(ALL_NON_MODULES.$(_tgt).NOTICES))))
+$(strip $(eval _path := $(sort $(ALL_NON_MODULES.$(_tgt).PATH))))
+$(strip $(eval _install_map := $(ALL_NON_MODULES.$(_tgt).ROOT_MAPPINGS)))
+$(strip $(eval \
+  $$(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)), \
+    $$(if $$(strip $$(ALL_TARGETS.$$(d).META_LIC)), \
+      , \
+      $$(eval NON_MODULES_WITHOUT_LICENSE_METADATA += $$(d))) \
+  )) \
+)
 
-# Emit each installed notice file rule if it references the current module
-$(if $(_infs),$(foreach inf,$(_infs),
-$(if $(strip $(filter $(1),$(INSTALLED_NOTICE_FILES.$(inf).MODULE))),
-$(strip $(eval _mif := $(firstword $(foreach m,$(_mifs),$(if $(filter %/src/$(m).txt,$(inf)),$(m))))))
-
-$(inf) : $(_dir)/$(1).meta_lic
-$(inf): PRIVATE_INSTALLED_MODULE := $(_mif)
-$(inf) : PRIVATE_NOTICES := $(_notices)
-
-$(inf): $(_notices)
-	@echo Notice file: $$< -- $$@
+$(_meta): PRIVATE_KINDS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_KINDS))
+$(_meta): PRIVATE_CONDITIONS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS))
+$(_meta): PRIVATE_NOTICES := $(_notices)
+$(_meta): PRIVATE_NOTICE_DEPS := $(_deps)
+$(_meta): PRIVATE_SOURCES := $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)
+$(_meta): PRIVATE_TARGETS := $(_tgt)
+$(_meta): PRIVATE_PATH := $(_path)
+$(_meta): PRIVATE_IS_CONTAINER := $(ALL_NON_MODULES.$(_tgt).IS_CONTAINER)
+$(_meta): PRIVATE_PACKAGE_NAME := $(strip $(ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME))
+$(_meta): PRIVATE_INSTALL_MAP := $(strip $(_install_map))
+$(_meta): PRIVATE_ARGUMENT_FILE := $(call intermediates-dir-for,PACKAGING,notice)/$(_meta)/arguments
+$(_meta): $(BUILD_LICENSE_METADATA)
+$(_meta) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
+	rm -f $$@
 	mkdir -p $$(dir $$@)
-	awk 'FNR==1 && NR > 1 {print "\n"} {print}' $$(PRIVATE_NOTICES) > $$@
-
-)))
+	mkdir -p $$(dir $$(PRIVATE_ARGUMENT_FILE))
+	$$(call dump-words-to-file,\
+	    $$(addprefix -k ,$$(PRIVATE_KINDS))\
+	    $$(addprefix -c ,$$(PRIVATE_CONDITIONS))\
+	    $$(addprefix -n ,$$(PRIVATE_NOTICES))\
+	    $$(addprefix -d ,$$(PRIVATE_NOTICE_DEPS))\
+	    $$(addprefix -s ,$$(PRIVATE_SOURCES))\
+	    $$(addprefix -m ,$$(PRIVATE_INSTALL_MAP))\
+	    $$(addprefix -t ,$$(PRIVATE_TARGETS))\
+	    $$(addprefix -r ,$$(PRIVATE_PATH)),\
+	    $$(PRIVATE_ARGUMENT_FILE))
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
+          -mt raw -mc unknown \
+	  $$(if $$(PRIVATE_IS_CONTAINER),-is_container) \
+	  $$(addprefix -r ,$$(PRIVATE_PATH)) \
+	  @$$(PRIVATE_ARGUMENT_FILE) \
+	  -o $$@
 
 endef
 
 ###########################################################
+## Declare the license metadata for non-module target $(1).
+##
+## $(2) -- license kinds e.g. SPDX-license-identifier-Apache-2.0
+## $(3) -- license conditions e.g. notice by_exception_only
+## $(4) -- license text filenames (notices)
+## $(5) -- package name
+## $(6) -- project path
+###########################################################
+define declare-license-metadata
+$(strip \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
+  $(eval ALL_NON_MODULES += $(_tgt)) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
+  $(eval ALL_NON_MODULES.$(_tgt).NOTICES := $(strip $(4))) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME := $(strip $(5))) \
+  $(eval ALL_NON_MODULES.$(_tgt).PATH := $(strip $(6))) \
+)
+endef
+
+###########################################################
+## Declare that non-module targets copied from project $(1) and
+## optionally ending in $(2) have the following license
+## metadata:
+##
+## $(3) -- license kinds e.g. SPDX-license-identifier-Apache-2.0
+## $(4) -- license conditions e.g. notice by_exception_only
+## $(5) -- license text filenames (notices)
+## $(6) -- package name
+###########################################################
+define declare-copy-files-license-metadata
+$(strip \
+  $(foreach _pair,$(filter $(1)%$(2),$(PRODUCT_COPY_FILES)),$(eval $(call declare-license-metadata,$(PRODUCT_OUT)/$(call word-colon,2,$(_pair)),$(3),$(4),$(5),$(6),$(1)))) \
+)
+endef
+
+###########################################################
+## Declare the license metadata for non-module container-type target $(1).
+##
+## Container-type targets are targets like .zip files that
+## merely aggregate other files.
+##
+## $(2) -- license kinds e.g. SPDX-license-identifier-Apache-2.0
+## $(3) -- license conditions e.g. notice by_exception_only
+## $(4) -- license text filenames (notices)
+## $(5) -- package name
+## $(6) -- project path
+###########################################################
+define declare-container-license-metadata
+$(strip \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
+  $(eval ALL_NON_MODULES += $(_tgt)) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
+  $(eval ALL_NON_MODULES.$(_tgt).NOTICES := $(strip $(4))) \
+  $(eval ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME := $(strip $(5))) \
+  $(eval ALL_NON_MODULES.$(_tgt).PATH := $(strip $(6))) \
+  $(eval ALL_NON_MODULES.$(_tgt).IS_CONTAINER := true) \
+)
+endef
+
+###########################################################
+## Declare that non-module target $(1) is a non-copyrightable file.
+##
+## e.g. an information-only file merely listing other files.
+###########################################################
+define declare-0p-target
+$(strip \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
+  $(eval ALL_0P_TARGETS += $(_tgt)) \
+)
+endef
+
+###########################################################
+## Declare that non-module targets copied from project $(1) and
+## optionally ending in $(2) are non-copyrightable files.
+##
+## e.g. an information-only file merely listing other files.
+###########################################################
+define declare-0p-copy-files
+$(strip \
+  $(foreach _pair,$(filter $(1)%$(2),$(PRODUCT_COPY_FILES)),$(eval $(call declare-0p-target,$(PRODUCT_OUT)/$(call word-colon,2,$(_pair))))) \
+)
+endef
+
+###########################################################
+## Declare non-module target $(1) to have a first-party license
+## (Android Apache 2.0)
+##
+## $(2) -- project path
+###########################################################
+define declare-1p-target
+$(call declare-license-metadata,$(1),SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,Android,$(2))
+endef
+
+###########################################################
+## Declare that non-module targets copied from project $(1) and
+## optionally ending in $(2) are first-party licensed
+## (Android Apache 2.0)
+###########################################################
+define declare-1p-copy-files
+$(foreach _pair,$(filter $(1)%$(2),$(PRODUCT_COPY_FILES)),$(call declare-1p-target,$(PRODUCT_OUT)/$(call word-colon,2,$(_pair)),$(1)))
+endef
+
+###########################################################
+## Declare non-module container-type target $(1) to have a
+## first-party license (Android Apache 2.0).
+##
+## Container-type targets are targets like .zip files that
+## merely aggregate other files.
+##
+## $92) -- project path
+###########################################################
+define declare-1p-container
+$(call declare-container-license-metadata,$(1),SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,Android,$(2))
+endef
+
+###########################################################
+## Declare license dependencies $(2) for non-module target $(1)
+###########################################################
+define declare-license-deps
+$(strip \
+  $(eval _tgt := $(strip $(1))) \
+  $(eval ALL_NON_MODULES += $(_tgt)) \
+  $(eval ALL_NON_MODULES.$(_tgt).DEPENDENCIES := $(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES) $(2))) \
+)
+endef
+
+###########################################################
+## Declare license dependencies $(2) for non-module container-type target $(1)
+##
+## Container-type targets are targets like .zip files that
+## merely aggregate other files.
+##
+## $(3) -- root mappings space-separated source:target
+###########################################################
+define declare-container-license-deps
+$(strip \
+  $(eval _tgt := $(strip $(1))) \
+  $(eval ALL_NON_MODULES += $(_tgt)) \
+  $(eval ALL_NON_MODULES.$(_tgt).DEPENDENCIES := $(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES) $(2))) \
+  $(eval ALL_NON_MODULES.$(_tgt).IS_CONTAINER := true) \
+  $(eval ALL_NON_MODULES.$(_tgt).ROOT_MAPPINGS := $(strip $(ALL_NON_MODULES.$(_tgt).ROOT_MAPPINGS) $(3))) \
+)
+endef
+
+###########################################################
+## Declares the rule to report targets with no license metadata.
+###########################################################
+define report-missing-licenses-rule
+.PHONY: reportmissinglicenses
+reportmissinglicenses: PRIVATE_NON_MODULES:=$(sort $(NON_MODULES_WITHOUT_LICENSE_METADATA))
+reportmissinglicenses: PRIVATE_COPIED_FILES:=$(sort $(filter $(NON_MODULES_WITHOUT_LICENSE_METADATA),$(foreach _pair,$(PRODUCT_COPY_FILES), $(PRODUCT_OUT)/$(call word-colon,2,$(_pair)))))
+reportmissinglicenses:
+	@echo Reporting $$(words $$(PRIVATE_NON_MODULES)) targets without license metadata
+	$$(foreach t,$$(PRIVATE_NON_MODULES),if ! [ -h $$(t) ]; then echo No license metadata for $$(t) >&2; fi;)
+	$$(foreach t,$$(PRIVATE_COPIED_FILES),if ! [ -h $$(t) ]; then echo No license metadata for copied file $$(t) >&2; fi;)
+
+endef
+
+
+###########################################################
+# Returns the unique list of built license metadata files.
+###########################################################
+define all-license-metadata
+$(sort \
+  $(foreach t,$(ALL_NON_MODULES),$(if $(filter 0p,$(ALL_TARGETS.$(t).META_LIC)),, $(ALL_TARGETS.$(t).META_LIC))) \
+  $(foreach m,$(ALL_MODULES), $(ALL_MODULES.$(m).META_LIC)) \
+)
+endef
+
+###########################################################
+# Declares the rule to report all library names used in any notice files.
+###########################################################
+define report-all-notice-library-names-rule
+$(strip $(eval _all := $(call all-license-metadata)))
+
+.PHONY: reportallnoticelibrarynames
+reportallnoticelibrarynames: PRIVATE_LIST_FILE := $(call license-metadata-dir)/filelist
+reportallnoticelibrarynames: | $(COMPLIANCENOTICE_SHIPPEDLIBS)
+reportallnoticelibrarynames: $(_all)
+	@echo Reporting notice library names for at least $$(words $(_all)) license metadata files
+	$(hide) rm -f $$(PRIVATE_LIST_FILE)
+	$(hide) mkdir -p $$(dir $$(PRIVATE_LIST_FILE))
+	$(hide) find out -name '*meta_lic' -type f -printf '"%p"\n' >$$(PRIVATE_LIST_FILE)
+	OUT_DIR=$(OUT_DIR) $(COMPLIANCENOTICE_SHIPPEDLIBS) @$$(PRIVATE_LIST_FILE)
+endef
+
+###########################################################
+# Declares the rule to build all license metadata.
+###########################################################
+define build-all-license-metadata-rule
+$(strip $(eval _all := $(call all-license-metadata)))
+
+.PHONY: alllicensemetadata
+alllicensemetadata: $(_all)
+	@echo Building all $(words $(_all)) license metadata files
+endef
+
+
+###########################################################
 ## Declares a license metadata build rule for ALL_MODULES
 ###########################################################
 define build-license-metadata
-$(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m))))
+$(strip \
+  $(strip $(eval _dir := $(call license-metadata-dir))) \
+  $(foreach t,$(sort $(ALL_0P_TARGETS)), \
+    $(eval ALL_TARGETS.$(t).META_LIC := 0p) \
+  ) \
+  $(foreach t,$(sort $(ALL_NON_MODULES)), \
+    $(eval ALL_TARGETS.$(t).META_LIC := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(t).meta_lic))) \
+  ) \
+  $(foreach t,$(sort $(ALL_NON_MODULES)),$(eval $(call non-module-license-metadata-rule,$(t)))) \
+  $(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m)))) \
+  $(eval $(call report-missing-licenses-rule)) \
+  $(eval $(call report-all-notice-library-names-rule)) \
+  $(eval $(call build-all-license-metadata-rule)))
 endef
 
 ###########################################################
@@ -1106,11 +1399,11 @@
 $(hide) mkdir -p $(dir $@)
 $(hide) $(BCC_COMPAT) -O3 -o $(dir $@)/$(notdir $(<:.bc=.o)) -fPIC -shared \
   -rt-path $(RS_PREBUILT_CLCORE) -mtriple $(RS_COMPAT_TRIPLE) $<
-$(hide) $(PRIVATE_CXX_LINK) -shared -Wl,-soname,$(notdir $@) -nostdlib \
+$(hide) $(PRIVATE_CXX_LINK) -fuse-ld=lld -target $(CLANG_TARGET_TRIPLE) -shared -Wl,-soname,$(notdir $@) -nostdlib \
   -Wl,-rpath,\$$ORIGIN/../lib \
   $(dir $@)/$(notdir $(<:.bc=.o)) \
   $(RS_PREBUILT_COMPILER_RT) \
-  -o $@ $(CLANG_TARGET_GLOBAL_LDFLAGS) -Wl,--hash-style=sysv \
+  -o $@ $(CLANG_TARGET_GLOBAL_LLDFLAGS) -Wl,--hash-style=sysv \
   -L $(SOONG_OUT_DIR)/ndk/platforms/android-$(PRIVATE_SDK_VERSION)/arch-$(TARGET_ARCH)/usr/lib64 \
   -L $(SOONG_OUT_DIR)/ndk/platforms/android-$(PRIVATE_SDK_VERSION)/arch-$(TARGET_ARCH)/usr/lib \
   $(call intermediates-dir-for,SHARED_LIBRARIES,libRSSupport)/libRSSupport.so \
@@ -1928,21 +2221,10 @@
 # b/37750224
 AAPT_ASAN_OPTIONS := ASAN_OPTIONS=detect_leaks=0
 
-# Search for generated R.java/Manifest.java in $1, copy the found R.java as $2.
-# Also copy them to a central 'R' directory to make it easier to add the files to an IDE.
+# Search for generated R.java in $1, copy the found R.java as $2.
 define find-generated-R.java
-$(hide) for GENERATED_MANIFEST_FILE in `find $(1) \
-  -name Manifest.java 2> /dev/null`; do \
-    dir=`awk '/package/{gsub(/\./,"/",$$2);gsub(/;/,"",$$2);print $$2;exit}' $$GENERATED_MANIFEST_FILE`; \
-    mkdir -p $(TARGET_COMMON_OUT_ROOT)/R/$$dir; \
-    cp $$GENERATED_MANIFEST_FILE $(TARGET_COMMON_OUT_ROOT)/R/$$dir; \
-  done;
 $(hide) for GENERATED_R_FILE in `find $(1) \
   -name R.java 2> /dev/null`; do \
-    dir=`awk '/package/{gsub(/\./,"/",$$2);gsub(/;/,"",$$2);print $$2;exit}' $$GENERATED_R_FILE`; \
-    mkdir -p $(TARGET_COMMON_OUT_ROOT)/R/$$dir; \
-    cp $$GENERATED_R_FILE $(TARGET_COMMON_OUT_ROOT)/R/$$dir \
-      || exit 31; \
     cp $$GENERATED_R_FILE $(2) || exit 32; \
   done;
 @# Ensure that the target file is always created, i.e. also in case we did not
@@ -2077,7 +2359,59 @@
         @$(call emit-line,$(wordlist 12001,12500,$(1)),$(2))
         @$(call emit-line,$(wordlist 12501,13000,$(1)),$(2))
         @$(call emit-line,$(wordlist 13001,13500,$(1)),$(2))
-        @$(if $(wordlist 13501,13502,$(1)),$(error Too many words ($(words $(1)))))
+        @$(call emit-line,$(wordlist 13501,14000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 14001,14500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 14501,15000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 15001,15500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 15501,16000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 16001,16500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 16501,17000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 17001,17500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 17501,18000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 18001,18500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 18501,19000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 19001,19500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 19501,20000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 20001,20500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 20501,21000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 21001,21500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 21501,22000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 22001,22500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 22501,23000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 23001,23500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 23501,24000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 24001,24500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 24501,25000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 25001,25500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 25501,26000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 26001,26500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 26501,27000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 27001,27500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 27501,28000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 28001,28500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 28501,29000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 29001,29500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 29501,30000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 30001,30500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 30501,31000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 31001,31500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 31501,32000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 32001,32500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 32501,33000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 33001,33500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 33501,34000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 34001,34500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 34501,35000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 35001,35500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 35501,36000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 36001,36500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 36501,37000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 37001,37500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 37501,38000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 38001,38500,$(1)),$(2))
+        @$(call emit-line,$(wordlist 38501,39000,$(1)),$(2))
+        @$(call emit-line,$(wordlist 39001,39500,$(1)),$(2))
+        @$(if $(wordlist 39501,39502,$(1)),$(error Too many words ($(words $(1)))))
 endef
 # Return jar arguments to compress files in a given directory
 # $(1): directory
@@ -2340,6 +2674,7 @@
 define add-jar-resources-to-package
   rm -rf $(3)
   mkdir -p $(3)
+  zipinfo -1 $(2) > /dev/null
   unzip -qo $(2) -d $(3) $$(zipinfo -1 $(2) | grep -v -E "\.class$$")
   $(JAR) uf $(1) $(call jar-args-sorted-files-in-directory,$(3))
 endef
@@ -2361,6 +2696,7 @@
 $(hide) mv $(1) $(1).unsigned
 $(hide) $(JAVA) -Djava.library.path=$$(dirname $(SIGNAPK_JNI_LIBRARY_PATH)) -jar $(SIGNAPK_JAR) \
     $(if $(strip $(PRIVATE_CERTIFICATE_LINEAGE)), --lineage $(PRIVATE_CERTIFICATE_LINEAGE)) \
+    $(if $(strip $(PRIVATE_ROTATION_MIN_SDK_VERSION)), --rotation-min-sdk-version $(PRIVATE_ROTATION_MIN_SDK_VERSION)) \
     $(PRIVATE_CERTIFICATE) $(PRIVATE_PRIVATE_KEY) \
     $(PRIVATE_ADDITIONAL_CERTIFICATES) $(1).unsigned $(1).signed
 $(hide) mv $(1).signed $(1)
@@ -2782,6 +3118,8 @@
 # $(3): full path to destination
 define symlink-file
 $(eval $(_symlink-file))
+$(eval $(call declare-license-metadata,$(3),,,,,,))
+$(eval $(call declare-license-deps,$(3),$(1)))
 endef
 
 define _symlink-file
@@ -2817,6 +3155,50 @@
 fi
 endef
 
+# Copy an unstripped binary to the symbols directory while also extracting
+# a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in symbols directory
+define copy-unstripped-elf-file-with-mapping
+$(call _copy-symbols-file-with-mapping,$(1),$(2),\
+  elf,$(patsubst $(TARGET_OUT_UNSTRIPPED)/%,$(call intermediates-dir-for,PACKAGING,elf_symbol_mapping)/%,$(2).textproto))
+endef
+
+# Copy an R8 dictionary to the packaging directory while also extracting
+# a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in packaging directory
+# $(3): path in mappings packaging directory
+define copy-r8-dictionary-file-with-mapping
+$(call _copy-symbols-file-with-mapping,$(1),$(2),r8,$(3))
+endef
+
+# Copy an unstripped binary or R8 dictionary to the symbols directory
+# while also extracting a hash mapping to the mapping directory.
+# $(1): unstripped intermediates file
+# $(2): path in symbols directory
+# $(3): file type (elf or r8)
+# $(4): path in the mappings directory
+define _copy-symbols-file-with-mapping
+$(2): .KATI_IMPLICIT_OUTPUTS := $(4)
+$(2): $(SYMBOLS_MAP)
+$(2): $(1)
+	@echo "Copy symbols with mapping: $$@"
+	$$(copy-file-to-target)
+	$(SYMBOLS_MAP) -$(strip $(3)) $(2) -write_if_changed $(4)
+.KATI_RESTAT: $(2)
+endef
+
+# Returns the directory to copy proguard dictionaries into
+define local-proguard-dictionary-directory
+$(call intermediates-dir-for,PACKAGING,proguard_dictionary)/out/target/common/obj/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates
+endef
+
+# Returns the directory to copy proguard dictionary mappings into
+define local-proguard-dictionary-mapping-directory
+$(call intermediates-dir-for,PACKAGING,proguard_dictionary_mapping)/out/target/common/obj/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates
+endef
+
 
 ###########################################################
 ## Commands to call R8
@@ -3004,9 +3386,10 @@
 # Can be passed a subdirectory to use for the common testcase directory.
 define compatibility_suite_dirs
   $(strip \
-    $(if $(COMPATIBILITY_TESTCASES_OUT_INCLUDE_MODULE_FOLDER_$(1)),\
-      $(COMPATIBILITY_TESTCASES_OUT_$(1))/$(LOCAL_MODULE)$(2),\
-      $(COMPATIBILITY_TESTCASES_OUT_$(1))) \
+    $(if $(COMPATIBILITY_TESTCASES_OUT_$(1)), \
+      $(if $(COMPATIBILITY_TESTCASES_OUT_INCLUDE_MODULE_FOLDER_$(1))$(LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY),\
+        $(COMPATIBILITY_TESTCASES_OUT_$(1))/$(LOCAL_MODULE)$(2),\
+        $(COMPATIBILITY_TESTCASES_OUT_$(1)))) \
     $($(my_prefix)OUT_TESTCASES)/$(LOCAL_MODULE)$(2))
 endef
 
@@ -3021,6 +3404,14 @@
 #    and use my_compat_dist_$(suite) to define the others.
 define create-suite-dependencies
 $(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
+  $(eval $(if $(strip $(module_license_metadata)),\
+    $$(foreach f,$$(my_compat_dist_$(suite)),$$(eval ALL_TARGETS.$$(call word-colon,2,$$(f)).META_LIC := $(module_license_metadata))),\
+    $$(eval my_test_data += $$(foreach f,$$(my_compat_dist_$(suite)), $$(call word-colon,2,$$(f)))) \
+  )) \
+  $(eval $(if $(strip $(module_license_metadata)),\
+    $$(foreach f,$$(my_compat_dist_config_$(suite)),$$(eval ALL_TARGETS.$$(call word-colon,2,$$(f)).META_LIC := $(module_license_metadata))),\
+    $$(eval my_test_config += $$(foreach f,$$(my_compat_dist_config_$(suite)), $$(call word-colon,2,$$(f)))) \
+  )) \
   $(if $(filter $(suite),$(ALL_COMPATIBILITY_SUITES)),,\
     $(eval ALL_COMPATIBILITY_SUITES += $(suite)) \
     $(eval COMPATIBILITY.$(suite).FILES :=) \
diff --git a/core/dex_preopt_config.mk b/core/dex_preopt_config.mk
index 0c806c1..d5293cf 100644
--- a/core/dex_preopt_config.mk
+++ b/core/dex_preopt_config.mk
@@ -109,6 +109,8 @@
   $(call add_json_list, SystemServerJars,                        $(PRODUCT_SYSTEM_SERVER_JARS))
   $(call add_json_list, SystemServerApps,                        $(PRODUCT_SYSTEM_SERVER_APPS))
   $(call add_json_list, ApexSystemServerJars,                    $(PRODUCT_APEX_SYSTEM_SERVER_JARS))
+  $(call add_json_list, StandaloneSystemServerJars,              $(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS))
+  $(call add_json_list, ApexStandaloneSystemServerJars,          $(PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS))
   $(call add_json_bool, BrokenSuboptimalOrderOfSystemServerJars, $(PRODUCT_BROKEN_SUBOPTIMAL_ORDER_OF_SYSTEM_SERVER_JARS))
   $(call add_json_list, SpeedApps,                               $(PRODUCT_DEXPREOPT_SPEED_APPS))
   $(call add_json_list, PreoptFlags,                             $(PRODUCT_DEX_PREOPT_DEFAULT_FLAGS))
diff --git a/core/dex_preopt_libart.mk b/core/dex_preopt_libart.mk
index 393053d..a2c9942 100644
--- a/core/dex_preopt_libart.mk
+++ b/core/dex_preopt_libart.mk
@@ -17,52 +17,83 @@
 #
 ####################################
 
-# Install $(1) to $(2) so that it is shared between architectures.
-# Returns the target path of the shared vdex file and installed symlink.
-define copy-vdex-file
-$(strip \
-  $(eval # Remove the arch dir) \
-  $(eval my_vdex_shared := $(dir $(patsubst %/,%,$(dir $(2))))$(notdir $(2))) \
-  $(if $(filter-out %_2ND_ARCH,$(my_boot_image_arch)), \
-    $(eval # Copy $(1) to directory one level up (i.e. with the arch dir removed).) \
-    $(eval $(call copy-one-file,$(1),$(my_vdex_shared))) \
-  ) \
-  $(eval # Create symlink at $(2) which points to the actual physical copy.) \
-  $(call symlink-file,$(my_vdex_shared),../$(notdir $(2)),$(2)) \
-  $(my_vdex_shared) $(2) \
-)
+# Takes a list of src:dest install pairs and returns a new list with a path
+# prefixed to each dest value.
+# $(1): list of src:dest install pairs
+# $(2): path to prefix to each dest value
+define prefix-copy-many-files-dest
+$(foreach v,$(1),$(call word-colon,1,$(v)):$(2)$(call word-colon,2,$(v)))
 endef
 
-# Same as 'copy-many-files' but it uses the vdex-specific helper above.
-define copy-vdex-files
-$(foreach v,$(1),$(call copy-vdex-file,$(call word-colon,1,$(v)),$(2)$(call word-colon,2,$(v))))
+# Converts an architecture-specific vdex path into a location that can be shared
+# between architectures.
+define vdex-shared-install-path
+$(dir $(patsubst %/,%,$(dir $(1))))$(notdir $(1))
+endef
+
+# Takes a list of src:dest install pairs of vdex files and returns a new list
+# where each dest has been rewritten to the shared location for vdex files.
+define vdex-copy-many-files-shared-dest
+$(foreach v,$(1),$(call word-colon,1,$(v)):$(call vdex-shared-install-path,$(call word-colon,2,$(v))))
+endef
+
+# Creates a rule to symlink an architecture specific vdex file to the shared
+# location for that vdex file.
+define symlink-vdex-file
+$(strip \
+  $(call symlink-file,\
+    $(call vdex-shared-install-path,$(1)),\
+    ../$(notdir $(1)),\
+    $(1))\
+  $(1))
+endef
+
+# Takes a list of src:dest install pairs of vdex files and creates rules to
+# symlink each dest to the shared location for that vdex file.
+define symlink-vdex-files
+$(foreach v,$(1),$(call symlink-vdex-file,$(call word-colon,2,$(v))))
 endef
 
 my_boot_image_module :=
 
 my_suffix := $(my_boot_image_name)_$($(my_boot_image_arch))
-my_copy_pairs := $(strip $(DEXPREOPT_IMAGE_BUILT_INSTALLED_$(my_suffix)))
+my_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
+my_vdex_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
+my_vdex_copy_shared_pairs := $(call vdex-copy-many-files-shared-dest,$(my_vdex_copy_pairs))
+ifeq (,$(filter %_2ND_ARCH,$(my_boot_image_arch)))
+  # Only install the vdex to the shared location for the primary architecture.
+  my_copy_pairs += $(my_vdex_copy_shared_pairs)
+endif
+
+my_unstripped_copy_pairs := $(call prefix-copy-many-files-dest,$(DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_syms))
 
 # Generate the boot image module only if there is any file to install.
-ifneq (,$(my_copy_pairs))
+ifneq (,$(strip $(my_copy_pairs)))
   my_first_pair := $(firstword $(my_copy_pairs))
   my_rest_pairs := $(wordlist 2,$(words $(my_copy_pairs)),$(my_copy_pairs))
 
   my_first_src := $(call word-colon,1,$(my_first_pair))
-  my_first_dest := $(my_boot_image_out)$(call word-colon,2,$(my_first_pair))
+  my_first_dest := $(call word-colon,2,$(my_first_pair))
 
-  my_installed := $(call copy-many-files,$(my_rest_pairs),$(my_boot_image_out))
-  my_installed += $(call copy-vdex-files,$(DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_out))
-  my_unstripped_installed := $(call copy-many-files,$(DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_$(my_suffix)),$(my_boot_image_syms))
+  my_installed := $(call copy-many-files,$(my_copy_pairs))
+  my_unstripped_installed := $(call copy-many-files,$(my_unstripped_copy_pairs))
+
+  my_symlinks := $(call symlink-vdex-files,$(my_vdex_copy_pairs))
 
   # We don't have a LOCAL_PATH for the auto-generated modules, so let it be the $(BUILD_SYSTEM).
   LOCAL_PATH := $(BUILD_SYSTEM)
+  # Hack to let these pseudo-modules wrapped around Soong modules use LOCAL_SOONG_INSTALLED_MODULE.
+  LOCAL_MODULE_MAKEFILE := $(SOONG_ANDROID_MK)
 
   include $(CLEAR_VARS)
   LOCAL_MODULE := dexpreopt_bootjar.$(my_suffix)
   LOCAL_PREBUILT_MODULE_FILE := $(my_first_src)
   LOCAL_MODULE_PATH := $(dir $(my_first_dest))
   LOCAL_MODULE_STEM := $(notdir $(my_first_dest))
+  LOCAL_SOONG_INSTALL_PAIRS := $(my_copy_pairs)
+  LOCAL_SOONG_INSTALL_SYMLINKS := $(my_symlinks)
+  LOCAL_SOONG_INSTALLED_MODULE := $(my_first_dest)
+  LOCAL_SOONG_LICENSE_METADATA := $(DEXPREOPT_IMAGE_LICENSE_METADATA_$(my_suffix))
   ifneq (,$(strip $(filter HOST_%,$(my_boot_image_arch))))
     LOCAL_IS_HOST_MODULE := true
   endif
@@ -71,9 +102,8 @@
   $(LOCAL_BUILT_MODULE): | $(my_unstripped_installed)
   # Installing boot.art causes all boot image bits to be installed.
   # Keep this old behavior in case anyone still needs it.
-  $(LOCAL_INSTALLED_MODULE): $(my_installed)
-  ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-  $(my_all_targets): $(my_installed)
+  $(LOCAL_INSTALLED_MODULE): $(wordlist 2,$(words $(my_installed)),$(my_installed)) $(my_symlinks)
+  $(my_all_targets): $(my_installed) $(my_symlinks)
 
   my_boot_image_module := $(LOCAL_MODULE)
 endif  # my_copy_pairs != empty
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index 1983022..ea50313 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -60,11 +60,6 @@
   LOCAL_DEX_PREOPT :=
 endif
 
-# Don't preopt system server jars that are updatable.
-ifneq (,$(filter %:$(LOCAL_MODULE), $(PRODUCT_APEX_SYSTEM_SERVER_JARS)))
-  LOCAL_DEX_PREOPT :=
-endif
-
 # if WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY=true and module is not in boot class path skip
 # Also preopt system server jars since selinux prevents system server from loading anything from
 # /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
@@ -278,6 +273,7 @@
 my_dexpreopt_image_locations_on_host :=
 my_dexpreopt_image_locations_on_device :=
 my_dexpreopt_infix := boot
+my_create_dexpreopt_config :=
 ifeq (true, $(DEXPREOPT_USE_ART_IMAGE))
   my_dexpreopt_infix := art
 endif
@@ -293,7 +289,16 @@
       LOCAL_UNCOMPRESS_DEX := true
     endif
   endif
+  my_create_dexpreopt_config := true
+endif
 
+# dexpreopt is disabled when TARGET_BUILD_UNBUNDLED_IMAGE is true,
+# but dexpreopt config files are required to dexpreopt in post-processing.
+ifeq ($(TARGET_BUILD_UNBUNDLED_IMAGE),true)
+  my_create_dexpreopt_config := true
+endif
+
+ifeq ($(my_create_dexpreopt_config), true)
   ifeq ($(LOCAL_MODULE_CLASS),JAVA_LIBRARIES)
     my_module_multilib := $(LOCAL_MULTILIB)
     # If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
@@ -402,8 +407,6 @@
 
   my_dexpreopt_config := $(intermediates)/dexpreopt.config
   my_dexpreopt_config_for_postprocessing := $(PRODUCT_OUT)/dexpreopt_config/$(LOCAL_MODULE)_dexpreopt.config
-  my_dexpreopt_script := $(intermediates)/dexpreopt.sh
-  my_dexpreopt_zip := $(intermediates)/dexpreopt.zip
   my_dexpreopt_config_merger := $(BUILD_SYSTEM)/dex_preopt_config_merger.py
 
   $(my_dexpreopt_config): $(my_dexpreopt_dep_configs) $(my_dexpreopt_config_merger)
@@ -416,12 +419,39 @@
 	echo -e -n '$(subst $(newline),\n,$(subst ','\'',$(subst \,\\,$(PRIVATE_CONTENTS))))' > $@
 	$(PRIVATE_CONFIG_MERGER) $@ $(PRIVATE_DEP_CONFIGS)
 
+$(eval $(call copy-one-file,$(my_dexpreopt_config),$(my_dexpreopt_config_for_postprocessing)))
+
+$(LOCAL_INSTALLED_MODULE): $(my_dexpreopt_config_for_postprocessing)
+
+# System server jars defined in Android.mk are deprecated.
+ifneq (true, $(PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS))
+  ifneq (,$(filter %:$(LOCAL_MODULE), $(PRODUCT_SYSTEM_SERVER_JARS) $(PRODUCT_APEX_SYSTEM_SERVER_JARS)))
+    $(error System server jars defined in Android.mk are deprecated. \
+      Convert $(LOCAL_MODULE) to Android.bp or temporarily disable the error \
+      with 'PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS := true')
+  endif
+endif
+
+ifdef LOCAL_DEX_PREOPT
+  # System server jars must be copied into predefined locations expected by
+  # dexpreopt. Copy rule must be exposed to Ninja (as it uses these files as
+  # inputs), so it cannot go in dexpreopt.sh.
+  ifneq (,$(filter %:$(LOCAL_MODULE), $(PRODUCT_SYSTEM_SERVER_JARS)))
+    my_dexpreopt_jar_copy := $(OUT_DIR)/soong/system_server_dexjars/$(LOCAL_MODULE).jar
+    $(my_dexpreopt_jar_copy): PRIVATE_BUILT_MODULE := $(LOCAL_BUILT_MODULE)
+    $(my_dexpreopt_jar_copy): $(LOCAL_BUILT_MODULE)
+	  @cp $(PRIVATE_BUILT_MODULE) $@
+  endif
+
+  my_dexpreopt_script := $(intermediates)/dexpreopt.sh
+  my_dexpreopt_zip := $(intermediates)/dexpreopt.zip
   .KATI_RESTAT: $(my_dexpreopt_script)
   $(my_dexpreopt_script): PRIVATE_MODULE := $(LOCAL_MODULE)
   $(my_dexpreopt_script): PRIVATE_GLOBAL_SOONG_CONFIG := $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE)
   $(my_dexpreopt_script): PRIVATE_GLOBAL_CONFIG := $(DEX_PREOPT_CONFIG_FOR_MAKE)
   $(my_dexpreopt_script): PRIVATE_MODULE_CONFIG := $(my_dexpreopt_config)
   $(my_dexpreopt_script): $(DEXPREOPT_GEN)
+  $(my_dexpreopt_script): $(my_dexpreopt_jar_copy)
   $(my_dexpreopt_script): $(my_dexpreopt_config) $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(DEX_PREOPT_CONFIG_FOR_MAKE)
 	@echo "$(PRIVATE_MODULE) dexpreopt gen"
 	$(DEXPREOPT_GEN) \
@@ -431,8 +461,6 @@
 	-dexpreopt_script $@ \
 	-out_dir $(OUT_DIR)
 
-  $(eval $(call copy-one-file,$(my_dexpreopt_config),$(my_dexpreopt_config_for_postprocessing)))
-
   my_dexpreopt_deps := $(my_dex_jar)
   my_dexpreopt_deps += $(if $(my_process_profile),$(LOCAL_DEX_PREOPT_PROFILE))
   my_dexpreopt_deps += \
@@ -468,7 +496,6 @@
 
   $(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
   $(LOCAL_INSTALLED_MODULE): $(my_dexpreopt_zip)
-  $(LOCAL_INSTALLED_MODULE): $(my_dexpreopt_config_for_postprocessing)
 
   $(my_all_targets): $(my_dexpreopt_zip)
 
@@ -477,3 +504,4 @@
   my_dexpreopt_zip :=
   my_dexpreopt_config_for_postprocessing :=
 endif # LOCAL_DEX_PREOPT
+endif # my_create_dexpreopt_config
\ No newline at end of file
diff --git a/core/dumpconfig.mk b/core/dumpconfig.mk
index 9b1f2c2..640fe10 100644
--- a/core/dumpconfig.mk
+++ b/core/dumpconfig.mk
@@ -117,7 +117,6 @@
 	9 \
 	LOCAL_PATH \
 	MAKEFILE_LIST \
-	PARENT_PRODUCT_FILES \
 	current_mk \
 	_eiv_ev \
 	_eiv_i \
diff --git a/core/dumpvar.mk b/core/dumpvar.mk
index 6b5c030..6f3d14f 100644
--- a/core/dumpvar.mk
+++ b/core/dumpvar.mk
@@ -35,3 +35,7 @@
 	  printf "'\n";)
 
 endif # CALLED_FROM_SETUP
+
+ifneq (,$(RBC_DUMP_CONFIG_FILE))
+$(call dump-variables-rbc,$(RBC_DUMP_CONFIG_FILE))
+endif
diff --git a/core/dynamic_binary.mk b/core/dynamic_binary.mk
index a9b3720..0d2cd7f 100644
--- a/core/dynamic_binary.mk
+++ b/core/dynamic_binary.mk
@@ -25,13 +25,8 @@
 # The includer of this file will define a rule to build this target.
 linked_module := $(intermediates)/LINKED/$(notdir $(my_installed_module_stem))
 
-ALL_ORIGINAL_DYNAMIC_BINARIES += $(linked_module)
-
-# Because TARGET_SYMBOL_FILTER_FILE depends on ALL_ORIGINAL_DYNAMIC_BINARIES,
-# the linked_module rules won't necessarily inherit the PRIVATE_
-# variables from LOCAL_BUILT_MODULE.  This tells binary.make to explicitly
-# define the PRIVATE_ variables for linked_module as well as for
-# LOCAL_BUILT_MODULE.
+# This tells binary.make to explicitly define the PRIVATE_ variables for
+# linked_module as well as for LOCAL_BUILT_MODULE.
 LOCAL_INTERMEDIATE_TARGETS := $(linked_module)
 
 ###################################
@@ -60,9 +55,7 @@
 endif
 symbolic_input := $(inject_module)
 symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-$(symbolic_output) : $(symbolic_input)
-	@echo "target Symbolic: $(PRIVATE_MODULE) ($@)"
-	$(copy-file-to-target)
+$(eval $(call copy-unstripped-elf-file-with-mapping,$(symbolic_input),$(symbolic_output)))
 
 ###########################################################
 ## Store breakpad symbols
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 8c25086..c32d380b 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -167,6 +167,10 @@
   else
     $(error Unsupported HOST_CROSS_OS $(HOST_CROSS_OS))
   endif
+else ifeq ($(HOST_OS),darwin)
+  HOST_CROSS_OS := darwin
+  HOST_CROSS_ARCH := arm64
+  HOST_CROSS_2ND_ARCH :=
 endif
 
 ifeq ($(HOST_OS),)
@@ -255,6 +259,7 @@
 # TARGET_COPY_OUT_* are all relative to the staging directory, ie PRODUCT_OUT.
 # Define them here so they can be used in product config files.
 TARGET_COPY_OUT_SYSTEM := system
+TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
 TARGET_COPY_OUT_SYSTEM_OTHER := system_other
 TARGET_COPY_OUT_DATA := data
 TARGET_COPY_OUT_ASAN := $(TARGET_COPY_OUT_DATA)/asan
@@ -274,8 +279,10 @@
 _odm_path_placeholder := ||ODM-PATH-PH||
 _vendor_dlkm_path_placeholder := ||VENDOR_DLKM-PATH-PH||
 _odm_dlkm_path_placeholder := ||ODM_DLKM-PATH-PH||
+_system_dlkm_path_placeholder := ||SYSTEM_DLKM-PATH-PH||
 TARGET_COPY_OUT_VENDOR := $(_vendor_path_placeholder)
 TARGET_COPY_OUT_VENDOR_RAMDISK := vendor_ramdisk
+TARGET_COPY_OUT_VENDOR_KERNEL_RAMDISK := vendor_kernel_ramdisk
 TARGET_COPY_OUT_PRODUCT := $(_product_path_placeholder)
 # TODO(b/135957588) TARGET_COPY_OUT_PRODUCT_SERVICES will copy the target to
 # product
@@ -284,6 +291,7 @@
 TARGET_COPY_OUT_ODM := $(_odm_path_placeholder)
 TARGET_COPY_OUT_VENDOR_DLKM := $(_vendor_dlkm_path_placeholder)
 TARGET_COPY_OUT_ODM_DLKM := $(_odm_dlkm_path_placeholder)
+TARGET_COPY_OUT_SYSTEM_DLKM := $(_system_dlkm_path_placeholder)
 
 # Returns the non-sanitized version of the path provided in $1.
 define get_non_asan_path
@@ -293,8 +301,11 @@
 #################################################################
 # Set up minimal BOOTCLASSPATH list of jars to build/execute
 # java code with dalvikvm/art.
-# Jars present in the ART apex. These should match exactly the list of
-# Java libraries in the ART apex build rule.
+# Jars present in the ART apex. These should match exactly the list of Java
+# libraries in art-bootclasspath-fragment. The APEX variant name
+# (com.android.art) is the same regardless which Soong module provides the ART
+# APEX. See the long comment in build/soong/java/dexprepopt_bootjars.go for
+# details.
 ART_APEX_JARS := \
     com.android.art:core-oj \
     com.android.art:core-libart \
@@ -307,6 +318,37 @@
 endif
 #################################################################
 
+# Dumps all variables that match [A-Z][A-Z0-9_]* (with a few exceptions)
+# to the file at $(1). It is used to print only the variables that are
+# likely to be relevant to the product or board configuration.
+# Soong config variables are dumped as $(call soong_config_set) calls
+# instead of the raw variable values, because mk2rbc can't read the
+# raw ones.
+define dump-variables-rbc
+$(eval _dump_variables_rbc_excluded := \
+  BOARD_PLAT_PRIVATE_SEPOLICY_DIR \
+  BOARD_PLAT_PUBLIC_SEPOLICY_DIR \
+  BUILD_NUMBER \
+  DATE \
+  LOCAL_PATH \
+  MAKEFILE_LIST \
+  PRODUCTS \
+  PRODUCT_COPY_OUT_% \
+  RBC_PRODUCT_CONFIG \
+  RBC_BOARD_CONFIG \
+  SOONG_% \
+  TOPDIR \
+  TRACE_BEGIN_SOONG \
+  USER)
+$(file >$(OUT_DIR)/dump-variables-rbc-temp.txt,$(subst $(space),$(newline),$(sort $(filter-out $(_dump_variables_rbc_excluded),$(.VARIABLES)))))
+$(file >$(1),\
+$(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-variables-rbc-temp.txt),\
+$(v) := $(strip $($(v)))$(newline))\
+$(foreach ns,$(sort $(SOONG_CONFIG_NAMESPACES)),\
+$(foreach v,$(sort $(SOONG_CONFIG_$(ns))),\
+$$(call soong_config_set,$(ns),$(v),$(SOONG_CONFIG_$(ns)_$(v)))$(newline))))
+endef
+
 # Read the product specs so we can get TARGET_DEVICE and other
 # variables that we need in order to locate the output files.
 include $(BUILD_SYSTEM)/product_config.mk
@@ -320,6 +362,12 @@
 SDK_HOST_ARCH := x86
 TARGET_OS := linux
 
+# Some board configuration files use $(PRODUCT_OUT)
+TARGET_OUT_ROOT := $(OUT_DIR)/target
+TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/product
+PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)
+.KATI_READONLY := TARGET_OUT_ROOT TARGET_PRODUCT_OUT_ROOT PRODUCT_OUT
+
 include $(BUILD_SYSTEM)/board_config.mk
 
 # the target build type defaults to release
@@ -332,28 +380,24 @@
 
 SOONG_OUT_DIR := $(OUT_DIR)/soong
 
-TARGET_OUT_ROOT := $(OUT_DIR)/target
-
 HOST_OUT_ROOT := $(OUT_DIR)/host
 
-.KATI_READONLY := SOONG_OUT_DIR TARGET_OUT_ROOT HOST_OUT_ROOT
+.KATI_READONLY := SOONG_OUT_DIR HOST_OUT_ROOT
 
 # We want to avoid two host bin directories in multilib build.
 HOST_OUT := $(HOST_OUT_ROOT)/$(HOST_OS)-$(HOST_PREBUILT_ARCH)
-SOONG_HOST_OUT := $(SOONG_OUT_DIR)/host/$(HOST_OS)-$(HOST_PREBUILT_ARCH)
+
+# Soong now installs to the same directory as Make.
+SOONG_HOST_OUT := $(HOST_OUT)
 
 HOST_CROSS_OUT := $(HOST_OUT_ROOT)/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)
 
 .KATI_READONLY := HOST_OUT SOONG_HOST_OUT HOST_CROSS_OUT
 
-TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/product
-
 TARGET_COMMON_OUT_ROOT := $(TARGET_OUT_ROOT)/common
 HOST_COMMON_OUT_ROOT := $(HOST_OUT_ROOT)/common
 
-PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)
-
-.KATI_READONLY := TARGET_PRODUCT_OUT_ROOT TARGET_COMMON_OUT_ROOT HOST_COMMON_OUT_ROOT PRODUCT_OUT
+.KATI_READONLY := TARGET_COMMON_OUT_ROOT HOST_COMMON_OUT_ROOT
 
 OUT_DOCS := $(TARGET_COMMON_OUT_ROOT)/docs
 OUT_NDK_DOCS := $(TARGET_COMMON_OUT_ROOT)/ndk-docs
@@ -814,6 +858,36 @@
     $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_ODM_DLKM_APPS_PRIVILEGED \
     , odm_dlkm should not contain any executables, libraries, or apps)
 
+TARGET_OUT_SYSTEM_DLKM := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM_DLKM)
+
+# Unlike other partitions, system_dlkm should only contain kernel modules.
+TARGET_OUT_SYSTEM_DLKM_EXECUTABLES :=
+TARGET_OUT_SYSTEM_DLKM_OPTIONAL_EXECUTABLES :=
+TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES :=
+TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE :=
+TARGET_OUT_SYSTEM_DLKM_JAVA_LIBRARIES :=
+TARGET_OUT_SYSTEM_DLKM_APPS :=
+TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_EXECUTABLES :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED :=
+$(KATI_obsolete_var \
+    TARGET_OUT_SYSTEM_DLKM_EXECUTABLES \
+    TARGET_OUT_SYSTEM_DLKM_OPTIONAL_EXECUTABLES \
+    TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES \
+    TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE \
+    TARGET_OUT_SYSTEM_DLKM_JAVA_LIBRARIES \
+    TARGET_OUT_SYSTEM_DLKM_APPS \
+    TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED \
+    $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_EXECUTABLES \
+    $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES \
+    $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE \
+    $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS \
+    $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED \
+    , system_dlkm should not contain any executables, libraries, or apps)
+
 TARGET_OUT_PRODUCT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_PRODUCT)
 TARGET_OUT_PRODUCT_EXECUTABLES := $(TARGET_OUT_PRODUCT)/bin
 .KATI_READONLY := TARGET_OUT_PRODUCT
@@ -926,7 +1000,11 @@
 TARGET_VENDOR_DEBUG_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK)
 TARGET_TEST_HARNESS_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_TEST_HARNESS_RAMDISK)
 
+TARGET_SYSTEM_DLKM_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM_DLKM)
+.KATI_READONLY := TARGET_SYSTEM_DLKM_OUT
+
 TARGET_VENDOR_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_RAMDISK)
+TARGET_VENDOR_KERNEL_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_KERNEL_RAMDISK)
 
 TARGET_ROOT_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_ROOT)
 TARGET_ROOT_OUT_BIN := $(TARGET_ROOT_OUT)/bin
diff --git a/core/envsetup.rbc b/core/envsetup.rbc
deleted file mode 100644
index 451623b..0000000
--- a/core/envsetup.rbc
+++ /dev/null
@@ -1,207 +0,0 @@
-# Copyright 2021 Google LLC
-#
-# 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
-#
-#      https://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.
-
-load(":build_id.rbc|init", _build_id_init = "init")
-
-def _all_versions():
-    """Returns all known versions."""
-    versions = ["OPR1", "OPD1", "OPD2", "OPM1", "OPM2", "PPR1", "PPD1", "PPD2", "PPM1", "PPM2", "QPR1"]
-    for v in ("Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"):
-        for e in ("P1A", "P1B", "P2A", "P2B", "D1A", "D1B", "D2A", "D2B", "Q1A", "Q1B", "Q2A", "Q2B", "Q3A", "Q3B"):
-            versions.append(v + e)
-    return versions
-
-def _allowed_versions(all_versions, min_version, max_version, default_version):
-    """Checks that version range and default versions is valid, returns all versions in range."""
-    for v in (min_version, max_version, default_version):
-        if v not in all_versions:
-            fail("% is invalid" % v)
-
-    min_i = all_versions.index(min_version)
-    max_i = all_versions.index(max_version)
-    def_i = all_versions.index(default_version)
-    if min_i > max_i:
-        fail("%s should come before %s in the version list" % (min_version, max_version))
-    if def_i < min_i or def_i > max_i:
-        fail("%s should come between % and %s" % (default_version, min_version, max_version))
-    return all_versions[min_i:max_i + 1]
-
-# This function is a manual conversion of the version_defaults.mk
-def _versions_default(g, all_versions):
-    """Handle various build version information.
-
-    Guarantees that the following are defined:
-     PLATFORM_VERSION
-     PLATFORM_SDK_VERSION
-     PLATFORM_VERSION_CODENAME
-     DEFAULT_APP_TARGET_SDK
-     BUILD_ID
-     BUILD_NUMBER
-     PLATFORM_SECURITY_PATCH
-     PLATFORM_VNDK_VERSION
-     PLATFORM_SYSTEMSDK_VERSIONS
-    """
-
-    # If build_id.rbc exists, it may override some of the defaults.
-    # Note that build.prop target also wants INTERNAL_BUILD_ID_MAKEFILE to be set if the file exists.
-    if _build_id_init != None:
-        _build_id_init(g)
-        g["INTERNAL_BUILD_ID_MAKEFILE"] = "build/make/core/build_id"
-
-    allowed_versions = _allowed_versions(all_versions, v_min, v_max, v_default)
-    g.setdefault("TARGET_PLATFORM_VERSION", v_default)
-    if g["TARGET_PLATFORM_VERSION"] not in allowed_versions:
-        fail("% is not valid, must be one of %s" % (g["TARGET_PLATFORM_VERSION"], allowed_versions))
-
-    g["DEFAULT_PLATFORM_VERSION"] = v_default
-    g["PLATFORM_VERSION_LAST_STABLE"] = 11
-    g.setdefault("PLATFORM_VERSION_CODENAME", g["TARGET_PLATFORM_VERSION"])
-    # TODO(asmundak): set PLATFORM_VERSION_ALL_CODENAMES
-
-    g.setdefault("PLATFORM_SDK_VERSION", 30)
-    version_codename = g["PLATFORM_VERSION_CODENAME"]
-    if version_codename == "REL":
-        g.setdefault("PLATFORM_VERSION", g["PLATFORM_VERSION_LAST_STABLE"])
-        g["PLATFORM_PREVIEW_SDK_VERSION"] = 0
-        g.setdefault("DEFAULT_APP_TARGET_SDK", g["PLATFORM_SDK_VERSION"])
-        g.setdefault("PLATFORM_VNDK_VERSION", g["PLATFORM_SDK_VERSION"])
-    else:
-        g.setdefault("PLATFORM_VERSION", version_codename)
-        g.setdefault("PLATFORM_PREVIEW_SDK_VERSION", 1)
-        g.setdefault("DEFAULT_APP_TARGET_SDK", version_codename)
-        g.setdefault("PLATFORM_VNDK_VERSION", version_codename)
-
-    g.setdefault("PLATFORM_SYSTEMSDK_MIN_VERSION", 28)
-    versions = [str(i) for i in range(g["PLATFORM_SYSTEMSDK_MIN_VERSION"], g["PLATFORM_SDK_VERSION"] + 1)]
-    versions.append(version_codename)
-    g["PLATFORM_SYSTEMSDK_VERSIONS"] = sorted(versions)
-
-    #  Used to indicate the security patch that has been applied to the device.
-    #  It must signify that the build includes all security patches issued up through the designated Android Public Security Bulletin.
-    #  It must be of the form "YYYY-MM-DD" on production devices.
-    #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
-    #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-    g.setdefault("PLATFORM_SECURITY_PATCH", "2021-03-05")
-    dt = 'TZ="GMT" %s' % g["PLATFORM_SECURITY_PATCH"]
-    g.setdefault("PLATFORM_SECURITY_PATCH_TIMESTAMP", rblf_shell("date -d '%s' +%%s" % dt))
-
-    # Used to indicate the base os applied to the device. Can be an arbitrary string, but must be a single word.
-    # If there is no $PLATFORM_BASE_OS set, keep it empty.
-    g.setdefault("PLATFORM_BASE_OS", "")
-
-    # Used to signify special builds.  E.g., branches and/or releases, like "M5-RC7".  Can be an arbitrary string, but
-    # must be a single word and a valid file name. If there is no BUILD_ID set, make it obvious.
-    g.setdefault("BUILD_ID", "UNKNOWN")
-
-    # BUILD_NUMBER should be set to the source control value that represents the current state of the source code.
-    # E.g., a perforce changelist number or a git hash.  Can be an arbitrary string (to allow for source control that
-    # uses something other than numbers), but must be a single word and a valid file name.
-    #
-    # If no BUILD_NUMBER is set, create a useful "I am an engineering build from this date/time" value.  Make it start
-    # with a non-digit so that anyone trying to parse it as an integer will probably get "0".
-    g.setdefault("BUILD_NUMBER", "eng.%s.%s" % (g["USER"], "TIMESTAMP"))
-
-    # Used to set minimum supported target sdk version. Apps targeting SDK version lower than the set value will result
-    # in a warning being shown when any activity from the app is started.
-    g.setdefault("PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION", 23)
-
-def init(g):
-    """Initializes globals.
-
-    The code is the Starlark counterpart of the contents of the
-    envsetup.mk file.
-    Args:
-        g: globals dictionary
-    """
-    all_versions = _all_versions()
-    _versions_default(g, all_versions)
-    for v in all_versions:
-        g["IS_AT_LEAST" + v] = True
-        if v == g["TARGET_PLATFORM_VERSION"]:
-            break
-
-    # ---------------------------------------------------------------
-    # If you update the build system such that the environment setup or buildspec.mk need to be updated,
-    # increment this number, and people who haven't re-run those will have to do so before they can build.
-    # Make sure to also update the corresponding value in buildspec.mk.default and envsetup.sh.
-    g["CORRECT_BUILD_ENV_SEQUENCE_NUMBER"] = 13
-
-    g.setdefault("TARGET_PRODUCT", "aosp_arm")
-    g.setdefault("TARGET_BUILD_VARIANT", "eng")
-
-    g.setdefault("TARGET_BUILD_APPS", [])
-    g["TARGET_BUILD_UNBUNDLED"] = (g["TARGET_BUILD_APPS"] != []) or (getattr(g, "TARGET_BUILD_UNBUNDLED_IMAGE", "") != "")
-
-    # ---------------------------------------------------------------
-    # Set up configuration for host machine.  We don't do cross-compiles except for arm, so the HOST
-    # is whatever we are running on.
-    host = rblf_shell("uname -sm")
-    if host.find("Linux") >= 0:
-        g["HOST_OS"] = "linux"
-    elif host.find("Darwin") >= 0:
-        g["HOST_OS"] = "darwin"
-    else:
-        fail("Cannot run on %s OS" % host)
-
-    # TODO(asmundak): set g.HOST_OS_EXTRA
-
-    g["BUILD_OS"] = g["HOST_OS"]
-
-    # TODO(asmundak): check cross-OS build
-
-    if host.find("x86_64") >= 0:
-        g["HOST_ARCH"] = "x86_64"
-        g["HOST_2ND_ARCH"] = "x86"
-        g["HOST_IS_64_BIT"] = True
-    elif host.find("i686") >= 0 or host.find("x86") >= 0:
-        fail("Building on a 32-bit x86 host is not supported: %s" % host)
-    elif g["HOST_OS"] == "darwin":
-        g["HOST_2ND_ARCH"] = ""
-
-    g["HOST_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-    g["HOST_2ND_ARCH_MODULE_SUFFIX"] = "_32"
-    g["HOST_CROSS_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-    g["HOST_CROSS_2ND_ARCH_MODULE_SUFFIX"] = "_64"
-    g["TARGET_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-
-    # TODO(asmundak): envsetup.mk lines 216-226:
-    # convert combo-related stuff from combo/select.mk
-
-    # on windows, the tools have .exe at the end, and we depend on the
-    # host config stuff being done first
-    g["BUILD_ARCH"] = g["HOST_ARCH"]
-    g["BUILD_2ND_ARCH"] = g["HOST_2ND_ARCH"]
-
-    # the host build defaults to release, and it must be release or debug
-    g.setdefault("HOST_BUILD_TYPE", "release")
-    if g["HOST_BUILD_TYPE"] not in ["release", "debug"]:
-        fail("HOST_BUILD_TYPE must be either release or debug, not '%s'" % g["HOST_BUILD_TYPE"])
-
-    # TODO(asmundak): there is more stuff in envsetup.mk lines 249-292, but
-    # it does not seem to affect product configuration. Revisit this.
-
-    g["ART_APEX_JARS"] = [
-        "com.android.art:core-oj",
-        "com.android.art:core-libart",
-        "com.android.art:okhttp",
-        "com.android.art:bouncycastle",
-        "com.android.art:apache-xml",
-    ]
-
-    if g.get("TARGET_BUILD_TYPE", "") != "debug":
-        g["TARGET_BUILD_TYPE"] = "release"
-
-v_default = "SP1A"
-v_min = "SP1A"
-v_max = "SP1A"
diff --git a/core/force_aapt2.mk b/core/force_aapt2.mk
index 25b45e4..5f3182a 100644
--- a/core/force_aapt2.mk
+++ b/core/force_aapt2.mk
@@ -44,10 +44,3 @@
   LOCAL_SDK_RES_VERSION := current
 endif
 
-ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
-  ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
-    # work around missing manifests by creating a default one
-    LOCAL_FULL_MANIFEST_FILE := $(call local-intermediates-dir,COMMON)/DefaultManifest.xml
-    $(call create-default-manifest-file,$(LOCAL_FULL_MANIFEST_FILE),$(call module-min-sdk-version))
-  endif
-endif
diff --git a/core/jacoco.mk b/core/jacoco.mk
index e8c74ee..7099526 100644
--- a/core/jacoco.mk
+++ b/core/jacoco.mk
@@ -50,6 +50,7 @@
 	unzip -qDD $(PRIVATE_FULL_CLASSES_PRE_JACOCO_JAR) \
 	  -d $(PRIVATE_UNZIPPED_PATH) \
 	  $(PRIVATE_INCLUDE_ARGS)
+	chmod -R =rwX $(PRIVATE_UNZIPPED_PATH)
 	(cd $(PRIVATE_UNZIPPED_PATH) && rm -rf $(PRIVATE_EXCLUDE_ARGS))
 	(cd $(PRIVATE_UNZIPPED_PATH) && find -not -name "*.class" -type f -exec rm {} \;)
 	touch $(PRIVATE_UNZIPPED_TIMESTAMP_PATH)
diff --git a/core/java.mk b/core/java.mk
index 123cbe8..a29f820 100644
--- a/core/java.mk
+++ b/core/java.mk
@@ -266,6 +266,7 @@
 
 # TODO(b/143658984): goma can't handle the --system argument to javac.
 #$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(GOMA_POOL)
+$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(JAVAC_NINJA_POOL)
 $(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(annotation_processor_flags)
 $(full_classes_compiled_jar): PRIVATE_JAR_EXCLUDE_FILES := $(LOCAL_JAR_EXCLUDE_FILES)
 $(full_classes_compiled_jar): PRIVATE_JAR_PACKAGES := $(LOCAL_JAR_PACKAGES)
@@ -489,12 +490,14 @@
 $(built_dex_intermediate): PRIVATE_DX_FLAGS := $(LOCAL_DX_FLAGS)
 
 ifdef LOCAL_PROGUARD_ENABLED
+  $(built_dex_intermediate): .KATI_NINJA_POOL := $(R8_NINJA_POOL)
   $(built_dex_intermediate): PRIVATE_EXTRA_INPUT_JAR := $(extra_input_jar)
   $(built_dex_intermediate): PRIVATE_PROGUARD_FLAGS := $(legacy_proguard_flags) $(common_proguard_flags) $(LOCAL_PROGUARD_FLAGS)
   $(built_dex_intermediate): PRIVATE_PROGUARD_DICTIONARY := $(proguard_dictionary)
   $(built_dex_intermediate) : $(full_classes_pre_proguard_jar) $(extra_input_jar) $(my_proguard_sdk_raise) $(common_proguard_flag_files) $(legacy_proguard_lib_deps) $(R8_COMPAT_PROGUARD) $(LOCAL_PROGUARD_FLAGS_DEPS)
 	$(transform-jar-to-dex-r8)
 else # !LOCAL_PROGUARD_ENABLED
+  $(built_dex_intermediate): .KATI_NINJA_POOL := $(D8_NINJA_POOL)
   $(built_dex_intermediate): PRIVATE_D8_LIBS := $(full_java_bootclasspath_libs) $(full_shared_java_header_libs)
   $(built_dex_intermediate): $(full_java_bootclasspath_libs) $(full_shared_java_header_libs)
   $(built_dex_intermediate): $(full_classes_pre_proguard_jar) $(DX) $(ZIP2ZIP)
diff --git a/core/java_common.mk b/core/java_common.mk
index f97f73c..5981b60 100644
--- a/core/java_common.mk
+++ b/core/java_common.mk
@@ -383,9 +383,8 @@
   endif # USE_CORE_LIB_BOOTCLASSPATH
 endif # !LOCAL_IS_HOST_MODULE
 
-ifdef RECORD_ALL_DEPS
+# (b/204397180) Record ALL_DEPS by default.
 ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) $(full_java_bootclasspath_libs)
-endif
 
 # Export the SDK libs. The sdk library names listed in LOCAL_SDK_LIBRARIES are first exported.
 # Then sdk library names exported from dependencies are all re-exported.
diff --git a/core/java_renderscript.mk b/core/java_renderscript.mk
index 572d6e4..055ff14 100644
--- a/core/java_renderscript.mk
+++ b/core/java_renderscript.mk
@@ -107,7 +107,7 @@
 # Prevent these from showing up on the device
 # One exception is librsjni.so, which is needed for
 # both native path and compat path.
-rs_jni_lib := $(call intermediates-dir-for,SHARED_LIBRARIES,librsjni.so)/librsjni.so
+rs_jni_lib := $(call intermediates-dir-for,SHARED_LIBRARIES,librsjni)/librsjni.so
 LOCAL_JNI_SHARED_LIBRARIES += librsjni
 
 ifneq (,$(TARGET_BUILD_USE_PREBUILT_SDKS)$(FORCE_BUILD_RS_COMPAT))
diff --git a/core/layoutlib_fonts.mk b/core/layoutlib_fonts.mk
new file mode 100644
index 0000000..d2a814f
--- /dev/null
+++ b/core/layoutlib_fonts.mk
@@ -0,0 +1,35 @@
+# Fonts for layoutlib
+
+FONT_TEMP := $(call intermediates-dir-for,PACKAGING,fonts,HOST,COMMON)
+
+# The font configuration files - system_fonts.xml, fallback_fonts.xml etc.
+font_config := $(sort $(wildcard frameworks/base/data/fonts/*.xml))
+font_config := $(addprefix $(FONT_TEMP)/, $(notdir $(font_config)))
+
+$(font_config): $(FONT_TEMP)/%.xml: \
+			frameworks/base/data/fonts/%.xml
+	$(hide) mkdir -p $(dir $@)
+	$(hide) cp -vf $< $@
+
+# List of fonts on the device that we want to ship. This is all .ttf, .ttc and .otf fonts.
+fonts_device := $(filter $(TARGET_OUT)/fonts/%.ttf $(TARGET_OUT)/fonts/%.ttc $(TARGET_OUT)/fonts/%.otf, $(INTERNAL_SYSTEMIMAGE_FILES))
+fonts_device := $(addprefix $(FONT_TEMP)/, $(notdir $(fonts_device)))
+
+# TODO: If the font file is a symlink, reuse the font renamed from the symlink
+# target.
+$(fonts_device): $(FONT_TEMP)/%: $(TARGET_OUT)/fonts/%
+	$(hide) mkdir -p $(dir $@)
+	$(hide) cp -vf $< $@
+
+# List of all dependencies - all fonts and configuration files.
+FONT_FILES := $(fonts_device) $(font_config)
+
+.PHONY: layoutlib layoutlib-tests
+layoutlib layoutlib-tests: $(FONT_FILES)
+
+$(call dist-for-goals, layoutlib, $(foreach m,$(FONT_FILES), $(m):layoutlib_native/fonts/$(notdir $(m))))
+
+FONT_TEMP :=
+font_config :=
+fonts_device :=
+FONT_FILES :=
diff --git a/core/main.mk b/core/main.mk
index c10a3cc..c63c6df 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -115,15 +115,6 @@
 EMMA_INSTRUMENT := true
 endif
 
-ifeq (true,$(EMMA_INSTRUMENT))
-# Adding the jacoco library can cause the inclusion of
-# some typically banned classes
-# So if the user didn't specify SKIP_BOOT_JARS_CHECK, enable it here
-ifndef SKIP_BOOT_JARS_CHECK
-SKIP_BOOT_JARS_CHECK := true
-endif
-endif
-
 ifdef TARGET_ARCH_SUITE
   # TODO(b/175577370): Enable this error.
   # $(error TARGET_ARCH_SUITE is not supported in kati/make builds)
@@ -151,11 +142,6 @@
 #
 # -----------------------------------------------------------------
 # Add the product-defined properties to the build properties.
-ifdef PRODUCT_SHIPPING_API_LEVEL
-ADDITIONAL_SYSTEM_PROPERTIES += \
-  ro.product.first_api_level=$(PRODUCT_SHIPPING_API_LEVEL)
-endif
-
 ifneq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true)
   ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES)
 else
@@ -320,6 +306,13 @@
     ro.vendor.build.dont_use_vabc=true
 endif
 
+# Set the flag in vendor. So VTS would know if the new fingerprint format is in use when
+# the system images are replaced by GSI.
+ifeq ($(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT),true)
+ADDITIONAL_VENDOR_PROPERTIES += \
+    ro.vendor.build.fingerprint_has_digest=1
+endif
+
 ADDITIONAL_VENDOR_PROPERTIES += \
     ro.vendor.build.security_patch=$(VENDOR_SECURITY_PATCH) \
     ro.product.board=$(TARGET_BOOTLOADER_BOARD_NAME) \
@@ -350,7 +343,7 @@
 ADDITIONAL_PRODUCT_PROPERTIES += ro.build.characteristics=$(TARGET_AAPT_CHARACTERISTICS)
 
 ifeq ($(AB_OTA_UPDATER),true)
-ADDITIONAL_PRODUCT_PROPERTIES += ro.product.ab_ota_partitions=$(subst $(space),$(comma),$(AB_OTA_PARTITIONS))
+ADDITIONAL_PRODUCT_PROPERTIES += ro.product.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS)))
 endif
 
 # -----------------------------------------------------------------
@@ -361,7 +354,7 @@
 
 is_sdk_build :=
 
-ifneq ($(filter sdk win_sdk sdk_addon,$(MAKECMDGOALS)),)
+ifneq ($(filter sdk sdk_addon,$(MAKECMDGOALS)),)
 is_sdk_build := true
 endif
 
@@ -536,13 +529,23 @@
 # Include all of the makefiles in the system
 #
 
-subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list) $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk
+subdir_makefiles := $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk $(SOONG_ANDROID_MK)
+# Android.mk files are only used on Linux builds, Mac only supports Android.bp
+ifeq ($(HOST_OS),linux)
+  subdir_makefiles += $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
+endif
+subdir_makefiles += $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk
 subdir_makefiles_total := $(words int $(subdir_makefiles) post finish)
 .KATI_READONLY := subdir_makefiles_total
 
 $(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))
 
+# For an unbundled image, we can skip blueprint_tools because unbundled image
+# aims to remove a large number framework projects from the manifest, the
+# sources or dependencies for these tools may be missing from the tree.
+ifeq (,$(TARGET_BUILD_UNBUNDLED_IMAGE))
 droid_targets : blueprint_tools
+endif
 
 endif # dont_bother
 
@@ -932,6 +935,7 @@
           $(eval my_testcases := $(HOST_OUT_TESTCASES)),\
           $(eval my_testcases := $$(COMPATIBILITY_TESTCASES_OUT_$(suite))))\
         $(eval target := $(my_testcases)/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
+        $(if $(strip $(ALL_TARGETS.$(target).META_LIC)),,$(eval ALL_TARGETS.$(target).META_LIC:=$(module_license_metadata)))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
           $$(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES) $(f):$(target))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
@@ -1201,7 +1205,8 @@
         $(subst $(_odm_path_placeholder),$(TARGET_COPY_OUT_ODM),\
           $(subst $(_vendor_dlkm_path_placeholder),$(TARGET_COPY_OUT_VENDOR_DLKM),\
             $(subst $(_odm_dlkm_path_placeholder),$(TARGET_COPY_OUT_ODM_DLKM),\
-              $(foreach p,$(1),$(call append-path,$(PRODUCT_OUT),$(p)$(2)))))))))
+              $(subst $(_system_dlkm_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_DLKM),\
+                $(foreach p,$(1),$(call append-path,$(PRODUCT_OUT),$(p)$(2))))))))))
 endef
 
 # Returns modules included automatically as a result of certain BoardConfig
@@ -1229,6 +1234,7 @@
 # Name resolution for LOCAL_REQUIRED_MODULES:
 #   See the select-bitness-of-required-modules definition.
 # $(1): product makefile
+
 define product-installed-files
   $(eval _pif_modules := \
     $(call get-product-var,$(1),PRODUCT_PACKAGES) \
@@ -1278,7 +1284,11 @@
 )
 endef
 
-ifdef FULL_BUILD
+ifeq ($(HOST_OS),darwin)
+  # Target builds are not supported on Mac
+  product_target_FILES :=
+  product_host_FILES := $(call host-installed-files,$(INTERNAL_PRODUCT))
+else ifdef FULL_BUILD
   ifneq (true,$(ALLOW_MISSING_DEPENDENCIES))
     # Check to ensure that all modules in PRODUCT_PACKAGES exist (opt in per product)
     ifeq (true,$(PRODUCT_ENFORCE_PACKAGES_EXIST))
@@ -1338,7 +1348,7 @@
 
   # Verify the artifact path requirements made by included products.
   is_asan := $(if $(filter address,$(SANITIZE_TARGET)),true)
-  ifneq (true,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS)))
+  ifeq (,$(or $(is_asan),$(DISABLE_ARTIFACT_PATH_REQUIREMENTS)))
     include $(BUILD_SYSTEM)/artifact_path_requirements.mk
   endif
 else
@@ -1434,7 +1444,9 @@
 # contains everything that's built during the current make, but it also further
 # extends ALL_DEFAULT_INSTALLED_MODULES.
 ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
-include $(BUILD_SYSTEM)/Makefile
+ifeq ($(HOST_OS),linux)
+  include $(BUILD_SYSTEM)/Makefile
+endif
 modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
 ALL_DEFAULT_INSTALLED_MODULES :=
 
@@ -1444,12 +1456,6 @@
 # fix-notice-deps replaces those unadorned module names with every built variant.
 $(call fix-notice-deps)
 
-# Create a license metadata rule per module. Could happen in base_rules.mk or
-# notice_files.mk; except, it has to happen after fix-notice-deps to avoid
-# missing dependency errors.
-$(call build-license-metadata)
-
-
 # These are additional goals that we build, in order to make sure that there
 # is as little code as possible in the tree that doesn't build.
 modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED))
@@ -1521,6 +1527,9 @@
 .PHONY: vendorbootimage
 vendorbootimage: $(INSTALLED_VENDOR_BOOTIMAGE_TARGET)
 
+.PHONY: vendorkernelbootimage
+vendorkernelbootimage: $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET)
+
 .PHONY: vendorbootimage_debug
 vendorbootimage_debug: $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)
 
@@ -1530,9 +1539,15 @@
 .PHONY: vendorramdisk
 vendorramdisk: $(INSTALLED_VENDOR_RAMDISK_TARGET)
 
+.PHONY: vendorkernelramdisk
+vendorkernelramdisk: $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET)
+
 .PHONY: vendorramdisk_debug
 vendorramdisk_debug: $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET)
 
+.PHONY: vendorramdisk_test_harness
+vendorramdisk_test_harness: $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET)
+
 .PHONY: productimage
 productimage: $(INSTALLED_PRODUCTIMAGE_TARGET)
 
@@ -1548,6 +1563,9 @@
 .PHONY: odm_dlkmimage
 odm_dlkmimage: $(INSTALLED_ODM_DLKMIMAGE_TARGET)
 
+.PHONY: system_dlkmimage
+system_dlkmimage: $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+
 .PHONY: systemotherimage
 systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
 
@@ -1557,6 +1575,13 @@
 .PHONY: bootimage
 bootimage: $(INSTALLED_BOOTIMAGE_TARGET)
 
+.PHONY: initbootimage
+initbootimage: $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
+ifeq (true,$(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST))
+$(call dist-for-goals, bootimage, $(INSTALLED_BOOTIMAGE_TARGET))
+endif
+
 .PHONY: bootimage_debug
 bootimage_debug: $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)
 
@@ -1576,9 +1601,11 @@
 # perform a full system build (either unbundled or not).
 .PHONY: droidcore-unbundled
 droidcore-unbundled: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
+    $(INSTALLED_FILES_OUTSIDE_IMAGES) \
     $(INSTALLED_SYSTEMIMAGE_TARGET) \
     $(INSTALLED_RAMDISK_TARGET) \
     $(INSTALLED_BOOTIMAGE_TARGET) \
+    $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
     $(INSTALLED_RADIOIMAGE_TARGET) \
     $(INSTALLED_DEBUG_RAMDISK_TARGET) \
     $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
@@ -1591,13 +1618,17 @@
     $(INSTALLED_BPTIMAGE_TARGET) \
     $(INSTALLED_VENDORIMAGE_TARGET) \
     $(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
+    $(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET) \
     $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \
+    $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) \
     $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \
     $(INSTALLED_VENDOR_RAMDISK_TARGET) \
+    $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \
     $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
     $(INSTALLED_ODMIMAGE_TARGET) \
     $(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
     $(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+    $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
     $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
     $(INSTALLED_PRODUCTIMAGE_TARGET) \
     $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
@@ -1613,6 +1644,8 @@
     $(INSTALLED_FILES_JSON_VENDOR_DLKM) \
     $(INSTALLED_FILES_FILE_ODM_DLKM) \
     $(INSTALLED_FILES_JSON_ODM_DLKM) \
+    $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \
+    $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \
     $(INSTALLED_FILES_FILE_PRODUCT) \
     $(INSTALLED_FILES_JSON_PRODUCT) \
     $(INSTALLED_FILES_FILE_SYSTEM_EXT) \
@@ -1627,12 +1660,13 @@
     $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \
     $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \
     $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \
+    $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \
+    $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \
     $(INSTALLED_FILES_FILE_ROOT) \
     $(INSTALLED_FILES_JSON_ROOT) \
     $(INSTALLED_FILES_FILE_RECOVERY) \
     $(INSTALLED_FILES_JSON_RECOVERY) \
-    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
-    soong_docs
+    $(INSTALLED_ANDROID_INFO_TXT_TARGET)
 
 # The droidcore target depends on the droidcore-unbundled subset and any other
 # targets for a non-unbundled (full source) full system build.
@@ -1648,7 +1682,11 @@
 endif
 
 .PHONY: apps_only
-ifneq ($(TARGET_BUILD_APPS),)
+ifeq ($(HOST_OS),darwin)
+  # Mac only supports building host modules
+  droid_targets: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) dist_files
+
+else ifneq ($(TARGET_BUILD_APPS),)
   # If this build is just for apps, only build apps and not the full system by default.
 
   unbundled_build_modules :=
@@ -1690,28 +1728,32 @@
   endif
 
   $(PROGUARD_DICT_ZIP) : $(apps_only_installed_files)
-  $(call dist-for-goals,apps_only, $(PROGUARD_DICT_ZIP))
+  $(call dist-for-goals,apps_only, $(PROGUARD_DICT_ZIP) $(PROGUARD_DICT_MAPPING))
+  $(call declare-container-license-deps,$(PROGUARD_DICT_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(PROGUARD_USAGE_ZIP) : $(apps_only_installed_files)
   $(call dist-for-goals,apps_only, $(PROGUARD_USAGE_ZIP))
+  $(call declare-container-license-deps,$(PROGUARD_USAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(SYMBOLS_ZIP) : $(apps_only_installed_files)
-  $(call dist-for-goals,apps_only, $(SYMBOLS_ZIP))
+  $(call dist-for-goals,apps_only, $(SYMBOLS_ZIP) $(SYMBOLS_MAPPING))
+  $(call declare-container-license-deps,$(SYMBOLS_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
   $(COVERAGE_ZIP) : $(apps_only_installed_files)
   $(call dist-for-goals,apps_only, $(COVERAGE_ZIP))
+  $(call declare-container-license-deps,$(COVERAGE_ZIP),$(apps_only_installed_files),$(PRODUCT_OUT)/:/)
 
 apps_only: $(unbundled_build_modules)
 
 droid_targets: apps_only
 
-# Combine the NOTICE files for a apps_only build
-$(eval $(call combine-notice-files, html, \
-    $(target_notice_file_txt), \
-    $(target_notice_file_html_or_xml), \
-    "Notices for files for apps:", \
-    $(TARGET_OUT_NOTICE_FILES), \
-    $(apps_only_installed_files)))
+# NOTICE files for a apps_only build
+$(eval $(call html-notice-rule,$(target_notice_file_html_or_xml),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/))
+
+$(eval $(call text-notice-rule,$(target_notice_file_txt),"Apps","Notices for files for apps:",$(unbundled_build_modules),$(PRODUCT_OUT)/ $(HOST_OUT)/))
+
+$(call declare-0p-target,$(target_notice_file_txt))
+$(call declare-0p-target,$(target_notice_html_or_xml))
 
 
 else ifeq ($(TARGET_BUILD_UNBUNDLED),$(TARGET_BUILD_UNBUNDLED_IMAGE))
@@ -1731,7 +1773,6 @@
   $(call dist-for-goals, droidcore, \
     $(BUILT_OTATOOLS_PACKAGE) \
     $(APPCOMPAT_ZIP) \
-    $(DEXPREOPT_CONFIG_ZIP) \
     $(DEXPREOPT_TOOLS_ZIP) \
   )
 
@@ -1748,7 +1789,9 @@
     $(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET) \
     $(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET) \
     $(SYMBOLS_ZIP) \
+    $(SYMBOLS_MAPPING) \
     $(PROGUARD_DICT_ZIP) \
+    $(PROGUARD_DICT_MAPPING) \
     $(PROGUARD_USAGE_ZIP) \
     $(COVERAGE_ZIP) \
     $(INSTALLED_FILES_FILE) \
@@ -1761,6 +1804,8 @@
     $(INSTALLED_FILES_JSON_VENDOR_DLKM) \
     $(INSTALLED_FILES_FILE_ODM_DLKM) \
     $(INSTALLED_FILES_JSON_ODM_DLKM) \
+    $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \
+    $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \
     $(INSTALLED_FILES_FILE_PRODUCT) \
     $(INSTALLED_FILES_JSON_PRODUCT) \
     $(INSTALLED_FILES_FILE_SYSTEM_EXT) \
@@ -1779,6 +1824,7 @@
     $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
     $(INSTALLED_MISC_INFO_TARGET) \
     $(INSTALLED_RAMDISK_TARGET) \
+    $(DEXPREOPT_CONFIG_ZIP) \
   )
 
   # Put a copy of the radio/bootloader files in the dist dir.
@@ -1805,6 +1851,8 @@
       $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \
       $(INSTALLED_FILES_FILE_VENDOR_RAMDISK) \
       $(INSTALLED_FILES_JSON_VENDOR_RAMDISK) \
+      $(INSTALLED_FILES_FILE_VENDOR_KERNEL_RAMDISK) \
+      $(INSTALLED_FILES_JSON_VENDOR_KERNEL_RAMDISK) \
       $(INSTALLED_FILES_FILE_VENDOR_DEBUG_RAMDISK) \
       $(INSTALLED_FILES_JSON_VENDOR_DEBUG_RAMDISK) \
       $(INSTALLED_DEBUG_RAMDISK_TARGET) \
@@ -1812,12 +1860,18 @@
       $(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) \
       $(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET) \
       $(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET) \
+      $(INSTALLED_VENDOR_TEST_HARNESS_RAMDISK_TARGET) \
       $(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET) \
       $(INSTALLED_VENDOR_RAMDISK_TARGET) \
       $(INSTALLED_VENDOR_DEBUG_RAMDISK_TARGET) \
+      $(INSTALLED_VENDOR_KERNEL_RAMDISK_TARGET) \
     )
   endif
 
+  ifeq ($(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST),true)
+    $(call dist-for-goals, droidcore-unbundled, $(INSTALLED_BOOTIMAGE_TARGET))
+  endif
+
   ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
     $(call dist-for-goals, droidcore-unbundled, \
       $(recovery_ramdisk) \
@@ -1841,12 +1895,16 @@
 	$(hide) mkdir -p $(dir $@)
 	$(hide) $(APICHECK_COMMAND) --input-api-jar $< --api-xml $@
 
+  $(foreach xml,$(sort $(api_xmls)),$(call declare-1p-target,$(xml),))
+
   $(call dist-for-goals, dist_files, $(api_xmls))
   api_xmls :=
 
   ifdef CLANG_COVERAGE
     $(foreach f,$(SOONG_NDK_API_XML), \
         $(call dist-for-goals,droidcore,$(f):ndk_apis/$(notdir $(f))))
+    $(foreach f,$(SOONG_CC_API_XML), \
+        $(call dist-for-goals,droidcore,$(f):cc_apis/$(notdir $(f))))
   endif
 
   # For full system build (whether unbundled or not), we configure
@@ -1872,16 +1930,15 @@
 .PHONY: docs
 docs: $(ALL_DOCS)
 
-.PHONY: sdk win_sdk winsdk-tools sdk_addon
+.PHONY: sdk sdk_addon
+ifeq ($(HOST_OS),linux)
 ALL_SDK_TARGETS := $(INTERNAL_SDK_TARGET)
 sdk: $(ALL_SDK_TARGETS)
-$(call dist-for-goals,sdk win_sdk, \
+$(call dist-for-goals,sdk, \
     $(ALL_SDK_TARGETS) \
-    $(SYMBOLS_ZIP) \
-    $(COVERAGE_ZIP) \
-    $(APPCOMPAT_ZIP) \
     $(INSTALLED_BUILD_PROP_TARGET) \
 )
+endif
 
 # umbrella targets to assit engineers in verifying builds
 .PHONY: java native target host java-host java-target native-host native-target \
@@ -1948,6 +2005,11 @@
   droidcore: ${APEX_ALLOWED_DEPS_CHECK}
 endif
 
+# Create a license metadata rule per module. Could happen in base_rules.mk or
+# notice_files.mk; except, it has to happen after fix-notice-deps to avoid
+# missing dependency errors.
+$(call build-license-metadata)
+
 $(call dist-write-file,$(KATI_PACKAGE_MK_DIR)/dist.mk)
 
 $(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing build rules ...)
diff --git a/core/ninja_config.mk b/core/ninja_config.mk
index 2e1bd69..e436b2c 100644
--- a/core/ninja_config.mk
+++ b/core/ninja_config.mk
@@ -25,7 +25,6 @@
 	cts \
 	custom_images \
 	dicttool_aosp \
-	dump-products \
 	eng \
 	oem_image \
 	online-system-api-sdk-docs \
@@ -38,15 +37,19 @@
 	test-art% \
 	user \
 	userdataimage \
-	userdebug \
-	win_sdk \
-	winsdk-tools
+	userdebug
 
 include $(wildcard vendor/*/build/ninja_config.mk)
 
 # Any Android goals that need to be built.
 ANDROID_GOALS := $(filter-out $(KATI_OUTPUT_PATTERNS),\
     $(sort $(ORIGINAL_MAKECMDGOALS) $(MAKECMDGOALS)))
+# Temporary compatibility support until the build server configs are updated
+ANDROID_GOALS := $(patsubst win_sdk,sdk,$(ANDROID_GOALS))
+ifneq ($(HOST_OS),linux)
+  ANDROID_GOALS := $(filter-out sdk,$(ANDROID_GOALS))
+  ANDROID_GOALS := $(patsubst sdk_repo,sdk-repo-build-tools sdk-repo-platform-tools,$(ANDROID_GOALS))
+endif
 # Goals we need to pass to Ninja.
 NINJA_GOALS := $(filter-out $(NINJA_EXCLUDE_GOALS), $(ANDROID_GOALS))
 ifndef NINJA_GOALS
diff --git a/core/node_fns.mk b/core/node_fns.mk
index 8d20160..2243cd727 100644
--- a/core/node_fns.mk
+++ b/core/node_fns.mk
@@ -208,7 +208,7 @@
 
   $(eval $(1).$(2).inherited := \
       $(call get-inherited-nodes,$(1).$(2),$(3)))
-  $(call _import-nodes-inner,$(1),$($(1).$(2).inherited),$(3))
+  $(call _import-nodes-inner,$(1),$($(1).$(2).inherited),$(3),$(4))
 
   $(call _expand-inherited-values,$(1),$(2),$(3),$(4))
 
diff --git a/core/notice_files.mk b/core/notice_files.mk
index 9678380..c05d4ea 100644
--- a/core/notice_files.mk
+++ b/core/notice_files.mk
@@ -11,10 +11,6 @@
 
 ifneq (,$(strip $(LOCAL_LICENSE_PACKAGE_NAME)))
 license_package_name:=$(strip $(LOCAL_LICENSE_PACKAGE_NAME))
-else ifdef my_register_name
-license_package_name:=$(my_register_name)
-else
-license_package_name:=$(strip $(LOCAL_MODULE))
 endif
 
 ifneq (,$(strip $(LOCAL_LICENSE_INSTALL_MAP)))
@@ -81,43 +77,78 @@
 # Include shared libraries' notices for "container" types, but not for binaries etc.
 notice_deps := \
     $(strip \
-        $(LOCAL_REQUIRED_MODULES) \
-        $(LOCAL_STATIC_LIBRARIES) \
-        $(LOCAL_WHOLE_STATIC_LIBRARIES) \
-        $(LOCAL_SHARED_LIBRARIES) \
-        $(LOCAL_DYLIB_LIBRARIES) \
-        $(LOCAL_RLIB_LIBRARIES) \
-        $(LOCAL_PROC_MACRO_LIBRARIES) \
-        $(LOCAL_HEADER_LIBRARIES) \
-        $(LOCAL_STATIC_JAVA_LIBRARIES) \
-        $(LOCAL_JAVA_LIBRARIES) \
-        $(LOCAL_JNI_SHARED_LIBRARIES) \
+        $(foreach d, \
+            $(LOCAL_REQUIRED_MODULES) \
+            $(LOCAL_STATIC_LIBRARIES) \
+            $(LOCAL_WHOLE_STATIC_LIBRARIES) \
+            $(LOCAL_SHARED_LIBRARIES) \
+            $(LOCAL_DYLIB_LIBRARIES) \
+            $(LOCAL_RLIB_LIBRARIES) \
+            $(LOCAL_PROC_MACRO_LIBRARIES) \
+            $(LOCAL_HEADER_LIBRARIES) \
+            $(LOCAL_STATIC_JAVA_LIBRARIES) \
+            $(LOCAL_JAVA_LIBRARIES) \
+            $(LOCAL_JNI_SHARED_LIBRARIES) \
+            ,$(subst :,_,$(d)):static \
+        ) \
     )
 else
 notice_deps := \
     $(strip \
-        $(LOCAL_REQUIRED_MODULES) \
-        $(LOCAL_STATIC_LIBRARIES) \
-        $(LOCAL_WHOLE_STATIC_LIBRARIES) \
-        $(LOCAL_RLIB_LIBRARIES) \
-        $(LOCAL_PROC_MACRO_LIBRARIES) \
-        $(LOCAL_HEADER_LIBRARIES) \
-        $(LOCAL_STATIC_JAVA_LIBRARIES) \
+        $(foreach d, \
+            $(LOCAL_REQUIRED_MODULES) \
+            $(LOCAL_STATIC_LIBRARIES) \
+            $(LOCAL_WHOLE_STATIC_LIBRARIES) \
+            $(LOCAL_RLIB_LIBRARIES) \
+            $(LOCAL_PROC_MACRO_LIBRARIES) \
+            $(LOCAL_HEADER_LIBRARIES) \
+            $(LOCAL_STATIC_JAVA_LIBRARIES) \
+            ,$(subst :,_,$(d)):static \
+        )$(foreach d, \
+            $(LOCAL_SHARED_LIBRARIES) \
+            $(LOCAL_DYLIB_LIBRARIES) \
+            $(LOCAL_JAVA_LIBRARIES) \
+            $(LOCAL_JNI_SHARED_LIBRARIES) \
+            ,$(subst :,_,$(d)):dynamic \
+        ) \
     )
 endif
 ifeq ($(LOCAL_IS_HOST_MODULE),true)
-notice_deps := $(strip $(notice_deps) $(LOCAL_HOST_REQUIRED_MODULES))
+notice_deps := $(strip $(notice_deps) $(foreach d,$(LOCAL_HOST_REQUIRED_MODULES),$(subst :,_,$(d)):static))
 else
-notice_deps := $(strip $(notice_deps) $(LOCAL_TARGET_REQUIRED_MODULES))
+notice_deps := $(strip $(notice_deps) $(foreach d,$(LOCAL_TARGET_REQUIRED_MODULES),$(subst :,_,$(d)):static))
 endif
 
+local_path := $(LOCAL_PATH)
+
+
+module_license_metadata :=
+
 ifdef my_register_name
-ALL_MODULES.$(my_register_name).LICENSE_PACKAGE_NAME := $(strip $(license_package_name))
-ALL_MODULES.$(my_register_name).LICENSE_KINDS := $(ALL_MODULES.$(my_register_name).LICENSE_KINDS) $(license_kinds)
-ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS := $(ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS) $(license_conditions)
-ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP := $(ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP) $(install_map)
-ALL_MODULES.$(my_register_name).NOTICE_DEPS := $(ALL_MODULES.$(my_register_name).NOTICE_DEPS) $(notice_deps)
-ALL_MODULES.$(my_register_name).IS_CONTAINER := $(strip $(filter-out false,$(ALL_MODULES.$(my_register_name).IS_CONTAINER) $(is_container)))
+  module_license_metadata := $(call local-intermediates-dir)/$(my_register_name).meta_lic
+
+  $(foreach target,$(ALL_MODULES.$(my_register_name).BUILT) $(ALL_MODULES.$(my_register_name).INSTALLED) $(my_test_data) $(my_test_config),\
+    $(eval ALL_TARGETS.$(target).META_LIC := $(module_license_metadata)))
+
+  ALL_MODULES.$(my_register_name).META_LIC := $(strip $(ALL_MODULES.$(my_register_name).META_LIC) $(module_license_metadata))
+
+  ifdef LOCAL_SOONG_LICENSE_METADATA
+    # Soong modules have already produced a license metadata file, copy it to where Make expects it.
+    $(eval $(call copy-one-file, $(LOCAL_SOONG_LICENSE_METADATA), $(module_license_metadata)))
+  else
+    # Make modules don't have enough information to produce a license metadata rule until after fix-notice-deps
+    # has been called, store the necessary information until later.
+    ALL_MODULES.$(my_register_name).DELAYED_META_LIC := $(strip $(ALL_MODULES.$(my_register_name).DELAYED_META_LIC) $(module_license_metadata))
+    ALL_MODULES.$(my_register_name).LICENSE_PACKAGE_NAME := $(strip $(license_package_name))
+    ALL_MODULES.$(my_register_name).MODULE_TYPE := $(strip $(ALL_MODULES.$(my_register_name).MODULE_TYPE) $(LOCAL_MODULE_TYPE))
+    ALL_MODULES.$(my_register_name).MODULE_CLASS := $(strip $(ALL_MODULES.$(my_register_name).MODULE_CLASS) $(LOCAL_MODULE_CLASS))
+    ALL_MODULES.$(my_register_name).LICENSE_KINDS := $(ALL_MODULES.$(my_register_name).LICENSE_KINDS) $(license_kinds)
+    ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS := $(ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS) $(license_conditions)
+    ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP := $(ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP) $(install_map)
+    ALL_MODULES.$(my_register_name).NOTICE_DEPS := $(ALL_MODULES.$(my_register_name).NOTICE_DEPS) $(notice_deps)
+    ALL_MODULES.$(my_register_name).IS_CONTAINER := $(strip $(filter-out false,$(ALL_MODULES.$(my_register_name).IS_CONTAINER) $(is_container)))
+    ALL_MODULES.$(my_register_name).PATH := $(strip $(ALL_MODULES.$(my_register_name).PATH) $(local_path))
+  endif
 endif
 
 ifdef notice_file
@@ -126,100 +157,4 @@
 ALL_MODULES.$(my_register_name).NOTICES := $(ALL_MODULES.$(my_register_name).NOTICES) $(notice_file)
 endif
 
-# This relies on the name of the directory in PRODUCT_OUT matching where
-# it's installed on the target - i.e. system, data, etc.  This does
-# not work for root and isn't exact, but it's probably good enough for
-# compliance.
-# Includes the leading slash
-ifdef LOCAL_INSTALLED_MODULE
-  module_installed_filename := $(patsubst $(PRODUCT_OUT)/%,%,$(LOCAL_INSTALLED_MODULE))
-else
-  # This module isn't installable
-  ifneq ($(filter  STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-    # Stick the static libraries with the dynamic libraries.
-    # We can't use xxx_OUT_STATIC_LIBRARIES because it points into
-    # device-obj or host-obj.
-    module_installed_filename := \
-        $(patsubst $(PRODUCT_OUT)/%,%,$($(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)OUT_SHARED_LIBRARIES))/$(notdir $(LOCAL_BUILT_MODULE))
-  else ifeq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
-    # Shared modules may be uninstallable(e.g. TARGET_SKIP_CURRENT_VNDK=true)
-    module_installed_filename :=
-  else
-    ifeq ($(LOCAL_MODULE_CLASS),JAVA_LIBRARIES)
-      # Stick the static java libraries with the regular java libraries.
-      module_leaf := $(notdir $(LOCAL_BUILT_MODULE))
-      # javalib.jar is the default name for the build module (and isn't meaningful)
-      # If that's what we have, substitute the module name instead.  These files
-      # aren't included on the device, so this name is synthetic anyway.
-      # Extra path "static" is added to try to avoid name conflict between the notice file of
-      # this 'uninstallable' Java module and the notice file for another 'installable' Java module
-      # whose stem is the same as this module's name.
-      ifneq ($(filter javalib.jar,$(module_leaf)),)
-        module_leaf := static/$(LOCAL_MODULE).jar
-      endif
-      module_installed_filename := \
-          $(patsubst $(PRODUCT_OUT)/%,%,$($(my_prefix)OUT_JAVA_LIBRARIES))/$(module_leaf)
-    else ifneq ($(filter ETC DATA,$(LOCAL_MODULE_CLASS)),)
-      # ETC and DATA modules may be uninstallable, yet still have a NOTICE file.
-      # e.g. apex components
-      module_installed_filename :=
-    else ifneq (,$(and $(filter %.sdk,$(LOCAL_MODULE)),$(filter $(patsubst %.sdk,%,$(LOCAL_MODULE)),$(SOONG_SDK_VARIANT_MODULES))))
-      # Soong produces uninstallable *.sdk shared libraries for embedding in APKs.
-      module_installed_filename := \
-          $(patsubst $(PRODUCT_OUT)/%,%,$($(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)OUT_SHARED_LIBRARIES))/$(notdir $(LOCAL_BUILT_MODULE))
-    endif # JAVA_LIBRARIES
-  endif # STATIC_LIBRARIES
-endif
-
-ifdef module_installed_filename
-
-# In case it's actually a host file
-module_installed_filename := $(patsubst $(HOST_OUT)/%,%,$(module_installed_filename))
-module_installed_filename := $(patsubst $(HOST_CROSS_OUT)/%,%,$(module_installed_filename))
-
-installed_notice_file := $($(my_prefix)OUT_NOTICE_FILES)/src/$(module_installed_filename).txt
-
-ifdef my_register_name
-ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE := $(ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE) $(installed_notice_file)
-ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES := $(ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES) $(module_installed_filename)
-INSTALLED_NOTICE_FILES.$(installed_notice_file).MODULE := $(my_register_name)
-else
-$(installed_notice_file): PRIVATE_INSTALLED_MODULE := $(module_installed_filename)
-$(installed_notice_file) : PRIVATE_NOTICES := $(notice_file)
-
-$(installed_notice_file): $(notice_file)
-	@echo Notice file: $< -- $@
-	$(hide) mkdir -p $(dir $@)
-	$(hide) awk 'FNR==1 && NR > 1 {print "\n"} {print}' $(PRIVATE_NOTICES) > $@
-endif
-
-ifdef LOCAL_INSTALLED_MODULE
-# Make LOCAL_INSTALLED_MODULE depend on NOTICE files if they exist
-# libraries so they get installed along with it.  Make it an order-only
-# dependency so we don't re-install a module when the NOTICE changes.
-$(LOCAL_INSTALLED_MODULE): | $(installed_notice_file)
-endif
-
-# To facilitate collecting NOTICE files for apps_only build,
-# we install the NOTICE file even if a module gets built but not installed,
-# because shared jni libraries won't be installed to the system image.
-ifdef TARGET_BUILD_APPS
-# for static Java libraries, we don't need to even build LOCAL_BUILT_MODULE,
-# but just javalib.jar in the common intermediate dir.
-ifeq ($(LOCAL_MODULE_CLASS),JAVA_LIBRARIES)
-$(intermediates.COMMON)/javalib.jar : | $(installed_notice_file)
-else
-$(LOCAL_BUILT_MODULE): | $(installed_notice_file)
-endif  # JAVA_LIBRARIES
-endif  # TARGET_BUILD_APPS
-
-endif  # module_installed_filename
 endif  # notice_file
-
-# Create a predictable, phony target to build this notice file.
-# Define it even if the notice file doesn't exist so that other
-# modules can depend on it.
-notice_target := NOTICE-$(if \
-    $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-$(LOCAL_MODULE_CLASS)-$(LOCAL_MODULE)
-.PHONY: $(notice_target)
-$(notice_target): $(installed_notice_file)
diff --git a/core/os_licensing.mk b/core/os_licensing.mk
new file mode 100644
index 0000000..416e4b2
--- /dev/null
+++ b/core/os_licensing.mk
@@ -0,0 +1,167 @@
+ifeq ($(TARGET_BUILD_APPS),)
+
+.PHONY: systemlicense
+systemlicense: $(call corresponding-license-metadata, $(SYSTEM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_NOTICE_DEPS))
+
+SYSTEM_NOTICE_DEPS += $(UNMOUNTED_NOTICE_DEPS)
+
+ifneq ($(PRODUCT_NOTICE_SPLIT),true)
+$(eval $(call html-notice-rule,$(target_notice_file_html_gz),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(installed_notice_html_or_xml_gz): $(target_notice_file_html_gz)
+	$(copy-file-to-target)
+else
+$(eval $(call xml-notice-rule,$(target_notice_file_xml_gz),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(eval $(call text-notice-rule,$(target_notice_file_txt),"System image",$(system_notice_file_message),$(SYSTEM_NOTICE_DEPS),$(SYSTEM_NOTICE_DEPS)))
+
+$(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
+	$(copy-file-to-target)
+endif
+
+$(call declare-0p-target,$(target_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_notice_html_or_xml_gz))
+endif
+
+.PHONY: vendorlicense
+vendorlicense: $(call corresponding-license-metadata, $(VENDOR_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(VENDOR_NOTICE_DEPS))
+
+VENDOR_NOTICE_DEPS += $(UNMOUNTED_NOTICE_DEPS)
+
+$(eval $(call text-notice-rule,$(target_vendor_notice_file_txt),"Vendor image", \
+         "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
+         $(VENDOR_NOTICE_DEPS),$(VENDOR_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_vendor_notice_file_xml_gz),"Vendor image", \
+         "Notices for files contained in all filesystem images except system/system_ext/product/odm/vendor_dlkm/odm_dlkm in this directory:", \
+         $(VENDOR_NOTICE_DEPS),$(VENDOR_NOTICE_DEPS)))
+
+$(installed_vendor_notice_xml_gz): $(target_vendor_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_vendor_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_vendor_notice_xml_gz))
+endif
+
+.PHONY: odmlicense
+odmlicense: $(call corresponding-license-metadata, $(ODM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(ODM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_odm_notice_file_txt),"ODM filesystem image", \
+         "Notices for files contained in the odm filesystem image in this directory:", \
+         $(ODM_NOTICE_DEPS),$(ODM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_odm_notice_file_xml_gz),"ODM filesystem image", \
+         "Notices for files contained in the odm filesystem image in this directory:", \
+         $(ODM_NOTICE_DEPS),$(ODM_NOTICE_DEPS)))
+
+$(installed_odm_notice_xml_gz): $(target_odm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_odm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_odm_notice_xml_gz))
+endif
+
+.PHONY: oemlicense
+oemlicense: $(call corresponding-license-metadata, $(OEM_NOTICE_DEPS)) reportmissinglicenses
+
+.PHONY: productlicense
+productlicense: $(call corresponding-license-metadata, $(PRODUCT_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(PRODUCT_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_product_notice_file_txt),"Product image", \
+         "Notices for files contained in the product filesystem image in this directory:", \
+         $(PRODUCT_NOTICE_DEPS),$(PRODUCT_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_product_notice_file_xml_gz),"Product image", \
+         "Notices for files contained in the product filesystem image in this directory:", \
+         $(PRODUCT_NOTICE_DEPS),$(PRODUCT_NOTICE_DEPS)))
+
+$(installed_product_notice_xml_gz): $(target_product_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_product_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_product_notice_xml_gz))
+endif
+
+.PHONY: systemextlicense
+systemextlicense: $(call corresponding-license-metadata, $(SYSTEM_EXT_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_EXT_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_system_ext_notice_file_txt),"System_ext image", \
+         "Notices for files contained in the system_ext filesystem image in this directory:", \
+         $(SYSTEM_EXT_NOTICE_DEPS),$(SYSTEM_EXT_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_system_ext_notice_file_xml_gz),"System_ext image", \
+         "Notices for files contained in the system_ext filesystem image in this directory:", \
+         $(SYSTEM_EXT_NOTICE_DEPS),$(SYSTEM_EXT_NOTICE_DEPS)))
+
+$(installed_system_ext_notice_xml_gz): $(target_system_ext_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_system_ext_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_system_ext_notice_xml_gz))
+endif
+
+.PHONY: vendor_dlkmlicense
+vendor_dlkmlicense: $(call corresponding-license-metadata, $(VENDOR_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(VENDOR_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_vendor_dlkm_notice_file_txt),"Vendor_dlkm image", \
+         "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
+         $(VENDOR_DLKM_NOTICE_DEPS),$(VENDOR_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_vendor_dlkm_notice_file_xml_gz),"Vendor_dlkm image", \
+         "Notices for files contained in the vendor_dlkm filesystem image in this directory:", \
+         $(VENDOR_DLKM_NOTICE_DEPS),$(VENDOR_DLKM_NOTICE_DEPS)))
+
+$(installed_vendor_dlkm_notice_xml_gz): $(target_vendor_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_vendor_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_vendor_dlkm_notice_xml_gz))
+endif
+
+.PHONY: odm_dlkmlicense
+odm_dlkmlicense: $(call corresponding-license-metadata, $(ODM_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(ODM_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_odm_dlkm_notice_file_txt),"ODM_dlkm filesystem image", \
+         "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
+         $(ODM_DLKM_NOTICE_DEPS),$(ODM_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_odm_dlkm_notice_file_xml_gz),"ODM_dlkm filesystem image", \
+         "Notices for files contained in the odm_dlkm filesystem image in this directory:", \
+         $(ODM_DLKM_NOTICE_DEPS),$(ODM_DLKM_NOTICE_DEPS)))
+
+$(installed_odm_dlkm_notice_xml_gz): $(target_odm_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_odm_dlkm_notice_xml_gz))
+endif
+
+.PHONY: system_dlkmlicense
+system_dlkmlicense: $(call corresponding-license-metadata, $(SYSTEM_DLKM_NOTICE_DEPS)) reportmissinglicenses
+
+ifneq (,$(SYSTEM_DLKM_NOTICE_DEPS))
+$(eval $(call text-notice-rule,$(target_system_dlkm_notice_file_txt),"System_dlkm filesystem image", \
+         "Notices for files contained in the system_dlkm filesystem image in this directory:", \
+         $(SYSTEM_DLKM_NOTICE_DEPS),$(SYSTEM_DLKM_NOTICE_DEPS)))
+
+$(eval $(call xml-notice-rule,$(target_system_dlkm_notice_file_xml_gz),"System_dlkm filesystem image", \
+         "Notices for files contained in the system_dlkm filesystem image in this directory:", \
+         $(SYSTEM_DLKM_NOTICE_DEPS),$(SYSTEM_DLKM_NOTICE_DEPS)))
+
+$(installed_system_dlkm_notice_xml_gz): $(target_system_dlkm_notice_file_xml_gz)
+	$(copy-file-to-target)
+
+$(call declare-0p-target,$(target_system_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(installed_sysetm_dlkm_notice_xml_gz))
+endif
+
+endif # not TARGET_BUILD_APPS
diff --git a/core/package_internal.mk b/core/package_internal.mk
index 9f5a599..c7a173b 100644
--- a/core/package_internal.mk
+++ b/core/package_internal.mk
@@ -35,6 +35,10 @@
 endif
 LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
 
+ifneq ($(strip $(LOCAL_MODULE_STEM)$(LOCAL_BUILT_MODULE_STEM)),)
+$(error $(LOCAL_PATH): Package modules may not define LOCAL_MODULE_STEM or LOCAL_BUILT_MODULE_STEM)
+endif
+
 ifneq ($(strip $(LOCAL_MODULE)),)
 $(error $(LOCAL_PATH): Package modules may not define LOCAL_MODULE)
 endif
@@ -93,6 +97,14 @@
 endif
 
 include $(BUILD_SYSTEM)/force_aapt2.mk
+# validate that app contains a manifest file for aapt2
+ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
+  ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
+    $(call pretty-error,App missing manifest file which is required by aapt2. \
+Provide a manifest file by either setting LOCAL_MANIFEST_FILE in Android.mk \
+or via a AndroidManifest.xml in this directory)
+  endif
+endif
 
 # Process Support Library dependencies.
 include $(BUILD_SYSTEM)/support_libraries.mk
@@ -469,6 +481,8 @@
 $(LOCAL_BUILT_MODULE): $(LOCAL_CERTIFICATE_LINEAGE)
 $(LOCAL_BUILT_MODULE): PRIVATE_CERTIFICATE_LINEAGE := $(LOCAL_CERTIFICATE_LINEAGE)
 
+$(LOCAL_BUILT_MODULE): PRIVATE_ROTATION_MIN_SDK_VERSION := $(LOCAL_ROTATION_MIN_SDK_VERSION)
+
 # Set a actual_partition_tag (calculated in base_rules.mk) for the package.
 PACKAGES.$(LOCAL_PACKAGE_NAME).PARTITION := $(actual_partition_tag)
 
diff --git a/core/product-graph.mk b/core/product-graph.mk
index 968d01b..379110e 100644
--- a/core/product-graph.mk
+++ b/core/product-graph.mk
@@ -14,16 +14,13 @@
 # limitations under the License.
 #
 
-# the foreach and the if remove the single space entries that creep in because of the evals
-define gather-all-products
-$(sort $(foreach p, \
-	$(eval _all_products_visited := )
-  $(call all-products-inner, $(PARENT_PRODUCT_FILES)) \
-	, $(if $(strip $(p)),$(strip $(p)),)) \
-)
+# the sort also acts as a strip to remove the single space entries that creep in because of the evals
+define gather-all-makefiles-for-current-product
+$(eval _all_products_visited := )\
+$(sort $(call gather-all-makefiles-for-current-product-inner,$(INTERNAL_PRODUCT)))
 endef
 
-define all-products-inner
+define gather-all-makefiles-for-current-product-inner
 	$(foreach p,$(1),\
 		$(if $(filter $(p),$(_all_products_visited)),, \
 			$(p) \
@@ -33,30 +30,12 @@
 	)
 endef
 
-this_makefile := build/make/core/product-graph.mk
-
-products_graph := $(OUT_DIR)/products.dot
-ifeq ($(strip $(ANDROID_PRODUCT_GRAPH)),)
-products_list := $(INTERNAL_PRODUCT)
-else
-ifeq ($(strip $(ANDROID_PRODUCT_GRAPH)),--all)
-products_list := --all
-else
-products_list := $(foreach prod,$(ANDROID_PRODUCT_GRAPH),$(call resolve-short-product-name,$(prod)))
-endif
-endif
-
-all_products := $(call gather-all-products)
-
-open_parethesis := (
-close_parenthesis := )
-
 node_color_target := orange
 node_color_common := beige
 node_color_vendor := lavenderblush
 node_color_default := white
 define node-color
-$(if $(filter $(1),$(PRIVATE_PRODUCTS_FILTER)),\
+$(if $(filter $(1),$(PRIVATE_TOP_LEVEL_MAKEFILE)),\
   $(node_color_target),\
   $(if $(filter build/make/target/product/%,$(1)),\
     $(node_color_common),\
@@ -65,89 +44,33 @@
 )
 endef
 
+open_parethesis := (
+close_parenthesis := )
+
 # Emit properties of a product node to a file.
 # $(1) the product
 # $(2) the output file
 define emit-product-node-props
 $(hide) echo \"$(1)\" [ \
-label=\"$(dir $(1))\\n$(notdir $(1))\\n\\n$(subst $(close_parenthesis),,$(subst $(open_parethesis),,$(call get-product-var,$(1),PRODUCT_MODEL)))\\n$(call get-product-var,$(1),PRODUCT_DEVICE)\" \
+label=\"$(dir $(1))\\n$(notdir $(1))$(if $(filter $(1),$(PRIVATE_TOP_LEVEL_MAKEFILE)),$(subst $(open_parethesis),,$(subst $(close_parenthesis),,\\n\\n$(PRODUCT_MODEL)\\n$(PRODUCT_DEVICE))))\" \
 style=\"filled\" fillcolor=\"$(strip $(call node-color,$(1)))\" \
-colorscheme=\"svg\" fontcolor=\"darkblue\" href=\"products/$(1).html\" \
+colorscheme=\"svg\" fontcolor=\"darkblue\" \
 ] >> $(2)
 
 endef
 
-$(products_graph): PRIVATE_PRODUCTS := $(all_products)
-$(products_graph): PRIVATE_PRODUCTS_FILTER := $(products_list)
+products_graph := $(OUT_DIR)/products.dot
 
-$(products_graph): $(this_makefile)
-	@echo Product graph DOT: $@ for $(PRIVATE_PRODUCTS_FILTER)
-	$(hide) echo 'digraph {' > $@.in
-	$(hide) echo 'graph [ ratio=.5 ];' >> $@.in
-	$(hide) $(foreach p,$(PRIVATE_PRODUCTS), \
-	  $(foreach d,$(PRODUCTS.$(strip $(p)).INHERITS_FROM), echo \"$(d)\" -\> \"$(p)\" >> $@.in;))
-	$(foreach p,$(PRIVATE_PRODUCTS),$(call emit-product-node-props,$(p),$@.in))
-	$(hide) echo '}' >> $@.in
-	$(hide) build/make/tools/filter-product-graph.py $(PRIVATE_PRODUCTS_FILTER) < $@.in > $@
-
-# Evaluates to the name of the product file
-# $(1) product file
-define product-debug-filename
-$(OUT_DIR)/products/$(strip $(1)).html
-endef
-
-# Makes a rule for the product debug info
-# $(1) product file
-define transform-product-debug
-$(OUT_DIR)/products/$(strip $(1)).txt: $(this_makefile)
-	@echo Product debug info file: $$@
-	$(hide) rm -f $$@
-	$(hide) mkdir -p $$(dir $$@)
-	$(hide) echo 'FILE=$(strip $(1))' >> $$@
-	$(hide) echo 'PRODUCT_NAME=$(call get-product-var,$(1),PRODUCT_NAME)' >> $$@
-	$(hide) echo 'PRODUCT_MODEL=$(call get-product-var,$(1),PRODUCT_MODEL)' >> $$@
-	$(hide) echo 'PRODUCT_LOCALES=$(call get-product-var,$(1),PRODUCT_LOCALES)' >> $$@
-	$(hide) echo 'PRODUCT_AAPT_CONFIG=$(call get-product-var,$(1),PRODUCT_AAPT_CONFIG)' >> $$@
-	$(hide) echo 'PRODUCT_AAPT_PREF_CONFIG=$(call get-product-var,$(1),PRODUCT_AAPT_PREF_CONFIG)' >> $$@
-	$(hide) echo 'PRODUCT_PACKAGES=$(call get-product-var,$(1),PRODUCT_PACKAGES)' >> $$@
-	$(hide) echo 'PRODUCT_DEVICE=$(call get-product-var,$(1),PRODUCT_DEVICE)' >> $$@
-	$(hide) echo 'PRODUCT_MANUFACTURER=$(call get-product-var,$(1),PRODUCT_MANUFACTURER)' >> $$@
-	$(hide) echo 'PRODUCT_PROPERTY_OVERRIDES=$(call get-product-var,$(1),PRODUCT_PROPERTY_OVERRIDES)' >> $$@
-	$(hide) echo 'PRODUCT_DEFAULT_PROPERTY_OVERRIDES=$(call get-product-var,$(1),PRODUCT_DEFAULT_PROPERTY_OVERRIDES)' >> $$@
-	$(hide) echo 'PRODUCT_SYSTEM_DEFAULT_PROPERTIES=$(call get-product-var,$(1),PRODUCT_SYSTEM_DEFAULT_PROPERTIES)' >> $$@
-	$(hide) echo 'PRODUCT_PRODUCT_PROPERTIES=$(call get-product-var,$(1),PRODUCT_PRODUCT_PROPERTIES)' >> $$@
-	$(hide) echo 'PRODUCT_SYSTEM_EXT_PROPERTIES=$(call get-product-var,$(1),PRODUCT_SYSTEM_EXT_PROPERTIES)' >> $$@
-	$(hide) echo 'PRODUCT_ODM_PROPERTIES=$(call get-product-var,$(1),PRODUCT_ODM_PROPERTIES)' >> $$@
-	$(hide) echo 'PRODUCT_CHARACTERISTICS=$(call get-product-var,$(1),PRODUCT_CHARACTERISTICS)' >> $$@
-	$(hide) echo 'PRODUCT_COPY_FILES=$(call get-product-var,$(1),PRODUCT_COPY_FILES)' >> $$@
-	$(hide) echo 'PRODUCT_OTA_PUBLIC_KEYS=$(call get-product-var,$(1),PRODUCT_OTA_PUBLIC_KEYS)' >> $$@
-	$(hide) echo 'PRODUCT_EXTRA_RECOVERY_KEYS=$(call get-product-var,$(1),PRODUCT_EXTRA_RECOVERY_KEYS)' >> $$@
-	$(hide) echo 'PRODUCT_PACKAGE_OVERLAYS=$(call get-product-var,$(1),PRODUCT_PACKAGE_OVERLAYS)' >> $$@
-	$(hide) echo 'DEVICE_PACKAGE_OVERLAYS=$(call get-product-var,$(1),DEVICE_PACKAGE_OVERLAYS)' >> $$@
-	$(hide) echo 'PRODUCT_SDK_ADDON_NAME=$(call get-product-var,$(1),PRODUCT_SDK_ADDON_NAME)' >> $$@
-	$(hide) echo 'PRODUCT_SDK_ADDON_COPY_FILES=$(call get-product-var,$(1),PRODUCT_SDK_ADDON_COPY_FILES)' >> $$@
-	$(hide) echo 'PRODUCT_SDK_ADDON_COPY_MODULES=$(call get-product-var,$(1),PRODUCT_SDK_ADDON_COPY_MODULES)' >> $$@
-	$(hide) echo 'PRODUCT_SDK_ADDON_DOC_MODULES=$(call get-product-var,$(1),PRODUCT_SDK_ADDON_DOC_MODULES)' >> $$@
-	$(hide) echo 'PRODUCT_DEFAULT_WIFI_CHANNELS=$(call get-product-var,$(1),PRODUCT_DEFAULT_WIFI_CHANNELS)' >> $$@
-	$(hide) echo 'PRODUCT_DEFAULT_DEV_CERTIFICATE=$(call get-product-var,$(1),PRODUCT_DEFAULT_DEV_CERTIFICATE)' >> $$@
-	$(hide) echo 'PRODUCT_MAINLINE_SEPOLICY_DEV_CERTIFICATES=$(call get-product-var,$(1),PRODUCT_MAINLINE_SEPOLICY_DEV_CERTIFICATES)' >> $$@
-	$(hide) echo 'PRODUCT_RESTRICT_VENDOR_FILES=$(call get-product-var,$(1),PRODUCT_RESTRICT_VENDOR_FILES)' >> $$@
-	$(hide) echo 'PRODUCT_VENDOR_KERNEL_HEADERS=$(call get-product-var,$(1),PRODUCT_VENDOR_KERNEL_HEADERS)' >> $$@
-
-$(call product-debug-filename, $(p)): \
-			$(OUT_DIR)/products/$(strip $(1)).txt \
-			build/make/tools/product_debug.py \
-			$(this_makefile)
-	@echo Product debug html file: $$@
-	$(hide) mkdir -p $$(dir $$@)
-	$(hide) cat $$< | build/make/tools/product_debug.py > $$@
-endef
-
-product_debug_files:=
-$(foreach p,$(all_products), \
-			$(eval $(call transform-product-debug, $(p))) \
-			$(eval product_debug_files += $(call product-debug-filename, $(p))) \
-   )
+$(products_graph): PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT := $(call gather-all-makefiles-for-current-product)
+$(products_graph): PRIVATE_TOP_LEVEL_MAKEFILE := $(INTERNAL_PRODUCT)
+$(products_graph):
+	@echo Product graph DOT: $@ for $(PRIVATE_TOP_LEVEL_MAKEFILE)
+	$(hide) echo 'digraph {' > $@
+	$(hide) echo 'graph [ ratio=.5 ];' >> $@
+	$(hide) $(foreach p,$(PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT), \
+	  $(foreach d,$(PRODUCTS.$(strip $(p)).INHERITS_FROM), echo \"$(d)\" -\> \"$(p)\" >> $@;))
+	$(foreach p,$(PRIVATE_ALL_MAKEFILES_FOR_THIS_PRODUCT),$(call emit-product-node-props,$(p),$@))
+	$(hide) echo '}' >> $@
 
 .PHONY: product-graph
 product-graph: $(products_graph)
diff --git a/core/product.mk b/core/product.mk
index eb99a9b..53fee1c 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -14,98 +14,6 @@
 # limitations under the License.
 #
 
-#
-# Functions for including AndroidProducts.mk files
-# PRODUCT_MAKEFILES is set up in AndroidProducts.mks.
-# Format of PRODUCT_MAKEFILES:
-# <product_name>:<path_to_the_product_makefile>
-# If the <product_name> is the same as the base file name (without dir
-# and the .mk suffix) of the product makefile, "<product_name>:" can be
-# omitted.
-
-#
-# Returns the list of all AndroidProducts.mk files.
-# $(call ) isn't necessary.
-#
-define _find-android-products-files
-$(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
-  $(SRC_TARGET_DIR)/product/AndroidProducts.mk
-endef
-
-#
-# For entries returned by get-product-makefiles, decode an entry to a short
-# product name. These either may be in the form of <name>:path/to/file.mk or
-# path/to/<name>.mk
-# $(1): The entry to decode
-#
-# Returns two words:
-#   <name> <file>
-#
-define _decode-product-name
-$(strip \
-  $(eval _cpm_words := $(subst :,$(space),$(1))) \
-  $(if $(word 2,$(_cpm_words)), \
-    $(wordlist 1,2,$(_cpm_words)), \
-    $(basename $(notdir $(1))) $(1)))
-endef
-
-#
-# Validates the new common lunch choices -- ensures that they're in an
-# appropriate form, and are paired with definitions of their products.
-# $(1): The new list of COMMON_LUNCH_CHOICES
-# $(2): The new list of PRODUCT_MAKEFILES
-#
-define _validate-common-lunch-choices
-$(strip $(foreach choice,$(1),\
-  $(eval _parts := $(subst -,$(space),$(choice))) \
-  $(if $(call math_lt,$(words $(_parts)),2), \
-    $(error $(LOCAL_DIR): $(choice): Invalid lunch choice)) \
-  $(if $(call math_gt_or_eq,$(words $(_parts)),4), \
-    $(error $(LOCAL_DIR): $(choice): Invalid lunch choice)) \
-  $(if $(filter-out eng userdebug user,$(word 2,$(_parts))), \
-    $(error $(LOCAL_DIR): $(choice): Invalid variant: $(word 2,$(_parts)))) \
-  $(if $(filter-out $(foreach p,$(2),$(call _decode-product-name,$(p))),$(word 1,$(_parts))), \
-    $(error $(LOCAL_DIR): $(word 1,$(_parts)): Product not defined in this file)) \
-  ))
-endef
-
-#
-# Returns the sorted concatenation of PRODUCT_MAKEFILES
-# variables set in the given AndroidProducts.mk files.
-# $(1): the list of AndroidProducts.mk files.
-#
-# As a side-effect, COMMON_LUNCH_CHOICES will be set to a
-# union of all of the COMMON_LUNCH_CHOICES definitions within
-# each AndroidProducts.mk file.
-#
-define get-product-makefiles
-$(sort \
-  $(eval _COMMON_LUNCH_CHOICES :=) \
-  $(foreach f,$(1), \
-    $(eval PRODUCT_MAKEFILES :=) \
-    $(eval COMMON_LUNCH_CHOICES :=) \
-    $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
-    $(eval include $(f)) \
-    $(call _validate-common-lunch-choices,$(COMMON_LUNCH_CHOICES),$(PRODUCT_MAKEFILES)) \
-    $(eval _COMMON_LUNCH_CHOICES += $(COMMON_LUNCH_CHOICES)) \
-    $(PRODUCT_MAKEFILES) \
-   ) \
-  $(eval PRODUCT_MAKEFILES :=) \
-  $(eval LOCAL_DIR :=) \
-  $(eval COMMON_LUNCH_CHOICES := $(sort $(_COMMON_LUNCH_CHOICES))) \
-  $(eval _COMMON_LUNCH_CHOICES :=) \
- )
-endef
-
-#
-# Returns the sorted concatenation of all PRODUCT_MAKEFILES
-# variables set in all AndroidProducts.mk files.
-# $(call ) isn't necessary.
-#
-define get-all-product-makefiles
-$(call get-product-makefiles,$(_find-android-products-files))
-endef
-
 # Variables that are meant to hold only a single value.
 # - The value set in the current makefile takes precedence over inherited values
 # - If multiple inherited makefiles set the var, the first-inherited value wins
@@ -183,6 +91,7 @@
 # signing tools can substitute them for the test key embedded by
 # default.
 _product_list_vars += PRODUCT_OTA_PUBLIC_KEYS
+_product_list_vars += PRODUCT_EXTRA_OTA_KEYS
 _product_list_vars += PRODUCT_EXTRA_RECOVERY_KEYS
 
 # Should we use the default resources or add any product specific overlays
@@ -213,12 +122,18 @@
 # The list of product-specific kernel header dirs
 _product_list_vars += PRODUCT_VENDOR_KERNEL_HEADERS
 
-# A list of module names of BOOTCLASSPATH (jar files)
+# A list of module names in BOOTCLASSPATH (jar files). Each module may be
+# prefixed with "<apex>:", which identifies the APEX that provides it. APEXes
+# are identified by their "variant" names, i.e. their `apex_name` values in
+# Soong, which default to the `name` values. The prefix can also be "platform:"
+# or "system_ext:", and defaults to "platform:" if left out. See the long
+# comment in build/soong/java/dexprepopt_bootjars.go for details.
 _product_list_vars += PRODUCT_BOOT_JARS
 
-# A list of extra BOOTCLASSPATH jars (to be appended after common jars).
-# Products that include device-specific makefiles before AOSP makefiles should use this
-# instead of PRODUCT_BOOT_JARS, so that device-specific jars go after common jars.
+# A list of extra BOOTCLASSPATH jars (to be appended after common jars),
+# following the same format as PRODUCT_BOOT_JARS. Products that include
+# device-specific makefiles before AOSP makefiles should use this instead of
+# PRODUCT_BOOT_JARS, so that device-specific jars go after common jars.
 _product_list_vars += PRODUCT_BOOT_JARS_EXTRA
 
 _product_single_value_vars += PRODUCT_SUPPORTS_BOOT_SIGNER
@@ -226,11 +141,19 @@
 _product_single_value_vars += PRODUCT_SUPPORTS_VERITY
 _product_single_value_vars += PRODUCT_SUPPORTS_VERITY_FEC
 _product_list_vars += PRODUCT_SYSTEM_SERVER_APPS
+# List of system_server classpath jars on the platform.
 _product_list_vars += PRODUCT_SYSTEM_SERVER_JARS
-# List of system_server jars delivered via apex. Format = <apex name>:<jar name>.
+# List of system_server classpath jars delivered via apex. Format = <apex name>:<jar name>.
 _product_list_vars += PRODUCT_APEX_SYSTEM_SERVER_JARS
+# List of jars on the platform that system_server loads dynamically using separate classloaders.
+_product_list_vars += PRODUCT_STANDALONE_SYSTEM_SERVER_JARS
+# List of jars delivered via apex that system_server loads dynamically using separate classloaders.
+# Format = <apex name>:<jar name>
+_product_list_vars += PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS
 # If true, then suboptimal order of system server jars does not cause an error.
 _product_single_value_vars += PRODUCT_BROKEN_SUBOPTIMAL_ORDER_OF_SYSTEM_SERVER_JARS
+# If true, then system server jars defined in Android.mk are supported.
+_product_single_value_vars += PRODUCT_BROKEN_DEPRECATED_MK_SYSTEM_SERVER_JARS
 
 # Additional system server jars to be appended at the end of the common list.
 # This is necessary to avoid jars reordering due to makefile inheritance order.
@@ -253,6 +176,7 @@
 _product_single_value_vars += PRODUCT_ODM_VERITY_PARTITION
 _product_single_value_vars += PRODUCT_VENDOR_DLKM_VERITY_PARTITION
 _product_single_value_vars += PRODUCT_ODM_DLKM_VERITY_PARTITION
+_product_single_value_vars += PRODUCT_SYSTEM_DLKM_VERITY_PARTITION
 _product_single_value_vars += PRODUCT_SYSTEM_SERVER_DEBUG_INFO
 _product_single_value_vars += PRODUCT_OTHER_JAVA_DEBUG_INFO
 
@@ -267,9 +191,10 @@
 _product_single_value_vars += PRODUCT_DEX_PREOPT_RESOLVE_STARTUP_STRINGS
 
 # Boot image options.
+_product_list_vars += PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION
 _product_single_value_vars += \
+    PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST \
     PRODUCT_USE_PROFILE_FOR_BOOT_IMAGE \
-    PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION \
     PRODUCT_USES_DEFAULT_ART_CONFIG \
 
 _product_single_value_vars += PRODUCT_SYSTEM_SERVER_COMPILER_FILTER
@@ -282,6 +207,7 @@
 _product_single_value_vars += PRODUCT_ODM_BASE_FS_PATH
 _product_single_value_vars += PRODUCT_VENDOR_DLKM_BASE_FS_PATH
 _product_single_value_vars += PRODUCT_ODM_DLKM_BASE_FS_PATH
+_product_single_value_vars += PRODUCT_SYSTEM_DLKM_BASE_FS_PATH
 
 # The first API level this product shipped with
 _product_single_value_vars += PRODUCT_SHIPPING_API_LEVEL
@@ -366,11 +292,6 @@
 _product_list_vars += PRODUCT_PACKAGE_NAME_OVERRIDES
 _product_list_vars += PRODUCT_CERTIFICATE_OVERRIDES
 
-# A list of <overridden-apex>:<override-apex> pairs that specifies APEX module
-# overrides to be applied to the APEX names in the boot jar variables
-# (PRODUCT_BOOT_JARS, PRODUCT_APEX_BOOT_JARS etc).
-_product_list_vars += PRODUCT_BOOT_JAR_MODULE_OVERRIDES
-
 # Controls for whether different partitions are built for the current product.
 _product_single_value_vars += PRODUCT_BUILD_SYSTEM_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_SYSTEM_OTHER_IMAGE
@@ -380,14 +301,20 @@
 _product_single_value_vars += PRODUCT_BUILD_ODM_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_VENDOR_DLKM_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_ODM_DLKM_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_SYSTEM_DLKM_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_CACHE_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_RAMDISK_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_USERDATA_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_RECOVERY_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_INIT_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_DEBUG_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_VENDOR_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_VENDOR_KERNEL_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_VBMETA_IMAGE
 _product_single_value_vars += PRODUCT_BUILD_SUPER_EMPTY_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_PVMFW_IMAGE
 
 # List of boot jars delivered via updatable APEXes, following the same format as
 # PRODUCT_BOOT_JARS.
@@ -428,20 +355,28 @@
 
 _product_single_value_vars += PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES
 
+# Install a copy of the debug policy to the system_ext partition, and allow
+# init-second-stage to load debug policy from system_ext.
+# This option is only meant to be set by compliance GSI targets.
+_product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT
+
+# If set, metadata files for the following artifacts will be generated.
+# - system/framework/*.jar
+# - system/framework/oat/<arch>/*.{oat,vdex,art}
+# - system/etc/boot-image.prof
+# - system/etc/dirty-image-objects
+# One fsverity metadata container file per one input file will be generated in
+# system.img, with a suffix ".fsv_meta". e.g. a container file for
+# "/system/framework/foo.jar" will be "system/framework/foo.jar.fsv_meta".
+_product_single_value_vars += PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
+# If true, sets the default for MODULE_BUILD_FROM_SOURCE. This overrides
+# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE but not an explicitly set value.
+_product_single_value_vars += PRODUCT_MODULE_BUILD_FROM_SOURCE
+
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
 
-define dump-product
-$(warning ==== $(1) ====)\
-$(foreach v,$(_product_var_list),\
-$(warning PRODUCTS.$(1).$(v) := $(call get-product-var,$(1),$(v))))\
-$(warning --------)
-endef
-
-define dump-products
-$(foreach p,$(PRODUCTS),$(call dump-product,$(p)))
-endef
-
 #
 # Functions for including product makefiles
 #
@@ -450,26 +385,27 @@
 # $(1): product to inherit
 #
 # To be called from product makefiles, and is later evaluated during the import-nodes
-# call below. It does three things:
+# call below. It does the following:
 #  1. Inherits all of the variables from $1.
 #  2. Records the inheritance in the .INHERITS_FROM variable
-#  3. Records the calling makefile in PARENT_PRODUCT_FILES
 #
-# (2) and (3) can be used together to reconstruct the include hierarchy
+# (2) and the PRODUCTS variable can be used together to reconstruct the include hierarchy
 # See e.g. product-graph.mk for an example of this.
 #
 define inherit-product
-  $(if $(findstring ../,$(1)),\
-    $(eval np := $(call normalize-paths,$(1))),\
-    $(eval np := $(strip $(1))))\
-  $(foreach v,$(_product_var_list), \
-      $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \
-  $(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
-  $(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
-  $(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
-  $(eval PARENT_PRODUCT_FILES := $(sort $(PARENT_PRODUCT_FILES) $(current_mk))) \
-  $(call dump-inherit,$(strip $(word 1,$(_include_stack))),$(1)) \
-  $(call dump-config-vals,$(current_mk),inherit)
+  $(eval _inherit_product_wildcard := $(wildcard $(1)))\
+  $(if $(_inherit_product_wildcard),,$(error $(1) does not exist.))\
+  $(foreach part,$(_inherit_product_wildcard),\
+    $(if $(findstring ../,$(part)),\
+      $(eval np := $(call normalize-paths,$(part))),\
+      $(eval np := $(strip $(part))))\
+    $(foreach v,$(_product_var_list), \
+        $(eval $(v) := $($(v)) $(INHERIT_TAG)$(np))) \
+    $(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
+    $(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
+    $(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
+    $(call dump-inherit,$(strip $(word 1,$(_include_stack))),$(1)) \
+    $(call dump-config-vals,$(current_mk),inherit))
 endef
 
 # Specifies a number of path prefixes, relative to PRODUCT_OUT, where the
@@ -517,64 +453,18 @@
 
 
 #
-# Does various consistency checks on all of the known products.
+# Does various consistency checks on the current product.
 # Takes no parameters, so $(call ) is not necessary.
 #
-define check-all-products
+define check-current-product
 $(if ,, \
-  $(eval _cap_names :=) \
-  $(foreach p,$(PRODUCTS), \
-    $(eval pn := $(strip $(PRODUCTS.$(p).PRODUCT_NAME))) \
-    $(if $(pn),,$(error $(p): PRODUCT_NAME must be defined.)) \
-    $(if $(filter $(pn),$(_cap_names)), \
-      $(error $(p): PRODUCT_NAME must be unique; "$(pn)" already used by $(strip \
-          $(foreach \
-            pp,$(PRODUCTS),
-              $(if $(filter $(pn),$(PRODUCTS.$(pp).PRODUCT_NAME)), \
-                $(pp) \
-               ))) \
-       ) \
-     ) \
-    $(eval _cap_names += $(pn)) \
-    $(if $(call is-c-identifier,$(pn)),, \
-      $(error $(p): PRODUCT_NAME must be a valid C identifier, not "$(pn)") \
-     ) \
-    $(eval pb := $(strip $(PRODUCTS.$(p).PRODUCT_BRAND))) \
-    $(if $(pb),,$(error $(p): PRODUCT_BRAND must be defined.)) \
-    $(foreach cf,$(strip $(PRODUCTS.$(p).PRODUCT_COPY_FILES)), \
-      $(if $(filter 2 3,$(words $(subst :,$(space),$(cf)))),, \
-        $(error $(p): malformed COPY_FILE "$(cf)") \
-       ) \
-     ) \
-   ) \
-)
-endef
-
-
-#
-# Returns the product makefile path for the product with the provided name
-#
-# $(1): short product name like "generic"
-#
-define _resolve-short-product-name
-  $(eval pn := $(strip $(1)))
-  $(eval p := \
-      $(foreach p,$(PRODUCTS), \
-          $(if $(filter $(pn),$(PRODUCTS.$(p).PRODUCT_NAME)), \
-            $(p) \
-       )) \
-   )
-  $(eval p := $(sort $(p)))
-  $(if $(filter 1,$(words $(p))), \
-    $(p), \
-    $(if $(filter 0,$(words $(p))), \
-      $(error No matches for product "$(pn)"), \
-      $(error Product "$(pn)" ambiguous: matches $(p)) \
-    ) \
-  )
-endef
-define resolve-short-product-name
-$(strip $(call _resolve-short-product-name,$(1)))
+  $(if $(call is-c-identifier,$(PRODUCT_NAME)),, \
+    $(error $(INTERNAL_PRODUCT): PRODUCT_NAME must be a valid C identifier, not "$(pn)")) \
+  $(if $(PRODUCT_BRAND),, \
+    $(error $(INTERNAL_PRODUCT): PRODUCT_BRAND must be defined.)) \
+  $(foreach cf,$(strip $(PRODUCT_COPY_FILES)), \
+    $(if $(filter 2 3,$(words $(subst :,$(space),$(cf)))),, \
+      $(error $(p): malformed COPY_FILE "$(cf)"))))
 endef
 
 # BoardConfig variables that are also inherited in product mks. Should ideally
diff --git a/core/product_config.mk b/core/product_config.mk
index a9f3d34..37146d3 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -77,6 +77,45 @@
 $(sort $(shell find $(2) -name "$(1)" -type f | $(SED_EXTENDED) "s:($(2)/?(.*)):\\1\\:$(3)/\\2:" | sed "s://:/:g"))
 endef
 
+#
+# Convert file file to the PRODUCT_COPY_FILES/PRODUCT_SDK_ADDON_COPY_FILES
+# format: for each file F return $(F):$(PREFIX)/$(notdir $(F))
+# $(1): files list
+# $(2): prefix
+
+define copy-files
+$(foreach f,$(1),$(f):$(2)/$(notdir $(f)))
+endef
+
+#
+# Convert the list of file names to the list of PRODUCT_COPY_FILES items
+# $(1): from pattern
+# $(2): to pattern
+# $(3): file names
+# E.g., calling product-copy-files-by-pattern with
+#   (from/%, to/%, a b)
+# returns
+#   from/a:to/a from/b:to/b
+define product-copy-files-by-pattern
+$(join $(patsubst %,$(1),$(3)),$(patsubst %,:$(2),$(3)))
+endef
+
+# Return empty unless the board matches
+define is-board-platform2
+$(filter $(1), $(TARGET_BOARD_PLATFORM))
+endef
+
+# Return empty unless the board is in the list
+define is-board-platform-in-list2
+$(filter $(1),$(TARGET_BOARD_PLATFORM))
+endef
+
+# Return empty unless the board is QCOM
+define is-vendor-board-qcom
+$(if $(strip $(TARGET_BOARD_PLATFORM) $(QCOM_BOARD_PLATFORMS)),$(filter $(TARGET_BOARD_PLATFORM),$(QCOM_BOARD_PLATFORMS)),\
+  $(error both TARGET_BOARD_PLATFORM=$(TARGET_BOARD_PLATFORM) and QCOM_BOARD_PLATFORMS=$(QCOM_BOARD_PLATFORMS)))
+endef
+
 # ---------------------------------------------------------------
 # Check for obsolete PRODUCT- and APP- goals
 ifeq ($(CALLED_FROM_SETUP),true)
@@ -107,97 +146,132 @@
 include $(BUILD_SYSTEM)/product.mk
 include $(BUILD_SYSTEM)/device.mk
 
-# Read in all of the product definitions specified by the AndroidProducts.mk
-# files in the tree.
-all_product_configs := $(get-all-product-makefiles)
+# Read all product definitions.
+#
+# Products are defined in AndroidProducts.mk files:
+android_products_makefiles := $(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
+  $(SRC_TARGET_DIR)/product/AndroidProducts.mk
 
-all_named_products :=
+# An AndroidProduct.mk file sets the following variables:
+#   PRODUCT_MAKEFILES specifies product makefiles. Each item in this list
+#     is either a <product>:path/to/file.mk, or just path/to/<product.mk>
+#   COMMON_LUNCH_CHOICES specifies <product>-<variant> values to be shown
+#     in the `lunch` menu
+#   STARLARK_OPT_IN_PRODUCTS specifies products to use Starlark-based
+#     product configuration by default
 
-# Find the product config makefile for the current product.
-# all_product_configs consists items like:
-# <product_name>:<path_to_the_product_makefile>
-# or just <path_to_the_product_makefile> in case the product name is the
-# same as the base filename of the product config makefile.
-current_product_makefile :=
-all_product_makefiles :=
-$(foreach f, $(all_product_configs),\
-    $(eval _cpm_words := $(call _decode-product-name,$(f)))\
-    $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
-    $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
-    $(eval all_product_makefiles += $(_cpm_word2))\
-    $(eval all_named_products += $(_cpm_word1))\
-    $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
-        $(eval current_product_makefile += $(_cpm_word2)),))
-_cpm_words :=
-_cpm_word1 :=
-_cpm_word2 :=
-current_product_makefile := $(strip $(current_product_makefile))
-all_product_makefiles := $(strip $(all_product_makefiles))
+# Builds a list of first/second elements of each pair:
+#   $(call _first,a:A b:B,:) returns 'a b'
+#   $(call _second,a-A b-B,-) returns 'A B'
+_first=$(filter-out $(2)%,$(subst $(2),$(space)$(2),$(1)))
+_second=$(filter-out %$(2),$(subst $(2),$(2)$(space),$(1)))
 
-load_all_product_makefiles :=
-ifneq (,$(filter product-graph, $(MAKECMDGOALS)))
-ifeq ($(ANDROID_PRODUCT_GRAPH),--all)
-load_all_product_makefiles := true
-endif
-endif
-ifneq (,$(filter dump-products,$(MAKECMDGOALS)))
-ifeq ($(ANDROID_DUMP_PRODUCTS),all)
-load_all_product_makefiles := true
-endif
-endif
+# Returns <product>:<path> pair from a PRODUCT_MAKEFILE item.
+# If an item is <product>:path/to/file.mk, return it as is,
+# otherwise assume that an item is path/to/<product>.mk and
+# return <product>:path/to/<product>.mk
+_product-spec=$(strip $(if $(findstring :,$(1)),$(1),$(basename $(notdir $(1))):$(1)))
+
+# Reads given AndroidProduct.mk file and sets the following variables:
+#  ap_product_paths -- the list of <product>:<path> pairs
+#  ap_common_lunch_choices -- the list of <product>-<build variant> items
+#  ap_products_using_starlark_config -- the list of products using starlark config
+# In addition, validates COMMON_LUNCH_CHOICES and STARLARK_OPT_IN_PRODUCTS values
+define _read-ap-file
+  $(eval PRODUCT_MAKEFILES :=) \
+  $(eval COMMON_LUNCH_CHOICES :=) \
+  $(eval STARLARK_OPT_IN_PRODUCTS := ) \
+  $(eval ap_product_paths :=) \
+  $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
+  $(eval include $(f)) \
+  $(foreach p, $(PRODUCT_MAKEFILES),$(eval ap_product_paths += $(call _product-spec,$(p)))) \
+  $(eval ap_common_lunch_choices  := $(COMMON_LUNCH_CHOICES)) \
+  $(eval ap_products_using_starlark_config := $(STARLARK_OPT_IN_PRODUCTS)) \
+  $(eval _products := $(call _first,$(ap_product_paths),:)) \
+  $(eval _bad := $(filter-out $(_products),$(call _first,$(ap_common_lunch_choices),-))) \
+  $(if $(_bad),$(error COMMON_LUNCH_CHOICES contains products(s) not defined in this file: $(_bad))) \
+  $(eval _bad := $(filter-out %-eng %-userdebug %-user,$(ap_common_lunch_choices))) \
+  $(if $(_bad),$(error invalid variant in COMMON_LUNCH_CHOICES: $(_bad)))
+  $(eval _bad := $(filter-out $(_products),$(ap_products_using_starlark_config))) \
+  $(if $(_bad),$(error STARLARK_OPT_IN_PRODUCTS contains product(s) not defined in this file: $(_bad)))
+endef
+
+# Build cumulative lists of all product specs/lunch choices/Starlark-based products.
+product_paths :=
+common_lunch_choices :=
+products_using_starlark_config :=
+$(foreach f,$(android_products_makefiles), \
+    $(call _read-ap-file,$(f)) \
+    $(eval product_paths += $(ap_product_paths)) \
+    $(eval common_lunch_choices += $(ap_common_lunch_choices)) \
+    $(eval products_using_starlark_config += $(ap_products_using_starlark_config)) \
+)
+
+# Dedup, extract product names, etc.
+product_paths := $(sort $(product_paths))
+all_named_products := $(sort $(call _first,$(product_paths),:))
+all_product_makefiles := $(sort $(call _second,$(product_paths),:))
+current_product_makefile := $(call _second,$(filter $(TARGET_PRODUCT):%,$(product_paths)),:)
+COMMON_LUNCH_CHOICES := $(sort $(common_lunch_choices))
+
+# Check that there are no duplicate product names
+$(foreach p,$(all_named_products), \
+  $(if $(filter 1,$(words $(filter $(p):%,$(product_paths)))),, \
+    $(error Product name must be unique, "$(p)" used by $(call _second,$(filter $(p):%,$(product_paths)),:))))
 
 ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),)
 _product_config_saved_KATI_ALLOW_RULES := $(.KATI_ALLOW_RULES)
 .KATI_ALLOW_RULES := $(ALLOW_RULES_IN_PRODUCT_CONFIG)
 endif
 
-ifeq ($(load_all_product_makefiles),true)
-# Import all product makefiles.
-$(call import-products, $(all_product_makefiles))
-else
-# Import just the current product.
-ifndef current_product_makefile
-$(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
+ifeq (,$(current_product_makefile))
+  $(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
 endif
-ifneq (1,$(words $(current_product_makefile)))
-$(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))
+
+ifneq (,$(filter $(TARGET_PRODUCT),$(products_using_starlark_config)))
+  RBC_PRODUCT_CONFIG := true
+  RBC_BOARD_CONFIG := true
 endif
+
+ifndef RBC_PRODUCT_CONFIG
 $(call import-products, $(current_product_makefile))
-endif  # Import all or just the current product makefile
+else
+  $(shell mkdir -p $(OUT_DIR)/rbc)
+  $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_product_config.mk)
 
-# Quick check
-$(check-all-products)
+  $(shell build/soong/scripts/update_out \
+    $(OUT_DIR)/rbc/rbc_product_config_results.mk \
+    build/soong/scripts/rbc-run \
+    $(current_product_makefile) \
+    $(OUT_DIR)/rbc/make_vars_pre_product_config.mk)
+  ifneq ($(.SHELLSTATUS),0)
+    $(error product configuration converter failed: $(.SHELLSTATUS))
+  endif
+  include $(OUT_DIR)/rbc/rbc_product_config_results.mk
+endif
 
-ifeq ($(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),)
+# This step was already handled in the RBC product configuration.
+ifeq ($(RBC_PRODUCT_CONFIG)$(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),)
 # Import all the products that have made artifact path requirements, so that we can verify
-# the artifacts they produce.
-# These are imported after check-all-products because some of them might not be real products.
+# the artifacts they produce. They might be intermediate makefiles instead of real products.
 $(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\
   $(if $(filter-out $(makefile),$(PRODUCTS)),$(eval $(call import-products,$(makefile))))\
 )
 endif
 
+INTERNAL_PRODUCT := $(current_product_makefile)
+# Strip and assign the PRODUCT_ variables.
+$(call strip-product-vars)
+
+# Quick check
+$(check-current-product)
+
 ifneq ($(ALLOW_RULES_IN_PRODUCT_CONFIG),)
 .KATI_ALLOW_RULES := $(_saved_KATI_ALLOW_RULES)
 _product_config_saved_KATI_ALLOW_RULES :=
 endif
 
-ifneq ($(filter dump-products, $(MAKECMDGOALS)),)
-$(dump-products)
-endif
-
-# Convert a short name like "sooner" into the path to the product
-# file defining that product.
-#
-INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))
-ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT))
-$(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT))
-endif
-
-
 ############################################################################
-# Strip and assign the PRODUCT_ variables.
-$(call strip-product-vars)
 
 current_product_makefile :=
 all_product_makefiles :=
@@ -258,24 +332,21 @@
 # All APEX jars come after /system and /system_ext jars, so adding core-icu4j at the end of the list
 PRODUCT_BOOT_JARS += com.android.i18n:core-icu4j
 
-# Replaces references to overridden boot jar modules in a boot jars variable.
-# $(1): Name of a boot jars variable with <apex>:<jar> pairs.
-define replace-boot-jar-module-overrides
-  $(foreach pair,$(PRODUCT_BOOT_JAR_MODULE_OVERRIDES),\
-    $(eval _rbjmo_from := $(call word-colon,1,$(pair)))\
-    $(eval _rbjmo_to := $(call word-colon,2,$(pair)))\
-    $(eval $(1) := $(patsubst $(_rbjmo_from):%,$(_rbjmo_to):%,$($(1)))))
-endef
-
-$(call replace-boot-jar-module-overrides,PRODUCT_BOOT_JARS)
-$(call replace-boot-jar-module-overrides,PRODUCT_APEX_BOOT_JARS)
-$(call replace-boot-jar-module-overrides,ART_APEX_JARS)
-
 # The extra system server jars must be appended at the end after common system server jars.
 PRODUCT_SYSTEM_SERVER_JARS += $(PRODUCT_SYSTEM_SERVER_JARS_EXTRA)
 
 PRODUCT_SYSTEM_SERVER_JARS := $(call qualify-platform-jars,$(PRODUCT_SYSTEM_SERVER_JARS))
 
+# Sort APEX boot and system server jars. We use deterministic alphabetical order
+# when constructing BOOTCLASSPATH and SYSTEMSERVERCLASSPATH definition on device
+# after an update. Enforce it in the build system as well to avoid recompiling
+# everything after an update due a change in the order.
+PRODUCT_APEX_BOOT_JARS := $(sort $(PRODUCT_APEX_BOOT_JARS))
+PRODUCT_APEX_SYSTEM_SERVER_JARS := $(sort $(PRODUCT_APEX_SYSTEM_SERVER_JARS))
+
+PRODUCT_STANDALONE_SYSTEM_SERVER_JARS := \
+  $(call qualify-platform-jars,$(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS))
+
 ifndef PRODUCT_SYSTEM_NAME
   PRODUCT_SYSTEM_NAME := $(PRODUCT_NAME)
 endif
@@ -321,6 +392,7 @@
 ENFORCE_SYSTEM_CERTIFICATE_ALLOW_LIST := $(PRODUCT_ARTIFACT_SYSTEM_CERTIFICATE_REQUIREMENT_ALLOW_LIST)
 
 PRODUCT_OTA_PUBLIC_KEYS := $(sort $(PRODUCT_OTA_PUBLIC_KEYS))
+PRODUCT_EXTRA_OTA_KEYS := $(sort $(PRODUCT_EXTRA_OTA_KEYS))
 PRODUCT_EXTRA_RECOVERY_KEYS := $(sort $(PRODUCT_EXTRA_RECOVERY_KEYS))
 
 # Resolve and setup per-module dex-preopt configs.
@@ -352,13 +424,25 @@
 _psmc_modules :=
 
 # Reset ADB keys for non-debuggable builds
-ifeq (,$(filter eng userdebug,$(TARGET_BUILD_VARIANT)),)
+ifeq (,$(filter eng userdebug,$(TARGET_BUILD_VARIANT)))
   PRODUCT_ADB_KEYS :=
 endif
 ifneq ($(filter-out 0 1,$(words $(PRODUCT_ADB_KEYS))),)
   $(error Only one file may be in PRODUCT_ADB_KEYS: $(PRODUCT_ADB_KEYS))
 endif
 
+# Show a warning wall of text if non-compliance-GSI products set this option.
+ifdef PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT
+  ifeq (,$(filter gsi_arm gsi_arm64 gsi_x86 gsi_x86_64 gsi_car_arm64 gsi_car_x86_64 gsi_tv_arm gsi_tv_arm64,$(PRODUCT_NAME)))
+    $(warning PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT is set but \
+      PRODUCT_NAME ($(PRODUCT_NAME)) doesn't look like a GSI for compliance \
+      testing. This is a special configuration for compliance GSI, so do make \
+      sure you understand the security implications before setting this \
+      option. If you don't know what this option does, then you probably \
+      shouldn't set this.)
+  endif
+endif
+
 ifndef PRODUCT_USE_DYNAMIC_PARTITIONS
   PRODUCT_USE_DYNAMIC_PARTITIONS := $(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS)
 endif
@@ -484,6 +568,7 @@
 
 # Copy and check the value of each PRODUCT_BUILD_*_IMAGE variable
 $(foreach image, \
+    PVMFW \
     SYSTEM \
     SYSTEM_OTHER \
     VENDOR \
@@ -492,6 +577,7 @@
     ODM \
     VENDOR_DLKM \
     ODM_DLKM \
+    SYSTEM_DLKM \
     CACHE \
     RAMDISK \
     USERDATA \
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 111e759..0189323 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -12,24 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("//build/make/core:envsetup.rbc", _envsetup_init = "init")
-
 """Runtime functions."""
 
-def _global_init():
-    """Returns dict created from the runtime environment."""
-    globals = dict()
+_soong_config_namespaces_key = "$SOONG_CONFIG_NAMESPACES"
+_dist_for_goals_key = "$dist_for_goals"
+def _init_globals(input_variables_init):
+    """Initializes dictionaries of global variables.
 
-    # Environment variables
-    for k in dir(rblf_env):
-        globals[k] = getattr(rblf_env, k)
+    This function runs the given input_variables_init function,
+    passing it a globals dictionary and a handle as if it
+    were a regular product. It then returns 2 copies of
+    the globals dictionary, so that one can be kept around
+    to diff changes made to the other later.
+    """
+    globals_base = {"PRODUCT_SOONG_NAMESPACES": []}
+    input_variables_init(globals_base, __h_new())
 
-    # Variables set as var=value command line arguments
-    for k in dir(rblf_cli):
-        globals[k] = getattr(rblf_cli, k)
-
-    globals.setdefault("PRODUCT_SOONG_NAMESPACES", [])
-    _envsetup_init(globals)
+    # Rerun input_variables_init to produce a copy
+    # of globals_base, because starlark doesn't support
+    # deep copying objects.
+    globals = {"PRODUCT_SOONG_NAMESPACES": []}
+    input_variables_init(globals, __h_new())
 
     # Variables that should be defined.
     mandatory_vars = [
@@ -37,7 +40,6 @@
         "PLATFORM_VERSION",
         "PRODUCT_SOONG_NAMESPACES",
         # TODO(asmundak): do we need TARGET_ARCH? AOSP does not reference it
-        "TARGET_BUILD_TYPE",
         "TARGET_BUILD_VARIANT",
         "TARGET_PRODUCT",
     ]
@@ -45,12 +47,11 @@
         if not bv in globals:
             fail(bv, " is not defined")
 
-    return globals
-
-_globals_base = _global_init()
+    return (globals, globals_base)
 
 def __print_attr(attr, value):
-    if not value:
+    # Allow using empty strings to clear variables, but not None values
+    if value == None:
         return
     if type(value) == "list":
         if _options.rearrange:
@@ -62,26 +63,53 @@
     elif _options.format == "pretty":
         print(attr, "=", repr(value))
     elif _options.format == "make":
-        print(attr, ":=", value)
+        # Trim all spacing to a single space
+        print(attr, ":=", _mkstrip(value))
     else:
         fail("bad output format", _options.format)
 
-def _printvars(globals, cfg):
-    """Prints known configuration variables."""
-    for attr, val in sorted(cfg.items()):
-        __print_attr(attr, val)
-    if _options.print_globals:
-        print()
-        for attr, val in sorted(globals.items()):
-            if attr not in _globals_base:
-                __print_attr(attr, val)
+def _printvars(state):
+    """Prints configuration and global variables."""
+    (globals, globals_base) = state
+    for attr, val in sorted(globals.items()):
+        if attr == _soong_config_namespaces_key:
+            __print_attr("SOONG_CONFIG_NAMESPACES", val.keys())
+            for nsname, nsvars in sorted(val.items()):
+                # Define SOONG_CONFIG_<ns> for Make, othewise
+                # it cannot be added to .KATI_READONLY list
+                if _options.format == "make":
+                    print("SOONG_CONFIG_" + nsname, ":=", " ".join(nsvars.keys()))
+                for var, val in sorted(nsvars.items()):
+                    if val:
+                        __print_attr("SOONG_CONFIG_%s_%s" % (nsname, var), val)
+                    else:
+                        print("SOONG_CONFIG_%s_%s :=" % (nsname, var))
+        elif attr == _dist_for_goals_key:
+            goals = []
+            src_dst_list = []
+            goal_dst_list = []
+            for goal_name, goal_src_dst_list in sorted(val.items()):
+                goals.append(goal_name)
+                for sd in sorted(goal_src_dst_list):
+                    src_dst_list.append(":".join(sd))
+                    goal_dst_list.append(":".join((goal_name, sd[1])))
+            print("_all_dist_goal_output_pairs:=", " ".join(goal_dst_list))
+            print("_all_dist_goals:=", " ".join(goals))
+            print("_all_dist_src_dst_pairs:=", " ".join(src_dst_list))
+        elif attr not in globals_base or globals_base[attr] != val:
+            __print_attr(attr, val)
 
 def __printvars_rearrange_list(value_list):
     """Rearrange value list: return only distinct elements, maybe sorted."""
     seen = {item: 0 for item in value_list}
     return sorted(seen.keys()) if _options.rearrange == "sort" else seen.keys()
 
-def _product_configuration(top_pcm_name, top_pcm):
+def __sort_pcm_names(pcm_names):
+    # We have to add an extension back onto the pcm names when sorting,
+    # or else the sort order could be wrong when one is a prefix of another.
+    return [x[:-3] for x in sorted([y + ".mk" for y in pcm_names], reverse=True)]
+
+def _product_configuration(top_pcm_name, top_pcm, input_variables_init):
     """Creates configuration."""
 
     # Product configuration is created by traversing product's inheritance
@@ -95,27 +123,21 @@
     # PCM means "Product Configuration Module", i.e., a Starlark file
     # whose body consists of a single init function.
 
-    globals = dict(**_globals_base)
-
-    config_postfix = []  # Configs in postfix order
+    globals, globals_base = _init_globals(input_variables_init)
 
     # Each PCM is represented by a quadruple of function, config, children names
     # and readyness (that is, the configurations from inherited PCMs have been
     # substituted).
     configs = {top_pcm_name: (top_pcm, None, [], False)}  # All known PCMs
 
-    stash = []  # Configs to push once their descendants are done
-
-    # Stack containing PCMs to be processed. An item in the stack
-    # is a pair of PCMs name and its height in the product inheritance tree.
-    pcm_stack = [(top_pcm_name, 0)]
-    pcm_count = 0
+    # Stack containing PCMs to be processed
+    pcm_stack = [top_pcm_name]
 
     # Run it until pcm_stack is exhausted, but no more than N times
     for n in range(1000):
         if not pcm_stack:
             break
-        (name, height) = pcm_stack.pop()
+        name = pcm_stack.pop()
         pcm, cfg, c, _ = configs[name]
 
         # cfg is set only after PCM has been called, leverage this
@@ -123,50 +145,101 @@
         if cfg != None:
             continue
 
-        # Push ancestors until we reach this node's height
-        config_postfix.extend([stash.pop() for i in range(len(stash) - height)])
-
         # Run this one, obtaining its configuration and child PCMs.
         if _options.trace_modules:
-            print("%d:" % n)
+            print("#%d: %s" % (n, name))
 
         # Run PCM.
         handle = __h_new()
         pcm(globals, handle)
 
+        if handle.artifact_path_requirements:
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENTS"] = handle.artifact_path_requirements
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_ALLOWED_LIST"] = handle.artifact_path_allowed_list
+            globals["PRODUCTS."+name+".mk.ARTIFACT_PATH_REQUIREMENT_IS_RELAXED"] = "true" if handle.artifact_path_requirement_is_relaxed[0] else ""
+            globals.setdefault("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", [])
+            globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] = sorted(globals["ARTIFACT_PATH_REQUIREMENT_PRODUCTS"] + [name+".mk"])
+
+        if handle.product_enforce_packages_exist[0]:
+            globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST"] = "true"
+            globals["PRODUCTS."+name+".mk.PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST"] = handle.product_enforce_packages_exist_allow_list
+
         # Now we know everything about this PCM, record it in 'configs'.
-        children = __h_inherited_modules(handle)
+        children = handle.inherited_modules
         if _options.trace_modules:
-            print("   ", "    ".join(children.keys()))
-        configs[name] = (pcm, __h_cfg(handle), children.keys(), False)
-        pcm_count = pcm_count + 1
+            print("#   ", "    ".join(children.keys()))
+        # Starlark dictionaries are guaranteed to iterate through in insertion order,
+        # so children.keys() will be ordered by the inherit() calls
+        configs[name] = (pcm, handle.cfg, children.keys(), False)
 
-        if len(children) == 0:
-            # Leaf PCM goes straight to the config_postfix
-            config_postfix.append(name)
-            continue
-
-        # Stash this PCM, process children in the sorted order
-        stash.append(name)
-        for child_name in sorted(children, reverse = True):
+        for child_name in __sort_pcm_names(children.keys()):
             if child_name not in configs:
                 configs[child_name] = (children[child_name], None, [], False)
-            pcm_stack.append((child_name, len(stash)))
+            pcm_stack.append(child_name)
     if pcm_stack:
         fail("Inheritance processing took too many iterations")
 
-    # Flush the stash
-    config_postfix.extend([stash.pop() for i in range(len(stash))])
-    if len(config_postfix) != pcm_count:
-        fail("Ran %d modules but postfix tree has only %d entries" % (pcm_count, len(config_postfix)))
+    for pcm_name in globals.get("ARTIFACT_PATH_REQUIREMENT_PRODUCTS", []):
+        for var, val in evaluate_finalized_product_variables(configs, pcm_name[:-3]).items():
+            globals["PRODUCTS."+pcm_name+"."+var] = val
 
-    if _options.trace_modules:
-        print("\n---Postfix---")
-        for x in config_postfix:
-            print("   ", x)
+    # Copy product config variables from the cfg dictionary to the
+    # PRODUCTS.<top_level_makefile_name>.<var_name> global variables.
+    for var, val in evaluate_finalized_product_variables(configs, top_pcm_name, _options.trace_modules).items():
+        globals["PRODUCTS."+top_pcm_name+".mk."+var] = val
+
+    # Record inheritance hierarchy in PRODUCTS.<file>.INHERITS_FROM variables.
+    # This is required for m product-graph.
+    for config in configs:
+        if len(configs[config][2]) > 0:
+            globals["PRODUCTS."+config+".mk.INHERITS_FROM"] = sorted([x + ".mk" for x in configs[config][2]])
+    globals["PRODUCTS"] = __words(globals.get("PRODUCTS", [])) + [top_pcm_name + ".mk"]
+
+    return (globals, globals_base)
+
+def evaluate_finalized_product_variables(configs, top_level_pcm_name, trace=False):
+    configs_postfix = []
+    pcm_stack = [(top_level_pcm_name, True)]
+    for i in range(1000):
+        if not pcm_stack:
+            break
+
+        pcm_name, before = pcm_stack.pop()
+        if before:
+            pcm_stack.append((pcm_name, False))
+            for child in __sort_pcm_names(configs[pcm_name][2]):
+                pcm_stack.append((child, True))
+        else:
+            configs_postfix.append(pcm_name)
+    if pcm_stack:
+        fail("Inheritance processing took too many iterations")
+
+    # clone the configs, because in the process of evaluating the
+    # final cfg dictionary we will remove values from the intermediate
+    # cfg dictionaries. We need to be able to call evaluate_finalized_product_variables()
+    # multiple times, so we can't change the origional configs object.
+    cloned_configs = {}
+    for pcm_name in configs:
+        # skip unneeded pcms
+        if pcm_name not in configs_postfix:
+            continue
+        pcm, cfg, children_names, ready = configs[pcm_name]
+        cloned_cfg = {}
+        for var, val in cfg.items():
+            if type(val) == 'list':
+                cloned_cfg[var] = list(val)
+            else:
+                cloned_cfg[var] = val
+        cloned_configs[pcm_name] = (pcm, cloned_cfg, children_names, ready)
+    configs = cloned_configs
+
+    if trace:
+        print("\n#---Postfix---")
+        for x in configs_postfix:
+            print("#   ", x)
 
     # Traverse the tree from the bottom, evaluating inherited values
-    for pcm_name in config_postfix:
+    for pcm_name in configs_postfix:
         pcm, cfg, children_names, ready = configs[pcm_name]
 
         # Should run
@@ -185,8 +258,33 @@
         _substitute_inherited(configs, pcm_name, cfg)
         _percolate_inherited(configs, pcm_name, cfg, children_names)
         configs[pcm_name] = pcm, cfg, children_names, True
+    return configs[top_level_pcm_name][1]
 
-    return globals, configs[top_pcm_name][1]
+def _dictionary_difference(a, b):
+    result = {}
+    for attr, val in a.items():
+        if attr not in b or b[attr] != val:
+            result[attr] = val
+    return result
+
+def _board_configuration(board_config_init, input_variables_init):
+    globals_base = {}
+    h_base = __h_new()
+    globals = {}
+    h = __h_new()
+
+    input_variables_init(globals_base, h_base)
+    input_variables_init(globals, h)
+    board_config_init(globals, h)
+
+    # Board configuration files aren't really supposed to change
+    # product configuration variables, but some do. You lose the
+    # inheritance features of the product config variables if you do.
+    for var, value in _dictionary_difference(h.cfg, h_base.cfg).items():
+        globals[var] = value
+
+    return (globals, globals_base)
+
 
 def _substitute_inherited(configs, pcm_name, cfg):
     """Substitutes inherited values in all the attributes.
@@ -241,9 +339,6 @@
         child_cfg = configs[child_name][1]
         for attr, value in child_cfg.items():
             if type(value) != "list":
-                if attr in percolated_attrs or not attr in cfg:
-                    cfg[attr] = value
-                    percolated_attrs[attr] = True
                 continue
             if attr in percolated_attrs:
                 # We already are percolating this one, just add this list
@@ -253,6 +348,19 @@
                 cfg[attr] = []
                 __move_items(cfg[attr], child_cfg, attr)
 
+    # single value variables need to be inherited in alphabetical order,
+    # not in the order of inherit() calls.
+    for child_name in sorted(children_names):
+        child_cfg = configs[child_name][1]
+        for attr, value in child_cfg.items():
+            if type(value) != "list":
+                # Single value variables take the first value available from the leftmost
+                # branch of the tree. If we also had "or attr in percolated_attrs" in this
+                # if statement, it would take the value from the rightmost branch.
+                if cfg.get(attr, "") == "":
+                    cfg[attr] = value
+                    percolated_attrs[attr] = True
+
     for attr in _options.trace_variables:
         if attr in percolated_attrs:
             print("%s: %s^=%s" % (cfg_name, attr, cfg[attr]))
@@ -267,6 +375,58 @@
     """Returns configuration item for the inherited module."""
     return (pcm_name,)
 
+def _soong_config_namespace(g, nsname):
+    """Adds given namespace if it does not exist."""
+
+    old = g.get(_soong_config_namespaces_key, {})
+    if old.get(nsname):
+        return
+
+    # A value cannot be updated, so we need to create a new dictionary
+    g[_soong_config_namespaces_key] = dict([(k,v) for k,v in old.items()] + [(nsname, {})])
+
+def _soong_config_set(g, nsname, var, value):
+    """Assigns the value to the variable in the namespace."""
+    _soong_config_namespace(g, nsname)
+    g[_soong_config_namespaces_key][nsname][var]=value
+
+def _soong_config_append(g, nsname, var, value):
+    """Appends to the value of the variable in the namespace."""
+    _soong_config_namespace(g, nsname)
+    ns = g[_soong_config_namespaces_key][nsname]
+    oldv = ns.get(var)
+    if oldv == None:
+        ns[var] = value
+    else:
+        ns[var] += " " + value
+
+
+def _soong_config_get(g, nsname, var):
+    """Gets to the value of the variable in the namespace."""
+    return g.get(_soong_config_namespaces_key, {}).get(nsname, {}).get(var, None)
+
+def _abspath(paths):
+    """Provided for compatibility, to be removed later."""
+    cwd = rblf_shell('pwd')
+    results = []
+    for path in __words(paths):
+        if path[0] != "/":
+            path = cwd + "/" + path
+
+        resultparts = []
+        for part in path.split('/'):
+            if part == "." or part == "":
+                continue
+            elif part == "..":
+                if resultparts:
+                    resultparts.pop()
+            else:
+                resultparts.append(part)
+        results.append("/" + "/".join(resultparts))
+
+    return " ".join(results)
+
+
 def _addprefix(prefix, string_or_list):
     """Adds prefix and returns a list.
 
@@ -296,8 +456,8 @@
 
 def __words(string_or_list):
     if type(string_or_list) == "list":
-        return string_or_list
-    return string_or_list.split()
+        string_or_list = " ".join(string_or_list)
+    return _mkstrip(string_or_list).split()
 
 # Handle manipulation functions.
 # A handle passed to a PCM consists of:
@@ -306,11 +466,16 @@
 #   default value list (initially empty, modified by inheriting)
 def __h_new():
     """Constructs a handle which is passed to PCM."""
-    return (dict(), dict(), list())
-
-def __h_inherited_modules(handle):
-    """Returns PCM's inherited modules dict."""
-    return handle[1]
+    return struct(
+        cfg = dict(),
+        inherited_modules = dict(),
+        default_list_value = list(),
+        artifact_path_requirements = list(),
+        artifact_path_allowed_list = list(),
+        artifact_path_requirement_is_relaxed = [False], # as a list so that we can reassign it
+        product_enforce_packages_exist = [False],
+        product_enforce_packages_exist_allow_list = [],
+    )
 
 def __h_cfg(handle):
     """Returns PCM's product configuration attributes dict.
@@ -318,7 +483,7 @@
     This function is also exported as rblf.cfg, and every PCM
     calls it at the beginning.
     """
-    return handle[0]
+    return handle.cfg
 
 def _setdefault(handle, attr):
     """If attribute has not been set, assigns default value to it.
@@ -327,9 +492,9 @@
     Only list attributes are initialized this way. The default
     value is kept in the PCM's handle. Calling inherit() updates it.
     """
-    cfg = handle[0]
+    cfg = handle.cfg
     if cfg.get(attr) == None:
-        cfg[attr] = list(handle[2])
+        cfg[attr] = list(handle.default_list_value)
     return cfg[attr]
 
 def _inherit(handle, pcm_name, pcm):
@@ -338,15 +503,35 @@
     This function is exported as rblf.inherit, PCM calls it when
     a module is inherited.
     """
-    cfg, inherited, default_lv = handle
-    inherited[pcm_name] = pcm
-    default_lv.append(_indirect(pcm_name))
+    handle.inherited_modules[pcm_name] = pcm
+    handle.default_list_value.append(_indirect(pcm_name))
 
     # Add inherited module reference to all configuration values
-    for attr, val in cfg.items():
+    for attr, val in handle.cfg.items():
         if type(val) == "list":
             val.append(_indirect(pcm_name))
 
+def __base(path):
+    """Returns basename."""
+    return path.rsplit("/",1)[-1]
+
+def _board_platform_in(g, string_or_list):
+    """Returns true if board is in the list."""
+    board = g.get("TARGET_BOARD_PLATFORM","")
+    if not board:
+        return False
+    return board in __words(string_or_list)
+
+
+def _board_platform_is(g, s):
+    """True if board is the same as argument."""
+    return g.get("TARGET_BOARD_PLATFORM","") == s
+
+
+def _copy_files(l, outdir):
+    """Generate <item>:<outdir>/item for each item."""
+    return ["%s:%s/%s" % (path, outdir, __base(path)) for path in __words(l)]
+
 def _copy_if_exists(path_pair):
     """If from file exists, returns [from:to] pair."""
     value = path_pair.split(":", 2)
@@ -354,11 +539,18 @@
     # Check that l[0] exists
     return [":".join(value)] if rblf_file_exists(value[0]) else []
 
-def _enforce_product_packages_exist(pkg_string_or_list):
+def _enforce_product_packages_exist(handle, pkg_string_or_list=[]):
     """Makes including non-existent modules in PRODUCT_PACKAGES an error."""
+    handle.product_enforce_packages_exist[0] = True
+    handle.product_enforce_packages_exist_allow_list.clear()
+    handle.product_enforce_packages_exist_allow_list.extend(__words(pkg_string_or_list))
 
-    #TODO(asmundak)
-    pass
+def _add_product_dex_preopt_module_config(handle, modules, config):
+    """Equivalent to add-product-dex-preopt-module-config from build/make/core/product.mk."""
+    modules = __words(modules)
+    config = _mkstrip(config).replace(" ", "|@SP@|")
+    _setdefault(handle, "PRODUCT_DEX_PREOPT_MODULE_CONFIGS")
+    handle.cfg["PRODUCT_DEX_PREOPT_MODULE_CONFIGS"] += [m + "=" + config for m in modules]
 
 def _file_wildcard_exists(file_pattern):
     """Return True if there are files matching given bash pattern."""
@@ -366,7 +558,14 @@
 
 def _find_and_copy(pattern, from_dir, to_dir):
     """Return a copy list for the files matching the pattern."""
-    return ["%s/%s:%s/%s" % (from_dir, f, to_dir, f) for f in rblf_wildcard(pattern, from_dir)]
+    return sorted([("%s/%s:%s/%s" % (from_dir, f, to_dir, f))
+        .replace("//", "/") for f in rblf_find_files(from_dir, pattern, only_files=1)])
+
+def _findstring(needle, haystack):
+    """Equivalent to GNU make's $(findstring)."""
+    if haystack.find(needle) < 0:
+        return ""
+    return needle
 
 def _filter_out(pattern, text):
     """Return all the words from `text' that do not match any word in `pattern'.
@@ -377,10 +576,15 @@
     Return:
         list of words
     """
-    rex = __mk2regex(__words(pattern))
+    patterns = [__mkparse_pattern(x) for x in __words(pattern)]
     res = []
     for w in __words(text):
-        if not _regex_match(rex, w):
+        match = False
+        for p in patterns:
+            if __mkpattern_matches(p, w):
+                match = True
+                break
+        if not match:
             res.append(w)
     return res
 
@@ -392,33 +596,62 @@
          which stands for any sequence of characters.
         text: string or list of words.
     """
-    rex = __mk2regex(__words(pattern))
+    patterns = [__mkparse_pattern(x) for x in __words(pattern)]
     res = []
     for w in __words(text):
-        if _regex_match(rex, w):
-            res.append(w)
+        for p in patterns:
+            if __mkpattern_matches(p, w):
+                res.append(w)
+                break
     return res
 
-def __mk2regex(words):
-    """Returns regular expression equivalent to Make pattern."""
+def _dir(paths):
+    """Equivalent to the GNU make function $(dir).
 
-    # TODO(asmundak): this will mishandle '\%'
-    return "^(" + "|".join([w.replace("%", ".*", 1) for w in words]) + ")"
+    Returns the folder of the file for each path in paths.
+    """
+    return " ".join([w.rsplit("/",1)[0] for w in __words(paths)])
 
-def _regex_match(regex, w):
-    return rblf_regex(regex, w)
+def _notdir(paths):
+    """Equivalent to the GNU make function $(notdir).
 
-def _require_artifacts_in_path(paths, allowed_paths):
-    """TODO."""
-    pass
+    Returns the name of the file at the end of each path in paths.
+    """
+    return " ".join([__base(w) for w in __words(paths)])
 
-def _require_artifacts_in_path_relaxed(paths, allowed_paths):
-    """TODO."""
-    pass
+def _require_artifacts_in_path(handle, paths, allowed_paths):
+    """Equivalent to require-artifacts-in-path in Make."""
+    handle.artifact_path_requirements.clear()
+    handle.artifact_path_requirements.extend(__words(paths))
+    handle.artifact_path_allowed_list.clear()
+    handle.artifact_path_allowed_list.extend(__words(allowed_paths))
+
+def _require_artifacts_in_path_relaxed(handle, paths, allowed_paths):
+    """Equivalent to require-artifacts-in-path-relaxed in Make."""
+    _require_artifacts_in_path(handle, paths, allowed_paths)
+    handle.artifact_path_requirement_is_relaxed[0] = True
 
 def _expand_wildcard(pattern):
     """Expands shell wildcard pattern."""
-    return rblf_wildcard(pattern)
+    result = []
+    for word in __words(pattern):
+        result.extend(rblf_wildcard(word))
+    return result
+
+def _mkdist_for_goals(g, goal, src_dst_list):
+    """Implements dist-for-goals macro."""
+    goals_map = g.get(_dist_for_goals_key, {})
+    pairs = goals_map.get(goal)
+    if pairs == None:
+        pairs = []
+        g[_dist_for_goals_key] = dict([(k,v) for k,v in goals_map.items()] + [(goal, pairs)])
+    for src_dst in __words(src_dst_list):
+        pair=src_dst.split(":")
+        if len(pair) > 2:
+            fail(src_dst + " should be a :-separated pair")
+        pairs.append((pair[0],pair[1] if len(pair) == 2 and pair[1] else __base(pair[0])))
+    g[_dist_for_goals_key][goal] = pairs
+
 
 def _mkerror(file, message = ""):
     """Prints error and stops."""
@@ -426,20 +659,162 @@
 
 def _mkwarning(file, message = ""):
     """Prints warning."""
-    print("%s: warning: %s" % (file, message))
+    rblf_log(file, "warning", message, sep = ':')
+
+def _mk2rbc_error(loc, message):
+    """Prints a message about conversion error and stops.
+
+    If RBC_MK2RBC_CONTINUE environment variable is set,
+    the execution will continue after the message is printed.
+    """
+    if _options.mk2rbc_continue:
+        rblf_log(loc, message, sep = ':')
+    else:
+        _mkerror(loc, message)
+
 
 def _mkinfo(file, message = ""):
     """Prints info."""
-    print(message)
+    rblf_log(message)
+
+
+def __mkparse_pattern(pattern):
+    """Parses Make's patsubst pattern.
+
+    This is equivalent to pattern.split('%', 1), except it
+    also takes into account escaping the % symbols.
+    """
+    in_escape = False
+    res = []
+    acc = ""
+    for c in pattern.elems():
+        if in_escape:
+            in_escape = False
+            acc += c
+        elif c == '\\':
+            in_escape = True
+        elif c == '%' and not res:
+            res.append(acc)
+            acc = ''
+        else:
+            acc += c
+    if in_escape:
+        acc += '\\'
+    res.append(acc)
+    return res
+
+def __mkpattern_matches(pattern, word):
+    """Returns if a pattern matches a given word.
+
+    The pattern must be a list of strings of length at most 2.
+    This checks if word is either equal to the pattern or
+    starts/ends with the two parts of the pattern.
+    """
+    if len(pattern) > 2:
+        fail("Pattern can have at most 2 components")
+    elif len(pattern) == 1:
+        return pattern[0]==word
+    else:
+        return ((len(word) >= len(pattern[0])+len(pattern[1]))
+            and word.startswith(pattern[0])
+            and word.endswith(pattern[1]))
+
+def __mkpatsubst_word(parsed_pattern,parsed_subst, word):
+    (before, after) = parsed_pattern
+    if not word.startswith(before):
+        return word
+    if not word.endswith(after):
+        return word
+    if len(parsed_subst) < 2:
+        return parsed_subst[0]
+    return parsed_subst[0] + word[len(before):len(word) - len(after)] + parsed_subst[1]
+
+
+def _mkpatsubst(pattern, replacement, s):
+    """Emulates Make's patsubst.
+
+    Tokenizes `s` (unless it is already a list), and then performs a simple
+    wildcard substitution (in other words, `foo%bar` pattern is equivalent to
+    the regular expression `^foo(.*)bar$, and the first `%` in replacement is
+    $1 in regex terms).
+    """
+    parsed_pattern = __mkparse_pattern(pattern)
+    if len(parsed_pattern) == 1:
+        out_words = [ replacement if x == pattern else x for x in __words(s)]
+    else:
+        parsed_replacement = __mkparse_pattern(replacement)
+        out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in __words(s)]
+    return out_words if type(s) == "list" else " ".join(out_words)
+
+
+def _mksort(input):
+    """Emulate Make's sort.
+
+    This is unique from a regular sort in that it also strips
+    the input, and removes duplicate words from the input.
+    """
+    input = sorted(__words(input))
+    result = []
+    for w in input:
+        if len(result) == 0 or result[-1] != w:
+            result.append(w)
+    return result
+
+
+def _mkstrip(s):
+    """Emulates Make's strip.
+
+    That is, removes string's leading and trailing whitespace characters and
+    replaces any sequence of whitespace characters with with a single space.
+    """
+    if type(s) != "string":
+        return s
+    result = ""
+    was_space = False
+    for ch in s.strip().elems():
+        is_space = ch.isspace()
+        if not is_space:
+            if was_space:
+                result += " "
+            result += ch
+        was_space = is_space
+    return result
+
+def _mksubst(old, new, s):
+    """Emulates Make's subst.
+
+    Replaces each occurence of 'old' with 'new'.
+    If 's' is a list, applies substitution to each item.
+    """
+    if type(s) == "list":
+        return [e.replace(old, new) for e in s]
+    return s.replace(old, new)
+
+
+def _product_copy_files_by_pattern(src, dest, s):
+    """Creates a copy list.
+
+    For each item in a given list, create <from>:<to> pair, where <from> and
+    <to> are the results of applying Make-style patsubst of <src> and <dest>
+    respectively. E.g. the result of calling this function with
+    ("foo/%", "bar/%", ["a", "b"])  will be
+    ["foo/a:bar/a", "foo/b:bar/b"].
+    """
+    parsed_src = __mkparse_pattern(src)
+    parsed_dest = __mkparse_pattern(dest)
+    parsed_percent = ["", ""]
+    words = s if type(s) == "list" else _mkstrip(s).split(" ")
+    return [ __mkpatsubst_word(parsed_percent, parsed_src, x) + ":" + __mkpatsubst_word(parsed_percent, parsed_dest, x) for x in words]
+
 
 def __get_options():
     """Returns struct containing runtime global settings."""
     settings = dict(
         format = "pretty",
-        print_globals = False,
         rearrange = "",
         trace_modules = False,
         trace_variables = [],
+        mk2rbc_continue = False,
     )
     for x in getattr(rblf_cli, "RBC_OUT", "").split(","):
         if x == "sort" or x == "unique":
@@ -449,7 +824,8 @@
         elif x == "pretty" or x == "make":
             settings["format"] = x
         elif x == "global":
-            settings["print_globals"] = True
+            # TODO: Remove this, kept for backwards compatibility
+            pass
         elif x != "":
             fail("RBC_OUT: got %s, should be one of: [pretty|make] [sort|unique]" % x)
     for x in getattr(rblf_cli, "RBC_DEBUG", "").split(","):
@@ -457,15 +833,27 @@
             settings["trace_modules"] = True
         elif x != "":
             settings["trace_variables"].append(x)
+    if getattr(rblf_cli, "RBC_MK2RBC_CONTINUE", ""):
+        settings["mk2rbc_continue"] = True
     return struct(**settings)
 
 # Settings used during debugging.
 _options = __get_options()
 rblf = struct(
+    soong_config_namespace = _soong_config_namespace,
+    soong_config_append = _soong_config_append,
+    soong_config_set = _soong_config_set,
+    soong_config_get = _soong_config_get,
+    abspath = _abspath,
+    add_product_dex_preopt_module_config = _add_product_dex_preopt_module_config,
     addprefix = _addprefix,
     addsuffix = _addsuffix,
+    board_platform_in = _board_platform_in,
+    board_platform_is = _board_platform_is,
+    copy_files = _copy_files,
     copy_if_exists = _copy_if_exists,
     cfg = __h_cfg,
+    dir = _dir,
     enforce_product_packages_exist = _enforce_product_packages_exist,
     expand_wildcard = _expand_wildcard,
     file_exists = rblf_file_exists,
@@ -473,17 +861,27 @@
     filter = _filter,
     filter_out = _filter_out,
     find_and_copy = _find_and_copy,
-    global_init = _global_init,
+    findstring = _findstring,
     inherit = _inherit,
     indirect = _indirect,
+    mk2rbc_error = _mk2rbc_error,
+    mkdist_for_goals = _mkdist_for_goals,
     mkinfo = _mkinfo,
     mkerror = _mkerror,
+    mkpatsubst = _mkpatsubst,
     mkwarning = _mkwarning,
+    mksort = _mksort,
+    mkstrip = _mkstrip,
+    mksubst = _mksubst,
+    notdir = _notdir,
     printvars = _printvars,
     product_configuration = _product_configuration,
+    board_configuration = _board_configuration,
+    product_copy_files_by_pattern = _product_copy_files_by_pattern,
     require_artifacts_in_path = _require_artifacts_in_path,
     require_artifacts_in_path_relaxed = _require_artifacts_in_path_relaxed,
     setdefault = _setdefault,
     shell = rblf_shell,
     warning = _mkwarning,
+    words = __words,
 )
diff --git a/core/proguard.flags b/core/proguard.flags
index 50049cb..185275e 100644
--- a/core/proguard.flags
+++ b/core/proguard.flags
@@ -15,35 +15,24 @@
 @**.VisibleForTesting *;
 }
 
-# Understand the @Keep support annotation.
--keep class android.support.annotation.Keep
--keep class androidx.annotation.Keep
+# Understand the common @Keep annotation from various Android packages:
+#  * android.support.annotation
+#  * androidx.annotation
+#  * com.android.internal.annotations
+-keep class **android**.annotation*.Keep
 
--keep @android.support.annotation.Keep class * {*;}
--keep @androidx.annotation.Keep class * {*;}
+-keep @**android**.annotation*.Keep class * { *; }
 
 -keepclasseswithmembers class * {
-    @android.support.annotation.Keep <methods>;
+    @**android**.annotation*.Keep <methods>;
 }
 
 -keepclasseswithmembers class * {
-    @androidx.annotation.Keep <methods>;
+    @**android**.annotation*.Keep <fields>;
 }
 
 -keepclasseswithmembers class * {
-    @android.support.annotation.Keep <fields>;
-}
-
--keepclasseswithmembers class * {
-    @androidx.annotation.Keep <fields>;
-}
-
--keepclasseswithmembers class * {
-    @android.support.annotation.Keep <init>(...);
-}
-
--keepclasseswithmembers class * {
-    @androidx.annotation.Keep <init>(...);
+    @**android**.annotation*.Keep <init>(...);
 }
 
 -include proguard_basic_keeps.flags
diff --git a/core/proguard_basic_keeps.flags b/core/proguard_basic_keeps.flags
index 28ec2d0..30c2341 100644
--- a/core/proguard_basic_keeps.flags
+++ b/core/proguard_basic_keeps.flags
@@ -9,7 +9,7 @@
 }
 
 # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
--keepclasseswithmembernames class * {
+-keepclasseswithmembernames,includedescriptorclasses class * {
     native <methods>;
 }
 
diff --git a/core/rbe.mk b/core/rbe.mk
index 19c0e42..fd3427a 100644
--- a/core/rbe.mk
+++ b/core/rbe.mk
@@ -22,6 +22,18 @@
     rbe_dir := prebuilts/remoteexecution-client/live/
   endif
 
+  ifdef RBE_CXX_POOL
+    cxx_pool := $(RBE_CXX_POOL)
+  else
+    cxx_pool := default
+  endif
+
+  ifdef RBE_JAVA_POOL
+    java_pool := $(RBE_JAVA_POOL)
+  else
+    java_pool := java16
+  endif
+
   ifdef RBE_CXX_EXEC_STRATEGY
     cxx_rbe_exec_strategy := $(RBE_CXX_EXEC_STRATEGY)
   else
@@ -34,12 +46,6 @@
     cxx_compare := false
   endif
 
-  ifdef RBE_CXX_COMPARE
-    cxx_compare := $(RBE_CXX_COMPARE)
-  else
-    cxx_compare := "false"
-  endif
-
   ifdef RBE_JAVAC_EXEC_STRATEGY
     javac_exec_strategy := $(RBE_JAVAC_EXEC_STRATEGY)
   else
@@ -59,8 +65,8 @@
   endif
 
   platform := container-image=docker://gcr.io/androidbuild-re-dockerimage/android-build-remoteexec-image@sha256:582efb38f0c229ea39952fff9e132ccbe183e14869b39888010dacf56b360d62
-  cxx_platform := $(platform),Pool=default
-  java_r8_d8_platform := $(platform),Pool=java16
+  cxx_platform := $(platform),Pool=$(cxx_pool)
+  java_r8_d8_platform := $(platform),Pool=$(java_pool)
 
   RBE_WRAPPER := $(rbe_dir)/rewrapper
   RBE_CXX := --labels=type=compile,lang=cpp,compiler=clang --env_var_allowlist=PWD --exec_strategy=$(cxx_rbe_exec_strategy) --platform=$(cxx_platform) --compare=$(cxx_compare)
diff --git a/core/robolectric_test_config_template.xml b/core/robolectric_test_config_template.xml
index e62175f..483b957 100644
--- a/core/robolectric_test_config_template.xml
+++ b/core/robolectric_test_config_template.xml
@@ -18,7 +18,7 @@
     <option name="test-suite-tag" value="robolectric" />
     <option name="test-suite-tag" value="robolectric-tests" />
 
-    <option name="java-folder" value="prebuilts/jdk/jdk9/linux-x86/" />
+    <option name="java-folder" value="prebuilts/jdk/jdk11/linux-x86/" />
     <option name="exclude-paths" value="java" />
     <option name="use-robolectric-resources" value="true" />
 
diff --git a/core/rust_device_test_config_template.xml b/core/rust_device_test_config_template.xml
index 9429d38..bfd2f47 100644
--- a/core/rust_device_test_config_template.xml
+++ b/core/rust_device_test_config_template.xml
@@ -15,6 +15,9 @@
 -->
 <!-- This test config file is auto-generated. -->
 <configuration description="Config to run {MODULE} device tests.">
+
+    {EXTRA_CONFIGS}
+
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
diff --git a/core/sdk_font.mk b/core/sdk_font.mk
deleted file mode 100644
index 1742925..0000000
--- a/core/sdk_font.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-###############################################################################
-# Fonts shipped with the SDK need to be renamed for Java to handle them
-# properly. Hence, a special script is used to rename the fonts. We bundle all
-# the fonts that are shipped on a newer non-space-constrained device. However,
-# OpenType fonts used on these devices are not supported by Java. Their
-# replacements are added separately.
-###############################################################################
-
-
-# The script that renames the font.
-sdk_font_rename_script := frameworks/layoutlib/rename_font/build_font_single.py
-
-# Location of the fonttools library that the above script depends on.
-fonttools_lib := external/fonttools/Lib
-
-# A temporary location to store the renamed fonts. atree picks all files in
-# this directory and bundles it with the SDK.
-SDK_FONT_TEMP := $(call intermediates-dir-for,PACKAGING,sdk-fonts,HOST,COMMON)
-
-# The font configuration files - system_fonts.xml, fallback_fonts.xml etc.
-sdk_font_config := $(sort $(wildcard frameworks/base/data/fonts/*.xml))
-sdk_font_config :=  $(addprefix $(SDK_FONT_TEMP)/standard/, $(notdir $(sdk_font_config)))
-
-$(sdk_font_config): $(SDK_FONT_TEMP)/standard/%.xml: \
-			frameworks/base/data/fonts/%.xml
-	$(hide) mkdir -p $(dir $@)
-	$(hide) cp -vf $< $@
-
-# List of fonts on the device that we want to ship. This is all .ttf fonts.
-sdk_fonts_device := $(filter $(TARGET_OUT)/fonts/%.ttf, $(INTERNAL_SYSTEMIMAGE_FILES))
-sdk_fonts_device := $(addprefix $(SDK_FONT_TEMP)/, $(notdir $(sdk_fonts_device)))
-
-# Macro to rename the font.
-sdk_rename_font = PYTHONPATH=$$PYTHONPATH:$(fonttools_lib) $(sdk_font_rename_script) \
-	    $1 $2
-
-# TODO: If the font file is a symlink, reuse the font renamed from the symlink
-# target.
-$(sdk_fonts_device): $(SDK_FONT_TEMP)/%.ttf: $(TARGET_OUT)/fonts/%.ttf \
-			$(sdk_font_rename_script)
-	$(hide) mkdir -p $(dir $@)
-	$(hide) $(call sdk_rename_font,$<,$@)
-
-# List of all dependencies - all fonts and configuration files.
-SDK_FONT_DEPS := $(sdk_fonts_device) $(sdk_font_config)
-
-# Define a macro to create rule for addititional fonts that we want to include
-# in the SDK.
-# $1 Output font name
-# $2 Source font path
-define sdk-extra-font-rule
-fontfullname := $$(SDK_FONT_TEMP)/$1
-ifeq ($$(filter $$(fontfullname),$$(sdk_fonts_device)),)
-SDK_FONT_DEPS += $$(fontfullname)
-$$(fontfullname): $2 $$(sdk_font_rename_script)
-	$$(hide) mkdir -p $$(dir $$@)
-	$$(hide) $$(call sdk_rename_font,$$<,$$@)
-endif
-fontfullname :=
-endef
-
-# These extra fonts are used as a replacement for OpenType fonts.
-$(eval $(call sdk-extra-font-rule,NanumGothic.ttf,external/naver-fonts/NanumGothic.ttf))
-$(eval $(call sdk-extra-font-rule,DroidSansFallback.ttf,frameworks/base/data/fonts/DroidSansFallbackFull.ttf))
-
-sdk-extra-font-rule :=
diff --git a/core/soong_android_app_set.mk b/core/soong_android_app_set.mk
index ef9eace..ec3d8c8 100644
--- a/core/soong_android_app_set.mk
+++ b/core/soong_android_app_set.mk
@@ -6,29 +6,19 @@
   $(call pretty-error,soong_apk_set.mk may only be used from Soong)
 endif
 
-LOCAL_BUILT_MODULE_STEM := $(LOCAL_APK_SET_INSTALL_FILE)
-LOCAL_INSTALLED_MODULE_STEM := $(LOCAL_APK_SET_INSTALL_FILE)
+LOCAL_BUILT_MODULE_STEM := package.apk
+LOCAL_INSTALLED_MODULE_STEM := $(notdir $(LOCAL_PREBUILT_MODULE_FILE))
+
+# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
+# to avoid checkbuilds making an extra copy of every module.
+LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
 
 #######################################
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-## Extract master APK from APK set into given directory
-# $(1) APK set
-# $(2) APK entry to install (e.g., splits/base.apk
+$(eval $(call copy-one-file,$(LOCAL_PREBUILT_MODULE_FILE),$(LOCAL_BUILT_MODULE)))
 
-define extract-install-file-from-apk-set
-$(LOCAL_BUILT_MODULE): $(1)
-	@echo "Extracting $$@"
-	unzip -pq $$< $(2) >$$@
-endef
-
-$(eval $(call extract-install-file-from-apk-set,$(LOCAL_PREBUILT_MODULE_FILE),$(LOCAL_APK_SET_INSTALL_FILE)))
-# unzip returns 11 it there was nothing to extract, which is expected,
-# $(LOCAL_APK_SET_INSTALL_FILE) has is already there.
-LOCAL_POST_INSTALL_CMD := unzip -qoDD -j -d $(dir $(LOCAL_INSTALLED_MODULE)) \
-	$(LOCAL_PREBUILT_MODULE_FILE) -x $(LOCAL_APK_SET_INSTALL_FILE) || [[ $$? -eq 11 ]]
-$(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
 PACKAGES.$(LOCAL_MODULE).OVERRIDES := $(strip $(LOCAL_OVERRIDES_PACKAGES))
 
 PACKAGES := $(PACKAGES) $(LOCAL_MODULE)
diff --git a/core/soong_app_prebuilt.mk b/core/soong_app_prebuilt.mk
index 82fb413..d771d22 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -28,6 +28,17 @@
 full_classes_pre_proguard_jar := $(intermediates.COMMON)/classes-pre-proguard.jar
 full_classes_header_jar := $(intermediates.COMMON)/classes-header.jar
 
+
+# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
+# to avoid checkbuilds making an extra copy of every module.
+LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_CLASSES_JAR)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_HEADER_JAR)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_FULL_MANIFEST_FILE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_DEXPREOPT_CONFIG)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_DEX_JAR)
+
 #######################################
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
@@ -89,18 +100,24 @@
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_DICT
+  my_proguard_dictionary_directory := $(local-proguard-dictionary-directory)
+  my_proguard_dictionary_mapping_directory := $(local-proguard-dictionary-mapping-directory)
   $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
     $(intermediates.COMMON)/proguard_dictionary))
-  $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary))
+  $(eval $(call copy-r8-dictionary-file-with-mapping,\
+    $(LOCAL_SOONG_PROGUARD_DICT),\
+    $(my_proguard_dictionary_directory)/proguard_dictionary,\
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto))
   $(eval $(call copy-one-file,$(LOCAL_SOONG_CLASSES_JAR),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar))
+    $(my_proguard_dictionary_directory)/classes.jar))
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
     $(intermediates.COMMON)/proguard_dictionary)
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary)
+    $(my_proguard_dictionary_directory)/proguard_dictionary)
   $(call add-dependency,$(LOCAL_BUILT_MODULE),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar)
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto)
+  $(call add-dependency,$(LOCAL_BUILT_MODULE),\
+    $(my_proguard_dictionary_directory)/classes.jar)
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_USAGE_ZIP
@@ -127,13 +144,6 @@
 java-dex: $(LOCAL_SOONG_DEX_JAR)
 
 
-my_built_installed := $(foreach f,$(LOCAL_SOONG_BUILT_INSTALLED),\
-  $(call word-colon,1,$(f)):$(PRODUCT_OUT)$(call word-colon,2,$(f)))
-my_installed := $(call copy-many-files, $(my_built_installed))
-ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-ALL_MODULES.$(my_register_name).BUILT_INSTALLED += $(my_built_installed)
-$(my_all_targets): $(my_installed)
-
 # Copy test suite files.
 ifdef LOCAL_COMPATIBILITY_SUITE
 my_apks_to_install := $(foreach f,$(filter %.apk %.idsig,$(LOCAL_SOONG_BUILT_INSTALLED)),$(call word-colon,1,$(f)))
diff --git a/core/soong_cc_prebuilt.mk b/core/soong_cc_rust_prebuilt.mk
similarity index 84%
rename from core/soong_cc_prebuilt.mk
rename to core/soong_cc_rust_prebuilt.mk
index 4d7b614..07e577a 100644
--- a/core/soong_cc_prebuilt.mk
+++ b/core/soong_cc_rust_prebuilt.mk
@@ -6,7 +6,7 @@
 # LOCAL_SOONG_VNDK_VERSION : means the version of VNDK where this module belongs
 
 ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
-  $(call pretty-error,soong_cc_prebuilt.mk may only be used from Soong)
+  $(call pretty-error,soong_cc_rust_prebuilt.mk may only be used from Soong)
 endif
 
 ifdef LOCAL_IS_HOST_MODULE
@@ -31,9 +31,9 @@
   $(call pretty-error,Unsupported LOCAL_MODULE_$(my_prefix)ARCH=$(LOCAL_MODULE_$(my_prefix)ARCH))
 endif
 
-# Don't install static libraries by default.
+# Don't install static/rlib/proc_macro libraries.
 ifndef LOCAL_UNINSTALLABLE_MODULE
-  ifeq (STATIC_LIBRARIES,$(LOCAL_MODULE_CLASS))
+  ifneq ($(filter STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
     LOCAL_UNINSTALLABLE_MODULE := true
   endif
 endif
@@ -45,11 +45,16 @@
   endif
 endif
 
+
+# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
+# to avoid checkbuilds making an extra copy of every module.
+LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
+
 #######################################
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
+ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES RLIB_LIBRARIES DYLIB_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
   # Soong module is a static or shared library
   EXPORTS_LIST += $(intermediates)
   EXPORTS.$(intermediates).FLAGS := $(LOCAL_EXPORT_CFLAGS)
@@ -108,6 +113,16 @@
     $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
       $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_shared_libraries))
   endif
+  ifdef LOCAL_DYLIB_LIBRARIES
+    my_dylibs := $(LOCAL_DYLIB_LIBRARIES)
+    # Treat these as shared library dependencies for installation purposes.
+    ifdef LOCAL_USE_VNDK
+      my_dylibs := $(foreach l,$(my_dylibs),\
+        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+    endif
+    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
+      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_dylibs))
+  endif
 endif
 
 my_check_same_vndk_variants :=
@@ -169,7 +184,7 @@
       # drop /root as /root is mounted as /
       my_unstripped_path := $(patsubst $(TARGET_OUT_UNSTRIPPED)/root/%,$(TARGET_OUT_UNSTRIPPED)/%, $(my_unstripped_path))
       symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-      $(eval $(call copy-one-file,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
+      $(eval $(call copy-unstripped-elf-file-with-mapping,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
       $(LOCAL_BUILT_MODULE): | $(symbolic_output)
 
       ifeq ($(BREAKPAD_GENERATE_SYMBOLS),true)
@@ -228,9 +243,9 @@
 
 $(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)
 
-# We don't care about installed static libraries, since the libraries have
+# We don't care about installed rlib/static libraries, since the libraries have
 # already been linked into the module at that point. We do, however, care
-# about the NOTICE files for any static libraries that we use.
+# about the NOTICE files for any rlib/static libraries that we use.
 # (see notice_files.mk)
 #
 # Filter out some NDK libraries that are not being exported.
@@ -242,6 +257,12 @@
 installed_static_library_notice_file_targets := \
     $(foreach lib,$(my_static_libraries) $(LOCAL_WHOLE_STATIC_LIBRARIES), \
       NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-STATIC_LIBRARIES-$(lib))
+installed_static_library_notice_file_targets += \
+    $(foreach lib,$(LOCAL_RLIB_LIBRARIES), \
+      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-RLIB_LIBRARIES-$(lib))
+installed_static_library_notice_file_targets += \
+    $(foreach lib,$(LOCAL_PROC_MACRO_LIBRARIES), \
+      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-PROC_MACRO_LIBRARIES-$(lib))
 
 $(notice_target): | $(installed_static_library_notice_file_targets)
 $(LOCAL_INSTALLED_MODULE): | $(notice_target)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index e0fd9be..d03b687 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -28,23 +28,26 @@
 $(call add_json_str,  Platform_sdk_codename,             $(PLATFORM_VERSION_CODENAME))
 $(call add_json_bool, Platform_sdk_final,                $(filter REL,$(PLATFORM_VERSION_CODENAME)))
 $(call add_json_val,  Platform_sdk_extension_version,    $(PLATFORM_SDK_EXTENSION_VERSION))
+$(call add_json_val,  Platform_base_sdk_extension_version, $(PLATFORM_BASE_SDK_EXTENSION_VERSION))
 $(call add_json_csv,  Platform_version_active_codenames, $(PLATFORM_VERSION_ALL_CODENAMES))
 $(call add_json_str,  Platform_security_patch,           $(PLATFORM_SECURITY_PATCH))
 $(call add_json_str,  Platform_preview_sdk_version,      $(PLATFORM_PREVIEW_SDK_VERSION))
 $(call add_json_str,  Platform_base_os,                  $(PLATFORM_BASE_OS))
+$(call add_json_str,  Platform_version_last_stable,      $(PLATFORM_VERSION_LAST_STABLE))
 
 $(call add_json_str,  Platform_min_supported_target_sdk_version, $(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION))
 
 $(call add_json_bool, Allow_missing_dependencies,        $(filter true,$(ALLOW_MISSING_DEPENDENCIES)))
 $(call add_json_bool, Unbundled_build,                   $(TARGET_BUILD_UNBUNDLED))
-$(call add_json_bool, Unbundled_build_apps,              $(TARGET_BUILD_APPS))
+$(call add_json_list, Unbundled_build_apps,              $(TARGET_BUILD_APPS))
+$(call add_json_bool, Unbundled_build_image,             $(TARGET_BUILD_UNBUNDLED_IMAGE))
 $(call add_json_bool, Always_use_prebuilt_sdks,          $(TARGET_BUILD_USE_PREBUILT_SDKS))
-$(call add_json_bool, Skip_boot_jars_check,              $(SKIP_BOOT_JARS_CHECK))
 
 $(call add_json_bool, Debuggable,                        $(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
 $(call add_json_bool, Eng,                               $(filter eng,$(TARGET_BUILD_VARIANT)))
 
 $(call add_json_str,  DeviceName,                        $(TARGET_DEVICE))
+$(call add_json_str,  DeviceProduct,                     $(TARGET_PRODUCT))
 $(call add_json_str,  DeviceArch,                        $(TARGET_ARCH))
 $(call add_json_str,  DeviceArchVariant,                 $(TARGET_ARCH_VARIANT))
 $(call add_json_str,  DeviceCpuVariant,                  $(TARGET_CPU_VARIANT))
@@ -73,6 +76,7 @@
 $(call add_json_str,  HostArch,                          $(HOST_ARCH))
 $(call add_json_str,  HostSecondaryArch,                 $(HOST_2ND_ARCH))
 $(call add_json_bool, HostStaticBinaries,                $(BUILD_HOST_static))
+$(call add_json_bool, HostMusl,                          $(USE_HOST_MUSL))
 
 $(call add_json_str,  CrossHost,                         $(HOST_CROSS_OS))
 $(call add_json_str,  CrossHostArch,                     $(HOST_CROSS_ARCH))
@@ -117,6 +121,7 @@
 
 $(call add_json_bool, GcovCoverage,                      $(filter true,$(NATIVE_COVERAGE)))
 $(call add_json_bool, ClangCoverage,                     $(filter true,$(CLANG_COVERAGE)))
+$(call add_json_bool, ClangCoverageContinuousMode,       $(filter true,$(CLANG_COVERAGE_CONTINUOUS_MODE)))
 $(call add_json_list, NativeCoveragePaths,               $(NATIVE_COVERAGE_PATHS))
 $(call add_json_list, NativeCoverageExcludePaths,        $(NATIVE_COVERAGE_EXCLUDE_PATHS))
 
@@ -163,6 +168,7 @@
 $(call add_json_list, VendorSnapshotDirsExcluded,        $(VENDOR_SNAPSHOT_DIRS_EXCLUDED))
 $(call add_json_list, RecoverySnapshotDirsIncluded,      $(RECOVERY_SNAPSHOT_DIRS_INCLUDED))
 $(call add_json_list, RecoverySnapshotDirsExcluded,      $(RECOVERY_SNAPSHOT_DIRS_EXCLUDED))
+$(call add_json_bool, HostFakeSnapshotEnabled,           $(HOST_FAKE_SNAPSHOT_ENABLE))
 
 $(call add_json_bool, Treble_linker_namespaces,          $(filter true,$(PRODUCT_TREBLE_LINKER_NAMESPACES)))
 $(call add_json_bool, Enforce_vintf_manifest,            $(filter true,$(PRODUCT_ENFORCE_VINTF_MANIFEST)))
@@ -172,6 +178,7 @@
 $(call add_json_str,  OdmPath,                           $(TARGET_COPY_OUT_ODM))
 $(call add_json_str,  VendorDlkmPath,                    $(TARGET_COPY_OUT_VENDOR_DLKM))
 $(call add_json_str,  OdmDlkmPath,                       $(TARGET_COPY_OUT_ODM_DLKM))
+$(call add_json_str,  SystemDlkmPath,                    $(TARGET_COPY_OUT_SYSTEM_DLKM))
 $(call add_json_str,  ProductPath,                       $(TARGET_COPY_OUT_PRODUCT))
 $(call add_json_str,  SystemExtPath,                     $(TARGET_COPY_OUT_SYSTEM_EXT))
 $(call add_json_bool, MinimizeJavaDebugInfo,             $(filter true,$(PRODUCT_MINIMIZE_JAVA_DEBUG_INFO)))
@@ -187,18 +194,28 @@
 
 $(call add_json_list, PgoAdditionalProfileDirs,          $(PGO_ADDITIONAL_PROFILE_DIRS))
 
+$(call add_json_list, BoardPlatVendorPolicy,             $(BOARD_PLAT_VENDOR_POLICY))
 $(call add_json_list, BoardReqdMaskPolicy,               $(BOARD_REQD_MASK_POLICY))
+$(call add_json_list, BoardSystemExtPublicPrebuiltDirs,  $(BOARD_SYSTEM_EXT_PUBLIC_PREBUILT_DIRS))
+$(call add_json_list, BoardSystemExtPrivatePrebuiltDirs, $(BOARD_SYSTEM_EXT_PRIVATE_PREBUILT_DIRS))
+$(call add_json_list, BoardProductPublicPrebuiltDirs,    $(BOARD_PRODUCT_PUBLIC_PREBUILT_DIRS))
+$(call add_json_list, BoardProductPrivatePrebuiltDirs,   $(BOARD_PRODUCT_PRIVATE_PREBUILT_DIRS))
 $(call add_json_list, BoardVendorSepolicyDirs,           $(BOARD_VENDOR_SEPOLICY_DIRS) $(BOARD_SEPOLICY_DIRS))
 $(call add_json_list, BoardOdmSepolicyDirs,              $(BOARD_ODM_SEPOLICY_DIRS))
 $(call add_json_list, BoardVendorDlkmSepolicyDirs,       $(BOARD_VENDOR_DLKM_SEPOLICY_DIRS))
 $(call add_json_list, BoardOdmDlkmSepolicyDirs,          $(BOARD_ODM_DLKM_SEPOLICY_DIRS))
+$(call add_json_list, BoardSystemDlkmSepolicyDirs,       $(BOARD_SYSTEM_DLKM_SEPOLICY_DIRS))
 # TODO: BOARD_PLAT_* dirs only kept for compatibility reasons. Will be a hard error on API level 31
 $(call add_json_list, SystemExtPublicSepolicyDirs,       $(SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS) $(BOARD_PLAT_PUBLIC_SEPOLICY_DIR))
 $(call add_json_list, SystemExtPrivateSepolicyDirs,      $(SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS) $(BOARD_PLAT_PRIVATE_SEPOLICY_DIR))
 $(call add_json_list, BoardSepolicyM4Defs,               $(BOARD_SEPOLICY_M4DEFS))
 $(call add_json_str,  BoardSepolicyVers,                 $(BOARD_SEPOLICY_VERS))
+$(call add_json_str,  SystemExtSepolicyPrebuiltApiDir,   $(BOARD_SYSTEM_EXT_PREBUILT_DIR))
+$(call add_json_str,  ProductSepolicyPrebuiltApiDir,     $(BOARD_PRODUCT_PREBUILT_DIR))
 
 $(call add_json_str,  PlatformSepolicyVersion,           $(PLATFORM_SEPOLICY_VERSION))
+$(call add_json_str,  TotSepolicyVersion,                $(TOT_SEPOLICY_VERSION))
+$(call add_json_list, PlatformSepolicyCompatVersions,    $(PLATFORM_SEPOLICY_COMPAT_VERSIONS))
 
 $(call add_json_bool, Flatten_apex,                      $(filter true,$(TARGET_FLATTEN_APEX)))
 $(call add_json_bool, ForceApexSymlinkOptimization,      $(filter true,$(TARGET_FORCE_APEX_SYMLINK_OPTIMIZATION)))
@@ -211,6 +228,8 @@
 $(call add_json_list, PackageNameOverrides,              $(PRODUCT_PACKAGE_NAME_OVERRIDES))
 $(call add_json_list, CertificateOverrides,              $(PRODUCT_CERTIFICATE_OVERRIDES))
 
+$(call add_json_str, ApexGlobalMinSdkVersionOverride,    $(APEX_GLOBAL_MIN_SDK_VERSION_OVERRIDE))
+
 $(call add_json_bool, EnforceSystemCertificate,          $(filter true,$(ENFORCE_SYSTEM_CERTIFICATE)))
 $(call add_json_list, EnforceSystemCertificateAllowList, $(ENFORCE_SYSTEM_CERTIFICATE_ALLOW_LIST))
 
@@ -256,6 +275,7 @@
 $(call add_json_bool, BuildBrokenEnforceSyspropOwner,     $(filter true,$(BUILD_BROKEN_ENFORCE_SYSPROP_OWNER)))
 $(call add_json_bool, BuildBrokenTrebleSyspropNeverallow, $(filter true,$(BUILD_BROKEN_TREBLE_SYSPROP_NEVERALLOW)))
 $(call add_json_bool, BuildBrokenVendorPropertyNamespace, $(filter true,$(BUILD_BROKEN_VENDOR_PROPERTY_NAMESPACE)))
+$(call add_json_list, BuildBrokenInputDirModules, $(BUILD_BROKEN_INPUT_DIR_MODULES))
 
 $(call add_json_bool, BuildDebugfsRestrictionsEnabled, $(filter true,$(PRODUCT_SET_DEBUGFS_RESTRICTIONS)))
 
@@ -265,6 +285,11 @@
 
 $(call add_json_bool, SepolicySplit, $(filter true,$(PRODUCT_SEPOLICY_SPLIT)))
 
+$(call add_json_list, SepolicyFreezeTestExtraDirs,         $(SEPOLICY_FREEZE_TEST_EXTRA_DIRS))
+$(call add_json_list, SepolicyFreezeTestExtraPrebuiltDirs, $(SEPOLICY_FREEZE_TEST_EXTRA_PREBUILT_DIRS))
+
+$(call add_json_bool, GenerateAidlNdkPlatformBackend, $(filter true,$(NEED_AIDL_NDK_PLATFORM_BACKEND)))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/soong_droiddoc_prebuilt.mk b/core/soong_droiddoc_prebuilt.mk
index 4dc5d08..ba597c5 100644
--- a/core/soong_droiddoc_prebuilt.mk
+++ b/core/soong_droiddoc_prebuilt.mk
@@ -6,6 +6,7 @@
 
 ifdef LOCAL_DROIDDOC_STUBS_SRCJAR
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_STUBS_SRCJAR),$(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar))
+$(eval ALL_TARGETS.$(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 ALL_DOCS += $(OUT_DOCS)/$(LOCAL_MODULE)-stubs.srcjar
 
 .PHONY: $(LOCAL_MODULE)
@@ -14,6 +15,7 @@
 
 ifdef LOCAL_DROIDDOC_DOC_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_DOC_ZIP),$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip))
+$(eval ALL_TARGETS.$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 $(call dist-for-goals,docs,$(OUT_DOCS)/$(LOCAL_MODULE)-docs.zip)
 
 .PHONY: $(LOCAL_MODULE) $(LOCAL_MODULE)-docs.zip
@@ -23,12 +25,15 @@
 
 ifdef LOCAL_DROIDDOC_ANNOTATIONS_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_ANNOTATIONS_ZIP),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_annotations.zip))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_annotations.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
 
 ifdef LOCAL_DROIDDOC_API_VERSIONS_XML
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_API_VERSIONS_XML),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_generated-api-versions.xml))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)_generated-api-versions.xml.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
 
 ifdef LOCAL_DROIDDOC_METADATA_ZIP
 $(eval $(call copy-one-file,$(LOCAL_DROIDDOC_METADATA_ZIP),$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)-metadata.zip))
+$(eval ALL_TARGETS.$(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/$(LOCAL_MODULE)-metadata.zip.META_LIC := $(LOCAL_SOONG_LICENSE_METADATA))
 endif
diff --git a/core/soong_java_prebuilt.mk b/core/soong_java_prebuilt.mk
index 1ebbf14..a8f475f 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -25,6 +25,15 @@
   LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_AAR)
 endif
 
+# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
+# to avoid checkbuilds making an extra copy of every module.
+LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_HEADER_JAR)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_FULL_MANIFEST_FILE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_DEXPREOPT_CONFIG)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE)
+LOCAL_ADDITIONAL_CHECKED_MODULE += $(LOCAL_SOONG_DEX_JAR)
+
 #######################################
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
@@ -53,18 +62,24 @@
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_DICT
+  my_proguard_dictionary_directory := $(local-proguard-dictionary-directory)
+  my_proguard_dictionary_mapping_directory := $(local-proguard-dictionary-mapping-directory)
   $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
     $(intermediates.COMMON)/proguard_dictionary))
-  $(eval $(call copy-one-file,$(LOCAL_SOONG_PROGUARD_DICT),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary))
+  $(eval $(call copy-r8-dictionary-file-with-mapping,\
+    $(LOCAL_SOONG_PROGUARD_DICT),\
+    $(my_proguard_dictionary_directory)/proguard_dictionary,\
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto))
   $(eval $(call copy-one-file,$(LOCAL_SOONG_CLASSES_JAR),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar))
+    $(my_proguard_dictionary_directory)/classes.jar))
   $(call add-dependency,$(common_javalib.jar),\
     $(intermediates.COMMON)/proguard_dictionary)
   $(call add-dependency,$(common_javalib.jar),\
-    $(call local-packaging-dir,proguard_dictionary)/proguard_dictionary)
+    $(my_proguard_dictionary_directory)/proguard_dictionary)
   $(call add-dependency,$(common_javalib.jar),\
-    $(call local-packaging-dir,proguard_dictionary)/classes.jar)
+    $(my_proguard_dictionary_mapping_directory)/proguard_dictionary.textproto)
+  $(call add-dependency,$(common_javalib.jar),\
+    $(my_proguard_dictionary_directory)/classes.jar)
 endif
 
 ifdef LOCAL_SOONG_PROGUARD_USAGE_ZIP
@@ -145,13 +160,7 @@
   endif
 endif  # LOCAL_SOONG_DEX_JAR
 
-my_built_installed := $(foreach f,$(LOCAL_SOONG_BUILT_INSTALLED),\
-  $(call word-colon,1,$(f)):$(PRODUCT_OUT)$(call word-colon,2,$(f)))
-my_installed := $(call copy-many-files, $(my_built_installed))
-ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-ALL_MODULES.$(my_register_name).BUILT_INSTALLED += $(my_built_installed)
 ALL_MODULES.$(my_register_name).CLASSES_JAR := $(full_classes_jar)
-$(my_register_name): $(my_installed)
 
 ifdef LOCAL_SOONG_AAR
   ALL_MODULES.$(my_register_name).AAR := $(LOCAL_SOONG_AAR)
diff --git a/core/soong_rust_prebuilt.mk b/core/soong_rust_prebuilt.mk
deleted file mode 100644
index 26c099b..0000000
--- a/core/soong_rust_prebuilt.mk
+++ /dev/null
@@ -1,184 +0,0 @@
-# Native prebuilt coming from Soong.
-# Extra inputs:
-# LOCAL_SOONG_UNSTRIPPED_BINARY
-
-ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
-  $(call pretty-error,soong_rust_prebuilt.mk may only be used from Soong)
-endif
-
-ifdef LOCAL_IS_HOST_MODULE
-  ifneq ($(HOST_OS),$(LOCAL_MODULE_HOST_OS))
-    my_prefix := HOST_CROSS_
-    LOCAL_HOST_PREFIX := $(my_prefix)
-  else
-    my_prefix := HOST_
-    LOCAL_HOST_PREFIX :=
-  endif
-else
-  my_prefix := TARGET_
-endif
-
-ifeq ($($(my_prefix)ARCH),$(LOCAL_MODULE_$(my_prefix)ARCH))
-  # primary arch
-  LOCAL_2ND_ARCH_VAR_PREFIX :=
-else ifeq ($($(my_prefix)2ND_ARCH),$(LOCAL_MODULE_$(my_prefix)ARCH))
-  # secondary arch
-  LOCAL_2ND_ARCH_VAR_PREFIX := $($(my_prefix)2ND_ARCH_VAR_PREFIX)
-else
-  $(call pretty-error,Unsupported LOCAL_MODULE_$(my_prefix)ARCH=$(LOCAL_MODULE_$(my_prefix)ARCH))
-endif
-
-# Don't install static/rlib/proc_macro libraries.
-ifndef LOCAL_UNINSTALLABLE_MODULE
-  ifneq ($(filter STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-    LOCAL_UNINSTALLABLE_MODULE := true
-  endif
-endif
-
-
-#######################################
-include $(BUILD_SYSTEM)/base_rules.mk
-#######################################
-
-ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES RLIB_LIBRARIES DYLIB_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-  # Soong module is a static or shared library
-  EXPORTS_LIST += $(intermediates)
-  EXPORTS.$(intermediates).FLAGS := $(LOCAL_EXPORT_CFLAGS)
-  EXPORTS.$(intermediates).DEPS := $(LOCAL_EXPORT_C_INCLUDE_DEPS)
-
-  SOONG_ALREADY_CONV += $(LOCAL_MODULE)
-
-  my_link_type := $(LOCAL_SOONG_LINK_TYPE)
-  my_warn_types :=
-  my_allowed_types :=
-  my_link_deps :=
-  my_2nd_arch_prefix := $(LOCAL_2ND_ARCH_VAR_PREFIX)
-  my_common :=
-  include $(BUILD_SYSTEM)/link_type.mk
-endif
-
-
-ifdef LOCAL_USE_VNDK
-  ifneq ($(LOCAL_VNDK_DEPEND_ON_CORE_VARIANT),true)
-    name_without_suffix := $(patsubst %.vendor,%,$(LOCAL_MODULE))
-    ifneq ($(name_without_suffix),$(LOCAL_MODULE))
-      SPLIT_VENDOR.$(LOCAL_MODULE_CLASS).$(name_without_suffix) := 1
-    else
-      name_without_suffix := $(patsubst %.product,%,$(LOCAL_MODULE))
-      ifneq ($(name_without_suffix),$(LOCAL_MODULE))
-        SPLIT_PRODUCT.$(LOCAL_MODULE_CLASS).$(name_without_suffix) := 1
-      endif
-    endif
-    name_without_suffix :=
-  endif
-endif
-
-# The real dependency will be added after all Android.mks are loaded and the install paths
-# of the shared libraries are determined.
-ifdef LOCAL_INSTALLED_MODULE
-  ifdef LOCAL_SHARED_LIBRARIES
-    my_shared_libraries := $(LOCAL_SHARED_LIBRARIES)
-    ifdef LOCAL_USE_VNDK
-      my_shared_libraries := $(foreach l,$(my_shared_libraries),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
-    endif
-    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
-      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_shared_libraries))
-  endif
-  ifdef LOCAL_DYLIB_LIBRARIES
-    my_dylibs := $(LOCAL_DYLIB_LIBRARIES)
-    # Treat these as shared library dependencies for installation purposes.
-    ifdef LOCAL_USE_VNDK
-      my_dylibs := $(foreach l,$(my_dylibs),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
-    endif
-    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
-      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_dylibs))
-  endif
-endif
-
-$(LOCAL_BUILT_MODULE): $(LOCAL_PREBUILT_MODULE_FILE)
-ifeq ($(LOCAL_IS_HOST_MODULE) $(if $(filter EXECUTABLES SHARED_LIBRARIES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),true,),true true)
-	$(copy-or-link-prebuilt-to-target)
-  ifneq ($(filter EXECUTABLES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),)
-	[ -x $@ ] || ( $(call echo-error,$@,Target of symlink is not executable); false )
-  endif
-else
-	$(transform-prebuilt-to-target)
-  ifneq ($(filter EXECUTABLES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),)
-	$(hide) chmod +x $@
-  endif
-endif
-
-ifndef LOCAL_IS_HOST_MODULE
-  ifdef LOCAL_SOONG_UNSTRIPPED_BINARY
-    my_symbol_path := $(if $(LOCAL_SOONG_SYMBOL_PATH),$(LOCAL_SOONG_SYMBOL_PATH),$(my_module_path))
-    # Store a copy with symbols for symbolic debugging
-    my_unstripped_path := $(TARGET_OUT_UNSTRIPPED)/$(patsubst $(PRODUCT_OUT)/%,%,$(my_symbol_path))
-    # drop /root as /root is mounted as /
-    my_unstripped_path := $(patsubst $(TARGET_OUT_UNSTRIPPED)/root/%,$(TARGET_OUT_UNSTRIPPED)/%, $(my_unstripped_path))
-    symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-    $(eval $(call copy-one-file,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
-    $(LOCAL_BUILT_MODULE): | $(symbolic_output)
-  endif
-endif
-
-create_coverage_zip :=
-
-ifeq ($(NATIVE_COVERAGE),true)
-   create_coverage_zip := true
-endif
-
-# Until Rust supports LLVM coverage, Soong assumes GCOV coverage in both cases.
-# Therefore we should create the coverage zip with the gcno files in this case as well.
-ifeq ($(CLANG_COVERAGE),true)
-   create_coverage_zip := true
-endif
-
-ifdef create_coverage_zip
-  ifneq (,$(strip $(LOCAL_PREBUILT_COVERAGE_ARCHIVE)))
-    $(eval $(call copy-one-file,$(LOCAL_PREBUILT_COVERAGE_ARCHIVE),$(intermediates)/$(LOCAL_MODULE).zip))
-    ifneq ($(LOCAL_UNINSTALLABLE_MODULE),true)
-      ifdef LOCAL_IS_HOST_MODULE
-        my_coverage_path := $($(my_prefix)OUT_COVERAGE)/$(patsubst $($(my_prefix)OUT)/%,%,$(my_module_path))
-      else
-        my_coverage_path := $(TARGET_OUT_COVERAGE)/$(patsubst $(PRODUCT_OUT)/%,%,$(my_module_path))
-      endif
-      my_coverage_path := $(my_coverage_path)/$(patsubst %.so,%,$(my_installed_module_stem)).zip
-      $(eval $(call copy-one-file,$(LOCAL_PREBUILT_COVERAGE_ARCHIVE),$(my_coverage_path)))
-      $(LOCAL_BUILT_MODULE): $(my_coverage_path)
-    endif
-  endif
-endif
-
-# A product may be configured to strip everything in some build variants.
-# We do the stripping as a post-install command so that LOCAL_BUILT_MODULE
-# is still with the symbols and we don't need to clean it (and relink) when
-# you switch build variant.
-ifneq ($(filter $(STRIP_EVERYTHING_BUILD_VARIANTS),$(TARGET_BUILD_VARIANT)),)
-$(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := \
-  $($(LOCAL_2ND_ARCH_VAR_PREFIX)TARGET_STRIP) --strip-all $(LOCAL_INSTALLED_MODULE)
-endif
-
-$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)
-
-# We don't care about installed rlib/static libraries, since the libraries have
-# already been linked into the module at that point. We do, however, care
-# about the NOTICE files for any rlib/static libraries that we use.
-# (see notice_files.mk)
-#
-# Filter out some NDK libraries that are not being exported.
-my_static_libraries := \
-    $(filter-out ndk_libc++_static ndk_libc++abi ndk_libandroid_support ndk_libunwind \
-      ndk_libc++_static.native_bridge ndk_libc++abi.native_bridge \
-      ndk_libandroid_support.native_bridge ndk_libunwind.native_bridge, \
-      $(LOCAL_STATIC_LIBRARIES))
-installed_static_library_notice_file_targets := \
-    $(foreach lib,$(my_static_libraries), \
-      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-STATIC_LIBRARIES-$(lib))
-installed_static_library_notice_file_targets += \
-    $(foreach lib,$(LOCAL_RLIB_LIBRARIES), \
-      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-RLIB_LIBRARIES-$(lib))
-
-$(notice_target): | $(installed_static_library_notice_file_targets)
-$(LOCAL_INSTALLED_MODULE): | $(notice_target)
diff --git a/core/static_java_library.mk b/core/static_java_library.mk
index 7a87322..4053985 100644
--- a/core/static_java_library.mk
+++ b/core/static_java_library.mk
@@ -101,6 +101,13 @@
 include $(BUILD_SYSTEM)/java_renderscript.mk
 
 ifeq (true,$(need_compile_res))
+# work around missing manifests by creating a default one
+ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
+  ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
+    LOCAL_FULL_MANIFEST_FILE := $(call local-intermediates-dir,COMMON)/DefaultManifest.xml
+    $(call create-default-manifest-file,$(LOCAL_FULL_MANIFEST_FILE),$(call module-min-sdk-version))
+  endif
+endif
 include $(BUILD_SYSTEM)/android_manifest.mk
 
 LOCAL_SDK_RES_VERSION:=$(strip $(LOCAL_SDK_RES_VERSION))
diff --git a/core/sysprop.mk b/core/sysprop.mk
index ec181f5..61c07ba 100644
--- a/core/sysprop.mk
+++ b/core/sysprop.mk
@@ -98,7 +98,7 @@
     $(eval _option := --allow-dup)\
 )
 
-$(2): $(POST_PROCESS_PROPS) $(INTERNAL_BUILD_ID_MAKEFILE) $(API_FINGERPRINT) $(3) $(6)
+$(2): $(POST_PROCESS_PROPS) $(INTERNAL_BUILD_ID_MAKEFILE) $(3) $(6)
 	$(hide) echo Building $$@
 	$(hide) mkdir -p $$(dir $$@)
 	$(hide) rm -f $$@ && touch $$@
@@ -128,6 +128,8 @@
 	        cat $(file) >> $$@;\
 	    fi;)
 	$(hide) echo "# end of file" >> $$@
+
+$(call declare-0p-target,$(2))
 endef
 
 # -----------------------------------------------------------------
@@ -262,6 +264,7 @@
 	        BOARD_BUILD_SYSTEM_ROOT_IMAGE="$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)" \
 	        BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT="$(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT)" \
 	        PLATFORM_VERSION="$(PLATFORM_VERSION)" \
+	        PLATFORM_DISPLAY_VERSION="$(PLATFORM_DISPLAY_VERSION)" \
 	        PLATFORM_VERSION_LAST_STABLE="$(PLATFORM_VERSION_LAST_STABLE)" \
 	        PLATFORM_SECURITY_PATCH="$(PLATFORM_SECURITY_PATCH)" \
 	        PLATFORM_BASE_OS="$(PLATFORM_BASE_OS)" \
@@ -270,6 +273,7 @@
 	        PLATFORM_PREVIEW_SDK_FINGERPRINT="$$(cat $(API_FINGERPRINT))" \
 	        PLATFORM_VERSION_CODENAME="$(PLATFORM_VERSION_CODENAME)" \
 	        PLATFORM_VERSION_ALL_CODENAMES="$(PLATFORM_VERSION_ALL_CODENAMES)" \
+	        PLATFORM_VERSION_KNOWN_CODENAMES="$(PLATFORM_VERSION_KNOWN_CODENAMES)" \
 	        PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION="$(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)" \
 	        BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
 	        $(if $(OEM_THUMBPRINT_PROPERTIES),BUILD_THUMBPRINT="$(BUILD_THUMBPRINT_FROM_FILE)") \
@@ -306,10 +310,6 @@
     PRODUCT_VENDOR_PROPERTIES
 endif
 
-_blacklist_names_ := \
-    $(PRODUCT_SYSTEM_PROPERTY_BLACKLIST) \
-    ro.product.first_api_level
-
 INSTALLED_BUILD_PROP_TARGET := $(TARGET_OUT)/build.prop
 
 $(eval $(call build-properties,\
@@ -317,10 +317,12 @@
     $(INSTALLED_BUILD_PROP_TARGET),\
     $(_prop_files_),\
     $(_prop_vars_),\
-    $(_blacklist_names_),\
+    $(PRODUCT_SYSTEM_PROPERTY_BLACKLIST),\
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # vendor/build.prop
 #
@@ -359,6 +361,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_VENDOR_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # product/etc/build.prop
 #
@@ -411,6 +415,8 @@
     $(_footers_),\
     $(_skip_common_properties)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_PRODUCT_BUILD_PROP_TARGET)))
+
 _skip_common_properties :=
 
 # ----------------------------------------------------------------
@@ -436,6 +442,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_ODM_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # vendor_dlkm/etc/build.prop
 #
@@ -450,6 +458,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_VENDOR_DLKM_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # odm_dlkm/etc/build.prop
 #
@@ -464,6 +474,24 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_ODM_DLKM_BUILD_PROP_TARGET)))
+
+# ----------------------------------------------------------------
+# system_dlkm/build.prop
+#
+
+INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET := $(TARGET_OUT_SYSTEM_DLKM)/etc/build.prop
+$(eval $(call build-properties,\
+    system_dlkm,\
+    $(INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET),\
+    $(empty),\
+    $(empty),\
+    $(empty),\
+    $(empty),\
+    $(empty)))
+
+$(eval $(call declare-1p-target,$(INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET)))
+
 # -----------------------------------------------------------------
 # system_ext/etc/build.prop
 #
@@ -485,6 +513,8 @@
     $(empty),\
     $(empty)))
 
+$(eval $(call declare-1p-target,$(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET)))
+
 # ----------------------------------------------------------------
 # ramdisk/boot/etc/build.prop
 #
@@ -499,3 +529,5 @@
     $(empty),\
     $(empty),\
     $(empty)))
+
+$(eval $(call declare-1p-target,$(INSTALLED_RAMDISK_BUILD_PROP_TARGET)))
diff --git a/core/tasks/OWNERS b/core/tasks/OWNERS
new file mode 100644
index 0000000..594930d
--- /dev/null
+++ b/core/tasks/OWNERS
@@ -0,0 +1 @@
+per-file art-host-tests.mk = dshi@google.com,dsrbecky@google.com,jdesprez@google.com,rpl@google.com
diff --git a/core/tasks/art-host-tests.mk b/core/tasks/art-host-tests.mk
index b9a349d..2af1ded 100644
--- a/core/tasks/art-host-tests.mk
+++ b/core/tasks/art-host-tests.mk
@@ -42,4 +42,7 @@
 art-host-tests: $(art_host_tests_zip)
 $(call dist-for-goals, art-host-tests, $(art_host_tests_zip))
 
+$(call declare-1p-container,$(art_host_tests_zip),)
+$(call declare-container-license-deps,$(art_host_tests_zip),$(COMPATIBILITY.art-host-tests.FILES) $(my_host_shared_lib_for_art_host_tests),$(PRODUCT_OUT)/:/)
+
 tests: art-host-tests
diff --git a/core/tasks/cts.mk b/core/tasks/cts.mk
index fdd9591..c282268 100644
--- a/core/tasks/cts.mk
+++ b/core/tasks/cts.mk
@@ -17,11 +17,220 @@
 test_suite_dynamic_config := cts/tools/cts-tradefed/DynamicConfig.xml
 test_suite_readme := cts/tools/cts-tradefed/README
 
+$(call declare-1p-target,$(test_suite_dynamic_config),cts)
+$(call declare-1p-target,$(test_suite_readme),cts)
+
 include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
 
 .PHONY: cts
-cts: $(compatibility_zip)
-$(call dist-for-goals, cts, $(compatibility_zip))
+cts: $(compatibility_zip) $(compatibility_tests_list_zip)
+$(call dist-for-goals, cts, $(compatibility_zip) $(compatibility_tests_list_zip))
 
 .PHONY: cts_v2
 cts_v2: cts
+
+# platform version check (b/32056228)
+# ============================================================
+ifneq (,$(wildcard cts/))
+  cts_platform_version_path := cts/tests/tests/os/assets/platform_versions.txt
+  cts_platform_version_string := $(shell cat $(cts_platform_version_path))
+  cts_platform_release_path := cts/tests/tests/os/assets/platform_releases.txt
+  cts_platform_release_string := $(shell cat $(cts_platform_release_path))
+
+  ifeq (,$(findstring $(PLATFORM_VERSION),$(cts_platform_version_string)))
+    define error_msg
+      ============================================================
+      Could not find version "$(PLATFORM_VERSION)" in CTS platform version file:
+      $(cts_platform_version_path)
+      Most likely PLATFORM_VERSION in build/core/version_defaults.mk
+      has changed and a new version must be added to this CTS file.
+      ============================================================
+    endef
+    $(error $(error_msg))
+  endif
+  ifeq (,$(findstring $(PLATFORM_VERSION_LAST_STABLE),$(cts_platform_release_string)))
+    define error_msg
+      ============================================================
+      Could not find version "$(PLATFORM_VERSION_LAST_STABLE)" in CTS platform release file:
+      $(cts_platform_release_path)
+      Most likely PLATFORM_VERSION_LAST_STABLE in build/core/version_defaults.mk
+      has changed and a new version must be added to this CTS file.
+      ============================================================
+    endef
+    $(error $(error_msg))
+  endif
+endif
+
+# Creates a "cts-verifier" directory that will contain:
+#
+# 1. Out directory with a "android-cts-verifier" containing the CTS Verifier
+#    and other binaries it needs.
+#
+# 2. Zipped version of the android-cts-verifier directory to be included with
+#    the build distribution.
+##
+cts-dir := $(HOST_OUT)/cts-verifier
+verifier-dir-name := android-cts-verifier
+verifier-dir := $(cts-dir)/$(verifier-dir-name)
+verifier-zip-name := $(verifier-dir-name).zip
+verifier-zip := $(cts-dir)/$(verifier-zip-name)
+
+cts : $(verifier-zip)
+$(verifier-zip): PRIVATE_DIR := $(cts-dir)
+$(verifier-zip): $(SOONG_ANDROID_CTS_VERIFIER_ZIP)
+	rm -rf $(PRIVATE_DIR)
+	mkdir -p $(PRIVATE_DIR)
+	unzip -q -d $(PRIVATE_DIR) $<
+	$(copy-file-to-target)
+
+# For producing CTS coverage reports.
+# Run "make cts-test-coverage" in the $ANDROID_BUILD_TOP directory.
+
+cts_api_coverage_exe := $(HOST_OUT_EXECUTABLES)/cts-api-coverage
+dexdeps_exe := $(HOST_OUT_EXECUTABLES)/dexdeps
+
+coverage_out := $(HOST_OUT)/cts-api-coverage
+
+api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/api.xml
+
+napi_text_description := cts/tools/cts-api-coverage/etc/ndk-api.xml
+napi_xml_description := $(coverage_out)/ndk-api.xml
+$(napi_xml_description) : $(napi_text_description) $(ACP)
+		$(hide) echo "Preparing NDK API XML: $@"
+		$(hide) mkdir -p $(dir $@)
+		$(hide) $(ACP)  $< $@
+
+system_api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/system-api.xml
+
+cts-test-coverage-report := $(coverage_out)/test-coverage.html
+cts-system-api-coverage-report := $(coverage_out)/system-api-coverage.html
+cts-system-api-xml-coverage-report := $(coverage_out)/system-api-coverage.xml
+cts-verifier-coverage-report := $(coverage_out)/verifier-coverage.html
+cts-combined-coverage-report := $(coverage_out)/combined-coverage.html
+cts-combined-xml-coverage-report := $(coverage_out)/combined-coverage.xml
+
+cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description) $(napi_xml_description)
+cts_system_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(system_api_xml_description)
+
+android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
+cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
+
+$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-test-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-test-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-test-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
+$(cts-test-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
+$(cts-test-coverage-report) : $(android_cts_zip) $(cts_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS Tests API-NDK Coverage Report",\
+			$(PRIVATE_TEST_CASES),html)
+
+$(cts-system-api-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS System API Coverage Report",\
+			$(PRIVATE_TEST_CASES),html)
+
+$(cts-system-api-xml-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-xml-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-xml-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
+			$(PRIVATE_TEST_CASES),xml)
+
+$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
+$(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-verifier-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
+$(cts-verifier-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
+$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS Verifier API Coverage Report",\
+			$(PRIVATE_TEST_CASES),html)
+
+$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
+$(cts-combined-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
+$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS Combined API Coverage Report",\
+			$(PRIVATE_TEST_CASES),html)
+
+$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
+$(cts-combined-xml-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
+$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS Combined API Coverage Report - XML",\
+			$(PRIVATE_TEST_CASES),xml)
+
+.PHONY: cts-test-coverage
+cts-test-coverage : $(cts-test-coverage-report)
+
+.PHONY: cts-system-api-coverage
+cts-system-api-coverage : $(cts-system-api-coverage-report)
+
+.PHONY: cts-system-api-xml-coverage
+cts-system-api-xml-coverage : $(cts-system-api-xml-coverage-report)
+
+.PHONY: cts-verifier-coverage
+cts-verifier-coverage : $(cts-verifier-coverage-report)
+
+.PHONY: cts-combined-coverage
+cts-combined-coverage : $(cts-combined-coverage-report)
+
+.PHONY: cts-combined-xml-coverage
+cts-combined-xml-coverage : $(cts-combined-xml-coverage-report)
+
+.PHONY: cts-coverage-report-all cts-api-coverage
+cts-coverage-report-all: cts-test-coverage cts-verifier-coverage cts-combined-coverage cts-combined-xml-coverage
+
+# Put the test coverage report in the dist dir if "cts-api-coverage" is among the build goals.
+$(call dist-for-goals, cts-api-coverage, $(cts-test-coverage-report):cts-test-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-coverage-report):cts-system-api-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-xml-coverage-report):cts-system-api-coverage-report.xml)
+$(call dist-for-goals, cts-api-coverage, $(cts-verifier-coverage-report):cts-verifier-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-combined-coverage-report):cts-combined-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-combined-xml-coverage-report):cts-combined-coverage-report.xml)
+
+ALL_TARGETS.$(cts-test-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-system-api-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-system-api-xml-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-verifier-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-combined-coverage-report).META_LIC:=$(module_license_metadata)
+ALL_TARGETS.$(cts-combined-xml-coverage-report).META_LIC:=$(module_license_metadata)
+
+# Arguments;
+#  1 - Name of the report printed out on the screen
+#  2 - List of apk files that will be scanned to generate the report
+#  3 - Format of the report
+define generate-coverage-report-cts
+	$(hide) mkdir -p $(dir $@)
+	$(hide) $(PRIVATE_CTS_API_COVERAGE_EXE) -d $(PRIVATE_DEXDEPS_EXE) -a $(PRIVATE_API_XML_DESC) -n $(PRIVATE_NAPI_XML_DESC) -f $(3) -o $@ $(2)
+	@ echo $(1): file://$$(cd $(dir $@); pwd)/$(notdir $@)
+endef
+
+# Reset temp vars
+cts_api_coverage_dependencies :=
+cts_system_api_coverage_dependencies :=
+cts-combined-coverage-report :=
+cts-combined-xml-coverage-report :=
+cts-verifier-coverage-report :=
+cts-test-coverage-report :=
+cts-system-api-coverage-report :=
+cts-system-api-xml-coverage-report :=
+api_xml_description :=
+api_text_description :=
+system_api_xml_description :=
+napi_xml_description :=
+napi_text_description :=
+coverage_out :=
+dexdeps_exe :=
+cts_api_coverage_exe :=
+cts_verifier_apk :=
+android_cts_zip :=
diff --git a/core/tasks/device-tests.mk b/core/tasks/device-tests.mk
index 73fad7c..3196f52 100644
--- a/core/tasks/device-tests.mk
+++ b/core/tasks/device-tests.mk
@@ -55,4 +55,7 @@
 device-tests: $(device-tests-zip)
 $(call dist-for-goals, device-tests, $(device-tests-zip) $(device-tests-list-zip) $(device-tests-configs-zip) $(device_tests_host_shared_libs_zip))
 
+$(call declare-1p-container,$(device-tests-zip),)
+$(call declare-container-license-deps,$(device-tests-zip),$(COMPATIBILITY.device-tests.FILES) $(my_host_shared_lib_for_device_tests),$(PRODUCT_OUT)/:/)
+
 tests: device-tests
diff --git a/core/tasks/dex_preopt_check.mk b/core/tasks/dex_preopt_check.mk
new file mode 100644
index 0000000..bfa1ec5
--- /dev/null
+++ b/core/tasks/dex_preopt_check.mk
@@ -0,0 +1,18 @@
+# Checks that some critical dexpreopt output files are installed.
+
+# Inputs:
+# DISABLE_DEXPREOPT_CHECK: True if the check should be disabled.
+# PRODUCT_PACKAGES: The list of packages to be installed for the product.
+# ALL_DEFAULT_INSTALLED_MODULES: The full list of modules going to be installed.
+# DEXPREOPT_SYSTEMSERVER_ARTIFACTS: The list of compilation artifacts of system server jars, which
+# 	is generated by Soong in dexpreopt_check.go.
+
+ifneq (true,$(DISABLE_DEXPREOPT_CHECK))
+  # Skip the check if the system server is not installed for the product.
+  ifneq (,$(filter services,$(PRODUCT_PACKAGES)))
+    $(call maybe-print-list-and-error,\
+      $(filter-out $(ALL_DEFAULT_INSTALLED_MODULES),$(DEXPREOPT_SYSTEMSERVER_ARTIFACTS)),\
+      Missing compilation artifacts. Dexpreopting is not working for some system server jars \
+    )
+  endif
+endif
diff --git a/core/tasks/find-shareduid-violation.mk b/core/tasks/find-shareduid-violation.mk
index d6885eb..b5feef1 100644
--- a/core/tasks/find-shareduid-violation.mk
+++ b/core/tasks/find-shareduid-violation.mk
@@ -35,4 +35,5 @@
 		--copy_out_system_ext $(TARGET_COPY_OUT_SYSTEM_EXT) \
 		> $@
 
+$(call declare-0p-target,$(shareduid_violation_modules_filename))
 $(call dist-for-goals,droidcore,$(shareduid_violation_modules_filename))
diff --git a/core/tasks/general-tests.mk b/core/tasks/general-tests.mk
index a820a28..5252394 100644
--- a/core/tasks/general-tests.mk
+++ b/core/tasks/general-tests.mk
@@ -40,6 +40,18 @@
 # Create an artifact to include all shared librariy files in general-tests.
 general_tests_host_shared_libs_zip := $(PRODUCT_OUT)/general-tests_host-shared-libs.zip
 
+# Copy kernel test modules to testcases directories
+include $(BUILD_SYSTEM)/tasks/tools/vts-kernel-tests.mk
+kernel_test_copy_pairs := \
+  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_host_out))
+copy_kernel_tests := $(call copy-many-files,$(kernel_test_copy_pairs))
+
+# PHONY target to be used to build and test `vts_kernel_tests` without building full vts
+.PHONY: vts_kernel_tests
+vts_kernel_tests: $(copy_kernel_tests)
+
+$(general_tests_zip) : $(copy_kernel_tests)
+$(general_tests_zip) : PRIVATE_KERNEL_TEST_HOST_OUT := $(kernel_test_host_out)
 $(general_tests_zip) : PRIVATE_general_tests_list_zip := $(general_tests_list_zip)
 $(general_tests_zip) : .KATI_IMPLICIT_OUTPUTS := $(general_tests_list_zip) $(general_tests_configs_zip) $(general_tests_host_shared_libs_zip)
 $(general_tests_zip) : PRIVATE_TOOLS := $(general_tests_tools)
@@ -52,6 +64,7 @@
 	rm -f $@ $(PRIVATE_general_tests_list_zip)
 	mkdir -p $(PRIVATE_INTERMEDIATES_DIR) $(PRIVATE_INTERMEDIATES_DIR)/tools
 	echo $(sort $(COMPATIBILITY.general-tests.FILES)) | tr " " "\n" > $(PRIVATE_INTERMEDIATES_DIR)/list
+	find $(PRIVATE_KERNEL_TEST_HOST_OUT) >> $(PRIVATE_INTERMEDIATES_DIR)/list
 	grep $(HOST_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/host.list || true
 	grep $(TARGET_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/target.list || true
 	grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/host.list > $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list || true
@@ -78,6 +91,9 @@
 general-tests: $(general_tests_zip)
 $(call dist-for-goals, general-tests, $(general_tests_zip) $(general_tests_list_zip) $(general_tests_configs_zip) $(general_tests_host_shared_libs_zip))
 
+$(call declare-1p-container,$(general_tests_zip),)
+$(call declare-container-license-deps,$(general_tests_zip),$(COMPATIBILITY.general-tests.FILES) $(general_tests_tools) $(my_host_shared_lib_for_general_tests),$(PRODUCT_OUT)/:/)
+
 intermediates_dir :=
 general_tests_tools :=
 general_tests_zip :=
diff --git a/core/tasks/host-unit-tests.mk b/core/tasks/host-unit-tests.mk
index 755b589..4453c29 100644
--- a/core/tasks/host-unit-tests.mk
+++ b/core/tasks/host-unit-tests.mk
@@ -39,7 +39,7 @@
 	  echo $$shared_lib >> $@-host-libs.list; \
 	done
 	grep $(TARGET_OUT_TESTCASES) $@.list > $@-target.list || true
-	$(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list \
+	$(hide) $(SOONG_ZIP) -L 0 -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list \
 	  -P target -C $(PRODUCT_OUT) -l $@-target.list \
 	  -P host/testcases -C $(HOST_OUT) -l $@-host-libs.list
 	rm -f $@.list $@-host.list $@-target.list $@-host-libs.list
@@ -47,4 +47,7 @@
 host-unit-tests: $(host_unit_tests_zip)
 $(call dist-for-goals, host-unit-tests, $(host_unit_tests_zip))
 
+$(call declare-1p-container,$(host_unit_tests_zip),)
+$(call declare-container-license-deps,$(host_unit_tests_zip),$(COMPATIBILITY.host-unit-tests.FILES) $(my_host_shared_lib_for_host_unit_tests),$(PRODUCT_OUT)/:/)
+
 tests: host-unit-tests
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index c838264..8097535 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -1,4 +1,5 @@
 # Print a list of the modules that could be built
+# Currently runtime_dependencies only include the runtime libs information for cc binaries.
 
 MODULE_INFO_JSON := $(PRODUCT_OUT)/module-info.json
 
@@ -16,11 +17,17 @@
 			'"module_name": "$(ALL_MODULES.$(m).MODULE_NAME)", ' \
 			'"test_config": [$(foreach w,$(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)),"$(w)", )], ' \
 			'"dependencies": [$(foreach w,$(sort $(ALL_DEPS.$(m).ALL_DEPS)),"$(w)", )], ' \
+			'"shared_libs": [$(foreach w,$(sort $(ALL_MODULES.$(m).SHARED_LIBS)),"$(w)", )], ' \
+			'"system_shared_libs": [$(foreach w,$(sort $(ALL_MODULES.$(m).SYSTEM_SHARED_LIBS)),"$(w)", )], ' \
 			'"srcs": [$(foreach w,$(sort $(ALL_MODULES.$(m).SRCS)),"$(w)", )], ' \
 			'"srcjars": [$(foreach w,$(sort $(ALL_MODULES.$(m).SRCJARS)),"$(w)", )], ' \
 			'"classes_jar": [$(foreach w,$(sort $(ALL_MODULES.$(m).CLASSES_JAR)),"$(w)", )], ' \
 			'"test_mainline_modules": [$(foreach w,$(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES)),"$(w)", )], ' \
 			'"is_unit_test": "$(ALL_MODULES.$(m).IS_UNIT_TEST)", ' \
+			'"data": [$(foreach w,$(sort $(ALL_MODULES.$(m).TEST_DATA)),"$(w)", )], ' \
+			'"runtime_dependencies": [$(foreach w,$(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES)),"$(w)", )], ' \
+			'"data_dependencies": [$(foreach w,$(sort $(ALL_MODULES.$(m).TEST_DATA_BINS)),"$(w)", )], ' \
+			'"supported_variants": [$(foreach w,$(sort $(ALL_MODULES.$(m).SUPPORTED_VARIANTS)),"$(w)", )], ' \
 			'},\n' \
 	 ) | sed -e 's/, *\]/]/g' -e 's/, *\}/ }/g' -e '$$s/,$$//' >> $@
 	$(hide) echo '}' >> $@
diff --git a/core/tasks/oem_image.mk b/core/tasks/oem_image.mk
index a847b9d..134be01 100644
--- a/core/tasks/oem_image.mk
+++ b/core/tasks/oem_image.mk
@@ -43,4 +43,7 @@
 oem_image : $(INSTALLED_OEMIMAGE_TARGET)
 $(call dist-for-goals, oem_image, $(INSTALLED_OEMIMAGE_TARGET))
 
+$(call declare-1p-container,$(INSTALLED_OEMIMAGE_TARGET),)
+$(call declare-container-license-deps,$(INSTALLED_OEMIMAGE_TARGET),$(INTERNAL_USERIMAGE_DEPS) $(INTERNAL_OEMIMAGE_FILES),$(INSTALLED_OEMIMAGE_TARGET):)
+
 endif  # oem_image in $(MAKECMDGOALS)
diff --git a/core/tasks/owners.mk b/core/tasks/owners.mk
index 6f32aaf..806b8ee 100644
--- a/core/tasks/owners.mk
+++ b/core/tasks/owners.mk
@@ -31,3 +31,5 @@
 owners : $(owners_zip)
 
 $(call dist-for-goals, general-tests, $(owners_zip))
+
+$(call declare-0p-target,$(owners_zip))
diff --git a/core/tasks/test_mapping.mk b/core/tasks/test_mapping.mk
index da64cab..0b0c93c 100644
--- a/core/tasks/test_mapping.mk
+++ b/core/tasks/test_mapping.mk
@@ -36,3 +36,5 @@
 test_mapping : $(test_mappings_zip)
 
 $(call dist-for-goals, dist_files test_mapping,$(test_mappings_zip))
+
+$(call declare-1p-target,$(test_mappings_zip),)
diff --git a/core/tasks/tools/build_custom_image.mk b/core/tasks/tools/build_custom_image.mk
index 4721591..f9ae2c1 100644
--- a/core/tasks/tools/build_custom_image.mk
+++ b/core/tasks/tools/build_custom_image.mk
@@ -57,7 +57,10 @@
 my_kernel_module_copy_files :=
 my_custom_image_modules_var := BOARD_$(strip $(call to-upper,$(my_custom_image_name)))_KERNEL_MODULES
 ifdef $(my_custom_image_modules_var)
-  my_kernel_module_copy_files += $(call build-image-kernel-modules,$(my_custom_image_modules_var),$(my_staging_dir),$(my_custom_image_name)/,$(call intermediates-dir-for,PACKAGING,depmod_$(my_custom_image_name)))
+$(foreach kmod,\
+  $(call build-image-kernel-modules,$($(my_custom_image_modules_var)),$(my_staging_dir),$(CUSTOM_IMAGE_MOUNT_POINT),$(call intermediates-dir-for,PACKAGING,depmod_$(my_custom_image_name)),$($(my_custom_image_modules_var)),modules.load,,$(call intermediates-dir-for,PACKAGING,depmod_$(my_custom_image_name)_stripped)),\
+  $(eval pair := $(subst :,$(space),$(kmod)))\
+  $(eval my_kernel_module_copy_files += $(word 1,$(pair)):$(subst $(my_staging_dir)/,,$(word 2,$(pair)))))
 endif
 
 # Collect CUSTOM_IMAGE_COPY_FILES.
diff --git a/core/tasks/tools/compatibility.mk b/core/tasks/tools/compatibility.mk
index 7d08a2f..4b8bd16 100644
--- a/core/tasks/tools/compatibility.mk
+++ b/core/tasks/tools/compatibility.mk
@@ -40,6 +40,8 @@
   $(HOST_OUT_EXECUTABLES)/$(test_suite_tradefed) \
   $(test_suite_readme)
 
+$(foreach f,$(test_suite_readme),$(if $(strip $(ALL_TARGETS.$(f).META_LIC)),,$(eval ALL_TARGETS.$(f).META_LIC := $(module_license_metadata))))
+
 test_tools += $(test_suite_tools)
 
 # The JDK to package into the test suite zip file.  Always package the linux JDK.
@@ -51,9 +53,24 @@
 $(test_suite_jdk): $(SOONG_ZIP)
 	$(SOONG_ZIP) -o $@ -P $(PRIVATE_SUBDIR)/jdk -C $(PRIVATE_JDK_DIR) -D $(PRIVATE_JDK_DIR)
 
+$(call declare-license-metadata,$(test_suite_jdk),SPDX-license-identifier-GPL-2.0-with-classpath-exception,restricted,\
+  $(test_suite_jdk_dir)/legal/java.base/LICENSE,JDK,prebuilts/jdk/$(notdir $(patsubst %/,%,$(dir $(test_suite_jdk_dir)))))
+
+
 # Include host shared libraries
 host_shared_libs := $(call copy-many-files, $(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES))
 
+$(if $(strip $(host_shared_libs)),\
+  $(foreach p,$(COMPATIBILITY.$(test_suite_name).HOST_SHARED_LIBRARY.FILES),\
+    $(eval _src := $(call word-colon,1,$(p)))\
+    $(eval _dst := $(call word-colon,2,$(p)))\
+    $(if $(strip $(ALL_TARGETS.$(_src).META_LIC)),\
+      $(eval ALL_TARGETS.$(_dst).META_LIC := $(ALL_TARGETS.$(_src).META_LIC)),\
+      $(warning $(_src) has no license metadata for $(_dst))\
+    )\
+  )\
+)
+
 compatibility_zip_deps := \
   $(test_artifacts) \
   $(test_tools) \
@@ -63,6 +80,7 @@
   $(MERGE_ZIPS) \
   $(SOONG_ZIP) \
   $(host_shared_libs) \
+  $(test_suite_extra_deps) \
 
 compatibility_zip_resources := $(out_dir)/tools $(out_dir)/testcases $(out_dir)/lib $(out_dir)/lib64
 
@@ -70,23 +88,21 @@
 test_suite_notice_txt := $(out_dir)/NOTICE.txt
 test_suite_notice_html := $(out_dir)/NOTICE.html
 
-$(eval $(call combine-notice-files, html, \
-         $(test_suite_notice_txt), \
-         $(test_suite_notice_html), \
-         "Notices for files contained in the test suites filesystem image in this directory:", \
-         $(HOST_OUT_NOTICE_FILES) $(TARGET_OUT_NOTICE_FILES), \
-         $(compatibility_zip_deps)))
-
 compatibility_zip_deps += $(test_suite_notice_txt)
 compatibility_zip_resources += $(test_suite_notice_txt)
 
+compatibility_tests_list_zip := $(out_dir)-tests_list.zip
+
 compatibility_zip := $(out_dir).zip
+$(compatibility_zip) : .KATI_IMPLICIT_OUTPUTS := $(compatibility_tests_list_zip)
 $(compatibility_zip): PRIVATE_OUT_DIR := $(out_dir)
 $(compatibility_zip): PRIVATE_TOOLS := $(test_tools) $(test_suite_prebuilt_tools)
 $(compatibility_zip): PRIVATE_SUITE_NAME := $(test_suite_name)
 $(compatibility_zip): PRIVATE_DYNAMIC_CONFIG := $(test_suite_dynamic_config)
 $(compatibility_zip): PRIVATE_RESOURCES := $(compatibility_zip_resources)
 $(compatibility_zip): PRIVATE_JDK := $(test_suite_jdk)
+$(compatibility_zip): PRIVATE_tests_list := $(out_dir)-tests_list
+$(compatibility_zip): PRIVATE_tests_list_zip := $(compatibility_tests_list_zip)
 $(compatibility_zip): $(compatibility_zip_deps) | $(ADB) $(ACP)
 # Make dir structure
 	mkdir -p $(PRIVATE_OUT_DIR)/tools $(PRIVATE_OUT_DIR)/testcases
@@ -99,6 +115,25 @@
 	$(SOONG_ZIP) -d -o $@.tmp -C $(dir $@) -l $@.list
 	$(MERGE_ZIPS) $@ $@.tmp $(PRIVATE_JDK)
 	rm -f $@.tmp
+# Build a list of tests
+	rm -f $(PRIVATE_tests_list)
+	$(hide) grep -e .*\\.config$$ $@.list | sed s%$(PRIVATE_OUT_DIR)/testcases/%%g > $(PRIVATE_tests_list)
+	$(SOONG_ZIP) -d -o $(PRIVATE_tests_list_zip) -j -f $(PRIVATE_tests_list)
+	rm -f $(PRIVATE_tests_list)
+
+$(call declare-0p-target,$(compatibility_tests_list_zip),)
+
+$(call declare-1p-container,$(compatibility_zip),)
+$(call declare-container-license-deps,$(compatibility_zip),$(compatibility_zip_deps) $(test_suite_jdk), $(out_dir)/:/)
+
+$(eval $(call html-notice-rule,$(test_suite_notice_html),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip)))
+$(eval $(call text-notice-rule,$(test_suite_notice_txt),"Test suites","Notices for files contained in the test suites filesystem image:",$(compatibility_zip),$(compatibility_zip)))
+
+$(call declare-0p-target,$(test_suite_notice_html))
+$(call declare-0p-target,$(test_suite_notice_txt))
+
+$(call declare-1p-copy-files,$(test_suite_dynamic_config),)
+$(call declare-1p-copy-files,$(test_suite_prebuilt_tools),)
 
 # Reset all input variables
 test_suite_name :=
@@ -110,3 +145,4 @@
 test_suite_jdk :=
 test_suite_jdk_dir :=
 host_shared_libs :=
+test_suite_extra_deps :=
diff --git a/core/tasks/tools/package-modules.mk b/core/tasks/tools/package-modules.mk
index 20a1694..f89d51e 100644
--- a/core/tasks/tools/package-modules.mk
+++ b/core/tasks/tools/package-modules.mk
@@ -21,6 +21,8 @@
 LOCAL_MODULE := $(my_package_name)
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
 LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_LICENSE_PACKAGE_NAME := Android
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := PACKAGING
 LOCAL_MODULE_STEM := $(my_package_name).zip
 LOCAL_UNINSTALLABLE_MODULE := true
diff --git a/core/tasks/tools/vts-kernel-tests.mk b/core/tasks/tools/vts-kernel-tests.mk
new file mode 100644
index 0000000..5fbb589
--- /dev/null
+++ b/core/tasks/tools/vts-kernel-tests.mk
@@ -0,0 +1,26 @@
+# 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.
+
+-include external/linux-kselftest/android/kselftest_test_list.mk
+-include external/ltp/android/ltp_package_list.mk
+
+include $(BUILD_SYSTEM)/tasks/tools/vts_package_utils.mk
+
+# Copy kernel test modules to testcases directories
+kernel_test_host_out := $(HOST_OUT_TESTCASES)/vts_kernel_tests
+kernel_test_vts_out := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)/testcases/vts_kernel_tests
+kernel_test_modules := \
+    $(kselftest_modules) \
+    ltp \
+    $(ltp_packages)
\ No newline at end of file
diff --git a/core/tasks/tools/vts_package_utils.mk b/core/tasks/tools/vts_package_utils.mk
index 47bf29c..f1159b3 100644
--- a/core/tasks/tools/vts_package_utils.mk
+++ b/core/tasks/tools/vts_package_utils.mk
@@ -29,5 +29,6 @@
       $(eval my_copy_dest := $(patsubst data/%,DATA/%,\
                                $(patsubst system/%,DATA/%,\
                                    $(patsubst $(PRODUCT_OUT)/%,%,$(ins)))))\
+      $(eval ALL_TARGETS.$(2)/$(my_copy_dest).META_LIC := $(if $(strip $(ALL_MODULES.$(m).META_LIC)),$(ALL_MODULES.$(m).META_LIC),$(ALL_MODULES.$(m).DELAYED_META_LIC)))\
       $(bui):$(2)/$(my_copy_dest))))
 endef
diff --git a/core/tasks/tradefed-tests-list.mk b/core/tasks/tradefed-tests-list.mk
index bcbdfcf..61bf136 100644
--- a/core/tasks/tradefed-tests-list.mk
+++ b/core/tasks/tradefed-tests-list.mk
@@ -35,4 +35,6 @@
 tradefed-tests-list : $(tradefed_tests_list_zip)
 $(call dist-for-goals, tradefed-tests-list, $(tradefed_tests_list_zip))
 
+$(call declare-1p-target,$(tradefed_tests_list_zip),)
+
 tests: tradefed-tests-list
diff --git a/core/tasks/vts-core-tests.mk b/core/tasks/vts-core-tests.mk
index 95c4d24..5e1b5d5 100644
--- a/core/tasks/vts-core-tests.mk
+++ b/core/tasks/vts-core-tests.mk
@@ -12,39 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
--include external/linux-kselftest/android/kselftest_test_list.mk
--include external/ltp/android/ltp_package_list.mk
-
-include $(BUILD_SYSTEM)/tasks/tools/vts_package_utils.mk
-
 test_suite_name := vts
 test_suite_tradefed := vts-tradefed
 test_suite_readme := test/vts/tools/vts-core-tradefed/README
 
-# Copy kernel test modules to testcases directories
-kernel_test_host_out := $(HOST_OUT_TESTCASES)/vts_kernel_tests
-kernel_test_vts_out := $(HOST_OUT)/$(test_suite_name)/android-$(test_suite_name)/testcases/vts_kernel_tests
-kernel_test_modules := \
-    $(kselftest_modules) \
-    ltp \
-    $(ltp_packages)
+include $(BUILD_SYSTEM)/tasks/tools/vts-kernel-tests.mk
 
 kernel_test_copy_pairs := \
-  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_vts_out)) \
-  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_host_out))
+  $(call target-native-copy-pairs,$(kernel_test_modules),$(kernel_test_vts_out))
 
 copy_kernel_tests := $(call copy-many-files,$(kernel_test_copy_pairs))
 
-# PHONY target to be used to build and test `vts_kernel_tests` without building full vts
-.PHONY: vts_kernel_tests
-vts_kernel_tests: $(copy_kernel_tests)
+test_suite_extra_deps := $(copy_kernel_tests)
 
 include $(BUILD_SYSTEM)/tasks/tools/compatibility.mk
 
-$(compatibility_zip): $(copy_kernel_tests)
-
 .PHONY: vts
-vts: $(compatibility_zip)
-$(call dist-for-goals, vts, $(compatibility_zip))
+vts: $(compatibility_zip) $(compatibility_tests_list_zip)
+$(call dist-for-goals, vts, $(compatibility_zip) $(compatibility_tests_list_zip))
 
 tests: vts
diff --git a/core/tasks/with-license.mk b/core/tasks/with-license.mk
index 469ad76..d41e77a 100644
--- a/core/tasks/with-license.mk
+++ b/core/tasks/with-license.mk
@@ -37,6 +37,10 @@
 		RADIO/bootloader.img:bootloader.img RADIO/radio.img:radio.img \
 		IMAGES/*.img:. OTA/android-info.txt:android-info.txt
 endif
+
+$(call declare-1p-container,$(license_image_input_zip),build)
+$(call declare-container-deps,$(license_image_input_zip),$(BUILT_TARGET_FILES_PACKAGE))
+
 with_license_zip := $(PRODUCT_OUT)/$(name).sh
 $(with_license_zip): PRIVATE_NAME := $(name)
 $(with_license_zip): PRIVATE_INPUT_ZIP := $(license_image_input_zip)
@@ -48,3 +52,7 @@
 		$(PRIVATE_INPUT_ZIP) $(PRIVATE_NAME) $(PRIVATE_VENDOR_BLOBS_LICENSE)
 with-license : $(with_license_zip)
 $(call dist-for-goals, with-license, $(with_license_zip))
+
+$(call declare-1p-container,$(with_license_zip),)
+$(call declare-container-license-deps,$(with_license_zip),$(license_image_input_zip),$(with_license_zip):)
+
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 85e3730..0884e85 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -19,6 +19,7 @@
 #
 # Guarantees that the following are defined:
 #     PLATFORM_VERSION
+#     PLATFORM_DISPLAY_VERSION
 #     PLATFORM_SDK_VERSION
 #     PLATFORM_VERSION_CODENAME
 #     DEFAULT_APP_TARGET_SDK
@@ -39,104 +40,25 @@
   include $(INTERNAL_BUILD_ID_MAKEFILE)
 endif
 
-DEFAULT_PLATFORM_VERSION := SP1A
-MIN_PLATFORM_VERSION := SP1A
-MAX_PLATFORM_VERSION := SP1A
-
-ALLOWED_VERSIONS := $(call allowed-platform-versions,\
-  $(MIN_PLATFORM_VERSION),\
-  $(MAX_PLATFORM_VERSION),\
-  $(DEFAULT_PLATFORM_VERSION))
-
-ifndef TARGET_PLATFORM_VERSION
-  TARGET_PLATFORM_VERSION := $(DEFAULT_PLATFORM_VERSION)
-endif
-
-ifeq (,$(filter $(ALLOWED_VERSIONS), $(TARGET_PLATFORM_VERSION)))
-  $(warning Invalid TARGET_PLATFORM_VERSION '$(TARGET_PLATFORM_VERSION)', must be one of)
-  $(error $(ALLOWED_VERSIONS))
-endif
-ALLOWED_VERSIONS :=
-MIN_PLATFORM_VERSION :=
-MAX_PLATFORM_VERSION :=
-
-.KATI_READONLY := \
-  DEFAULT_PLATFORM_VERSION \
-  TARGET_PLATFORM_VERSION
-
-# Default versions for each TARGET_PLATFORM_VERSION
-# TODO: PLATFORM_VERSION, PLATFORM_SDK_VERSION, etc. should be conditional
-# on this
-
-# This is the canonical definition of the platform version,
-# which is the version that we reveal to the end user.
-# Update this value when the platform version changes (rather
-# than overriding it somewhere else).  Can be an arbitrary string.
-
-# When you change PLATFORM_VERSION for a given PLATFORM_SDK_VERSION
-# please add that PLATFORM_VERSION as well as clean up obsolete PLATFORM_VERSION's
-# in the following text file:
-# cts/tests/tests/os/assets/platform_versions.txt
-
-# Note that there should be one PLATFORM_VERSION and PLATFORM_VERSION_CODENAME
-# entry for each unreleased API level, regardless of
-# MIN_PLATFORM_VERSION/MAX_PLATFORM_VERSION. PLATFORM_VERSION is used to
-# generate the range of allowed SDK versions, so it must have an entry for every
-# unreleased API level targetable by this branch, not just those that are valid
-# lunch targets for this branch.
+DEFAULT_PLATFORM_VERSION := TP1A
+.KATI_READONLY := DEFAULT_PLATFORM_VERSION
+MIN_PLATFORM_VERSION := TP1A
+MAX_PLATFORM_VERSION := TP1A
 
 # The last stable version name of the platform that was released.  During
 # development, this stays at that previous version, while the codename indicates
 # further work based on the previous version.
-PLATFORM_VERSION_LAST_STABLE := 12
+PLATFORM_VERSION_LAST_STABLE := 13
 .KATI_READONLY := PLATFORM_VERSION_LAST_STABLE
 
 # These are the current development codenames, if the build is not a final
 # release build.  If this is a final release build, it is simply "REL".
-PLATFORM_VERSION_CODENAME.SP1A := REL
+PLATFORM_VERSION_CODENAME.TP1A := REL
 
-ifndef PLATFORM_VERSION_CODENAME
-  PLATFORM_VERSION_CODENAME := $(PLATFORM_VERSION_CODENAME.$(TARGET_PLATFORM_VERSION))
-  ifndef PLATFORM_VERSION_CODENAME
-    # PLATFORM_VERSION_CODENAME falls back to TARGET_PLATFORM_VERSION
-    PLATFORM_VERSION_CODENAME := $(TARGET_PLATFORM_VERSION)
-  endif
-
-  # This is all of the *active* development codenames.
-  # This confusing name is needed because
-  # all_codenames has been baked into build.prop for ages.
-  #
-  # Should be either the same as PLATFORM_VERSION_CODENAME or a comma-separated
-  # list of additional codenames after PLATFORM_VERSION_CODENAME.
-  PLATFORM_VERSION_ALL_CODENAMES :=
-
-  # Build a list of all active code names. Avoid duplicates, and stop when we
-  # reach a codename that matches PLATFORM_VERSION_CODENAME (anything beyond
-  # that is not included in our build).
-  _versions_in_target := \
-    $(call find_and_earlier,$(ALL_VERSIONS),$(TARGET_PLATFORM_VERSION))
-  $(foreach version,$(_versions_in_target),\
-    $(eval _codename := $(PLATFORM_VERSION_CODENAME.$(version)))\
-    $(if $(filter $(_codename),$(PLATFORM_VERSION_ALL_CODENAMES)),,\
-      $(eval PLATFORM_VERSION_ALL_CODENAMES += $(_codename))))
-
-  # And convert from space separated to comma separated.
-  PLATFORM_VERSION_ALL_CODENAMES := \
-    $(subst $(space),$(comma),$(strip $(PLATFORM_VERSION_ALL_CODENAMES)))
-
-endif
-.KATI_READONLY := \
-  PLATFORM_VERSION_CODENAME \
-  PLATFORM_VERSION_ALL_CODENAMES
-
-ifndef PLATFORM_VERSION
-  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-      PLATFORM_VERSION := $(PLATFORM_VERSION_LAST_STABLE)
-  else
-      PLATFORM_VERSION := $(PLATFORM_VERSION_CODENAME)
-  endif
-endif
-.KATI_READONLY := PLATFORM_VERSION
+# This is the user-visible version.  In a final release build it should
+# be empty to use PLATFORM_VERSION as the user-visible version.  For
+# a preview release it can be set to a user-friendly value like `12 Preview 1`
+PLATFORM_DISPLAY_VERSION := 13
 
 ifndef PLATFORM_SDK_VERSION
   # This is the canonical definition of the SDK version, which defines
@@ -151,95 +73,29 @@
   # When you increment the PLATFORM_SDK_VERSION please ensure you also
   # clear out the following text file of all older PLATFORM_VERSION's:
   # cts/tests/tests/os/assets/platform_versions.txt
-  PLATFORM_SDK_VERSION := 31
+  PLATFORM_SDK_VERSION := 33
 endif
 .KATI_READONLY := PLATFORM_SDK_VERSION
 
 # This is the sdk extension version of this tree.
-PLATFORM_SDK_EXTENSION_VERSION := 2
+PLATFORM_SDK_EXTENSION_VERSION := 3
 .KATI_READONLY := PLATFORM_SDK_EXTENSION_VERSION
 
 # This is the sdk extension version that PLATFORM_SDK_VERSION ships with.
-PLATFORM_BASE_SDK_EXTENSION_VERSION :=$= 0
+PLATFORM_BASE_SDK_EXTENSION_VERSION := 3
+.KATI_READONLY := PLATFORM_BASE_SDK_EXTENSION_VERSION
 
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-  PLATFORM_PREVIEW_SDK_VERSION := 0
-else
-  ifndef PLATFORM_PREVIEW_SDK_VERSION
-    # This is the definition of a preview SDK version over and above the current
-    # platform SDK version. Unlike the platform SDK version, a higher value
-    # for preview SDK version does NOT mean that all prior preview APIs are
-    # included. Packages reading this value to determine compatibility with
-    # known APIs should check that this value is precisely equal to the preview
-    # SDK version the package was built for, otherwise it should fall back to
-    # assuming the device can only support APIs as of the previous official
-    # public release.
-    # This value will always be forced to 0 for release builds by the logic
-    # in the "ifeq" block above, so the value below will be used on any
-    # non-release builds, and it should always be at least 1, to indicate that
-    # APIs may have changed since the claimed PLATFORM_SDK_VERSION.
-    PLATFORM_PREVIEW_SDK_VERSION := 1
-  endif
-endif
-.KATI_READONLY := PLATFORM_PREVIEW_SDK_VERSION
+# This are all known codenames.
+PLATFORM_VERSION_KNOWN_CODENAMES := \
+Base Base11 Cupcake Donut Eclair Eclair01 EclairMr1 Froyo Gingerbread GingerbreadMr1 \
+Honeycomb HoneycombMr1 HoneycombMr2 IceCreamSandwich IceCreamSandwichMr1 \
+JellyBean JellyBeanMr1 JellyBeanMr2 Kitkat KitkatWatch Lollipop LollipopMr1 M N NMr1 O OMr1 P \
+Q R S Sv2 Tiramisu
 
-ifndef DEFAULT_APP_TARGET_SDK
-  # This is the default minSdkVersion and targetSdkVersion to use for
-  # all .apks created by the build system.  It can be overridden by explicitly
-  # setting these in the .apk's AndroidManifest.xml.  It is either the code
-  # name of the development build or, if this is a release build, the official
-  # SDK version of this release.
-  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-    DEFAULT_APP_TARGET_SDK := $(PLATFORM_SDK_VERSION)
-  else
-    DEFAULT_APP_TARGET_SDK := $(PLATFORM_VERSION_CODENAME)
-  endif
-endif
-.KATI_READONLY := DEFAULT_APP_TARGET_SDK
-
-ifndef PLATFORM_VNDK_VERSION
-  # This is the definition of the VNDK version for the current VNDK libraries.
-  # The version is only available when PLATFORM_VERSION_CODENAME == REL.
-  # Otherwise, it will be set to a CODENAME version. The ABI is allowed to be
-  # changed only before the Android version is released. Once
-  # PLATFORM_VNDK_VERSION is set to actual version, the ABI for this version
-  # will be frozon and emit build errors if any ABI for the VNDK libs are
-  # changed.
-  # After that the snapshot of the VNDK with this version will be generated.
-  #
-  # The VNDK version follows PLATFORM_SDK_VERSION.
-  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-    PLATFORM_VNDK_VERSION := $(PLATFORM_SDK_VERSION)
-  else
-    PLATFORM_VNDK_VERSION := $(PLATFORM_VERSION_CODENAME)
-  endif
-endif
-.KATI_READONLY := PLATFORM_VNDK_VERSION
-
-ifndef PLATFORM_SYSTEMSDK_MIN_VERSION
-  # This is the oldest version of system SDK that the platform supports. Contrary
-  # to the public SDK where platform essentially supports all previous SDK versions,
-  # platform supports only a few number of recent system SDK versions as some of
-  # old system APIs are gradually deprecated, removed and then deleted.
-  PLATFORM_SYSTEMSDK_MIN_VERSION := 28
-endif
-.KATI_READONLY := PLATFORM_SYSTEMSDK_MIN_VERSION
-
-# This is the list of system SDK versions that the current platform supports.
-PLATFORM_SYSTEMSDK_VERSIONS :=
-ifneq (,$(PLATFORM_SYSTEMSDK_MIN_VERSION))
-  $(if $(call math_is_number,$(PLATFORM_SYSTEMSDK_MIN_VERSION)),,\
-    $(error PLATFORM_SYSTEMSDK_MIN_VERSION must be a number, but was $(PLATFORM_SYSTEMSDK_MIN_VERSION)))
-  PLATFORM_SYSTEMSDK_VERSIONS := $(call int_range_list,$(PLATFORM_SYSTEMSDK_MIN_VERSION),$(PLATFORM_SDK_VERSION))
-endif
-# Platform always supports the current version
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-  PLATFORM_SYSTEMSDK_VERSIONS += $(PLATFORM_SDK_VERSION)
-else
-  PLATFORM_SYSTEMSDK_VERSIONS += $(PLATFORM_VERSION_CODENAME)
-endif
-PLATFORM_SYSTEMSDK_VERSIONS := $(strip $(sort $(PLATFORM_SYSTEMSDK_VERSIONS)))
-.KATI_READONLY := PLATFORM_SYSTEMSDK_VERSIONS
+# Convert from space separated list to comma separated
+PLATFORM_VERSION_KNOWN_CODENAMES := \
+  $(call normalize-comma-list,$(PLATFORM_VERSION_KNOWN_CODENAMES))
+.KATI_READONLY := PLATFORM_VERSION_KNOWN_CODENAMES
 
 ifndef PLATFORM_SECURITY_PATCH
     #  Used to indicate the security patch that has been applied to the device.
@@ -247,69 +103,7 @@
     #  It must be of the form "YYYY-MM-DD" on production devices.
     #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
     #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-      PLATFORM_SECURITY_PATCH := 2022-11-01
-endif
-.KATI_READONLY := PLATFORM_SECURITY_PATCH
-
-ifndef PLATFORM_SECURITY_PATCH_TIMESTAMP
-  # Used to indicate the matching timestamp for the security patch string in PLATFORM_SECURITY_PATCH.
-  PLATFORM_SECURITY_PATCH_TIMESTAMP := $(shell date -d 'TZ="GMT" $(PLATFORM_SECURITY_PATCH)' +%s)
-endif
-.KATI_READONLY := PLATFORM_SECURITY_PATCH_TIMESTAMP
-
-ifndef PLATFORM_BASE_OS
-  # Used to indicate the base os applied to the device.
-  # Can be an arbitrary string, but must be a single word.
-  #
-  # If there is no $PLATFORM_BASE_OS set, keep it empty.
-  PLATFORM_BASE_OS :=
-endif
-.KATI_READONLY := PLATFORM_BASE_OS
-
-ifndef BUILD_ID
-  # Used to signify special builds.  E.g., branches and/or releases,
-  # like "M5-RC7".  Can be an arbitrary string, but must be a single
-  # word and a valid file name.
-  #
-  # If there is no BUILD_ID set, make it obvious.
-  BUILD_ID := UNKNOWN
-endif
-.KATI_READONLY := BUILD_ID
-
-ifndef BUILD_DATETIME
-  # Used to reproduce builds by setting the same time. Must be the number
-  # of seconds since the Epoch.
-  BUILD_DATETIME := $(shell date +%s)
+    PLATFORM_SECURITY_PATCH := 2022-11-05
 endif
 
-DATE := date -d @$(BUILD_DATETIME)
-.KATI_READONLY := DATE
-
-# Everything should be using BUILD_DATETIME_FROM_FILE instead.
-# BUILD_DATETIME and DATE can be removed once BUILD_NUMBER moves
-# to soong_ui.
-$(KATI_obsolete_var BUILD_DATETIME,Use BUILD_DATETIME_FROM_FILE)
-
-HAS_BUILD_NUMBER := true
-ifndef BUILD_NUMBER
-  # BUILD_NUMBER should be set to the source control value that
-  # represents the current state of the source code.  E.g., a
-  # perforce changelist number or a git hash.  Can be an arbitrary string
-  # (to allow for source control that uses something other than numbers),
-  # but must be a single word and a valid file name.
-  #
-  # If no BUILD_NUMBER is set, create a useful "I am an engineering build
-  # from this date/time" value.  Make it start with a non-digit so that
-  # anyone trying to parse it as an integer will probably get "0".
-  BUILD_NUMBER := eng.$(shell echo $${BUILD_USERNAME:0:6}).$(shell $(DATE) +%Y%m%d.%H%M%S)
-  HAS_BUILD_NUMBER := false
-endif
-.KATI_READONLY := BUILD_NUMBER HAS_BUILD_NUMBER
-
-ifndef PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION
-  # Used to set minimum supported target sdk version. Apps targeting sdk
-  # version lower than the set value will result in a warning being shown
-  # when any activity from the app is started.
-  PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 23
-endif
-.KATI_READONLY := PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION
+include $(BUILD_SYSTEM)/version_util.mk
diff --git a/core/version_util.mk b/core/version_util.mk
new file mode 100644
index 0000000..3a0d4b5
--- /dev/null
+++ b/core/version_util.mk
@@ -0,0 +1,258 @@
+#
+# Copyright (C) 2008 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.
+#
+
+#
+
+ALLOWED_VERSIONS := $(call allowed-platform-versions,\
+  $(MIN_PLATFORM_VERSION),\
+  $(MAX_PLATFORM_VERSION),\
+  $(DEFAULT_PLATFORM_VERSION))
+
+ifndef TARGET_PLATFORM_VERSION
+  TARGET_PLATFORM_VERSION := $(DEFAULT_PLATFORM_VERSION)
+endif
+
+ifeq (,$(filter $(ALLOWED_VERSIONS), $(TARGET_PLATFORM_VERSION)))
+  $(warning Invalid TARGET_PLATFORM_VERSION '$(TARGET_PLATFORM_VERSION)', must be one of)
+  $(error $(ALLOWED_VERSIONS))
+endif
+ALLOWED_VERSIONS :=
+MIN_PLATFORM_VERSION :=
+MAX_PLATFORM_VERSION :=
+
+.KATI_READONLY := TARGET_PLATFORM_VERSION
+
+# Default versions for each TARGET_PLATFORM_VERSION
+# TODO: PLATFORM_VERSION, PLATFORM_SDK_VERSION, etc. should be conditional
+# on this
+
+# This is the canonical definition of the platform version,
+# which is the version that we reveal to the end user.
+# Update this value when the platform version changes (rather
+# than overriding it somewhere else).  Can be an arbitrary string.
+
+# When you change PLATFORM_VERSION for a given PLATFORM_SDK_VERSION
+# please add that PLATFORM_VERSION as well as clean up obsolete PLATFORM_VERSION's
+# in the following text file:
+# cts/tests/tests/os/assets/platform_versions.txt
+
+# Note that there should be one PLATFORM_VERSION and PLATFORM_VERSION_CODENAME
+# entry for each unreleased API level, regardless of
+# MIN_PLATFORM_VERSION/MAX_PLATFORM_VERSION. PLATFORM_VERSION is used to
+# generate the range of allowed SDK versions, so it must have an entry for every
+# unreleased API level targetable by this branch, not just those that are valid
+# lunch targets for this branch.
+
+ifndef PLATFORM_VERSION_CODENAME
+  PLATFORM_VERSION_CODENAME := $(PLATFORM_VERSION_CODENAME.$(TARGET_PLATFORM_VERSION))
+  ifndef PLATFORM_VERSION_CODENAME
+    # PLATFORM_VERSION_CODENAME falls back to TARGET_PLATFORM_VERSION
+    PLATFORM_VERSION_CODENAME := $(TARGET_PLATFORM_VERSION)
+  endif
+
+  # This is all of the *active* development codenames.
+  # This confusing name is needed because
+  # all_codenames has been baked into build.prop for ages.
+  #
+  # Should be either the same as PLATFORM_VERSION_CODENAME or a comma-separated
+  # list of additional codenames after PLATFORM_VERSION_CODENAME.
+  PLATFORM_VERSION_ALL_CODENAMES :=
+
+  # Build a list of all active code names. Avoid duplicates, and stop when we
+  # reach a codename that matches PLATFORM_VERSION_CODENAME (anything beyond
+  # that is not included in our build).
+  _versions_in_target := \
+    $(call find_and_earlier,$(ALL_VERSIONS),$(TARGET_PLATFORM_VERSION))
+  $(foreach version,$(_versions_in_target),\
+    $(eval _codename := $(PLATFORM_VERSION_CODENAME.$(version)))\
+    $(if $(filter $(_codename),$(PLATFORM_VERSION_ALL_CODENAMES)),,\
+      $(eval PLATFORM_VERSION_ALL_CODENAMES += $(_codename))))
+
+  # And convert from space separated to comma separated.
+  PLATFORM_VERSION_ALL_CODENAMES := \
+    $(subst $(space),$(comma),$(strip $(PLATFORM_VERSION_ALL_CODENAMES)))
+
+endif
+.KATI_READONLY := \
+  PLATFORM_VERSION_CODENAME \
+  PLATFORM_VERSION_ALL_CODENAMES
+
+ifneq (REL,$(PLATFORM_VERSION_CODENAME))
+  codenames := \
+    $(subst $(comma),$(space),$(strip $(PLATFORM_VERSION_KNOWN_CODENAMES)))
+  ifeq ($(filter $(PLATFORM_VERSION_CODENAME),$(codenames)),)
+    $(error '$(PLATFORM_VERSION_CODENAME)' is not in '$(codenames)'. \
+        Add PLATFORM_VERSION_CODENAME to PLATFORM_VERSION_KNOWN_CODENAMES)
+  endif
+endif
+
+ifndef PLATFORM_VERSION
+  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+      PLATFORM_VERSION := $(PLATFORM_VERSION_LAST_STABLE)
+  else
+      PLATFORM_VERSION := $(PLATFORM_VERSION_CODENAME)
+  endif
+endif
+.KATI_READONLY := PLATFORM_VERSION
+
+ifndef PLATFORM_DISPLAY_VERSION
+  PLATFORM_DISPLAY_VERSION := $(PLATFORM_VERSION)
+endif
+.KATI_READONLY := PLATFORM_DISPLAY_VERSION
+
+ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+  PLATFORM_PREVIEW_SDK_VERSION := 0
+else
+  ifndef PLATFORM_PREVIEW_SDK_VERSION
+    # This is the definition of a preview SDK version over and above the current
+    # platform SDK version. Unlike the platform SDK version, a higher value
+    # for preview SDK version does NOT mean that all prior preview APIs are
+    # included. Packages reading this value to determine compatibility with
+    # known APIs should check that this value is precisely equal to the preview
+    # SDK version the package was built for, otherwise it should fall back to
+    # assuming the device can only support APIs as of the previous official
+    # public release.
+    # This value will always be forced to 0 for release builds by the logic
+    # in the "ifeq" block above, so the value below will be used on any
+    # non-release builds, and it should always be at least 1, to indicate that
+    # APIs may have changed since the claimed PLATFORM_SDK_VERSION.
+    PLATFORM_PREVIEW_SDK_VERSION := 1
+  endif
+endif
+.KATI_READONLY := PLATFORM_PREVIEW_SDK_VERSION
+
+ifndef DEFAULT_APP_TARGET_SDK
+  # This is the default minSdkVersion and targetSdkVersion to use for
+  # all .apks created by the build system.  It can be overridden by explicitly
+  # setting these in the .apk's AndroidManifest.xml.  It is either the code
+  # name of the development build or, if this is a release build, the official
+  # SDK version of this release.
+  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+    DEFAULT_APP_TARGET_SDK := $(PLATFORM_SDK_VERSION)
+  else
+    DEFAULT_APP_TARGET_SDK := $(PLATFORM_VERSION_CODENAME)
+  endif
+endif
+.KATI_READONLY := DEFAULT_APP_TARGET_SDK
+
+ifndef PLATFORM_VNDK_VERSION
+  # This is the definition of the VNDK version for the current VNDK libraries.
+  # The version is only available when PLATFORM_VERSION_CODENAME == REL.
+  # Otherwise, it will be set to a CODENAME version. The ABI is allowed to be
+  # changed only before the Android version is released. Once
+  # PLATFORM_VNDK_VERSION is set to actual version, the ABI for this version
+  # will be frozon and emit build errors if any ABI for the VNDK libs are
+  # changed.
+  # After that the snapshot of the VNDK with this version will be generated.
+  #
+  # The VNDK version follows PLATFORM_SDK_VERSION.
+  ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+    PLATFORM_VNDK_VERSION := $(PLATFORM_SDK_VERSION)
+  else
+    PLATFORM_VNDK_VERSION := $(PLATFORM_VERSION_CODENAME)
+  endif
+endif
+.KATI_READONLY := PLATFORM_VNDK_VERSION
+
+ifndef PLATFORM_SYSTEMSDK_MIN_VERSION
+  # This is the oldest version of system SDK that the platform supports. Contrary
+  # to the public SDK where platform essentially supports all previous SDK versions,
+  # platform supports only a few number of recent system SDK versions as some of
+  # old system APIs are gradually deprecated, removed and then deleted.
+  PLATFORM_SYSTEMSDK_MIN_VERSION := 28
+endif
+.KATI_READONLY := PLATFORM_SYSTEMSDK_MIN_VERSION
+
+# This is the list of system SDK versions that the current platform supports.
+PLATFORM_SYSTEMSDK_VERSIONS :=
+ifneq (,$(PLATFORM_SYSTEMSDK_MIN_VERSION))
+  $(if $(call math_is_number,$(PLATFORM_SYSTEMSDK_MIN_VERSION)),,\
+    $(error PLATFORM_SYSTEMSDK_MIN_VERSION must be a number, but was $(PLATFORM_SYSTEMSDK_MIN_VERSION)))
+  PLATFORM_SYSTEMSDK_VERSIONS := $(call int_range_list,$(PLATFORM_SYSTEMSDK_MIN_VERSION),$(PLATFORM_SDK_VERSION))
+endif
+# Platform always supports the current version
+ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+  PLATFORM_SYSTEMSDK_VERSIONS += $(PLATFORM_SDK_VERSION)
+else
+  PLATFORM_SYSTEMSDK_VERSIONS += $(subst $(comma),$(space),$(PLATFORM_VERSION_ALL_CODENAMES))
+endif
+PLATFORM_SYSTEMSDK_VERSIONS := $(strip $(sort $(PLATFORM_SYSTEMSDK_VERSIONS)))
+.KATI_READONLY := PLATFORM_SYSTEMSDK_VERSIONS
+
+.KATI_READONLY := PLATFORM_SECURITY_PATCH
+
+ifndef PLATFORM_SECURITY_PATCH_TIMESTAMP
+  # Used to indicate the matching timestamp for the security patch string in PLATFORM_SECURITY_PATCH.
+  PLATFORM_SECURITY_PATCH_TIMESTAMP := $(shell date -d 'TZ="GMT" $(PLATFORM_SECURITY_PATCH)' +%s)
+endif
+.KATI_READONLY := PLATFORM_SECURITY_PATCH_TIMESTAMP
+
+ifndef PLATFORM_BASE_OS
+  # Used to indicate the base os applied to the device.
+  # Can be an arbitrary string, but must be a single word.
+  #
+  # If there is no $PLATFORM_BASE_OS set, keep it empty.
+  PLATFORM_BASE_OS :=
+endif
+.KATI_READONLY := PLATFORM_BASE_OS
+
+ifndef BUILD_ID
+  # Used to signify special builds.  E.g., branches and/or releases,
+  # like "M5-RC7".  Can be an arbitrary string, but must be a single
+  # word and a valid file name.
+  #
+  # If there is no BUILD_ID set, make it obvious.
+  BUILD_ID := UNKNOWN
+endif
+.KATI_READONLY := BUILD_ID
+
+ifndef BUILD_DATETIME
+  # Used to reproduce builds by setting the same time. Must be the number
+  # of seconds since the Epoch.
+  BUILD_DATETIME := $(shell date +%s)
+endif
+
+DATE := date -d @$(BUILD_DATETIME)
+.KATI_READONLY := DATE
+
+# Everything should be using BUILD_DATETIME_FROM_FILE instead.
+# BUILD_DATETIME and DATE can be removed once BUILD_NUMBER moves
+# to soong_ui.
+$(KATI_obsolete_var BUILD_DATETIME,Use BUILD_DATETIME_FROM_FILE)
+
+HAS_BUILD_NUMBER := true
+ifndef BUILD_NUMBER
+  # BUILD_NUMBER should be set to the source control value that
+  # represents the current state of the source code.  E.g., a
+  # perforce changelist number or a git hash.  Can be an arbitrary string
+  # (to allow for source control that uses something other than numbers),
+  # but must be a single word and a valid file name.
+  #
+  # If no BUILD_NUMBER is set, create a useful "I am an engineering build
+  # from this date/time" value.  Make it start with a non-digit so that
+  # anyone trying to parse it as an integer will probably get "0".
+  BUILD_NUMBER := eng.$(shell echo $${BUILD_USERNAME:0:6}).$(shell $(DATE) +%Y%m%d.%H%M%S)
+  HAS_BUILD_NUMBER := false
+endif
+.KATI_READONLY := BUILD_NUMBER HAS_BUILD_NUMBER
+
+ifndef PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION
+  # Used to set minimum supported target sdk version. Apps targeting sdk
+  # version lower than the set value will result in a warning being shown
+  # when any activity from the app is started.
+  PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 23
+endif
+.KATI_READONLY := PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION
diff --git a/envsetup.sh b/envsetup.sh
index 8a995c7..be6061d 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -240,7 +240,7 @@
         export ANDROID_TOOLCHAIN_2ND_ARCH=$gccprebuiltdir/$toolchaindir2
     fi
 
-    export ANDROID_DEV_SCRIPTS=$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin
+    export ANDROID_DEV_SCRIPTS=$T/development/scripts:$T/prebuilts/devtools/tools
 
     # add kernel specific binaries
     case $(uname -s) in
@@ -252,9 +252,7 @@
     esac
 
     ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN
-    if [ -n "$ANDROID_TOOLCHAIN_2ND_ARCH" ]; then
-        ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCH
-    fi
+    ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCH
     ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_DEV_SCRIPTS
 
     # Append llvm binutils prebuilts path to ANDROID_BUILD_PATHS.
@@ -287,8 +285,9 @@
     local ACLOUD_PATH="$T/prebuilts/asuite/acloud/$os_arch"
     local AIDEGEN_PATH="$T/prebuilts/asuite/aidegen/$os_arch"
     local ATEST_PATH="$T/prebuilts/asuite/atest/$os_arch"
-    export ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH:
+    ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH
 
+    export ANDROID_BUILD_PATHS=$(tr -s : <<<"${ANDROID_BUILD_PATHS}:")
     export PATH=$ANDROID_BUILD_PATHS$PATH
 
     # out with the duplicate old
@@ -331,15 +330,15 @@
 
 function bazel()
 {
-    local T="$(gettop)"
-    if [ ! "$T" ]; then
-        echo "Couldn't locate the top of the tree.  Try setting TOP."
-        return
+    if which bazel &>/dev/null; then
+        >&2 echo "NOTE: bazel() function sourced from Android's envsetup.sh is being used instead of $(which bazel)"
+        >&2 echo
     fi
 
-    if which bazel &>/dev/null; then
-        >&2 echo "NOTE: bazel() function sourced from envsetup.sh is being used instead of $(which bazel)"
-        >&2 echo
+    local T="$(gettop)"
+    if [ ! "$T" ]; then
+        >&2 echo "Couldn't locate the top of the Android tree. Try setting TOP. This bazel() function cannot be used outside of the AOSP directory."
+        return
     fi
 
     "$T/tools/bazel" "$@"
@@ -426,6 +425,61 @@
     complete -F _complete_android_module_names m
 }
 
+function multitree_lunch_help()
+{
+    echo "usage: lunch PRODUCT-VARIANT" 1>&2
+    echo "    Set up android build environment based on a product short name and variant" 1>&2
+    echo 1>&2
+    echo "lunch COMBO_FILE VARIANT" 1>&2
+    echo "    Set up android build environment based on a specific lunch combo file" 1>&2
+    echo "    and variant." 1>&2
+    echo 1>&2
+    echo "lunch --print [CONFIG]" 1>&2
+    echo "    Print the contents of a configuration.  If CONFIG is supplied, that config" 1>&2
+    echo "    will be flattened and printed.  If CONFIG is not supplied, the currently" 1>&2
+    echo "    selected config will be printed.  Returns 0 on success or nonzero on error." 1>&2
+    echo 1>&2
+    echo "lunch --list" 1>&2
+    echo "    List all possible combo files available in the current tree" 1>&2
+    echo 1>&2
+    echo "lunch --help" 1>&2
+    echo "lunch -h" 1>&2
+    echo "    Prints this message." 1>&2
+}
+
+function multitree_lunch()
+{
+    local code
+    local results
+    if $(echo "$1" | grep -q '^-') ; then
+        # Calls starting with a -- argument are passed directly and the function
+        # returns with the lunch.py exit code.
+        build/make/orchestrator/core/lunch.py "$@"
+        code=$?
+        if [[ $code -eq 2 ]] ; then
+          echo 1>&2
+          multitree_lunch_help
+          return $code
+        elif [[ $code -ne 0 ]] ; then
+          return $code
+        fi
+    else
+        # All other calls go through the --lunch variant of lunch.py
+        results=($(build/make/orchestrator/core/lunch.py --lunch "$@"))
+        code=$?
+        if [[ $code -eq 2 ]] ; then
+          echo 1>&2
+          multitree_lunch_help
+          return $code
+        elif [[ $code -ne 0 ]] ; then
+          return $code
+        fi
+
+        export TARGET_BUILD_COMBO=${results[0]}
+        export TARGET_BUILD_VARIANT=${results[1]}
+    fi
+}
+
 function choosetype()
 {
     echo "Build type choices are:"
@@ -626,7 +680,7 @@
         return
     fi
 
-    echo "Lunch menu... pick a combo:"
+    echo "Lunch menu .. Here are the common combinations:"
 
     local i=1
     local choice
@@ -648,12 +702,16 @@
         return 1
     fi
 
+    local used_lunch_menu=0
+
     if [ "$1" ]; then
         answer=$1
     else
         print_lunch_menu
-        echo -n "Which would you like? [aosp_arm-eng] "
+        echo "Which would you like? [aosp_arm-eng]"
+        echo -n "Pick from common choices above (e.g. 13) or specify your own (e.g. aosp_barbet-eng): "
         read answer
+        used_lunch_menu=1
     fi
 
     local selection=
@@ -703,6 +761,10 @@
     build_build_var_cache
     if [ $? -ne 0 ]
     then
+        if [[ "$product" =~ .*_(eng|user|userdebug) ]]
+        then
+            echo "Did you mean -${product/*_/}? (dash instead of underscore)"
+        fi
         return 1
     fi
     export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
@@ -714,11 +776,20 @@
     fi
     export TARGET_BUILD_TYPE=release
 
+    if [ $used_lunch_menu -eq 1 ]; then
+      echo
+      echo "Hint: next time you can simply run 'lunch $selection'"
+    fi
+
     [[ -n "${ANDROID_QUIET_BUILD:-}" ]] || echo
 
     set_stuff_for_environment
     [[ -n "${ANDROID_QUIET_BUILD:-}" ]] || printconfig
     destroy_build_var_cache
+
+    if [[ -n "${CHECK_MU_CONFIG:-}" ]]; then
+      check_mu_config
+    fi
 }
 
 unset COMMON_LUNCH_CHOICES_CACHE
@@ -746,7 +817,9 @@
     local arch="$(echo $* | xargs -n 1 echo | \grep -E '^(arm|x86|arm64|x86_64)$' | xargs)"
     local variant="$(echo $* | xargs -n 1 echo | \grep -E '^(user|userdebug|eng)$' | xargs)"
     local density="$(echo $* | xargs -n 1 echo | \grep -E '^(ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|alldpi)$' | xargs)"
-    local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|arm|x86|arm64|x86_64|ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|alldpi)$' | xargs)"
+    local keys="$(echo $* | xargs -n 1 echo | \grep -E '^(devkeys)$' | xargs)"
+    local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|arm|x86|arm64|x86_64|ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|alldpi|devkeys)$' | xargs)"
+
 
     if [ "$showHelp" != "" ]; then
       $(gettop)/build/make/tapasHelp.sh
@@ -765,6 +838,10 @@
         echo "tapas: Error: Multiple densities supplied: $density"
         return
     fi
+    if [ $(echo $keys | wc -w) -gt 1 ]; then
+        echo "tapas: Error: Multiple keys supplied: $keys"
+        return
+    fi
 
     local product=aosp_arm
     case $arch in
@@ -772,6 +849,10 @@
       arm64)  product=aosp_arm64;;
       x86_64) product=aosp_x86_64;;
     esac
+    if [ -n "$keys" ]; then
+        product=${product/aosp_/aosp_${keys}_}
+    fi;
+
     if [ -z "$variant" ]; then
         variant=eng
     fi
@@ -1455,7 +1536,7 @@
         > $ANDROID_PRODUCT_OUT/module-info.json.build.log 2>&1
 }
 
-# Verifies that module-info.txt exists, creating it if it doesn't.
+# Verifies that module-info.txt exists, returning nonzero if it doesn't.
 function verifymodinfo() {
     if [ ! "$ANDROID_PRODUCT_OUT" ]; then
         if [ "$QUIET_VERIFYMODINFO" != "true" ] ; then
@@ -1466,7 +1547,7 @@
 
     if [ ! -f "$ANDROID_PRODUCT_OUT/module-info.json" ]; then
         if [ "$QUIET_VERIFYMODINFO" != "true" ] ; then
-            echo "Could not find module-info.json. It will only be built once, and it can be updated with 'refreshmod'" >&2
+            echo "Could not find module-info.json. Please run 'refreshmod' first." >&2
         fi
         return 1
     fi
@@ -1585,6 +1666,10 @@
 function installmod() {
     if [[ $# -eq 0 ]]; then
         echo "usage: installmod [adb install arguments] <module>" >&2
+        echo "" >&2
+        echo "Only flags to be passed after the \"install\" in adb install are supported," >&2
+        echo "with the exception of -s. If -s is passed it will be placed before the \"install\"." >&2
+        echo "-s must be the first flag passed if it exists." >&2
         return 1
     fi
 
@@ -1599,9 +1684,18 @@
         echo "Module '$1' does not produce a file ending with .apk (try 'refreshmod' if there have been build changes?)" >&2
         return 1
     fi
+    local serial_device=""
+    if [[ "$1" == "-s" ]]; then
+        if [[ $# -le 2 ]]; then
+            echo "-s requires an argument" >&2
+            return 1
+        fi
+        serial_device="-s $2"
+        shift 2
+    fi
     local length=$(( $# - 1 ))
-    echo adb install ${@:1:$length} $_path
-    adb install ${@:1:$length} $_path
+    echo adb $serial_device install ${@:1:$length} $_path
+    adb $serial_device install ${@:1:$length} $_path
 }
 
 function _complete_android_module_names() {
@@ -1657,12 +1751,19 @@
     if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
         color_failed=$'\E'"[0;31m"
         color_success=$'\E'"[0;32m"
+        color_warning=$'\E'"[0;33m"
         color_reset=$'\E'"[00m"
     else
         color_failed=""
         color_success=""
         color_reset=""
     fi
+
+    if [[ "x${USE_RBE}" == "x" && $mins -gt 15 && "${ANDROID_BUILD_ENVIRONMENT_CONFIG}" == "googler" ]]; then
+        echo
+        echo "${color_warning}Start using RBE (http://go/build-fast) to get faster builds!${color_reset}"
+    fi
+
     echo
     if [ $ret -eq 0 ] ; then
         echo -n "${color_success}#### build completed successfully "
@@ -1687,7 +1788,23 @@
     if T="$(gettop)"; then
       _wrap_build "$T/build/soong/soong_ui.bash" --build-mode --${bc} --dir="$(pwd)" "$@"
     else
-      echo "Couldn't locate the top of the tree. Try setting TOP."
+      >&2 echo "Couldn't locate the top of the tree. Try setting TOP."
+      return 1
+    fi
+)
+
+# Convenience entry point (like m) to use Bazel in AOSP.
+function b()
+(
+    # Generate BUILD, bzl files into the synthetic Bazel workspace (out/soong/workspace).
+    _trigger_build "all-modules" bp2build USE_BAZEL_ANALYSIS= || return 1
+    # Then, run Bazel using the synthetic workspace as the --package_path.
+    if [[ -z "$@" ]]; then
+        # If there are no args, show help.
+        bazel help
+    else
+        # Else, always run with the bp2build configuration, which sets Bazel's package path to the synthetic workspace.
+        bazel "$@" --config=bp2build
     fi
 )
 
diff --git a/finalize_branch_for_release.sh b/finalize_branch_for_release.sh
new file mode 100755
index 0000000..d498beb
--- /dev/null
+++ b/finalize_branch_for_release.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+set -e
+
+source ../envsetup.sh
+
+# default target to modify tree and build SDK
+lunch aosp_arm64-userdebug
+
+set -x
+
+# This script is WIP and only finalizes part of the Android branch for release.
+# The full process can be found at (INTERNAL) go/android-sdk-finalization.
+
+# VNDK snapshot (TODO)
+# SDK snapshots (TODO)
+# Update references in the codebase to new API version (TODO)
+# ...
+
+AIDL_TRANSITIVE_FREEZE=true m aidl-freeze-api
+
+# TODO(b/229413853): test while simulating 'rel' for more requirements AIDL_FROZEN_REL=true
+m # test build
+
+# Build SDK (TODO)
+# lunch sdk...
+# m ...
diff --git a/help.sh b/help.sh
index 06a9056..e51adc1 100755
--- a/help.sh
+++ b/help.sh
@@ -52,6 +52,8 @@
                             Stands for "VendorDlkm, NO Dependencies"
     odnod                   Quickly rebuild the odm_dlkm image from built packages
                             Stands for "OdmDlkm, NO Dependencies"
+    sdnod                   Quickly rebuild the system_dlkm image from built packages
+                            Stands for "SystemDlkm, NO Dependencies"
 
 
 So, for example, you could run:
diff --git a/orchestrator/core/lunch.py b/orchestrator/core/lunch.py
new file mode 100755
index 0000000..35dac73
--- /dev/null
+++ b/orchestrator/core/lunch.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python3
+#
+# 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.
+
+import argparse
+import glob
+import json
+import os
+import sys
+
+EXIT_STATUS_OK = 0
+EXIT_STATUS_ERROR = 1
+EXIT_STATUS_NEED_HELP = 2
+
+def FindDirs(path, name, ttl=6):
+    """Search at most ttl directories deep inside path for a directory called name."""
+    # The dance with subdirs is so that we recurse in sorted order.
+    subdirs = []
+    with os.scandir(path) as it:
+        for dirent in sorted(it, key=lambda x: x.name):
+            try:
+                if dirent.is_dir():
+                    if dirent.name == name:
+                        yield os.path.join(path, dirent.name)
+                    elif ttl > 0:
+                        subdirs.append(dirent.name)
+            except OSError:
+                # Consume filesystem errors, e.g. too many links, permission etc.
+                pass
+    for subdir in subdirs:
+        yield from FindDirs(os.path.join(path, subdir), name, ttl-1)
+
+
+def WalkPaths(path, matcher, ttl=10):
+    """Do a traversal of all files under path yielding each file that matches
+    matcher."""
+    # First look for files, then recurse into directories as needed.
+    # The dance with subdirs is so that we recurse in sorted order.
+    subdirs = []
+    with os.scandir(path) as it:
+        for dirent in sorted(it, key=lambda x: x.name):
+            try:
+                if dirent.is_file():
+                    if matcher(dirent.name):
+                        yield os.path.join(path, dirent.name)
+                if dirent.is_dir():
+                    if ttl > 0:
+                        subdirs.append(dirent.name)
+            except OSError:
+                # Consume filesystem errors, e.g. too many links, permission etc.
+                pass
+    for subdir in sorted(subdirs):
+        yield from WalkPaths(os.path.join(path, subdir), matcher, ttl-1)
+
+
+def FindFile(path, filename):
+    """Return a file called filename inside path, no more than ttl levels deep.
+
+    Directories are searched alphabetically.
+    """
+    for f in WalkPaths(path, lambda x: x == filename):
+        return f
+
+
+def FindConfigDirs(workspace_root):
+    """Find the configuration files in the well known locations inside workspace_root
+
+        <workspace_root>/build/orchestrator/multitree_combos
+           (AOSP devices, such as cuttlefish)
+
+        <workspace_root>/vendor/**/multitree_combos
+            (specific to a vendor and not open sourced)
+
+        <workspace_root>/device/**/multitree_combos
+            (specific to a vendor and are open sourced)
+
+    Directories are returned specifically in this order, so that aosp can't be
+    overridden, but vendor overrides device.
+    """
+
+    # TODO: When orchestrator is in its own git project remove the "make/" here
+    yield os.path.join(workspace_root, "build/make/orchestrator/multitree_combos")
+
+    dirs = ["vendor", "device"]
+    for d in dirs:
+        yield from FindDirs(os.path.join(workspace_root, d), "multitree_combos")
+
+
+def FindNamedConfig(workspace_root, shortname):
+    """Find the config with the given shortname inside workspace_root.
+
+    Config directories are searched in the order described in FindConfigDirs,
+    and inside those directories, alphabetically."""
+    filename = shortname + ".mcombo"
+    for config_dir in FindConfigDirs(workspace_root):
+        found = FindFile(config_dir, filename)
+        if found:
+            return found
+    return None
+
+
+def ParseProductVariant(s):
+    """Split a PRODUCT-VARIANT name, or return None if it doesn't match that pattern."""
+    split = s.split("-")
+    if len(split) != 2:
+        return None
+    return split
+
+
+def ChooseConfigFromArgs(workspace_root, args):
+    """Return the config file we should use for the given argument,
+    or null if there's no file that matches that."""
+    if len(args) == 1:
+        # Prefer PRODUCT-VARIANT syntax so if there happens to be a matching
+        # file we don't match that.
+        pv = ParseProductVariant(args[0])
+        if pv:
+            config = FindNamedConfig(workspace_root, pv[0])
+            if config:
+                return (config, pv[1])
+            return None, None
+    # Look for a specifically named file
+    if os.path.isfile(args[0]):
+        return (args[0], args[1] if len(args) > 1 else None)
+    # That file didn't exist, return that we didn't find it.
+    return None, None
+
+
+class ConfigException(Exception):
+    ERROR_PARSE = "parse"
+    ERROR_CYCLE = "cycle"
+
+    def __init__(self, kind, message, locations, line=0):
+        """Error thrown when loading and parsing configurations.
+
+        Args:
+            message: Error message to display to user
+            locations: List of filenames of the include history.  The 0 index one
+                       the location where the actual error occurred
+        """
+        if len(locations):
+            s = locations[0]
+            if line:
+                s += ":"
+                s += str(line)
+            s += ": "
+        else:
+            s = ""
+        s += message
+        if len(locations):
+            for loc in locations[1:]:
+                s += "\n        included from %s" % loc
+        super().__init__(s)
+        self.kind = kind
+        self.message = message
+        self.locations = locations
+        self.line = line
+
+
+def LoadConfig(filename):
+    """Load a config, including processing the inherits fields.
+
+    Raises:
+        ConfigException on errors
+    """
+    def LoadAndMerge(fn, visited):
+        with open(fn) as f:
+            try:
+                contents = json.load(f)
+            except json.decoder.JSONDecodeError as ex:
+                if True:
+                    raise ConfigException(ConfigException.ERROR_PARSE, ex.msg, visited, ex.lineno)
+                else:
+                    sys.stderr.write("exception %s" % ex.__dict__)
+                    raise ex
+            # Merge all the parents into one data, with first-wins policy
+            inherited_data = {}
+            for parent in contents.get("inherits", []):
+                if parent in visited:
+                    raise ConfigException(ConfigException.ERROR_CYCLE, "Cycle detected in inherits",
+                            visited)
+                DeepMerge(inherited_data, LoadAndMerge(parent, [parent,] + visited))
+            # Then merge inherited_data into contents, but what's already there will win.
+            DeepMerge(contents, inherited_data)
+            contents.pop("inherits", None)
+        return contents
+    return LoadAndMerge(filename, [filename,])
+
+
+def DeepMerge(merged, addition):
+    """Merge all fields of addition into merged. Pre-existing fields win."""
+    for k, v in addition.items():
+        if k in merged:
+            if isinstance(v, dict) and isinstance(merged[k], dict):
+                DeepMerge(merged[k], v)
+        else:
+            merged[k] = v
+
+
+def Lunch(args):
+    """Handle the lunch command."""
+    # Check that we're at the top of a multitree workspace
+    # TODO: Choose the right sentinel file
+    if not os.path.exists("build/make/orchestrator"):
+        sys.stderr.write("ERROR: lunch.py must be run from the root of a multi-tree workspace\n")
+        return EXIT_STATUS_ERROR
+
+    # Choose the config file
+    config_file, variant = ChooseConfigFromArgs(".", args)
+
+    if config_file == None:
+        sys.stderr.write("Can't find lunch combo file for: %s\n" % " ".join(args))
+        return EXIT_STATUS_NEED_HELP
+    if variant == None:
+        sys.stderr.write("Can't find variant for: %s\n" % " ".join(args))
+        return EXIT_STATUS_NEED_HELP
+
+    # Parse the config file
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write(str(ex))
+        return EXIT_STATUS_ERROR
+
+    # Fail if the lunchable bit isn't set, because this isn't a usable config
+    if not config.get("lunchable", False):
+        sys.stderr.write("%s: Lunch config file (or inherited files) does not have the 'lunchable'"
+                % config_file)
+        sys.stderr.write(" flag set, which means it is probably not a complete lunch spec.\n")
+
+    # All the validation has passed, so print the name of the file and the variant
+    sys.stdout.write("%s\n" % config_file)
+    sys.stdout.write("%s\n" % variant)
+
+    return EXIT_STATUS_OK
+
+
+def FindAllComboFiles(workspace_root):
+    """Find all .mcombo files in the prescribed locations in the tree."""
+    for dir in FindConfigDirs(workspace_root):
+        for file in WalkPaths(dir, lambda x: x.endswith(".mcombo")):
+            yield file
+
+
+def IsFileLunchable(config_file):
+    """Parse config_file, flatten the inheritance, and return whether it can be
+    used as a lunch target."""
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write("%s" % ex)
+        return False
+    return config.get("lunchable", False)
+
+
+def FindAllLunchable(workspace_root):
+    """Find all mcombo files in the tree (rooted at workspace_root) that when
+    parsed (and inheritance is flattened) have lunchable: true."""
+    for f in [x for x in FindAllComboFiles(workspace_root) if IsFileLunchable(x)]:
+        yield f
+
+
+def List():
+    """Handle the --list command."""
+    for f in sorted(FindAllLunchable(".")):
+        print(f)
+
+
+def Print(args):
+    """Handle the --print command."""
+    # Parse args
+    if len(args) == 0:
+        config_file = os.environ.get("TARGET_BUILD_COMBO")
+        if not config_file:
+            sys.stderr.write("TARGET_BUILD_COMBO not set. Run lunch or pass a combo file.\n")
+            return EXIT_STATUS_NEED_HELP
+    elif len(args) == 1:
+        config_file = args[0]
+    else:
+        return EXIT_STATUS_NEED_HELP
+
+    # Parse the config file
+    try:
+        config = LoadConfig(config_file)
+    except ConfigException as ex:
+        sys.stderr.write(str(ex))
+        return EXIT_STATUS_ERROR
+
+    # Print the config in json form
+    json.dump(config, sys.stdout, indent=4)
+
+    return EXIT_STATUS_OK
+
+
+def main(argv):
+    if len(argv) < 2 or argv[1] == "-h" or argv[1] == "--help":
+        return EXIT_STATUS_NEED_HELP
+
+    if len(argv) == 2 and argv[1] == "--list":
+        List()
+        return EXIT_STATUS_OK
+
+    if len(argv) == 2 and argv[1] == "--print":
+        return Print(argv[2:])
+        return EXIT_STATUS_OK
+
+    if (len(argv) == 2 or len(argv) == 3) and argv[1] == "--lunch":
+        return Lunch(argv[2:])
+
+    sys.stderr.write("Unknown lunch command: %s\n" % " ".join(argv[1:]))
+    return EXIT_STATUS_NEED_HELP
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
+
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/core/test/configs/another/bad.mcombo b/orchestrator/core/test/configs/another/bad.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/another/bad.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/another/dir/a b/orchestrator/core/test/configs/another/dir/a
new file mode 100644
index 0000000..7898192
--- /dev/null
+++ b/orchestrator/core/test/configs/another/dir/a
@@ -0,0 +1 @@
+a
diff --git a/orchestrator/core/test/configs/b-eng b/orchestrator/core/test/configs/b-eng
new file mode 100644
index 0000000..eceb3f3
--- /dev/null
+++ b/orchestrator/core/test/configs/b-eng
@@ -0,0 +1 @@
+INVALID FILE
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo
new file mode 100644
index 0000000..8cc8370
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/b.mcombo
@@ -0,0 +1,3 @@
+{
+    "lunchable": "true"
+}
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt
new file mode 100644
index 0000000..f9805f2
--- /dev/null
+++ b/orchestrator/core/test/configs/build/make/orchestrator/multitree_combos/not_a_combo.txt
@@ -0,0 +1 @@
+not a combo file
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/b.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/d.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/device/aa/bb/multitree_combos/v.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo b/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/orchestrator/core/test/configs/device/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
diff --git a/orchestrator/core/test/configs/parsing/cycles/1.mcombo b/orchestrator/core/test/configs/parsing/cycles/1.mcombo
new file mode 100644
index 0000000..ab8fe33
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/1.mcombo
@@ -0,0 +1,5 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/2.mcombo"
+    ]
+}
diff --git a/orchestrator/core/test/configs/parsing/cycles/2.mcombo b/orchestrator/core/test/configs/parsing/cycles/2.mcombo
new file mode 100644
index 0000000..2b774d0
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/2.mcombo
@@ -0,0 +1,6 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/3.mcombo"
+    ]
+}
+
diff --git a/orchestrator/core/test/configs/parsing/cycles/3.mcombo b/orchestrator/core/test/configs/parsing/cycles/3.mcombo
new file mode 100644
index 0000000..41b629b
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/cycles/3.mcombo
@@ -0,0 +1,6 @@
+{
+    "inherits": [
+        "test/configs/parsing/cycles/1.mcombo"
+    ]
+}
+
diff --git a/orchestrator/core/test/configs/parsing/merge/1.mcombo b/orchestrator/core/test/configs/parsing/merge/1.mcombo
new file mode 100644
index 0000000..a5a57d7
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/1.mcombo
@@ -0,0 +1,13 @@
+{
+    "inherits": [
+        "test/configs/parsing/merge/2.mcombo",
+        "test/configs/parsing/merge/3.mcombo"
+    ],
+    "in_1": "1",
+    "in_1_2": "1",
+    "merged": {
+        "merged_1": "1",
+        "merged_1_2": "1"
+    },
+    "dict_1": { "a" : "b" }
+}
diff --git a/orchestrator/core/test/configs/parsing/merge/2.mcombo b/orchestrator/core/test/configs/parsing/merge/2.mcombo
new file mode 100644
index 0000000..00963e2
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/2.mcombo
@@ -0,0 +1,12 @@
+{
+    "in_1_2": "2",
+    "in_2": "2",
+    "in_2_3": "2",
+    "merged": {
+        "merged_1_2": "2",
+        "merged_2": "2",
+        "merged_2_3": "2"
+    },
+    "dict_2": { "a" : "b" }
+}
+
diff --git a/orchestrator/core/test/configs/parsing/merge/3.mcombo b/orchestrator/core/test/configs/parsing/merge/3.mcombo
new file mode 100644
index 0000000..5fc9d900
--- /dev/null
+++ b/orchestrator/core/test/configs/parsing/merge/3.mcombo
@@ -0,0 +1,10 @@
+{
+    "in_3": "3",
+    "in_2_3": "3",
+    "merged": {
+        "merged_3": "3",
+        "merged_2_3": "3"
+    },
+    "dict_3": { "a" : "b" }
+}
+
diff --git a/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/b.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/aa/bb/multitree_combos/v.mcombo
@@ -0,0 +1 @@
+{}
diff --git a/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo b/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/orchestrator/core/test/configs/vendor/this/one/is/deeper/than/will/be/found/by/the/ttl/multitree_combos/too_deep.mcombo
diff --git a/orchestrator/core/test_lunch.py b/orchestrator/core/test_lunch.py
new file mode 100755
index 0000000..3c39493
--- /dev/null
+++ b/orchestrator/core/test_lunch.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2008 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.
+
+import sys
+import unittest
+
+sys.dont_write_bytecode = True
+import lunch
+
+class TestStringMethods(unittest.TestCase):
+
+    def test_find_dirs(self):
+        self.assertEqual([x for x in lunch.FindDirs("test/configs", "multitree_combos")], [
+                    "test/configs/build/make/orchestrator/multitree_combos",
+                    "test/configs/device/aa/bb/multitree_combos",
+                    "test/configs/vendor/aa/bb/multitree_combos"])
+
+    def test_find_file(self):
+        # Finds the one in device first because this is searching from the root,
+        # not using FindNamedConfig.
+        self.assertEqual(lunch.FindFile("test/configs", "v.mcombo"),
+                   "test/configs/device/aa/bb/multitree_combos/v.mcombo")
+
+    def test_find_config_dirs(self):
+        self.assertEqual([x for x in lunch.FindConfigDirs("test/configs")], [
+                    "test/configs/build/make/orchestrator/multitree_combos",
+                    "test/configs/vendor/aa/bb/multitree_combos",
+                    "test/configs/device/aa/bb/multitree_combos"])
+
+    def test_find_named_config(self):
+        # Inside build/orchestrator, overriding device and vendor
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "b"),
+                    "test/configs/build/make/orchestrator/multitree_combos/b.mcombo")
+
+        # Nested dir inside a combo dir
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "nested"),
+                    "test/configs/build/make/orchestrator/multitree_combos/nested/nested.mcombo")
+
+        # Inside vendor, overriding device
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "v"),
+                    "test/configs/vendor/aa/bb/multitree_combos/v.mcombo")
+
+        # Inside device
+        self.assertEqual(lunch.FindNamedConfig("test/configs", "d"),
+                    "test/configs/device/aa/bb/multitree_combos/d.mcombo")
+
+        # Make sure we don't look too deep (for performance)
+        self.assertIsNone(lunch.FindNamedConfig("test/configs", "too_deep"))
+
+
+    def test_choose_config_file(self):
+        # Empty string argument
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", [""]),
+                    (None, None))
+
+        # A PRODUCT-VARIANT name
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["v-eng"]),
+                    ("test/configs/vendor/aa/bb/multitree_combos/v.mcombo", "eng"))
+
+        # A PRODUCT-VARIANT name that conflicts with a file
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["b-eng"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
+
+        # A PRODUCT-VARIANT that doesn't exist
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs", ["z-user"]),
+                    (None, None))
+
+        # An explicit file
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", "eng"))
+
+        # An explicit file that doesn't exist
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/doesnt_exist.mcombo", "eng"]),
+                    (None, None))
+
+        # An explicit file without a variant should fail
+        self.assertEqual(lunch.ChooseConfigFromArgs("test/configs",
+                        ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"]),
+                    ("test/configs/build/make/orchestrator/multitree_combos/b.mcombo", None))
+
+
+    def test_config_cycles(self):
+        # Test that we catch cycles
+        with self.assertRaises(lunch.ConfigException) as context:
+            lunch.LoadConfig("test/configs/parsing/cycles/1.mcombo")
+        self.assertEqual(context.exception.kind, lunch.ConfigException.ERROR_CYCLE)
+
+    def test_config_merge(self):
+        # Test the merge logic
+        self.assertEqual(lunch.LoadConfig("test/configs/parsing/merge/1.mcombo"), {
+                            "in_1": "1",
+                            "in_1_2": "1",
+                            "merged": {"merged_1": "1",
+                                "merged_1_2": "1",
+                                "merged_2": "2",
+                                "merged_2_3": "2",
+                                "merged_3": "3"},
+                            "dict_1": {"a": "b"},
+                            "in_2": "2",
+                            "in_2_3": "2",
+                            "dict_2": {"a": "b"},
+                            "in_3": "3",
+                            "dict_3": {"a": "b"}
+                        })
+
+    def test_list(self):
+        self.assertEqual(sorted(lunch.FindAllLunchable("test/configs")),
+                ["test/configs/build/make/orchestrator/multitree_combos/b.mcombo"])
+
+if __name__ == "__main__":
+    unittest.main()
+
+# vim: sts=4:ts=4:sw=4
diff --git a/orchestrator/multitree_combos/test.mcombo b/orchestrator/multitree_combos/test.mcombo
new file mode 100644
index 0000000..3ad0717
--- /dev/null
+++ b/orchestrator/multitree_combos/test.mcombo
@@ -0,0 +1,16 @@
+{
+    "lunchable": true,
+    "system": {
+        "tree": "inner_tree_system",
+        "product": "system_lunch_product"
+    },
+    "vendor": {
+        "tree": "inner_tree_vendor",
+        "product": "vendor_lunch_product"
+    },
+    "modules": {
+        "com.android.something": {
+            "tree": "inner_tree_module"
+        }
+    }
+}
diff --git a/rbesetup.sh b/rbesetup.sh
index ec39e6e..3b0e7cf 100644
--- a/rbesetup.sh
+++ b/rbesetup.sh
@@ -24,8 +24,11 @@
 }
 
 # This function needs to run first as the remaining defining functions may be
-# using the envsetup.sh defined functions.
-_source_env_setup_script || return
+# using the envsetup.sh defined functions. Skip this part if this script is already
+# being invoked from envsetup.sh.
+if [[ "$1" != "--skip-envsetup" ]]; then
+  _source_env_setup_script || return
+fi
 
 # This function prefixes the given command with appropriate variables needed
 # for the build to be executed with RBE.
diff --git a/tapasHelp.sh b/tapasHelp.sh
index 0f46130..7cb5f2c 100755
--- a/tapasHelp.sh
+++ b/tapasHelp.sh
@@ -6,7 +6,7 @@
 cd ../..
 TOP="${PWD}"
 
-message='usage: tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
+message='usage: tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user] [devkeys]
 
 tapas selects individual apps to be built by the Android build system. Unlike
 "lunch", "tapas" does not request the building of images for a device.
diff --git a/target/board/Android.mk b/target/board/Android.mk
index 4dd6b17..baa3d3a 100644
--- a/target/board/Android.mk
+++ b/target/board/Android.mk
@@ -24,10 +24,14 @@
 	$(call pretty,"Generated: ($@)")
 ifdef board_info_txt
 	$(hide) grep -v '#' $< > $@
-else
+else ifdef TARGET_BOOTLOADER_BOARD_NAME
 	$(hide) echo "board=$(TARGET_BOOTLOADER_BOARD_NAME)" > $@
+else
+	$(hide) echo "" > $@
 endif
 
+$(call declare-0p-target,$(INSTALLED_ANDROID_INFO_TXT_TARGET))
+
 # Copy compatibility metadata to the device.
 
 # Device Manifest
diff --git a/target/board/BoardConfigGkiCommon.mk b/target/board/BoardConfigGkiCommon.mk
deleted file mode 100644
index f480b93..0000000
--- a/target/board/BoardConfigGkiCommon.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Enable GKI 2.0 signing.
-BOARD_GKI_SIGNING_KEY_PATH := build/make/target/product/gsi/testkey_rsa2048.pem
-BOARD_GKI_SIGNING_ALGORITHM := SHA256_RSA2048
-
-# The following is needed to allow release signing process appends more extra
-# args, e.g., passing --signing_helper_with_files from mkbootimg to avbtool.
-# See b/178559811 for more details.
-BOARD_GKI_SIGNING_SIGNATURE_ARGS := --prop foo:bar
-
-# Sets boot SPL.
-BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
-
-# Boot image with ramdisk and kernel
-BOARD_RAMDISK_USE_LZ4 := true
-BOARD_BOOT_HEADER_VERSION := 4
-BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
-BOARD_USES_RECOVERY_AS_BOOT :=
-TARGET_NO_KERNEL := false
-BOARD_USES_GENERIC_KERNEL_IMAGE := true
-BOARD_KERNEL_MODULE_INTERFACE_VERSIONS := \
-    5.4-android12-unstable \
-    5.10-android12-unstable \
-
-# Copy boot image in $OUT to target files. This is defined for targets where
-# the installed GKI APEXes are built from source.
-BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES := true
-
-# No vendor_boot
-BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT :=
-
-# No recovery
-BOARD_EXCLUDE_KERNEL_FROM_RECOVERY_IMAGE :=
diff --git a/target/board/BoardConfigGsiCommon.mk b/target/board/BoardConfigGsiCommon.mk
index c577870..53714a8 100644
--- a/target/board/BoardConfigGsiCommon.mk
+++ b/target/board/BoardConfigGsiCommon.mk
@@ -11,8 +11,11 @@
 # This flag is set by mainline but isn't desired for GSI.
 BOARD_USES_SYSTEM_OTHER_ODEX :=
 
-# system.img is always ext4 and non-sparsed.
+# system.img is ext4/erofs and non-sparsed.
+GSI_FILE_SYSTEM_TYPE ?= ext4
+BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := $(GSI_FILE_SYSTEM_TYPE)
 TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true
+TARGET_USERIMAGES_SPARSE_EROFS_DISABLED := true
 
 # GSI also includes make_f2fs to support userdata parition in f2fs
 # for some devices
@@ -35,6 +38,12 @@
 #   updating the last seen rollback index in the tamper-evident storage.
 BOARD_AVB_ROLLBACK_INDEX := 0
 
+# The chained vbmeta settings for boot images.
+BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa4096.pem
+BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA4096
+BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
+BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2
+
 # Enable AVB chained partition for system.
 # https://android.googlesource.com/platform/external/avb/+/master/README.md
 BOARD_AVB_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
@@ -57,12 +66,6 @@
 #     This flag is set by mainline but isn't desired for GSI
 BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR :=
 
-# Enable chain partition for boot, mainly for GKI images.
-BOARD_AVB_BOOT_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
-BOARD_AVB_BOOT_ALGORITHM := SHA256_RSA2048
-BOARD_AVB_BOOT_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP)
-BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION := 2
-
 # GSI specific System Properties
 ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
 TARGET_SYSTEM_EXT_PROP := build/make/target/board/gsi_system_ext.prop
diff --git a/target/board/BoardConfigPixelCommon.mk b/target/board/BoardConfigPixelCommon.mk
index a970fec..22521b5 100644
--- a/target/board/BoardConfigPixelCommon.mk
+++ b/target/board/BoardConfigPixelCommon.mk
@@ -5,6 +5,7 @@
 # Using sha256 for dm-verity partitions. b/156162446
 # system, system_other, system_ext and product.
 BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 BOARD_AVB_SYSTEM_OTHER_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 BOARD_AVB_SYSTEM_EXT_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
 BOARD_AVB_PRODUCT_ADD_HASHTREE_FOOTER_ARGS += --hash_algorithm sha256
diff --git a/target/board/generic_arm64/BoardConfig.mk b/target/board/generic_arm64/BoardConfig.mk
index 49ae216..45ed3da 100644
--- a/target/board/generic_arm64/BoardConfig.mk
+++ b/target/board/generic_arm64/BoardConfig.mk
@@ -53,37 +53,6 @@
 endif
 
 include build/make/target/board/BoardConfigGsiCommon.mk
-include build/make/target/board/BoardConfigGkiCommon.mk
-
-BOARD_KERNEL-4.19-GZ_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-4.19-GZ-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.4_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.4-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.4-GZ_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.4-GZ-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.4-LZ4_BOOTIMAGE_PARTITION_SIZE := 53477376
-BOARD_KERNEL-5.4-LZ4-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 53477376
-BOARD_KERNEL-5.10_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-GZ_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.10-GZ-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 47185920
-BOARD_KERNEL-5.10-LZ4_BOOTIMAGE_PARTITION_SIZE := 53477376
-BOARD_KERNEL-5.10-LZ4-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 53477376
-
-BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
-
-BOARD_KERNEL_BINARIES := \
-    kernel-4.19-gz \
-    kernel-5.4 kernel-5.4-gz kernel-5.4-lz4 \
-    kernel-5.10 kernel-5.10-gz kernel-5.10-lz4 \
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-BOARD_KERNEL_BINARIES += \
-    kernel-4.19-gz-allsyms \
-    kernel-5.4-allsyms kernel-5.4-gz-allsyms kernel-5.4-lz4-allsyms \
-    kernel-5.10-allsyms kernel-5.10-gz-allsyms kernel-5.10-lz4-allsyms \
-
-endif
 
 # Some vendors still haven't cleaned up all device specific directories under
 # root!
diff --git a/target/board/generic_arm64/device.mk b/target/board/generic_arm64/device.mk
index b331af4..598bef1 100644
--- a/target/board/generic_arm64/device.mk
+++ b/target/board/generic_arm64/device.mk
@@ -13,33 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/4.19/arm64/kernel-4.19-gz:kernel-4.19-gz \
-    kernel/prebuilts/5.4/arm64/kernel-5.4:kernel-5.4 \
-    kernel/prebuilts/5.4/arm64/kernel-5.4-gz:kernel-5.4-gz \
-    kernel/prebuilts/5.4/arm64/kernel-5.4-lz4:kernel-5.4-lz4 \
-    kernel/prebuilts/5.10/arm64/kernel-5.10:kernel-5.10 \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-gz:kernel-5.10-gz \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-lz4:kernel-5.10-lz4 \
-
-$(call dist-for-goals, dist_files, kernel/prebuilts/4.19/arm64/prebuilt-info.txt:kernel/4.19/prebuilt-info.txt)
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.4/arm64/prebuilt-info.txt:kernel/5.4/prebuilt-info.txt)
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.10/arm64/prebuilt-info.txt:kernel/5.10/prebuilt-info.txt)
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/4.19/arm64/kernel-4.19-gz-allsyms:kernel-4.19-gz-allsyms \
-    kernel/prebuilts/5.4/arm64/kernel-5.4-allsyms:kernel-5.4-allsyms \
-    kernel/prebuilts/5.4/arm64/kernel-5.4-gz-allsyms:kernel-5.4-gz-allsyms \
-    kernel/prebuilts/5.4/arm64/kernel-5.4-lz4-allsyms:kernel-5.4-lz4-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-allsyms:kernel-5.10-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-gz-allsyms:kernel-5.10-gz-allsyms \
-    kernel/prebuilts/5.10/arm64/kernel-5.10-lz4-allsyms:kernel-5.10-lz4-allsyms \
-
-endif
-
-PRODUCT_BUILD_VENDOR_BOOT_IMAGE := false
-PRODUCT_BUILD_RECOVERY_IMAGE := false
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
diff --git a/target/board/generic_x86_64/BoardConfig.mk b/target/board/generic_x86_64/BoardConfig.mk
index bdc862e..93694f2 100755
--- a/target/board/generic_x86_64/BoardConfig.mk
+++ b/target/board/generic_x86_64/BoardConfig.mk
@@ -24,28 +24,7 @@
 
 include build/make/target/board/BoardConfigGsiCommon.mk
 
-ifdef BUILDING_GSI
-include build/make/target/board/BoardConfigGkiCommon.mk
-
-BOARD_KERNEL-5.4_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.4-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10_BOOTIMAGE_PARTITION_SIZE := 67108864
-BOARD_KERNEL-5.10-ALLSYMS_BOOTIMAGE_PARTITION_SIZE := 67108864
-
-BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
-
-BOARD_KERNEL_BINARIES := \
-    kernel-5.4 \
-    kernel-5.10 \
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-BOARD_KERNEL_BINARIES += \
-    kernel-5.4-allsyms \
-    kernel-5.10-allsyms \
-
-endif
-
-else # BUILDING_GSI
+ifndef BUILDING_GSI
 include build/make/target/board/BoardConfigEmuCommon.mk
 
 BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
@@ -63,4 +42,4 @@
 WIFI_DRIVER_FW_PATH_STA     := "/dev/null"
 WIFI_DRIVER_FW_PATH_AP      := "/dev/null"
 
-endif # BUILDING_GSI
+endif # !BUILDING_GSI
diff --git a/target/board/generic_x86_64/device.mk b/target/board/generic_x86_64/device.mk
index f31a491..fa1eb67 100755
--- a/target/board/generic_x86_64/device.mk
+++ b/target/board/generic_x86_64/device.mk
@@ -13,22 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/5.4/x86_64/kernel-5.4:kernel-5.4 \
-    kernel/prebuilts/5.10/x86_64/kernel-5.10:kernel-5.10 \
-
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.4/x86_64/prebuilt-info.txt:kernel/5.4/prebuilt-info.txt)
-$(call dist-for-goals, dist_files, kernel/prebuilts/5.10/x86_64/prebuilt-info.txt:kernel/5.10/prebuilt-info.txt)
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-PRODUCT_COPY_FILES += \
-    kernel/prebuilts/5.4/x86_64/kernel-5.4-allsyms:kernel-5.4-allsyms \
-    kernel/prebuilts/5.10/x86_64/kernel-5.10-allsyms:kernel-5.10-allsyms \
-
-endif
-
-PRODUCT_BUILD_VENDOR_BOOT_IMAGE := false
-PRODUCT_BUILD_RECOVERY_IMAGE := false
-
-$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
diff --git a/target/product/iorap_large_memory_config.mk b/target/board/ndk/BoardConfig.mk
similarity index 76%
rename from target/product/iorap_large_memory_config.mk
rename to target/board/ndk/BoardConfig.mk
index 0c6c89a..da8b5f3 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/target/board/ndk/BoardConfig.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2020 The Android Open Source Project
+# 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.
@@ -12,3 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
+TARGET_ARCH_SUITE := ndk
+TARGET_USES_64_BIT_BINDER := true
+
+MALLOC_SVELTE := true
+
+USE_SAFESTACK := false
diff --git a/target/board/ndk/README.md b/target/board/ndk/README.md
new file mode 100644
index 0000000..d8f3a16
--- /dev/null
+++ b/target/board/ndk/README.md
@@ -0,0 +1,2 @@
+This device is suitable for a soong-only build that builds for all the architectures
+needed for the ndk.
diff --git a/target/product/AndroidProducts.mk b/target/product/AndroidProducts.mk
index 7d9d90e..ee702e5 100644
--- a/target/product/AndroidProducts.mk
+++ b/target/product/AndroidProducts.mk
@@ -61,6 +61,7 @@
     $(LOCAL_DIR)/mainline_system_x86.mk \
     $(LOCAL_DIR)/mainline_system_x86_64.mk \
     $(LOCAL_DIR)/mainline_system_x86_arm.mk \
+    $(LOCAL_DIR)/ndk.mk \
     $(LOCAL_DIR)/sdk_arm64.mk \
     $(LOCAL_DIR)/sdk.mk \
     $(LOCAL_DIR)/sdk_phone_arm64.mk \
diff --git a/target/product/OWNERS b/target/product/OWNERS
index 82e6e88..b3d8998 100644
--- a/target/product/OWNERS
+++ b/target/product/OWNERS
@@ -2,4 +2,4 @@
 
 # GSI
 per-file gsi_release.mk = file:/target/product/gsi/OWNERS
-per-file gsi_keys.mk = file:/target/product/gsi/OWNERS
+per-file developer_gsi_keys.mk = file:/target/product/gsi/OWNERS
diff --git a/target/product/aosp_arm64.mk b/target/product/aosp_arm64.mk
index 38f82a2..01897b7 100644
--- a/target/product/aosp_arm64.mk
+++ b/target/product/aosp_arm64.mk
@@ -53,6 +53,7 @@
 #
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_arm64/device.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/non_ab_device.mk)
 
 #
 # Special settings for GSI releasing
diff --git a/target/product/aosp_x86_64.mk b/target/product/aosp_x86_64.mk
index 5d78264..b3cfae4 100644
--- a/target/product/aosp_x86_64.mk
+++ b/target/product/aosp_x86_64.mk
@@ -56,6 +56,7 @@
 $(call inherit-product-if-exists, device/generic/goldfish/x86_64-vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_64/device.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/non_ab_device.mk)
 
 #
 # Special settings for GSI releasing
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 14ce1af..c919ac4 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -50,7 +50,9 @@
     charger \
     cmd \
     com.android.adbd \
+    com.android.adservices \
     com.android.appsearch \
+    com.android.btservices \
     com.android.conscrypt \
     com.android.extservices \
     com.android.i18n \
@@ -59,6 +61,7 @@
     com.android.media \
     com.android.media.swcodec \
     com.android.mediaprovider \
+    com.android.ondevicepersonalization \
     com.android.os.statsd \
     com.android.permission \
     com.android.resolv \
@@ -67,6 +70,7 @@
     com.android.sdkext \
     com.android.tethering \
     com.android.tzdata \
+    com.android.uwb \
     com.android.wifi \
     ContactsProvider \
     content \
@@ -76,8 +80,10 @@
     device_config \
     dmctl \
     dnsmasq \
+    dmesgd \
     DownloadProvider \
     dpm \
+    dump.erofs \
     dumpstate \
     dumpsys \
     DynamicSystemInstallationService \
@@ -88,6 +94,7 @@
     framework-minus-apex \
     framework-res \
     framework-sysconfig.xml \
+    fsck.erofs \
     fsck_msdos \
     fsverity-release-cert-der \
     fs_config_files_system \
@@ -113,7 +120,6 @@
     init_system \
     input \
     installd \
-    iorapd \
     ip \
     iptables \
     ip-up-vpn \
@@ -132,6 +138,7 @@
     libaudioeffect_jni \
     libbinder \
     libbinder_ndk \
+    libbinder_rpc_unstable \
     libc.bootstrap \
     libcamera2ndk \
     libcutils \
@@ -211,6 +218,7 @@
     MediaProviderLegacy \
     mediaserver \
     mke2fs \
+    mkfs.erofs \
     monkey \
     mtpd \
     ndc \
@@ -288,7 +296,7 @@
 # HWASAN runtime for SANITIZE_TARGET=hwaddress builds
 ifneq (,$(filter hwaddress,$(SANITIZE_TARGET)))
   PRODUCT_PACKAGES += \
-   libclang_rt.hwasan-aarch64-android.bootstrap
+   libclang_rt.hwasan.bootstrap
 endif
 
 # Jacoco agent JARS to be built and installed, if any.
@@ -316,18 +324,20 @@
     atest \
     bcc \
     bit \
+    dump.erofs \
     e2fsck \
     fastboot \
     flags_health_check \
+    fsck.erofs \
     icu-data_host_i18n_apex \
     icu_tzdata.dat_host_tzdata_apex \
     idmap2 \
     incident_report \
     ld.mc \
     lpdump \
-    mdnsd \
     minigzip \
     mke2fs \
+    mkfs.erofs \
     resize2fs \
     sgdisk \
     sqlite3 \
@@ -361,7 +371,6 @@
     adb_keys \
     arping \
     dmuserd \
-    gdbserver \
     idlcli \
     init-debug.rc \
     iotop \
@@ -373,6 +382,7 @@
     profcollectd \
     profcollectctl \
     remount \
+    servicedispatcher \
     showmap \
     sqlite3 \
     ss \
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index 07b3361..5004b85 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -25,6 +25,7 @@
     linker.recovery \
     otacerts.recovery \
     recovery \
+    servicemanager.recovery \
     shell_and_utilities_recovery \
     watchdogd.recovery \
 
@@ -78,7 +79,8 @@
 PRODUCT_PACKAGES += \
     vendor_compatibility_matrix.xml \
 
-# Packages to update the recovery partition, which will be installed on
-# /vendor. TODO(b/141648565): Don't install these unless they're needed.
+# Base modules and settings for the debug ramdisk, which is then packed
+# into a boot-debug.img and a vendor_boot-debug.img.
 PRODUCT_PACKAGES += \
-    applypatch
+    adb_debug.prop \
+    userdebug_plat_sepolicy.cil
diff --git a/target/product/cfi-common.mk b/target/product/cfi-common.mk
index 6ce4fbe..3aa2be7 100644
--- a/target/product/cfi-common.mk
+++ b/target/product/cfi-common.mk
@@ -30,7 +30,7 @@
     hardware/qcom/wlan/qcwcn/wpa_supplicant_8_lib \
     hardware/interfaces/keymaster \
     hardware/interfaces/security \
-    system/bt \
+    packages/modules/Bluetooth/system \
     system/chre \
     system/core/libnetutils \
     system/libziparchive \
diff --git a/target/product/core_64_bit_only.mk b/target/product/core_64_bit_only.mk
index 53c9c74..061728f 100644
--- a/target/product/core_64_bit_only.mk
+++ b/target/product/core_64_bit_only.mk
@@ -25,6 +25,9 @@
 # Set the zygote property to select the 64-bit script.
 # This line must be parsed before the one in core_minimal.mk
 PRODUCT_VENDOR_PROPERTIES += ro.zygote=zygote64
+# A 64-bit-only platform does not have dex2oat32, so make sure dex2oat64 is
+# used for dexopt.
+PRODUCT_VENDOR_PROPERTIES += dalvik.vm.dex2oat64.enabled=true
 
 TARGET_SUPPORTS_32_BIT_APPS := false
 TARGET_SUPPORTS_64_BIT_APPS := true
diff --git a/target/product/core_no_zygote.mk b/target/product/core_no_zygote.mk
new file mode 100644
index 0000000..205a897
--- /dev/null
+++ b/target/product/core_no_zygote.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+# Inherit from this product for devices that do not include a zygote using:
+# $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+# The inheritance for this must come before the inheritance chain that leads
+# to core_minimal.mk.
+
+# Copy the no-zygote startup script
+PRODUCT_COPY_FILES += system/core/rootdir/init.no_zygote.rc:system/etc/init/hw/init.no_zygote.rc
+
+# Set the zygote property to select the no-zygote script.
+# This line must be parsed before the one in core_minimal.mk
+PRODUCT_VENDOR_PROPERTIES += ro.zygote=no_zygote
+
+TARGET_SUPPORTS_32_BIT_APPS := false
+TARGET_SUPPORTS_64_BIT_APPS := false
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index 1a32fd9..e2bb9d5 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -14,6 +14,9 @@
 # limitations under the License.
 #
 
+# This file contains product config for the ART module that is common for
+# platform and unbundled builds.
+
 ifeq ($(ART_APEX_JARS),)
   $(error ART_APEX_JARS is empty; cannot initialize PRODUCT_BOOT_JARS variable)
 endif
@@ -25,9 +28,8 @@
 # 4. Non-updatable APEX jars
 # 5. Updatable APEX jars
 #
-# ART APEX jars (1) are defined in ART_APEX_JARS. System, system_ext, and non updatable boot jars
-# are defined below in PRODUCT_BOOT_JARS. All updatable APEX boot jars are part of
-# PRODUCT_APEX_BOOT_JARS.
+# ART APEX jars (1) are defined in ART_APEX_JARS. System and system_ext boot jars are defined below
+# in PRODUCT_BOOT_JARS. All other non-art APEX boot jars are part of the PRODUCT_APEX_BOOT_JARS.
 #
 # The actual runtime ordering matching above is determined by derive_classpath service at runtime.
 # See packages/modules/SdkExtensions/README.md for more details.
@@ -47,28 +49,57 @@
 
 # APEX boot jars. Keep the list sorted by module names and then library names.
 # Note: core-icu4j is moved back to PRODUCT_BOOT_JARS in product_config.mk at a later stage.
+# Note: For modules available in Q, DO NOT add new entries here.
 PRODUCT_APEX_BOOT_JARS := \
+    com.android.adservices:framework-adservices \
+    com.android.adservices:framework-sdksandbox \
     com.android.appsearch:framework-appsearch \
+    com.android.btservices:framework-bluetooth \
     com.android.conscrypt:conscrypt \
     com.android.i18n:core-icu4j \
     com.android.ipsec:android.net.ipsec.ike \
     com.android.media:updatable-media \
     com.android.mediaprovider:framework-mediaprovider \
+    com.android.ondevicepersonalization:framework-ondevicepersonalization \
     com.android.os.statsd:framework-statsd \
     com.android.permission:framework-permission \
     com.android.permission:framework-permission-s \
     com.android.scheduling:framework-scheduling \
     com.android.sdkext:framework-sdkextensions \
     com.android.tethering:framework-connectivity \
+    com.android.tethering:framework-connectivity-t \
     com.android.tethering:framework-tethering \
-    com.android.wifi:framework-wifi
+    com.android.uwb:framework-uwb \
+    com.android.wifi:framework-wifi \
 
-# APEX system server jars. Keep the list sorted by module names and then library names.
+# List of system_server classpath jars delivered via apex.
+# Keep the list sorted by module names and then library names.
+# Note: For modules available in Q, DO NOT add new entries here.
 PRODUCT_APEX_SYSTEM_SERVER_JARS := \
+    com.android.adservices:service-adservices \
+    com.android.adservices:service-sdksandbox \
     com.android.appsearch:service-appsearch \
+    com.android.art:service-art \
     com.android.media:service-media-s \
     com.android.permission:service-permission \
 
+PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION += art/build/boot/boot-image-profile.txt
+
+# List of jars on the platform that system_server loads dynamically using separate classloaders.
+# Keep the list sorted library names.
+PRODUCT_STANDALONE_SYSTEM_SERVER_JARS := \
+
+# List of jars delivered via apex that system_server loads dynamically using separate classloaders.
+# Keep the list sorted by module names and then library names.
+# Note: For modules available in Q, DO NOT add new entries here.
+PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS := \
+    com.android.btservices:service-bluetooth \
+    com.android.os.statsd:service-statsd \
+    com.android.scheduling:service-scheduling \
+    com.android.tethering:service-connectivity \
+    com.android.uwb:service-uwb \
+    com.android.wifi:service-wifi \
+
 # Minimal configuration for running dex2oat (default argument values).
 # PRODUCT_USES_DEFAULT_ART_CONFIG must be true to enable boot image compilation.
 PRODUCT_USES_DEFAULT_ART_CONFIG := true
diff --git a/target/product/emulator_vendor.mk b/target/product/emulator_vendor.mk
index b9f33ab..f71b275 100644
--- a/target/product/emulator_vendor.mk
+++ b/target/product/emulator_vendor.mk
@@ -37,7 +37,7 @@
 
 #watchdog tiggers reboot because location service is not
 #responding, disble it for now.
-#still keep it on internal master as it is still working
+#still keep it on internal main (master) as it is still working
 #once it is fixed in aosp, remove this block of comment.
 #PRODUCT_VENDOR_PROPERTIES += \
 #config.disable_location=true
diff --git a/target/product/full_base.mk b/target/product/full_base.mk
index a8e1e91..39c66da3 100644
--- a/target/product/full_base.mk
+++ b/target/product/full_base.mk
@@ -28,12 +28,6 @@
     PhotoTable \
     preinstalled-packages-platform-full-base.xml
 
-# Bluetooth:
-#   audio.a2dp.default is a system module. Generic system image includes
-#   audio.a2dp.default to support A2DP if board has the capability.
-PRODUCT_PACKAGES += \
-    audio.a2dp.default
-
 # Net:
 #   Vendors can use the platform-provided network configuration utilities (ip,
 #   iptable, etc.) to configure the Linux networking stack, but these utilities
diff --git a/target/product/generic_ramdisk.mk b/target/product/generic_ramdisk.mk
index ae81329..fb0370e 100644
--- a/target/product/generic_ramdisk.mk
+++ b/target/product/generic_ramdisk.mk
@@ -22,9 +22,14 @@
 # Ramdisk
 PRODUCT_PACKAGES += \
     init_first_stage \
+    e2fsck.ramdisk \
+    fsck.f2fs.ramdisk \
+    tune2fs.ramdisk \
+    snapuserd.ramdisk \
 
 # Debug ramdisk
 PRODUCT_PACKAGES += \
+    adb_debug.prop \
     userdebug_plat_sepolicy.cil \
 
 _my_paths := \
diff --git a/target/product/generic_system.mk b/target/product/generic_system.mk
index f13c9db..1a639ef 100644
--- a/target/product/generic_system.mk
+++ b/target/product/generic_system.mk
@@ -52,11 +52,6 @@
     cppreopts.sh \
     otapreopt_script \
 
-# Bluetooth libraries
-PRODUCT_PACKAGES += \
-    audio.a2dp.default \
-    audio.hearing_aid.default \
-
 # For ringtones that rely on forward lock encryption
 PRODUCT_PACKAGES += libfwdlockengine
 
diff --git a/target/product/gsi/32.txt b/target/product/gsi/32.txt
new file mode 100644
index 0000000..971ec922
--- /dev/null
+++ b/target/product/gsi/32.txt
@@ -0,0 +1,223 @@
+LLNDK: libEGL.so
+LLNDK: libGLESv1_CM.so
+LLNDK: libGLESv2.so
+LLNDK: libGLESv3.so
+LLNDK: libRS.so
+LLNDK: libandroid_net.so
+LLNDK: libbinder_ndk.so
+LLNDK: libc.so
+LLNDK: libcgrouprc.so
+LLNDK: libdl.so
+LLNDK: libft2.so
+LLNDK: liblog.so
+LLNDK: libm.so
+LLNDK: libmediandk.so
+LLNDK: libnativewindow.so
+LLNDK: libneuralnetworks.so
+LLNDK: libselinux.so
+LLNDK: libsync.so
+LLNDK: libvndksupport.so
+LLNDK: libvulkan.so
+VNDK-SP: android.hardware.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.common.fmq-V1-ndk_platform.so
+VNDK-SP: android.hardware.graphics.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.graphics.common@1.0.so
+VNDK-SP: android.hardware.graphics.common@1.1.so
+VNDK-SP: android.hardware.graphics.common@1.2.so
+VNDK-SP: android.hardware.graphics.mapper@2.0.so
+VNDK-SP: android.hardware.graphics.mapper@2.1.so
+VNDK-SP: android.hardware.graphics.mapper@3.0.so
+VNDK-SP: android.hardware.graphics.mapper@4.0.so
+VNDK-SP: android.hardware.renderscript@1.0.so
+VNDK-SP: android.hidl.memory.token@1.0.so
+VNDK-SP: android.hidl.memory@1.0-impl.so
+VNDK-SP: android.hidl.memory@1.0.so
+VNDK-SP: android.hidl.safe_union@1.0.so
+VNDK-SP: libRSCpuRef.so
+VNDK-SP: libRSDriver.so
+VNDK-SP: libRS_internal.so
+VNDK-SP: libbacktrace.so
+VNDK-SP: libbase.so
+VNDK-SP: libbcinfo.so
+VNDK-SP: libblas.so
+VNDK-SP: libc++.so
+VNDK-SP: libcompiler_rt.so
+VNDK-SP: libcutils.so
+VNDK-SP: libdmabufheap.so
+VNDK-SP: libgralloctypes.so
+VNDK-SP: libhardware.so
+VNDK-SP: libhidlbase.so
+VNDK-SP: libhidlmemory.so
+VNDK-SP: libion.so
+VNDK-SP: libjsoncpp.so
+VNDK-SP: liblzma.so
+VNDK-SP: libprocessgroup.so
+VNDK-SP: libunwindstack.so
+VNDK-SP: libutils.so
+VNDK-SP: libutilscallstack.so
+VNDK-SP: libz.so
+VNDK-core: android.hardware.audio.common@2.0.so
+VNDK-core: android.hardware.authsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk_platform.so
+VNDK-core: android.hardware.configstore-utils.so
+VNDK-core: android.hardware.configstore@1.0.so
+VNDK-core: android.hardware.configstore@1.1.so
+VNDK-core: android.hardware.confirmationui-support-lib.so
+VNDK-core: android.hardware.gnss-V1-ndk_platform.so
+VNDK-core: android.hardware.graphics.allocator@2.0.so
+VNDK-core: android.hardware.graphics.allocator@3.0.so
+VNDK-core: android.hardware.graphics.allocator@4.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
+VNDK-core: android.hardware.identity-V3-ndk_platform.so
+VNDK-core: android.hardware.keymaster-V3-ndk_platform.so
+VNDK-core: android.hardware.light-V1-ndk_platform.so
+VNDK-core: android.hardware.media.bufferpool@2.0.so
+VNDK-core: android.hardware.media.omx@1.0.so
+VNDK-core: android.hardware.media@1.0.so
+VNDK-core: android.hardware.memtrack-V1-ndk_platform.so
+VNDK-core: android.hardware.memtrack@1.0.so
+VNDK-core: android.hardware.oemlock-V1-ndk_platform.so
+VNDK-core: android.hardware.power-V2-ndk_platform.so
+VNDK-core: android.hardware.power.stats-V1-ndk_platform.so
+VNDK-core: android.hardware.rebootescrow-V1-ndk_platform.so
+VNDK-core: android.hardware.security.keymint-V1-ndk_platform.so
+VNDK-core: android.hardware.security.secureclock-V1-ndk_platform.so
+VNDK-core: android.hardware.security.sharedsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.soundtrigger@2.0-core.so
+VNDK-core: android.hardware.soundtrigger@2.0.so
+VNDK-core: android.hardware.vibrator-V2-ndk_platform.so
+VNDK-core: android.hardware.weaver-V1-ndk_platform.so
+VNDK-core: android.hidl.token@1.0-utils.so
+VNDK-core: android.hidl.token@1.0.so
+VNDK-core: android.system.keystore2-V1-ndk_platform.so
+VNDK-core: android.system.suspend@1.0.so
+VNDK-core: libaudioroute.so
+VNDK-core: libaudioutils.so
+VNDK-core: libbinder.so
+VNDK-core: libbufferqueueconverter.so
+VNDK-core: libcamera_metadata.so
+VNDK-core: libcap.so
+VNDK-core: libcn-cbor.so
+VNDK-core: libcodec2.so
+VNDK-core: libcrypto.so
+VNDK-core: libcrypto_utils.so
+VNDK-core: libcurl.so
+VNDK-core: libdiskconfig.so
+VNDK-core: libdumpstateutil.so
+VNDK-core: libevent.so
+VNDK-core: libexif.so
+VNDK-core: libexpat.so
+VNDK-core: libfmq.so
+VNDK-core: libgatekeeper.so
+VNDK-core: libgui.so
+VNDK-core: libhardware_legacy.so
+VNDK-core: libhidlallocatorutils.so
+VNDK-core: libjpeg.so
+VNDK-core: libldacBT_abr.so
+VNDK-core: libldacBT_enc.so
+VNDK-core: liblz4.so
+VNDK-core: libmedia_helper.so
+VNDK-core: libmedia_omx.so
+VNDK-core: libmemtrack.so
+VNDK-core: libminijail.so
+VNDK-core: libmkbootimg_abi_check.so
+VNDK-core: libnetutils.so
+VNDK-core: libnl.so
+VNDK-core: libpcre2.so
+VNDK-core: libpiex.so
+VNDK-core: libpng.so
+VNDK-core: libpower.so
+VNDK-core: libprocinfo.so
+VNDK-core: libradio_metadata.so
+VNDK-core: libspeexresampler.so
+VNDK-core: libsqlite.so
+VNDK-core: libssl.so
+VNDK-core: libstagefright_bufferpool@2.0.so
+VNDK-core: libstagefright_bufferqueue_helper.so
+VNDK-core: libstagefright_foundation.so
+VNDK-core: libstagefright_omx.so
+VNDK-core: libstagefright_omx_utils.so
+VNDK-core: libstagefright_xmlparser.so
+VNDK-core: libsysutils.so
+VNDK-core: libtinyalsa.so
+VNDK-core: libtinyxml2.so
+VNDK-core: libui.so
+VNDK-core: libusbhost.so
+VNDK-core: libwifi-system-iface.so
+VNDK-core: libxml2.so
+VNDK-core: libyuv.so
+VNDK-core: libziparchive.so
+VNDK-private: libbacktrace.so
+VNDK-private: libblas.so
+VNDK-private: libcompiler_rt.so
+VNDK-private: libft2.so
+VNDK-private: libgui.so
+VNDK-product: android.hardware.audio.common@2.0.so
+VNDK-product: android.hardware.configstore@1.0.so
+VNDK-product: android.hardware.configstore@1.1.so
+VNDK-product: android.hardware.graphics.allocator@2.0.so
+VNDK-product: android.hardware.graphics.allocator@3.0.so
+VNDK-product: android.hardware.graphics.allocator@4.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-product: android.hardware.graphics.common@1.0.so
+VNDK-product: android.hardware.graphics.common@1.1.so
+VNDK-product: android.hardware.graphics.common@1.2.so
+VNDK-product: android.hardware.graphics.mapper@2.0.so
+VNDK-product: android.hardware.graphics.mapper@2.1.so
+VNDK-product: android.hardware.graphics.mapper@3.0.so
+VNDK-product: android.hardware.graphics.mapper@4.0.so
+VNDK-product: android.hardware.media.bufferpool@2.0.so
+VNDK-product: android.hardware.media.omx@1.0.so
+VNDK-product: android.hardware.media@1.0.so
+VNDK-product: android.hardware.memtrack@1.0.so
+VNDK-product: android.hardware.renderscript@1.0.so
+VNDK-product: android.hardware.soundtrigger@2.0.so
+VNDK-product: android.hidl.memory.token@1.0.so
+VNDK-product: android.hidl.memory@1.0.so
+VNDK-product: android.hidl.safe_union@1.0.so
+VNDK-product: android.hidl.token@1.0.so
+VNDK-product: android.system.suspend@1.0.so
+VNDK-product: libaudioutils.so
+VNDK-product: libbacktrace.so
+VNDK-product: libbase.so
+VNDK-product: libc++.so
+VNDK-product: libcamera_metadata.so
+VNDK-product: libcap.so
+VNDK-product: libcompiler_rt.so
+VNDK-product: libcrypto.so
+VNDK-product: libcurl.so
+VNDK-product: libcutils.so
+VNDK-product: libevent.so
+VNDK-product: libexpat.so
+VNDK-product: libfmq.so
+VNDK-product: libhidlbase.so
+VNDK-product: libhidlmemory.so
+VNDK-product: libion.so
+VNDK-product: libjpeg.so
+VNDK-product: libjsoncpp.so
+VNDK-product: libldacBT_abr.so
+VNDK-product: libldacBT_enc.so
+VNDK-product: liblz4.so
+VNDK-product: liblzma.so
+VNDK-product: libminijail.so
+VNDK-product: libnl.so
+VNDK-product: libpcre2.so
+VNDK-product: libpiex.so
+VNDK-product: libpng.so
+VNDK-product: libprocessgroup.so
+VNDK-product: libprocinfo.so
+VNDK-product: libspeexresampler.so
+VNDK-product: libssl.so
+VNDK-product: libtinyalsa.so
+VNDK-product: libtinyxml2.so
+VNDK-product: libunwindstack.so
+VNDK-product: libutils.so
+VNDK-product: libutilscallstack.so
+VNDK-product: libwifi-system-iface.so
+VNDK-product: libxml2.so
+VNDK-product: libyuv.so
+VNDK-product: libz.so
+VNDK-product: libziparchive.so
diff --git a/target/product/gsi/33.txt b/target/product/gsi/33.txt
new file mode 100644
index 0000000..03a143d
--- /dev/null
+++ b/target/product/gsi/33.txt
@@ -0,0 +1,254 @@
+LLNDK: libEGL.so
+LLNDK: libGLESv1_CM.so
+LLNDK: libGLESv2.so
+LLNDK: libGLESv3.so
+LLNDK: libRS.so
+LLNDK: libandroid_net.so
+LLNDK: libbinder_ndk.so
+LLNDK: libc.so
+LLNDK: libcgrouprc.so
+LLNDK: libdl.so
+LLNDK: libft2.so
+LLNDK: liblog.so
+LLNDK: libm.so
+LLNDK: libmediandk.so
+LLNDK: libnativewindow.so
+LLNDK: libneuralnetworks.so
+LLNDK: libselinux.so
+LLNDK: libsync.so
+LLNDK: libvndksupport.so
+LLNDK: libvulkan.so
+VNDK-SP: android.hardware.common-V2-ndk.so
+VNDK-SP: android.hardware.common.fmq-V1-ndk.so
+VNDK-SP: android.hardware.graphics.allocator-V1-ndk.so
+VNDK-SP: android.hardware.graphics.common-V3-ndk.so
+VNDK-SP: android.hardware.graphics.common@1.0.so
+VNDK-SP: android.hardware.graphics.common@1.1.so
+VNDK-SP: android.hardware.graphics.common@1.2.so
+VNDK-SP: android.hardware.graphics.composer3-V1-ndk.so
+VNDK-SP: android.hardware.graphics.mapper@2.0.so
+VNDK-SP: android.hardware.graphics.mapper@2.1.so
+VNDK-SP: android.hardware.graphics.mapper@3.0.so
+VNDK-SP: android.hardware.graphics.mapper@4.0.so
+VNDK-SP: android.hardware.renderscript@1.0.so
+VNDK-SP: android.hidl.memory.token@1.0.so
+VNDK-SP: android.hidl.memory@1.0-impl.so
+VNDK-SP: android.hidl.memory@1.0.so
+VNDK-SP: android.hidl.safe_union@1.0.so
+VNDK-SP: libRSCpuRef.so
+VNDK-SP: libRSDriver.so
+VNDK-SP: libRS_internal.so
+VNDK-SP: libbacktrace.so
+VNDK-SP: libbase.so
+VNDK-SP: libbcinfo.so
+VNDK-SP: libblas.so
+VNDK-SP: libc++.so
+VNDK-SP: libcompiler_rt.so
+VNDK-SP: libcutils.so
+VNDK-SP: libdmabufheap.so
+VNDK-SP: libgralloctypes.so
+VNDK-SP: libhardware.so
+VNDK-SP: libhidlbase.so
+VNDK-SP: libhidlmemory.so
+VNDK-SP: libion.so
+VNDK-SP: libjsoncpp.so
+VNDK-SP: liblzma.so
+VNDK-SP: libprocessgroup.so
+VNDK-SP: libunwindstack.so
+VNDK-SP: libutils.so
+VNDK-SP: libutilscallstack.so
+VNDK-SP: libz.so
+VNDK-core: android.hardware.audio.common-V1-ndk.so
+VNDK-core: android.hardware.audio.common@2.0.so
+VNDK-core: android.hardware.authsecret-V1-ndk.so
+VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk.so
+VNDK-core: android.hardware.bluetooth.audio-V2-ndk.so
+VNDK-core: android.hardware.camera.common-V1-ndk.so
+VNDK-core: android.hardware.camera.device-V1-ndk.so
+VNDK-core: android.hardware.camera.metadata-V1-ndk.so
+VNDK-core: android.hardware.camera.provider-V1-ndk.so
+VNDK-core: android.hardware.configstore-utils.so
+VNDK-core: android.hardware.configstore@1.0.so
+VNDK-core: android.hardware.configstore@1.1.so
+VNDK-core: android.hardware.confirmationui-support-lib.so
+VNDK-core: android.hardware.drm-V1-ndk.so
+VNDK-core: android.hardware.dumpstate-V1-ndk.so
+VNDK-core: android.hardware.gnss-V2-ndk.so
+VNDK-core: android.hardware.graphics.allocator@2.0.so
+VNDK-core: android.hardware.graphics.allocator@3.0.so
+VNDK-core: android.hardware.graphics.allocator@4.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-core: android.hardware.health-V1-ndk.so
+VNDK-core: android.hardware.health.storage-V1-ndk.so
+VNDK-core: android.hardware.identity-V4-ndk.so
+VNDK-core: android.hardware.ir-V1-ndk.so
+VNDK-core: android.hardware.keymaster-V3-ndk.so
+VNDK-core: android.hardware.light-V2-ndk.so
+VNDK-core: android.hardware.media.bufferpool@2.0.so
+VNDK-core: android.hardware.media.omx@1.0.so
+VNDK-core: android.hardware.media@1.0.so
+VNDK-core: android.hardware.memtrack-V1-ndk.so
+VNDK-core: android.hardware.memtrack@1.0.so
+VNDK-core: android.hardware.nfc-V1-ndk.so
+VNDK-core: android.hardware.oemlock-V1-ndk.so
+VNDK-core: android.hardware.power-V3-ndk.so
+VNDK-core: android.hardware.power.stats-V1-ndk.so
+VNDK-core: android.hardware.radio-V1-ndk.so
+VNDK-core: android.hardware.radio.config-V1-ndk.so
+VNDK-core: android.hardware.radio.data-V1-ndk.so
+VNDK-core: android.hardware.radio.messaging-V1-ndk.so
+VNDK-core: android.hardware.radio.modem-V1-ndk.so
+VNDK-core: android.hardware.radio.network-V1-ndk.so
+VNDK-core: android.hardware.radio.sim-V1-ndk.so
+VNDK-core: android.hardware.radio.voice-V1-ndk.so
+VNDK-core: android.hardware.rebootescrow-V1-ndk.so
+VNDK-core: android.hardware.security.dice-V1-ndk.so
+VNDK-core: android.hardware.security.keymint-V2-ndk.so
+VNDK-core: android.hardware.security.secureclock-V1-ndk.so
+VNDK-core: android.hardware.security.sharedsecret-V1-ndk.so
+VNDK-core: android.hardware.sensors-V1-ndk.so
+VNDK-core: android.hardware.soundtrigger3-V1-ndk.so
+VNDK-core: android.hardware.soundtrigger@2.0-core.so
+VNDK-core: android.hardware.soundtrigger@2.0.so
+VNDK-core: android.hardware.usb-V1-ndk.so
+VNDK-core: android.hardware.uwb-V1-ndk.so
+VNDK-core: android.hardware.vibrator-V2-ndk.so
+VNDK-core: android.hardware.weaver-V1-ndk.so
+VNDK-core: android.hardware.wifi.hostapd-V1-ndk.so
+VNDK-core: android.hardware.wifi.supplicant-V1-ndk.so
+VNDK-core: android.hidl.token@1.0-utils.so
+VNDK-core: android.hidl.token@1.0.so
+VNDK-core: android.media.audio.common.types-V1-ndk.so
+VNDK-core: android.media.soundtrigger.types-V1-ndk.so
+VNDK-core: android.system.keystore2-V2-ndk.so
+VNDK-core: android.system.suspend-V1-ndk.so
+VNDK-core: android.system.suspend@1.0.so
+VNDK-core: libaudioroute.so
+VNDK-core: libaudioutils.so
+VNDK-core: libbinder.so
+VNDK-core: libbufferqueueconverter.so
+VNDK-core: libcamera_metadata.so
+VNDK-core: libcap.so
+VNDK-core: libcn-cbor.so
+VNDK-core: libcodec2.so
+VNDK-core: libcrypto.so
+VNDK-core: libcrypto_utils.so
+VNDK-core: libcurl.so
+VNDK-core: libdiskconfig.so
+VNDK-core: libdumpstateutil.so
+VNDK-core: libevent.so
+VNDK-core: libexif.so
+VNDK-core: libexpat.so
+VNDK-core: libfmq.so
+VNDK-core: libgatekeeper.so
+VNDK-core: libgui.so
+VNDK-core: libhardware_legacy.so
+VNDK-core: libhidlallocatorutils.so
+VNDK-core: libjpeg.so
+VNDK-core: libldacBT_abr.so
+VNDK-core: libldacBT_enc.so
+VNDK-core: liblz4.so
+VNDK-core: libmedia_helper.so
+VNDK-core: libmedia_omx.so
+VNDK-core: libmemtrack.so
+VNDK-core: libminijail.so
+VNDK-core: libmkbootimg_abi_check.so
+VNDK-core: libnetutils.so
+VNDK-core: libnl.so
+VNDK-core: libpcre2.so
+VNDK-core: libpiex.so
+VNDK-core: libpng.so
+VNDK-core: libpower.so
+VNDK-core: libprocinfo.so
+VNDK-core: libradio_metadata.so
+VNDK-core: libspeexresampler.so
+VNDK-core: libsqlite.so
+VNDK-core: libssl.so
+VNDK-core: libstagefright_bufferpool@2.0.so
+VNDK-core: libstagefright_bufferqueue_helper.so
+VNDK-core: libstagefright_foundation.so
+VNDK-core: libstagefright_omx.so
+VNDK-core: libstagefright_omx_utils.so
+VNDK-core: libstagefright_xmlparser.so
+VNDK-core: libsysutils.so
+VNDK-core: libtinyalsa.so
+VNDK-core: libtinyxml2.so
+VNDK-core: libui.so
+VNDK-core: libusbhost.so
+VNDK-core: libwifi-system-iface.so
+VNDK-core: libxml2.so
+VNDK-core: libyuv.so
+VNDK-core: libziparchive.so
+VNDK-private: libbacktrace.so
+VNDK-private: libblas.so
+VNDK-private: libcompiler_rt.so
+VNDK-private: libft2.so
+VNDK-private: libgui.so
+VNDK-product: android.hardware.audio.common@2.0.so
+VNDK-product: android.hardware.configstore@1.0.so
+VNDK-product: android.hardware.configstore@1.1.so
+VNDK-product: android.hardware.graphics.allocator@2.0.so
+VNDK-product: android.hardware.graphics.allocator@3.0.so
+VNDK-product: android.hardware.graphics.allocator@4.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@1.0.so
+VNDK-product: android.hardware.graphics.bufferqueue@2.0.so
+VNDK-product: android.hardware.graphics.common@1.0.so
+VNDK-product: android.hardware.graphics.common@1.1.so
+VNDK-product: android.hardware.graphics.common@1.2.so
+VNDK-product: android.hardware.graphics.mapper@2.0.so
+VNDK-product: android.hardware.graphics.mapper@2.1.so
+VNDK-product: android.hardware.graphics.mapper@3.0.so
+VNDK-product: android.hardware.graphics.mapper@4.0.so
+VNDK-product: android.hardware.media.bufferpool@2.0.so
+VNDK-product: android.hardware.media.omx@1.0.so
+VNDK-product: android.hardware.media@1.0.so
+VNDK-product: android.hardware.memtrack@1.0.so
+VNDK-product: android.hardware.renderscript@1.0.so
+VNDK-product: android.hardware.soundtrigger@2.0.so
+VNDK-product: android.hidl.memory.token@1.0.so
+VNDK-product: android.hidl.memory@1.0.so
+VNDK-product: android.hidl.safe_union@1.0.so
+VNDK-product: android.hidl.token@1.0.so
+VNDK-product: android.system.suspend@1.0.so
+VNDK-product: libaudioutils.so
+VNDK-product: libbacktrace.so
+VNDK-product: libbase.so
+VNDK-product: libc++.so
+VNDK-product: libcamera_metadata.so
+VNDK-product: libcap.so
+VNDK-product: libcompiler_rt.so
+VNDK-product: libcrypto.so
+VNDK-product: libcurl.so
+VNDK-product: libcutils.so
+VNDK-product: libevent.so
+VNDK-product: libexpat.so
+VNDK-product: libfmq.so
+VNDK-product: libhidlbase.so
+VNDK-product: libhidlmemory.so
+VNDK-product: libion.so
+VNDK-product: libjpeg.so
+VNDK-product: libjsoncpp.so
+VNDK-product: libldacBT_abr.so
+VNDK-product: libldacBT_enc.so
+VNDK-product: liblz4.so
+VNDK-product: liblzma.so
+VNDK-product: libminijail.so
+VNDK-product: libnl.so
+VNDK-product: libpcre2.so
+VNDK-product: libpiex.so
+VNDK-product: libpng.so
+VNDK-product: libprocessgroup.so
+VNDK-product: libprocinfo.so
+VNDK-product: libspeexresampler.so
+VNDK-product: libssl.so
+VNDK-product: libtinyalsa.so
+VNDK-product: libtinyxml2.so
+VNDK-product: libunwindstack.so
+VNDK-product: libutils.so
+VNDK-product: libutilscallstack.so
+VNDK-product: libwifi-system-iface.so
+VNDK-product: libxml2.so
+VNDK-product: libyuv.so
+VNDK-product: libz.so
+VNDK-product: libziparchive.so
diff --git a/target/product/gsi/Android.bp b/target/product/gsi/Android.bp
index 88472eb..a8af9c4 100644
--- a/target/product/gsi/Android.bp
+++ b/target/product/gsi/Android.bp
@@ -14,11 +14,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 filegroup {
diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk
index cb4fdcb..85e551d 100644
--- a/target/product/gsi/Android.mk
+++ b/target/product/gsi/Android.mk
@@ -50,11 +50,21 @@
 _vndk_check_failure_message += "       Run \`update-vndk-list.sh\` to update $(LATEST_VNDK_LIB_LIST)"
 endif
 
+# The *-ndk_platform.so libraries no longer exist and are removed from the VNDK set. However, they
+# can exist if NEED_AIDL_NDK_PLATFORM_BACKEND is set to true for legacy devices. Don't be bothered
+# with the extraneous libraries.
+ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
+	_READ_INTERNAL_VNDK_LIB_LIST := sed /ndk_platform.so/d $(INTERNAL_VNDK_LIB_LIST)
+else
+	_READ_INTERNAL_VNDK_LIB_LIST := cat $(INTERNAL_VNDK_LIB_LIST)
+endif
+
 $(check-vndk-list-timestamp): $(INTERNAL_VNDK_LIB_LIST) $(LATEST_VNDK_LIB_LIST) $(HOST_OUT_EXECUTABLES)/update-vndk-list.sh
-	$(hide) ( diff --old-line-format="Removed %L" \
+	$(hide) ($(_READ_INTERNAL_VNDK_LIB_LIST) | sort | \
+	diff --old-line-format="Removed %L" \
 	  --new-line-format="Added %L" \
 	  --unchanged-line-format="" \
-	  $(LATEST_VNDK_LIB_LIST) $(INTERNAL_VNDK_LIB_LIST) \
+	  <(cat $(LATEST_VNDK_LIB_LIST) | sort) - \
 	  || ( echo -e $(_vndk_check_failure_message); exit 1 ))
 	$(hide) mkdir -p $(dir $@)
 	$(hide) touch $@
@@ -63,8 +73,9 @@
 # Script to update the latest VNDK lib list
 include $(CLEAR_VARS)
 LOCAL_MODULE := update-vndk-list.sh
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
 LOCAL_IS_HOST_MODULE := true
@@ -84,9 +95,13 @@
 	        echo "  echo Run lunch or choosecombo first" >> $@; \
 	        echo "  exit 1" >> $@; \
 	        echo "fi" >> $@; \
-	        echo "cd \$${ANDROID_BUILD_TOP}" >> $@; \
-	        echo "cp $(PRIVATE_INTERNAL_VNDK_LIB_LIST) $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@; \
-	        echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@
+	        echo "cd \$${ANDROID_BUILD_TOP}" >> $@
+ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
+	$(hide) echo "sed /ndk_platform.so/d $(PRIVATE_INTERNAL_VNDK_LIB_LIST) > $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
+else
+	$(hide) echo "cp $(PRIVATE_INTERNAL_VNDK_LIB_LIST) $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
+endif
+	$(hide) echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@
 endif
 	@chmod a+x $@
 
@@ -156,8 +171,9 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := vndk_package
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 # Filter LLNDK libs moved to APEX to avoid pulling them into /system/LIB
 LOCAL_REQUIRED_MODULES := \
     $(filter-out $(LLNDK_MOVED_TO_APEX_LIBRARIES),$(LLNDK_LIBRARIES))
@@ -181,8 +197,9 @@
 	_vndk_versions += $(BOARD_VNDK_VERSION)
 endif
 LOCAL_MODULE := vndk_apex_snapshot_package
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(foreach vndk_ver,$(_vndk_versions),com.android.vndk.v$(vndk_ver))
 include $(BUILD_PHONY_PACKAGE)
 
@@ -195,8 +212,9 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := gsi_skip_mount.cfg
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_STEM := skip_mount.cfg
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
@@ -220,8 +238,9 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := init.gsi.rc
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_SYSTEM_EXT_MODULE := true
@@ -232,11 +251,12 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := init.vndk-nodef.rc
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_SYSTEM_EXT_MODULE := true
-LOCAL_MODULE_RELATIVE_PATH := init
+LOCAL_MODULE_RELATIVE_PATH := gsi
 
 include $(BUILD_PREBUILT)
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 971ec922..03a143d 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -18,12 +18,14 @@
 LLNDK: libsync.so
 LLNDK: libvndksupport.so
 LLNDK: libvulkan.so
-VNDK-SP: android.hardware.common-V2-ndk_platform.so
-VNDK-SP: android.hardware.common.fmq-V1-ndk_platform.so
-VNDK-SP: android.hardware.graphics.common-V2-ndk_platform.so
+VNDK-SP: android.hardware.common-V2-ndk.so
+VNDK-SP: android.hardware.common.fmq-V1-ndk.so
+VNDK-SP: android.hardware.graphics.allocator-V1-ndk.so
+VNDK-SP: android.hardware.graphics.common-V3-ndk.so
 VNDK-SP: android.hardware.graphics.common@1.0.so
 VNDK-SP: android.hardware.graphics.common@1.1.so
 VNDK-SP: android.hardware.graphics.common@1.2.so
+VNDK-SP: android.hardware.graphics.composer3-V1-ndk.so
 VNDK-SP: android.hardware.graphics.mapper@2.0.so
 VNDK-SP: android.hardware.graphics.mapper@2.1.so
 VNDK-SP: android.hardware.graphics.mapper@3.0.so
@@ -56,42 +58,71 @@
 VNDK-SP: libutils.so
 VNDK-SP: libutilscallstack.so
 VNDK-SP: libz.so
+VNDK-core: android.hardware.audio.common-V1-ndk.so
 VNDK-core: android.hardware.audio.common@2.0.so
-VNDK-core: android.hardware.authsecret-V1-ndk_platform.so
-VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk_platform.so
+VNDK-core: android.hardware.authsecret-V1-ndk.so
+VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk.so
+VNDK-core: android.hardware.bluetooth.audio-V2-ndk.so
+VNDK-core: android.hardware.camera.common-V1-ndk.so
+VNDK-core: android.hardware.camera.device-V1-ndk.so
+VNDK-core: android.hardware.camera.metadata-V1-ndk.so
+VNDK-core: android.hardware.camera.provider-V1-ndk.so
 VNDK-core: android.hardware.configstore-utils.so
 VNDK-core: android.hardware.configstore@1.0.so
 VNDK-core: android.hardware.configstore@1.1.so
 VNDK-core: android.hardware.confirmationui-support-lib.so
-VNDK-core: android.hardware.gnss-V1-ndk_platform.so
+VNDK-core: android.hardware.drm-V1-ndk.so
+VNDK-core: android.hardware.dumpstate-V1-ndk.so
+VNDK-core: android.hardware.gnss-V2-ndk.so
 VNDK-core: android.hardware.graphics.allocator@2.0.so
 VNDK-core: android.hardware.graphics.allocator@3.0.so
 VNDK-core: android.hardware.graphics.allocator@4.0.so
 VNDK-core: android.hardware.graphics.bufferqueue@1.0.so
 VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
-VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
-VNDK-core: android.hardware.identity-V3-ndk_platform.so
-VNDK-core: android.hardware.keymaster-V3-ndk_platform.so
-VNDK-core: android.hardware.light-V1-ndk_platform.so
+VNDK-core: android.hardware.health-V1-ndk.so
+VNDK-core: android.hardware.health.storage-V1-ndk.so
+VNDK-core: android.hardware.identity-V4-ndk.so
+VNDK-core: android.hardware.ir-V1-ndk.so
+VNDK-core: android.hardware.keymaster-V3-ndk.so
+VNDK-core: android.hardware.light-V2-ndk.so
 VNDK-core: android.hardware.media.bufferpool@2.0.so
 VNDK-core: android.hardware.media.omx@1.0.so
 VNDK-core: android.hardware.media@1.0.so
-VNDK-core: android.hardware.memtrack-V1-ndk_platform.so
+VNDK-core: android.hardware.memtrack-V1-ndk.so
 VNDK-core: android.hardware.memtrack@1.0.so
-VNDK-core: android.hardware.oemlock-V1-ndk_platform.so
-VNDK-core: android.hardware.power-V2-ndk_platform.so
-VNDK-core: android.hardware.power.stats-V1-ndk_platform.so
-VNDK-core: android.hardware.rebootescrow-V1-ndk_platform.so
-VNDK-core: android.hardware.security.keymint-V1-ndk_platform.so
-VNDK-core: android.hardware.security.secureclock-V1-ndk_platform.so
-VNDK-core: android.hardware.security.sharedsecret-V1-ndk_platform.so
+VNDK-core: android.hardware.nfc-V1-ndk.so
+VNDK-core: android.hardware.oemlock-V1-ndk.so
+VNDK-core: android.hardware.power-V3-ndk.so
+VNDK-core: android.hardware.power.stats-V1-ndk.so
+VNDK-core: android.hardware.radio-V1-ndk.so
+VNDK-core: android.hardware.radio.config-V1-ndk.so
+VNDK-core: android.hardware.radio.data-V1-ndk.so
+VNDK-core: android.hardware.radio.messaging-V1-ndk.so
+VNDK-core: android.hardware.radio.modem-V1-ndk.so
+VNDK-core: android.hardware.radio.network-V1-ndk.so
+VNDK-core: android.hardware.radio.sim-V1-ndk.so
+VNDK-core: android.hardware.radio.voice-V1-ndk.so
+VNDK-core: android.hardware.rebootescrow-V1-ndk.so
+VNDK-core: android.hardware.security.dice-V1-ndk.so
+VNDK-core: android.hardware.security.keymint-V2-ndk.so
+VNDK-core: android.hardware.security.secureclock-V1-ndk.so
+VNDK-core: android.hardware.security.sharedsecret-V1-ndk.so
+VNDK-core: android.hardware.sensors-V1-ndk.so
+VNDK-core: android.hardware.soundtrigger3-V1-ndk.so
 VNDK-core: android.hardware.soundtrigger@2.0-core.so
 VNDK-core: android.hardware.soundtrigger@2.0.so
-VNDK-core: android.hardware.vibrator-V2-ndk_platform.so
-VNDK-core: android.hardware.weaver-V1-ndk_platform.so
+VNDK-core: android.hardware.usb-V1-ndk.so
+VNDK-core: android.hardware.uwb-V1-ndk.so
+VNDK-core: android.hardware.vibrator-V2-ndk.so
+VNDK-core: android.hardware.weaver-V1-ndk.so
+VNDK-core: android.hardware.wifi.hostapd-V1-ndk.so
+VNDK-core: android.hardware.wifi.supplicant-V1-ndk.so
 VNDK-core: android.hidl.token@1.0-utils.so
 VNDK-core: android.hidl.token@1.0.so
-VNDK-core: android.system.keystore2-V1-ndk_platform.so
+VNDK-core: android.media.audio.common.types-V1-ndk.so
+VNDK-core: android.media.soundtrigger.types-V1-ndk.so
+VNDK-core: android.system.keystore2-V2-ndk.so
+VNDK-core: android.system.suspend-V1-ndk.so
 VNDK-core: android.system.suspend@1.0.so
 VNDK-core: libaudioroute.so
 VNDK-core: libaudioutils.so
diff --git a/target/product/gsi/init.gsi.rc b/target/product/gsi/init.gsi.rc
index f482843..69c8e46 100644
--- a/target/product/gsi/init.gsi.rc
+++ b/target/product/gsi/init.gsi.rc
@@ -2,4 +2,4 @@
 # Android init script for GSI required initialization
 #
 
-import /system/system_ext/etc/init/init.vndk-${ro.vndk.version:-nodef}.rc
+import /system/system_ext/etc/gsi/init.vndk-${ro.vndk.version:-nodef}.rc
diff --git a/target/product/gsi/init.vndk-nodef.rc b/target/product/gsi/init.vndk-nodef.rc
index efeef11..1b141a0 100644
--- a/target/product/gsi/init.vndk-nodef.rc
+++ b/target/product/gsi/init.vndk-nodef.rc
@@ -1,3 +1,3 @@
 on early-init
-    # Must define BOARD_VNDK_VERSION
+    # Reboot if BOARD_VNDK_VERSION is not defined
     exec - root -- /system/bin/reboot bootloader
diff --git a/target/product/gsi_keys.mk b/target/product/gsi_keys.mk
deleted file mode 100644
index 5a814db..0000000
--- a/target/product/gsi_keys.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# 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.
-#
-
-# Include GSI keys into first-stage ramdisk, so we can enable verified
-# boot when booting a GSI.
-PRODUCT_PACKAGES += \
-    q-gsi.avbpubkey \
-    r-gsi.avbpubkey \
-    s-gsi.avbpubkey \
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index d924d0b..74501cd 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -34,7 +34,7 @@
 # GSI should always support up-to-date platform features.
 # Keep this value at the latest API level to ensure latest build system
 # default configs are applied.
-PRODUCT_SHIPPING_API_LEVEL := 30
+PRODUCT_SHIPPING_API_LEVEL := 31
 
 # Enable dynamic partitions to facilitate mixing onto Cuttlefish
 PRODUCT_USE_DYNAMIC_PARTITIONS := true
@@ -62,15 +62,27 @@
     init.gsi.rc \
     init.vndk-nodef.rc \
 
-# Support additional P, Q and R VNDK packages
-PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30
+# Support additional VNDK snapshots
+PRODUCT_EXTRA_VNDK_VERSIONS := \
+    28 \
+    29 \
+    30 \
+    31 \
+    32 \
 
 # Do not build non-GSI partition images.
 PRODUCT_BUILD_CACHE_IMAGE := false
+PRODUCT_BUILD_DEBUG_BOOT_IMAGE := false
+PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE := false
 PRODUCT_BUILD_USERDATA_IMAGE := false
 PRODUCT_BUILD_VENDOR_IMAGE := false
 PRODUCT_BUILD_SUPER_PARTITION := false
 PRODUCT_BUILD_SUPER_EMPTY_IMAGE := false
+PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST := true
 
 # Always build modules from source
 MODULE_BUILD_FROM_SOURCE := true
+
+# Additional settings used in all GSI builds
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.crypto.metadata_init_delete_all_keys.enabled=false \
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index b7a2d0d..41233b2 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -34,7 +34,6 @@
 PRODUCT_PACKAGES += \
     BasicDreams \
     BlockedNumberProvider \
-    Bluetooth \
     BluetoothMidiService \
     BookmarkProvider \
     BuiltInPrintService \
@@ -43,7 +42,6 @@
     CameraExtensionsProxy \
     CaptivePortalLogin \
     CertInstaller \
-    clatd \
     DocumentsUI \
     DownloadProviderUi \
     EasterEgg \
diff --git a/target/product/media_system.mk b/target/product/media_system.mk
index 30a8621..79bd74a 100644
--- a/target/product/media_system.mk
+++ b/target/product/media_system.mk
@@ -27,7 +27,6 @@
     com.android.media.remotedisplay.xml \
     CompanionDeviceManager \
     drmserver \
-    ethernet-service \
     fsck.f2fs \
     HTMLViewer \
     libfilterpack_imageproc \
@@ -51,8 +50,7 @@
 # The order here is the same order they end up on the classpath, so it matters.
 PRODUCT_SYSTEM_SERVER_JARS := \
     com.android.location.provider \
-    services \
-    ethernet-service
+    services
 
 PRODUCT_COPY_FILES += \
     system/core/rootdir/etc/public.libraries.android.txt:system/etc/public.libraries.txt
diff --git a/target/product/module_common.mk b/target/product/module_common.mk
index 03340db..54f3949 100644
--- a/target/product/module_common.mk
+++ b/target/product/module_common.mk
@@ -21,3 +21,7 @@
 # Enables treble, which enabled certain -D compilation flags. In particular, libhidlbase
 # uses -DENFORCE_VINTF_MANIFEST. See b/185759877
 PRODUCT_SHIPPING_API_LEVEL := 29
+
+# Builds using a module product should build modules from source, even if
+# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE says otherwise.
+PRODUCT_MODULE_BUILD_FROM_SOURCE := true
diff --git a/target/product/iorap_large_memory_config.mk b/target/product/ndk.mk
similarity index 65%
copy from target/product/iorap_large_memory_config.mk
copy to target/product/ndk.mk
index 0c6c89a..1dfd0db 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/target/product/ndk.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2020 The Android Open Source Project
+# 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.
@@ -12,3 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
+# This device is suitable for soong-only build that builds for all the architectures
+# needed for the ndk. It is not going to work for normal `lunch <foo> && m` workflows.
+
+PRODUCT_NAME := ndk
+PRODUCT_BRAND := Android
+PRODUCT_DEVICE := ndk
diff --git a/target/product/non_ab_device.mk b/target/product/non_ab_device.mk
new file mode 100644
index 0000000..6dc4506
--- /dev/null
+++ b/target/product/non_ab_device.mk
@@ -0,0 +1,5 @@
+# Packages to update the recovery partition, which will be installed on
+# /vendor. Don't install these unless they're needed.
+PRODUCT_PACKAGES += \
+    applypatch
+
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index b511aa6..b6560fc 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -61,7 +61,7 @@
   apex_test_module := art-check-release-apex-gen-fakebin
 endif
 
-ifeq (true,$(SOONG_CONFIG_art_module_source_build)
+ifeq (true,$(call soong_config_get,art_module,source_build))
   PRODUCT_HOST_PACKAGES += $(apex_test_module)
 endif
 
@@ -75,6 +75,14 @@
 PRODUCT_PACKAGES += \
     hiddenapi-package-whitelist.xml \
 
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+  # Don't depend on the framework boot image profile in unbundled builds where
+  # frameworks/base may not be present.
+  # TODO(b/179900989): We may not need this check once we stop using full
+  # platform products on the thin ART manifest branch.
+  PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION += frameworks/base/boot/boot-image-profile.txt
+endif
+
 # The dalvik.vm.dexopt.thermal-cutoff property must contain one of the values
 # listed here:
 #
@@ -93,7 +101,7 @@
     dalvik.vm.appimageformat=lz4
 
 PRODUCT_SYSTEM_PROPERTIES += \
-    ro.dalvik.vm.native.bridge=0
+    ro.dalvik.vm.native.bridge?=0
 
 # Different dexopt types for different package update/install times.
 # On eng builds, make "boot" reasons only extract for faster turnaround.
@@ -127,10 +135,6 @@
     pm.dexopt.cmdline?=verify \
     pm.dexopt.shared?=speed
 
-# Pass file with the list of updatable boot class path packages to dex2oat.
-PRODUCT_SYSTEM_PROPERTIES += \
-    dalvik.vm.dex2oat-updatable-bcp-packages-file=/system/etc/updatable-bcp-packages.txt
-
 # Enable resolution of startup const strings.
 PRODUCT_SYSTEM_PROPERTIES += \
     dalvik.vm.dex2oat-resolve-startup-strings=true
@@ -144,17 +148,6 @@
     dalvik.vm.minidebuginfo=true \
     dalvik.vm.dex2oat-minidebuginfo=true
 
-# Two other device configs are added to IORap besides "ro.iorapd.enable".
-# IORap by default is off and starts when
-# (https://source.corp.google.com/android/system/iorap/iorapd.rc?q=iorapd.rc)
-#
-# * "ro.iorapd.enable" is true excluding unset
-# * One of the device configs is true.
-#
-# "ro.iorapd.enable" has to be set to true, so that iorap can be started.
-PRODUCT_SYSTEM_PROPERTIES += \
-    ro.iorapd.enable?=true
-
 # Enable Madvising of the whole art, odex and vdex files to MADV_WILLNEED.
 # The size specified here is the size limit of how much of the file
 # (in bytes) is madvised.
diff --git a/target/product/sdk.mk b/target/product/sdk.mk
index 96d8cc9..fa7e1ad 100644
--- a/target/product/sdk.mk
+++ b/target/product/sdk.mk
@@ -14,8 +14,11 @@
 # limitations under the License.
 #
 
-# Don't modify this file - It's just an alias!
+# This is a simple product that uses configures the minimum amount
+# needed to build the SDK (without the emulator).
 
-$(call inherit-product, $(SRC_TARGET_DIR)/product/sdk_phone_armv7.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_default.mk)
 
 PRODUCT_NAME := sdk
+PRODUCT_BRAND := Android
+PRODUCT_DEVICE := mainline_x86
diff --git a/target/product/sdk_phone_arm64.mk b/target/product/sdk_phone_arm64.mk
index 0831b54..4203d45 100644
--- a/target/product/sdk_phone_arm64.mk
+++ b/target/product/sdk_phone_arm64.mk
@@ -50,10 +50,6 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/emulator_arm64/device.mk)
 
-# Define the host tools and libs that are parts of the SDK.
-$(call inherit-product, sdk/build/product_sdk.mk)
-$(call inherit-product, development/build/product_sdk.mk)
-
 # keep this apk for sdk targets for now
 PRODUCT_PACKAGES += \
     EmulatorSmokeTests
diff --git a/target/product/sdk_phone_armv7.mk b/target/product/sdk_phone_armv7.mk
index f649980..6c88b44 100644
--- a/target/product/sdk_phone_armv7.mk
+++ b/target/product/sdk_phone_armv7.mk
@@ -49,10 +49,6 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/emulator_arm/device.mk)
 
-# Define the host tools and libs that are parts of the SDK.
-$(call inherit-product, sdk/build/product_sdk.mk)
-$(call inherit-product, development/build/product_sdk.mk)
-
 # keep this apk for sdk targets for now
 PRODUCT_PACKAGES += \
     EmulatorSmokeTests
diff --git a/target/product/sdk_phone_x86.mk b/target/product/sdk_phone_x86.mk
index 0e1bca4..a324e5f 100644
--- a/target/product/sdk_phone_x86.mk
+++ b/target/product/sdk_phone_x86.mk
@@ -49,10 +49,6 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/emulator_x86/device.mk)
 
-# Define the host tools and libs that are parts of the SDK.
-$(call inherit-product-if-exists, sdk/build/product_sdk.mk)
-$(call inherit-product-if-exists, development/build/product_sdk.mk)
-
 # Overrides
 PRODUCT_BRAND := Android
 PRODUCT_NAME := sdk_phone_x86
diff --git a/target/product/sdk_phone_x86_64.mk b/target/product/sdk_phone_x86_64.mk
index fffac04..ff9018d 100644
--- a/target/product/sdk_phone_x86_64.mk
+++ b/target/product/sdk_phone_x86_64.mk
@@ -50,10 +50,6 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/emulator_x86_64/device.mk)
 
-# Define the host tools and libs that are parts of the SDK.
-$(call inherit-product-if-exists, sdk/build/product_sdk.mk)
-$(call inherit-product-if-exists, development/build/product_sdk.mk)
-
 # Overrides
 PRODUCT_BRAND := Android
 PRODUCT_NAME := sdk_phone_x86_64
diff --git a/target/product/security/Android.bp b/target/product/security/Android.bp
index 99f7742..1e26d59 100644
--- a/target/product/security/Android.bp
+++ b/target/product/security/Android.bp
@@ -1,11 +1,7 @@
 // AOSP test certificate
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_app_certificate {
diff --git a/target/product/security/Android.mk b/target/product/security/Android.mk
index cedad5b..ad25a92 100644
--- a/target/product/security/Android.mk
+++ b/target/product/security/Android.mk
@@ -5,8 +5,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := verity_key
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
@@ -26,8 +27,9 @@
 ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
   include $(CLEAR_VARS)
   LOCAL_MODULE := verity_key_ramdisk
-  LOCAL_LICENSE_KINDS := legacy_restricted
-  LOCAL_LICENSE_CONDITIONS := restricted
+  LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+  LOCAL_LICENSE_CONDITIONS := notice
+  LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
   LOCAL_MODULE_CLASS := ETC
   LOCAL_SRC_FILES := verity_key
   LOCAL_MODULE_STEM := verity_key
@@ -41,8 +43,9 @@
   ifneq ($(filter eng userdebug,$(TARGET_BUILD_VARIANT)),)
     include $(CLEAR_VARS)
     LOCAL_MODULE := adb_keys
-    LOCAL_LICENSE_KINDS := legacy_restricted
-    LOCAL_LICENSE_CONDITIONS := restricted
+    LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+    LOCAL_LICENSE_CONDITIONS := notice
+    LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
     LOCAL_MODULE_CLASS := ETC
     LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
     LOCAL_PREBUILT_MODULE_FILE := $(PRODUCT_ADB_KEYS)
@@ -57,15 +60,24 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := otacerts
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_STEM := otacerts.zip
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/security
 include $(BUILD_SYSTEM)/base_rules.mk
+
+extra_ota_keys := $(addsuffix .x509.pem,$(PRODUCT_EXTRA_OTA_KEYS))
+
 $(LOCAL_BUILT_MODULE): PRIVATE_CERT := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
-$(LOCAL_BUILT_MODULE): $(SOONG_ZIP) $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
-	$(SOONG_ZIP) -o $@ -j -symlinks=false -f $(PRIVATE_CERT)
+$(LOCAL_BUILT_MODULE): PRIVATE_EXTRA_OTA_KEYS := $(extra_ota_keys)
+$(LOCAL_BUILT_MODULE): \
+	    $(SOONG_ZIP) \
+	    $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem \
+	    $(extra_ota_keys)
+	$(SOONG_ZIP) -o $@ -j -symlinks=false \
+	    $(addprefix -f ,$(PRIVATE_CERT) $(PRIVATE_EXTRA_OTA_KEYS))
 
 
 #######################################
@@ -73,14 +85,15 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := otacerts.recovery
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_STEM := otacerts.zip
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/security
 include $(BUILD_SYSTEM)/base_rules.mk
 
-extra_recovery_keys := $(patsubst %,%.x509.pem,$(PRODUCT_EXTRA_RECOVERY_KEYS))
+extra_recovery_keys := $(addsuffix .x509.pem,$(PRODUCT_EXTRA_RECOVERY_KEYS))
 
 $(LOCAL_BUILT_MODULE): PRIVATE_CERT := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
 $(LOCAL_BUILT_MODULE): PRIVATE_EXTRA_RECOVERY_KEYS := $(extra_recovery_keys)
@@ -89,4 +102,4 @@
 	    $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem \
 	    $(extra_recovery_keys)
 	$(SOONG_ZIP) -o $@ -j -symlinks=false \
-	    $(foreach key_file, $(PRIVATE_CERT) $(PRIVATE_EXTRA_RECOVERY_KEYS), -f $(key_file))
+	    $(addprefix -f ,$(PRIVATE_CERT) $(PRIVATE_EXTRA_RECOVERY_KEYS))
diff --git a/target/product/security/bluetooth.pk8 b/target/product/security/bluetooth.pk8
new file mode 100644
index 0000000..c6ea434
--- /dev/null
+++ b/target/product/security/bluetooth.pk8
Binary files differ
diff --git a/target/product/security/bluetooth.x509.pem b/target/product/security/bluetooth.x509.pem
new file mode 100644
index 0000000..396d7c9
--- /dev/null
+++ b/target/product/security/bluetooth.x509.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGOzCCBCOgAwIBAgIUEiZapaWZVSter06CJMf2kHi8PIswDQYJKoZIhvcNAQEL
+BQAwgasxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMScwJQYDVQQDDB5jb20uYW5kcm9pZC5ibHVldG9vdGguc2VydmljZXMxIjAg
+BgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMjIwMzE1MDAzNjAz
+WhgPNDc2MDAyMDkwMDM2MDNaMIGrMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs
+aWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9p
+ZDEQMA4GA1UECwwHQW5kcm9pZDEnMCUGA1UEAwweY29tLmFuZHJvaWQuYmx1ZXRv
+b3RoLnNlcnZpY2VzMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsVlq9pozUREGlb8u8Y0A
+fYwPs5OuavNx/EsX03aTjmAXUfSOMAewqzUXDIRjw8UQvOW63utaZ0go9osDPzNf
+VEftmGxW/AUC+HWGaLDQfCYO3ficPPOS7xpEhGZERNbnhvh5qX0NBt6mJygsfpOm
+RPThbi6Ig2Brxh1eqVYqRkTjhNFKD6gCd1PdMmUSF88xEYaZWvTkET89Zh38lLza
+2x/wfNZmCSAVurNw1Kf9NQfYsaGHwMsjrvTyhG93TTYXzRBFzAO2WlBiw6R0tQr8
+ZW5XCM9Yo6AS0KXiU0ZWwOXxhGdr38rNd7j9nZtpFwWmN1kgeb/vpEfq0Ylua9By
+uURnfJZu2K4TbFamuyjihItra2ZKOtFNPDeuggKMCkuZz6WU8FCoMEpnq5P2agxN
+OGAa7ynXdNzek98N3TGX8qtfEgCv6vyuM0gakJ6D9nM43nsCm1LkB/JA0CacWyRz
+ljaLL1C4S43azEOYyOOb94ITnkZCQGtH33kxzamyPLIZ37VF4+v6yTXySLBzOnhe
+Os5uBIDohVJuI838bLhZf8e5mIrnjiKwsmExXiQvgidbwvZKCz9n8YT4iUhWPx4F
+W+GPcivZsvsECcnJ2QURK1zhir5QuLS7ZbAth4kiEUxJ6ujF5jftE+L/ClK2LiY0
+2IXWRCct8J1hfJZZx8lm3PUCAwEAAaNTMFEwHQYDVR0OBBYEFO5CgtQzKbTEd/Q9
+rxK14a9BBwFZMB8GA1UdIwQYMBaAFO5CgtQzKbTEd/Q9rxK14a9BBwFZMA8GA1Ud
+EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAGrGS1zmaoARVq7qhoY+xzSc
+1I/Tzf6vG6aHBC+CcIoSM2oqr6TGH+ADHAY6jhu/qzv1ij3gtoInAkBtkWvYsCIV
+eISPj8Qomcd8EIeW77p+ArKzS4HY5m1c/O4D/5rkl6c0exFq4Pdw9V8xyM98QtLd
+oj4xzzXUTPOIwkROHkj8otcML28m/MC0l/4b+flHnPqKFuLBjhxi9b/ZfwaXfjkx
+TcXpM3nPH8zN7kaJpS1fPW1IJyxJYvT022uK+afpezTmyS/50aOncUGjDJRw8CcO
+B88O8lpizDD3tD7P6jVOpRRJS4SnkVErbIn1xdWER6ubhnnycH7UmDVIx+vNd/t6
+YDa377au8Za+LnbDPfV1+Og+RaJSEIjJgfYyqnjBxGdRGN21VbqJdRzo/eO4ZFd2
+mGVtMosVr0jw4O8r60o9oMMWBTbFpxOI929QdcV+X1Lz8A8BZz0faXfZ2Z9usctu
+W2FtZge3tsJ07z7kuhNdbnm2yQVfd0FqiJsapUjlhgcdFVoDWPuqOfWAoG31ble6
+eiNnxfjiCckPWyciIE6lw97nvavGjlUacH5qVG86hOWU7xyBgeQ0PH4e+Nxr50yU
+A0GMxni1gefZFG8qEPdNRuDT1QdqDGh/8Ea11GEUMXdAxk0UzqyAtLDr6MbwK6lV
+mqmeueFdogdjvQ3mXe94
+-----END CERTIFICATE-----
diff --git a/target/product/security/sdk_sandbox.pk8 b/target/product/security/sdk_sandbox.pk8
new file mode 100644
index 0000000..23b880b
--- /dev/null
+++ b/target/product/security/sdk_sandbox.pk8
Binary files differ
diff --git a/target/product/security/sdk_sandbox.x509.pem b/target/product/security/sdk_sandbox.x509.pem
new file mode 100644
index 0000000..0bd20f3
--- /dev/null
+++ b/target/product/security/sdk_sandbox.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUMWJGQnrJU7zBEpPqv63u2HOlib0wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
+ZHJvaWQuY29tMB4XDTIxMTEwMjE3MDIxNFoXDTQ5MDMyMDE3MDIxNFowgZQxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
+biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
+VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09j3dyTxv8ojb4sXjrWX
+smXTYEez/u6X6po8+mWXp1xl1Y9xjYrxZROIE1MJL8aay8iYJihqx7RBWTPJYtYZ
+TLElA3dyQuMgDIKtlQR3QAMRoc2IKrkfcIboEs71xl78EnTSQfRJTUEFvNigzjfB
+e3JVtNDC9BR/33Iv9oNED84qW9C54h4TWHLyvo75unzPQUGS6uEIhhHa/8ynZZQW
+YEd0NwAQNqbcMdbN8Bn6sRRCidEOIPd8Uu8DtIofLi7/YMo4CH1Q5f5UQbtPtqU2
+m8fjQN9WYzMazvWltRE+HYDH9YnXCLAsVicNdmFhAlXri15nG2AiRnSrHu/panAc
+6wIDAQABo1MwUTAdBgNVHQ4EFgQU3F5r2DhJbRfkJKuqs1hjP/0dCUEwHwYDVR0j
+BBgwFoAU3F5r2DhJbRfkJKuqs1hjP/0dCUEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAwQQ8/D3f/WS5cwqcsFpT+Qzik9yTu53nsXz/pBDSbeM3
+zX1RCejXsmXhPjN7cu0uJYlrIuArOagHSC5pDci6GzcwunnnkRazSAmTpHLSRgeb
+cLgKHLCph9sulI1r82x9upF47zLlbfkTrtGJryej+yWJ2Ne8irJIPeNR0z0sTBWJ
+2Ngg55ezFWj3mihzw4Z6YU9txJB7Gj9eNYXdcubjoNs2mSU/6dR+HwJtD64FuH3x
+QLGMZscizCN8N6b5xayjwPsszQhaHI4iR4oGJ9prbDd0JoylwWr2LrQhYuWQCn20
+cG5YhrtZshj6f1eGV1TDYd8xziapilqwzrchARvP8g==
+-----END CERTIFICATE-----
diff --git a/target/product/telephony_vendor.mk b/target/product/telephony_vendor.mk
index 86dbcc9..94887cf 100644
--- a/target/product/telephony_vendor.mk
+++ b/target/product/telephony_vendor.mk
@@ -20,5 +20,3 @@
 # /vendor packages
 PRODUCT_PACKAGES := \
     rild \
-
-PRODUCT_COPY_FILES := \
diff --git a/target/product/virtual_ab_ota/android_t_baseline.mk b/target/product/virtual_ab_ota/android_t_baseline.mk
new file mode 100644
index 0000000..18e08e4
--- /dev/null
+++ b/target/product/virtual_ab_ota/android_t_baseline.mk
@@ -0,0 +1,52 @@
+#
+# 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.
+
+# This file enables baseline features, such as io_uring,
+# userspace merge, etc. But sets compression method to none.
+# This .mk file also removes snapuserd from vendor ramdisk,
+# as T launching devices will have init_boot which has snapuserd
+# in generic ramdisk.
+# T launching devices should include this .mk file, and configure
+# compression algorithm by setting
+# PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD to gz or brotli. Complete
+# set of supported algorithms can be found in
+# system/core/fs_mgr/libsnapshot/cow_writer.cpp
+
+PRODUCT_VIRTUAL_AB_OTA := true
+
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.enabled=true
+
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true
+
+PRODUCT_VIRTUAL_AB_COMPRESSION := true
+PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD ?= none
+PRODUCT_PACKAGES += \
+    snapuserd \
+
+# For dedicated recovery partitions, we need to include snapuserd
+# For GKI devices, BOARD_USES_RECOVERY_AS_BOOT is empty, but
+# so is BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT.
+ifdef BUILDING_RECOVERY_IMAGE
+ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+ifneq ($(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT),true)
+PRODUCT_PACKAGES += \
+    snapuserd.recovery
+endif
+endif
+endif
+
diff --git a/target/product/virtual_ab_ota/compression.mk b/target/product/virtual_ab_ota/compression.mk
index 8301047..d5bd2a5 100644
--- a/target/product/virtual_ab_ota/compression.mk
+++ b/target/product/virtual_ab_ota/compression.mk
@@ -17,6 +17,8 @@
 $(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk)
 
 PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true
 PRODUCT_VIRTUAL_AB_COMPRESSION := true
 PRODUCT_PACKAGES += \
     snapuserd.vendor_ramdisk \
diff --git a/target/product/iorap_large_memory_config.mk b/target/product/virtual_ab_ota/compression_with_xor.mk
similarity index 71%
copy from target/product/iorap_large_memory_config.mk
copy to target/product/virtual_ab_ota/compression_with_xor.mk
index 0c6c89a..7d92532 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/target/product/virtual_ab_ota/compression_with_xor.mk
@@ -1,4 +1,5 @@
-# Copyright (C) 2020 The Android Open Source Project
+#
+# Copyright (C) 2021 The Android Open-Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,3 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)
+
+
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true
diff --git a/target/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk b/target/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk
index bc81b33..de1f07d 100644
--- a/target/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk
+++ b/target/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk
@@ -24,3 +24,4 @@
 PRODUCT_PACKAGES += \
     linker.vendor_ramdisk \
     e2fsck.vendor_ramdisk \
+    fsck.f2fs.vendor_ramdisk \
diff --git a/target/product/iorap_large_memory_config.mk b/tests/artifact_path_requirements/inherit1.rbc
similarity index 63%
copy from target/product/iorap_large_memory_config.mk
copy to tests/artifact_path_requirements/inherit1.rbc
index 0c6c89a..dcef1bf 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/tests/artifact_path_requirements/inherit1.rbc
@@ -1,14 +1,21 @@
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright 2022 Google LLC
 #
 # 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
+#      https://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.
-#
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit3.rbc", _inherit3_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit3", _inherit3_init)
diff --git a/core/build_id.rbc b/tests/artifact_path_requirements/inherit2.rbc
similarity index 62%
copy from core/build_id.rbc
copy to tests/artifact_path_requirements/inherit2.rbc
index 4f33833..597b4e9 100644
--- a/core/build_id.rbc
+++ b/tests/artifact_path_requirements/inherit2.rbc
@@ -1,5 +1,4 @@
-
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit4.rbc", _inherit4_init = "init")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit4", _inherit4_init)
+  rblf.require_artifacts_in_path(handle, "vendor/", "")
diff --git a/core/build_id.rbc b/tests/artifact_path_requirements/inherit3.rbc
similarity index 62%
copy from core/build_id.rbc
copy to tests/artifact_path_requirements/inherit3.rbc
index 4f33833..597b4e9 100644
--- a/core/build_id.rbc
+++ b/tests/artifact_path_requirements/inherit3.rbc
@@ -1,5 +1,4 @@
-
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit4.rbc", _inherit4_init = "init")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.inherit(handle, "test/inherit4", _inherit4_init)
+  rblf.require_artifacts_in_path(handle, "vendor/", "")
diff --git a/core/build_id.rbc b/tests/artifact_path_requirements/inherit4.rbc
similarity index 62%
copy from core/build_id.rbc
copy to tests/artifact_path_requirements/inherit4.rbc
index 4f33833..52028fe 100644
--- a/core/build_id.rbc
+++ b/tests/artifact_path_requirements/inherit4.rbc
@@ -1,5 +1,4 @@
-
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,9 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += ["foo/bar/baz.txt:vendor/etc/baz.txt"]
diff --git a/tests/artifact_path_requirements/product.rbc b/tests/artifact_path_requirements/product.rbc
new file mode 100644
index 0000000..7d1f169
--- /dev/null
+++ b/tests/artifact_path_requirements/product.rbc
@@ -0,0 +1,24 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit1.rbc", _inherit1_init = "init")
+load(":inherit2.rbc", _inherit2_init = "init")
+load(":inherit3.rbc", _inherit3_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "test/inherit1", _inherit1_init)
+  rblf.inherit(handle, "test/inherit2", _inherit2_init)
+  rblf.inherit(handle, "test/inherit3", _inherit3_init)
diff --git a/tests/artifact_path_requirements/test.rbc b/tests/artifact_path_requirements/test.rbc
new file mode 100644
index 0000000..0a344d1
--- /dev/null
+++ b/tests/artifact_path_requirements/test.rbc
@@ -0,0 +1,27 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load("//build/make/tests/input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+
+def assert_eq(expected, actual):
+    if expected != actual:
+        fail("Expected '%s', got '%s'" % (expected, actual))
+
+def test():
+    (globals, globals_base) = rblf.product_configuration("test/product", init, input_variables_init)
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/product.mk.PRODUCT_COPY_FILES"])
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/inherit2.mk.PRODUCT_COPY_FILES"])
+    assert_eq(["foo/bar/baz.txt:vendor/etc/baz.txt"], globals["PRODUCTS.test/inherit3.mk.PRODUCT_COPY_FILES"])
diff --git a/target/product/iorap_large_memory_config.mk b/tests/board.rbc
similarity index 69%
copy from target/product/iorap_large_memory_config.mk
copy to tests/board.rbc
index 0c6c89a..8696e40 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/tests/board.rbc
@@ -1,14 +1,19 @@
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright 2021 Google LLC
 #
 # 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
+#      https://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.
-#
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["A_LIST_VARIABLE"] += ["bar"]
diff --git a/target/product/iorap_large_memory_config.mk b/tests/board_input_vars.rbc
similarity index 69%
copy from target/product/iorap_large_memory_config.mk
copy to tests/board_input_vars.rbc
index 0c6c89a..69d9cd6 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/tests/board_input_vars.rbc
@@ -1,14 +1,19 @@
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright 2021 Google LLC
 #
 # 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
+#      https://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.
-#
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["A_LIST_VARIABLE"] = ["foo"]
diff --git a/tests/conversion_error.rbc b/tests/conversion_error.rbc
new file mode 100644
index 0000000..5212378
--- /dev/null
+++ b/tests/conversion_error.rbc
@@ -0,0 +1,27 @@
+# Copyright 2021 Google LLC
+#
+# 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
+#
+#      https://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.
+
+
+# Run test configuration and verify its result.
+# The main configuration file is device.rbc.
+# It inherits part1.rbc and also includes include1.rbc
+# TODO(asmundak): more tests are needed to verify that:
+#  * multi-level inheritance works as expected
+#  * all runtime functions (wildcard, regex, etc.) work
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":version_defaults.rbc", "version_defaults")
+load(":device.rbc", "init")
+
+rblf.mk2rbc_error("file.mk:123", "cannot convert")
diff --git a/tests/device.rbc b/tests/device.rbc
deleted file mode 100644
index 5d4e70c..0000000
--- a/tests/device.rbc
+++ /dev/null
@@ -1,42 +0,0 @@
-
-# Copyright 2021 Google LLC
-#
-# 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
-#
-#      https://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.
-
-# Top-level test configuration.
-# Converted from the following makefile
-### PRODUCT_PACKAGES += dev
-### PRODUCT_HOST_PACKAGES += host
-### $(call inherit-product, $(LOCAL_PATH)/part1.mk)
-### PRODUCT_COPY_FILES += device_from:device_to
-### include $(LOCAL_PATH)/include1.mk
-### PRODUCT_PACKAGES += dev_after
-### PRODUCT_COPY_FILES += $(call find-copy-subdir-files,audio_platform_info*.xml,device/google/redfin/audio,$(TARGET_COPY_OUT_VENDOR)/etc) xyz
-
-load("//build/make/core:product_config.rbc", "rblf")
-load(":part1.rbc", _part1_init = "init")
-load(":include1.rbc", _include1_init = "init")
-
-def init(g, handle):
-  cfg = rblf.cfg(handle)
-  rblf.setdefault(handle, "PRODUCT_PACKAGES")
-  cfg["PRODUCT_PACKAGES"] += ["dev"]
-  rblf.setdefault(handle, "PRODUCT_HOST_PACKAGES")
-  cfg["PRODUCT_HOST_PACKAGES"] += ["host"]
-  rblf.inherit(handle, "test/part1", _part1_init)
-  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
-  cfg["PRODUCT_COPY_FILES"] += ["device_from:device_to"]
-  _include1_init(g, handle)
-  cfg["PRODUCT_PACKAGES"] += ["dev_after"]
-  cfg["PRODUCT_COPY_FILES"] += (rblf.find_and_copy("audio_platform_info*.xml", "device/google/redfin/audio", "||VENDOR-PATH-PH||/etc") +
-      ["xyz"])
diff --git a/tests/input_variables.rbc b/tests/input_variables.rbc
new file mode 100644
index 0000000..0bb100f
--- /dev/null
+++ b/tests/input_variables.rbc
@@ -0,0 +1,28 @@
+# Copyright 2021 Google LLC
+#
+# 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
+#
+#      https://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.
+
+# This file was generated by running `m RBC_PRODUCT_CONFIG=1 nothing`
+# and then copying it from out/rbc/out/rbc/make_vars_pre_product_config.rbc.
+# It was manually trimmed down afterwards to just the variables we need.
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["PLATFORM_VERSION_CODENAME"] = "Tiramisu"
+  g["PLATFORM_VERSION"] = "Tiramisu"
+  g["TARGET_BUILD_VARIANT"] = "userdebug"
+  g["TARGET_BUILD_TYPE"] = "release"
+  g["TARGET_PRODUCT"] = "aosp_arm64"
+  g["PLATFORM_SDK_VERSION"] = "31"
diff --git a/tests/part1.rbc b/tests/part1.rbc
index 3e50751..ae79d32 100644
--- a/tests/part1.rbc
+++ b/tests/part1.rbc
@@ -26,3 +26,5 @@
   cfg["PRODUCT_COPY_FILES"] += ["part_from:part_to"]
   rblf.setdefault(handle, "PRODUCT_PRODUCT_PROPERTIES")
   cfg["PRODUCT_PRODUCT_PROPERTIES"] += ["part_properties"]
+  rblf.soong_config_namespace(g, "NS1")
+  rblf.soong_config_append(g, "NS1", "v1", "abc_part1")
diff --git a/target/product/iorap_large_memory_config.mk b/tests/prefixed_sort_order/base-secondary.rbc
similarity index 67%
copy from target/product/iorap_large_memory_config.mk
copy to tests/prefixed_sort_order/base-secondary.rbc
index 0c6c89a..5446e8f 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/tests/prefixed_sort_order/base-secondary.rbc
@@ -1,14 +1,21 @@
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright 2022 Google LLC
 #
 # 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
+#      https://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.
-#
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  g.setdefault("MY_VAR", [])
+  g["MY_VAR"] += ["foo"]
diff --git a/target/product/iorap_large_memory_config.mk b/tests/prefixed_sort_order/base.rbc
similarity index 67%
copy from target/product/iorap_large_memory_config.mk
copy to tests/prefixed_sort_order/base.rbc
index 0c6c89a..05b0d5d 100644
--- a/target/product/iorap_large_memory_config.mk
+++ b/tests/prefixed_sort_order/base.rbc
@@ -1,14 +1,21 @@
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright 2022 Google LLC
 #
 # 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
+#      https://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.
-#
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  g.setdefault("MY_VAR", [])
+  g["MY_VAR"] += ["bar"]
diff --git a/tests/prefixed_sort_order/product.rbc b/tests/prefixed_sort_order/product.rbc
new file mode 100644
index 0000000..619b2c0
--- /dev/null
+++ b/tests/prefixed_sort_order/product.rbc
@@ -0,0 +1,29 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":base.rbc", _base_init = "init")
+load(":base-secondary.rbc", _base_secondary_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  # It's important that base-secondary uses a dash, an underscore won't expose the sort order issue:
+  # >>> sorted(["base", "base-secondary"])
+  # ['base', 'base-secondary']
+  # >>> sorted(["base.mk", "base-secondary.mk"])
+  # ['base-secondary.mk', 'base.mk']
+
+  rblf.inherit(handle, "base", _base_init)
+  rblf.inherit(handle, "base-secondary", _base_secondary_init)
diff --git a/tests/prefixed_sort_order/test.rbc b/tests/prefixed_sort_order/test.rbc
new file mode 100644
index 0000000..e59a509
--- /dev/null
+++ b/tests/prefixed_sort_order/test.rbc
@@ -0,0 +1,26 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load("//build/make/tests/input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+
+
+def assert_eq(expected, actual):
+    if expected != actual:
+        fail("Expected '%s', got '%s'" % (expected, actual))
+
+def test():
+    (globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
+    assert_eq(["foo", "bar"], globals["MY_VAR"])
diff --git a/tests/product.rbc b/tests/product.rbc
new file mode 100644
index 0000000..9ae6393
--- /dev/null
+++ b/tests/product.rbc
@@ -0,0 +1,71 @@
+
+# Copyright 2021 Google LLC
+#
+# 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
+#
+#      https://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.
+
+# Top-level test configuration.
+# Converted from the following makefile
+### PRODUCT_PACKAGES += dev
+### PRODUCT_HOST_PACKAGES += host
+### $(call inherit-product, $(LOCAL_PATH)/part1.mk)
+### PRODUCT_COPY_FILES += device_from:device_to
+### include $(LOCAL_PATH)/include1.mk
+### PRODUCT_PACKAGES += dev_after
+### PRODUCT_COPY_FILES += $(call find-copy-subdir-files,audio_platform_info*.xml,device/google/redfin/audio,$(TARGET_COPY_OUT_VENDOR)/etc) xyz:/etc/xyz
+### PRODUCT_COPY_FILES += $(call copy-files,x.xml y.xml,/etc)
+### $(call add_soong_config_namespace,NS1)
+### $(call soong_config_append,NS1,v1,abc)
+### $(call soong_config_append,NS1,v2,def)
+### $(call add_soong_config_var_value,NS2,v3,abc)
+### $(call soong_config_set,NS2,v3,xyz)
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":part1.rbc", _part1_init = "init")
+load(":include1.rbc", _include1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.setdefault(handle, "PRODUCT_PACKAGES")
+  cfg["PRODUCT_PACKAGES"] += ["dev"]
+  rblf.setdefault(handle, "PRODUCT_HOST_PACKAGES")
+  cfg["PRODUCT_HOST_PACKAGES"] += ["host"]
+  rblf.inherit(handle, "test/part1", _part1_init)
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += ["device_from:device_to"]
+  _include1_init(g, handle)
+  cfg["PRODUCT_PACKAGES"] += ["dev_after"]
+  cfg["PRODUCT_COPY_FILES"] += (rblf.find_and_copy("audio_platform_info*.xml", "device/google/redfin", "||VENDOR-PATH-PH||/etc") +
+      ["xyz:/etc/xyz"])
+  cfg["PRODUCT_COPY_FILES"] += rblf.copy_files("x.xml y.xml", "/etc")
+  cfg["PRODUCT_COPY_FILES"] += rblf.copy_files(["from/sub/x", "from/sub/y"], "to")
+
+  rblf.soong_config_namespace(g, "NS1")
+  rblf.soong_config_append(g, "NS1", "v1", "abc")
+  rblf.soong_config_append(g, "NS1", "v2", "def")
+  rblf.soong_config_set(g, "NS2", "v3", "abc")
+  rblf.soong_config_set(g, "NS2", "v3", "xyz")
+
+  rblf.mkdist_for_goals(g, "goal", "dir1/file1:out1 dir1/file2:out2")
+  rblf.mkdist_for_goals(g, "goal", "dir2/file2:")
+
+  if rblf.board_platform_in(g, "board1 board2"):
+    cfg["PRODUCT_PACKAGES"] += ["bad_package"]
+  g["TARGET_BOARD_PLATFORM"] = "board1"
+  if rblf.board_platform_in(g, "board1 board2"):
+    cfg["PRODUCT_PACKAGES"] += ["board1_in"]
+  if rblf.board_platform_in(g, ["board3","board2"]):
+    cfg["PRODUCT_PACKAGES"] += ["bad_board_in"]
+  if rblf.board_platform_is(g, "board1"):
+    cfg["PRODUCT_PACKAGES"] += ["board1_is"]
+  if rblf.board_platform_is(g, "board2"):
+    cfg["PRODUCT_PACKAGES"] += ["bad_board1_is"]
diff --git a/tests/run.rbc b/tests/run.rbc
index b13f835..2d35e85 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -1,4 +1,3 @@
-
 # Copyright 2021 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,37 +13,142 @@
 # limitations under the License.
 
 
-# Run test configuration and verify its result.
-# The main configuration file is device.rbc.
+# Run test product configuration and verify its result.
+# The main configuration file is product.rbc.
 # It inherits part1.rbc and also includes include1.rbc
 # TODO(asmundak): more tests are needed to verify that:
 #  * multi-level inheritance works as expected
 #  * all runtime functions (wildcard, regex, etc.) work
 
 load("//build/make/core:product_config.rbc", "rblf")
-load(":device.rbc", "init")
+load(":input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+load(":board.rbc", board_init = "init")
+load(":board_input_vars.rbc", board_input_vars_init = "init")
+load("//build/make/tests/single_value_inheritance:test.rbc", test_single_value_inheritance = "test")
+load("//build/make/tests/artifact_path_requirements:test.rbc", test_artifact_path_requirements = "test")
+load("//build/make/tests/prefixed_sort_order:test.rbc", test_prefixed_sort_order = "test")
 
 def assert_eq(expected, actual):
     if expected != actual:
-        fail("Expected %s, got %s" % (expected, actual))
+        fail("Expected '%s', got '%s'" % (expected, actual))
 
+def assert_dict_subset(expected, actual):
+    for key, val in expected.items():
+        assert_eq(val, actual[key])
 
-globals, config = rblf.product_configuration("test/device", init)
+# Unit tests for non-trivial runtime functions
+assert_eq(["a", "b", "c"], rblf.mksort("b a    c c"))
+assert_eq(["a", "b", "c"], rblf.mksort(["b", "a", "c", "c"]))
+
+assert_eq("", rblf.mkstrip(" \n \t    "))
+assert_eq("a b c", rblf.mkstrip("  a b   \n  c \t"))
+assert_eq(1, rblf.mkstrip(1))
+
+assert_eq("b1 b2", rblf.mksubst("a", "b", "a1 a2"))
+assert_eq(["b1", "x2"], rblf.mksubst("a", "b", ["a1", "x2"]))
+
+assert_eq("ABcdYZ", rblf.mkpatsubst("ab%yz", "AB%YZ", "abcdyz"))
+assert_eq("bcz", rblf.mkpatsubst("a%z", "A%Z", "bcz"))
+assert_eq(["Ay", "Az"], rblf.mkpatsubst("a%", "A%", ["ay", "az"]))
+assert_eq("AcZ bcz", rblf.mkpatsubst("a%z", "A%Z", "acz  bcz"))
+assert_eq("Abcd", rblf.mkpatsubst("a%", "A%", "abcd"))
+assert_eq("abcZ", rblf.mkpatsubst("%z", "%Z", "abcz"))
+assert_eq("azx b", rblf.mkpatsubst("az", "AZ", "azx  b"))
+assert_eq(["azx", "b"], rblf.mkpatsubst("az", "AZ", ["azx", "b"]))
+assert_eq("ABC", rblf.mkpatsubst("abc", "ABC", "abc"))
+assert_eq(["%/foo"], rblf.mkpatsubst("%", "\\%/%", ["foo"]))
+assert_eq(["foo/%"], rblf.mkpatsubst("%", "%/%", ["foo"]))
+assert_eq(["from/a:to/a", "from/b:to/b"], rblf.product_copy_files_by_pattern("from/%", "to/%", "a b"))
+
+assert_eq([], rblf.filter(["a", "", "b"], "f"))
+assert_eq(["ab%c", "axyzb%c"], rblf.filter(["a%b%c"], ["ab%c", "axyzb%c", "axyzb%cd", "axyzbwc"]))
+assert_eq(["abc", "bcd"], rblf.filter(["a%", "b%"], ["abc", "def", "bcd", "xabc"]))
+assert_eq(["b", "ab"], rblf.filter_out(["a", "" ], ["a", "", "b", "ab"]))
+assert_eq(["c"], rblf.filter_out(["a", "b" ], ["a", "b", "c"]))
+assert_eq(["c"], rblf.filter_out(["a%", "b" ], ["abc", "b", "c"]))
+
+assert_eq("foo.c no_folder", rblf.notdir(["src/foo.c", "no_folder"]))
+assert_eq("foo.c no_folder", rblf.notdir("src/foo.c no_folder"))
+assert_eq("", rblf.notdir("/"))
+assert_eq("", rblf.notdir(""))
+
+cwd = rblf_shell('pwd')
+assert_eq(cwd+"/foo/bar", rblf.abspath("foo/bar"))
+assert_eq(cwd+"/bar", rblf.abspath("foo/.././bar"))
+assert_eq(cwd+"/bar", rblf.abspath("foo/..////bar//"))
+assert_eq("/foo/baz", rblf.abspath("/foo/bar/../baz"))
+assert_eq(cwd+"/foo/bar "+cwd+"/foo/baz", rblf.abspath("foo/bar foo/baz"))
+assert_eq("/baz", rblf.abspath("/../../../../../../../../../../../../../../../../baz"))
+
+assert_eq(
+    ["build/make/tests/board.rbc", "build/make/tests/board_input_vars.rbc"],
+    rblf.expand_wildcard("build/make/tests/board*.rbc")
+)
+assert_eq(
+    ["build/make/tests/run.rbc", "build/make/tests/product.rbc"],
+    rblf.expand_wildcard("build/make/tests/run.rbc build/make/tests/product.rbc")
+)
+assert_eq(
+    ["build/make/tests/run.rbc"],
+    rblf.expand_wildcard("build/make/tests/run.rbc build/make/tests/nonexistent.rbc")
+)
+
+(globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
+assert_dict_subset({
+    "PRODUCTS.test/device.mk.PRODUCT_COPY_FILES": [
+        "part_from:part_to",
+        "device_from:device_to",
+        "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio/audio_platform_info_noextcodec_snd.xml",
+        "xyz:/etc/xyz",
+        "x.xml:/etc/x.xml",
+        "y.xml:/etc/y.xml",
+        "from/sub/x:to/x",
+        "from/sub/y:to/y",
+    ],
+    "PRODUCTS.test/device.mk.PRODUCT_HOST_PACKAGES": ["host"],
+    "PRODUCTS.test/device.mk.PRODUCT_PACKAGES": [
+        "dev",
+        "inc",
+        "dev_after",
+        "board1_in",
+        "board1_is",
+    ],
+    "PRODUCTS.test/device.mk.PRODUCT_PRODUCT_PROPERTIES": ["part_properties"]
+}, globals)
+
+ns = globals["$SOONG_CONFIG_NAMESPACES"]
 assert_eq(
     {
-      "PRODUCT_COPY_FILES": [
-          "part_from:part_to",
-          "device_from:device_to",
-          "device/google/redfin/audio/audio_platform_info_noextcodec_snd.xml:||VENDOR-PATH-PH||/etc/audio_platform_info_noextcodec_snd.xml",
-          "xyz"
-      ],
-      "PRODUCT_HOST_PACKAGES": ["host"],
-      "PRODUCT_PACKAGES": [
-          "dev",
-          "inc",
-          "dev_after"
-      ],
-      "PRODUCT_PRODUCT_PROPERTIES": ["part_properties"]
+        "NS1": {
+            "v1": "abc abc_part1",
+            "v2": "def"
+        },
+        "NS2": {
+            "v3": "xyz"
+        }
     },
-    { k:v for k, v in sorted(config.items()) }
+    {k:v for k, v in sorted(ns.items()) }
 )
+
+assert_eq("Tiramisu", globals["PLATFORM_VERSION"])
+assert_eq("31", globals["PLATFORM_SDK_VERSION"])
+
+assert_eq("xyz", rblf.soong_config_get(globals, "NS2", "v3"))
+assert_eq(None, rblf.soong_config_get(globals, "NS2", "nonexistant_var"))
+
+goals = globals["$dist_for_goals"]
+assert_eq(
+    {
+        "goal": [("dir1/file1", "out1"), ("dir1/file2", "out2"), ("dir2/file2", "file2")]
+    },
+    { k:v for k,v in sorted(goals.items()) }
+)
+
+(board_globals, board_globals_base) = rblf.board_configuration(board_init, board_input_vars_init)
+assert_eq({"A_LIST_VARIABLE": ["foo", "bar"]}, board_globals)
+assert_eq({"A_LIST_VARIABLE": ["foo"]}, board_globals_base)
+
+test_single_value_inheritance()
+test_artifact_path_requirements()
+test_prefixed_sort_order()
diff --git a/core/build_id.rbc b/tests/single_value_inheritance/inherit1.rbc
similarity index 62%
rename from core/build_id.rbc
rename to tests/single_value_inheritance/inherit1.rbc
index 4f33833..0cc98a9 100644
--- a/core/build_id.rbc
+++ b/tests/single_value_inheritance/inherit1.rbc
@@ -1,5 +1,4 @@
-
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,9 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  cfg["PRODUCT_CHARACTERISTICS"] = "tablet"
+  cfg["PRODUCT_DEFAULT_DEV_CERTIFICATE"] = "vendor/myvendor/certs/devkeys/devkey"
+  cfg.setdefault("PRODUCT_PACKAGES", [])
+  cfg["PRODUCT_PACKAGES"] += ["bar"]
diff --git a/core/build_id.rbc b/tests/single_value_inheritance/inherit2.rbc
similarity index 62%
copy from core/build_id.rbc
copy to tests/single_value_inheritance/inherit2.rbc
index 4f33833..ed5e569 100644
--- a/core/build_id.rbc
+++ b/tests/single_value_inheritance/inherit2.rbc
@@ -1,5 +1,4 @@
-
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+
+  cfg["PRODUCT_CHARACTERISTICS"] = "nosdcard"
+  cfg.setdefault("PRODUCT_PACKAGES", [])
+  cfg["PRODUCT_PACKAGES"] += ["foo"]
diff --git a/tests/single_value_inheritance/product.rbc b/tests/single_value_inheritance/product.rbc
new file mode 100644
index 0000000..d090af6
--- /dev/null
+++ b/tests/single_value_inheritance/product.rbc
@@ -0,0 +1,24 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":inherit1.rbc", _inherit1_init = "init")
+load(":inherit2.rbc", _inherit2_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "test/inherit2", _inherit2_init)
+  rblf.inherit(handle, "test/inherit1", _inherit1_init)
+
+  cfg["PRODUCT_DEFAULT_DEV_CERTIFICATE"] = ""
diff --git a/tests/single_value_inheritance/test.rbc b/tests/single_value_inheritance/test.rbc
new file mode 100644
index 0000000..e4f44f4
--- /dev/null
+++ b/tests/single_value_inheritance/test.rbc
@@ -0,0 +1,28 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      https://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.
+
+load("//build/make/core:product_config.rbc", "rblf")
+load("//build/make/tests/input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+
+
+def assert_eq(expected, actual):
+    if expected != actual:
+        fail("Expected '%s', got '%s'" % (expected, actual))
+
+def test():
+    (globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
+    assert_eq("tablet", globals["PRODUCTS.test/device.mk.PRODUCT_CHARACTERISTICS"])
+    assert_eq("vendor/myvendor/certs/devkeys/devkey", globals["PRODUCTS.test/device.mk.PRODUCT_DEFAULT_DEV_CERTIFICATE"])
+    assert_eq(["foo", "bar"], globals["PRODUCTS.test/device.mk.PRODUCT_PACKAGES"])
diff --git a/tests/version_defaults.rbc b/tests/version_defaults.rbc
new file mode 100644
index 0000000..9b35b57
--- /dev/null
+++ b/tests/version_defaults.rbc
@@ -0,0 +1,11 @@
+version_defaults = struct(
+    codenames = { "SP1A" : "S" },
+    default_platform_version = "SP1A",
+    max_platform_version = "SP1A",
+    min_platform_version = "SP1A",
+    platform_base_sdk_extension_version = 0,
+    platform_sdk_extension_version = 1,
+    platform_sdk_version = 30,
+    platform_security_patch = "2021-08-05",
+    platform_version_last_stable = 11,
+)
diff --git a/tools/Android.bp b/tools/Android.bp
index 269e610..6601c60 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -14,41 +14,17 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    //   SPDX-license-identifier-CC-BY
-    //   SPDX-license-identifier-GPL
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 python_binary_host {
   name: "generate-self-extracting-archive",
   srcs: ["generate-self-extracting-archive.py"],
-  version: {
-    py2: {
-      enabled: true,
-    },
-    py3: {
-      enabled: false,
-    },
-  },
 }
 
 python_binary_host {
   name: "post_process_props",
   srcs: ["post_process_props.py"],
-  version: {
-    py2: {
-      enabled: false,
-    },
-    py3: {
-      enabled: true,
-    },
-  },
 }
 
 python_test_host {
@@ -58,14 +34,6 @@
     "post_process_props.py",
     "test_post_process_props.py",
   ],
-  version: {
-    py2: {
-      enabled: false,
-    },
-    py3: {
-      enabled: true,
-    },
-  },
   test_config: "post_process_props_unittest.xml",
   test_suites: ["general-tests"],
 }
@@ -73,14 +41,6 @@
 python_binary_host {
   name: "extract_kernel",
   srcs: ["extract_kernel.py"],
-  version: {
-    py2: {
-      enabled: false,
-    },
-    py3: {
-      enabled: true,
-    },
-  },
 }
 
 genrule_defaults {
diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel
new file mode 100644
index 0000000..3170820
--- /dev/null
+++ b/tools/BUILD.bazel
@@ -0,0 +1,20 @@
+py_library(
+    name="event_log_tags",
+    srcs = ["event_log_tags.py"],
+)
+
+py_binary(
+    name="java-event-log-tags",
+    srcs=["java-event-log-tags.py"],
+    deps=[":event_log_tags"],
+    visibility = ["//visibility:public"],
+    python_version = "PY3",
+)
+
+py_binary(
+    name="merge-event-log-tags",
+    srcs=["merge-event-log-tags.py"],
+    deps=[":event_log_tags"],
+    visibility = ["//visibility:public"],
+    python_version = "PY3",
+)
diff --git a/tools/acp/Android.bp b/tools/acp/Android.bp
index 78738b0..47b23b2 100644
--- a/tools/acp/Android.bp
+++ b/tools/acp/Android.bp
@@ -4,11 +4,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_binary_host {
diff --git a/tools/atree/Android.bp b/tools/atree/Android.bp
index 7906d8b..fdae3e0 100644
--- a/tools/atree/Android.bp
+++ b/tools/atree/Android.bp
@@ -4,11 +4,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_binary_host {
diff --git a/tools/build-license-metadata.sh b/tools/build-license-metadata.sh
deleted file mode 100755
index a138dbe..0000000
--- a/tools/build-license-metadata.sh
+++ /dev/null
@@ -1,313 +0,0 @@
-#!/bin/sh
-
-set -u
-
-ME=$(basename $0)
-
-USAGE="Usage: ${ME} {options}
-
-Builds a license metadata specification and outputs it to stdout or {outfile}.
-
-The available options are:
-
--k kind...              license kinds
--c condition...         license conditions
--p package...           license package name
--n notice...            license notice file
--d dependency...        license metadata file dependency
--t target...            targets
--m target:installed...  map dependent targets to their installed names
--is_container           preserved dependent target name when given
--o outfile              output file
-"
-
-# Global flag variables
-license_kinds=
-license_conditions=
-license_package_name=
-license_notice=
-license_deps=
-targets=
-installmap=
-is_container=false
-ofile=
-
-# Global variables
-depfiles=" "
-effective_conditions=
-
-
-# Exits with a message.
-#
-# When the exit status is 2, assumes a usage error and outputs the usage message
-# to stderr before outputting the specific error message to stderr.
-#
-# Parameters:
-#   Optional numeric exit status (defaults to 2, i.e. a usage error.)
-#   Remaining args treated as an error message sent to stderr.
-die() {
-  lstatus=2
-  case "${1:-}" in *[^0-9]*) ;; *) lstatus="$1"; shift ;; esac
-  case "${lstatus}" in 2) echo "${USAGE}" >&2; echo >&2 ;; esac
-  if [ -n "$*" ]; then
-    echo -e "$*\n" >&2
-  fi
-  exit $lstatus
-}
-
-
-# Sets the flag variables based on the command-line.
-#
-# invoke with: process_args "$@"
-process_args() {
-  lcurr_flag=
-  while [ "$#" -gt '0' ]; do
-    case "${1}" in
-      -h)
-        echo "${USAGE}"
-        exit 0
-        ;;
-      -k)
-        lcurr_flag=kind
-        ;;
-      -c)
-        lcurr_flag=condition
-        ;;
-      -p)
-        lcurr_flag=package
-        ;;
-      -n)
-        lcurr_flag=notice
-        ;;
-      -d)
-        lcurr_flag=dependency
-        ;;
-      -t)
-        lcurr_flag=target
-        ;;
-      -m)
-        lcurr_flag=installmap
-        ;;
-      -o)
-        lcurr_flag=ofile
-        ;;
-      -is_container)
-        lcurr_flag=
-        is_container=true
-        ;;
-      -*)
-        die "Unknown flag: \"${1}\""
-        ;;
-      *)
-        case "${lcurr_flag}" in
-          kind)
-            license_kinds="${license_kinds}${license_kinds:+ }${1}"
-            ;;
-          condition)
-            license_conditions="${license_conditions}${license_conditions:+ }${1}"
-            ;;
-          package)
-            license_package_name="${license_package_name}${license_package_name:+ }${1}"
-            ;;
-          notice)
-            license_notice="${license_notice}${license_notice:+ }${1}"
-            ;;
-          dependency)
-            license_deps="${license_deps}${license_deps:+ }${1}"
-            ;;
-          target)
-            targets="${targets}${targets:+ }${1}"
-            ;;
-          installmap)
-            installmap="${installmap}${installmap:+ }${1}"
-            ;;
-          ofile)
-            if [ -n "${ofile}" ]; then
-              die "Output file -o appears twice as \"${ofile}\" and \"${1}\""
-            fi
-            ofile="${1}"
-            ;;
-          *)
-            die "Must precede argument \"${1}\" with type flag."
-            ;;
-        esac
-        ;;
-    esac
-    shift
-  done
-}
-
-# Reads a license metadata file from stdin, and outputs the named dependencies.
-#
-# No parameters.
-extract_deps() {
-  awk '$1 == "dep_name:" { sub(/^"/, "", $2); sub(/"$/, "", $2); print $2; }'
-}
-
-# Populates the depfiles variable identifying dependency files.
-#
-# Starting with the dependencies enumerated in license_deps, calculates the
-# transitive closure of all dependencies.
-#
-# Dependency names ending in .meta_module indirectly reference license
-# metadata with 1 license metadata filename per line.
-#
-# No parameters; no output.
-read_deps() {
-  lnewdeps=
-  for d in ${license_deps}; do
-    case "${d}" in
-      *.meta_module)
-        lnewdeps="${lnewdeps}${lnewdeps:+ }"$(cat "${d}") ;;
-      *)
-        lnewdeps="${lnewdeps}${lnewdeps:+ }${d}" ;;
-    esac
-  done
-  lnewdeps=$(echo "${lnewdeps}" | tr ' ' '\n' | sort -u)
-  lalldeps=
-  ldeps=
-  lmod=
-  ldep=
-  while [ "${#lnewdeps}" -gt '0' ]; do
-    ldeps="${lnewdeps}"
-    lnewdeps=
-    for ldep in ${ldeps}; do
-      depfiles="${depfiles}${ldep} "
-      lalldeps="${lalldeps}${lalldeps:+ }"$(cat "${ldep}" | extract_deps)
-    done
-    lalldeps=$(for d in ${lalldeps}; do echo "${d}"; done | sort -u)
-    for d in ${lalldeps}; do
-      ldeps="${d}"
-      case "${d}" in *.meta_module) ldeps=$(cat "${d}") ;; esac
-      for lmod in ${ldeps}; do
-        if ! expr "${depfiles}" : ".* ${lmod} .*" >/dev/null 2>&1; then
-          lnewdeps="${lnewdeps}${lnewdeps:+ }${lmod}"
-        fi
-      done
-    done
-    lalldeps=
-  done
-}
-
-# Returns the effective license conditions for the current license metadata.
-#
-# If a module is restricted or links in a restricted module, the effective
-# license has a restricted condition.
-calculate_effective_conditions() {
-  lconditions="${license_conditions}"
-  case "${license_conditions}" in
-    *restricted*) : do nothing ;;
-    *)
-       for d in ${depfiles}; do
-         if cat "${d}" | egrep -q 'effective_condition\s*:.*restricted' ; then
-           lconditions="${lconditions}${lconditions:+ }restricted"
-           break
-         fi
-       done
-     ;;
-  esac
-  echo "${lconditions}"
-}
-
-
-process_args "$@"
-
-if [ -n "${ofile}" ]; then
-  # truncate the output file before appending results
-  : >"${ofile}"
-else
-  ofile=/dev/stdout
-fi
-
-# spit out the license metadata file content
-(
-  echo 'license_package_name: "'${license_package_name}'"'
-  for kind in ${license_kinds}; do
-    echo 'license_kind: "'${kind}'"'
-  done
-  for condition in ${license_conditions}; do
-    echo 'license_condition: "'${condition}'"'
-  done
-  for f in ${license_notice}; do
-    echo 'license_text: "'${f}'"'
-  done
-  echo "is_container: ${is_container}"
-  for t in ${targets}; do
-    echo 'target: "'${t}'"'
-  done
-  for m in ${installmap}; do
-    echo 'install_map: "'${m}'"'
-  done
-) >>"${ofile}"
-read_deps
-effective_conditions=$(calculate_effective_conditions)
-for condition in ${effective_conditions}; do
-  echo 'effective_condition: "'${condition}'"'
-done >>"${ofile}"
-for dep in ${depfiles}; do
-  echo 'dep {'
-  cat "${dep}" | \
-    awk -v name="${dep}" '
-      function strip_type() {
-        $1 = ""
-        sub(/^\s*/, "")
-      }
-      BEGIN {
-        print "  dep_name: " name
-      }
-      $1 == "license_package_name:" {
-        strip_type()
-        print "  dep_package_name: "$0
-      }
-      $1 == "dep_name:" {
-        print "  dep_sub_dep: "$2
-      }
-      $1 == "license_kind:" {
-        print "  dep_license_kind: "$2
-      }
-      $1 == "license_condition:" {
-        print "  dep_license_condition: "$2
-      }
-      $1 == "is_container:" {
-        print "  dep_is_container: "$2
-      }
-      $1 == "license_text:" {
-        strip_type()
-        print "  dep_license_text: "$0
-      }
-      $1 == "target:" {
-        print "  dep_target: "$2
-      }
-      $1 == "install_map:" {
-        print "  dep_install_map: "$2
-      }
-  '
-  # The restricted license kind is contagious to all linked dependencies.
-  dep_conditions=$(echo $(
-      cat "${dep}" | awk '
-        $1 == "effective_condition:" {
-          $1 = ""
-          sub(/^\s*/, "")
-          gsub(/"/, "")
-          print
-        }
-      '
-  ))
-  for condition in ${dep_conditions}; do
-    echo '  dep_effective_condition: "'${condition}'"'
-  done
-  if ! ${is_container}; then
-    case "${dep_conditions}" in
-      *restricted*) : already restricted -- nothing to inherit ;;
-      *)
-        case "${effective_conditions}" in
-          *restricted*)
-            # "contagious" restricted infects everything linked to restricted
-            echo '  dep_effective_condition: "restricted"'
-            ;;
-        esac
-        ;;
-    esac
-  fi
-  echo '}'
-done >>"${ofile}"
diff --git a/tools/buildinfo.sh b/tools/buildinfo.sh
index a349cba..536a381 100755
--- a/tools/buildinfo.sh
+++ b/tools/buildinfo.sh
@@ -16,8 +16,10 @@
 echo "ro.build.version.preview_sdk_fingerprint=$PLATFORM_PREVIEW_SDK_FINGERPRINT"
 echo "ro.build.version.codename=$PLATFORM_VERSION_CODENAME"
 echo "ro.build.version.all_codenames=$PLATFORM_VERSION_ALL_CODENAMES"
+echo "ro.build.version.known_codenames=$PLATFORM_VERSION_KNOWN_CODENAMES"
 echo "ro.build.version.release=$PLATFORM_VERSION_LAST_STABLE"
 echo "ro.build.version.release_or_codename=$PLATFORM_VERSION"
+echo "ro.build.version.release_or_preview_display=$PLATFORM_DISPLAY_VERSION"
 echo "ro.build.version.security_patch=$PLATFORM_SECURITY_PATCH"
 echo "ro.build.version.base_os=$PLATFORM_BASE_OS"
 echo "ro.build.version.min_supported_target_sdk=$PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION"
diff --git a/tools/canoninja/README.md b/tools/canoninja/README.md
new file mode 100644
index 0000000..506acf7
--- /dev/null
+++ b/tools/canoninja/README.md
@@ -0,0 +1,151 @@
+# Ninja File Canonicalizer
+
+Suppose we have a tool that generates a Ninja file from some other description (think Kati and makefiles), and during
+the testing we discovered a regression. Furthermore, suppose that the generated Ninja file is large (think millions of
+lines). And, the new Ninja file has build statements and rules in a slightly different order. As the tool generates the
+rule names, the real differences in the output of the `diff` command are drowned in noise. Enter Canoninja.
+
+Canoninja renames each Ninja rule to the hash of its contents. After that, we can just sort the build statements, and a
+simple `comm` command immediately reveal the essential difference between the files.
+
+## Example
+
+Consider the following makefile
+
+```makefile
+second :=
+first: foo
+foo:
+	@echo foo
+second: bar
+bar:
+	@echo bar
+```
+
+Depending on Kati version converting it to Ninja file will yield either:
+
+```
+$ cat /tmp/1.ninja
+# Generated by kati 06f2569b2d16628608c000a76e3d495a5a5528cb
+
+pool local_pool
+ depth = 72
+
+build _kati_always_build_: phony
+
+build first: phony foo
+rule rule0
+ description = build $out
+ command = /bin/sh -c "echo foo"
+build foo: rule0
+build second: phony bar
+rule rule1
+ description = build $out
+ command = /bin/sh -c "echo bar"
+build bar: rule1
+
+default first
+```
+
+or
+
+```
+$ cat 2.ninja
+# Generated by kati 371194da71b3e191fea6f2ccceb7b061bd0de310
+
+pool local_pool
+ depth = 72
+
+build _kati_always_build_: phony
+
+build second: phony bar
+rule rule0
+ description = build $out
+ command = /bin/sh -c "echo bar"
+build bar: rule0
+build first: phony foo
+rule rule1
+ description = build $out
+ command = /bin/sh -c "echo foo"
+build foo: rule1
+
+default first
+```
+
+This is a quirk in Kati, see https://github.com/google/kati/issues/238
+
+Trying to find out the difference between the targets even after sorting them isn't too helpful:
+
+```
+diff <(grep '^build' /tmp/1.ninja|sort) <(grep '^build' /tmp/2.ninja | sort)
+1c1
+< build bar: rule1
+---
+> build bar: rule0
+3c3
+< build foo: rule0
+---
+> build foo: rule1
+```
+
+However, running these files through `canoninja` yields
+
+```
+$ canoninja /tmp/1.ninja
+# Generated by kati 06f2569b2d16628608c000a76e3d495a5a5528cb
+
+pool local_pool
+ depth = 72
+
+build _kati_always_build_: phony
+
+build first: phony foo
+rule R2f9981d3c152fc255370dc67028244f7bed72a03
+ description = build $out
+ command = /bin/sh -c "echo foo"
+build foo: R2f9981d3c152fc255370dc67028244f7bed72a03
+build second: phony bar
+rule R62640f3f9095cf2da5b9d9e2a82f746cc710c94c
+ description = build $out
+ command = /bin/sh -c "echo bar"
+build bar: R62640f3f9095cf2da5b9d9e2a82f746cc710c94c
+
+default first
+```
+
+and
+
+```
+~/go/bin/canoninja /tmp/2.ninja
+# Generated by kati 371194da71b3e191fea6f2ccceb7b061bd0de310
+
+pool local_pool
+ depth = 72
+
+build _kati_always_build_: phony
+
+build second: phony bar
+rule R62640f3f9095cf2da5b9d9e2a82f746cc710c94c
+ description = build $out
+ command = /bin/sh -c "echo bar"
+build bar: R62640f3f9095cf2da5b9d9e2a82f746cc710c94c
+build first: phony foo
+rule R2f9981d3c152fc255370dc67028244f7bed72a03
+ description = build $out
+ command = /bin/sh -c "echo foo"
+build foo: R2f9981d3c152fc255370dc67028244f7bed72a03
+
+default first
+```
+
+and when we extract only build statements and sort them, we see that both Ninja files define the same graph:
+
+```shell
+$ diff <(~/go/bin/canoninja /tmp/1.ninja | grep '^build' | sort) \
+       <(~/go/bin/canoninja /tmp/2.ninja | grep '^build' | sort)
+```
+
+# Todo
+
+* Optionally output only the build statements, optionally sorted
+* Handle continuation lines correctly
diff --git a/tools/canoninja/canoninja.go b/tools/canoninja/canoninja.go
new file mode 100644
index 0000000..681a694
--- /dev/null
+++ b/tools/canoninja/canoninja.go
@@ -0,0 +1,130 @@
+package canoninja
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"encoding/hex"
+	"fmt"
+	"io"
+)
+
+var (
+	rulePrefix  = []byte("rule ")
+	buildPrefix = []byte("build ")
+	phonyRule   = []byte("phony")
+)
+
+func Generate(path string, buffer []byte, sink io.Writer) error {
+	// Break file into lines
+	from := 0
+	var lines [][]byte
+	for from < len(buffer) {
+		line := getLine(buffer[from:])
+		lines = append(lines, line)
+		from += len(line)
+	}
+
+	// FOr each rule, calculate and remember its digest
+	ruleDigest := make(map[string]string)
+	for i := 0; i < len(lines); {
+		if bytes.HasPrefix(lines[i], rulePrefix) {
+			// Find ruleName
+			rn := ruleName(lines[i])
+			if len(rn) == 0 {
+				return fmt.Errorf("%s:%d: rule name is missing or on the next line", path, i+1)
+			}
+			sRuleName := string(rn)
+			if _, ok := ruleDigest[sRuleName]; ok {
+				return fmt.Errorf("%s:%d: the rule %s has been already defined", path, i+1, sRuleName)
+			}
+			// Calculate rule text digest as a digests of line digests.
+			var digests []byte
+			doDigest := func(b []byte) {
+				h := sha1.New()
+				h.Write(b)
+				digests = h.Sum(digests)
+
+			}
+			// For the first line, digest everything after rule's name
+			doDigest(lines[i][cap(lines[i])+len(rn)-cap(rn):])
+			for i++; i < len(lines) && lines[i][0] == ' '; i++ {
+				doDigest(lines[i])
+			}
+			h := sha1.New()
+			h.Write(digests)
+			ruleDigest[sRuleName] = "R" + hex.EncodeToString(h.Sum(nil))
+
+		} else {
+			i++
+		}
+	}
+
+	// Rewrite rule names.
+	for i, line := range lines {
+		if bytes.HasPrefix(line, buildPrefix) {
+			brn := getBuildRuleName(line)
+			if bytes.Equal(brn, phonyRule) {
+				sink.Write(line)
+				continue
+			}
+			if len(brn) == 0 {
+				return fmt.Errorf("%s:%d: build statement lacks rule name", path, i+1)
+			}
+			sink.Write(line[0 : cap(line)-cap(brn)])
+			if digest, ok := ruleDigest[string(brn)]; ok {
+				sink.Write([]byte(digest))
+			} else {
+				return fmt.Errorf("%s:%d: no rule for this build target", path, i+1)
+			}
+			sink.Write(line[cap(line)+len(brn)-cap(brn):])
+		} else if bytes.HasPrefix(line, rulePrefix) {
+			rn := ruleName(line)
+			// Write everything before it
+			sink.Write(line[0 : cap(line)-cap(rn)])
+			sink.Write([]byte(ruleDigest[string(rn)]))
+			sink.Write(line[cap(line)+len(rn)-cap(rn):])
+		} else {
+			//goland:noinspection GoUnhandledErrorResult
+			sink.Write(line)
+		}
+	}
+	return nil
+}
+
+func getLine(b []byte) []byte {
+	if n := bytes.IndexByte(b, '\n'); n >= 0 {
+		return b[:n+1]
+	}
+	return b
+}
+
+// Returns build statement's rule name
+func getBuildRuleName(line []byte) []byte {
+	n := bytes.IndexByte(line, ':')
+	if n <= 0 {
+		return nil
+	}
+	ruleName := line[n+1:]
+	if ruleName[0] == ' ' {
+		ruleName = bytes.TrimLeft(ruleName, " ")
+	}
+	if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 {
+		ruleName = ruleName[0:n]
+	}
+	return ruleName
+}
+
+// Returns rule statement's rule name
+func ruleName(lineAfterRule []byte) []byte {
+	ruleName := lineAfterRule[len(rulePrefix):]
+	if len(ruleName) == 0 {
+		return ruleName
+	}
+	if ruleName[0] == ' ' {
+		ruleName = bytes.TrimLeft(ruleName, " ")
+	}
+	if n := bytes.IndexAny(ruleName, " \t\r\n"); n >= 0 {
+		ruleName = ruleName[0:n]
+	}
+	return ruleName
+}
diff --git a/tools/canoninja/canoninja_test.go b/tools/canoninja/canoninja_test.go
new file mode 100644
index 0000000..3c45f8c
--- /dev/null
+++ b/tools/canoninja/canoninja_test.go
@@ -0,0 +1,47 @@
+package canoninja
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestGenerate(t *testing.T) {
+	tests := []struct {
+		name     string
+		in       []byte
+		wantSink string
+		wantErr  bool
+	}{
+		{
+			name: "1",
+			in: []byte(`
+rule rule1
+  abcd
+rule rule2
+  abcd
+build x: rule1
+`),
+			wantSink: `
+rule R9c97aba7f61994be6862f5ea9a62d26130c7f48b
+  abcd
+rule R9c97aba7f61994be6862f5ea9a62d26130c7f48b
+  abcd
+build x: R9c97aba7f61994be6862f5ea9a62d26130c7f48b
+`,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sink := &bytes.Buffer{}
+			err := Generate("<file>", tt.in, sink)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if gotSink := sink.String(); gotSink != tt.wantSink {
+				t.Errorf("Generate() gotSink = %v, want %v", gotSink, tt.wantSink)
+			}
+		})
+	}
+}
diff --git a/tools/canoninja/cmd/canoninja.go b/tools/canoninja/cmd/canoninja.go
new file mode 100644
index 0000000..71802ef
--- /dev/null
+++ b/tools/canoninja/cmd/canoninja.go
@@ -0,0 +1,36 @@
+package main
+
+/*
+   Canoninja reads a Ninja file and changes the rule names to be the digest of the rule contents.
+   Feed  it to a filter that extracts only build statements, sort them, and you will have a crude
+   but effective tool to find small differences between two Ninja files.
+*/
+
+import (
+	"canoninja"
+	"flag"
+	"fmt"
+	"os"
+)
+
+func main() {
+	flag.Parse()
+	files := flag.Args()
+	if len(files) == 0 {
+		files = []string{"/dev/stdin"}
+	}
+	rc := 0
+	for _, f := range files {
+		if buffer, err := os.ReadFile(f); err == nil {
+			err = canoninja.Generate(f, buffer, os.Stdout)
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err)
+				rc = 1
+			}
+		} else {
+			fmt.Fprintf(os.Stderr, "%s: %s\n", f, err)
+			rc = 1
+		}
+	}
+	os.Exit(rc)
+}
diff --git a/tools/canoninja/go.mod b/tools/canoninja/go.mod
new file mode 100644
index 0000000..c5a924e
--- /dev/null
+++ b/tools/canoninja/go.mod
@@ -0,0 +1 @@
+module canoninja
diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py
index 1ff8e65..045cb1d 100755
--- a/tools/check_elf_file.py
+++ b/tools/check_elf_file.py
@@ -195,10 +195,12 @@
   @classmethod
   def _read_llvm_readobj(cls, elf_file_path, header, llvm_readobj):
     """Run llvm-readobj and parse the output."""
-    proc = subprocess.Popen(
-      [llvm_readobj, '-dynamic-table', '-dyn-symbols', elf_file_path],
-      stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    cmd = [llvm_readobj, '--dynamic-table', '--dyn-symbols', elf_file_path]
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, _ = proc.communicate()
+    rc = proc.returncode
+    if rc != 0:
+      raise subprocess.CalledProcessError(rc, cmd, out)
     lines = out.splitlines()
     return cls._parse_llvm_readobj(elf_file_path, header, lines)
 
diff --git a/tools/checkowners.py b/tools/checkowners.py
index d6853d8..f037321 100755
--- a/tools/checkowners.py
+++ b/tools/checkowners.py
@@ -5,8 +5,8 @@
 import argparse
 import re
 import sys
-import urllib
-import urllib2
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
 
 parser = argparse.ArgumentParser(description='Check OWNERS file syntax')
 parser.add_argument('-v', '--verbose', dest='verbose',
@@ -25,15 +25,15 @@
 
 def echo(msg):
   if args.verbose:
-    print msg
+    print(msg)
 
 
 def find_address(address):
   if address not in checked_addresses:
     request = (gerrit_server + '/accounts/?n=1&q=email:'
-               + urllib.quote(address))
+               + urllib.parse.quote(address))
     echo('Checking email address: ' + address)
-    result = urllib2.urlopen(request).read()
+    result = urllib.request.urlopen(request).read()
     checked_addresses[address] = result.find('"_account_id":') >= 0
     if checked_addresses[address]:
       echo('Found email address: ' + address)
@@ -43,7 +43,7 @@
 def check_address(fname, num, address):
   if find_address(address):
     return 0
-  print '%s:%d: ERROR: unknown email address: %s' % (fname, num, address)
+  print('%s:%d: ERROR: unknown email address: %s' % (fname, num, address))
   return 1
 
 
@@ -72,7 +72,7 @@
       stripped_line = re.sub('#.*$', '', line).strip()
       if not patterns.match(stripped_line):
         error += 1
-        print '%s:%d: ERROR: unknown line [%s]' % (fname, num, line.strip())
+        print('%s:%d: ERROR: unknown line [%s]' % (fname, num, line.strip()))
       elif args.check_address:
         if perfile_pattern.match(stripped_line):
           for addr in perfile_pattern.match(stripped_line).group(1).split(','):
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
new file mode 100644
index 0000000..225f3a5
--- /dev/null
+++ b/tools/compliance/Android.bp
@@ -0,0 +1,164 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "compliance_checkshare",
+    srcs: ["cmd/checkshare/checkshare.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/checkshare/checkshare_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliancenotice_bom",
+    srcs: ["cmd/bom/bom.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/bom/bom_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliancenotice_shippedlibs",
+    srcs: ["cmd/shippedlibs/shippedlibs.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/shippedlibs/shippedlibs_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliance_listshare",
+    srcs: ["cmd/listshare/listshare.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/listshare/listshare_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliance_dumpgraph",
+    srcs: ["cmd/dumpgraph/dumpgraph.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/dumpgraph/dumpgraph_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliance_dumpresolutions",
+    srcs: ["cmd/dumpresolutions/dumpresolutions.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/dumpresolutions/dumpresolutions_test.go"],
+}
+
+blueprint_go_binary {
+    name: "htmlnotice",
+    srcs: ["cmd/htmlnotice/htmlnotice.go"],
+    deps: [
+        "compliance-module",
+        "blueprint-deptools",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/htmlnotice/htmlnotice_test.go"],
+}
+
+blueprint_go_binary {
+    name: "compliance_rtrace",
+    srcs: ["cmd/rtrace/rtrace.go"],
+    deps: [
+        "compliance-module",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/rtrace/rtrace_test.go"],
+}
+
+blueprint_go_binary {
+    name: "textnotice",
+    srcs: ["cmd/textnotice/textnotice.go"],
+    deps: [
+        "compliance-module",
+        "blueprint-deptools",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/textnotice/textnotice_test.go"],
+}
+
+blueprint_go_binary {
+    name: "xmlnotice",
+    srcs: ["cmd/xmlnotice/xmlnotice.go"],
+    deps: [
+        "compliance-module",
+        "blueprint-deptools",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/xmlnotice/xmlnotice_test.go"],
+}
+
+bootstrap_go_package {
+    name: "compliance-module",
+    srcs: [
+        "condition.go",
+        "conditionset.go",
+        "doc.go",
+        "graph.go",
+        "noticeindex.go",
+        "policy_policy.go",
+        "policy_resolve.go",
+        "policy_resolvenotices.go",
+        "policy_resolveshare.go",
+        "policy_resolveprivacy.go",
+        "policy_shareprivacyconflicts.go",
+        "policy_shipped.go",
+        "policy_walk.go",
+        "readgraph.go",
+        "resolution.go",
+        "resolutionset.go",
+    ],
+    testSrcs: [
+        "condition_test.go",
+        "conditionset_test.go",
+        "readgraph_test.go",
+        "policy_policy_test.go",
+        "policy_resolve_test.go",
+        "policy_resolvenotices_test.go",
+        "policy_resolveshare_test.go",
+        "policy_resolveprivacy_test.go",
+        "policy_shareprivacyconflicts_test.go",
+        "policy_shipped_test.go",
+        "policy_walk_test.go",
+        "resolutionset_test.go",
+        "test_util.go",
+    ],
+    deps: [
+        "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
+        "license_metadata_proto",
+    ],
+    pkgPath: "android/soong/tools/compliance",
+}
diff --git a/tools/compliance/cmd/bom/bom.go b/tools/compliance/cmd/bom/bom.go
new file mode 100644
index 0000000..187f828
--- /dev/null
+++ b/tools/compliance/cmd/bom/bom.go
@@ -0,0 +1,189 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout      io.Writer
+	stderr      io.Writer
+	rootFS      fs.FS
+	stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a bill of materials. i.e. the list of installed paths.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the bill of materials. (default stdout)")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	if *outputFile != "-" {
+		ofile = &bytes.Buffer{}
+	}
+
+	ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix}
+
+	err := billOfMaterials(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// billOfMaterials implements the bom utility.
+func billOfMaterials(ctx *context, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// rs contains all notice resolutions.
+	rs := compliance.ResolveNotices(licenseGraph)
+
+	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+	if err != nil {
+		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	for path := range ni.InstallPaths() {
+		fmt.Fprintln(ctx.stdout, ctx.strip(path))
+	}
+	return nil
+}
diff --git a/tools/compliance/cmd/bom/bom_test.go b/tools/compliance/cmd/bom/bom_test.go
new file mode 100644
index 0000000..87a3b50
--- /dev/null
+++ b/tools/compliance/cmd/bom/bom_test.go
@@ -0,0 +1,322 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		stripPrefix string
+		expectedOut []string
+	}{
+		{
+			condition:   "firstparty",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			stripPrefix: "out/target/product/fictional",
+			expectedOut: []string{
+				"/system/apex/highest.apex",
+				"/system/apex/highest.apex/bin/bin1",
+				"/system/apex/highest.apex/bin/bin2",
+				"/system/apex/highest.apex/lib/liba.so",
+				"/system/apex/highest.apex/lib/libb.so",
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			stripPrefix: "out/target/product/fictional/data/",
+			expectedOut: []string{
+				"container.zip",
+				"container.zip/bin1",
+				"container.zip/bin2",
+				"container.zip/liba.so",
+				"container.zip/libb.so",
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			stripPrefix: "out/target/product/fictional/bin/",
+			expectedOut: []string{"application"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"bin/bin1"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"lib/libd.so"},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"out/target/product/fictional/system/apex/highest.apex",
+				"out/target/product/fictional/system/apex/highest.apex/bin/bin1",
+				"out/target/product/fictional/system/apex/highest.apex/bin/bin2",
+				"out/target/product/fictional/system/apex/highest.apex/lib/liba.so",
+				"out/target/product/fictional/system/apex/highest.apex/lib/libb.so",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"out/target/product/fictional/data/container.zip",
+				"out/target/product/fictional/data/container.zip/bin1",
+				"out/target/product/fictional/data/container.zip/bin2",
+				"out/target/product/fictional/data/container.zip/liba.so",
+				"out/target/product/fictional/data/container.zip/libb.so",
+			},
+		},
+		{
+			condition:   "notice",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"out/target/product/fictional/bin/application"},
+		},
+		{
+			condition:   "notice",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"out/target/product/fictional/system/bin/bin1"},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"out/target/product/fictional/system/lib/libd.so"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/apex/",
+			expectedOut: []string{
+				"highest.apex",
+				"highest.apex/bin/bin1",
+				"highest.apex/bin/bin2",
+				"highest.apex/lib/liba.so",
+				"highest.apex/lib/libb.so",
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			stripPrefix: "out/target/product/fictional/data/",
+			expectedOut: []string{
+				"container.zip",
+				"container.zip/bin1",
+				"container.zip/bin2",
+				"container.zip/liba.so",
+				"container.zip/libb.so",
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			stripPrefix: "out/target/product/fictional/bin/",
+			expectedOut: []string{"application"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"bin/bin1"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"lib/libd.so"},
+		},
+		{
+			condition:   "restricted",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/apex/",
+			expectedOut: []string{
+				"highest.apex",
+				"highest.apex/bin/bin1",
+				"highest.apex/bin/bin2",
+				"highest.apex/lib/liba.so",
+				"highest.apex/lib/libb.so",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			stripPrefix: "out/target/product/fictional/data/",
+			expectedOut: []string{
+				"container.zip",
+				"container.zip/bin1",
+				"container.zip/bin2",
+				"container.zip/liba.so",
+				"container.zip/libb.so",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			stripPrefix: "out/target/product/fictional/bin/",
+			expectedOut: []string{"application"},
+		},
+		{
+			condition:   "restricted",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"bin/bin1"},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"lib/libd.so"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/apex/",
+			expectedOut: []string{
+				"highest.apex",
+				"highest.apex/bin/bin1",
+				"highest.apex/bin/bin2",
+				"highest.apex/lib/liba.so",
+				"highest.apex/lib/libb.so",
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			stripPrefix: "out/target/product/fictional/data/",
+			expectedOut: []string{
+				"container.zip",
+				"container.zip/bin1",
+				"container.zip/bin2",
+				"container.zip/liba.so",
+				"container.zip/libb.so",
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			stripPrefix: "out/target/product/fictional/bin/",
+			expectedOut: []string{"application"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"bin/bin1"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/",
+			expectedOut: []string{"lib/libd.so"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), []string{tt.stripPrefix}}
+
+			err := billOfMaterials(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("bom: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("bom: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", strings.Join(tt.expectedOut, "\n"))
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("bom: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if tt.expectedOut[lineno] != line {
+					t.Errorf("bom: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno])
+				}
+				lineno++
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("bom: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno])
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/checkshare/checkshare.go b/tools/compliance/cmd/checkshare/checkshare.go
new file mode 100644
index 0000000..f7b4cd2
--- /dev/null
+++ b/tools/compliance/cmd/checkshare/checkshare.go
@@ -0,0 +1,178 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failConflicts     = fmt.Errorf("conflicts")
+	failNoneRequested = fmt.Errorf("\nNo metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses")
+)
+
+// byError orders conflicts by error string
+type byError []compliance.SourceSharePrivacyConflict
+
+func (l byError) Len() int           { return len(l) }
+func (l byError) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
+func (l byError) Less(i, j int) bool { return l[i].Error() < l[j].Error() }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...}
+
+Reports on stderr any targets where policy says that the source both
+must and must not be shared. The error report indicates the target, the
+license condition that has a source privacy policy, and the license
+condition that has a source sharing policy.
+
+Any given target may appear multiple times with different combinations
+of conflicting license conditions.
+
+If all the source code that policy says must be shared may be shared,
+outputs "PASS" to stdout and exits with status 0.
+
+If policy says any source must both be shared and not be shared,
+outputs "FAIL" to stdout and exits with status 1.
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	err := checkShare(ofile, os.Stderr, compliance.FS, flags.Args()...)
+	if err != nil {
+		if err != failConflicts {
+			if err == failNoneRequested {
+				flags.Usage()
+			}
+			fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		}
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// checkShare implements the checkshare utility.
+func checkShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
+
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %w\n", files, os.Getenv("PWD"), err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// Apply policy to find conflicts and report them to stderr lexicographically ordered.
+	conflicts := compliance.ConflictingSharedPrivateSource(licenseGraph)
+	sort.Sort(byError(conflicts))
+	for _, conflict := range conflicts {
+		fmt.Fprintln(stderr, conflict.Error())
+	}
+
+	// Indicate pass or fail on stdout.
+	if len(conflicts) > 0 {
+		fmt.Fprintln(stdout, "FAIL")
+		return failConflicts
+	}
+	fmt.Fprintln(stdout, "PASS")
+	return nil
+}
diff --git a/tools/compliance/cmd/checkshare/checkshare_test.go b/tools/compliance/cmd/checkshare/checkshare_test.go
new file mode 100644
index 0000000..fdcab292
--- /dev/null
+++ b/tools/compliance/cmd/checkshare/checkshare_test.go
@@ -0,0 +1,300 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+type outcome struct {
+	target           string
+	privacyCondition string
+	shareCondition   string
+}
+
+func (o *outcome) String() string {
+	return fmt.Sprintf("%s %s and must share from %s", o.target, o.privacyCondition, o.shareCondition)
+}
+
+type outcomeList []*outcome
+
+func (ol outcomeList) String() string {
+	result := ""
+	for _, o := range ol {
+		result = result + o.String() + "\n"
+	}
+	return result
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition        string
+		name             string
+		outDir           string
+		roots            []string
+		expectedStdout   string
+		expectedOutcomes outcomeList
+	}{
+		{
+			condition:      "firstparty",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "proprietary",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/lib/liba.so.meta_lic",
+					privacyCondition: "proprietary",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic", "lib/libb.so.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			err := checkShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil && err != failConflicts {
+				t.Fatalf("checkshare: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			var actualStdout string
+			for _, s := range strings.Split(stdout.String(), "\n") {
+				ts := strings.TrimLeft(s, " \t")
+				if len(ts) < 1 {
+					continue
+				}
+				if len(actualStdout) > 0 {
+					t.Errorf("checkshare: unexpected multiple output lines %q, want %q", actualStdout+"\n"+ts, tt.expectedStdout)
+				}
+				actualStdout = ts
+			}
+			if actualStdout != tt.expectedStdout {
+				t.Errorf("checkshare: unexpected stdout %q, want %q", actualStdout, tt.expectedStdout)
+			}
+			errList := strings.Split(stderr.String(), "\n")
+			actualOutcomes := make(outcomeList, 0, len(errList))
+			for _, cstring := range errList {
+				ts := strings.TrimLeft(cstring, " \t")
+				if len(ts) < 1 {
+					continue
+				}
+				cFields := strings.Split(ts, " ")
+				actualOutcomes = append(actualOutcomes, &outcome{
+					target:           cFields[0],
+					privacyCondition: cFields[1],
+					shareCondition:   cFields[6],
+				})
+			}
+			if len(actualOutcomes) != len(tt.expectedOutcomes) {
+				t.Errorf("checkshare: unexpected got %d outcomes %s, want %d outcomes %s",
+					len(actualOutcomes), actualOutcomes, len(tt.expectedOutcomes), tt.expectedOutcomes)
+				return
+			}
+			for i := range actualOutcomes {
+				if actualOutcomes[i].String() != tt.expectedOutcomes[i].String() {
+					t.Errorf("checkshare: unexpected outcome #%d, got %q, want %q",
+						i+1, actualOutcomes[i], tt.expectedOutcomes[i])
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph.go b/tools/compliance/cmd/dumpgraph/dumpgraph.go
new file mode 100644
index 0000000..5625779
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph.go
@@ -0,0 +1,266 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	graphViz        bool
+	labelConditions bool
+	stripPrefix     []string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs space-separated Target Dependency Annotations tuples for each
+edge in the license graph. When -dot flag given, outputs the nodes and
+edges in graphViz directed graph format.
+
+In plain text mode, multiple values within a field are colon-separated.
+e.g. multiple annotations appear as annotation1:annotation2:annotation3
+or when -label_conditions is requested, Target and Dependency become
+target:condition1:condition2 etc.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
+	labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
+	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	ctx := &context{*graphViz, *labelConditions, *stripPrefix}
+
+	err := dumpGraph(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// dumpGraph implements the dumpgraph utility.
+func dumpGraph(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// Sort the edges of the graph.
+	edges := licenseGraph.Edges()
+	sort.Sort(edges)
+
+	// nodes maps license metadata file names to graphViz node names when ctx.graphViz is true.
+	var nodes map[string]string
+	n := 0
+
+	// targetOut calculates the string to output for `target` separating conditions as needed using `sep`.
+	targetOut := func(target *compliance.TargetNode, sep string) string {
+		tOut := ctx.strip(target.Name())
+		if ctx.labelConditions {
+			conditions := target.LicenseConditions().Names()
+			sort.Strings(conditions)
+			if len(conditions) > 0 {
+				tOut += sep + strings.Join(conditions, sep)
+			}
+		}
+		return tOut
+	}
+
+	// makeNode maps `target` to a graphViz node name.
+	makeNode := func(target *compliance.TargetNode) {
+		tName := target.Name()
+		if _, ok := nodes[tName]; !ok {
+			nodeName := fmt.Sprintf("n%d", n)
+			nodes[tName] = nodeName
+			fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
+			n++
+		}
+	}
+
+	// If graphviz output, map targets to node names, and start the directed graph.
+	if ctx.graphViz {
+		nodes = make(map[string]string)
+		targets := licenseGraph.Targets()
+		sort.Sort(targets)
+
+		fmt.Fprintf(stdout, "strict digraph {\n\trankdir=RL;\n")
+		for _, target := range targets {
+			makeNode(target)
+		}
+	}
+
+	// Print the sorted edges to stdout ...
+	for _, e := range edges {
+		// sort the annotations for repeatability/stability
+		annotations := e.Annotations().AsList()
+		sort.Strings(annotations)
+
+		tName := e.Target().Name()
+		dName := e.Dependency().Name()
+
+		if ctx.graphViz {
+			// ... one edge per line labelled with \\n-separated annotations.
+			tNode := nodes[tName]
+			dNode := nodes[dName]
+			fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", dNode, tNode, strings.Join(annotations, "\\n"))
+		} else {
+			// ... one edge per line with annotations in a colon-separated tuple.
+			fmt.Fprintf(stdout, "%s %s %s\n", targetOut(e.Target(), ":"), targetOut(e.Dependency(), ":"), strings.Join(annotations, ":"))
+		}
+	}
+
+	// If graphViz output, rank the root nodes together, and complete the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "\t{rank=same;")
+		for _, f := range files {
+			fName := f
+			if !strings.HasSuffix(fName, ".meta_lic") {
+				fName += ".meta_lic"
+			}
+			if fNode, ok := nodes[fName]; ok {
+				fmt.Fprintf(stdout, " %s", fNode)
+			}
+		}
+		fmt.Fprintf(stdout, "}\n}\n")
+	}
+	return nil
+}
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph_test.go b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
new file mode 100644
index 0000000..d1deed3
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
@@ -0,0 +1,1273 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test_plaintext(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		ctx         context
+		expectedOut []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libd.so.meta_lic dynamic",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin1.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin2.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libd.so.meta_lic dynamic",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin1.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin2.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/application.meta_lic testdata/firstparty/bin/bin3.meta_lic toolchain",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libd.so.meta_lic dynamic",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin1.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin2.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libd.so.meta_lic dynamic",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin1.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin2.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/application.meta_lic testdata/notice/bin/bin3.meta_lic toolchain",
+				"testdata/notice/application.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/application.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libd.so.meta_lic dynamic",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin1.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin2.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:reciprocal static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:reciprocal static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libd.so.meta_lic dynamic",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin1.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin2.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/bin/bin3.meta_lic toolchain",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic dynamic",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic dynamic",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/application.meta_lic testdata/restricted/bin/bin3.meta_lic toolchain",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic dynamic",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin1.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:by_exception_only:proprietary static",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libb.so.meta_lic:restricted dynamic",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:by_exception_only:proprietary static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic dynamic",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin1.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/application.meta_lic testdata/proprietary/bin/bin3.meta_lic toolchain",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				expectedOut.WriteString(eo)
+				expectedOut.WriteString("\n")
+			}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpgraph: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
+
+type testContext struct {
+	nextNode int
+	nodes    map[string]string
+}
+
+type matcher interface {
+	matchString(*testContext) string
+	typeString() string
+}
+
+type targetMatcher struct {
+	target     string
+	conditions []string
+}
+
+func (tm *targetMatcher) matchString(ctx *testContext) string {
+	m := tm.target
+	if len(tm.conditions) > 0 {
+		m += "\\n" + strings.Join(tm.conditions, "\\n")
+	}
+	m = ctx.nodes[tm.target] + " [label=\"" + m + "\"];"
+	return m
+}
+
+func (tm *targetMatcher) typeString() string {
+	return "target"
+}
+
+type edgeMatcher struct {
+	target      string
+	dep         string
+	annotations []string
+}
+
+func (em *edgeMatcher) matchString(ctx *testContext) string {
+	return ctx.nodes[em.dep] + " -> " + ctx.nodes[em.target] + " [label=\"" + strings.Join(em.annotations, "\\n") + "\"];"
+}
+
+func (tm *edgeMatcher) typeString() string {
+	return "edge"
+}
+
+type getMatcher func(*testContext) matcher
+
+func matchTarget(target string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		ctx.nodes[target] = fmt.Sprintf("n%d", ctx.nextNode)
+		ctx.nextNode++
+		return &targetMatcher{target, append([]string{}, conditions...)}
+	}
+}
+
+func matchEdge(target, dep string, annotations ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		if _, ok := ctx.nodes[target]; !ok {
+			panic(fmt.Errorf("no node for target %v in %v -> %v [label=\"%s\"];", target, dep, target, strings.Join(annotations, "\\n")))
+		}
+		if _, ok := ctx.nodes[dep]; !ok {
+			panic(fmt.Errorf("no node for dep %v in %v -> %v [label=\"%s\"];", target, dep, target, strings.Join(annotations, "\\n")))
+		}
+		return &edgeMatcher{target, dep, append([]string{}, annotations...)}
+	}
+}
+
+func Test_graphviz(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		ctx         context
+		expectedOut []getMatcher
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/highest.apex.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/container.zip.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/application.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin3.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/firstparty/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/highest.apex.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/container.zip.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/application.meta_lic"),
+				matchTarget("testdata/notice/bin/bin3.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/notice/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/highest.apex.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "reciprocal"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/container.zip.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/application.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin3.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/reciprocal/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/highest.apex.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "restricted_allows_dynamic_linking"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/container.zip.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/application.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin3.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/restricted/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/highest.apex.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libc.a.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/container.zip.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/application.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin3.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/proprietary/lib/libd.so.meta_lic")},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			ctx := &testContext{0, make(map[string]string)}
+
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				m := eo(ctx)
+				expectedOut.WriteString(m.matchString(ctx))
+				expectedOut.WriteString("\n")
+			}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			tt.ctx.graphViz = true
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpgraph: gotStderr = %v, want none", stderr)
+			}
+			outList := strings.Split(stdout.String(), "\n")
+			outLine := 0
+			if outList[outLine] != "strict digraph {" {
+				t.Errorf("dumpgraph: got 1st line %v, want strict digraph {", outList[outLine])
+			}
+			outLine++
+			if strings.HasPrefix(strings.TrimLeft(outList[outLine], " \t"), "rankdir") {
+				outLine++
+			}
+			endOut := len(outList)
+			for endOut > 0 && strings.TrimLeft(outList[endOut-1], " \t") == "" {
+				endOut--
+			}
+			if outList[endOut-1] != "}" {
+				t.Errorf("dumpgraph: got last line %v, want }", outList[endOut-1])
+			}
+			endOut--
+			if strings.HasPrefix(strings.TrimLeft(outList[endOut-1], " \t"), "{rank=same") {
+				endOut--
+			}
+			expectedList := strings.Split(expectedOut.String(), "\n")
+			for len(expectedList) > 0 && expectedList[len(expectedList)-1] == "" {
+				expectedList = expectedList[0 : len(expectedList)-1]
+			}
+			matchLine := 0
+
+			for outLine < endOut && matchLine < len(expectedList) && strings.TrimLeft(outList[outLine], " \t") == expectedList[matchLine] {
+				outLine++
+				matchLine++
+			}
+			if outLine < endOut || matchLine < len(expectedList) {
+				if outLine >= endOut {
+					t.Errorf("dumpgraph: missing lines at end of graph, want %d lines %v", len(expectedList)-matchLine, strings.Join(expectedList[matchLine:], "\n"))
+				} else if matchLine >= len(expectedList) {
+					t.Errorf("dumpgraph: unexpected lines at end of graph starting line %d, got %v, want nothing", outLine+1, strings.Join(outList[outLine:], "\n"))
+				} else {
+					t.Errorf("dumpgraph: at line %d, got %v, want %v", outLine+1, strings.Join(outList[outLine:], "\n"), strings.Join(expectedList[matchLine:], "\n"))
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
new file mode 100644
index 0000000..dc0cf88
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
@@ -0,0 +1,312 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	conditions      []compliance.LicenseCondition
+	graphViz        bool
+	labelConditions bool
+	stripPrefix     []string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a space-separated Target ActsOn Origin Condition tuple for each
+resolution in the graph. When -dot flag given, outputs nodes and edges
+in graphviz directed graph format.
+
+If one or more '-c condition' conditions are given, outputs the
+resolution for the union of the conditions. Otherwise, outputs the
+resolution for all conditions.
+
+In plain text mode, when '-label_conditions' is requested, the Target
+and Origin have colon-separated license conditions appended:
+i.e. target:condition1:condition2 etc.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	conditions := newMultiString(flags, "c", "License condition to resolve. (may be given multiple times)")
+	graphViz := flags.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
+	labelConditions := flags.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
+	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	lcs := make([]compliance.LicenseCondition, 0, len(*conditions))
+	for _, name := range *conditions {
+		lcs = append(lcs, compliance.RecognizedConditionNames[name])
+	}
+	ctx := &context{
+		conditions:      lcs,
+		graphViz:        *graphViz,
+		labelConditions: *labelConditions,
+		stripPrefix:     *stripPrefix,
+	}
+	_, err := dumpResolutions(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// dumpResolutions implements the dumpresolutions utility.
+func dumpResolutions(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
+	if len(files) < 1 {
+		return nil, failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return nil, failNoLicenses
+	}
+
+	compliance.ResolveTopDownConditions(licenseGraph)
+	cs := compliance.AllLicenseConditions
+	if len(ctx.conditions) > 0 {
+		cs = compliance.NewLicenseConditionSet()
+		for _, c := range ctx.conditions {
+			cs = cs.Plus(c)
+		}
+	}
+
+	resolutions := compliance.WalkResolutionsForCondition(licenseGraph, cs)
+
+	// nodes maps license metadata file names to graphViz node names when graphViz requested.
+	nodes := make(map[string]string)
+	n := 0
+
+	// targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
+	targetOut := func(target *compliance.TargetNode, sep string) string {
+		tOut := ctx.strip(target.Name())
+		if ctx.labelConditions {
+			conditions := target.LicenseConditions().Names()
+			if len(conditions) > 0 {
+				tOut += sep + strings.Join(conditions, sep)
+			}
+		}
+		return tOut
+	}
+
+	// makeNode maps `target` to a graphViz node name.
+	makeNode := func(target *compliance.TargetNode) {
+		tName := target.Name()
+		if _, ok := nodes[tName]; !ok {
+			nodeName := fmt.Sprintf("n%d", n)
+			nodes[tName] = nodeName
+			fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
+			n++
+		}
+	}
+
+	// outputResolution prints a resolution in the requested format to `stdout`, where one can read
+	// a resolution as `tname` resolves `oname`'s conditions named in `cnames`.
+	// `tname` is the name of the target the resolution applies to.
+	// `cnames` is the list of conditions to resolve.
+	outputResolution := func(tname, aname string, cnames []string) {
+		if ctx.graphViz {
+			// ... one edge per line labelled with \\n-separated annotations.
+			tNode := nodes[tname]
+			aNode := nodes[aname]
+			fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", tNode, aNode, strings.Join(cnames, "\\n"))
+		} else {
+			// ... one edge per line with names in a colon-separated tuple.
+			fmt.Fprintf(stdout, "%s %s %s\n", tname, aname, strings.Join(cnames, ":"))
+		}
+	}
+
+	// Sort the resolutions by targetname for repeatability/stability.
+	targets := resolutions.AttachesTo()
+	sort.Sort(targets)
+
+	// If graphviz output, start the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "strict digraph {\n\trankdir=LR;\n")
+		for _, target := range targets {
+			makeNode(target)
+			rl := resolutions.Resolutions(target)
+			sort.Sort(rl)
+			for _, r := range rl {
+				makeNode(r.ActsOn())
+			}
+		}
+	}
+
+	// Output the sorted targets.
+	for _, target := range targets {
+		var tname string
+		if ctx.graphViz {
+			tname = target.Name()
+		} else {
+			tname = targetOut(target, ":")
+		}
+
+		rl := resolutions.Resolutions(target)
+		sort.Sort(rl)
+		for _, r := range rl {
+			var aname string
+			if ctx.graphViz {
+				aname = r.ActsOn().Name()
+			} else {
+				aname = targetOut(r.ActsOn(), ":")
+			}
+
+			// cnames accumulates the list of condition names originating at a single origin that apply to `target`.
+			cnames := r.Resolves().Names()
+
+			// Output 1 line for each attachesTo+actsOn combination.
+			outputResolution(tname, aname, cnames)
+		}
+	}
+	// If graphViz output, rank the root nodes together, and complete the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "\t{rank=same;")
+		for _, f := range files {
+			fName := f
+			if !strings.HasSuffix(fName, ".meta_lic") {
+				fName += ".meta_lic"
+			}
+			if fNode, ok := nodes[fName]; ok {
+				fmt.Fprintf(stdout, " %s", fNode)
+			}
+		}
+		fmt.Fprintf(stdout, "}\n}\n")
+	}
+	return licenseGraph, nil
+}
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
new file mode 100644
index 0000000..63fd157
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
@@ -0,0 +1,3360 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test_plaintext(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		ctx         context
+		expectedOut []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/highest.apex.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  append(compliance.ImpliesPrivate.AsList(), compliance.ImpliesShared.AsList()...),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/container.zip.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/application.meta_lic testdata/firstparty/application.meta_lic notice",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/highest.apex.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/container.zip.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/application.meta_lic testdata/notice/application.meta_lic notice",
+				"testdata/notice/application.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/highest.apex.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:reciprocal reciprocal",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:reciprocal reciprocal",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal",
+				"lib/liba.so.meta_lic:reciprocal lib/liba.so.meta_lic:reciprocal reciprocal",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/container.zip.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/application.meta_lic notice",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice:restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice:restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic bin/bin2.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic restricted:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic bin/bin2.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic restricted:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice:restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal:restricted_allows_dynamic_linking",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice:restricted",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice:restricted",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice:restricted:restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal:restricted_allows_dynamic_linking",
+				"lib/liba.so.meta_lic:restricted_allows_dynamic_linking lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+				"lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted:restricted_allows_dynamic_linking",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/highest.apex.meta_lic notice:restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic proprietary:by_exception_only",
+				"bin/bin2.meta_lic bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"highest.apex.meta_lic highest.apex.meta_lic notice:restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic proprietary:by_exception_only",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{
+				"bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic restricted",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic proprietary",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+				"highest.apex.meta_lic bin/bin2.meta_lic proprietary",
+				"highest.apex.meta_lic lib/liba.so.meta_lic proprietary",
+				"highest.apex.meta_lic lib/libc.a.meta_lic proprietary",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic proprietary",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic restricted:proprietary",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic restricted:proprietary",
+				"highest.apex.meta_lic highest.apex.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic proprietary",
+				"highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic proprietary",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}, labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+				"bin/bin2.meta_lic:proprietary:by_exception_only bin/bin2.meta_lic:proprietary:by_exception_only restricted:proprietary:by_exception_only",
+				"bin/bin2.meta_lic:proprietary:by_exception_only lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:proprietary:by_exception_only restricted:proprietary:by_exception_only",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice:restricted",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+				"lib/liba.so.meta_lic:proprietary:by_exception_only lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+				"lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/container.zip.meta_lic notice:restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/application.meta_lic testdata/proprietary/application.meta_lic notice:restricted",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic restricted:proprietary:by_exception_only",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic notice",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				expectedOut.WriteString(eo)
+				expectedOut.WriteString("\n")
+			}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			_, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpresolutions: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("dumpresoliutions: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
+
+type testContext struct {
+	nextNode int
+	nodes    map[string]string
+}
+
+type matcher interface {
+	matchString(*testContext, *compliance.LicenseGraph) string
+	typeString() string
+}
+
+type targetMatcher struct {
+	target     string
+	conditions []string
+}
+
+// newTestCondition constructs a test license condition in the license graph.
+func newTestCondition(lg *compliance.LicenseGraph, conditionName ...string) compliance.LicenseConditionSet {
+	cs := compliance.NewLicenseConditionSet()
+	for _, name := range conditionName {
+		cs = cs.Plus(compliance.RecognizedConditionNames[name])
+	}
+	if cs.IsEmpty() && len(conditionName) != 0 {
+		panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+	}
+	return cs
+}
+
+func (tm *targetMatcher) matchString(ctx *testContext, lg *compliance.LicenseGraph) string {
+	cs := newTestCondition(lg, tm.conditions...)
+	m := tm.target
+	if !cs.IsEmpty() {
+		m += "\\n" + strings.Join(cs.Names(), "\\n")
+	}
+	m = ctx.nodes[tm.target] + " [label=\"" + m + "\"];"
+	return m
+}
+
+func (tm *targetMatcher) typeString() string {
+	return "target"
+}
+
+type resolutionMatcher struct {
+	appliesTo  string
+	actsOn     string
+	conditions []string
+}
+
+func (rm *resolutionMatcher) matchString(ctx *testContext, lg *compliance.LicenseGraph) string {
+	cs := newTestCondition(lg, rm.conditions...)
+	return ctx.nodes[rm.appliesTo] + " -> " + ctx.nodes[rm.actsOn] +
+		" [label=\"" + strings.Join(cs.Names(), "\\n") + "\"];"
+}
+
+func (rm *resolutionMatcher) typeString() string {
+	return "resolution"
+}
+
+type getMatcher func(*testContext) matcher
+
+func matchTarget(target string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		ctx.nodes[target] = fmt.Sprintf("n%d", ctx.nextNode)
+		ctx.nextNode++
+		return &targetMatcher{target, append([]string{}, conditions...)}
+	}
+}
+
+func matchResolution(appliesTo, actsOn string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		if _, ok := ctx.nodes[appliesTo]; !ok {
+			ctx.nodes[appliesTo] = fmt.Sprintf("unknown%d", ctx.nextNode)
+			ctx.nextNode++
+		}
+		if _, ok := ctx.nodes[actsOn]; !ok {
+			ctx.nodes[actsOn] = fmt.Sprintf("unknown%d", ctx.nextNode)
+			ctx.nextNode++
+		}
+		return &resolutionMatcher{appliesTo, actsOn, append([]string{}, conditions...)}
+	}
+}
+
+func Test_graphviz(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		ctx         context
+		expectedOut []getMatcher
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/highest.apex.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/firstparty/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/container.zip.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/application.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/application.meta_lic",
+					"testdata/firstparty/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/application.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/highest.apex.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/notice/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/container.zip.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/application.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/application.meta_lic",
+					"testdata/notice/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/application.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/highest.apex.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/reciprocal/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "reciprocal"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/container.zip.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/application.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/application.meta_lic",
+					"testdata/reciprocal/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/application.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/highest.apex.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/highest.apex.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/restricted/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "restricted_allows_dynamic_linking"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/container.zip.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/container.zip.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/application.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/application.meta_lic",
+					"restricted",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted_allows_dynamic_linking"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"restricted_allows_dynamic_linking",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/highest.apex.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/highest.apex.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"by_exception_only",
+					"restricted",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []compliance.LicenseCondition{compliance.NoticeCondition},
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.AsList(),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesPrivate.AsList(),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: []string{"testdata/proprietary/"}, labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libc.a.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("bin/bin2.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/container.zip.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/container.zip.meta_lic",
+					"restricted",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/application.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/application.meta_lic",
+					"notice",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"restricted",
+					"by_exception_only",
+					"proprietary"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			ctx := &testContext{0, make(map[string]string)}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			tt.ctx.graphViz = true
+			lg, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpresolutions: gotStderr = %v, want none", stderr)
+			}
+
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				m := eo(ctx)
+				expectedOut.WriteString(m.matchString(ctx, lg))
+				expectedOut.WriteString("\n")
+			}
+
+			outList := strings.Split(stdout.String(), "\n")
+			outLine := 0
+			if outList[outLine] != "strict digraph {" {
+				t.Errorf("dumpresolutions: got 1st line %v, want strict digraph {", outList[outLine])
+			}
+			outLine++
+			if strings.HasPrefix(strings.TrimLeft(outList[outLine], " \t"), "rankdir") {
+				outLine++
+			}
+			endOut := len(outList)
+			for endOut > 0 && strings.TrimLeft(outList[endOut-1], " \t") == "" {
+				endOut--
+			}
+			if outList[endOut-1] != "}" {
+				t.Errorf("dumpresolutions: got last line %v, want }", outList[endOut-1])
+			}
+			endOut--
+			if strings.HasPrefix(strings.TrimLeft(outList[endOut-1], " \t"), "{rank=same") {
+				endOut--
+			}
+			expectedList := strings.Split(expectedOut.String(), "\n")
+			for len(expectedList) > 0 && expectedList[len(expectedList)-1] == "" {
+				expectedList = expectedList[0 : len(expectedList)-1]
+			}
+			matchLine := 0
+
+			for outLine < endOut && matchLine < len(expectedList) && strings.TrimLeft(outList[outLine], " \t") == expectedList[matchLine] {
+				outLine++
+				matchLine++
+			}
+			if outLine < endOut || matchLine < len(expectedList) {
+				if outLine >= endOut {
+					t.Errorf("dumpresolutions: missing lines at end of graph, want %d lines %v", len(expectedList)-matchLine, strings.Join(expectedList[matchLine:], "\n"))
+				} else if matchLine >= len(expectedList) {
+					t.Errorf("dumpresolutions: unexpected lines at end of graph starting line %d, got %v, want nothing", outLine+1, strings.Join(outList[outLine:], "\n"))
+				} else {
+					t.Errorf("dumpresolutions: at line %d, got %v, want %v", outLine+1, strings.Join(outList[outLine:], "\n"), strings.Join(expectedList[matchLine:], "\n"))
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice.go b/tools/compliance/cmd/htmlnotice/htmlnotice.go
new file mode 100644
index 0000000..1a49610
--- /dev/null
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice.go
@@ -0,0 +1,281 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"compress/gzip"
+	"flag"
+	"fmt"
+	"html"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+
+	"github.com/google/blueprint/deptools"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout      io.Writer
+	stderr      io.Writer
+	rootFS      fs.FS
+	includeTOC  bool
+	product     string
+	stripPrefix []string
+	title       string
+	deps        *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				p = ctx.product
+			}
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs an html NOTICE.html or gzipped NOTICE.html.gz file if the -o filename
+ends with ".gz".
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
+	depsFile := flags.String("d", "", "Where to write the deps file")
+	includeTOC := flags.Bool("toc", true, "Whether to include a table of contents.")
+	product := flags.String("product", "", "The name of the product for which the notice is generated.")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+	title := flags.String("title", "", "The title of the notice file.")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	var closer io.Closer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+	if strings.HasSuffix(*outputFile, ".gz") {
+		ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+		closer = ofile.(io.Closer)
+	}
+
+	var deps []string
+
+	ctx := &context{ofile, os.Stderr, compliance.FS, *includeTOC, *product, *stripPrefix, *title, &deps}
+
+	err := htmlNotice(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if closer != nil {
+		closer.Close()
+	}
+
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+	if *depsFile != "" {
+		err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// htmlNotice implements the htmlnotice utility.
+func htmlNotice(ctx *context, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// rs contains all notice resolutions.
+	rs := compliance.ResolveNotices(licenseGraph)
+
+	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+	if err != nil {
+		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	fmt.Fprintln(ctx.stdout, "<!DOCTYPE html>")
+	fmt.Fprintln(ctx.stdout, "<html><head>")
+	fmt.Fprintln(ctx.stdout, "<style type=\"text/css\">")
+	fmt.Fprintln(ctx.stdout, "body { padding: 2px; margin: 0; }")
+	fmt.Fprintln(ctx.stdout, "ul { list-style-type: none; margin: 0; padding: 0; }")
+	fmt.Fprintln(ctx.stdout, "li { padding-left: 1em; }")
+	fmt.Fprintln(ctx.stdout, ".file-list { margin-left: 1em; }")
+	fmt.Fprintln(ctx.stdout, "</style>")
+	if len(ctx.title) > 0 {
+		fmt.Fprintf(ctx.stdout, "<title>%s</title>\n", html.EscapeString(ctx.title))
+	} else if len(ctx.product) > 0 {
+		fmt.Fprintf(ctx.stdout, "<title>%s</title>\n", html.EscapeString(ctx.product))
+	}
+	fmt.Fprintln(ctx.stdout, "</head>")
+	fmt.Fprintln(ctx.stdout, "<body>")
+
+	if len(ctx.title) > 0 {
+		fmt.Fprintf(ctx.stdout, "  <h1>%s</h1>\n", html.EscapeString(ctx.title))
+	} else if len(ctx.product) > 0 {
+		fmt.Fprintf(ctx.stdout, "  <h1>%s</h1>\n", html.EscapeString(ctx.product))
+	}
+	ids := make(map[string]string)
+	if ctx.includeTOC {
+		fmt.Fprintln(ctx.stdout, "  <ul class=\"toc\">")
+		i := 0
+		for installPath := range ni.InstallPaths() {
+			id := fmt.Sprintf("id%d", i)
+			i++
+			ids[installPath] = id
+			fmt.Fprintf(ctx.stdout, "    <li id=\"%s\"><strong>%s</strong>\n      <ul>\n", id, html.EscapeString(ctx.strip(installPath)))
+			for _, h := range ni.InstallHashes(installPath) {
+				libs := ni.InstallHashLibs(installPath, h)
+				fmt.Fprintf(ctx.stdout, "        <li><a href=\"#%s\">%s</a>\n", h.String(), html.EscapeString(strings.Join(libs, ", ")))
+			}
+			fmt.Fprintln(ctx.stdout, "      </ul>")
+		}
+		fmt.Fprintln(ctx.stdout, "  </ul><!-- toc -->")
+	}
+	for h := range ni.Hashes() {
+		fmt.Fprintln(ctx.stdout, "  <hr>")
+		for _, libName := range ni.HashLibs(h) {
+			fmt.Fprintf(ctx.stdout, "  <strong>%s</strong> used by:\n    <ul class=\"file-list\">\n", html.EscapeString(libName))
+			for _, installPath := range ni.HashLibInstalls(h, libName) {
+				if id, ok := ids[installPath]; ok {
+					fmt.Fprintf(ctx.stdout, "      <li><a href=\"#%s\">%s</a>\n", id, html.EscapeString(ctx.strip(installPath)))
+				} else {
+					fmt.Fprintf(ctx.stdout, "      <li>%s\n", html.EscapeString(ctx.strip(installPath)))
+				}
+			}
+			fmt.Fprintf(ctx.stdout, "    </ul>\n")
+		}
+		fmt.Fprintf(ctx.stdout, "  </ul>\n  <a id=\"%s\"/><pre class=\"license-text\">", h.String())
+		fmt.Fprintln(ctx.stdout, html.EscapeString(string(ni.HashText(h))))
+		fmt.Fprintln(ctx.stdout, "  </pre><!-- license-text -->")
+	}
+	fmt.Fprintln(ctx.stdout, "</body></html>")
+
+	*ctx.deps = ni.InputNoticeFiles()
+
+	return nil
+}
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
new file mode 100644
index 0000000..b927018
--- /dev/null
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
@@ -0,0 +1,919 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"html"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+var (
+	horizontalRule = regexp.MustCompile(`^\s*<hr>\s*$`)
+	bodyTag        = regexp.MustCompile(`^\s*<body>\s*$`)
+	boilerPlate    = regexp.MustCompile(`^\s*(?:<ul class="file-list">|<ul>|</.*)\s*$`)
+	tocTag         = regexp.MustCompile(`^\s*<ul class="toc">\s*$`)
+	libraryName    = regexp.MustCompile(`^\s*<strong>(.*)</strong>\s\s*used\s\s*by\s*:\s*$`)
+	licenseText    = regexp.MustCompile(`^\s*<a id="[^"]{32}"/><pre class="license-text">(.*)$`)
+	titleTag       = regexp.MustCompile(`^\s*<title>(.*)</title>\s*$`)
+	h1Tag          = regexp.MustCompile(`^\s*<h1>(.*)</h1>\s*$`)
+	usedByTarget   = regexp.MustCompile(`^\s*<li>(?:<a href="#id[0-9]+">)?((?:out/(?:[^/<]*/)+)[^/<]*)(?:</a>)?\s*$`)
+	installTarget  = regexp.MustCompile(`^\s*<li id="id[0-9]+"><strong>(.*)</strong>\s*$`)
+	libReference   = regexp.MustCompile(`^\s*<li><a href="#[^"]{32}">(.*)</a>\s*$`)
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition    string
+		name         string
+		outDir       string
+		roots        []string
+		includeTOC   bool
+		stripPrefix  string
+		title        string
+		expectedOut  []matcher
+		expectedDeps []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/liba.so"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition:  "firstparty",
+			name:       "apex+toc",
+			roots:      []string{"highest.apex.meta_lic"},
+			includeTOC: true,
+			expectedOut: []matcher{
+				toc{},
+				target{"highest.apex"},
+				uses{"Android"},
+				target{"highest.apex/bin/bin1"},
+				uses{"Android"},
+				target{"highest.apex/bin/bin2"},
+				uses{"Android"},
+				target{"highest.apex/lib/liba.so"},
+				uses{"Android"},
+				target{"highest.apex/lib/libb.so"},
+				uses{"Android"},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/liba.so"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex-with-title",
+			roots:     []string{"highest.apex.meta_lic"},
+			title:     "Emperor",
+			expectedOut: []matcher{
+				pageTitle{"Emperor"},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/liba.so"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition:  "firstparty",
+			name:       "apex-with-title+toc",
+			roots:      []string{"highest.apex.meta_lic"},
+			includeTOC: true,
+			title:      "Emperor",
+			expectedOut: []matcher{
+				pageTitle{"Emperor"},
+				toc{},
+				target{"highest.apex"},
+				uses{"Android"},
+				target{"highest.apex/bin/bin1"},
+				uses{"Android"},
+				target{"highest.apex/bin/bin2"},
+				uses{"Android"},
+				target{"highest.apex/lib/liba.so"},
+				uses{"Android"},
+				target{"highest.apex/lib/libb.so"},
+				uses{"Android"},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/liba.so"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/liba.so"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"lib/libd.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				restricted{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				restricted{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				restricted{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			var deps []string
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
+
+			err := htmlNotice(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("htmlnotice: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("htmlnotice: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			inBody := false
+			hasTitle := false
+			ttle, expectTitle := tt.expectedOut[0].(pageTitle)
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if !inBody {
+					if expectTitle {
+						if tl := checkTitle(line); len(tl) > 0 {
+							if tl != ttle.t {
+								t.Errorf("htmlnotice: unexpected title: got %q, want %q", tl, ttle.t)
+							}
+							hasTitle = true
+						}
+					}
+					if bodyTag.MatchString(line) {
+						inBody = true
+						if expectTitle && !hasTitle {
+							t.Errorf("htmlnotice: missing title: got no <title> tag, want <title>%s</title>", ttle.t)
+						}
+					}
+					continue
+				}
+				if boilerPlate.MatchString(line) {
+					continue
+				}
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("htmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if !tt.expectedOut[lineno].isMatch(line) {
+					t.Errorf("htmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+				}
+				lineno++
+			}
+			if !inBody {
+				t.Errorf("htmlnotice: missing body: got no <body> tag, want <body> tag followed by %s", matcherList(tt.expectedOut).String())
+				return
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("htmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+			}
+
+			t.Logf("got deps: %q", deps)
+
+			t.Logf("want deps: %q", tt.expectedDeps)
+
+			if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+				t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+					strings.Join(w, "\n"), strings.Join(g, "\n"))
+			}
+		})
+	}
+}
+
+func checkTitle(line string) string {
+	groups := titleTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return ""
+	}
+	return groups[1]
+}
+
+type matcher interface {
+	isMatch(line string) bool
+	String() string
+}
+
+type pageTitle struct {
+	t string
+}
+
+func (m pageTitle) isMatch(line string) bool {
+	groups := h1Tag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == html.EscapeString(m.t)
+}
+
+func (m pageTitle) String() string {
+	return "  <h1>" + html.EscapeString(m.t) + "</h1>"
+}
+
+type toc struct{}
+
+func (m toc) isMatch(line string) bool {
+	return tocTag.MatchString(line)
+}
+
+func (m toc) String() string {
+	return `  <ul class="toc">`
+}
+
+type target struct {
+	name string
+}
+
+func (m target) isMatch(line string) bool {
+	groups := installTarget.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
+}
+
+func (m target) String() string {
+	return `  <li id="id#"><strong>` + html.EscapeString(m.name) + `</strong>`
+}
+
+type uses struct {
+	name string
+}
+
+func (m uses) isMatch(line string) bool {
+	groups := libReference.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == html.EscapeString(m.name)
+}
+
+func (m uses) String() string {
+	return `  <li><a href="#hash">` + html.EscapeString(m.name) + `</a>`
+}
+
+type hr struct{}
+
+func (m hr) isMatch(line string) bool {
+	return horizontalRule.MatchString(line)
+}
+
+func (m hr) String() string {
+	return "  <hr>"
+}
+
+type library struct {
+	name string
+}
+
+func (m library) isMatch(line string) bool {
+	groups := libraryName.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == html.EscapeString(m.name)
+}
+
+func (m library) String() string {
+	return "  <strong>" + html.EscapeString(m.name) + "</strong> used by:"
+}
+
+type usedBy struct {
+	name string
+}
+
+func (m usedBy) isMatch(line string) bool {
+	groups := usedByTarget.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
+}
+
+func (m usedBy) String() string {
+	return "  <li>out/.../" + html.EscapeString(m.name)
+}
+
+func matchesText(line, text string) bool {
+	groups := licenseText.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == html.EscapeString(text)
+}
+
+func expectedText(text string) string {
+	return `  <a href="#hash"/><pre class="license-text">` + html.EscapeString(text)
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+	return matchesText(line, "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+	return expectedText("&&&First Party License&&&")
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+	return matchesText(line, "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+	return expectedText("%%%Notice License%%%")
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+	return matchesText(line, "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+	return expectedText("$$$Reciprocal License$$$")
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+	return matchesText(line, "###Restricted License###")
+}
+
+func (m restricted) String() string {
+	return expectedText("###Restricted License###")
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+	return matchesText(line, "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+	return expectedText("@@@Proprietary License@@@")
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+	var sb strings.Builder
+	for _, m := range l {
+		s := m.String()
+		if s[:3] == s[len(s)-3:] {
+			fmt.Fprintln(&sb)
+		}
+		fmt.Fprintf(&sb, "%s\n", s)
+		if s[:3] == s[len(s)-3:] {
+			fmt.Fprintln(&sb)
+		}
+	}
+	return sb.String()
+}
diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go
new file mode 100644
index 0000000..31bd1b2
--- /dev/null
+++ b/tools/compliance/cmd/listshare/listshare.go
@@ -0,0 +1,182 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...}
+
+Outputs a csv file with 1 project per line in the first field followed
+by target:condition pairs describing why the project must be shared.
+
+Each target is the path to a generated license metadata file for a
+Soong module or Make target, and the license condition is either
+restricted (e.g. GPL) or reciprocal (e.g. MPL).
+`, filepath.Base(os.Args[0]))
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the list of projects to share. (default stdout)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	err := listShare(ofile, os.Stderr, compliance.FS, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// listShare implements the listshare utility.
+func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %v\n", files, os.Getenv("PWD"), err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// shareSource contains all source-sharing resolutions.
+	shareSource := compliance.ResolveSourceSharing(licenseGraph)
+
+	// Group the resolutions by project.
+	presolution := make(map[string]compliance.LicenseConditionSet)
+	for _, target := range shareSource.AttachesTo() {
+		rl := shareSource.Resolutions(target)
+		sort.Sort(rl)
+		for _, r := range rl {
+			for _, p := range r.ActsOn().Projects() {
+				if _, ok := presolution[p]; !ok {
+					presolution[p] = r.Resolves()
+					continue
+				}
+				presolution[p] = presolution[p].Union(r.Resolves())
+			}
+		}
+	}
+
+	// Sort the projects for repeatability/stability.
+	projects := make([]string, 0, len(presolution))
+	for p := range presolution {
+		projects = append(projects, p)
+	}
+	sort.Strings(projects)
+
+	// Output the sorted projects and the source-sharing license conditions that each project resolves.
+	for _, p := range projects {
+		if presolution[p].IsEmpty() {
+			fmt.Fprintf(stdout, "%s\n", p)
+		} else {
+			fmt.Fprintf(stdout, "%s,%s\n", p, strings.Join(presolution[p].Names(), ","))
+		}
+	}
+
+	return nil
+}
diff --git a/tools/compliance/cmd/listshare/listshare_test.go b/tools/compliance/cmd/listshare/listshare_test.go
new file mode 100644
index 0000000..c1e38be
--- /dev/null
+++ b/tools/compliance/cmd/listshare/listshare_test.go
@@ -0,0 +1,509 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	type projectShare struct {
+		project    string
+		conditions []string
+	}
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		expectedOut []projectShare
+	}{
+		{
+			condition:   "firstparty",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"reciprocal"},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"reciprocal"},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"reciprocal"},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"reciprocal",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "device/library",
+					conditions: []string{"restricted_allows_dynamic_linking"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"restricted"},
+				},
+				{
+					project: "highest/apex",
+					conditions: []string{
+						"restricted",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"restricted"},
+				},
+				{
+					project: "container/zip",
+					conditions: []string{
+						"restricted",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project:    "device/library",
+					conditions: []string{"restricted_allows_dynamic_linking"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"restricted"},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"restricted",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "distributable/application",
+					conditions: []string{
+						"restricted",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"restricted_allows_dynamic_linking",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"reciprocal",
+						"restricted_allows_dynamic_linking",
+					},
+				},
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "highest/apex",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "distributable/application",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "regressgpl1",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl1",
+			name:      "containerplus",
+			roots:     []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl2",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/gpl",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+		{
+			condition: "regressgpl2",
+			name:      "containerplus",
+			roots:     []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "bin/threelibraries",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/apache",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/c++",
+					conditions: []string{"restricted"},
+				},
+				{
+					project:    "lib/gpl",
+					conditions: []string{"restricted"},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, p := range tt.expectedOut {
+				expectedOut.WriteString(p.project)
+				for _, lc := range p.conditions {
+					expectedOut.WriteString(",")
+					expectedOut.WriteString(lc)
+				}
+				expectedOut.WriteString("\n")
+			}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			err := listShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			if err != nil {
+				t.Fatalf("listshare: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("listshare: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
new file mode 100644
index 0000000..667cdce
--- /dev/null
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -0,0 +1,259 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoSources     = fmt.Errorf("\nNo projects or metadata files to trace back from")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	sources     []string
+	stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a space-separated Target ActsOn Origin Condition tuple for each
+resolution in the graph. When -dot flag given, outputs nodes and edges
+in graphviz directed graph format.
+
+If one or more '-c condition' conditions are given, outputs the
+resolution for the union of the conditions. Otherwise, outputs the
+resolution for all conditions.
+
+In plain text mode, when '-label_conditions' is requested, the Target
+and Origin have colon-separated license conditions appended:
+i.e. target:condition1:condition2 etc.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
+	sources := newMultiString(flags, "rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*sources) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n")
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	ctx := &context{
+		sources:     *sources,
+		stripPrefix: *stripPrefix,
+	}
+	_, err := traceRestricted(ctx, ofile, os.Stderr, compliance.FS, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// traceRestricted implements the rtrace utility.
+func traceRestricted(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
+	if len(files) < 1 {
+		return nil, failNoneRequested
+	}
+
+	if len(ctx.sources) < 1 {
+		return nil, failNoSources
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return nil, failNoLicenses
+	}
+
+	sourceMap := make(map[string]struct{})
+	for _, source := range ctx.sources {
+		sourceMap[source] = struct{}{}
+	}
+
+	compliance.TraceTopDownConditions(licenseGraph, func(tn *compliance.TargetNode) compliance.LicenseConditionSet {
+		if _, isPresent := sourceMap[tn.Name()]; isPresent {
+			return compliance.ImpliesRestricted
+		}
+		for _, project := range tn.Projects() {
+			if _, isPresent := sourceMap[project]; isPresent {
+				return compliance.ImpliesRestricted
+			}
+		}
+		return compliance.NewLicenseConditionSet()
+	})
+
+	// targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
+	targetOut := func(target *compliance.TargetNode, sep string) string {
+		tOut := ctx.strip(target.Name())
+		return tOut
+	}
+
+	// outputResolution prints a resolution in the requested format to `stdout`, where one can read
+	// a resolution as `tname` resolves conditions named in `cnames`.
+	// `tname` is the name of the target the resolution traces back to.
+	// `cnames` is the list of conditions to resolve.
+	outputResolution := func(tname string, cnames []string) {
+		// ... one edge per line with names in a colon-separated tuple.
+		fmt.Fprintf(stdout, "%s %s\n", tname, strings.Join(cnames, ":"))
+	}
+
+	// Sort the resolutions by targetname for repeatability/stability.
+	actions := compliance.WalkResolutionsForCondition(licenseGraph, compliance.ImpliesShared).AllActions()
+	targets := make(compliance.TargetNodeList, 0, len(actions))
+	for tn := range actions {
+		if tn.LicenseConditions().MatchesAnySet(compliance.ImpliesRestricted) {
+			targets = append(targets, tn)
+		}
+	}
+	sort.Sort(targets)
+
+	// Output the sorted targets.
+	for _, target := range targets {
+		var tname string
+		tname = targetOut(target, ":")
+
+		// cnames accumulates the list of condition names originating at a single origin that apply to `target`.
+		cnames := target.LicenseConditions().Names()
+
+		// Output 1 line for each attachesTo+actsOn combination.
+		outputResolution(tname, cnames)
+	}
+	fmt.Fprintf(stdout, "restricted conditions trace to %d targets\n", len(targets))
+	if 0 == len(targets) {
+		fmt.Fprintln(stdout, "  (check for typos in project names or metadata files)")
+	}
+	return licenseGraph, nil
+}
diff --git a/tools/compliance/cmd/rtrace/rtrace_test.go b/tools/compliance/cmd/rtrace/rtrace_test.go
new file mode 100644
index 0000000..cbe9461
--- /dev/null
+++ b/tools/compliance/cmd/rtrace/rtrace_test.go
@@ -0,0 +1,319 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test_plaintext(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		ctx         context
+		expectedOut []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/firstparty/bin/bin1.meta_lic"},
+				stripPrefix: []string{"testdata/firstparty/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/notice/bin/bin1.meta_lic"},
+				stripPrefix: []string{"testdata/notice/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/reciprocal/bin/bin1.meta_lic"},
+				stripPrefix: []string{"testdata/reciprocal/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_bin1",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/restricted/bin/bin1.meta_lic"},
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{"lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_bin2",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/restricted/bin/bin2.meta_lic"},
+				stripPrefix: []string{"testdata/restricted/"},
+			},
+			expectedOut: []string{"lib/libb.so.meta_lic restricted"},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+				"testdata/restricted/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"testdata/proprietary/lib/libb.so.meta_lic restricted"},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_bin1",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/proprietary/bin/bin1.meta_lic"},
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_bin2",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				sources:     []string{"testdata/proprietary/bin/bin2.meta_lic"},
+				stripPrefix: []string{"testdata/proprietary/"},
+			},
+			expectedOut: []string{"lib/libb.so.meta_lic restricted"},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{"testdata/proprietary/lib/libb.so.meta_lic restricted"},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				expectedOut.WriteString(eo)
+				expectedOut.WriteString("\n")
+			}
+			fmt.Fprintf(expectedOut, "restricted conditions trace to %d targets\n", len(tt.expectedOut))
+			if 0 == len(tt.expectedOut) {
+				fmt.Fprintln(expectedOut, "  (check for typos in project names or metadata files)")
+			}
+
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+			if len(tt.ctx.sources) < 1 {
+				tt.ctx.sources = rootFiles
+			}
+			_, err := traceRestricted(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
+			t.Logf("rtrace: stderr = %v", stderr)
+			t.Logf("rtrace: stdout = %v", stdout)
+			if err != nil {
+				t.Fatalf("rtrace: error = %v", err)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("rtrace: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for startLine < len(outList) && startLine < len(expectedList) && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("rtrace: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs.go b/tools/compliance/cmd/shippedlibs/shippedlibs.go
new file mode 100644
index 0000000..add6dd6
--- /dev/null
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs.go
@@ -0,0 +1,165 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout io.Writer
+	stderr io.Writer
+	rootFS fs.FS
+}
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	outputFile := flags.String("o", "-", "Where to write the library list. (default stdout)")
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a list of libraries used in the shipped images.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	err := flags.Parse(expandedArgs)
+	if err != nil {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+	}
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	if *outputFile != "-" {
+		ofile = &bytes.Buffer{}
+	}
+
+	ctx := &context{ofile, os.Stderr, compliance.FS}
+
+	err = shippedLibs(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// shippedLibs implements the shippedlibs utility.
+func shippedLibs(ctx *context, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// rs contains all notice resolutions.
+	rs := compliance.ResolveNotices(licenseGraph)
+
+	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+	if err != nil {
+		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	for lib := range ni.Libraries() {
+		fmt.Fprintln(ctx.stdout, lib)
+	}
+	return nil
+}
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
new file mode 100644
index 0000000..983747c
--- /dev/null
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
@@ -0,0 +1,241 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		outDir      string
+		roots       []string
+		expectedOut []string
+	}{
+		{
+			condition:   "firstparty",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"Android"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []string{"Android"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"Android"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"Android"},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"Android"},
+		},
+		{
+			condition:   "notice",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "notice",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "notice",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"Android", "Device"},
+		},
+		{
+			condition:   "notice",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"External"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"Android", "Device"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"External"},
+		},
+		{
+			condition:   "restricted",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "restricted",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "restricted",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"Android", "Device"},
+		},
+		{
+			condition:   "restricted",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"External"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []string{"Android", "Device"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{"Android", "Device", "External"},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{"External"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir)}
+
+			err := shippedLibs(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("shippedLibs: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("shippedLibs: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", strings.Join(tt.expectedOut, "\n"))
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("shippedLibs: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if tt.expectedOut[lineno] != line {
+					t.Errorf("shippedLibs: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno])
+				}
+				lineno++
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("shippedLibs: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno])
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/testdata/README.md b/tools/compliance/cmd/testdata/README.md
new file mode 100644
index 0000000..07564c2
--- /dev/null
+++ b/tools/compliance/cmd/testdata/README.md
@@ -0,0 +1,326 @@
+## Test data
+
+Each non-regression directory under testdata/ defines a similar build graph.
+All have the same structure, but different versions of the graph have different
+license metadata.
+
+The regression* directories can have whatever structure is required for the
+specific test case.
+
+### Testdata build graph structure:
+
+The structure is meant to simulate some common scenarios:
+
+*   a `lib/` directory with some libraries
+*   a `bin/` directory with some executables
+*   one of the binaries, `bin3`, is a toolchain executable like a compiler
+*   an `application` built with the `bin3` compiler and linking a couple libraries
+*   a pure aggregation `continer.zip` that merely bundles files together, and
+*   an apex file (more like an apk file) with some binaries and libraries.
+
+The testdata starts with a `firstparty/` version containng only first-party
+licenses, and each subsequent directory introduces more restrictive conditions:
+
+*   `notice/` starts with `firstparty/` adds third-party notice conditions
+*   `reciprocal/` starts with `notice/` and adds some reciprocal conditions
+*   `restricted/` starts with `reciprocal/` and adds some restricted conditions
+*   `proprietary/` starts with `restricted/` and add some privacy conditions
+
+#### a `lib/` directory with some libraries
+
+```dot
+strict digraph {
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+}
+```
+
+#### a `bin/` directory with some executables
+
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic\ntoolchain"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	{rank=same; bin1 bin2 bin3}
+}
+
+#### an `application` built with the `bin3` compiler and linking a couple libraries
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="application.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	{rank=same; app}
+}
+```
+
+#### a pure aggregation `container.zip` that merely bundles files together
+
+```dot
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	container [label="container.zip.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	{rank=same; container}
+}
+```
+
+#### an apex file (more like an apk file) with some binaries and libraries
+
+```dot
+strict digraph {
+	rankdir=LR;
+	apex [label="highest.apex.meta_lic"];
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; apex}
+}
+```
+
+#### the whole build graph
+
+```dot
+strict digraph {
+	rankdir=LR;
+	apex [label="highest.apex.meta_lic"];
+	app [label="application.meta_lic"];
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	container [label="container.zip.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+
+### firstparty/ testdata starts with all first-party licensing
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="firstparty/application.meta_lic"];
+	bin1 [label="firstparty/bin/bin1.meta_lic"];
+	bin2 [label="firstparty/bin/bin2.meta_lic"];
+	bin3 [label="firstparty/bin/bin3.meta_lic"];
+	container [label="firstparty/container.zip.meta_lic"];
+	apex [label="firstparty/highest.apex.meta_lic"];
+	liba [label="firstparty/lib/liba.so.meta_lic"];
+	libb [label="firstparty/lib/libb.so.meta_lic"];
+	libc [label="firstparty/lib/libc.a.meta_lic"];
+	lib [label="firstparty/lib/libd.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### notice/ testdata introduces third-party notice conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="notice/application.meta_lic"];
+	bin1 [label="notice/bin/bin1.meta_lic"];
+	bin2 [label="notice/bin/bin2.meta_lic"];
+	bin3 [label="notice/bin/bin3.meta_lic\nnotice"];
+	container [label="notice/container.zip.meta_lic"];
+	apex [label="notice/highest.apex.meta_lic"];
+	liba [label="notice/lib/liba.so.meta_lic\nnotice"];
+	libb [label="notice/lib/libb.so.meta_lic"];
+	libc [label="notice/lib/libc.a.meta_lic\nnotice"];
+	libd [label="notice/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### reciprocal/ testdata introduces third-party reciprocal sharing conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="reciprocal/application.meta_lic"];
+	bin1 [label="reciprocal/bin/bin1.meta_lic"];
+	bin2 [label="reciprocal/bin/bin2.meta_lic"];
+	bin3 [label="reciprocal/bin/bin3.meta_lic\nnotice"];
+	container [label="reciprocal/container.zip.meta_lic"];
+	apex [label="reciprocal/highest.apex.meta_lic"];
+	liba [label="reciprocal/lib/liba.so.meta_lic\nreciprocal"];
+	libb [label="reciprocal/lib/libb.so.meta_lic"];
+	libc [label="reciprocal/lib/libc.a.meta_lic\nreciprocal"];
+	libd [label="reciprocal/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### restricted/ testdata introduces restricted source sharing conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="restricted/application.meta_lic"];
+	bin1 [label="restricted/bin/bin1.meta_lic"];
+	bin2 [label="restricted/bin/bin2.meta_lic"];
+	bin3 [label="restricted/bin/bin3.meta_lic\nrestricted"];
+	container [label="restricted/container.zip.meta_lic"];
+	apex [label="restricted/highest.apex.meta_lic"];
+	liba [label="restricted/lib/liba.so.meta_lic\nrestricted"];
+	libb [label="restricted/lib/libb.so.meta_lic\nrestricted"];
+	libc [label="restricted/lib/libc.a.meta_lic\nreciprocal"];
+	libd [label="restricted/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### proprietary/ testdata introduces privacy conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="proprietary/application.meta_lic"];
+	bin1 [label="proprietary/bin/bin1.meta_lic"];
+	bin2 [label="proprietary/bin/bin2.meta_lic\nby_exception_only\nproprietary"];
+	bin3 [label="proprietary/bin/bin3.meta_lic\nrestricted"];
+	container [label="proprietary/container.zip.meta_lic"];
+	apex [label="proprietary/highest.apex.meta_lic"];
+	liba [label="proprietary/lib/liba.so.meta_lic\nby_exception_only\nproprietary"];
+	libb [label="proprietary/lib/libb.so.meta_lic\nrestricted"];
+	libc [label="proprietary/lib/libc.a.meta_lic\nby_exception_only\nproprietary"];
+	libd [label="proprietary/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
diff --git a/tools/compliance/cmd/testdata/firstparty/FIRST_PARTY_LICENSE b/tools/compliance/cmd/testdata/firstparty/FIRST_PARTY_LICENSE
new file mode 100644
index 0000000..a7e7e64
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/FIRST_PARTY_LICENSE
@@ -0,0 +1 @@
+&&&First Party License&&&
diff --git a/tools/compliance/cmd/testdata/firstparty/application.meta_lic b/tools/compliance/cmd/testdata/firstparty/application.meta_lic
new file mode 100644
index 0000000..ac3338f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/firstparty/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic
new file mode 100644
index 0000000..3007129
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic
new file mode 100644
index 0000000..89bc6a4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic
new file mode 100644
index 0000000..a81c764
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic b/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic
new file mode 100644
index 0000000..9f6a679
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/firstparty/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic b/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic
new file mode 100644
index 0000000..abad5f1
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "/lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "/lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "/bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "/bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/firstparty/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic
new file mode 100644
index 0000000..2985719
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic
new file mode 100644
index 0000000..e60ef73
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic
new file mode 100644
index 0000000..24d3f0d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "Android"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic
new file mode 100644
index 0000000..f7e537c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Android"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/notice/NOTICE_LICENSE b/tools/compliance/cmd/testdata/notice/NOTICE_LICENSE
new file mode 100644
index 0000000..752b249
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/NOTICE_LICENSE
@@ -0,0 +1 @@
+%%%Notice License%%%
diff --git a/tools/compliance/cmd/testdata/notice/application.meta_lic b/tools/compliance/cmd/testdata/notice/application.meta_lic
new file mode 100644
index 0000000..8ce0a98
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/notice/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic
new file mode 100644
index 0000000..6d173a4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic
new file mode 100644
index 0000000..a9e9c71
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/notice/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic
new file mode 100644
index 0000000..bb9a3d5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/notice/container.zip.meta_lic b/tools/compliance/cmd/testdata/notice/container.zip.meta_lic
new file mode 100644
index 0000000..e9c0511
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/notice/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic b/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic
new file mode 100644
index 0000000..2abb76e
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "/lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "/lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "/bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "/bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/notice/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic
new file mode 100644
index 0000000..7fed5d7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic
new file mode 100644
index 0000000..e60ef73
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic
new file mode 100644
index 0000000..8dbc41b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic
new file mode 100644
index 0000000..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/PROPRIETARY_LICENSE b/tools/compliance/cmd/testdata/proprietary/PROPRIETARY_LICENSE
new file mode 100644
index 0000000..5d0eb09
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/PROPRIETARY_LICENSE
@@ -0,0 +1 @@
+@@@Proprietary License@@@
diff --git a/tools/compliance/cmd/testdata/proprietary/application.meta_lic b/tools/compliance/cmd/testdata/proprietary/application.meta_lic
new file mode 100644
index 0000000..f307c5c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/proprietary/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic
new file mode 100644
index 0000000..e0394da
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic
new file mode 100644
index 0000000..da64aa6
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic
@@ -0,0 +1,20 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+license_texts:  "testdata/proprietary/PROPRIETARY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
new file mode 100644
index 0000000..7ef14e9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic b/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic
new file mode 100644
index 0000000..d6605f4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/proprietary/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic b/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic
new file mode 100644
index 0000000..27ced10
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "/lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "/lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "/bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "/bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/proprietary/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic
new file mode 100644
index 0000000..ceb0f9f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic
@@ -0,0 +1,10 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+license_texts:  "testdata/proprietary/PROPRIETARY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
new file mode 100644
index 0000000..739d357
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
new file mode 100644
index 0000000..5440ea7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+license_texts:  "testdata/proprietary/PROPRIETARY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic
new file mode 100644
index 0000000..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/RECIPROCAL_LICENSE b/tools/compliance/cmd/testdata/reciprocal/RECIPROCAL_LICENSE
new file mode 100644
index 0000000..82c2019
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/RECIPROCAL_LICENSE
@@ -0,0 +1 @@
+$$$Reciprocal License$$$
diff --git a/tools/compliance/cmd/testdata/reciprocal/application.meta_lic b/tools/compliance/cmd/testdata/reciprocal/application.meta_lic
new file mode 100644
index 0000000..60233cb
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic
new file mode 100644
index 0000000..54d552f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic
new file mode 100644
index 0000000..a28cb91
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic
new file mode 100644
index 0000000..bb9a3d5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic b/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic
new file mode 100644
index 0000000..feb08fe
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic b/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic
new file mode 100644
index 0000000..185d04a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "/lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "/lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "/bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "/bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic
new file mode 100644
index 0000000..dd05155
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+license_texts:  "testdata/reciprocal/RECIPROCAL_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic
new file mode 100644
index 0000000..e60ef73
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic
new file mode 100644
index 0000000..f794305
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+license_texts:  "testdata/reciprocal/RECIPROCAL_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic
new file mode 100644
index 0000000..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl1/README.md b/tools/compliance/cmd/testdata/regressgpl1/README.md
new file mode 100644
index 0000000..6ab872d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/README.md
@@ -0,0 +1,32 @@
+## Shipped versus non-shipped libraries with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries only needs to be shared if shipped
+alongside the container with the binaries.
+
+```dot
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic\nnotice"];
+	bin2 [label="bin/bin2.meta_lic\nnotice"];
+	bin3 [label="bin/bin3.meta_lic\nnotice"];
+	container [label="container.zip.meta_lic\nnotice"];
+	libapache [label="lib/libapache.so.meta_lic\nnotice"];
+	libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+	libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+	container -> bin1[label="static"];
+	container -> bin2 [label="static"];
+	container -> bin3 [label="static"];
+	bin1 -> libcxx [label="dynamic"];
+	bin2 -> libapache [label="dynamic"];
+	bin2 -> libcxx [label="dynamic"];
+	bin3 -> libapache [label="dynamic"];
+	bin3 -> libcxx [label="dynamic"];
+	bin3 -> libgpl [label="dynamic"];
+	libapache -> libcxx [label="dynamic"];
+	libgpl -> libcxx [label="dynamic"];
+	{rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
new file mode 100644
index 0000000..4afd240
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/onelibrary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
new file mode 100644
index 0000000..4e56fa3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/twolibraries"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
new file mode 100644
index 0000000..16290d3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "bin/threelibraries"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libgpl.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl1/lib/libgpl.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
new file mode 100644
index 0000000..21b6d5a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
@@ -0,0 +1,32 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl1/bin/bin3.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..9184501
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+projects:  "lib/apache"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "lib/c++"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed:  "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..a3afa2b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name:  "External"
+projects:  "lib/gpl"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed:  "out/target/product/fictional/system/lib/libgpl.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl1/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/README.md b/tools/compliance/cmd/testdata/regressgpl2/README.md
new file mode 100644
index 0000000..9da0f4d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/README.md
@@ -0,0 +1,35 @@
+## Libraries shipped inside container with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries needs to be shared when shipped as
+part of the container with the binaries.
+
+```dot
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic\nnotice"];
+	bin2 [label="bin/bin2.meta_lic\nnotice"];
+	bin3 [label="bin/bin3.meta_lic\nnotice"];
+	container [label="container.zip.meta_lic\nnotice"];
+	libapache [label="lib/libapache.so.meta_lic\nnotice"];
+	libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+	libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+	container -> bin1[label="static"];
+	container -> bin2 [label="static"];
+	container -> bin3 [label="static"];
+	container -> libapache [label="static"];
+	container -> libcxx [label="static"];
+	container -> libgpl [label="static"];
+	bin1 -> libcxx [label="dynamic"];
+	bin2 -> libapache [label="dynamic"];
+	bin2 -> libcxx [label="dynamic"];
+	bin3 -> libapache [label="dynamic"];
+	bin3 -> libcxx [label="dynamic"];
+	bin3 -> libgpl [label="dynamic"];
+	libapache -> libcxx [label="dynamic"];
+	libgpl -> libcxx [label="dynamic"];
+	{rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
new file mode 100644
index 0000000..839fd9c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/onelibrary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
new file mode 100644
index 0000000..96baaae
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "bin/twolibraries"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
new file mode 100644
index 0000000..3cd8602
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "bin/threelibraries"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libgpl.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
new file mode 100644
index 0000000..d32bf94
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/bin/bin3"
+sources:  "out/target/product/fictional/system/lib/libapache.so"
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/bin/bin3.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libapache.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..ae47340
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name:  "Android"
+projects:  "lib/apache"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed:  "out/target/product/fictional/system/lib/libapache.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "lib/c++"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed:  "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..4e78697
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name:  "External"
+projects:  "lib/gpl"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed:  "out/target/product/fictional/system/lib/libgpl.so"
+sources:  "out/target/product/fictional/system/lib/libc++.so"
+deps:  {
+  file:  "testdata/regressgpl2/lib/libc++.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE b/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE
new file mode 100644
index 0000000..16a2819
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE
@@ -0,0 +1 @@
+###Restricted License###
diff --git a/tools/compliance/cmd/testdata/restricted/application.meta_lic b/tools/compliance/cmd/testdata/restricted/application.meta_lic
new file mode 100644
index 0000000..7ef536d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/restricted/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic
new file mode 100644
index 0000000..ef0d0c0
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic
new file mode 100644
index 0000000..331d5ac
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed:  "out/target/product/fictional/system/bin/bin2"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
new file mode 100644
index 0000000..7ef14e9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic b/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic
new file mode 100644
index 0000000..47e0e24
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  "/"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  "/"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/restricted/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic b/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic
new file mode 100644
index 0000000..3042309
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "/lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "/lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "/bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "/bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/restricted/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
new file mode 100644
index 0000000..a505d4a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
new file mode 100644
index 0000000..739d357
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
new file mode 100644
index 0000000..f794305
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+license_texts:  "testdata/reciprocal/RECIPROCAL_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic
new file mode 100644
index 0000000..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+license_texts:  "testdata/notice/NOTICE_LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/textnotice/textnotice.go b/tools/compliance/cmd/textnotice/textnotice.go
new file mode 100644
index 0000000..9beaf58
--- /dev/null
+++ b/tools/compliance/cmd/textnotice/textnotice.go
@@ -0,0 +1,236 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"compress/gzip"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+
+	"github.com/google/blueprint/deptools"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout      io.Writer
+	stderr      io.Writer
+	rootFS      fs.FS
+	product     string
+	stripPrefix []string
+	title       string
+	deps        *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				p = ctx.product
+			}
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a text NOTICE file.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
+	depsFile := flags.String("d", "", "Where to write the deps file")
+	product := flags.String("product", "", "The name of the product for which the notice is generated.")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+	title := flags.String("title", "", "The title of the notice file.")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	var closer io.Closer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+	if strings.HasSuffix(*outputFile, ".gz") {
+		ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+		closer = ofile.(io.Closer)
+	}
+
+	var deps []string
+
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
+
+	err := textNotice(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if closer != nil {
+		closer.Close()
+	}
+
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+	if *depsFile != "" {
+		err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// textNotice implements the textNotice utility.
+func textNotice(ctx *context, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// rs contains all notice resolutions.
+	rs := compliance.ResolveNotices(licenseGraph)
+
+	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+	if err != nil {
+		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	if len(ctx.title) > 0 {
+		fmt.Fprintf(ctx.stdout, "%s\n\n", ctx.title)
+	}
+	for h := range ni.Hashes() {
+		fmt.Fprintln(ctx.stdout, "==============================================================================")
+		for _, libName := range ni.HashLibs(h) {
+			fmt.Fprintf(ctx.stdout, "%s used by:\n", libName)
+			for _, installPath := range ni.HashLibInstalls(h, libName) {
+				fmt.Fprintf(ctx.stdout, "  %s\n", ctx.strip(installPath))
+			}
+			fmt.Fprintln(ctx.stdout)
+		}
+		ctx.stdout.Write(ni.HashText(h))
+		fmt.Fprintln(ctx.stdout)
+	}
+
+	*ctx.deps = ni.InputNoticeFiles()
+
+	return nil
+}
diff --git a/tools/compliance/cmd/textnotice/textnotice_test.go b/tools/compliance/cmd/textnotice/textnotice_test.go
new file mode 100644
index 0000000..e661a44
--- /dev/null
+++ b/tools/compliance/cmd/textnotice/textnotice_test.go
@@ -0,0 +1,719 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+var (
+	horizontalRule = regexp.MustCompile("^===[=]*===$")
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition    string
+		name         string
+		outDir       string
+		roots        []string
+		stripPrefix  string
+		expectedOut  []matcher
+		expectedDeps []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/liba.so"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/liba.so"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"lib/libd.so"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/bin/bin2"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/bin2"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				restricted{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				restricted{},
+				hr{},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				usedBy{"highest.apex/lib/libb.so"},
+				restricted{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex"},
+				usedBy{"highest.apex/bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"highest.apex/bin/bin2"},
+				library{"Device"},
+				usedBy{"highest.apex/bin/bin1"},
+				usedBy{"highest.apex/lib/liba.so"},
+				library{"External"},
+				usedBy{"highest.apex/bin/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				usedBy{"container.zip/libb.so"},
+				restricted{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip"},
+				usedBy{"container.zip/bin1"},
+				firstParty{},
+				hr{},
+				library{"Android"},
+				usedBy{"container.zip/bin2"},
+				library{"Device"},
+				usedBy{"container.zip/bin1"},
+				usedBy{"container.zip/liba.so"},
+				library{"External"},
+				usedBy{"container.zip/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"application"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"application"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"Android"},
+				usedBy{"bin/bin1"},
+				firstParty{},
+				hr{},
+				library{"Device"},
+				usedBy{"bin/bin1"},
+				library{"External"},
+				usedBy{"bin/bin1"},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				hr{},
+				library{"External"},
+				usedBy{"lib/libd.so"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			var deps []string
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
+
+			err := textNotice(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("textnotice: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("textnotice: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if !tt.expectedOut[lineno].isMatch(line) {
+					t.Errorf("unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+				}
+				lineno++
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("textnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+			}
+
+			t.Logf("got deps: %q", deps)
+
+			t.Logf("want deps: %q", tt.expectedDeps)
+
+			if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+				t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+					strings.Join(w, "\n"), strings.Join(g, "\n"))
+			}
+		})
+	}
+}
+
+type matcher interface {
+	isMatch(line string) bool
+	String() string
+}
+
+type hr struct{}
+
+func (m hr) isMatch(line string) bool {
+	return horizontalRule.MatchString(line)
+}
+
+func (m hr) String() string {
+	return " ================================================== "
+}
+
+type library struct {
+	name string
+}
+
+func (m library) isMatch(line string) bool {
+	return strings.HasPrefix(line, m.name+" ")
+}
+
+func (m library) String() string {
+	return m.name + " used by:"
+}
+
+type usedBy struct {
+	name string
+}
+
+func (m usedBy) isMatch(line string) bool {
+	return len(line) > 0 && line[0] == ' ' && strings.HasPrefix(strings.TrimLeft(line, " "), "out/") && strings.HasSuffix(line, "/"+m.name)
+}
+
+func (m usedBy) String() string {
+	return "  out/.../" + m.name
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+	return strings.HasPrefix(strings.TrimLeft(line, " "), "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+	return "&&&First Party License&&&"
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+	return strings.HasPrefix(strings.TrimLeft(line, " "), "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+	return "%%%Notice License%%%"
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+	return strings.HasPrefix(strings.TrimLeft(line, " "), "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+	return "$$$Reciprocal License$$$"
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+	return strings.HasPrefix(strings.TrimLeft(line, " "), "###Restricted License###")
+}
+
+func (m restricted) String() string {
+	return "###Restricted License###"
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+	return strings.HasPrefix(strings.TrimLeft(line, " "), "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+	return "@@@Proprietary License@@@"
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+	var sb strings.Builder
+	for _, m := range l {
+		s := m.String()
+		if s[:3] == s[len(s)-3:] {
+			fmt.Fprintln(&sb)
+		}
+		fmt.Fprintf(&sb, "%s\n", s)
+		if s[:3] == s[len(s)-3:] {
+			fmt.Fprintln(&sb)
+		}
+	}
+	return sb.String()
+}
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice.go b/tools/compliance/cmd/xmlnotice/xmlnotice.go
new file mode 100644
index 0000000..2097b7c
--- /dev/null
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice.go
@@ -0,0 +1,244 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/xml"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+
+	"github.com/google/blueprint/deptools"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout      io.Writer
+	stderr      io.Writer
+	rootFS      fs.FS
+	product     string
+	stripPrefix []string
+	title       string
+	deps        *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				p = ctx.product
+			}
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs an xml NOTICE.xml or gzipped NOTICE.xml.gz file if the -o filename ends
+with ".gz".
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the NOTICE xml or xml.gz file. (default stdout)")
+	depsFile := flags.String("d", "", "Where to write the deps file")
+	product := flags.String("product", "", "The name of the product for which the notice is generated.")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+	title := flags.String("title", "", "The title of the notice file.")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	var closer io.Closer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+	if strings.HasSuffix(*outputFile, ".gz") {
+		ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+		closer = ofile.(io.Closer)
+	}
+
+	var deps []string
+
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
+
+	err := xmlNotice(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	if closer != nil {
+		closer.Close()
+	}
+
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+	if *depsFile != "" {
+		err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+// xmlNotice implements the xmlnotice utility.
+func xmlNotice(ctx *context, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// rs contains all notice resolutions.
+	rs := compliance.ResolveNotices(licenseGraph)
+
+	ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+	if err != nil {
+		return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	fmt.Fprintln(ctx.stdout, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")
+	fmt.Fprintln(ctx.stdout, "<licenses>")
+
+	for installPath := range ni.InstallPaths() {
+		p := ctx.strip(installPath)
+		for _, h := range ni.InstallHashes(installPath) {
+			for _, lib := range ni.InstallHashLibs(installPath, h) {
+				fmt.Fprintf(ctx.stdout, "<file-name contentId=\"%s\" lib=\"", h.String())
+				xml.EscapeText(ctx.stdout, []byte(lib))
+				fmt.Fprintf(ctx.stdout, "\">")
+				xml.EscapeText(ctx.stdout, []byte(p))
+				fmt.Fprintln(ctx.stdout, "</file-name>")
+			}
+		}
+	}
+	for h := range ni.Hashes() {
+		fmt.Fprintf(ctx.stdout, "<file-content contentId=\"%s\"><![CDATA[", h)
+		xml.EscapeText(ctx.stdout, ni.HashText(h))
+		fmt.Fprintf(ctx.stdout, "]]></file-content>\n\n")
+	}
+	fmt.Fprintln(ctx.stdout, "</licenses>")
+
+	*ctx.deps = ni.InputNoticeFiles()
+
+	return nil
+}
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
new file mode 100644
index 0000000..731e783
--- /dev/null
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
@@ -0,0 +1,637 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/xml"
+	"fmt"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+
+	"android/soong/tools/compliance"
+)
+
+var (
+	installTarget = regexp.MustCompile(`^<file-name contentId="[^"]{32}" lib="([^"]*)">([^<]+)</file-name>`)
+	licenseText = regexp.MustCompile(`^<file-content contentId="[^"]{32}"><![[]CDATA[[]([^]]*)[]][]]></file-content>`)
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition    string
+		name         string
+		outDir       string
+		roots        []string
+		stripPrefix  string
+		expectedOut  []matcher
+		expectedDeps []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				target{"highest.apex", "Android"},
+				target{"highest.apex/bin/bin1", "Android"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/lib/liba.so", "Android"},
+				target{"highest.apex/lib/libb.so", "Android"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				target{"container.zip", "Android"},
+				target{"container.zip/bin1", "Android"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/liba.so", "Android"},
+				target{"container.zip/libb.so", "Android"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				target{"application", "Android"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				target{"bin/bin1", "Android"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				target{"lib/libd.so", "Android"},
+				firstParty{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				target{"highest.apex", "Android"},
+				target{"highest.apex/bin/bin1", "Android"},
+				target{"highest.apex/bin/bin1", "Device"},
+				target{"highest.apex/bin/bin1", "External"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/lib/liba.so", "Device"},
+				target{"highest.apex/lib/libb.so", "Android"},
+				firstParty{},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				target{"container.zip", "Android"},
+				target{"container.zip/bin1", "Android"},
+				target{"container.zip/bin1", "Device"},
+				target{"container.zip/bin1", "External"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/liba.so", "Device"},
+				target{"container.zip/libb.so", "Android"},
+				firstParty{},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				target{"application", "Android"},
+				target{"application", "Device"},
+				firstParty{},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				target{"bin/bin1", "Android"},
+				target{"bin/bin1", "Device"},
+				target{"bin/bin1", "External"},
+				firstParty{},
+				notice{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				target{"lib/libd.so", "External"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				target{"highest.apex", "Android"},
+				target{"highest.apex/bin/bin1", "Android"},
+				target{"highest.apex/bin/bin1", "Device"},
+				target{"highest.apex/bin/bin1", "External"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/lib/liba.so", "Device"},
+				target{"highest.apex/lib/libb.so", "Android"},
+				firstParty{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				target{"container.zip", "Android"},
+				target{"container.zip/bin1", "Android"},
+				target{"container.zip/bin1", "Device"},
+				target{"container.zip/bin1", "External"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/liba.so", "Device"},
+				target{"container.zip/libb.so", "Android"},
+				firstParty{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				target{"application", "Android"},
+				target{"application", "Device"},
+				firstParty{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				target{"bin/bin1", "Android"},
+				target{"bin/bin1", "Device"},
+				target{"bin/bin1", "External"},
+				firstParty{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				target{"lib/libd.so", "External"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				target{"highest.apex", "Android"},
+				target{"highest.apex/bin/bin1", "Android"},
+				target{"highest.apex/bin/bin1", "Device"},
+				target{"highest.apex/bin/bin1", "External"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/lib/liba.so", "Device"},
+				target{"highest.apex/lib/libb.so", "Android"},
+				firstParty{},
+				restricted{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				target{"container.zip", "Android"},
+				target{"container.zip/bin1", "Android"},
+				target{"container.zip/bin1", "Device"},
+				target{"container.zip/bin1", "External"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/liba.so", "Device"},
+				target{"container.zip/libb.so", "Android"},
+				firstParty{},
+				restricted{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				target{"application", "Android"},
+				target{"application", "Device"},
+				firstParty{},
+				restricted{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				target{"bin/bin1", "Android"},
+				target{"bin/bin1", "Device"},
+				target{"bin/bin1", "External"},
+				firstParty{},
+				restricted{},
+				reciprocal{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				target{"lib/libd.so", "External"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				target{"highest.apex", "Android"},
+				target{"highest.apex/bin/bin1", "Android"},
+				target{"highest.apex/bin/bin1", "Device"},
+				target{"highest.apex/bin/bin1", "External"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/bin/bin2", "Android"},
+				target{"highest.apex/lib/liba.so", "Device"},
+				target{"highest.apex/lib/libb.so", "Android"},
+				restricted{},
+				firstParty{},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				target{"container.zip", "Android"},
+				target{"container.zip/bin1", "Android"},
+				target{"container.zip/bin1", "Device"},
+				target{"container.zip/bin1", "External"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/bin2", "Android"},
+				target{"container.zip/liba.so", "Device"},
+				target{"container.zip/libb.so", "Android"},
+				restricted{},
+				firstParty{},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				target{"application", "Android"},
+				target{"application", "Device"},
+				firstParty{},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				target{"bin/bin1", "Android"},
+				target{"bin/bin1", "Device"},
+				target{"bin/bin1", "External"},
+				firstParty{},
+				proprietary{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				target{"lib/libd.so", "External"},
+				notice{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			var deps []string
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
+
+			err := xmlNotice(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("xmlnotice: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("xmlnotice: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			inBody := false
+			outOfBody := true
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if lineno == 0 && !inBody && `<?xml version="1.0" encoding="utf-8"?>` == line {
+					continue
+				}
+				if !inBody {
+					if "<licenses>" == line {
+						inBody = true
+						outOfBody = false
+					}
+					continue
+				} else if "</licenses>" == line {
+					outOfBody = true
+					continue
+				}
+
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("xmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if !tt.expectedOut[lineno].isMatch(line) {
+					t.Errorf("xmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+				}
+				lineno++
+			}
+			if !inBody {
+				t.Errorf("xmlnotice: missing <licenses> tag: got no <licenses> tag, want <licenses> tag on 2nd line")
+			}
+			if !outOfBody {
+				t.Errorf("xmlnotice: missing </licenses> tag: got no </licenses> tag, want </licenses> tag on last line")
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("xmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+			}
+
+			t.Logf("got deps: %q", deps)
+
+			t.Logf("want deps: %q", tt.expectedDeps)
+
+			if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+				t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+					strings.Join(w, "\n"), strings.Join(g, "\n"))
+			}
+		})
+	}
+}
+
+func escape(s string) string {
+	b := &bytes.Buffer{}
+	xml.EscapeText(b, []byte(s))
+	return b.String()
+}
+
+type matcher interface {
+	isMatch(line string) bool
+	String() string
+}
+
+type target struct {
+	name string
+	lib string
+}
+
+func (m target) isMatch(line string) bool {
+	groups := installTarget.FindStringSubmatch(line)
+	if len(groups) != 3 {
+		return false
+	}
+	return groups[1] == escape(m.lib) && strings.HasPrefix(groups[2], "out/") && strings.HasSuffix(groups[2], "/"+escape(m.name))
+}
+
+func (m target) String() string {
+	return `<file-name contentId="hash" lib="` + escape(m.lib) + `">` + escape(m.name) + `</file-name>`
+}
+
+func matchesText(line, text string) bool {
+	groups := licenseText.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == escape(text + "\n")
+}
+
+func expectedText(text string) string {
+	return `<file-content contentId="hash"><![CDATA[` + escape(text + "\n") + `]]></file-content>`
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+	return matchesText(line, "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+	return expectedText("&&&First Party License&&&")
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+	return matchesText(line, "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+	return expectedText("%%%Notice License%%%")
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+	return matchesText(line, "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+	return expectedText("$$$Reciprocal License$$$")
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+	return matchesText(line, "###Restricted License###")
+}
+
+func (m restricted) String() string {
+	return expectedText("###Restricted License###")
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+	return matchesText(line, "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+	return expectedText("@@@Proprietary License@@@")
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+	var sb strings.Builder
+	fmt.Fprintln(&sb, `<?xml version="1.0" encoding="utf-8"?>`)
+	fmt.Fprintln(&sb, `<licenses>`)
+	for _, m := range l {
+		s := m.String()
+		fmt.Fprintln(&sb, s)
+		if _, ok := m.(target); !ok {
+			fmt.Fprintln(&sb)
+		}
+	}
+	fmt.Fprintln(&sb, `/<licenses>`)
+	return sb.String()
+}
diff --git a/tools/compliance/condition.go b/tools/compliance/condition.go
new file mode 100644
index 0000000..cfe6f82
--- /dev/null
+++ b/tools/compliance/condition.go
@@ -0,0 +1,102 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+)
+
+// LicenseCondition identifies a recognized license condition by setting the
+// corresponding bit.
+type LicenseCondition uint16
+
+// LicenseConditionMask is a bitmask for the recognized license conditions.
+const LicenseConditionMask = LicenseCondition(0x3ff)
+
+const (
+	// UnencumberedCondition identifies public domain or public domain-
+	// like license that disclaims copyright.
+	UnencumberedCondition = LicenseCondition(0x0001)
+	// PermissiveCondition identifies a license without notice or other
+	// significant requirements.
+	PermissiveCondition = LicenseCondition(0x0002)
+	// NoticeCondition identifies a typical open-source license with only
+	// notice or attribution requirements.
+	NoticeCondition = LicenseCondition(0x0004)
+	// ReciprocalCondition identifies a license with requirement to share
+	// the module's source only.
+	ReciprocalCondition = LicenseCondition(0x0008)
+	// RestrictedCondition identifies a license with requirement to share
+	// all source code linked to the module's source.
+	RestrictedCondition = LicenseCondition(0x0010)
+	// RestrictedClasspathExceptionCondition identifies RestrictedCondition
+	// waived for dynamic linking from independent modules.
+	RestrictedClasspathExceptionCondition = LicenseCondition(0x0020)
+	// WeaklyRestrictedCondition identifies a RestrictedCondition waived
+	// for dynamic linking.
+	WeaklyRestrictedCondition = LicenseCondition(0x0040)
+	// ProprietaryCondition identifies a license with source privacy
+	// requirements.
+	ProprietaryCondition = LicenseCondition(0x0080)
+	// ByExceptionOnly identifies a license where policy requires product
+	// counsel review prior to use.
+	ByExceptionOnlyCondition = LicenseCondition(0x0100)
+	// NotAllowedCondition identifies a license with onerous conditions
+	// where policy prohibits use.
+	NotAllowedCondition = LicenseCondition(0x0200)
+)
+
+var (
+	// RecognizedConditionNames maps condition strings to LicenseCondition.
+	RecognizedConditionNames = map[string]LicenseCondition{
+		"unencumbered":                        UnencumberedCondition,
+		"permissive":                          PermissiveCondition,
+		"notice":                              NoticeCondition,
+		"reciprocal":                          ReciprocalCondition,
+		"restricted":                          RestrictedCondition,
+		"restricted_with_classpath_exception": RestrictedClasspathExceptionCondition,
+		"restricted_allows_dynamic_linking":   WeaklyRestrictedCondition,
+		"proprietary":                         ProprietaryCondition,
+		"by_exception_only":                   ByExceptionOnlyCondition,
+		"not_allowed":                         NotAllowedCondition,
+	}
+)
+
+// Name returns the condition string corresponding to the LicenseCondition.
+func (lc LicenseCondition) Name() string {
+	switch lc {
+	case UnencumberedCondition:
+		return "unencumbered"
+	case PermissiveCondition:
+		return "permissive"
+	case NoticeCondition:
+		return "notice"
+	case ReciprocalCondition:
+		return "reciprocal"
+	case RestrictedCondition:
+		return "restricted"
+	case RestrictedClasspathExceptionCondition:
+		return "restricted_with_classpath_exception"
+	case WeaklyRestrictedCondition:
+		return "restricted_allows_dynamic_linking"
+	case ProprietaryCondition:
+		return "proprietary"
+	case ByExceptionOnlyCondition:
+		return "by_exception_only"
+	case NotAllowedCondition:
+		return "not_allowed"
+	}
+	panic(fmt.Errorf("unrecognized license condition: %04x", lc))
+}
diff --git a/tools/compliance/condition_test.go b/tools/compliance/condition_test.go
new file mode 100644
index 0000000..778ce4a
--- /dev/null
+++ b/tools/compliance/condition_test.go
@@ -0,0 +1,67 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"testing"
+)
+
+func TestConditionSetHas(t *testing.T) {
+	impliesShare := ImpliesShared
+
+	t.Logf("testing with imliesShare=%04x", impliesShare)
+
+	if impliesShare.HasAny(NoticeCondition) {
+		t.Errorf("impliesShare.HasAny(\"notice\"=%04x) got true, want false", NoticeCondition)
+	}
+
+	if !impliesShare.HasAny(RestrictedCondition) {
+		t.Errorf("impliesShare.HasAny(\"restricted\"=%04x) got false, want true", RestrictedCondition)
+	}
+
+	if !impliesShare.HasAny(ReciprocalCondition) {
+		t.Errorf("impliesShare.HasAny(\"reciprocal\"=%04x) got false, want true", ReciprocalCondition)
+	}
+
+	if impliesShare.HasAny(LicenseCondition(0x0000)) {
+		t.Errorf("impliesShare.HasAny(nil=%04x) got true, want false", LicenseCondition(0x0000))
+	}
+}
+
+func TestConditionName(t *testing.T) {
+	for expected, condition := range RecognizedConditionNames {
+		actual := condition.Name()
+		if expected != actual {
+			t.Errorf("unexpected name for condition %04x: got %s, want %s", condition, actual, expected)
+		}
+	}
+}
+
+func TestConditionName_InvalidCondition(t *testing.T) {
+	panicked := false
+	var lc LicenseCondition
+	func() {
+		defer func() {
+			if err := recover(); err != nil {
+				panicked = true
+			}
+		}()
+		name := lc.Name()
+		t.Errorf("invalid condition unexpected name: got %s, wanted panic", name)
+	}()
+	if !panicked {
+		t.Errorf("no expected panic for %04x.Name(): got no panic, wanted panic", lc)
+	}
+}
diff --git a/tools/compliance/conditionset.go b/tools/compliance/conditionset.go
new file mode 100644
index 0000000..7a12ddc
--- /dev/null
+++ b/tools/compliance/conditionset.go
@@ -0,0 +1,189 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"strings"
+)
+
+// LicenseConditionSet identifies sets of license conditions.
+type LicenseConditionSet LicenseCondition
+
+// AllLicenseConditions is the set of all recognized license conditions.
+const AllLicenseConditions = LicenseConditionSet(LicenseConditionMask)
+
+// NewLicenseConditionSet returns a set containing exactly the elements of
+// `conditions`.
+func NewLicenseConditionSet(conditions ...LicenseCondition) LicenseConditionSet {
+	cs := LicenseConditionSet(0x00)
+	for _, lc := range conditions {
+		cs |= LicenseConditionSet(lc)
+	}
+	return cs
+}
+
+// Plus returns a new set containing all of the elements of `cs` and all of the
+// `conditions`.
+func (cs LicenseConditionSet) Plus(conditions ...LicenseCondition) LicenseConditionSet {
+	result := cs
+	for _, lc := range conditions {
+		result |= LicenseConditionSet(lc)
+	}
+	return result
+}
+
+// Union returns a new set containing all of the elements of `cs` and all of the
+// elements of the `other` sets.
+func (cs LicenseConditionSet) Union(other ...LicenseConditionSet) LicenseConditionSet {
+	result := cs
+	for _, ls := range other {
+		result |= ls
+	}
+	return result
+}
+
+// MatchingAny returns the subset of `cs` equal to any of the `conditions`.
+func (cs LicenseConditionSet) MatchingAny(conditions ...LicenseCondition) LicenseConditionSet {
+	result := LicenseConditionSet(0x00)
+	for _, lc := range conditions {
+		result |= cs & LicenseConditionSet(lc)
+	}
+	return result
+}
+
+// MatchingAnySet returns the subset of `cs` that are members of any of the
+// `other` sets.
+func (cs LicenseConditionSet) MatchingAnySet(other ...LicenseConditionSet) LicenseConditionSet {
+	result := LicenseConditionSet(0x00)
+	for _, ls := range other {
+		result |= cs & ls
+	}
+	return result
+}
+
+// HasAny returns true when `cs` contains at least one of the `conditions`.
+func (cs LicenseConditionSet) HasAny(conditions ...LicenseCondition) bool {
+	for _, lc := range conditions {
+		if 0x0000 != (cs & LicenseConditionSet(lc)) {
+			return true
+		}
+	}
+	return false
+}
+
+// MatchesAnySet returns true when `cs` has a non-empty intersection with at
+// least one of the `other` condition sets.
+func (cs LicenseConditionSet) MatchesAnySet(other ...LicenseConditionSet) bool {
+	for _, ls := range other {
+		if 0x0000 != (cs & ls) {
+			return true
+		}
+	}
+	return false
+}
+
+// HasAll returns true when `cs` contains every one of the `conditions`.
+func (cs LicenseConditionSet) HasAll(conditions ...LicenseCondition) bool {
+	for _, lc := range conditions {
+		if 0x0000 == (cs & LicenseConditionSet(lc)) {
+			return false
+		}
+	}
+	return true
+}
+
+// MatchesEverySet returns true when `cs` has a non-empty intersection with
+// each of the `other` condition sets.
+func (cs LicenseConditionSet) MatchesEverySet(other ...LicenseConditionSet) bool {
+	for _, ls := range other {
+		if 0x0000 == (cs & ls) {
+			return false
+		}
+	}
+	return true
+}
+
+// Intersection returns the subset of `cs` that are members of every `other`
+// set.
+func (cs LicenseConditionSet) Intersection(other ...LicenseConditionSet) LicenseConditionSet {
+	result := cs
+	for _, ls := range other {
+		result &= ls
+	}
+	return result
+}
+
+// Minus returns the subset of `cs` that are not equaal to any `conditions`.
+func (cs LicenseConditionSet) Minus(conditions ...LicenseCondition) LicenseConditionSet {
+	result := cs
+	for _, lc := range conditions {
+		result &^= LicenseConditionSet(lc)
+	}
+	return result
+}
+
+// Difference returns the subset of `cs` that are not members of any `other`
+// set.
+func (cs LicenseConditionSet) Difference(other ...LicenseConditionSet) LicenseConditionSet {
+	result := cs
+	for _, ls := range other {
+		result &^= ls
+	}
+	return result
+}
+
+// Len returns the number of license conditions in the set.
+func (cs LicenseConditionSet) Len() int {
+	size := 0
+	for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+		if 0x00 != (cs & lc) {
+			size++
+		}
+	}
+	return size
+}
+
+// AsList returns an array of the license conditions in the set.
+func (cs LicenseConditionSet) AsList() []LicenseCondition {
+	result := make([]LicenseCondition, 0, cs.Len())
+	for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+		if 0x00 != (cs & lc) {
+			result = append(result, LicenseCondition(lc))
+		}
+	}
+	return result
+}
+
+// Names returns an array of the names of the license conditions in the set.
+func (cs LicenseConditionSet) Names() []string {
+	result := make([]string, 0, cs.Len())
+	for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+		if 0x00 != (cs & lc) {
+			result = append(result, LicenseCondition(lc).Name())
+		}
+	}
+	return result
+}
+
+// IsEmpty returns true when the set contains no license conditions.
+func (cs LicenseConditionSet) IsEmpty() bool {
+	return 0x00 == (cs & AllLicenseConditions)
+}
+
+// String returns a human-readable string representation of the set.
+func (cs LicenseConditionSet) String() string {
+	return fmt.Sprintf("{%s}", strings.Join(cs.Names(), "|"))
+}
diff --git a/tools/compliance/conditionset_test.go b/tools/compliance/conditionset_test.go
new file mode 100644
index 0000000..c91912f
--- /dev/null
+++ b/tools/compliance/conditionset_test.go
@@ -0,0 +1,657 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestConditionSet(t *testing.T) {
+	tests := []struct {
+		name        string
+		conditions  []string
+		plus        *[]string
+		minus       *[]string
+		matchingAny map[string][]string
+		expected    []string
+	}{
+		{
+			name:       "empty",
+			conditions: []string{},
+			plus:       &[]string{},
+			matchingAny: map[string][]string{
+				"notice":                []string{},
+				"restricted":            []string{},
+				"restricted|reciprocal": []string{},
+			},
+			expected: []string{},
+		},
+		{
+			name:       "emptyminusnothing",
+			conditions: []string{},
+			minus:      &[]string{},
+			matchingAny: map[string][]string{
+				"notice":                []string{},
+				"restricted":            []string{},
+				"restricted|reciprocal": []string{},
+			},
+			expected: []string{},
+		},
+		{
+			name:       "emptyminusnotice",
+			conditions: []string{},
+			minus:      &[]string{"notice"},
+			matchingAny: map[string][]string{
+				"notice":                []string{},
+				"restricted":            []string{},
+				"restricted|reciprocal": []string{},
+			},
+			expected: []string{},
+		},
+		{
+			name:       "noticeonly",
+			conditions: []string{"notice"},
+			matchingAny: map[string][]string{
+				"notice":             []string{"notice"},
+				"notice|proprietary": []string{"notice"},
+				"restricted":         []string{},
+			},
+			expected: []string{"notice"},
+		},
+		{
+			name:       "allnoticeonly",
+			conditions: []string{"notice"},
+			plus:       &[]string{"notice"},
+			matchingAny: map[string][]string{
+				"notice":             []string{"notice"},
+				"notice|proprietary": []string{"notice"},
+				"restricted":         []string{},
+			},
+			expected: []string{"notice"},
+		},
+		{
+			name:       "emptyplusnotice",
+			conditions: []string{},
+			plus:       &[]string{"notice"},
+			matchingAny: map[string][]string{
+				"notice":             []string{"notice"},
+				"notice|proprietary": []string{"notice"},
+				"restricted":         []string{},
+			},
+			expected: []string{"notice"},
+		},
+		{
+			name:       "everything",
+			conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
+			plus:       &[]string{"restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
+			matchingAny: map[string][]string{
+				"unencumbered":                        []string{"unencumbered"},
+				"permissive":                          []string{"permissive"},
+				"notice":                              []string{"notice"},
+				"reciprocal":                          []string{"reciprocal"},
+				"restricted":                          []string{"restricted"},
+				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
+				"proprietary":                         []string{"proprietary"},
+				"by_exception_only":                   []string{"by_exception_only"},
+				"not_allowed":                         []string{"not_allowed"},
+				"notice|proprietary":                  []string{"notice", "proprietary"},
+			},
+			expected: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+		},
+		{
+			name: "everythingplusminusnothing",
+			conditions: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+			plus:  &[]string{},
+			minus: &[]string{},
+			matchingAny: map[string][]string{
+				"unencumbered|permissive|notice": []string{"unencumbered", "permissive", "notice"},
+				"restricted|reciprocal":          []string{"reciprocal", "restricted"},
+				"proprietary|by_exception_only":  []string{"proprietary", "by_exception_only"},
+				"not_allowed":                    []string{"not_allowed"},
+			},
+			expected: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+		},
+		{
+			name:       "allbutone",
+			conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
+			plus:       &[]string{"restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
+			matchingAny: map[string][]string{
+				"unencumbered":                        []string{"unencumbered"},
+				"permissive":                          []string{"permissive"},
+				"notice":                              []string{"notice"},
+				"reciprocal":                          []string{"reciprocal"},
+				"restricted":                          []string{"restricted"},
+				"restricted_with_classpath_exception": []string{},
+				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
+				"proprietary":                         []string{"proprietary"},
+				"by_exception_only":                   []string{"by_exception_only"},
+				"not_allowed":                         []string{"not_allowed"},
+				"notice|proprietary":                  []string{"notice", "proprietary"},
+			},
+			expected: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+		},
+		{
+			name: "everythingminusone",
+			conditions: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+			minus: &[]string{"restricted_allows_dynamic_linking"},
+			matchingAny: map[string][]string{
+				"unencumbered":                        []string{"unencumbered"},
+				"permissive":                          []string{"permissive"},
+				"notice":                              []string{"notice"},
+				"reciprocal":                          []string{"reciprocal"},
+				"restricted":                          []string{"restricted"},
+				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+				"restricted_allows_dynamic_linking":   []string{},
+				"proprietary":                         []string{"proprietary"},
+				"by_exception_only":                   []string{"by_exception_only"},
+				"not_allowed":                         []string{"not_allowed"},
+				"restricted|proprietary":              []string{"restricted", "proprietary"},
+			},
+			expected: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+		},
+		{
+			name: "everythingminuseverything",
+			conditions: []string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+			minus: &[]string{
+				"unencumbered",
+				"permissive",
+				"notice",
+				"reciprocal",
+				"restricted",
+				"restricted_with_classpath_exception",
+				"restricted_allows_dynamic_linking",
+				"proprietary",
+				"by_exception_only",
+				"not_allowed",
+			},
+			matchingAny: map[string][]string{
+				"unencumbered":                        []string{},
+				"permissive":                          []string{},
+				"notice":                              []string{},
+				"reciprocal":                          []string{},
+				"restricted":                          []string{},
+				"restricted_with_classpath_exception": []string{},
+				"restricted_allows_dynamic_linking":   []string{},
+				"proprietary":                         []string{},
+				"by_exception_only":                   []string{},
+				"not_allowed":                         []string{},
+				"restricted|proprietary":              []string{},
+			},
+			expected: []string{},
+		},
+		{
+			name:       "restrictedplus",
+			conditions: []string{"restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking"},
+			plus:       &[]string{"permissive", "notice", "restricted", "proprietary"},
+			matchingAny: map[string][]string{
+				"unencumbered":                        []string{},
+				"permissive":                          []string{"permissive"},
+				"notice":                              []string{"notice"},
+				"restricted":                          []string{"restricted"},
+				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
+				"proprietary":                         []string{"proprietary"},
+				"restricted|proprietary":              []string{"restricted", "proprietary"},
+				"by_exception_only":                   []string{},
+				"proprietary|by_exception_only":       []string{"proprietary"},
+			},
+			expected: []string{"permissive", "notice", "restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "proprietary"},
+		},
+	}
+	for _, tt := range tests {
+		toConditions := func(names []string) []LicenseCondition {
+			result := make([]LicenseCondition, 0, len(names))
+			for _, name := range names {
+				result = append(result, RecognizedConditionNames[name])
+			}
+			return result
+		}
+		populate := func() LicenseConditionSet {
+			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+			if tt.plus != nil {
+				testSet = testSet.Plus(toConditions(*tt.plus)...)
+			}
+			if tt.minus != nil {
+				testSet = testSet.Minus(toConditions(*tt.minus)...)
+			}
+			return testSet
+		}
+		populateSet := func() LicenseConditionSet {
+			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+			if tt.plus != nil {
+				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
+			}
+			if tt.minus != nil {
+				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
+			}
+			return testSet
+		}
+		populatePlusSet := func() LicenseConditionSet {
+			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+			if tt.plus != nil {
+				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
+			}
+			if tt.minus != nil {
+				testSet = testSet.Minus(toConditions(*tt.minus)...)
+			}
+			return testSet
+		}
+		populateMinusSet := func() LicenseConditionSet {
+			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+			if tt.plus != nil {
+				testSet = testSet.Plus(toConditions(*tt.plus)...)
+			}
+			if tt.minus != nil {
+				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
+			}
+			return testSet
+		}
+		checkMatching := func(cs LicenseConditionSet, t *testing.T) {
+			for data, expectedNames := range tt.matchingAny {
+				expectedConditions := toConditions(expectedNames)
+				expected := NewLicenseConditionSet(expectedConditions...)
+				actual := cs.MatchingAny(toConditions(strings.Split(data, "|"))...)
+				actualNames := actual.Names()
+
+				t.Logf("MatchingAny(%s): actual set %04x %s", data, actual, actual.String())
+				t.Logf("MatchingAny(%s): expected set %04x %s", data, expected, expected.String())
+
+				if actual != expected {
+					t.Errorf("MatchingAny(%s): got %04x, want %04x", data, actual, expected)
+					continue
+				}
+				if len(actualNames) != len(expectedNames) {
+					t.Errorf("len(MatchinAny(%s).Names()): got %d, want %d",
+						data, len(actualNames), len(expectedNames))
+				} else {
+					for i := 0; i < len(actualNames); i++ {
+						if actualNames[i] != expectedNames[i] {
+							t.Errorf("MatchingAny(%s).Names()[%d]: got %s, want %s",
+								data, i, actualNames[i], expectedNames[i])
+							break
+						}
+					}
+				}
+				actualConditions := actual.AsList()
+				if len(actualConditions) != len(expectedConditions) {
+					t.Errorf("len(MatchingAny(%s).AsList()):  got %d, want %d",
+						data, len(actualNames), len(expectedNames))
+				} else {
+					for i := 0; i < len(actualNames); i++ {
+						if actualNames[i] != expectedNames[i] {
+							t.Errorf("MatchingAny(%s).AsList()[%d]: got %s, want %s",
+								data, i, actualNames[i], expectedNames[i])
+							break
+						}
+					}
+				}
+			}
+		}
+		checkMatchingSet := func(cs LicenseConditionSet, t *testing.T) {
+			for data, expectedNames := range tt.matchingAny {
+				expected := NewLicenseConditionSet(toConditions(expectedNames)...)
+				actual := cs.MatchingAnySet(NewLicenseConditionSet(toConditions(strings.Split(data, "|"))...))
+				actualNames := actual.Names()
+
+				t.Logf("MatchingAnySet(%s): actual set %04x %s", data, actual, actual.String())
+				t.Logf("MatchingAnySet(%s): expected set %04x %s", data, expected, expected.String())
+
+				if actual != expected {
+					t.Errorf("MatchingAnySet(%s): got %04x, want %04x", data, actual, expected)
+					continue
+				}
+				if len(actualNames) != len(expectedNames) {
+					t.Errorf("len(MatchingAnySet(%s).Names()): got %d, want %d",
+						data, len(actualNames), len(expectedNames))
+				} else {
+					for i := 0; i < len(actualNames); i++ {
+						if actualNames[i] != expectedNames[i] {
+							t.Errorf("MatchingAnySet(%s).Names()[%d]: got %s, want %s",
+								data, i, actualNames[i], expectedNames[i])
+							break
+						}
+					}
+				}
+				expectedConditions := toConditions(expectedNames)
+				actualConditions := actual.AsList()
+				if len(actualConditions) != len(expectedConditions) {
+					t.Errorf("len(MatchingAnySet(%s).AsList()): got %d, want %d",
+						data, len(actualNames), len(expectedNames))
+				} else {
+					for i := 0; i < len(actualNames); i++ {
+						if actualNames[i] != expectedNames[i] {
+							t.Errorf("MatchingAnySet(%s).AsList()[%d]: got %s, want %s",
+								data, i, actualNames[i], expectedNames[i])
+							break
+						}
+					}
+				}
+			}
+		}
+
+		checkExpected := func(actual LicenseConditionSet, t *testing.T) bool {
+			t.Logf("checkExpected{%s}", strings.Join(tt.expected, ", "))
+
+			expectedConditions := toConditions(tt.expected)
+			expected := NewLicenseConditionSet(expectedConditions...)
+
+			actualNames := actual.Names()
+
+			t.Logf("actual license condition set: %04x %s", actual, actual.String())
+			t.Logf("expected license condition set: %04x %s", expected, expected.String())
+
+			if actual != expected {
+				t.Errorf("checkExpected: got %04x, want %04x", actual, expected)
+				return false
+			}
+
+			if len(actualNames) != len(tt.expected) {
+				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
+			} else {
+				for i := 0; i < len(actualNames); i++ {
+					if actualNames[i] != tt.expected[i] {
+						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
+						break
+					}
+				}
+			}
+
+			actualConditions := actual.AsList()
+			if len(actualConditions) != len(expectedConditions) {
+				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
+			} else {
+				for i := 0; i < len(actualConditions); i++ {
+					if actualConditions[i] != expectedConditions[i] {
+						t.Errorf("actual.AsList()[%d]: got %s, want %s",
+							i, actualConditions[i].Name(), expectedConditions[i].Name())
+						break
+					}
+				}
+			}
+
+			if len(tt.expected) == 0 {
+				if !actual.IsEmpty() {
+					t.Errorf("actual.IsEmpty(): got false, want true")
+				}
+				if actual.HasAny(expectedConditions...) {
+					t.Errorf("actual.HasAny(): got true, want false")
+				}
+			} else {
+				if actual.IsEmpty() {
+					t.Errorf("actual.IsEmpty(): got true, want false")
+				}
+				if !actual.HasAny(expectedConditions...) {
+					t.Errorf("actual.HasAny(all expected): got false, want true")
+				}
+			}
+			if !actual.HasAll(expectedConditions...) {
+				t.Errorf("actual.Hasll(all expected): want true, got false")
+			}
+			for _, expectedCondition := range expectedConditions {
+				if !actual.HasAny(expectedCondition) {
+					t.Errorf("actual.HasAny(%q): got false, want true", expectedCondition.Name())
+				}
+				if !actual.HasAll(expectedCondition) {
+					t.Errorf("actual.HasAll(%q): got false, want true", expectedCondition.Name())
+				}
+			}
+
+			notExpected := (AllLicenseConditions &^ expected)
+			notExpectedList := notExpected.AsList()
+			t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
+
+			if len(tt.expected) == 0 {
+				if actual.HasAny(append(expectedConditions, notExpectedList...)...) {
+					t.Errorf("actual.HasAny(all conditions): want false, got true")
+				}
+			} else {
+				if !actual.HasAny(append(expectedConditions, notExpectedList...)...) {
+					t.Errorf("actual.HasAny(all conditions): want true, got false")
+				}
+			}
+			if len(notExpectedList) == 0 {
+				if !actual.HasAll(append(expectedConditions, notExpectedList...)...) {
+					t.Errorf("actual.HasAll(all conditions): want true, got false")
+				}
+			} else {
+				if actual.HasAll(append(expectedConditions, notExpectedList...)...) {
+					t.Errorf("actual.HasAll(all conditions): want false, got true")
+				}
+			}
+			for _, unexpectedCondition := range notExpectedList {
+				if actual.HasAny(unexpectedCondition) {
+					t.Errorf("actual.HasAny(%q): got true, want false", unexpectedCondition.Name())
+				}
+				if actual.HasAll(unexpectedCondition) {
+					t.Errorf("actual.HasAll(%q): got true, want false", unexpectedCondition.Name())
+				}
+			}
+			return true
+		}
+
+		checkExpectedSet := func(actual LicenseConditionSet, t *testing.T) bool {
+			t.Logf("checkExpectedSet{%s}", strings.Join(tt.expected, ", "))
+
+			expectedConditions := toConditions(tt.expected)
+			expected := NewLicenseConditionSet(expectedConditions...)
+
+			actualNames := actual.Names()
+
+			t.Logf("actual license condition set: %04x %s", actual, actual.String())
+			t.Logf("expected license condition set: %04x %s", expected, expected.String())
+
+			if actual != expected {
+				t.Errorf("checkExpectedSet: got %04x, want %04x", actual, expected)
+				return false
+			}
+
+			if len(actualNames) != len(tt.expected) {
+				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
+			} else {
+				for i := 0; i < len(actualNames); i++ {
+					if actualNames[i] != tt.expected[i] {
+						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
+						break
+					}
+				}
+			}
+
+			actualConditions := actual.AsList()
+			if len(actualConditions) != len(expectedConditions) {
+				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
+			} else {
+				for i := 0; i < len(actualConditions); i++ {
+					if actualConditions[i] != expectedConditions[i] {
+						t.Errorf("actual.AsList()[%d}: got %s, want %s",
+							i, actualConditions[i].Name(), expectedConditions[i].Name())
+						break
+					}
+				}
+			}
+
+			if len(tt.expected) == 0 {
+				if !actual.IsEmpty() {
+					t.Errorf("actual.IsEmpty(): got false, want true")
+				}
+				if actual.MatchesAnySet(expected) {
+					t.Errorf("actual.MatchesAnySet({}): got true, want false")
+				}
+				if actual.MatchesEverySet(expected, expected) {
+					t.Errorf("actual.MatchesEverySet({}, {}): want false, got true")
+				}
+			} else {
+				if actual.IsEmpty() {
+					t.Errorf("actual.IsEmpty(): got true, want false")
+				}
+				if !actual.MatchesAnySet(expected) {
+					t.Errorf("actual.MatchesAnySet({all expected}): want true, got false")
+				}
+				if !actual.MatchesEverySet(expected, expected) {
+					t.Errorf("actual.MatchesEverySet({all expected}, {all expected}): want true, got false")
+				}
+			}
+
+			notExpected := (AllLicenseConditions &^ expected)
+			t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
+
+			if len(tt.expected) == 0 {
+				if actual.MatchesAnySet(expected, notExpected) {
+					t.Errorf("empty actual.MatchesAnySet({expected}, {not expected}): want false, got true")
+				}
+			} else {
+				if !actual.MatchesAnySet(expected, notExpected) {
+					t.Errorf("actual.MatchesAnySet({expected}, {not expected}): want true, got false")
+				}
+			}
+			if actual.MatchesAnySet(notExpected) {
+				t.Errorf("actual.MatchesAnySet({not expected}): want false, got true")
+			}
+			if actual.MatchesEverySet(notExpected) {
+				t.Errorf("actual.MatchesEverySet({not expected}): want false, got true")
+			}
+			if actual.MatchesEverySet(expected, notExpected) {
+				t.Errorf("actual.MatchesEverySet({expected}, {not expected}): want false, got true")
+			}
+
+			if !actual.Difference(expected).IsEmpty() {
+				t.Errorf("actual.Difference({expected}).IsEmpty(): want true, got false")
+			}
+			if expected != actual.Intersection(expected) {
+				t.Errorf("expected == actual.Intersection({expected}): want true, got false (%04x != %04x)", expected, actual.Intersection(expected))
+			}
+			if actual != actual.Intersection(expected) {
+				t.Errorf("actual == actual.Intersection({expected}): want true, got false (%04x != %04x)", actual, actual.Intersection(expected))
+			}
+			return true
+		}
+
+		t.Run(tt.name, func(t *testing.T) {
+			cs := populate()
+			if checkExpected(cs, t) {
+				checkMatching(cs, t)
+			}
+			if checkExpectedSet(cs, t) {
+				checkMatchingSet(cs, t)
+			}
+		})
+
+		t.Run(tt.name+"_sets", func(t *testing.T) {
+			cs := populateSet()
+			if checkExpected(cs, t) {
+				checkMatching(cs, t)
+			}
+			if checkExpectedSet(cs, t) {
+				checkMatchingSet(cs, t)
+			}
+		})
+
+		t.Run(tt.name+"_plusset", func(t *testing.T) {
+			cs := populatePlusSet()
+			if checkExpected(cs, t) {
+				checkMatching(cs, t)
+			}
+			if checkExpectedSet(cs, t) {
+				checkMatchingSet(cs, t)
+			}
+		})
+
+		t.Run(tt.name+"_minusset", func(t *testing.T) {
+			cs := populateMinusSet()
+			if checkExpected(cs, t) {
+				checkMatching(cs, t)
+			}
+			if checkExpectedSet(cs, t) {
+				checkMatchingSet(cs, t)
+			}
+		})
+	}
+}
diff --git a/tools/compliance/doc.go b/tools/compliance/doc.go
new file mode 100644
index 0000000..a47c1cf
--- /dev/null
+++ b/tools/compliance/doc.go
@@ -0,0 +1,77 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+
+Package compliance provides an approved means for reading, consuming, and
+analyzing license metadata graphs.
+
+Assuming the license metadata and dependencies are fully and accurately
+recorded in the build system, any discrepancy between the official policy for
+open source license compliance and this code is a bug in this code.
+
+A few principal types to understand are LicenseGraph, LicenseCondition, and
+ResolutionSet.
+
+LicenseGraph
+------------
+
+A LicenseGraph is an immutable graph of the targets and dependencies reachable
+from a specific set of root targets. In general, the root targets will be the
+artifacts in a release or distribution. While conceptually immutable, parts of
+the graph may be loaded or evaluated lazily.
+
+LicenseCondition
+----------------
+
+A LicenseCondition is an immutable tuple pairing a condition name with an
+originating target. e.g. Per current policy, a static library licensed under an
+MIT license would pair a "notice" condition with the static library target, and
+a dynamic license licensed under GPL would pair a "restricted" condition with
+the dynamic library target.
+
+ResolutionSet
+-------------
+
+A ResolutionSet is an immutable set of `AttachesTo`, `ActsOn`, `Resolves`
+tuples describing how license conditions apply to targets.
+
+`AttachesTo` is the trigger for acting. Distribution of the target invokes
+the policy.
+
+`ActsOn` is the target to share, give notice for, hide etc.
+
+`Resolves` is the license condition that the action resolves.
+
+Remember: Each license condition pairs a condition name with an originating
+target so each resolution in a ResolutionSet has two targets it applies to and
+one target from which it originates, all of which may be the same target.
+
+For most condition types, `ActsOn` and `Resolves.Origin` will be the same
+target. For example, a notice condition policy means attribution or notice must
+be given for the target where the condition originates. Likewise, a proprietary
+condition policy means the privacy of the target where the condition originates
+must be respected. i.e. The thing acted on is the origin.
+
+Restricted conditions are different. The infectious nature of restricted often
+means sharing code that is not the target where the restricted condition
+originates. Linking an MIT library to a GPL library implies a policy to share
+the MIT library despite the MIT license having no source sharing requirement.
+
+In this case, one or more resolution tuples will have the MIT license module in
+`ActsOn` and the restricted condition originating at the GPL library module in
+`Resolves`. These tuples will `AttachTo` every target that depends on the GPL
+library because shipping any of those targets trigger the policy to share the
+code.
+*/
+package compliance
diff --git a/tools/compliance/go.mod b/tools/compliance/go.mod
new file mode 100644
index 0000000..61e2158
--- /dev/null
+++ b/tools/compliance/go.mod
@@ -0,0 +1,18 @@
+module android/soong/tools/compliance
+
+require google.golang.org/protobuf v0.0.0
+
+replace google.golang.org/protobuf v0.0.0 => ../../../../external/golang-protobuf
+
+require android/soong v0.0.0
+
+replace android/soong v0.0.0 => ../../../soong									      
+// Indirect deps from golang-protobuf
+exclude github.com/golang/protobuf v1.5.0
+
+replace github.com/google/go-cmp v0.5.5 => ../../../../external/go-cmp
+
+// Indirect dep from go-cmp
+exclude golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
+
+go 1.18
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
new file mode 100644
index 0000000..e73ab46
--- /dev/null
+++ b/tools/compliance/graph.go
@@ -0,0 +1,528 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+)
+
+// LicenseGraph describes the immutable license metadata for a set of root
+// targets and the transitive closure of their dependencies.
+//
+// Alternatively, a graph is a set of edges. In this case directed, annotated
+// edges from targets to dependencies.
+//
+// A LicenseGraph provides the frame of reference for all of the other types
+// defined here. It is possible to have multiple graphs, and to have targets,
+// edges, and resolutions from multiple graphs. But it is an error to try to
+// mix items from different graphs in the same operation.
+// May panic if attempted.
+//
+// The compliance package assumes specific private implementations of each of
+// these interfaces. May panic if attempts are made to combine different
+// implementations of some interfaces with expected implementations of other
+// interfaces here.
+type LicenseGraph struct {
+	// rootFiles identifies the original set of files to read. (immutable)
+	//
+	// Defines the starting "top" for top-down walks.
+	//
+	// Alternatively, an instance of licenseGraphImp conceptually defines a scope within
+	// the universe of build graphs as a sub-graph rooted at rootFiles where all edges
+	// and targets for the instance are defined relative to and within that scope. For
+	// most analyses, the correct scope is to root the graph at all of the distributed
+	// artifacts.
+	rootFiles []string
+
+	// edges lists the directed edges in the graph from target to dependency. (guarded by mu)
+	//
+	// Alternatively, the graph is the set of `edges`.
+	edges TargetEdgeList
+
+	// targets identifies, indexes, and describes the entire set of target node files.
+	/// (guarded by mu)
+	targets map[string]*TargetNode
+
+	// wgBU becomes non-nil when the bottom-up resolve begins and reaches 0
+	// (i.e. Wait() proceeds) when the bottom-up resolve completes. (guarded by mu)
+	wgBU *sync.WaitGroup
+
+	// wgTD becomes non-nil when the top-down resolve begins and reaches 0 (i.e. Wait()
+	// proceeds) when the top-down resolve completes. (guarded by mu)
+	wgTD *sync.WaitGroup
+
+	// shippedNodes caches the results of a full walk of nodes identifying targets
+	// distributed either directly or as derivative works. (creation guarded by mu)
+	shippedNodes *TargetNodeSet
+
+	// mu guards against concurrent update.
+	mu sync.Mutex
+}
+
+// Edges returns the list of edges in the graph. (unordered)
+func (lg *LicenseGraph) Edges() TargetEdgeList {
+	edges := make(TargetEdgeList, 0, len(lg.edges))
+	edges = append(edges, lg.edges...)
+	return edges
+}
+
+// Targets returns the list of target nodes in the graph. (unordered)
+func (lg *LicenseGraph) Targets() TargetNodeList {
+	targets := make(TargetNodeList, 0, len(lg.targets))
+	for _, target := range lg.targets {
+		targets = append(targets, target)
+	}
+	return targets
+}
+
+// compliance-only LicenseGraph methods
+
+// newLicenseGraph constructs a new, empty instance of LicenseGraph.
+func newLicenseGraph() *LicenseGraph {
+	return &LicenseGraph{
+		rootFiles: []string{},
+		targets:   make(map[string]*TargetNode),
+	}
+}
+
+// TargetEdge describes a directed, annotated edge from a target to a
+// dependency. (immutable)
+//
+// A LicenseGraph, above, is a set of TargetEdges.
+//
+// i.e. `Target` depends on `Dependency` in the manner described by
+// `Annotations`.
+type TargetEdge struct {
+	// target and dependency identify the nodes connected by the edge.
+	target, dependency *TargetNode
+
+	// annotations identifies the set of compliance-relevant annotations describing the edge.
+	annotations TargetEdgeAnnotations
+}
+
+// Target identifies the target that depends on the dependency.
+//
+// Target needs Dependency to build.
+func (e *TargetEdge) Target() *TargetNode {
+	return e.target
+}
+
+// Dependency identifies the target depended on by the target.
+//
+// Dependency builds without Target, but Target needs Dependency to build.
+func (e *TargetEdge) Dependency() *TargetNode {
+	return e.dependency
+}
+
+// Annotations describes the type of edge by the set of annotations attached to
+// it.
+//
+// Only annotations prescribed by policy have any meaning for licensing, and
+// the meaning for licensing is likewise prescribed by policy. Other annotations
+// are preserved and ignored by policy.
+func (e *TargetEdge) Annotations() TargetEdgeAnnotations {
+	return e.annotations
+}
+
+// String returns a human-readable string representation of the edge.
+func (e *TargetEdge) String() string {
+	return fmt.Sprintf("%s -[%s]> %s", e.target.name, strings.Join(e.annotations.AsList(), ", "), e.dependency.name)
+}
+
+// TargetEdgeList orders lists of edges by target then dependency then annotations.
+type TargetEdgeList []*TargetEdge
+
+// Len returns the count of the elmements in the list.
+func (l TargetEdgeList) Len() int { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l TargetEdgeList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than the `j`th.
+func (l TargetEdgeList) Less(i, j int) bool {
+	namei := l[i].target.name
+	namej := l[j].target.name
+	if namei == namej {
+		namei = l[i].dependency.name
+		namej = l[j].dependency.name
+	}
+	if namei == namej {
+		return l[i].annotations.Compare(l[j].annotations) < 0
+	}
+	return namei < namej
+}
+
+// TargetEdgePathSegment describes a single arc in a TargetPath associating the
+// edge with a context `ctx` defined by whatever process is creating the path.
+type TargetEdgePathSegment struct {
+	edge *TargetEdge
+	ctx  interface{}
+}
+
+// Target identifies the target that depends on the dependency.
+//
+// Target needs Dependency to build.
+func (s TargetEdgePathSegment) Target() *TargetNode {
+	return s.edge.target
+}
+
+// Dependency identifies the target depended on by the target.
+//
+// Dependency builds without Target, but Target needs Dependency to build.
+func (s TargetEdgePathSegment) Dependency() *TargetNode {
+	return s.edge.dependency
+}
+
+// Annotations describes the type of edge by the set of annotations attached to
+// it.
+//
+// Only annotations prescribed by policy have any meaning for licensing, and
+// the meaning for licensing is likewise prescribed by policy. Other annotations
+// are preserved and ignored by policy.
+func (s TargetEdgePathSegment) Annotations() TargetEdgeAnnotations {
+	return s.edge.annotations
+}
+
+// Context returns the context associated with the path segment. The type and
+// value of the context defined by the process creating the path.
+func (s TargetEdgePathSegment) Context() interface{} {
+	return s.ctx
+}
+
+// String returns a human-readable string representation of the edge.
+func (s TargetEdgePathSegment) String() string {
+	return fmt.Sprintf("%s -[%s]> %s", s.edge.target.name, strings.Join(s.edge.annotations.AsList(), ", "), s.edge.dependency.name)
+}
+
+// TargetEdgePath describes a sequence of edges starting at a root and ending
+// at some final dependency.
+type TargetEdgePath []TargetEdgePathSegment
+
+// NewTargetEdgePath creates a new, empty path with capacity `cap`.
+func NewTargetEdgePath(cap int) *TargetEdgePath {
+	p := make(TargetEdgePath, 0, cap)
+	return &p
+}
+
+// Push appends a new edge to the list verifying that the target of the new
+// edge is the dependency of the prior.
+func (p *TargetEdgePath) Push(edge *TargetEdge, ctx interface{}) {
+	if len(*p) == 0 {
+		*p = append(*p, TargetEdgePathSegment{edge, ctx})
+		return
+	}
+	if (*p)[len(*p)-1].edge.dependency != edge.target {
+		panic(fmt.Errorf("disjoint path %s does not end at %s", p.String(), edge.target.name))
+	}
+	*p = append(*p, TargetEdgePathSegment{edge, ctx})
+}
+
+// Pop shortens the path by 1 edge.
+func (p *TargetEdgePath) Pop() {
+	if len(*p) == 0 {
+		panic(fmt.Errorf("attempt to remove edge from empty path"))
+	}
+	*p = (*p)[:len(*p)-1]
+}
+
+// Clear makes the path length 0.
+func (p *TargetEdgePath) Clear() {
+	*p = (*p)[:0]
+}
+
+// Copy makes a new path with the same value.
+func (p *TargetEdgePath) Copy() *TargetEdgePath {
+	result := make(TargetEdgePath, 0, len(*p))
+	for _, e := range *p {
+		result = append(result, e)
+	}
+	return &result
+}
+
+// String returns a string representation of the path: [n1 -> n2 -> ... -> nn].
+func (p *TargetEdgePath) String() string {
+	if p == nil {
+		return "nil"
+	}
+	if len(*p) == 0 {
+		return "[]"
+	}
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	for _, s := range *p {
+		fmt.Fprintf(&sb, "%s -> ", s.edge.target.name)
+	}
+	lastSegment := (*p)[len(*p)-1]
+	fmt.Fprintf(&sb, "%s]", lastSegment.edge.dependency.name)
+	return sb.String()
+}
+
+// TargetNode describes a module or target identified by the name of a specific
+// metadata file. (immutable)
+//
+// Each metadata file corresponds to a Soong module or to a Make target.
+//
+// A target node can appear as the target or as the dependency in edges.
+// Most target nodes appear as both target in one edge and as dependency in
+// other edges.
+type TargetNode targetNode
+
+// Name returns the string that identifies the target node.
+// i.e. path to license metadata file
+func (tn *TargetNode) Name() string {
+	return tn.name
+}
+
+// Dependencies returns the list of edges to dependencies of `tn`.
+func (tn *TargetNode) Dependencies() TargetEdgeList {
+	edges := make(TargetEdgeList, 0, len(tn.edges))
+	edges = append(edges, tn.edges...)
+	return edges
+}
+
+// PackageName returns the string that identifes the package for the target.
+func (tn *TargetNode) PackageName() string {
+	return tn.proto.GetPackageName()
+}
+
+// ModuleTypes returns the list of module types implementing the target.
+// (unordered)
+//
+// In an ideal world, only 1 module type would implement each target, but the
+// interactions between Soong and Make for host versus product and for a
+// variety of architectures sometimes causes multiple module types per target
+// (often a regular build target and a prebuilt.)
+func (tn *TargetNode) ModuleTypes() []string {
+	return append([]string{}, tn.proto.ModuleTypes...)
+}
+
+// ModuleClasses returns the list of module classes implementing the target.
+// (unordered)
+func (tn *TargetNode) ModuleClasses() []string {
+	return append([]string{}, tn.proto.ModuleClasses...)
+}
+
+// Projects returns the projects defining the target node. (unordered)
+//
+// In an ideal world, only 1 project defines a target, but the interaction
+// between Soong and Make for a variety of architectures and for host versus
+// product means a module is sometimes defined more than once.
+func (tn *TargetNode) Projects() []string {
+	return append([]string{}, tn.proto.Projects...)
+}
+
+// LicenseKinds returns the list of license kind names for the module or
+// target. (unordered)
+//
+// e.g. SPDX-license-identifier-MIT or legacy_proprietary
+func (tn *TargetNode) LicenseKinds() []string {
+	return append([]string{}, tn.proto.LicenseKinds...)
+}
+
+// LicenseConditions returns a copy of the set of license conditions
+// originating at the target. The values that appear and how each is resolved
+// is a matter of policy. (unordered)
+//
+// e.g. notice or proprietary
+func (tn *TargetNode) LicenseConditions() LicenseConditionSet {
+	return tn.licenseConditions
+}
+
+// LicenseTexts returns the paths to the files containing the license texts for
+// the target. (unordered)
+func (tn *TargetNode) LicenseTexts() []string {
+	return append([]string{}, tn.proto.LicenseTexts...)
+}
+
+// IsContainer returns true if the target represents a container that merely
+// aggregates other targets.
+func (tn *TargetNode) IsContainer() bool {
+	return tn.proto.GetIsContainer()
+}
+
+// Built returns the list of files built by the module or target. (unordered)
+func (tn *TargetNode) Built() []string {
+	return append([]string{}, tn.proto.Built...)
+}
+
+// Installed returns the list of files installed by the module or target.
+// (unordered)
+func (tn *TargetNode) Installed() []string {
+	return append([]string{}, tn.proto.Installed...)
+}
+
+// TargetFiles returns the list of files built or installed by the module or
+// target. (unordered)
+func (tn *TargetNode) TargetFiles() []string {
+	return append(tn.proto.Built, tn.proto.Installed...)
+}
+
+// InstallMap returns the list of path name transformations to make to move
+// files from their original location in the file system to their destination
+// inside a container. (unordered)
+func (tn *TargetNode) InstallMap() []InstallMap {
+	result := make([]InstallMap, 0, len(tn.proto.InstallMap))
+	for _, im := range tn.proto.InstallMap {
+		result = append(result, InstallMap{im.GetFromPath(), im.GetContainerPath()})
+	}
+	return result
+}
+
+// Sources returns the list of file names depended on by the target, which may
+// be a proper subset of those made available by dependency modules.
+// (unordered)
+func (tn *TargetNode) Sources() []string {
+	return append([]string{}, tn.proto.Sources...)
+}
+
+// InstallMap describes the mapping from an input filesystem file to file in a
+// container.
+type InstallMap struct {
+	// FromPath is the input path on the filesystem.
+	FromPath string
+
+	// ContainerPath is the path to the same file inside the container or
+	// installed location.
+	ContainerPath string
+}
+
+// TargetEdgeAnnotations describes an immutable set of annotations attached to
+// an edge from a target to a dependency.
+//
+// Annotations typically distinguish between static linkage versus dynamic
+// versus tools that are used at build time but are not linked in any way.
+type TargetEdgeAnnotations struct {
+	annotations map[string]struct{}
+}
+
+// newEdgeAnnotations creates a new instance of TargetEdgeAnnotations.
+func newEdgeAnnotations() TargetEdgeAnnotations {
+	return TargetEdgeAnnotations{make(map[string]struct{})}
+}
+
+// HasAnnotation returns true if an annotation `ann` is in the set.
+func (ea TargetEdgeAnnotations) HasAnnotation(ann string) bool {
+	_, ok := ea.annotations[ann]
+	return ok
+}
+
+// Compare orders TargetAnnotations returning:
+// -1 when ea < other,
+// +1 when ea > other, and
+// 0 when ea == other.
+func (ea TargetEdgeAnnotations) Compare(other TargetEdgeAnnotations) int {
+	a1 := ea.AsList()
+	a2 := other.AsList()
+	sort.Strings(a1)
+	sort.Strings(a2)
+	for k := 0; k < len(a1) && k < len(a2); k++ {
+		if a1[k] < a2[k] {
+			return -1
+		}
+		if a1[k] > a2[k] {
+			return 1
+		}
+	}
+	if len(a1) < len(a2) {
+		return -1
+	}
+	if len(a1) > len(a2) {
+		return 1
+	}
+	return 0
+}
+
+// AsList returns the list of annotation names attached to the edge.
+// (unordered)
+func (ea TargetEdgeAnnotations) AsList() []string {
+	l := make([]string, 0, len(ea.annotations))
+	for ann := range ea.annotations {
+		l = append(l, ann)
+	}
+	return l
+}
+
+// TargetNodeSet describes a set of distinct nodes in a license graph.
+type TargetNodeSet struct {
+	nodes map[*TargetNode]struct{}
+}
+
+// Contains returns true when `target` is an element of the set.
+func (ts *TargetNodeSet) Contains(target *TargetNode) bool {
+	_, isPresent := ts.nodes[target]
+	return isPresent
+}
+
+// AsList returns the list of target nodes in the set. (unordered)
+func (ts *TargetNodeSet) AsList() TargetNodeList {
+	result := make(TargetNodeList, 0, len(ts.nodes))
+	for tn := range ts.nodes {
+		result = append(result, tn)
+	}
+	return result
+}
+
+// Names returns the array of target node namess in the set. (unordered)
+func (ts *TargetNodeSet) Names() []string {
+	result := make([]string, 0, len(ts.nodes))
+	for tn := range ts.nodes {
+		result = append(result, tn.name)
+	}
+	return result
+}
+
+// String returns a human-readable string representation of the set.
+func (ts *TargetNodeSet) String() string {
+	return fmt.Sprintf("{%s}", strings.Join(ts.Names(), ", "))
+}
+
+// TargetNodeList orders a list of targets by name.
+type TargetNodeList []*TargetNode
+
+// Len returns the count of elements in the list.
+func (l TargetNodeList) Len() int { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l TargetNodeList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographicallt less than the `j`th.
+func (l TargetNodeList) Less(i, j int) bool {
+	return l[i].name < l[j].name
+}
+
+// String returns a string representation of the list.
+func (l TargetNodeList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, tn := range l {
+		fmt.Fprintf(&sb, "%s%s", sep, tn.name)
+		sep = " "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// Names returns an array the names of the nodes in the same order as the nodes in the list.
+func (l TargetNodeList) Names() []string {
+	result := make([]string, 0, len(l))
+	for _, tn := range l {
+		result = append(result, tn.name)
+	}
+	return result
+}
diff --git a/tools/compliance/noticeindex.go b/tools/compliance/noticeindex.go
new file mode 100644
index 0000000..f082383
--- /dev/null
+++ b/tools/compliance/noticeindex.go
@@ -0,0 +1,697 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bufio"
+	"crypto/md5"
+	"fmt"
+	"io"
+	"io/fs"
+	"net/url"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strings"
+)
+
+const (
+	noProjectName = "\u2205"
+)
+
+var (
+	nameRegexp         = regexp.MustCompile(`^\s*name\s*:\s*"(.*)"\s*$`)
+	descRegexp         = regexp.MustCompile(`^\s*description\s*:\s*"(.*)"\s*$`)
+	versionRegexp      = regexp.MustCompile(`^\s*version\s*:\s*"(.*)"\s*$`)
+	licensesPathRegexp = regexp.MustCompile(`licen[cs]es?/`)
+)
+
+// NoticeIndex transforms license metadata into license text hashes, library
+// names, and install paths indexing them for fast lookup/iteration.
+type NoticeIndex struct {
+	// lg identifies the license graph to which the index applies.
+	lg *LicenseGraph
+	// rs identifies the set of resolutions upon which the index is based.
+	rs ResolutionSet
+	// shipped identifies the set of target nodes shipped directly or as derivative works.
+	shipped *TargetNodeSet
+	// rootFS locates the root of the file system from which to read the files.
+	rootFS fs.FS
+	// hash maps license text filenames to content hashes
+	hash map[string]hash
+	// text maps content hashes to content
+	text map[hash][]byte
+	// hashLibInstall maps hashes to libraries to install paths.
+	hashLibInstall map[hash]map[string]map[string]struct{}
+	// installHashLib maps install paths to libraries to hashes.
+	installHashLib map[string]map[hash]map[string]struct{}
+	// libHash maps libraries to hashes.
+	libHash map[string]map[hash]struct{}
+	// targetHash maps target nodes to hashes.
+	targetHashes map[*TargetNode]map[hash]struct{}
+	// projectName maps project directory names to project name text.
+	projectName map[string]string
+	// files lists all the files accessed during indexing
+	files []string
+}
+
+// IndexLicenseTexts creates a hashed index of license texts for `lg` and `rs`
+// using the files rooted at `rootFS`.
+func IndexLicenseTexts(rootFS fs.FS, lg *LicenseGraph, rs ResolutionSet) (*NoticeIndex, error) {
+	if rs == nil {
+		rs = ResolveNotices(lg)
+	}
+	ni := &NoticeIndex{
+		lg:             lg,
+		rs:             rs,
+		shipped:        ShippedNodes(lg),
+		rootFS:         rootFS,
+		hash:           make(map[string]hash),
+		text:           make(map[hash][]byte),
+		hashLibInstall: make(map[hash]map[string]map[string]struct{}),
+		installHashLib: make(map[string]map[hash]map[string]struct{}),
+		libHash:        make(map[string]map[hash]struct{}),
+		targetHashes:   make(map[*TargetNode]map[hash]struct{}),
+		projectName:    make(map[string]string),
+	}
+
+	// index adds all license texts for `tn` to the index.
+	index := func(tn *TargetNode) (map[hash]struct{}, error) {
+		if hashes, ok := ni.targetHashes[tn]; ok {
+			return hashes, nil
+		}
+		hashes := make(map[hash]struct{})
+		for _, text := range tn.LicenseTexts() {
+			fname := strings.SplitN(text, ":", 2)[0]
+			if _, ok := ni.hash[fname]; !ok {
+				err := ni.addText(fname)
+				if err != nil {
+					return nil, err
+				}
+			}
+			hash := ni.hash[fname]
+			if _, ok := hashes[hash]; !ok {
+				hashes[hash] = struct{}{}
+			}
+		}
+		ni.targetHashes[tn] = hashes
+		return hashes, nil
+	}
+
+	link := func(tn *TargetNode, hashes map[hash]struct{}, installPaths []string) {
+		for h := range hashes {
+			libName := ni.getLibName(tn, h)
+			if _, ok := ni.libHash[libName]; !ok {
+				ni.libHash[libName] = make(map[hash]struct{})
+			}
+			if _, ok := ni.hashLibInstall[h]; !ok {
+				ni.hashLibInstall[h] = make(map[string]map[string]struct{})
+			}
+			if _, ok := ni.libHash[libName][h]; !ok {
+				ni.libHash[libName][h] = struct{}{}
+			}
+			for _, installPath := range installPaths {
+				if _, ok := ni.installHashLib[installPath]; !ok {
+					ni.installHashLib[installPath] = make(map[hash]map[string]struct{})
+					ni.installHashLib[installPath][h] = make(map[string]struct{})
+					ni.installHashLib[installPath][h][libName] = struct{}{}
+				} else if _, ok = ni.installHashLib[installPath][h]; !ok {
+					ni.installHashLib[installPath][h] = make(map[string]struct{})
+					ni.installHashLib[installPath][h][libName] = struct{}{}
+				} else if _, ok = ni.installHashLib[installPath][h][libName]; !ok {
+					ni.installHashLib[installPath][h][libName] = struct{}{}
+				}
+				if _, ok := ni.hashLibInstall[h]; !ok {
+					ni.hashLibInstall[h] = make(map[string]map[string]struct{})
+					ni.hashLibInstall[h][libName] = make(map[string]struct{})
+					ni.hashLibInstall[h][libName][installPath] = struct{}{}
+				} else if _, ok = ni.hashLibInstall[h][libName]; !ok {
+					ni.hashLibInstall[h][libName] = make(map[string]struct{})
+					ni.hashLibInstall[h][libName][installPath] = struct{}{}
+				} else if _, ok = ni.hashLibInstall[h][libName][installPath]; !ok {
+					ni.hashLibInstall[h][libName][installPath] = struct{}{}
+				}
+			}
+		}
+	}
+
+	// returns error from walk below.
+	var err error
+
+	WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		if err != nil {
+			return false
+		}
+		if !ni.shipped.Contains(tn) {
+			return false
+		}
+		installPaths := getInstallPaths(tn, path)
+		var hashes map[hash]struct{}
+		hashes, err = index(tn)
+		if err != nil {
+			return false
+		}
+		link(tn, hashes, installPaths)
+		if tn.IsContainer() {
+			return true
+		}
+
+		for _, r := range rs.Resolutions(tn) {
+			hashes, err = index(r.actsOn)
+			if err != nil {
+				return false
+			}
+			link(r.actsOn, hashes, installPaths)
+		}
+		return false
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return ni, nil
+}
+
+// Hashes returns an ordered channel of the hashed license texts.
+func (ni *NoticeIndex) Hashes() chan hash {
+	c := make(chan hash)
+	go func() {
+		libs := make([]string, 0, len(ni.libHash))
+		for libName := range ni.libHash {
+			libs = append(libs, libName)
+		}
+		sort.Strings(libs)
+		hashes := make(map[hash]struct{})
+		for _, libName := range libs {
+			hl := make([]hash, 0, len(ni.libHash[libName]))
+			for h := range ni.libHash[libName] {
+				if _, ok := hashes[h]; ok {
+					continue
+				}
+				hashes[h] = struct{}{}
+				hl = append(hl, h)
+			}
+			if len(hl) > 0 {
+				sort.Sort(hashList{ni, libName, "", &hl})
+				for _, h := range hl {
+					c <- h
+				}
+			}
+		}
+		close(c)
+	}()
+	return c
+}
+
+// InputNoticeFiles returns the list of files that were hashed during IndexLicenseTexts.
+func (ni *NoticeIndex) InputNoticeFiles() []string {
+	files := append([]string(nil), ni.files...)
+	sort.Strings(files)
+	return files
+}
+
+// HashLibs returns the ordered array of library names using the license text
+// hashed as `h`.
+func (ni *NoticeIndex) HashLibs(h hash) []string {
+	libs := make([]string, 0, len(ni.hashLibInstall[h]))
+	for libName := range ni.hashLibInstall[h] {
+		libs = append(libs, libName)
+	}
+	sort.Strings(libs)
+	return libs
+}
+
+// HashLibInstalls returns the ordered array of install paths referencing
+// library `libName` using the license text hashed as `h`.
+func (ni *NoticeIndex) HashLibInstalls(h hash, libName string) []string {
+	installs := make([]string, 0, len(ni.hashLibInstall[h][libName]))
+	for installPath := range ni.hashLibInstall[h][libName] {
+		installs = append(installs, installPath)
+	}
+	sort.Strings(installs)
+	return installs
+}
+
+// InstallPaths returns the ordered channel of indexed install paths.
+func (ni *NoticeIndex) InstallPaths() chan string {
+	c := make(chan string)
+	go func() {
+		paths := make([]string, 0, len(ni.installHashLib))
+		for path := range ni.installHashLib {
+			paths = append(paths, path)
+		}
+		sort.Strings(paths)
+		for _, installPath := range paths {
+			c <- installPath
+		}
+		close(c)
+	}()
+	return c
+}
+
+// InstallHashes returns the ordered array of hashes attached to `installPath`.
+func (ni *NoticeIndex) InstallHashes(installPath string) []hash {
+	result := make([]hash, 0, len(ni.installHashLib[installPath]))
+	for h := range ni.installHashLib[installPath] {
+		result = append(result, h)
+	}
+	if len(result) > 0 {
+		sort.Sort(hashList{ni, "", installPath, &result})
+	}
+	return result
+}
+
+// InstallHashLibs returns the ordered array of library names attached to
+// `installPath` as hash `h`.
+func (ni *NoticeIndex) InstallHashLibs(installPath string, h hash) []string {
+	result := make([]string, 0, len(ni.installHashLib[installPath][h]))
+	for libName := range ni.installHashLib[installPath][h] {
+		result = append(result, libName)
+	}
+	sort.Strings(result)
+	return result
+}
+
+// Libraries returns the ordered channel of indexed library names.
+func (ni *NoticeIndex) Libraries() chan string {
+	c := make(chan string)
+	go func() {
+		libs := make([]string, 0, len(ni.libHash))
+		for lib := range ni.libHash {
+			libs = append(libs, lib)
+		}
+		sort.Strings(libs)
+		for _, lib := range libs {
+			c <- lib
+		}
+		close(c)
+	}()
+	return c
+}
+
+// HashText returns the file content of the license text hashed as `h`.
+func (ni *NoticeIndex) HashText(h hash) []byte {
+	return ni.text[h]
+}
+
+// getLibName returns the name of the library associated with `noticeFor`.
+func (ni *NoticeIndex) getLibName(noticeFor *TargetNode, h hash) string {
+	for _, text := range noticeFor.LicenseTexts() {
+		if !strings.Contains(text, ":") {
+			if ni.hash[text].key != h.key {
+				continue
+			}
+			ln := ni.checkMetadataForLicenseText(noticeFor, text)
+			if len(ln) > 0 {
+				return ln
+			}
+			continue
+		}
+
+		fields := strings.SplitN(text, ":", 2)
+		fname, pname := fields[0], fields[1]
+		if ni.hash[fname].key != h.key {
+			continue
+		}
+
+		ln, err := url.QueryUnescape(pname)
+		if err != nil {
+			continue
+		}
+		return ln
+	}
+	// use name from METADATA if available
+	ln := ni.checkMetadata(noticeFor)
+	if len(ln) > 0 {
+		return ln
+	}
+	// use package_name: from license{} module if available
+	pn := noticeFor.PackageName()
+	if len(pn) > 0 {
+		return pn
+	}
+	for _, p := range noticeFor.Projects() {
+		if strings.HasPrefix(p, "prebuilts/") {
+			for _, licenseText := range noticeFor.LicenseTexts() {
+				if !strings.HasPrefix(licenseText, "prebuilts/") {
+					continue
+				}
+				if !strings.Contains(licenseText, ":") {
+					if ni.hash[licenseText].key != h.key {
+						continue
+					}
+				} else {
+					fields := strings.SplitN(licenseText, ":", 2)
+					fname := fields[0]
+					if ni.hash[fname].key != h.key {
+						continue
+					}
+				}
+				for r, prefix := range SafePrebuiltPrefixes {
+					match := r.FindString(licenseText)
+					if len(match) == 0 {
+						continue
+					}
+					strip := SafePathPrefixes[prefix]
+					if strip {
+						// strip entire prefix
+						match = licenseText[len(match):]
+					} else {
+						// strip from prebuilts/ until safe prefix
+						match = licenseText[len(match)-len(prefix):]
+					}
+					// remove LICENSE or NOTICE or other filename
+					li := strings.LastIndex(match, "/")
+					if li > 0 {
+						match = match[:li]
+					}
+					// remove *licenses/ path segment and subdirectory if in path
+					if offsets := licensesPathRegexp.FindAllStringIndex(match, -1); offsets != nil && offsets[len(offsets)-1][0] > 0 {
+						match = match[:offsets[len(offsets)-1][0]]
+						li = strings.LastIndex(match, "/")
+						if li > 0 {
+							match = match[:li]
+						}
+					}
+					return match
+				}
+				break
+			}
+		}
+		for prefix, strip := range SafePathPrefixes {
+			if strings.HasPrefix(p, prefix) {
+				if strip {
+					return p[len(prefix):]
+				} else {
+					return p
+				}
+			}
+		}
+	}
+	// strip off [./]meta_lic from license metadata path and extract base name
+	n := noticeFor.name[:len(noticeFor.name)-9]
+	li := strings.LastIndex(n, "/")
+	if li > 0 {
+		n = n[li+1:]
+	}
+	fi := strings.Index(n, "@")
+	if fi > 0 {
+		n = n[:fi]
+	}
+	return n
+}
+
+// checkMetadata tries to look up a library name from a METADATA file associated with `noticeFor`.
+func (ni *NoticeIndex) checkMetadata(noticeFor *TargetNode) string {
+	for _, p := range noticeFor.Projects() {
+		if name, ok := ni.projectName[p]; ok {
+			if name == noProjectName {
+				continue
+			}
+			return name
+		}
+		name, err := ni.checkMetadataFile(filepath.Join(p, "METADATA"))
+		if err != nil {
+			ni.projectName[p] = noProjectName
+			continue
+		}
+		if len(name) == 0 {
+			ni.projectName[p] = noProjectName
+			continue
+		}
+		ni.projectName[p] = name
+		return name
+	}
+	return ""
+}
+
+// checkMetadataForLicenseText
+func (ni *NoticeIndex) checkMetadataForLicenseText(noticeFor *TargetNode, licenseText string) string {
+	p := ""
+	for _, proj := range noticeFor.Projects() {
+		if strings.HasPrefix(licenseText, proj) {
+			p = proj
+		}
+	}
+	if len(p) == 0 {
+		p = filepath.Dir(licenseText)
+		for {
+			fi, err := fs.Stat(ni.rootFS, filepath.Join(p, ".git"))
+			if err == nil && fi.IsDir() {
+				break
+			}
+			if strings.Contains(p, "/") && p != "/" {
+				p = filepath.Dir(p)
+				continue
+			}
+			return ""
+		}
+	}
+	if name, ok := ni.projectName[p]; ok {
+		if name == noProjectName {
+			return ""
+		}
+		return name
+	}
+	name, err := ni.checkMetadataFile(filepath.Join(p, "METADATA"))
+	if err == nil && len(name) > 0 {
+		ni.projectName[p] = name
+		return name
+	}
+	ni.projectName[p] = noProjectName
+	return ""
+}
+
+// checkMetadataFile tries to look up a library name from a METADATA file at `path`.
+func (ni *NoticeIndex) checkMetadataFile(path string) (string, error) {
+	f, err := ni.rootFS.Open(path)
+	if err != nil {
+		return "", err
+	}
+	name := ""
+	description := ""
+	version := ""
+	s := bufio.NewScanner(f)
+	for s.Scan() {
+		line := s.Text()
+		m := nameRegexp.FindStringSubmatch(line)
+		if m != nil {
+			if 1 < len(m) && m[1] != "" {
+				name = m[1]
+			}
+			if version != "" {
+				break
+			}
+			continue
+		}
+		m = versionRegexp.FindStringSubmatch(line)
+		if m != nil {
+			if 1 < len(m) && m[1] != "" {
+				version = m[1]
+			}
+			if name != "" {
+				break
+			}
+			continue
+		}
+		m = descRegexp.FindStringSubmatch(line)
+		if m != nil {
+			if 1 < len(m) && m[1] != "" {
+				description = m[1]
+			}
+		}
+	}
+	_ = s.Err()
+	_ = f.Close()
+	if name != "" {
+		if version != "" {
+			if version[0] == 'v' || version[0] == 'V' {
+				return name + "_" + version, nil
+			} else {
+				return name + "_v_" + version, nil
+			}
+		}
+		return name, nil
+	}
+	if description != "" {
+		return description, nil
+	}
+	return "", nil
+}
+
+// addText reads and indexes the content of a license text file.
+func (ni *NoticeIndex) addText(file string) error {
+	f, err := ni.rootFS.Open(filepath.Clean(file))
+	if err != nil {
+		return fmt.Errorf("error opening license text file %q: %w", file, err)
+	}
+
+	// read the file
+	text, err := io.ReadAll(f)
+	if err != nil {
+		return fmt.Errorf("error reading license text file %q: %w", file, err)
+	}
+
+	hash := hash{fmt.Sprintf("%x", md5.Sum(text))}
+	ni.hash[file] = hash
+	if _, alreadyPresent := ni.text[hash]; !alreadyPresent {
+		ni.text[hash] = text
+	}
+
+	ni.files = append(ni.files, file)
+
+	return nil
+}
+
+// getInstallPaths returns the names of the used dependencies mapped to their
+// installed locations.
+func getInstallPaths(attachesTo *TargetNode, path TargetEdgePath) []string {
+	if len(path) == 0 {
+		installs := attachesTo.Installed()
+		if 0 == len(installs) {
+			installs = attachesTo.Built()
+		}
+		return installs
+	}
+
+	var getInstalls func(path TargetEdgePath) []string
+
+	getInstalls = func(path TargetEdgePath) []string {
+		// deps contains the output targets from the dependencies in the path
+		var deps []string
+		if len(path) > 1 {
+			// recursively get the targets from the sub-path skipping 1 path segment
+			deps = getInstalls(path[1:])
+		} else {
+			// stop recursion at 1 path segment
+			deps = path[0].Dependency().TargetFiles()
+		}
+		size := 0
+		prefixes := path[0].Target().TargetFiles()
+		installMap := path[0].Target().InstallMap()
+		sources := path[0].Target().Sources()
+		for _, dep := range deps {
+			found := false
+			for _, source := range sources {
+				if strings.HasPrefix(dep, source) {
+					found = true
+					break
+				}
+			}
+			if !found {
+				continue
+			}
+			for _, im := range installMap {
+				if strings.HasPrefix(dep, im.FromPath) {
+					size += len(prefixes)
+					break
+				}
+			}
+		}
+
+		installs := make([]string, 0, size)
+		for _, dep := range deps {
+			found := false
+			for _, source := range sources {
+				if strings.HasPrefix(dep, source) {
+					found = true
+					break
+				}
+			}
+			if !found {
+				continue
+			}
+			for _, im := range installMap {
+				if strings.HasPrefix(dep, im.FromPath) {
+					for _, prefix := range prefixes {
+						installs = append(installs, prefix+im.ContainerPath+dep[len(im.FromPath):])
+					}
+					break
+				}
+			}
+		}
+		return installs
+	}
+	allInstalls := getInstalls(path)
+	installs := path[0].Target().Installed()
+	if len(installs) == 0 {
+		return allInstalls
+	}
+	result := make([]string, 0, len(allInstalls))
+	for _, install := range allInstalls {
+		for _, prefix := range installs {
+			if strings.HasPrefix(install, prefix) {
+				result = append(result, install)
+			}
+		}
+	}
+	return result
+}
+
+// hash is an opaque string derived from md5sum.
+type hash struct {
+	key string
+}
+
+// String returns the hexadecimal representation of the hash.
+func (h hash) String() string {
+	return h.key
+}
+
+// hashList orders an array of hashes
+type hashList struct {
+	ni          *NoticeIndex
+	libName     string
+	installPath string
+	hashes      *[]hash
+}
+
+// Len returns the count of elements in the slice.
+func (l hashList) Len() int { return len(*l.hashes) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l hashList) Swap(i, j int) { (*l.hashes)[i], (*l.hashes)[j] = (*l.hashes)[j], (*l.hashes)[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l hashList) Less(i, j int) bool {
+	var insti, instj int
+	if len(l.libName) > 0 {
+		insti = len(l.ni.hashLibInstall[(*l.hashes)[i]][l.libName])
+		instj = len(l.ni.hashLibInstall[(*l.hashes)[j]][l.libName])
+	} else {
+		libsi := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[i])
+		libsj := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[j])
+		libsis := strings.Join(libsi, " ")
+		libsjs := strings.Join(libsj, " ")
+		if libsis != libsjs {
+			return libsis < libsjs
+		}
+	}
+	if insti == instj {
+		leni := len(l.ni.text[(*l.hashes)[i]])
+		lenj := len(l.ni.text[(*l.hashes)[j]])
+		if leni == lenj {
+			// all else equal, just order by hash value
+			return (*l.hashes)[i].key < (*l.hashes)[j].key
+		}
+		// put shortest texts first within same # of installs
+		return leni < lenj
+	}
+	// reverse order of # installs so that most popular appears first
+	return instj < insti
+}
diff --git a/tools/compliance/policy_policy.go b/tools/compliance/policy_policy.go
new file mode 100644
index 0000000..60bdf48
--- /dev/null
+++ b/tools/compliance/policy_policy.go
@@ -0,0 +1,289 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"regexp"
+	"strings"
+)
+
+var (
+	// RecognizedAnnotations identifies the set of annotations that have
+	// meaning for compliance policy.
+	RecognizedAnnotations = map[string]string{
+		// used in readgraph.go to avoid creating 1000's of copies of the below 3 strings.
+		"static":    "static",
+		"dynamic":   "dynamic",
+		"toolchain": "toolchain",
+	}
+
+	// SafePathPrefixes maps the path prefixes presumed not to contain any
+	// proprietary or confidential pathnames to whether to strip the prefix
+	// from the path when used as the library name for notices.
+	SafePathPrefixes = map[string]bool{
+		"external/":    true,
+		"art/":         false,
+		"build/":       false,
+		"cts/":         false,
+		"dalvik/":      false,
+		"developers/":  false,
+		"development/": false,
+		"frameworks/":  false,
+		"packages/":    true,
+		"prebuilts/":   false,
+		"sdk/":         false,
+		"system/":      false,
+		"test/":        false,
+		"toolchain/":   false,
+		"tools/":       false,
+	}
+
+	// SafePrebuiltPrefixes maps the regular expression to match a prebuilt
+	// containing the path of a safe prefix to the safe prefix.
+	SafePrebuiltPrefixes = make(map[*regexp.Regexp]string)
+
+	// ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
+	ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition)
+
+	// ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
+	ImpliesPermissive = LicenseConditionSet(PermissiveCondition)
+
+	// ImpliesNotice lists the condition names implying a notice or attribution policy.
+	ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition |
+		RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition |
+		ProprietaryCondition | ByExceptionOnlyCondition)
+
+	// ImpliesReciprocal lists the condition names implying a local source-sharing policy.
+	ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition)
+
+	// Restricted lists the condition names implying an infectious source-sharing policy.
+	ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
+
+	// ImpliesProprietary lists the condition names implying a confidentiality policy.
+	ImpliesProprietary = LicenseConditionSet(ProprietaryCondition)
+
+	// ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
+	ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition)
+
+	// ImpliesPrivate lists the condition names implying a source-code privacy policy.
+	ImpliesPrivate = LicenseConditionSet(ProprietaryCondition)
+
+	// ImpliesShared lists the condition names implying a source-code sharing policy.
+	ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
+)
+
+var (
+	anyLgpl      = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
+	versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
+	genericGpl   = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
+	ccBySa       = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
+)
+
+func init() {
+	for prefix := range SafePathPrefixes {
+		if prefix == "prebuilts/" {
+			continue
+		}
+		r := regexp.MustCompile("^prebuilts/[^ ]*/" + prefix)
+		SafePrebuiltPrefixes[r] = prefix
+	}
+}
+
+// LicenseConditionSetFromNames returns a set containing the recognized `names` and
+// silently ignoring or discarding the unrecognized `names`.
+func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
+	cs := NewLicenseConditionSet()
+	for _, name := range names {
+		if name == "restricted" {
+			if 0 == len(tn.LicenseKinds()) {
+				cs = cs.Plus(RestrictedCondition)
+				continue
+			}
+			hasLgpl := false
+			hasClasspath := false
+			hasGeneric := false
+			for _, kind := range tn.LicenseKinds() {
+				if strings.HasSuffix(kind, "-with-classpath-exception") {
+					cs = cs.Plus(RestrictedClasspathExceptionCondition)
+					hasClasspath = true
+				} else if anyLgpl.MatchString(kind) {
+					cs = cs.Plus(WeaklyRestrictedCondition)
+					hasLgpl = true
+				} else if versionedGpl.MatchString(kind) {
+					cs = cs.Plus(RestrictedCondition)
+				} else if genericGpl.MatchString(kind) {
+					hasGeneric = true
+				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
+					cs = cs.Plus(RestrictedCondition)
+				} else {
+					cs = cs.Plus(RestrictedCondition)
+				}
+			}
+			if hasGeneric && !hasLgpl && !hasClasspath {
+				cs = cs.Plus(RestrictedCondition)
+			}
+			continue
+		}
+		if lc, ok := RecognizedConditionNames[name]; ok {
+			cs |= LicenseConditionSet(lc)
+		}
+	}
+	return cs
+}
+
+// Resolution happens in three phases:
+//
+// 1. A bottom-up traversal propagates (restricted) license conditions up to
+// targets from dendencies as needed.
+//
+// 2. For each condition of interest, a top-down traversal propagates
+// (restricted) conditions down from targets into linked dependencies.
+//
+// 3. Finally, a walk of the shipped target nodes attaches resolutions to the
+// ancestor nodes from the root down to and including the first non-container.
+//
+// e.g. If a disk image contains a binary bin1 that links a library liba, the
+// notice requirement for liba gets attached to the disk image and to bin1.
+// Because liba doesn't actually get shipped as a separate artifact, but only
+// as bits in bin1, it has no actions 'attached' to it. The actions attached
+// to the image and to bin1 'act on' liba by providing notice.
+//
+// The behavior of the 3 phases gets controlled by the 3 functions below.
+//
+// The first function controls what happens during the bottom-up propagation.
+// Restricted conditions propagate up all non-toolchain dependencies; except,
+// some do not propagate up dynamic links, which may depend on whether the
+// modules are independent.
+//
+// The second function controls what happens during the top-down propagation.
+// Restricted conditions propagate down as above with the added caveat that
+// inherited restricted conditions do not propagate from pure aggregates to
+// their dependencies.
+//
+// The final function controls which conditions apply/get attached to ancestors
+// depending on the types of dependencies involved. All conditions apply across
+// normal derivation dependencies. No conditions apply across toolchain
+// dependencies. Some restricted conditions apply across dynamic link
+// dependencies.
+//
+// Not all restricted licenses are create equal. Some have special rules or
+// exceptions. e.g. LGPL or "with classpath excption".
+
+// depConditionsPropagatingToTarget returns the conditions which propagate up an
+// edge from dependency to target.
+//
+// This function sets the policy for the bottom-up propagation and how conditions
+// flow up the graph from dependencies to targets.
+//
+// If a pure aggregation is built into a derivative work that is not a pure
+// aggregation, per policy it ceases to be a pure aggregation in the context of
+// that derivative work. The `treatAsAggregate` parameter will be false for
+// non-aggregates and for aggregates in non-aggregate contexts.
+func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
+	result := LicenseConditionSet(0x0000)
+	if edgeIsDerivation(e) {
+		result |= depConditions & ImpliesRestricted
+		return result
+	}
+	if !edgeIsDynamicLink(e) {
+		return result
+	}
+
+	result |= depConditions & LicenseConditionSet(RestrictedCondition)
+	if 0 != (depConditions&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) {
+		result |= LicenseConditionSet(RestrictedClasspathExceptionCondition)
+	}
+	return result
+}
+
+// targetConditionsPropagatingToDep returns the conditions which propagate down
+// an edge from target to dependency.
+//
+// This function sets the policy for the top-down traversal and how conditions
+// flow down the graph from targets to dependencies.
+//
+// If a pure aggregation is built into a derivative work that is not a pure
+// aggregation, per policy it ceases to be a pure aggregation in the context of
+// that derivative work. The `treatAsAggregate` parameter will be false for
+// non-aggregates and for aggregates in non-aggregate contexts.
+func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet {
+	result := targetConditions
+
+	// reverse direction -- none of these apply to things depended-on, only to targets depending-on.
+	result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition)
+
+	if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
+		// target is not a derivative work of dependency and is not linked to dependency
+		result = result.Difference(ImpliesRestricted)
+		return result
+	}
+	if treatAsAggregate {
+		// If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
+		// Otherwise, restricted does not propagate back down to dependencies.
+		if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) {
+			result = result.Difference(ImpliesRestricted)
+		}
+		return result
+	}
+	if edgeIsDerivation(e) {
+		return result
+	}
+	result = result.Minus(WeaklyRestrictedCondition)
+	if edgeNodesAreIndependentModules(e) {
+		result = result.Minus(RestrictedClasspathExceptionCondition)
+	}
+	return result
+}
+
+// conditionsAttachingAcrossEdge returns the subset of conditions in `universe`
+// that apply across edge `e`.
+//
+// This function sets the policy for attaching actions to ancestor nodes in the
+// final resolution walk.
+func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet {
+	result := universe
+	if edgeIsDerivation(e) {
+		return result
+	}
+	if !edgeIsDynamicLink(e) {
+		return NewLicenseConditionSet()
+	}
+
+	result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition)
+	if 0 != (result&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) {
+		result &= LicenseConditionSet(RestrictedCondition)
+	}
+	return result
+}
+
+// edgeIsDynamicLink returns true for edges representing shared libraries
+// linked dynamically at runtime.
+func edgeIsDynamicLink(e *TargetEdge) bool {
+	return e.annotations.HasAnnotation("dynamic")
+}
+
+// edgeIsDerivation returns true for edges where the target is a derivative
+// work of dependency.
+func edgeIsDerivation(e *TargetEdge) bool {
+	isDynamic := e.annotations.HasAnnotation("dynamic")
+	isToolchain := e.annotations.HasAnnotation("toolchain")
+	return !isDynamic && !isToolchain
+}
+
+// edgeNodesAreIndependentModules returns true for edges where the target and
+// dependency are independent modules.
+func edgeNodesAreIndependentModules(e *TargetEdge) bool {
+	return e.target.PackageName() != e.dependency.PackageName()
+}
diff --git a/tools/compliance/policy_policy_test.go b/tools/compliance/policy_policy_test.go
new file mode 100644
index 0000000..27ce16c
--- /dev/null
+++ b/tools/compliance/policy_policy_test.go
@@ -0,0 +1,309 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestPolicy_edgeConditions(t *testing.T) {
+	tests := []struct {
+		name                     string
+		edge                     annotated
+		treatAsAggregate         bool
+		otherCondition           string
+		expectedDepActions       []string
+		expectedTargetConditions []string
+	}{
+		{
+			name:                     "firstparty",
+			edge:                     annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "notice",
+			edge:                     annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fponlgpl",
+			edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
+				"lgplLib.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "fponlgpldynamic",
+			edge:                     annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fpongpl",
+			edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fpongpldynamic",
+			edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "independentmodule",
+			edge:                     annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "independentmodulestatic",
+			edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "dependentmodule",
+			edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			expectedDepActions: []string{
+				"dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+			},
+			expectedTargetConditions: []string{},
+		},
+
+		{
+			name:                     "lgplonfp",
+			edge:                     annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"lgplBin.meta_lic:restricted_allows_dynamic_linking"},
+		},
+		{
+			name:                     "lgplonfpdynamic",
+			edge:                     annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "gplonfp",
+			edge:                     annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "gplcontainer",
+			edge:                     annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate:         true,
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplContainer.meta_lic:restricted"},
+		},
+		{
+			name:             "gploncontainer",
+			edge:             annotated{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate: true,
+			otherCondition:   "gplLib.meta_lic:restricted",
+			expectedDepActions: []string{
+				"apacheContainer.meta_lic:gplLib.meta_lic:restricted",
+				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:             "gplonbin",
+			edge:             annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate: false,
+			otherCondition:   "gplLib.meta_lic:restricted",
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{"gplLib.meta_lic:restricted"},
+		},
+		{
+			name:                     "gplonfpdynamic",
+			edge:                     annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "independentmodulereverse",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "independentmodulereversestatic",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
+		},
+		{
+			name:                     "dependentmodulereverse",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
+		},
+		{
+			name: "ponr",
+			edge: annotated{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"proprietary.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "ronp",
+			edge:                     annotated{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "noticeonb_e_o",
+			edge:                     annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "b_e_oonnotice",
+			edge:                     annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "noticeonrecip",
+			edge:                     annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "reciponnotice",
+			edge:                     annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			fs := make(testFS)
+			stderr := &bytes.Buffer{}
+			target := meta[tt.edge.target] + fmt.Sprintf("deps: {\n  file: \"%s\"\n", tt.edge.dep)
+			for _, ann := range tt.edge.annotations {
+				target += fmt.Sprintf("  annotations: \"%s\"\n", ann)
+			}
+			fs[tt.edge.target] = []byte(target + "}\n")
+			fs[tt.edge.dep] = []byte(meta[tt.edge.dep])
+			lg, err := ReadLicenseGraph(&fs, stderr, []string{tt.edge.target})
+			if err != nil {
+				t.Errorf("unexpected error reading graph: %s", err)
+				return
+			}
+			edge := lg.Edges()[0]
+			// simulate a condition inherited from another edge/dependency.
+			otherTarget := ""
+			otherCondition := ""
+			var otn *TargetNode
+			if len(tt.otherCondition) > 0 {
+				fields := strings.Split(tt.otherCondition, ":")
+				otherTarget = fields[0]
+				otherCondition = fields[1]
+				otn = &TargetNode{name: otherTarget}
+				// other target must exist in graph
+				lg.targets[otherTarget] = otn
+				otn.licenseConditions = LicenseConditionSet(RecognizedConditionNames[otherCondition])
+			}
+			targets := make(map[string]*TargetNode)
+			targets[edge.target.name] = edge.target
+			targets[edge.dependency.name] = edge.dependency
+			if otn != nil {
+				targets[otn.name] = otn
+			}
+			if tt.expectedDepActions != nil {
+				t.Run("depConditionsPropagatingToTarget", func(t *testing.T) {
+					depConditions := edge.dependency.LicenseConditions()
+					if otherTarget != "" {
+						// simulate a sub-dependency's condition having already propagated up to dep and about to go to target
+						otherCs := otn.LicenseConditions()
+						depConditions |= otherCs
+					}
+					t.Logf("calculate target actions for edge=%s, dep conditions=%04x, treatAsAggregate=%v", edge.String(), depConditions, tt.treatAsAggregate)
+					csActual := depConditionsPropagatingToTarget(lg, edge, depConditions, tt.treatAsAggregate)
+					t.Logf("calculated target conditions as %04x{%s}", csActual, strings.Join(csActual.Names(), ", "))
+					csExpected := NewLicenseConditionSet()
+					for _, triple := range tt.expectedDepActions {
+						fields := strings.Split(triple, ":")
+						expectedConditions := NewLicenseConditionSet()
+						for _, cname := range fields[2:] {
+							expectedConditions = expectedConditions.Plus(RecognizedConditionNames[cname])
+						}
+						csExpected |= expectedConditions
+					}
+					t.Logf("expected target conditions as %04x{%s}", csExpected, strings.Join(csExpected.Names(), ", "))
+					if csActual != csExpected {
+						t.Errorf("unexpected license conditions: got %04x, want %04x", csActual, csExpected)
+					}
+				})
+			}
+			if tt.expectedTargetConditions != nil {
+				t.Run("targetConditionsPropagatingToDep", func(t *testing.T) {
+					targetConditions := edge.target.LicenseConditions()
+					if otherTarget != "" {
+						targetConditions = targetConditions.Union(otn.licenseConditions)
+					}
+					t.Logf("calculate dep conditions for edge=%s, target conditions=%v, treatAsAggregate=%v", edge.String(), targetConditions.Names(), tt.treatAsAggregate)
+					cs := targetConditionsPropagatingToDep(lg, edge, targetConditions, tt.treatAsAggregate, AllResolutions)
+					t.Logf("calculated dep conditions as %v", cs.Names())
+					actual := cs.Names()
+					sort.Strings(actual)
+					expected := make([]string, 0)
+					for _, expectedDepCondition := range tt.expectedTargetConditions {
+						expected = append(expected, strings.Split(expectedDepCondition, ":")[1])
+					}
+					sort.Strings(expected)
+					if len(actual) != len(expected) {
+						t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions",
+							actual, len(actual), expected, len(expected))
+					} else {
+						for i := 0; i < len(actual); i++ {
+							if actual[i] != expected[i] {
+								t.Errorf("unexpected target condition at element %d: got %q, want %q",
+									i, actual[i], expected[i])
+							}
+						}
+					}
+				})
+			}
+		})
+	}
+}
diff --git a/tools/compliance/policy_resolve.go b/tools/compliance/policy_resolve.go
new file mode 100644
index 0000000..d357aec
--- /dev/null
+++ b/tools/compliance/policy_resolve.go
@@ -0,0 +1,234 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"sync"
+)
+
+var (
+	// AllResolutions is a TraceConditions function that resolves all
+	// unfiltered license conditions.
+	AllResolutions = TraceConditions(func(tn *TargetNode) LicenseConditionSet { return tn.licenseConditions })
+)
+
+// TraceConditions is a function that returns the conditions to trace for each
+// target node `tn`.
+type TraceConditions func(tn *TargetNode) LicenseConditionSet
+
+// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
+// propagating conditions up the graph as necessary according to the properties
+// of each edge and according to each license condition in question.
+//
+// e.g. if a "restricted" condition applies to a binary, it also applies to all
+// of the statically-linked libraries and the transitive closure of their static
+// dependencies; even if neither they nor the transitive closure of their
+// dependencies originate any "restricted" conditions. The bottom-up walk will
+// not resolve the library and its transitive closure, but the later top-down
+// walk will.
+func ResolveBottomUpConditions(lg *LicenseGraph) {
+	TraceBottomUpConditions(lg, AllResolutions)
+}
+
+// TraceBottomUpConditions performs a bottom-up walk of the LicenseGraph
+// propagating trace conditions from `conditionsFn` up the graph as necessary
+// according to the properties of each edge and according to each license
+// condition in question.
+func TraceBottomUpConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
+
+	// short-cut if already walked and cached
+	lg.mu.Lock()
+	wg := lg.wgBU
+
+	if wg != nil {
+		lg.mu.Unlock()
+		wg.Wait()
+		return
+	}
+	wg = &sync.WaitGroup{}
+	wg.Add(1)
+	lg.wgBU = wg
+	lg.mu.Unlock()
+
+	// amap identifes targets previously walked. (guarded by mu)
+	amap := make(map[*TargetNode]struct{})
+
+	// cmap identifies targets previously walked as pure aggregates. i.e. as containers
+	// (guarded by mu)
+	cmap := make(map[*TargetNode]struct{})
+	var mu sync.Mutex
+
+	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
+
+	walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
+		priorWalkResults := func() (LicenseConditionSet, bool) {
+			mu.Lock()
+			defer mu.Unlock()
+
+			if _, alreadyWalked := amap[target]; alreadyWalked {
+				if treatAsAggregate {
+					return target.resolution, true
+				}
+				if _, asAggregate := cmap[target]; !asAggregate {
+					return target.resolution, true
+				}
+				// previously walked in a pure aggregate context,
+				// needs to walk again in non-aggregate context
+				delete(cmap, target)
+			} else {
+				target.resolution |= conditionsFn(target)
+				amap[target] = struct{}{}
+			}
+			if treatAsAggregate {
+				cmap[target] = struct{}{}
+			}
+			return target.resolution, false
+		}
+		cs, alreadyWalked := priorWalkResults()
+		if alreadyWalked {
+			return cs
+		}
+
+		c := make(chan LicenseConditionSet, len(target.edges))
+		// add all the conditions from all the dependencies
+		for _, edge := range target.edges {
+			go func(edge *TargetEdge) {
+				// walk dependency to get its conditions
+				cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
+
+				// turn those into the conditions that apply to the target
+				cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
+
+				c <- cs
+			}(edge)
+		}
+		for i := 0; i < len(target.edges); i++ {
+			cs |= <-c
+		}
+		mu.Lock()
+		target.resolution |= cs
+		mu.Unlock()
+
+		// return conditions up the tree
+		return cs
+	}
+
+	// walk each of the roots
+	for _, rname := range lg.rootFiles {
+		rnode := lg.targets[rname]
+		_ = walk(rnode, rnode.IsContainer())
+	}
+
+	wg.Done()
+}
+
+// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
+// propagating conditions from target to dependency.
+//
+// e.g. For current policy, none of the conditions propagate from target to
+// dependency except restricted. For restricted, the policy is to share the
+// source of any libraries linked to restricted code and to provide notice.
+func ResolveTopDownConditions(lg *LicenseGraph) {
+	TraceTopDownConditions(lg, AllResolutions)
+}
+
+// TraceTopDownCondtions performs a top-down walk of the LicenseGraph
+// propagating trace conditions returned by `conditionsFn` from target to
+// dependency.
+func TraceTopDownConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
+
+	// short-cut if already walked and cached
+	lg.mu.Lock()
+	wg := lg.wgTD
+
+	if wg != nil {
+		lg.mu.Unlock()
+		wg.Wait()
+		return
+	}
+	wg = &sync.WaitGroup{}
+	wg.Add(1)
+	lg.wgTD = wg
+	lg.mu.Unlock()
+
+	// start with the conditions propagated up the graph
+	TraceBottomUpConditions(lg, conditionsFn)
+
+	// amap contains the set of targets already walked. (guarded by mu)
+	amap := make(map[*TargetNode]struct{})
+
+	// cmap contains the set of targets walked as pure aggregates. i.e. containers
+	// (guarded by mu)
+	cmap := make(map[*TargetNode]struct{})
+
+	// mu guards concurrent access to cmap
+	var mu sync.Mutex
+
+	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
+
+	walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
+		defer wg.Done()
+		mu.Lock()
+		fnode.resolution |= conditionsFn(fnode)
+		fnode.resolution |= cs
+		amap[fnode] = struct{}{}
+		if treatAsAggregate {
+			cmap[fnode] = struct{}{}
+		}
+		cs = fnode.resolution
+		mu.Unlock()
+		// for each dependency
+		for _, edge := range fnode.edges {
+			func(edge *TargetEdge) {
+				// dcs holds the dpendency conditions inherited from the target
+				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
+				dnode := edge.dependency
+				mu.Lock()
+				defer mu.Unlock()
+				depcs := dnode.resolution
+				_, alreadyWalked := amap[dnode]
+				if !dcs.IsEmpty() && alreadyWalked {
+					if dcs.Difference(depcs).IsEmpty() {
+						// no new conditions
+
+						// pure aggregates never need walking a 2nd time with same conditions
+						if treatAsAggregate {
+							return
+						}
+						// non-aggregates don't need walking as non-aggregate a 2nd time
+						if _, asAggregate := cmap[dnode]; !asAggregate {
+							return
+						}
+						// previously walked as pure aggregate; need to re-walk as non-aggregate
+						delete(cmap, dnode)
+					}
+				}
+				// add the conditions to the dependency
+				wg.Add(1)
+				go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
+			}(edge)
+		}
+	}
+
+	// walk each of the roots
+	for _, rname := range lg.rootFiles {
+		rnode := lg.targets[rname]
+		wg.Add(1)
+		// add the conditions to the root and its transitive closure
+		go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
+	}
+	wg.Done()
+	wg.Wait()
+}
diff --git a/tools/compliance/policy_resolve_test.go b/tools/compliance/policy_resolve_test.go
new file mode 100644
index 0000000..f98e4cc
--- /dev/null
+++ b/tools/compliance/policy_resolve_test.go
@@ -0,0 +1,672 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"sort"
+	"testing"
+)
+
+func TestResolveBottomUpConditions(t *testing.T) {
+	tests := []struct {
+		name            string
+		roots           []string
+		edges           []annotated
+		expectedActions []tcond
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartytool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartywide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+
+			logGraph(lg, t)
+
+			ResolveBottomUpConditions(lg)
+			actual := asActionList(lg)
+			sort.Sort(actual)
+			t.Logf("actual: %s", actual.String())
+
+			expected := toActionList(lg, tt.expectedActions)
+			sort.Sort(expected)
+			t.Logf("expected: %s", expected.String())
+
+			if len(actual) != len(expected) {
+				t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+				return
+			}
+			for i := 0; i < len(actual); i++ {
+				if actual[i] != expected[i] {
+					t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+				}
+			}
+		})
+	}
+}
+
+func TestResolveTopDownConditions(t *testing.T) {
+	tests := []struct {
+		name            string
+		roots           []string
+		edges           []annotated
+		expectedActions []tcond
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitLib.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"mitLib.meta_lic", "notice"},
+				{"gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mplLib.meta_lic", "reciprocal|restricted"},
+				{"mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "notice|restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice|restricted"},
+				{"mitBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+				{"mplLib.meta_lic", "reciprocal|restricted"},
+				{"mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted"},
+				{"apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []tcond{
+				{"apacheContainer.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []tcond{
+				{"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+				{"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+				{"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+
+			logGraph(lg, t)
+
+			ResolveTopDownConditions(lg)
+			actual := asActionList(lg)
+			sort.Sort(actual)
+			t.Logf("actual: %s", actual.String())
+
+			expected := toActionList(lg, tt.expectedActions)
+			sort.Sort(expected)
+			t.Logf("expected: %s", expected.String())
+
+			if len(actual) != len(expected) {
+				t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+				return
+			}
+			for i := 0; i < len(actual); i++ {
+				if actual[i] != expected[i] {
+					t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/policy_resolvenotices.go b/tools/compliance/policy_resolvenotices.go
new file mode 100644
index 0000000..99f6d42
--- /dev/null
+++ b/tools/compliance/policy_resolvenotices.go
@@ -0,0 +1,21 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+// ResolveNotices implements the policy for notices.
+func ResolveNotices(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesNotice)
+}
diff --git a/tools/compliance/policy_resolvenotices_test.go b/tools/compliance/policy_resolvenotices_test.go
new file mode 100644
index 0000000..cd9dd71
--- /dev/null
+++ b/tools/compliance/policy_resolvenotices_test.go
@@ -0,0 +1,468 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveNotices(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "mitLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicwideshipped",
+			roots: []string{"apacheContainer.meta_lic", "gplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestrictedtoolshipped",
+			roots: []string{"apacheBin.meta_lic", "lgplBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeepshipped",
+			roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwideshipped",
+			roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamicshipped",
+			roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveNotices(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy_resolveprivacy.go b/tools/compliance/policy_resolveprivacy.go
new file mode 100644
index 0000000..2a7992e
--- /dev/null
+++ b/tools/compliance/policy_resolveprivacy.go
@@ -0,0 +1,21 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+// ResolveSourcePrivacy implements the policy for source privacy.
+func ResolveSourcePrivacy(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesPrivate)
+}
diff --git a/tools/compliance/policy_resolveprivacy_test.go b/tools/compliance/policy_resolveprivacy_test.go
new file mode 100644
index 0000000..e8c953a
--- /dev/null
+++ b/tools/compliance/policy_resolveprivacy_test.go
@@ -0,0 +1,87 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveSourcePrivacy(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "notice",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "lgpl",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "proprietaryonresricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:  "restrictedonproprietary",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveSourcePrivacy(lg)
+			checkResolves(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy_resolveshare.go b/tools/compliance/policy_resolveshare.go
new file mode 100644
index 0000000..9b6a8bb
--- /dev/null
+++ b/tools/compliance/policy_resolveshare.go
@@ -0,0 +1,21 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+// ResolveSourceSharing implements the policy for source-sharing.
+func ResolveSourceSharing(lg *LicenseGraph) ResolutionSet {
+	ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, ImpliesShared)
+}
diff --git a/tools/compliance/policy_resolveshare_test.go b/tools/compliance/policy_resolveshare_test.go
new file mode 100644
index 0000000..c451b86
--- /dev/null
+++ b/tools/compliance/policy_resolveshare_test.go
@@ -0,0 +1,297 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveSourceSharing(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "independentmodulerestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "independentmodulerestrictedshipped",
+			roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulestaticrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulerestricted",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulerestrictedshipclasspath",
+			roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfprestricted",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfpdynamicrestricted",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfpdynamicrestrictedshiplib",
+			roots: []string{"lgplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfprestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplcontainerrestricted",
+			roots: []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gploncontainerrestricted",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonbinrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfpdynamicrestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfpdynamicrestrictedshiplib",
+			roots: []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulereverserestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulereversestaticrestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulereverserestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulereverserestrictedshipdependent",
+			roots: []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "ponrrestricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "ronprestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "noticeonb_e_orestricted",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "b_e_oonnoticerestricted",
+			roots: []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "noticeonreciprecip",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:  "reciponnoticerecip",
+			roots: []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveSourceSharing(lg)
+			checkResolves(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy_shareprivacyconflicts.go b/tools/compliance/policy_shareprivacyconflicts.go
new file mode 100644
index 0000000..279e179
--- /dev/null
+++ b/tools/compliance/policy_shareprivacyconflicts.go
@@ -0,0 +1,71 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+)
+
+// SourceSharePrivacyConflict describes an individual conflict between a source-sharing
+// condition and a source privacy condition
+type SourceSharePrivacyConflict struct {
+	SourceNode       *TargetNode
+	ShareCondition   LicenseCondition
+	PrivacyCondition LicenseCondition
+}
+
+// Error returns a string describing the conflict.
+func (conflict SourceSharePrivacyConflict) Error() string {
+	return fmt.Sprintf("%s %s and must share from %s condition\n", conflict.SourceNode.name,
+		conflict.PrivacyCondition.Name(), conflict.ShareCondition.Name())
+}
+
+// IsEqualTo returns true when `conflict` and `other` describe the same conflict.
+func (conflict SourceSharePrivacyConflict) IsEqualTo(other SourceSharePrivacyConflict) bool {
+	return conflict.SourceNode.name == other.SourceNode.name &&
+		conflict.ShareCondition == other.ShareCondition &&
+		conflict.PrivacyCondition == other.PrivacyCondition
+}
+
+// ConflictingSharedPrivateSource lists all of the targets where conflicting conditions to
+// share the source and to keep the source private apply to the target.
+func ConflictingSharedPrivateSource(lg *LicenseGraph) []SourceSharePrivacyConflict {
+
+	ResolveTopDownConditions(lg)
+	// combined is the combination of source-sharing and source privacy.
+	combined := WalkActionsForCondition(lg, ImpliesShared.Union(ImpliesPrivate))
+
+	// size is the size of the result
+	size := 0
+	for _, cs := range combined {
+		size += cs.Intersection(ImpliesShared).Len() * cs.Intersection(ImpliesPrivate).Len()
+	}
+	if size == 0 {
+		return nil
+	}
+	result := make([]SourceSharePrivacyConflict, 0, size)
+	for actsOn, cs := range combined {
+		pconditions := cs.Intersection(ImpliesPrivate).AsList()
+		ssconditions := cs.Intersection(ImpliesShared).AsList()
+
+		// report all conflicting condition combinations
+		for _, p := range pconditions {
+			for _, ss := range ssconditions {
+				result = append(result, SourceSharePrivacyConflict{actsOn, ss, p})
+			}
+		}
+	}
+	return result
+}
diff --git a/tools/compliance/policy_shareprivacyconflicts_test.go b/tools/compliance/policy_shareprivacyconflicts_test.go
new file mode 100644
index 0000000..069daa2
--- /dev/null
+++ b/tools/compliance/policy_shareprivacyconflicts_test.go
@@ -0,0 +1,123 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"sort"
+	"testing"
+)
+
+// byConflict orders conflicts by target then share then privacy
+type byConflict []SourceSharePrivacyConflict
+
+// Len returns the count of elements in the slice.
+func (l byConflict) Len() int { return len(l) }
+
+// Swap rearranged 2 elements so that each occupies the other's former
+// position.
+func (l byConflict) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l byConflict) Less(i, j int) bool {
+	if l[i].SourceNode.Name() == l[j].SourceNode.Name() {
+		if l[i].ShareCondition.Name() == l[j].ShareCondition.Name() {
+			return l[i].PrivacyCondition.Name() < l[j].PrivacyCondition.Name()
+		}
+		return l[i].ShareCondition.Name() < l[j].ShareCondition.Name()
+	}
+	return l[i].SourceNode.Name() < l[j].SourceNode.Name()
+}
+
+func TestConflictingSharedPrivateSource(t *testing.T) {
+	tests := []struct {
+		name              string
+		roots             []string
+		edges             []annotated
+		expectedConflicts []confl
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "notice",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "lgpl",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "proprietaryonrestricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{
+				{"proprietary.meta_lic", "gplLib.meta_lic:restricted", "proprietary.meta_lic:proprietary"},
+			},
+		},
+		{
+			name:  "restrictedonproprietary",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{
+				{"proprietary.meta_lic", "gplBin.meta_lic:restricted", "proprietary.meta_lic:proprietary"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedConflicts := toConflictList(lg, tt.expectedConflicts)
+			actualConflicts := ConflictingSharedPrivateSource(lg)
+			sort.Sort(byConflict(expectedConflicts))
+			sort.Sort(byConflict(actualConflicts))
+			if len(expectedConflicts) != len(actualConflicts) {
+				t.Errorf("unexpected number of share/privacy conflicts: got %v with %d conflicts, want %v with %d conflicts",
+					actualConflicts, len(actualConflicts), expectedConflicts, len(expectedConflicts))
+			} else {
+				for i := 0; i < len(actualConflicts); i++ {
+					if !actualConflicts[i].IsEqualTo(expectedConflicts[i]) {
+						t.Errorf("unexpected share/privacy conflict at element %d: got %q, want %q",
+							i, actualConflicts[i].Error(), expectedConflicts[i].Error())
+					}
+				}
+			}
+
+		})
+	}
+}
diff --git a/tools/compliance/policy_shipped.go b/tools/compliance/policy_shipped.go
new file mode 100644
index 0000000..75c8399
--- /dev/null
+++ b/tools/compliance/policy_shipped.go
@@ -0,0 +1,54 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+// ShippedNodes returns the set of nodes in a license graph where the target or
+// a derivative work gets distributed. (caches result)
+func ShippedNodes(lg *LicenseGraph) *TargetNodeSet {
+	lg.mu.Lock()
+	shipped := lg.shippedNodes
+	lg.mu.Unlock()
+	if shipped != nil {
+		return shipped
+	}
+
+	tset := make(map[*TargetNode]struct{})
+
+	WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		if _, alreadyWalked := tset[tn]; alreadyWalked {
+			return false
+		}
+		if len(path) > 0 {
+			if !edgeIsDerivation(path[len(path)-1].edge) {
+				return false
+			}
+		}
+		tset[tn] = struct{}{}
+		return true
+	})
+
+	shipped = &TargetNodeSet{tset}
+
+	lg.mu.Lock()
+	if lg.shippedNodes == nil {
+		lg.shippedNodes = shipped
+	} else {
+		// if we end up with 2, release the later for garbage collection.
+		shipped = lg.shippedNodes
+	}
+	lg.mu.Unlock()
+
+	return shipped
+}
diff --git a/tools/compliance/policy_shipped_test.go b/tools/compliance/policy_shipped_test.go
new file mode 100644
index 0000000..3ae9b46
--- /dev/null
+++ b/tools/compliance/policy_shipped_test.go
@@ -0,0 +1,143 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestShippedNodes(t *testing.T) {
+	tests := []struct {
+		name          string
+		roots         []string
+		edges         []annotated
+		expectedNodes []string
+	}{
+		{
+			name:          "singleton",
+			roots:         []string{"apacheLib.meta_lic"},
+			edges:         []annotated{},
+			expectedNodes: []string{"apacheLib.meta_lic"},
+		},
+		{
+			name:  "simplebinary",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"},
+		},
+		{
+			name:  "simpledynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{"apacheBin.meta_lic"},
+		},
+		{
+			name:  "container",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{
+				"apacheContainer.meta_lic",
+				"apacheLib.meta_lic",
+				"gplLib.meta_lic",
+			},
+		},
+		{
+			name:  "binary",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+				"gplLib.meta_lic",
+			},
+		},
+		{
+			name:  "binarydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+			},
+		},
+		{
+			name:  "containerdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{
+				"apacheContainer.meta_lic",
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			t.Logf("graph:")
+			for _, edge := range lg.Edges() {
+				t.Logf("  %s", edge.String())
+			}
+			expectedNodes := append([]string{}, tt.expectedNodes...)
+			nodeset := ShippedNodes(lg)
+			t.Logf("shipped node set: %s", nodeset.String())
+
+			actualNodes := nodeset.Names()
+			t.Logf("shipped nodes: [%s]", strings.Join(actualNodes, ", "))
+
+			sort.Strings(expectedNodes)
+			sort.Strings(actualNodes)
+
+			t.Logf("sorted nodes: [%s]", strings.Join(actualNodes, ", "))
+			t.Logf("expected nodes: [%s]", strings.Join(expectedNodes, ", "))
+			if len(expectedNodes) != len(actualNodes) {
+				t.Errorf("unexpected number of shipped nodes: %d nodes, want %d nodes",
+					len(actualNodes), len(expectedNodes))
+				return
+			}
+			for i := 0; i < len(actualNodes); i++ {
+				if expectedNodes[i] != actualNodes[i] {
+					t.Errorf("unexpected node at index %d: got %q, want %q",
+						i, actualNodes[i], expectedNodes[i])
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/policy_walk.go b/tools/compliance/policy_walk.go
new file mode 100644
index 0000000..f4d7bba
--- /dev/null
+++ b/tools/compliance/policy_walk.go
@@ -0,0 +1,238 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+// EdgeContextProvider is an interface for injecting edge-specific context
+// into walk paths.
+type EdgeContextProvider interface {
+	// Context returns the context for `edge` when added to `path`.
+	Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{}
+}
+
+// NoEdgeContext implements EdgeContextProvider for walks that use no context.
+type NoEdgeContext struct{}
+
+// Context returns nil.
+func (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+	return nil
+}
+
+// ApplicableConditionsContext provides the subset of conditions in `universe`
+// that apply to each edge in a path.
+type ApplicableConditionsContext struct {
+	universe LicenseConditionSet
+}
+
+// Context returns the LicenseConditionSet applicable to the edge.
+func (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+	universe := ctx.universe
+	if len(path) > 0 {
+		universe = path[len(path)-1].ctx.(LicenseConditionSet)
+	}
+	return conditionsAttachingAcrossEdge(lg, edge, universe)
+}
+
+// VisitNode is called for each root and for each walked dependency node by
+// WalkTopDown. When VisitNode returns true, WalkTopDown will proceed to walk
+// down the dependences of the node
+type VisitNode func(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool
+
+// WalkTopDown does a top-down walk of `lg` calling `visit` and descending
+// into depenencies when `visit` returns true.
+func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
+	path := NewTargetEdgePath(32)
+
+	var walk func(fnode *TargetNode)
+	walk = func(fnode *TargetNode) {
+		visitChildren := visit(lg, fnode, *path)
+		if !visitChildren {
+			return
+		}
+		for _, edge := range fnode.edges {
+			var edgeContext interface{}
+			if ctx == nil {
+				edgeContext = nil
+			} else {
+				edgeContext = ctx.Context(lg, *path, edge)
+			}
+			path.Push(edge, edgeContext)
+			walk(edge.dependency)
+			path.Pop()
+		}
+	}
+
+	for _, r := range lg.rootFiles {
+		path.Clear()
+		walk(lg.targets[r])
+	}
+}
+
+// resolutionKey identifies results from walking a specific target for a
+// specific set of conditions.
+type resolutionKey struct {
+	target *TargetNode
+	cs     LicenseConditionSet
+}
+
+// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for `conditions`.
+func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
+	shipped := ShippedNodes(lg)
+
+	// rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
+	rmap := make(map[resolutionKey]ActionSet)
+
+	// cmap identifies previously walked target/condition pairs.
+	cmap := make(map[resolutionKey]struct{})
+
+	// result accumulates the resolutions to return.
+	result := make(ResolutionSet)
+	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		universe := conditions
+		if len(path) > 0 {
+			universe = path[len(path)-1].ctx.(LicenseConditionSet)
+		}
+
+		if universe.IsEmpty() {
+			return false
+		}
+		key := resolutionKey{tn, universe}
+
+		if _, alreadyWalked := cmap[key]; alreadyWalked {
+			pure := true
+			for _, p := range path {
+				target := p.Target()
+				tkey := resolutionKey{target, universe}
+				if _, ok := rmap[tkey]; !ok {
+					rmap[tkey] = make(ActionSet)
+				}
+				// attach prior walk outcome to ancestor
+				for actsOn, cs := range rmap[key] {
+					rmap[tkey][actsOn] = cs
+				}
+				// if prior walk produced results, copy results
+				// to ancestor.
+				if _, ok := result[tn]; ok && pure {
+					if _, ok := result[target]; !ok {
+						result[target] = make(ActionSet)
+					}
+					for actsOn, cs := range result[tn] {
+						result[target][actsOn] = cs
+					}
+					pure = target.IsContainer()
+				}
+			}
+			// if all ancestors are pure aggregates, attach
+			// matching prior walk conditions to self. Prior walk
+			// will not have done so if any ancestor was not an
+			// aggregate.
+			if pure {
+				match := rmap[key][tn].Intersection(universe)
+				if !match.IsEmpty() {
+					if _, ok := result[tn]; !ok {
+						result[tn] = make(ActionSet)
+					}
+					result[tn][tn] = match
+				}
+			}
+			return false
+		}
+		// no need to walk node or dependencies if not shipped
+		if !shipped.Contains(tn) {
+			return false
+		}
+		if _, ok := rmap[key]; !ok {
+			rmap[key] = make(ActionSet)
+		}
+		// add self to walk outcome
+		rmap[key][tn] = tn.resolution
+		cmap[key] = struct{}{}
+		cs := tn.resolution
+		if !cs.IsEmpty() {
+			cs = cs.Intersection(universe)
+			pure := true
+			for _, p := range path {
+				target := p.Target()
+				tkey := resolutionKey{target, universe}
+				if _, ok := rmap[tkey]; !ok {
+					rmap[tkey] = make(ActionSet)
+				}
+				// copy current node's action into ancestor
+				rmap[tkey][tn] = tn.resolution
+				// conditionally put matching conditions into
+				// result
+				if pure && !cs.IsEmpty() {
+					if _, ok := result[target]; !ok {
+						result[target] = make(ActionSet)
+					}
+					result[target][tn] = cs
+					pure = target.IsContainer()
+				}
+			}
+			// if all ancestors are pure aggregates, attach
+			// matching conditions to self.
+			if pure && !cs.IsEmpty() {
+				if _, ok := result[tn]; !ok {
+					result[tn] = make(ActionSet)
+				}
+				result[tn][tn] = cs
+			}
+		}
+		return true
+	})
+
+	return result
+}
+
+// WalkActionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for `conditions`.
+func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
+	shipped := ShippedNodes(lg)
+
+	// cmap identifies previously walked target/condition pairs.
+	cmap := make(map[resolutionKey]struct{})
+
+	// amap maps 'actsOn' targets to the applicable conditions
+	//
+	// amap is the resulting ActionSet
+	amap := make(ActionSet)
+	WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		universe := conditions
+		if len(path) > 0 {
+			universe = path[len(path)-1].ctx.(LicenseConditionSet)
+		}
+		if universe.IsEmpty() {
+			return false
+		}
+		key := resolutionKey{tn, universe}
+		if _, ok := cmap[key]; ok {
+			return false
+		}
+		if !shipped.Contains(tn) {
+			return false
+		}
+		cs := universe.Intersection(tn.resolution)
+		if !cs.IsEmpty() {
+			if _, ok := amap[tn]; ok {
+				amap[tn] = cs
+			} else {
+				amap[tn] = amap[tn].Union(cs)
+			}
+		}
+		return true
+	})
+
+	return amap
+}
diff --git a/tools/compliance/policy_walk_test.go b/tools/compliance/policy_walk_test.go
new file mode 100644
index 0000000..92867f9
--- /dev/null
+++ b/tools/compliance/policy_walk_test.go
@@ -0,0 +1,1240 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestWalkResolutionsForCondition(t *testing.T) {
+	tests := []struct {
+		name                string
+		condition           LicenseConditionSet
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:      "firstparty",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "notice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "fponlgplnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "fponlgpldynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "independentmodulestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "ronpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronpproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "noticeonb_e_onotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonb_e_orestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "noticeonb_e_ob_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+				{"by_exception.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "b_e_oonnoticeb_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonrecipnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "noticeonreciprecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "reciponnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+				{"mplBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "reciponnoticerecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			ResolveTopDownConditions(lg)
+			actualRs := WalkResolutionsForCondition(lg, tt.condition)
+			checkResolves(actualRs, expectedRs, t)
+		})
+	}
+}
+
+func TestWalkActionsForCondition(t *testing.T) {
+	tests := []struct {
+		name            string
+		condition       LicenseConditionSet
+		roots           []string
+		edges           []annotated
+		expectedActions []act
+	}{
+		{
+			name:      "firstparty",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "notice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "fponlgplnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "fponlgpldynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "independentmodulestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedActions: []act{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "ronpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronpproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "noticeonb_e_onotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonb_e_orestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "noticeonb_e_ob_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{},
+		},
+		{
+			name:      "b_e_oonnoticeb_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonrecipnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "noticeonreciprecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "reciponnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "reciponnoticerecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedActions: []act{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %s, want no error", err)
+				return
+			}
+			expectedAs := toActionSet(lg, tt.expectedActions)
+			ResolveTopDownConditions(lg)
+			actualAs := WalkActionsForCondition(lg, tt.condition)
+			checkResolvesActions(lg, actualAs, expectedAs, t)
+		})
+	}
+}
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
new file mode 100644
index 0000000..7516440
--- /dev/null
+++ b/tools/compliance/readgraph.go
@@ -0,0 +1,285 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"strings"
+	"sync"
+
+	"android/soong/compliance/license_metadata_proto"
+
+	"google.golang.org/protobuf/encoding/prototext"
+)
+
+var (
+	// ConcurrentReaders is the size of the task pool for limiting resource usage e.g. open files.
+	ConcurrentReaders = 5
+)
+
+type globalFS struct{}
+
+func (s globalFS) Open(name string) (fs.File, error) {
+	return os.Open(name)
+}
+
+var FS globalFS
+
+// GetFS returns a filesystem for accessing files under the OUT_DIR environment variable.
+func GetFS(outDir string) fs.FS {
+	if len(outDir) > 0 {
+		return os.DirFS(outDir)
+	}
+	return os.DirFS(".")
+}
+
+// result describes the outcome of reading and parsing a single license metadata file.
+type result struct {
+	// file identifies the path to the license metadata file
+	file string
+
+	// target contains the parsed metadata or nil if an error
+	target *TargetNode
+
+	// err is nil unless an error occurs
+	err error
+}
+
+// receiver coordinates the tasks for reading and parsing license metadata files.
+type receiver struct {
+	// lg accumulates the read metadata and becomes the final resulting LicenseGraph.
+	lg *LicenseGraph
+
+	// rootFS locates the root of the file system from which to read the files.
+	rootFS fs.FS
+
+	// stderr identifies the error output writer.
+	stderr io.Writer
+
+	// task provides a fixed-size task pool to limit concurrent open files etc.
+	task chan bool
+
+	// results returns one license metadata file result at a time.
+	results chan *result
+
+	// wg detects when done
+	wg sync.WaitGroup
+}
+
+// ReadLicenseGraph reads and parses `files` and their dependencies into a LicenseGraph.
+//
+// `files` become the root files of the graph for top-down walks of the graph.
+func ReadLicenseGraph(rootFS fs.FS, stderr io.Writer, files []string) (*LicenseGraph, error) {
+	if len(files) == 0 {
+		return nil, fmt.Errorf("no license metadata to analyze")
+	}
+	if ConcurrentReaders < 1 {
+		return nil, fmt.Errorf("need at least one task in pool")
+	}
+
+	lg := newLicenseGraph()
+	for _, f := range files {
+		if strings.HasSuffix(f, "meta_lic") {
+			lg.rootFiles = append(lg.rootFiles, f)
+		} else {
+			lg.rootFiles = append(lg.rootFiles, f+".meta_lic")
+		}
+	}
+
+	recv := &receiver{
+		lg:      lg,
+		rootFS:  rootFS,
+		stderr:  stderr,
+		task:    make(chan bool, ConcurrentReaders),
+		results: make(chan *result, ConcurrentReaders),
+		wg:      sync.WaitGroup{},
+	}
+	for i := 0; i < ConcurrentReaders; i++ {
+		recv.task <- true
+	}
+
+	readFiles := func() {
+		lg.mu.Lock()
+		// identify the metadata files to schedule reading tasks for
+		for _, f := range lg.rootFiles {
+			lg.targets[f] = nil
+		}
+		lg.mu.Unlock()
+
+		// schedule tasks to read the files
+		for _, f := range lg.rootFiles {
+			readFile(recv, f)
+		}
+
+		// schedule a task to wait until finished and close the channel.
+		go func() {
+			recv.wg.Wait()
+			close(recv.task)
+			close(recv.results)
+		}()
+	}
+	go readFiles()
+
+	// tasks to read license metadata files are scheduled; read and process results from channel
+	var err error
+	for recv.results != nil {
+		select {
+		case r, ok := <-recv.results:
+			if ok {
+				// handle errors by nil'ing ls, setting err, and clobbering results channel
+				if r.err != nil {
+					err = r.err
+					fmt.Fprintf(recv.stderr, "%s\n", err.Error())
+					lg = nil
+					recv.results = nil
+					continue
+				}
+
+				// record the parsed metadata (guarded by mutex)
+				recv.lg.mu.Lock()
+				lg.targets[r.target.name] = r.target
+				recv.lg.mu.Unlock()
+			} else {
+				// finished -- nil the results channel
+				recv.results = nil
+			}
+		}
+	}
+
+	if lg != nil {
+		esize := 0
+		for _, tn := range lg.targets {
+			esize += len(tn.proto.Deps)
+		}
+		lg.edges = make(TargetEdgeList, 0, esize)
+		for _, tn := range lg.targets {
+			tn.licenseConditions = LicenseConditionSetFromNames(tn, tn.proto.LicenseConditions...)
+			err = addDependencies(lg, tn)
+			if err != nil {
+				return nil, fmt.Errorf("error indexing dependencies for %q: %w", tn.name, err)
+			}
+			tn.proto.Deps = []*license_metadata_proto.AnnotatedDependency{}
+		}
+	}
+	return lg, err
+
+}
+
+// targetNode contains the license metadata for a node in the license graph.
+type targetNode struct {
+	proto license_metadata_proto.LicenseMetadata
+
+	// name is the path to the metadata file.
+	name string
+
+	// lg is the license graph the node belongs to.
+	lg *LicenseGraph
+
+	// edges identifies the dependencies of the target.
+	edges TargetEdgeList
+
+	// licenseConditions identifies the set of license conditions originating at the target node.
+	licenseConditions LicenseConditionSet
+
+	// resolution identifies the set of conditions resolved by acting on the target node.
+	resolution LicenseConditionSet
+}
+
+// addDependencies converts the proto AnnotatedDependencies into `edges`
+func addDependencies(lg *LicenseGraph, tn *TargetNode) error {
+	tn.edges = make(TargetEdgeList, 0, len(tn.proto.Deps))
+	for _, ad := range tn.proto.Deps {
+		dependency := ad.GetFile()
+		if len(dependency) == 0 {
+			return fmt.Errorf("missing dependency name")
+		}
+		dtn, ok := lg.targets[dependency]
+		if !ok {
+			return fmt.Errorf("unknown dependency name %q", dependency)
+		}
+		if dtn == nil {
+			return fmt.Errorf("nil dependency for name %q", dependency)
+		}
+		annotations := newEdgeAnnotations()
+		for _, a := range ad.Annotations {
+			// look up a common constant annotation string from a small map
+			// instead of creating 1000's of copies of the same 3 strings.
+			if ann, ok := RecognizedAnnotations[a]; ok {
+				annotations.annotations[ann] = struct{}{}
+			}
+		}
+		edge := &TargetEdge{tn, dtn, annotations}
+		lg.edges = append(lg.edges, edge)
+		tn.edges = append(tn.edges, edge)
+	}
+	return nil
+}
+
+// readFile is a task to read and parse a single license metadata file, and to schedule
+// additional tasks for reading and parsing dependencies as necessary.
+func readFile(recv *receiver, file string) {
+	recv.wg.Add(1)
+	<-recv.task
+	go func() {
+		f, err := recv.rootFS.Open(file)
+		if err != nil {
+			recv.results <- &result{file, nil, fmt.Errorf("error opening license metadata %q: %w", file, err)}
+			return
+		}
+
+		// read the file
+		data, err := io.ReadAll(f)
+		if err != nil {
+			recv.results <- &result{file, nil, fmt.Errorf("error reading license metadata %q: %w", file, err)}
+			return
+		}
+		f.Close()
+
+		tn := &TargetNode{lg: recv.lg, name: file}
+
+		err = prototext.Unmarshal(data, &tn.proto)
+		if err != nil {
+			recv.results <- &result{file, nil, fmt.Errorf("error license metadata %q: %w", file, err)}
+			return
+		}
+
+		// send result for this file and release task before scheduling dependencies,
+		// but do not signal done to WaitGroup until dependencies are scheduled.
+		recv.results <- &result{file, tn, nil}
+		recv.task <- true
+
+		// schedule tasks as necessary to read dependencies
+		for _, ad := range tn.proto.Deps {
+			dependency := ad.GetFile()
+			// decide, signal and record whether to schedule task in critical section
+			recv.lg.mu.Lock()
+			_, alreadyScheduled := recv.lg.targets[dependency]
+			if !alreadyScheduled {
+				recv.lg.targets[dependency] = nil
+			}
+			recv.lg.mu.Unlock()
+			// schedule task to read dependency file outside critical section
+			if !alreadyScheduled {
+				readFile(recv, dependency)
+			}
+		}
+
+		// signal task done after scheduling dependencies
+		recv.wg.Done()
+	}()
+}
diff --git a/tools/compliance/readgraph_test.go b/tools/compliance/readgraph_test.go
new file mode 100644
index 0000000..bcf9f39
--- /dev/null
+++ b/tools/compliance/readgraph_test.go
@@ -0,0 +1,150 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestReadLicenseGraph(t *testing.T) {
+	tests := []struct {
+		name            string
+		fs              *testFS
+		roots           []string
+		expectedError   string
+		expectedEdges   []edge
+		expectedTargets []string
+	}{
+		{
+			name: "trivial",
+			fs: &testFS{
+				"app.meta_lic": []byte("package_name: \"Android\"\n"),
+			},
+			roots:           []string{"app.meta_lic"},
+			expectedEdges:   []edge{},
+			expectedTargets: []string{"app.meta_lic"},
+		},
+		{
+			name: "unterminated",
+			fs: &testFS{
+				"app.meta_lic": []byte("package_name: \"Android\n"),
+			},
+			roots:         []string{"app.meta_lic"},
+			expectedError: `invalid character '\n' in string`,
+		},
+		{
+			name: "danglingref",
+			fs: &testFS{
+				"app.meta_lic": []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+			},
+			roots:         []string{"app.meta_lic"},
+			expectedError: `unknown file "lib.meta_lic"`,
+		},
+		{
+			name: "singleedge",
+			fs: &testFS{
+				"app.meta_lic": []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+				"lib.meta_lic": []byte(AOSP),
+			},
+			roots:           []string{"app.meta_lic"},
+			expectedEdges:   []edge{{"app.meta_lic", "lib.meta_lic"}},
+			expectedTargets: []string{"app.meta_lic", "lib.meta_lic"},
+		},
+		{
+			name: "fullgraph",
+			fs: &testFS{
+				"apex.meta_lic": []byte(AOSP + "deps: {\n  file: \"app.meta_lic\"\n}\ndeps: {\n  file: \"bin.meta_lic\"\n}\n"),
+				"app.meta_lic":  []byte(AOSP),
+				"bin.meta_lic":  []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+				"lib.meta_lic":  []byte(AOSP),
+			},
+			roots: []string{"apex.meta_lic"},
+			expectedEdges: []edge{
+				{"apex.meta_lic", "app.meta_lic"},
+				{"apex.meta_lic", "bin.meta_lic"},
+				{"bin.meta_lic", "lib.meta_lic"},
+			},
+			expectedTargets: []string{"apex.meta_lic", "app.meta_lic", "bin.meta_lic", "lib.meta_lic"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := ReadLicenseGraph(tt.fs, stderr, tt.roots)
+			if err != nil {
+				if len(tt.expectedError) == 0 {
+					t.Errorf("unexpected error: got %s, want no error", err)
+				} else if !strings.Contains(err.Error(), tt.expectedError) {
+					t.Errorf("unexpected error: got %s, want %q", err, tt.expectedError)
+				}
+				return
+			}
+			if len(tt.expectedError) > 0 {
+				t.Errorf("unexpected success: got no error, want %q err", tt.expectedError)
+				return
+			}
+			if lg == nil {
+				t.Errorf("missing license graph: got nil, want license graph")
+				return
+			}
+			actualEdges := make([]edge, 0)
+			for _, e := range lg.Edges() {
+				actualEdges = append(actualEdges, edge{e.Target().Name(), e.Dependency().Name()})
+			}
+			sort.Sort(byEdge(tt.expectedEdges))
+			sort.Sort(byEdge(actualEdges))
+			t.Logf("actualEdges:")
+			for _, edge := range actualEdges {
+				t.Logf("  %s", edge.String())
+			}
+			t.Logf("expectedEdges:")
+			for _, edge := range actualEdges {
+				t.Logf("  %s", edge.String())
+			}
+			if len(tt.expectedEdges) != len(actualEdges) {
+				t.Errorf("len(actualEdges): got %d, want %d", len(actualEdges), len(tt.expectedEdges))
+			} else {
+				for i := 0; i < len(actualEdges); i++ {
+					if tt.expectedEdges[i] != actualEdges[i] {
+						t.Errorf("actualEdges[%d]: got %s, want %s", i, actualEdges[i], tt.expectedEdges[i])
+					}
+				}
+			}
+
+			actualTargets := make([]string, 0)
+			for _, t := range lg.Targets() {
+				actualTargets = append(actualTargets, t.Name())
+			}
+			sort.Strings(tt.expectedTargets)
+			sort.Strings(actualTargets)
+
+			t.Logf("actualTargets: %v", actualTargets)
+			t.Logf("expectedTargets: %v", tt.expectedTargets)
+
+			if len(tt.expectedTargets) != len(actualTargets) {
+				t.Errorf("len(actualTargets): got %d, want %d", len(actualTargets), len(tt.expectedTargets))
+			} else {
+				for i := 0; i < len(actualTargets); i++ {
+					if tt.expectedTargets[i] != actualTargets[i] {
+						t.Errorf("actualTargets[%d]: got %s, want %s", i, actualTargets[i], tt.expectedTargets[i])
+					}
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/resolution.go b/tools/compliance/resolution.go
new file mode 100644
index 0000000..acc61e2
--- /dev/null
+++ b/tools/compliance/resolution.go
@@ -0,0 +1,149 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// Resolution describes an action to resolve one or more license conditions.
+//
+// `AttachesTo` identifies the target node that when distributed triggers the action.
+// `ActsOn` identifies the target node that is the object of the action.
+// `Resolves` identifies one or more license conditions that the action resolves.
+//
+// e.g. Suppose an MIT library is linked to a binary that also links to GPL code.
+//
+// A resolution would attach to the binary to share (act on) the MIT library to
+// resolve the restricted condition originating from the GPL code.
+type Resolution struct {
+	attachesTo, actsOn *TargetNode
+	cs                 LicenseConditionSet
+}
+
+// AttachesTo returns the target node the resolution attaches to.
+func (r Resolution) AttachesTo() *TargetNode {
+	return r.attachesTo
+}
+
+// ActsOn returns the target node that must be acted on to resolve the condition.
+//
+// i.e. The node for which notice must be given or whose source must be shared etc.
+func (r Resolution) ActsOn() *TargetNode {
+	return r.actsOn
+}
+
+// Resolves returns the set of license condition the resolution satisfies.
+func (r Resolution) Resolves() LicenseConditionSet {
+	return r.cs
+}
+
+// asString returns a string representation of the resolution.
+func (r Resolution) asString() string {
+	var sb strings.Builder
+	names := r.cs.Names()
+	sort.Strings(names)
+	fmt.Fprintf(&sb, "%s -> %s{%s}", r.attachesTo.name, r.actsOn.name, strings.Join(names, ", "))
+	return sb.String()
+}
+
+// ResolutionList represents a partial order of Resolutions ordered by
+// AttachesTo() and ActsOn() leaving `Resolves()` unordered.
+type ResolutionList []Resolution
+
+// Len returns the count of elements in the list.
+func (l ResolutionList) Len() int { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l ResolutionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than tht `j`th.
+func (l ResolutionList) Less(i, j int) bool {
+	if l[i].attachesTo.name == l[j].attachesTo.name {
+		return l[i].actsOn.name < l[j].actsOn.name
+	}
+	return l[i].attachesTo.name < l[j].attachesTo.name
+}
+
+// String returns a string representation of the list.
+func (rl ResolutionList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, r := range rl {
+		fmt.Fprintf(&sb, "%s%s", sep, r.asString())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// AllConditions returns the union of all license conditions resolved by any
+// element of the list.
+func (rl ResolutionList) AllConditions() LicenseConditionSet {
+	result := NewLicenseConditionSet()
+	for _, r := range rl {
+		result = result.Union(r.cs)
+	}
+	return result
+}
+
+// ByName returns the sub-list of resolutions resolving conditions matching
+// `names`.
+func (rl ResolutionList) Matching(conditions LicenseConditionSet) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountMatching(conditions))
+	for _, r := range rl {
+		if r.Resolves().MatchesAnySet(conditions) {
+			result = append(result, Resolution{r.attachesTo, r.actsOn, r.cs.MatchingAnySet(conditions)})
+		}
+	}
+	return result
+}
+
+// CountMatching returns the number of resolutions resolving conditions matching
+// `conditions`.
+func (rl ResolutionList) CountMatching(conditions LicenseConditionSet) int {
+	c := 0
+	for _, r := range rl {
+		if r.Resolves().MatchesAnySet(conditions) {
+			c++
+		}
+	}
+	return c
+}
+
+// ByActsOn returns the sub-list of resolutions matching `actsOn`.
+func (rl ResolutionList) ByActsOn(actsOn *TargetNode) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountByActsOn(actsOn))
+	for _, r := range rl {
+		if r.actsOn == actsOn {
+			result = append(result, r)
+		}
+	}
+	return result
+}
+
+// CountByActsOn returns the number of resolutions matching `actsOn`.
+func (rl ResolutionList) CountByActsOn(actsOn *TargetNode) int {
+	c := 0
+	for _, r := range rl {
+		if r.actsOn == actsOn {
+			c++
+		}
+	}
+	return c
+}
diff --git a/tools/compliance/resolutionset.go b/tools/compliance/resolutionset.go
new file mode 100644
index 0000000..7c8f333
--- /dev/null
+++ b/tools/compliance/resolutionset.go
@@ -0,0 +1,133 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"strings"
+)
+
+// ResolutionSet describes an immutable set of targets and the license
+// conditions each target must satisfy or "resolve" in a specific context.
+//
+// Ultimately, the purpose of recording the license metadata and building a
+// license graph is to identify, describe, and verify the necessary actions or
+// operations for compliance policy.
+//
+// i.e. What is the source-sharing policy? Has it been met? Meet it.
+//
+// i.e. Are there incompatible policy requirements? Such as a source-sharing
+// policy applied to code that policy also says may not be shared? If so, stop
+// and remove the dependencies that create the situation.
+//
+// The ResolutionSet is the base unit for mapping license conditions to the
+// targets triggering some necessary action per policy. Different ResolutionSet
+// values may be calculated for different contexts.
+//
+// e.g. Suppose an unencumbered binary links in a notice .a library.
+//
+// An "unencumbered" condition would originate from the binary, and a "notice"
+// condition would originate from the .a library. A ResolutionSet for the
+// context of the Notice policy might attach both conditions to the binary to
+// act on the origin of each condition. By attaching the notice condition to
+// the binary, the ResolutionSet stipulates the policy that the release of the
+// unencumbered binary must provide suitable notice for the .a library.
+//
+// The resulting ResolutionSet could be used for building a notice file, for
+// validating that a suitable notice has been built into the distribution, or
+// for reporting what notices need to be given.
+//
+// The action is defined by the context. In the above example, the action is
+// providing notice for the module acted on. In another context, the action
+// might be sharing the source-code or preserving the privacy of the module
+// acted on.
+type ResolutionSet map[*TargetNode]ActionSet
+
+// AttachesTo identifies the list of targets triggering action to resolve
+// conditions. (unordered)
+func (rs ResolutionSet) AttachesTo() TargetNodeList {
+	result := make(TargetNodeList, 0, len(rs))
+	for attachesTo := range rs {
+		result = append(result, attachesTo)
+	}
+	return result
+}
+
+// AttachesToTarget returns true if the set contains conditions that
+// are `attachedTo`.
+func (rs ResolutionSet) AttachesToTarget(target *TargetNode) bool {
+	_, isPresent := rs[target]
+	return isPresent
+}
+
+// Resolutions returns the list of resolutions that `attachedTo`
+// target must resolve. Returns empty list if no conditions apply.
+func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList {
+	as, ok := rs[attachesTo]
+	if !ok {
+		return nil
+	}
+	result := make(ResolutionList, 0, len(as))
+	for actsOn, cs := range as {
+		result = append(result, Resolution{attachesTo, actsOn, cs})
+	}
+	return result
+}
+
+// AllActions returns the set of actions required to resolve the set omitting
+// the attachment.
+func (rs ResolutionSet) AllActions() ActionSet {
+	result := make(ActionSet)
+	for _, as := range rs {
+		for actsOn, cs := range as {
+			if _, ok := result[actsOn]; ok {
+				result[actsOn] = cs.Union(result[actsOn])
+			} else {
+				result[actsOn] = cs
+			}
+		}
+	}
+	return result
+}
+
+// String returns a human-readable string representation of the set.
+func (rs ResolutionSet) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "{")
+	sep := ""
+	for attachesTo, as := range rs {
+		fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.Name(), as.String())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "}")
+	return sb.String()
+}
+
+// ActionSet identifies a set of targets to act on and the license conditions
+// the action will resolve.
+type ActionSet map[*TargetNode]LicenseConditionSet
+
+// String returns a human-readable string representation of the set.
+func (as ActionSet) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "{")
+	sep := ""
+	for actsOn, cs := range as {
+		fmt.Fprintf(&sb, "%s%s%s", sep, actsOn.Name(), cs.String())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "}")
+	return sb.String()
+}
diff --git a/tools/compliance/resolutionset_test.go b/tools/compliance/resolutionset_test.go
new file mode 100644
index 0000000..89cdfeb
--- /dev/null
+++ b/tools/compliance/resolutionset_test.go
@@ -0,0 +1,136 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"sort"
+	"testing"
+)
+
+var (
+	// bottomUp describes the bottom-up resolve of a hypothetical graph
+	// the graph has a container image, a couple binaries, and a couple
+	// libraries. bin1 statically links lib1 and dynamically links lib2;
+	// bin2 dynamically links lib1 and statically links lib2.
+	// binc represents a compiler or other toolchain binary used for
+	// building the other binaries.
+	bottomUp = []res{
+		{"image", "image", "image", "notice"},
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib1", "lib1", "notice"},
+		{"image", "lib2", "lib2", "notice"},
+		{"binc", "binc", "binc", "proprietary"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin1", "lib1", "lib1", "notice"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "lib2", "notice"},
+		{"lib1", "lib1", "lib1", "notice"},
+		{"lib2", "lib2", "lib2", "notice"},
+	}
+
+	// notice describes bottomUp after a top-down notice resolve.
+	notice = []res{
+		{"image", "image", "image", "notice"},
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib1", "lib1", "notice"},
+		{"image", "lib2", "bin2", "restricted"},
+		{"image", "lib2", "lib2", "notice"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin1", "lib1", "lib1", "notice"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "bin2", "restricted"},
+		{"bin2", "lib2", "lib2", "notice"},
+		{"lib1", "lib1", "lib1", "notice"},
+		{"lib2", "lib2", "lib2", "notice"},
+	}
+
+	// share describes bottomUp after a top-down share resolve.
+	share = []res{
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib2", "bin2", "restricted"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "bin2", "restricted"},
+	}
+
+	// proprietary describes bottomUp after a top-down proprietary resolve.
+	// Note that the proprietary binc is not reachable through the toolchain
+	// dependency.
+	proprietary = []res{}
+)
+
+func TestResolutionSet_AttachesTo(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+
+	t.Logf("checking resolution set %s", rsShare.String())
+
+	actual := rsShare.AttachesTo().Names()
+	sort.Strings(actual)
+
+	expected := []string{"bin1", "bin2", "image"}
+
+	t.Logf("actual rsShare: %v", actual)
+	t.Logf("expected rsShare: %v", expected)
+
+	if len(actual) != len(expected) {
+		t.Errorf("rsShare: wrong number of targets: got %d, want %d", len(actual), len(expected))
+		return
+	}
+	for i := 0; i < len(actual); i++ {
+		if actual[i] != expected[i] {
+			t.Errorf("rsShare: unexpected target at index %d: got %s, want %s", i, actual[i], expected[i])
+		}
+	}
+
+	rsPrivate := toResolutionSet(lg, proprietary)
+	actual = rsPrivate.AttachesTo().Names()
+	expected = []string{}
+
+	t.Logf("actual rsPrivate: %v", actual)
+	t.Logf("expected rsPrivate: %v", expected)
+
+	if len(actual) != len(expected) {
+		t.Errorf("rsPrivate: wrong number of targets: got %d, want %d", len(actual), len(expected))
+		return
+	}
+	for i := 0; i < len(actual); i++ {
+		if actual[i] != expected[i] {
+			t.Errorf("rsPrivate: unexpected target at index %d: got %s, want %s", i, actual[i], expected[i])
+		}
+	}
+}
+
+func TestResolutionSet_AttachesToTarget(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+
+	t.Logf("checking resolution set %s", rsShare.String())
+
+	if rsShare.AttachesToTarget(newTestNode(lg, "binc")) {
+		t.Errorf("actual.AttachesToTarget(\"binc\"): got true, want false")
+	}
+	if !rsShare.AttachesToTarget(newTestNode(lg, "image")) {
+		t.Errorf("actual.AttachesToTarget(\"image\"): got false want true")
+	}
+}
diff --git a/tools/compliance/test_util.go b/tools/compliance/test_util.go
new file mode 100644
index 0000000..26d7461
--- /dev/null
+++ b/tools/compliance/test_util.go
@@ -0,0 +1,603 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compliance
+
+import (
+	"fmt"
+	"io"
+	"io/fs"
+	"sort"
+	"strings"
+	"testing"
+)
+
+const (
+	// AOSP starts a test metadata file for Android Apache-2.0 licensing.
+	AOSP = `` +
+		`package_name: "Android"
+license_kinds: "SPDX-license-identifier-Apache-2.0"
+license_conditions: "notice"
+`
+
+	// GPL starts a test metadata file for GPL 2.0 licensing.
+	GPL = `` +
+		`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+`
+
+	// Classpath starts a test metadata file for GPL 2.0 with classpath exception licensing.
+	Classpath = `` +
+		`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception"
+license_conditions: "restricted"
+`
+
+	// DependentModule starts a test metadata file for a module in the same package as `Classpath`.
+	DependentModule = `` +
+		`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+`
+
+	// LGPL starts a test metadata file for a module with LGPL 2.0 licensing.
+	LGPL = `` +
+		`package_name: "Free Library"
+license_kinds: "SPDX-license-identifier-LGPL-2.0"
+license_conditions: "restricted"
+`
+
+	// MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
+	MPL = `` +
+		`package_name: "Reciprocal"
+license_kinds: "SPDX-license-identifier-MPL-2.0"
+license_conditions: "reciprocal"
+`
+
+	// MIT starts a test metadata file for a module with generic notice (MIT) licensing.
+	MIT = `` +
+		`package_name: "Android"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+`
+
+	// Proprietary starts a test metadata file for a module with proprietary licensing.
+	Proprietary = `` +
+		`package_name: "Android"
+license_kinds: "legacy_proprietary"
+license_conditions: "proprietary"
+`
+
+	// ByException starts a test metadata file for a module with by_exception_only licensing.
+	ByException = `` +
+		`package_name: "Special"
+license_kinds: "legacy_by_exception_only"
+license_conditions: "by_exception_only"
+`
+)
+
+var (
+	// meta maps test file names to metadata file content without dependencies.
+	meta = map[string]string{
+		"apacheBin.meta_lic":                 AOSP,
+		"apacheLib.meta_lic":                 AOSP,
+		"apacheContainer.meta_lic":           AOSP + "is_container: true\n",
+		"dependentModule.meta_lic":           DependentModule,
+		"gplWithClasspathException.meta_lic": Classpath,
+		"gplBin.meta_lic":                    GPL,
+		"gplLib.meta_lic":                    GPL,
+		"gplContainer.meta_lic":              GPL + "is_container: true\n",
+		"lgplBin.meta_lic":                   LGPL,
+		"lgplLib.meta_lic":                   LGPL,
+		"mitBin.meta_lic":                    MIT,
+		"mitLib.meta_lic":                    MIT,
+		"mplBin.meta_lic":                    MPL,
+		"mplLib.meta_lic":                    MPL,
+		"proprietary.meta_lic":               Proprietary,
+		"by_exception.meta_lic":              ByException,
+	}
+)
+
+// newTestNode constructs a test node in the license graph.
+func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
+	if tn, alreadyExists := lg.targets[targetName]; alreadyExists {
+		return tn
+	}
+	tn := &TargetNode{name: targetName}
+	lg.targets[targetName] = tn
+	return tn
+}
+
+// newTestCondition constructs a test license condition in the license graph.
+func newTestCondition(lg *LicenseGraph, targetName string, conditionName string) LicenseCondition {
+	tn := newTestNode(lg, targetName)
+	cl := LicenseConditionSetFromNames(tn, conditionName).AsList()
+	if len(cl) == 0 {
+		panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+	} else if len(cl) != 1 {
+		panic(fmt.Errorf("unexpected multiple conditions from condition name: %q: got %d, want 1", conditionName, len(cl)))
+	}
+	lc := cl[0]
+	tn.licenseConditions = tn.licenseConditions.Plus(lc)
+	return lc
+}
+
+// newTestConditionSet constructs a test license condition set in the license graph.
+func newTestConditionSet(lg *LicenseGraph, targetName string, conditionName []string) LicenseConditionSet {
+	tn := newTestNode(lg, targetName)
+	cs := LicenseConditionSetFromNames(tn, conditionName...)
+	if cs.IsEmpty() {
+		panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+	}
+	tn.licenseConditions = tn.licenseConditions.Union(cs)
+	return cs
+}
+
+// testFS implements a test file system (fs.FS) simulated by a map from filename to []byte content.
+type testFS map[string][]byte
+
+// Open implements fs.FS.Open() to open a file based on the filename.
+func (fs *testFS) Open(name string) (fs.File, error) {
+	if _, ok := (*fs)[name]; !ok {
+		return nil, fmt.Errorf("unknown file %q", name)
+	}
+	return &testFile{fs, name, 0}, nil
+}
+
+// testFile implements a test file (fs.File) based on testFS above.
+type testFile struct {
+	fs   *testFS
+	name string
+	posn int
+}
+
+// Stat not implemented to obviate implementing fs.FileInfo.
+func (f *testFile) Stat() (fs.FileInfo, error) {
+	return nil, fmt.Errorf("unimplemented")
+}
+
+// Read copies bytes from the testFS map.
+func (f *testFile) Read(b []byte) (int, error) {
+	if f.posn < 0 {
+		return 0, fmt.Errorf("file not open: %q", f.name)
+	}
+	if f.posn >= len((*f.fs)[f.name]) {
+		return 0, io.EOF
+	}
+	n := copy(b, (*f.fs)[f.name][f.posn:])
+	f.posn += n
+	return n, nil
+}
+
+// Close marks the testFile as no longer in use.
+func (f *testFile) Close() error {
+	if f.posn < 0 {
+		return fmt.Errorf("file already closed: %q", f.name)
+	}
+	f.posn = -1
+	return nil
+}
+
+// edge describes test data edges to define test graphs.
+type edge struct {
+	target, dep string
+}
+
+// String returns a string representation of the edge.
+func (e edge) String() string {
+	return e.target + " -> " + e.dep
+}
+
+// byEdge orders edges by target then dep name then annotations.
+type byEdge []edge
+
+// Len returns the count of elements in the slice.
+func (l byEdge) Len() int { return len(l) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l byEdge) Less(i, j int) bool {
+	if l[i].target == l[j].target {
+		return l[i].dep < l[j].dep
+	}
+	return l[i].target < l[j].target
+}
+
+// annotated describes annotated test data edges to define test graphs.
+type annotated struct {
+	target, dep string
+	annotations []string
+}
+
+func (e annotated) String() string {
+	if e.annotations != nil {
+		return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]"
+	}
+	return e.target + " -> " + e.dep
+}
+
+func (e annotated) IsEqualTo(other annotated) bool {
+	if e.target != other.target {
+		return false
+	}
+	if e.dep != other.dep {
+		return false
+	}
+	if len(e.annotations) != len(other.annotations) {
+		return false
+	}
+	a1 := append([]string{}, e.annotations...)
+	a2 := append([]string{}, other.annotations...)
+	for i := 0; i < len(a1); i++ {
+		if a1[i] != a2[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// toGraph converts a list of roots and a list of annotated edges into a test license graph.
+func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) {
+	deps := make(map[string][]annotated)
+	for _, root := range roots {
+		deps[root] = []annotated{}
+	}
+	for _, edge := range edges {
+		if prev, ok := deps[edge.target]; ok {
+			deps[edge.target] = append(prev, edge)
+		} else {
+			deps[edge.target] = []annotated{edge}
+		}
+		if _, ok := deps[edge.dep]; !ok {
+			deps[edge.dep] = []annotated{}
+		}
+	}
+	fs := make(testFS)
+	for file, edges := range deps {
+		body := meta[file]
+		for _, edge := range edges {
+			body += fmt.Sprintf("deps: {\n  file: %q\n", edge.dep)
+			for _, ann := range edge.annotations {
+				body += fmt.Sprintf("  annotations: %q\n", ann)
+			}
+			body += "}\n"
+		}
+		fs[file] = []byte(body)
+	}
+
+	return ReadLicenseGraph(&fs, stderr, roots)
+}
+
+// logGraph outputs a representation of the graph to a test log.
+func logGraph(lg *LicenseGraph, t *testing.T) {
+	t.Logf("license graph:")
+	t.Logf("  targets:")
+	for _, target := range lg.Targets() {
+		t.Logf("    %s%s in package %q", target.Name(), target.LicenseConditions().String(), target.PackageName())
+	}
+	t.Logf("  /targets")
+	t.Logf("  edges:")
+	for _, edge := range lg.Edges() {
+		t.Logf("    %s", edge.String())
+	}
+	t.Logf("  /edges")
+	t.Logf("/license graph")
+}
+
+// byAnnotatedEdge orders edges by target then dep name then annotations.
+type byAnnotatedEdge []annotated
+
+func (l byAnnotatedEdge) Len() int      { return len(l) }
+func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l byAnnotatedEdge) Less(i, j int) bool {
+	if l[i].target == l[j].target {
+		if l[i].dep == l[j].dep {
+			ai := append([]string{}, l[i].annotations...)
+			aj := append([]string{}, l[j].annotations...)
+			sort.Strings(ai)
+			sort.Strings(aj)
+			for k := 0; k < len(ai) && k < len(aj); k++ {
+				if ai[k] == aj[k] {
+					continue
+				}
+				return ai[k] < aj[k]
+			}
+			return len(ai) < len(aj)
+		}
+		return l[i].dep < l[j].dep
+	}
+	return l[i].target < l[j].target
+}
+
+// act describes test data resolution actions to define test action sets.
+type act struct {
+	actsOn, origin, condition string
+}
+
+// String returns a human-readable string representing the test action.
+func (a act) String() string {
+	return fmt.Sprintf("%s{%s:%s}", a.actsOn, a.origin, a.condition)
+}
+
+// toActionSet converts a list of act test data into a test action set.
+func toActionSet(lg *LicenseGraph, data []act) ActionSet {
+	as := make(ActionSet)
+	for _, a := range data {
+		actsOn := newTestNode(lg, a.actsOn)
+		cs := newTestConditionSet(lg, a.origin, strings.Split(a.condition, "|"))
+		as[actsOn] = cs
+	}
+	return as
+}
+
+// res describes test data resolutions to define test resolution sets.
+type res struct {
+	attachesTo, actsOn, origin, condition string
+}
+
+// toResolutionSet converts a list of res test data into a test resolution set.
+func toResolutionSet(lg *LicenseGraph, data []res) ResolutionSet {
+	rmap := make(ResolutionSet)
+	for _, r := range data {
+		attachesTo := newTestNode(lg, r.attachesTo)
+		actsOn := newTestNode(lg, r.actsOn)
+		if _, ok := rmap[attachesTo]; !ok {
+			rmap[attachesTo] = make(ActionSet)
+		}
+		cs := newTestConditionSet(lg, r.origin, strings.Split(r.condition, ":"))
+		rmap[attachesTo][actsOn] |= cs
+	}
+	return rmap
+}
+
+// tcond associates a target name with '|' separated string conditions.
+type tcond struct {
+	target, conditions string
+}
+
+// action represents a single element of an ActionSet for testing.
+type action struct {
+	target *TargetNode
+	cs     LicenseConditionSet
+}
+
+// String returns a human-readable string representation of the action.
+func (a action) String() string {
+	return fmt.Sprintf("%s%s", a.target.Name(), a.cs.String())
+}
+
+// actionList represents an array of actions and a total order defined by
+// target name followed by license condition set.
+type actionList []action
+
+// String returns a human-readable string representation of the list.
+func (l actionList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, a := range l {
+		fmt.Fprintf(&sb, "%s%s", sep, a.String())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// Len returns the count of elements in the slice.
+func (l actionList) Len() int { return len(l) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l actionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l actionList) Less(i, j int) bool {
+	if l[i].target == l[j].target {
+		return l[i].cs < l[j].cs
+	}
+	return l[i].target.Name() < l[j].target.Name()
+}
+
+// asActionList represents the resolved license conditions in a license graph
+// as an actionList for comparison in a test.
+func asActionList(lg *LicenseGraph) actionList {
+	result := make(actionList, 0, len(lg.targets))
+	for _, target := range lg.targets {
+		cs := target.resolution
+		if cs.IsEmpty() {
+			continue
+		}
+		result = append(result, action{target, cs})
+	}
+	return result
+}
+
+// toActionList converts an array of tcond into an actionList for comparison
+// in a test.
+func toActionList(lg *LicenseGraph, actions []tcond) actionList {
+	result := make(actionList, 0, len(actions))
+	for _, actn := range actions {
+		target := newTestNode(lg, actn.target)
+		cs := NewLicenseConditionSet()
+		for _, name := range strings.Split(actn.conditions, "|") {
+			lc, ok := RecognizedConditionNames[name]
+			if !ok {
+				panic(fmt.Errorf("Unrecognized test condition name: %q", name))
+			}
+			cs = cs.Plus(lc)
+		}
+		result = append(result, action{target, cs})
+	}
+	return result
+}
+
+// confl defines test data for a SourceSharePrivacyConflict as a target name,
+// source condition name, privacy condition name triple.
+type confl struct {
+	sourceNode, share, privacy string
+}
+
+// toConflictList converts confl test data into an array of
+// SourceSharePrivacyConflict for comparison in a test.
+func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict {
+	result := make([]SourceSharePrivacyConflict, 0, len(data))
+	for _, c := range data {
+		fields := strings.Split(c.share, ":")
+		oshare := fields[0]
+		cshare := fields[1]
+		fields = strings.Split(c.privacy, ":")
+		oprivacy := fields[0]
+		cprivacy := fields[1]
+		result = append(result, SourceSharePrivacyConflict{
+			newTestNode(lg, c.sourceNode),
+			newTestCondition(lg, oshare, cshare),
+			newTestCondition(lg, oprivacy, cprivacy),
+		})
+	}
+	return result
+}
+
+// checkSameActions compares an actual action set to an expected action set for a test.
+func checkSameActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
+	rsActual := make(ResolutionSet)
+	rsExpected := make(ResolutionSet)
+	testNode := newTestNode(lg, "test")
+	rsActual[testNode] = asActual
+	rsExpected[testNode] = asExpected
+	checkSame(rsActual, rsExpected, t)
+}
+
+// checkSame compares an actual resolution set to an expected resolution set for a test.
+func checkSame(rsActual, rsExpected ResolutionSet, t *testing.T) {
+	t.Logf("actual resolution set: %s", rsActual.String())
+	t.Logf("expected resolution set: %s", rsExpected.String())
+
+	actualTargets := rsActual.AttachesTo()
+	sort.Sort(actualTargets)
+
+	expectedTargets := rsExpected.AttachesTo()
+	sort.Sort(expectedTargets)
+
+	t.Logf("actual targets: %s", actualTargets.String())
+	t.Logf("expected targets: %s", expectedTargets.String())
+
+	for _, target := range expectedTargets {
+		if !rsActual.AttachesToTarget(target) {
+			t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
+			continue
+		}
+		expectedRl := rsExpected.Resolutions(target)
+		sort.Sort(expectedRl)
+		actualRl := rsActual.Resolutions(target)
+		sort.Sort(actualRl)
+		if len(expectedRl) != len(actualRl) {
+			t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
+				target.name, len(actualRl), len(expectedRl))
+			continue
+		}
+		for i := 0; i < len(expectedRl); i++ {
+			if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
+				t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
+					target.name, i, actualRl[i].asString(), expectedRl[i].asString())
+				continue
+			}
+			expectedConditions := expectedRl[i].Resolves()
+			actualConditions := actualRl[i].Resolves()
+			if expectedConditions != actualConditions {
+				t.Errorf("unexpected conditions apply to %q acting on %q: got %04x with names %s, want %04x with names %s",
+					target.name, expectedRl[i].actsOn.name,
+					actualConditions, actualConditions.Names(),
+					expectedConditions, expectedConditions.Names())
+				continue
+			}
+		}
+
+	}
+	for _, target := range actualTargets {
+		if !rsExpected.AttachesToTarget(target) {
+			t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
+		}
+	}
+}
+
+// checkResolvesActions compares an actual action set to an expected action set for a test verifying the actual set
+// resolves all of the expected conditions.
+func checkResolvesActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
+	rsActual := make(ResolutionSet)
+	rsExpected := make(ResolutionSet)
+	testNode := newTestNode(lg, "test")
+	rsActual[testNode] = asActual
+	rsExpected[testNode] = asExpected
+	checkResolves(rsActual, rsExpected, t)
+}
+
+// checkResolves compares an actual resolution set to an expected resolution set for a test verifying the actual set
+// resolves all of the expected conditions.
+func checkResolves(rsActual, rsExpected ResolutionSet, t *testing.T) {
+	t.Logf("actual resolution set: %s", rsActual.String())
+	t.Logf("expected resolution set: %s", rsExpected.String())
+
+	actualTargets := rsActual.AttachesTo()
+	sort.Sort(actualTargets)
+
+	expectedTargets := rsExpected.AttachesTo()
+	sort.Sort(expectedTargets)
+
+	t.Logf("actual targets: %s", actualTargets.String())
+	t.Logf("expected targets: %s", expectedTargets.String())
+
+	for _, target := range expectedTargets {
+		if !rsActual.AttachesToTarget(target) {
+			t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
+			continue
+		}
+		expectedRl := rsExpected.Resolutions(target)
+		sort.Sort(expectedRl)
+		actualRl := rsActual.Resolutions(target)
+		sort.Sort(actualRl)
+		if len(expectedRl) != len(actualRl) {
+			t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
+				target.name, len(actualRl), len(expectedRl))
+			continue
+		}
+		for i := 0; i < len(expectedRl); i++ {
+			if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
+				t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
+					target.name, i, actualRl[i].asString(), expectedRl[i].asString())
+				continue
+			}
+			expectedConditions := expectedRl[i].Resolves()
+			actualConditions := actualRl[i].Resolves()
+			if expectedConditions != (expectedConditions & actualConditions) {
+				t.Errorf("expected conditions missing from %q acting on %q: got %04x with names %s, want %04x with names %s",
+					target.name, expectedRl[i].actsOn.name,
+					actualConditions, actualConditions.Names(),
+					expectedConditions, expectedConditions.Names())
+				continue
+			}
+		}
+
+	}
+	for _, target := range actualTargets {
+		if !rsExpected.AttachesToTarget(target) {
+			t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
+		}
+	}
+}
diff --git a/tools/droiddoc/Android.bp b/tools/droiddoc/Android.bp
index efd30c1..71d4939 100644
--- a/tools/droiddoc/Android.bp
+++ b/tools/droiddoc/Android.bp
@@ -14,15 +14,22 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    //   SPDX-license-identifier-CC-BY
-    //   SPDX-license-identifier-GPL
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "build_make_tools_droiddoc_license",
+    ],
+}
+
+license {
+    name: "build_make_tools_droiddoc_license",
+    package_name: "Android Droiddoc Templates",
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-CC-BY-2.5",
+        "SPDX-license-identifier-GPL-3.0",
+        "SPDX-license-identifier-MIT",
+    ],
+    license_text: ["LICENSE"],
 }
 
 droiddoc_exported_dir {
diff --git a/tools/droiddoc/LICENSE b/tools/droiddoc/LICENSE
new file mode 100644
index 0000000..b591dde
--- /dev/null
+++ b/tools/droiddoc/LICENSE
@@ -0,0 +1,1095 @@
+-----------------------------------------------------
+microtemplate.js
+
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+
+-----------------------------------------------------
+jquery-history.js
+
+/**
+ * jQuery history event v0.1
+ * Copyright (c) 2008 Tom Rodenberg <tarodenberg gmail com>
+ * Licensed under the GPL (http://www.gnu.org/licenses/gpl.html) license.
+ */
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
+-----------------------------------------------------
+yui-3.3.0-reset-min.css
+
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 3.3.0
+build: 3167
+*/
+
+
+Software License Agreement (BSD License)
+Copyright (c) 2010, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or
+without modification, are permitted provided that the following conditions are
+met:
+
+    Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+    Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+    Neither the name of Yahoo! Inc. nor the names of its contributors may be
+    used to endorse or promote products derived from this software without
+    specific prior written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Sources of Intellectual Property Included in the YUI Library
+
+YUI is issued by Yahoo! under the BSD license above. Below is a list of certain
+publicly available software that is the source of intellectual property in YUI,
+along with the licensing terms that pertain to thosesources of IP. This list is
+for informational purposes only and is not intended to represent an exhaustive
+list of third party contributions to the YUI.
+
+    Douglas Crockford's JSON parsing and stringifying methods: In the JSON
+    Utility, Douglas Crockford's JSON parsing and stringifying methods are
+    adapted from work published at JSON.org. The adapted work is in the public
+    domain.
+
+    Robert Penner's animation-easing algorithms: In the Animation Utility, YUI
+    makes use of Robert Penner's algorithms for easing.
+
+    Geoff Stearns's SWFObject: In the Charts Control and the Uploader versions
+    through 2.7.0, YUI makes use of Geoff Stearns's SWFObject v1.5 for Flash
+    Player detection and embedding. More information on SWFObject can be found
+    here (http://blog.deconcept.com/swfobject/). SWFObject is (c) 2007 Geoff
+    Stearns and is released under the MIT License
+    (http://www.opensource.org/licenses/mit-license.php).
+
+    Diego Perini's IEContentLoaded technique: The Event Utility employs a
+    technique developed by Diego Perini and licensed under GPL. YUI's use of
+    this technique is included under our BSD license with the author's
+    permission.
+
+
+From MIT license link above:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------------------------------------------------
+customizations.cs
+
+  Except as noted, this content is 
+  licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+  Creative Commons Attribution 2.5</a>.
+
+
+Creative Commons
+Creative Commons Legal Code
+
+Attribution 2.5
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
+SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT
+RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS.
+CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND
+DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
+BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS
+CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.
+
+1. Definitions
+
+    "Collective Work" means a work, such as a periodical issue, anthology or
+    encyclopedia, in which the Work in its entirety in unmodified form, along
+    with a number of other contributions, constituting separate and independent
+    works in themselves, are assembled into a collective whole. A work that
+    constitutes a Collective Work will not be considered a Derivative Work (as
+    defined below) for the purposes of this License.
+
+    "Derivative Work" means a work based upon the Work or upon the Work and
+    other pre-existing works, such as a translation, musical arrangement,
+    dramatization, fictionalization, motion picture version, sound recording,
+    art reproduction, abridgment, condensation, or any other form in which the
+    Work may be recast, transformed, or adapted, except that a work that
+    constitutes a Collective Work will not be considered a Derivative Work for
+    the purpose of this License. For the avoidance of doubt, where the Work is
+    a musical composition or sound recording, the synchronization of the Work
+    in timed-relation with a moving image ("synching") will be considered a
+    Derivative Work for the purpose of this License.
+
+    "Licensor" means the individual or entity that offers the Work under the
+    terms of this License.
+
+    "Original Author" means the individual or entity who created the Work.
+
+    "Work" means the copyrightable work of authorship offered under the terms
+    of this License.
+
+    "You" means an individual or entity exercising rights under this License
+    who has not previously violated the terms of this License with respect to
+    the Work, or who has received express permission from the Licensor to
+    exercise rights under this License despite a previous violation.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or
+restrict any rights arising from fair use, first sale or other limitations on
+the exclusive rights of the copyright owner under copyright law or other
+applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor
+hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the
+duration of the applicable copyright) license to exercise the rights in the
+Work as stated below:
+
+    to reproduce the Work, to incorporate the Work into one or more Collective
+    Works, and to reproduce the Work as incorporated in the Collective Works;
+
+    to create and reproduce Derivative Works;
+
+    to distribute copies or phonorecords of, display publicly, perform
+    publicly, and perform publicly by means of a digital audio transmission the
+    Work including as incorporated in Collective Works;
+
+    to distribute copies or phonorecords of, display publicly, perform
+    publicly, and perform publicly by means of a digital audio transmission
+    Derivative Works.
+
+    For the avoidance of doubt, where the work is a musical composition:
+        Performance Royalties Under Blanket Licenses. Licensor waives the
+        exclusive right to collect, whether individually or via a performance
+        rights society (e.g. ASCAP, BMI, SESAC), royalties for the public
+        performance or public digital performance (e.g. webcast) of the Work.
+
+        Mechanical Rights and Statutory Royalties. Licensor waives the
+        exclusive right to collect, whether individually or via a music rights
+        agency or designated agent (e.g. Harry Fox Agency), royalties for any
+        phonorecord You create from the Work ("cover version") and distribute,
+        subject to the compulsory license created by 17 USC Section 115 of the
+        US Copyright Act (or the equivalent in other jurisdictions).
+
+    Webcasting Rights and Statutory Royalties. For the avoidance of doubt,
+    where the Work is a sound recording, Licensor waives the exclusive right to
+    collect, whether individually or via a performance-rights society (e.g.
+    SoundExchange), royalties for the public digital performance (e.g. webcast)
+    of the Work, subject to the compulsory license created by 17 USC Section
+    114 of the US Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether now known or
+hereafter devised. The above rights include the right to make such
+modifications as are technically necessary to exercise the rights in other
+media and formats. All rights not expressly granted by Licensor are hereby
+reserved.
+
+4. Restrictions.The license granted in Section 3 above is expressly made
+subject to and limited by the following restrictions:
+
+    You may distribute, publicly display, publicly perform, or publicly
+    digitally perform the Work only under the terms of this License, and You
+    must include a copy of, or the Uniform Resource Identifier for, this
+    License with every copy or phonorecord of the Work You distribute, publicly
+    display, publicly perform, or publicly digitally perform. You may not offer
+    or impose any terms on the Work that alter or restrict the terms of this
+    License or the recipients' exercise of the rights granted hereunder. You
+    may not sublicense the Work. You must keep intact all notices that refer to
+    this License and to the disclaimer of warranties. You may not distribute,
+    publicly display, publicly perform, or publicly digitally perform the Work
+    with any technological measures that control access or use of the Work in a
+    manner inconsistent with the terms of this License Agreement. The above
+    applies to the Work as incorporated in a Collective Work, but this does not
+    require the Collective Work apart from the Work itself to be made subject
+    to the terms of this License. If You create a Collective Work, upon notice
+    from any Licensor You must, to the extent practicable, remove from the
+    Collective Work any credit as required by clause 4(b), as requested. If You
+    create a Derivative Work, upon notice from any Licensor You must, to the
+    extent practicable, remove from the Derivative Work any credit as required
+    by clause 4(b), as requested.
+
+    If you distribute, publicly display, publicly perform, or publicly
+    digitally perform the Work or any Derivative Works or Collective Works, You
+    must keep intact all copyright notices for the Work and provide, reasonable
+    to the medium or means You are utilizing: (i) the name of the Original
+    Author (or pseudonym, if applicable) if supplied, and/or (ii) if the
+    Original Author and/or Licensor designate another party or parties (e.g. a
+    sponsor institute, publishing entity, journal) for attribution in
+    Licensor's copyright notice, terms of service or by other reasonable means,
+    the name of such party or parties; the title of the Work if supplied; to
+    the extent reasonably practicable, the Uniform Resource Identifier, if any,
+    that Licensor specifies to be associated with the Work, unless such URI
+    does not refer to the copyright notice or licensing information for the
+    Work; and in the case of a Derivative Work, a credit identifying the use of
+    the Work in the Derivative Work (e.g., "French translation of the Work by
+    Original Author," or "Screenplay based on original Work by Original
+    Author"). Such credit may be implemented in any reasonable manner;
+    provided, however, that in the case of a Derivative Work or Collective
+    Work, at a minimum such credit will appear where any other comparable
+    authorship credit appears and in a manner at least as prominent as such
+    other comparable authorship credit.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS
+THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND
+CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A
+PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
+ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE.
+SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH
+EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN
+NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
+INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS
+LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+    This License and the rights granted hereunder will terminate automatically
+    upon any breach by You of the terms of this License. Individuals or
+    entities who have received Derivative Works or Collective Works from You
+    under this License, however, will not have their licenses terminated
+    provided such individuals or entities remain in full compliance with those
+    licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of
+    this License.
+
+    Subject to the above terms and conditions, the license granted here is
+    perpetual (for the duration of the applicable copyright in the Work).
+    Notwithstanding the above, Licensor reserves the right to release the Work
+    under different license terms or to stop distributing the Work at any time;
+    provided, however that any such election will not serve to withdraw this
+    License (or any other license that has been, or is required to be, granted
+    under the terms of this License), and this License will continue in full
+    force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+    Each time You distribute or publicly digitally perform the Work or a
+    Collective Work, the Licensor offers to the recipient a license to the Work
+    on the same terms and conditions as the license granted to You under this
+    License.
+
+    Each time You distribute or publicly digitally perform a Derivative Work,
+    Licensor offers to the recipient a license to the original Work on the same
+    terms and conditions as the license granted to You under this License.
+
+    If any provision of this License is invalid or unenforceable under
+    applicable law, it shall not affect the validity or enforceability of the
+    remainder of the terms of this License, and without further action by the
+    parties to this agreement, such provision shall be reformed to the minimum
+    extent necessary to make such provision valid and enforceable.
+
+    No term or provision of this License shall be deemed waived and no breach
+    consented to unless such waiver or consent shall be in writing and signed
+    by the party to be charged with such waiver or consent.
+
+    This License constitutes the entire agreement between the parties with
+    respect to the Work licensed here. There are no understandings, agreements
+    or representations with respect to the Work not specified here. Licensor
+    shall not be bound by any additional provisions that may appear in any
+    communication from You. This License may not be modified without the mutual
+    written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no warranty
+whatsoever in connection with the Work. Creative Commons will not be liable to
+You or any party on any legal theory for any damages whatsoever, including
+without limitation any general, special, incidental or consequential damages
+arising in connection to this license. Notwithstanding the foregoing two (2)
+sentences, if Creative Commons has expressly identified itself as the Licensor
+hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is
+licensed under the CCPL, neither party will use the trademark "Creative
+Commons" or any related trademark or logo of Creative Commons without the prior
+written consent of Creative Commons. Any permitted use will be in compliance
+with Creative Commons' then-current trademark usage guidelines, as may be
+published on its website or otherwise made available upon request from time to
+time.
+
+Creative Commons may be contacted at https://creativecommons.org/.
+
+-----------------------------------------------------
+jquery-resizable.min.js
+
+/*
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+
+The MIT License (MIT)
+
+Copyright (c) 2009 John Resig
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------------------------------------------------
+jquery-1.6.2.min.js
+
+/*!
+ * jQuery JavaScript Library v1.6.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Jun 30 14:16:56 2011 -0400
+ */
+
+The MIT License (MIT)
+
+Copyright (c) 2011 John Resig, and The Dojo Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/tools/event_log_tags.bzl b/tools/event_log_tags.bzl
new file mode 100644
index 0000000..35305ae
--- /dev/null
+++ b/tools/event_log_tags.bzl
@@ -0,0 +1,35 @@
+"""Event log tags generation rule"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+
+def _event_log_tags_impl(ctx):
+    out_files = []
+    for logtag_file in ctx.files.srcs:
+        out_filename = paths.replace_extension(logtag_file.basename, ".java")
+        out_file = ctx.actions.declare_file(out_filename)
+        out_files.append(out_file)
+        ctx.actions.run(
+            inputs = [logtag_file],
+            outputs = [out_file],
+            arguments = [
+                "-o",
+                out_file.path,
+                logtag_file.path,
+            ],
+            progress_message = "Generating Java logtag file from %s" % logtag_file.short_path,
+            executable = ctx.executable._logtag_to_java_tool,
+        )
+    return [DefaultInfo(files = depset(out_files))]
+
+event_log_tags = rule(
+    implementation = _event_log_tags_impl,
+    attrs = {
+        "srcs": attr.label_list(allow_files = [".logtags"], mandatory = True),
+        "_logtag_to_java_tool": attr.label(
+            executable = True,
+            cfg = "exec",
+            allow_files = True,
+            default = Label("//build/make/tools:java-event-log-tags"),
+        ),
+    },
+)
diff --git a/tools/event_log_tags.py b/tools/event_log_tags.py
index 35b2de0..a6ae9f1 100644
--- a/tools/event_log_tags.py
+++ b/tools/event_log_tags.py
@@ -55,12 +55,13 @@
     if file_object is None:
       try:
         file_object = open(filename, "rb")
-      except (IOError, OSError), e:
+      except (IOError, OSError) as e:
         self.AddError(str(e))
         return
 
     try:
       for self.linenum, line in enumerate(file_object):
+        line = line.decode('utf-8')
         self.linenum += 1
         line = re.sub('#.*$', '', line) # strip trailing comments
         line = line.strip()
@@ -100,7 +101,7 @@
 
         self.tags.append(Tag(tag, tagname, description,
                              self.filename, self.linenum))
-    except (IOError, OSError), e:
+    except (IOError, OSError) as e:
       self.AddError(str(e))
 
 
@@ -128,8 +129,8 @@
       output_file = "<stdout>"
     else:
       out = open(output_file, "wb")
-    out.write(data)
+    out.write(str.encode(data))
     out.close()
-  except (IOError, OSError), e:
-    print >> sys.stderr, "failed to write %s: %s" % (output_file, e)
+  except (IOError, OSError) as e:
+    print("failed to write %s: %s" % (output_file, e), file=sys.stderr)
     sys.exit(1)
diff --git a/tools/filter-product-graph.py b/tools/filter-product-graph.py
deleted file mode 100755
index b3a5b42..0000000
--- a/tools/filter-product-graph.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-# vim: ts=2 sw=2 nocindent
-
-import re
-import sys
-
-def choose_regex(regs, line):
-  for func,reg in regs:
-    m = reg.match(line)
-    if m:
-      return (func,m)
-  return (None,None)
-
-def gather(included, deps):
-  result = set()
-  for inc in included:
-    result.add(inc)
-    for d in deps:
-      if inc == d[1]:
-        result.add(d[0])
-  return result
-
-def main():
-  deps = []
-  infos = []
-  def dependency(m):
-    deps.append((m.group(1), m.group(2)))
-  def info(m):
-    infos.append((m.group(1), m.group(2)))
-
-  REGS = [
-      (dependency, re.compile(r'"(.*)"\s*->\s*"(.*)"')), 
-      (info, re.compile(r'"(.*)"(\s*\[.*\])')), 
-    ]
-
-  lines = sys.stdin.readlines()
-  lines = [line.strip() for line in lines]
-
-  for line in lines:
-    func,m = choose_regex(REGS, line)
-    if func:
-      func(m)
-
-  # filter
-  sys.stderr.write("argv: " + str(sys.argv) + "\n")
-  if not (len(sys.argv) == 2 and sys.argv[1] == "--all"):
-    targets = sys.argv[1:]
-
-    included = set(targets)
-    prevLen = -1
-    while prevLen != len(included):
-      prevLen = len(included)
-      included = gather(included, deps)
-
-    deps = [dep for dep in deps if dep[1] in included]
-    infos = [info for info in infos if info[0] in included]
-
-  print "digraph {"
-  print "graph [ ratio=.5 ];"
-  for dep in deps:
-    print '"%s" -> "%s"' % dep
-  for info in infos:
-    print '"%s"%s' % info
-  print "}"
-
-
-if __name__ == "__main__":
-  main()
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index 4544e07..8891a0a 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -14,11 +14,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 bootstrap_go_package {
diff --git a/tools/fs_config/Android.mk b/tools/fs_config/Android.mk
index 10d25e0..c36c3aa 100644
--- a/tools/fs_config/Android.mk
+++ b/tools/fs_config/Android.mk
@@ -27,13 +27,29 @@
 system_android_filesystem_config := system/core/libcutils/include/private/android_filesystem_config.h
 system_capability_header := bionic/libc/kernel/uapi/linux/capability.h
 
-# List of supported vendor, oem, odm, vendor_dlkm, odm_dlkm, product and system_ext Partitions
+# Use snapshots if exist
+vendor_android_filesystem_config := $(strip \
+  $(if $(filter-out current,$(BOARD_VNDK_VERSION)), \
+    $(SOONG_VENDOR_$(BOARD_VNDK_VERSION)_SNAPSHOT_DIR)/include/$(system_android_filesystem_config)))
+ifeq (,$(wildcard $(vendor_android_filesystem_config)))
+vendor_android_filesystem_config := $(system_android_filesystem_config)
+endif
+
+vendor_capability_header := $(strip \
+  $(if $(filter-out current,$(BOARD_VNDK_VERSION)), \
+    $(SOONG_VENDOR_$(BOARD_VNDK_VERSION)_SNAPSHOT_DIR)/include/$(system_capability_header)))
+ifeq (,$(wildcard $(vendor_capability_header)))
+vendor_capability_header := $(system_capability_header)
+endif
+
+# List of supported vendor, oem, odm, vendor_dlkm, odm_dlkm, and system_dlkm Partitions
 fs_config_generate_extra_partition_list := $(strip \
   $(if $(BOARD_USES_VENDORIMAGE)$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),vendor) \
   $(if $(BOARD_USES_OEMIMAGE)$(BOARD_OEMIMAGE_FILE_SYSTEM_TYPE),oem) \
   $(if $(BOARD_USES_ODMIMAGE)$(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE),odm) \
   $(if $(BOARD_USES_VENDOR_DLKMIMAGE)$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE),vendor_dlkm) \
   $(if $(BOARD_USES_ODM_DLKMIMAGE)$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE),odm_dlkm) \
+  $(if $(BOARD_USES_SYSTEM_DLKMIMAGE)$(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE),system_dlkm) \
 )
 
 ##################################
@@ -42,8 +58,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_dirs
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := \
   fs_config_dirs_system \
   fs_config_dirs_system_ext \
@@ -57,8 +74,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_files
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := \
   fs_config_files_system \
   fs_config_files_system_ext \
@@ -73,8 +91,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_dirs_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_system_ext)
 include $(BUILD_PHONY_PACKAGE)
 
@@ -85,8 +104,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_files_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_system_ext)
 include $(BUILD_PHONY_PACKAGE)
 
@@ -97,8 +117,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_dirs_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_product)
 include $(BUILD_PHONY_PACKAGE)
 
@@ -109,8 +130,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_files_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_product)
 include $(BUILD_PHONY_PACKAGE)
 
@@ -121,8 +143,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_dirs_nonsystem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_dirs_$(t))
 include $(BUILD_PHONY_PACKAGE)
 
@@ -133,8 +156,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_files_nonsystem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_files_$(t))
 include $(BUILD_PHONY_PACKAGE)
 
@@ -145,8 +169,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_dirs_system
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 include $(BUILD_SYSTEM)/base_rules.mk
@@ -172,8 +197,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := fs_config_files_system
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 include $(BUILD_SYSTEM)/base_rules.mk
@@ -200,16 +226,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_vendor
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -226,16 +253,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_vendor
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -255,8 +283,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_oem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
@@ -281,8 +310,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_oem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
@@ -310,16 +340,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_odm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -336,16 +367,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_odm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -365,16 +397,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_vendor_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -391,16 +424,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_vendor_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -420,16 +454,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_odm_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -446,16 +481,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_odm_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
 include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config)
-$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
 $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
-$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
 	@mkdir -p $(dir $@)
 	$< fsconfig \
 	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
@@ -467,6 +503,63 @@
 
 endif
 
+ifneq ($(filter system_dlkm,$(fs_config_generate_extra_partition_list)),)
+##################################
+# Generate the system_dlkm/etc/fs_config_dirs binary file for the target
+# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES
+# in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := _fs_config_dirs_system_dlkm
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
+LOCAL_MODULE_CLASS := ETC
+LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
+	@mkdir -p $(dir $@)
+	$< fsconfig \
+	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
+	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
+	   --partition system_dlkm \
+	   --dirs \
+	   --out_file $@ \
+	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
+
+##################################
+# Generate the system_dlkm/etc/fs_config_files binary file for the target
+# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES
+# in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := _fs_config_files_system_dlkm
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
+LOCAL_MODULE_CLASS := ETC
+LOCAL_INSTALLED_MODULE_STEM := fs_config_files
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
+	@mkdir -p $(dir $@)
+	$< fsconfig \
+	   --aid-header $(PRIVATE_ANDROID_FS_HDR) \
+	   --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
+	   --partition system_dlkm \
+	   --files \
+	   --out_file $@ \
+	   $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
+
+endif
+
 ifneq ($(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),)
 ##################################
 # Generate the product/etc/fs_config_dirs binary file for the target
@@ -475,8 +568,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc
@@ -501,8 +595,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc
@@ -529,8 +624,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_dirs_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
 LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc
@@ -555,8 +651,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := _fs_config_files_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
 LOCAL_MODULE_CLASS := ETC
 LOCAL_INSTALLED_MODULE_STEM := fs_config_files
 LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc
diff --git a/tools/fs_get_stats/Android.bp b/tools/fs_get_stats/Android.bp
index 9457de4..0697999 100644
--- a/tools/fs_get_stats/Android.bp
+++ b/tools/fs_get_stats/Android.bp
@@ -1,10 +1,6 @@
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_binary_host {
diff --git a/tools/generate-notice-files.py b/tools/generate-notice-files.py
index bf958fb..5e3010f 100755
--- a/tools/generate-notice-files.py
+++ b/tools/generate-notice-files.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2012 The Android Open Source Project
 #
@@ -30,20 +30,18 @@
 import os
 import os.path
 import re
+import struct
 import sys
 
 MD5_BLOCKSIZE = 1024 * 1024
 HTML_ESCAPE_TABLE = {
-    "&": "&amp;",
-    '"': "&quot;",
-    "'": "&apos;",
-    ">": "&gt;",
-    "<": "&lt;",
+    b"&": b"&amp;",
+    b'"': b"&quot;",
+    b"'": b"&apos;",
+    b">": b"&gt;",
+    b"<": b"&lt;",
     }
 
-def hexify(s):
-    return ("%02x"*len(s)) % tuple(map(ord, s))
-
 def md5sum(filename):
     """Calculate an MD5 of the file given by FILENAME,
     and return hex digest as a string.
@@ -57,20 +55,26 @@
             break
         sum.update(block)
     f.close()
-    return hexify(sum.digest())
+    return sum.hexdigest()
 
 
 def html_escape(text):
     """Produce entities within text."""
-    return "".join(HTML_ESCAPE_TABLE.get(c,c) for c in 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="""
+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_dirs, output_filename):
@@ -90,13 +94,13 @@
     # Open the output file, and output the header pieces
     output_file = open(output_filename, "wb")
 
-    print >> output_file, "<html><head>"
-    print >> output_file, HTML_OUTPUT_CSS
-    print >> output_file, '</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">'
+    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
-    print >> output_file, '<div class="toc">'
-    print >> output_file, "<ul>"
+    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))
@@ -104,31 +108,29 @@
     # Print out a nice table of contents
     for filename in sorted_filenames:
         stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<li><a href="#id%d">%s</a></li>' % (id_table.get(filename), stripped_filename)
+        output_file.write(('<li><a href="#id%d">%s</a></li>\n' % (id_table.get(filename), stripped_filename)).encode())
 
-    print >> output_file, "</ul>"
-    print >> output_file, "</div><!-- table of contents -->"
+    output_file.write(b"</ul>\n")
+    output_file.write(b"</div><!-- table of contents -->\n")
     # Output the individual notice file lists
-    print >>output_file, '<table cellpadding="0" cellspacing="0" border="0">'
+    output_file.write(b'<table cellpadding="0" cellspacing="0" border="0">\n')
     for value in file_hash:
-        print >> output_file, '<tr id="id%d"><td class="same-license">' % id_table.get(value[0])
-        print >> output_file, '<div class="label">Notices for file(s):</div>'
-        print >> output_file, '<div class="file-list">'
+        output_file.write(b'<tr id="id%d"><td class="same-license">\n' % id_table.get(value[0]))
+        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:
-            print >> output_file, "%s <br/>" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))
-        print >> output_file, "</div><!-- file-list -->"
-        print >> output_file
-        print >> output_file, '<pre class="license-text">'
-        print >> output_file, html_escape(open(value[0]).read())
-        print >> output_file, "</pre><!-- license-text -->"
-        print >> output_file, "</td></tr><!-- same-license -->"
-        print >> output_file
-        print >> output_file
-        print >> output_file
+            output_file.write(("%s <br/>\n" % SRC_DIR_STRIP_RE.sub(r"\1", filename)).encode())
+        output_file.write(b"</div><!-- file-list -->\n")
+        output_file.write(b"\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
-    print >> output_file, "</table>"
-    print >> output_file, "</body></html>"
+    output_file.write(b"</table>\n")
+    output_file.write(b"</body></html>\n")
     output_file.close()
 
 def combine_notice_files_text(file_hash, input_dirs, output_filename, file_title):
@@ -136,14 +138,18 @@
 
     SRC_DIR_STRIP_RE = re.compile("(?:" + "|".join(input_dirs) + ")(/.*).txt")
     output_file = open(output_filename, "wb")
-    print >> output_file, file_title
+    output_file.write(file_title.encode())
+    output_file.write(b"\n")
     for value in file_hash:
-      print >> output_file, "============================================================"
-      print >> output_file, "Notices for file(s):"
-      for filename in value:
-        print >> output_file, SRC_DIR_STRIP_RE.sub(r"\1", filename)
-      print >> output_file, "------------------------------------------------------------"
-      print >> output_file, open(value[0]).read()
+        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_dirs, output_filename):
@@ -154,15 +160,15 @@
     # 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 in files_with_same_hash.keys():
-        for filename in files_with_same_hash[file_key]:
+    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")
 
-    print >> output_file, '<?xml version="1.0" encoding="utf-8"?>'
-    print >> output_file, "<licenses>"
+    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(id_table.keys())
@@ -170,10 +176,8 @@
     # Print out a nice table of contents
     for filename in sorted_filenames:
         stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<file-name contentId="%s">%s</file-name>' % (id_table.get(filename), stripped_filename)
-
-    print >> output_file
-    print >> output_file
+        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
@@ -183,11 +187,13 @@
             continue
         processed_file_keys.append(file_key)
 
-        print >> output_file, '<file-content contentId="%s"><![CDATA[%s]]></file-content>' % (file_key, html_escape(open(filename).read()))
-        print >> output_file
+        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
-    print >> output_file, "</licenses>"
+    output_file.write(b"</licenses>\n")
     output_file.close()
 
 def get_args():
@@ -254,7 +260,7 @@
                     file_md5sum = md5sum(filename)
                     files_with_same_hash[file_md5sum].append(filename)
 
-    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(files_with_same_hash.keys())]
+    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(list(files_with_same_hash))]
     combine_notice_files_text(filesets, input_dirs, txt_output_file, file_title)
 
     if html_output_file is not None:
diff --git a/tools/generate-self-extracting-archive.py b/tools/generate-self-extracting-archive.py
index 5b0628d..c9f56cb 100755
--- a/tools/generate-self-extracting-archive.py
+++ b/tools/generate-self-extracting-archive.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2019 The Android Open Source Project
 #
@@ -120,7 +120,7 @@
 
 def main(argv):
   if len(argv) != 5:
-    print 'generate-self-extracting-archive.py expects exactly 4 arguments'
+    print('generate-self-extracting-archive.py expects exactly 4 arguments')
     sys.exit(1)
 
   output_filename = argv[1]
@@ -134,11 +134,11 @@
     license = license_file.read()
 
   if not license:
-    print 'License file was empty'
+    print('License file was empty')
     sys.exit(1)
 
   if 'SOFTWARE LICENSE AGREEMENT' not in license:
-    print 'License does not look like a license'
+    print('License does not look like a license')
     sys.exit(1)
 
   comment_line = '# %s\n' % comment
diff --git a/tools/java-event-log-tags.py b/tools/java-event-log-tags.py
index 37cd712..4bd6d2b 100755
--- a/tools/java-event-log-tags.py
+++ b/tools/java-event-log-tags.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2009 The Android Open Source Project
 #
@@ -23,7 +23,7 @@
 -h to display this usage message and exit.
 """
 
-import cStringIO
+from io import StringIO
 import getopt
 import os
 import os.path
@@ -36,24 +36,24 @@
 
 try:
   opts, args = getopt.getopt(sys.argv[1:], "ho:")
-except getopt.GetoptError, err:
-  print str(err)
-  print __doc__
+except getopt.GetoptError as err:
+  print(str(err))
+  print(__doc__)
   sys.exit(2)
 
 for o, a in opts:
   if o == "-h":
-    print __doc__
+    print(__doc__)
     sys.exit(2)
   elif o == "-o":
     output_file = a
   else:
-    print >> sys.stderr, "unhandled option %s" % (o,)
+    print("unhandled option %s" % (o,), file=sys.stderr)
     sys.exit(1)
 
 if len(args) != 1 and len(args) != 2:
-  print "need one or two input files, not %d" % (len(args),)
-  print __doc__
+  print("need one or two input files, not %d" % (len(args),))
+  print(__doc__)
   sys.exit(1)
 
 fn = args[0]
@@ -92,10 +92,10 @@
 
 if tagfile.errors:
   for fn, ln, msg in tagfile.errors:
-    print >> sys.stderr, "%s:%d: error: %s" % (fn, ln, msg)
+    print("%s:%d: error: %s" % (fn, ln, msg), file=sys.stderr)
   sys.exit(1)
 
-buffer = cStringIO.StringIO()
+buffer = StringIO()
 buffer.write("/* This file is auto-generated.  DO NOT MODIFY.\n"
              " * Source file: %s\n"
              " */\n\n" % (fn,))
diff --git a/tools/libhost/Android.bp b/tools/libhost/Android.bp
index a83f2e7..cd99af8 100644
--- a/tools/libhost/Android.bp
+++ b/tools/libhost/Android.bp
@@ -1,10 +1,6 @@
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "build_make_license"
-    // to get the below license kinds:
-    //   legacy_restricted
-    default_applicable_licenses: ["build_make_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 cc_library_host_static {
diff --git a/tools/merge-event-log-tags.py b/tools/merge-event-log-tags.py
index 64bad3f..292604c 100755
--- a/tools/merge-event-log-tags.py
+++ b/tools/merge-event-log-tags.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2009 The Android Open Source Project
 #
@@ -24,7 +24,7 @@
 -h to display this usage message and exit.
 """
 
-import cStringIO
+from io import StringIO
 import getopt
 try:
   import hashlib
@@ -48,21 +48,21 @@
 
 try:
   opts, args = getopt.getopt(sys.argv[1:], "ho:m:")
-except getopt.GetoptError, err:
-  print str(err)
-  print __doc__
+except getopt.GetoptError as err:
+  print(str(err))
+  print(__doc__)
   sys.exit(2)
 
 for o, a in opts:
   if o == "-h":
-    print __doc__
+    print(__doc__)
     sys.exit(2)
   elif o == "-o":
     output_file = a
   elif o == "-m":
     pre_merged_file = a
   else:
-    print >> sys.stderr, "unhandled option %s" % (o,)
+    print("unhandled option %s" % (o,), file=sys.stderr)
     sys.exit(1)
 
 # Restrictions on tags:
@@ -133,12 +133,12 @@
 
 if errors:
   for fn, ln, msg in errors:
-    print >> sys.stderr, "%s:%d: error: %s" % (fn, ln, msg)
+    print("%s:%d: error: %s" % (fn, ln, msg), file=sys.stderr)
   sys.exit(1)
 
 if warnings:
   for fn, ln, msg in warnings:
-    print >> sys.stderr, "%s:%d: warning: %s" % (fn, ln, msg)
+    print("%s:%d: warning: %s" % (fn, ln, msg), file=sys.stderr)
 
 # Python's hash function (a) isn't great and (b) varies between
 # versions of python.  Using md5 is overkill here but is the same from
@@ -154,14 +154,14 @@
 # If we were provided pre-merged tags (w/ the -m option), then don't
 # ever try to allocate one, just fail if we don't have a number
 
-for name, t in sorted(by_tagname.iteritems()):
+for name, t in sorted(by_tagname.items()):
   if t.tagnum is None:
     if pre_merged_tags:
       try:
         t.tagnum = pre_merged_tags[t.tagname]
       except KeyError:
-        print >> sys.stderr, ("Error: Tag number not defined for tag `%s'."
-            +" Have you done a full build?") % t.tagname
+        print("Error: Tag number not defined for tag `%s'. Have you done a full build?" % t.tagname,
+              file=sys.stderr)
         sys.exit(1)
     else:
       while True:
@@ -174,8 +174,8 @@
 
 # by_tagnum should be complete now; we've assigned numbers to all tags.
 
-buffer = cStringIO.StringIO()
-for n, t in sorted(by_tagnum.iteritems()):
+buffer = StringIO()
+for n, t in sorted(by_tagnum.items()):
   if t.description:
     buffer.write("%d %s %s\n" % (t.tagnum, t.tagname, t.description))
   else:
diff --git a/tools/mk2bp_catalog.py b/tools/mk2bp_catalog.py
index c2afb9b..3fc6236 100755
--- a/tools/mk2bp_catalog.py
+++ b/tools/mk2bp_catalog.py
@@ -308,19 +308,31 @@
     print("""<th class="Count Warning">%s</th>""" % analyzer.title)
   print("      </tr>")
 
+# get all modules in $(PRODUCT_PACKAGE) and the corresponding deps
+def get_module_product_packages_plus_deps(initial_modules, result, soong_data):
+  for module in initial_modules:
+    if module in result:
+      continue
+    result.add(module)
+    if module in soong_data.deps:
+      get_module_product_packages_plus_deps(soong_data.deps[module], result, soong_data)
+
 def main():
   parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
   parser.add_argument("--device", type=str, required=True,
                       help="TARGET_DEVICE")
+  parser.add_argument("--product-packages", type=argparse.FileType('r'),
+                      default=None,
+                      help="PRODUCT_PACKAGES")
   parser.add_argument("--title", type=str,
                       help="page title")
   parser.add_argument("--codesearch", type=str,
                       default="https://cs.android.com/android/platform/superproject/+/master:",
                       help="page title")
-  parser.add_argument("--out_dir", type=str,
+  parser.add_argument("--out-dir", type=str,
                       default=None,
                       help="Equivalent of $OUT_DIR, which will also be checked if"
-                        + " --out_dir is unset. If neither is set, default is"
+                        + " --out-dir is unset. If neither is set, default is"
                         + " 'out'.")
   parser.add_argument("--mode", type=str,
                       default="html",
@@ -354,16 +366,25 @@
       continue
     all_makefiles[filename] = Makefile(filename)
 
+  # Get all the modules in $(PRODUCT_PACKAGES) and the correspoding deps
+  product_package_modules_plus_deps = set()
+  if args.product_packages:
+    product_package_top_modules = args.product_packages.read().strip().split('\n')
+    get_module_product_packages_plus_deps(product_package_top_modules, product_package_modules_plus_deps, soong)
+
   if args.mode == "html":
-    HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+    HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
+        product_packages_modules=product_package_modules_plus_deps).execute()
   elif args.mode == "csv":
-    CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+    CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
+        product_packages_modules=product_package_modules_plus_deps).execute()
 
 class HtmlProcessor(object):
-  def __init__(self, args, soong, all_makefiles):
+  def __init__(self, args, soong, all_makefiles, product_packages_modules):
     self.args = args
     self.soong = soong
     self.all_makefiles = all_makefiles
+    self.product_packages_modules = product_packages_modules
     self.annotations = Annotations()
 
   def execute(self):
@@ -376,6 +397,8 @@
     modules_by_partition = dict()
     partitions = set()
     for installed, module in self.soong.installed.items():
+      if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
+        continue
       partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
       modules_by_partition.setdefault(partition, []).append(module)
       partitions.add(partition)
@@ -985,10 +1008,11 @@
       return "";
 
 class CsvProcessor(object):
-  def __init__(self, args, soong, all_makefiles):
+  def __init__(self, args, soong, all_makefiles, product_packages_modules):
     self.args = args
     self.soong = soong
     self.all_makefiles = all_makefiles
+    self.product_packages_modules = product_packages_modules
 
   def execute(self):
     csvout = csv.writer(sys.stdout)
@@ -1004,6 +1028,8 @@
     for filename in sorted(self.all_makefiles.keys()):
       makefile = self.all_makefiles[filename]
       for module in self.soong.reverse_makefiles[filename]:
+        if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
+          continue
         row = [filename, module]
         # Partitions
         row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT,
diff --git a/tools/mk2bp_partition.py b/tools/mk2bp_partition.py
new file mode 100644
index 0000000..30c1135
--- /dev/null
+++ b/tools/mk2bp_partition.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+"""
+The complete list of the remaining Make files in each partition for all lunch targets
+
+How to run?
+python3 $(path-to-file)/mk2bp_partition.py
+"""
+
+from pathlib import Path
+
+import csv
+import datetime
+import os
+import shutil
+import subprocess
+import sys
+import time
+
+def get_top():
+  path = '.'
+  while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')):
+    if os.path.abspath(path) == '/':
+      sys.exit('Could not find android source tree root.')
+    path = os.path.join(path, '..')
+  return os.path.abspath(path)
+
+# get the values of a build variable
+def get_build_var(variable, product, build_variant):
+  """Returns the result of the shell command get_build_var."""
+  env = {
+      **os.environ,
+      'TARGET_PRODUCT': product if product else '',
+      'TARGET_BUILD_VARIANT': build_variant if build_variant else '',
+  }
+  return subprocess.run([
+      'build/soong/soong_ui.bash',
+      '--dumpvar-mode',
+      variable
+  ], check=True, capture_output=True, env=env, text=True).stdout.strip()
+
+def get_make_file_partitions():
+    lunch_targets = set(get_build_var("all_named_products", "", "").split())
+    total_lunch_targets = len(lunch_targets)
+    makefile_by_partition = dict()
+    partitions = set()
+    current_count = 0
+    start_time = time.time()
+    # cannot run command `m lunch_target`
+    broken_targets = {"mainline_sdk", "ndk"}
+    for lunch_target in sorted(lunch_targets):
+        current_count += 1
+        current_time = time.time()
+        print (current_count, "/", total_lunch_targets, lunch_target, datetime.timedelta(seconds=current_time - start_time))
+        if lunch_target in broken_targets:
+            continue
+        installed_product_out = get_build_var("PRODUCT_OUT", lunch_target, "userdebug")
+        filename = os.path.join(installed_product_out, "mk2bp_remaining.csv")
+        copy_filename = os.path.join(installed_product_out, lunch_target + "_mk2bp_remaining.csv")
+        # only generate if not exists
+        if not os.path.exists(copy_filename):
+            bash_cmd = "bash build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=" + lunch_target
+            bash_cmd += " TARGET_BUILD_VARIANT=userdebug " + filename
+            subprocess.run(bash_cmd, shell=True, text=True, check=True, stdout=subprocess.DEVNULL)
+            # generate a copied .csv file, to avoid possible overwritings
+            with open(copy_filename, "w") as file:
+                shutil.copyfile(filename, copy_filename)
+
+        # open mk2bp_remaining.csv file
+        with open(copy_filename, "r") as csvfile:
+            reader = csv.reader(csvfile, delimiter=",", quotechar='"')
+            # bypass the header row
+            next(reader, None)
+            for row in reader:
+                # read partition information
+                partition = row[2]
+                makefile_by_partition.setdefault(partition, set()).add(row[0])
+                partitions.add(partition)
+
+    # write merged make file list for each partition into a csv file
+    installed_path = Path(installed_product_out).parents[0].as_posix()
+    csv_path = installed_path + "/mk2bp_partition.csv"
+    with open(csv_path, "wt") as csvfile:
+        writer = csv.writer(csvfile, delimiter=",")
+        count_makefile = 0
+        for partition in sorted(partitions):
+            number_file = len(makefile_by_partition[partition])
+            count_makefile += number_file
+            writer.writerow([partition, number_file])
+            for makefile in sorted(makefile_by_partition[partition]):
+                writer.writerow([makefile])
+        row = ["The total count of make files is ", count_makefile]
+        writer.writerow(row)
+
+def main():
+    os.chdir(get_top())
+    get_make_file_partitions()
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/post_process_props.py b/tools/post_process_props.py
index efbf614..38d17a8 100755
--- a/tools/post_process_props.py
+++ b/tools/post_process_props.py
@@ -38,11 +38,6 @@
       else:
         val = val + ",adb"
       prop_list.put("persist.sys.usb.config", val)
-  # UsbDeviceManager expects a value here.  If it doesn't get it, it will
-  # default to "adb". That might not the right policy there, but it's better
-  # to be explicit.
-  if not prop_list.get_value("persist.sys.usb.config"):
-    prop_list.put("persist.sys.usb.config", "none")
 
 def validate_grf_props(prop_list, sdk_version):
   """Validate GRF properties if exist.
diff --git a/tools/product_debug.py b/tools/product_debug.py
deleted file mode 100755
index ff2657c..0000000
--- a/tools/product_debug.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-
-import os
-import re
-import sys
-
-def break_lines(key, val):
-  # these don't get split
-  if key in ("PRODUCT_MODEL"):
-    return (key,val)
-  return (key, "\n".join(val.split()))
-
-def split_line(line):
-  words = line.split("=", 1)
-  if len(words) == 1:
-    return (words[0], "")
-  else:
-    return (words[0], words[1])
-
-def sort_lines(text):
-  lines = text.split()
-  lines.sort()
-  return "\n".join(lines)
-
-def parse_variables(lines):
-  return [split_line(line) for line in lines if line.strip()]
-
-def render_variables(variables):
-  variables = dict(variables)
-  del variables["FILE"]
-  variables = list(variables.iteritems())
-  variables.sort(lambda a, b: cmp(a[0], b[0]))
-  return ("<table id='variables'>"
-      + "\n".join([ "<tr><th>%(key)s</th><td>%(val)s</td></tr>" % { "key": key, "val": val }
-        for key,val in variables])
-      +"</table>")
-
-def linkify_inherit(variables, text, func_name):
-  groups = re.split("(\\$\\(call " + func_name + ",.*\\))", text)
-  result = ""
-  for i in range(0,len(groups)/2):
-    i = i * 2
-    result = result + groups[i]
-    s = groups[i+1]
-    href = s.split(",", 1)[1].strip()[:-1]
-    href = href.replace("$(SRC_TARGET_DIR)", "build/target")
-    href = ("../" * variables["FILE"].count("/")) + href + ".html"
-    result = result + "<a href=\"%s\">%s</a>" % (href,s)
-  result = result + groups[-1]
-  return result
-
-def render_original(variables, text):
-  text = linkify_inherit(variables, text, "inherit-product")
-  text = linkify_inherit(variables, text, "inherit-product-if-exists")
-  return text
-
-def read_file(fn):
-  f = file(fn)
-  text = f.read()
-  f.close()
-  return text
-
-def main(argv):
-  # read the variables
-  lines = sys.stdin.readlines()
-  variables = parse_variables(lines)
-
-  # format the variables
-  variables = [break_lines(key,val) for key,val in variables]
-
-  # now it's a dict
-  variables = dict(variables)
-
-  sorted_vars = (
-      "PRODUCT_COPY_FILES",
-      "PRODUCT_PACKAGES",
-      "PRODUCT_LOCALES",
-      "PRODUCT_PROPERTY_OVERRIDES",
-    )
-
-  for key in sorted_vars:
-    variables[key] = sort_lines(variables[key])
-
-  # the original file
-  original = read_file(variables["FILE"])
-
-  # formatting
-  values = dict(variables)
-  values.update({
-    "variables": render_variables(variables),
-    "original": render_original(variables, original),
-  })
-  print """<html>
-
-
-<head>
-  <title>%(FILE)s</title>
-  <style type="text/css">
-    body {
-      font-family: Helvetica, Arial, sans-serif;
-      padding-bottom: 20px;
-    }
-    #variables {
-      border-collapse: collapse;
-    }
-    #variables th, #variables td {
-      vertical-align: top;
-      text-align: left;
-      border-top: 1px solid #c5cdde;
-      border-bottom: 1px solid #c5cdde;
-      padding: 2px 10px 2px 10px;
-    }
-    #variables th {
-      font-size: 10pt;
-      background-color: #e2ecff
-    }
-    #variables td {
-      background-color: #ebf2ff;
-      white-space: pre;
-      font-size: 10pt;
-    }
-    #original {
-      background-color: #ebf2ff;
-      border-top: 1px solid #c5cdde;
-      border-bottom: 1px solid #c5cdde;
-      padding: 2px 10px 2px 10px;
-      white-space: pre;
-      font-size: 10pt;
-    }
-  </style>
-</head>
-<body>
-<h1>%(FILE)s</h1>
-<a href="#Original">Original</a>
-<a href="#Variables">Variables</a>
-<h2><a name="Original"></a>Original</h2>
-<div id="original">%(original)s</div>
-<h2><a name="Variables"></a>Variables</h2>
-%(variables)s
-</body>
-</html>
-""" % values
-
-if __name__ == "__main__":
-  main(sys.argv)
diff --git a/tools/rbcrun/README.md b/tools/rbcrun/README.md
index fb58c89..7f40597 100644
--- a/tools/rbcrun/README.md
+++ b/tools/rbcrun/README.md
@@ -68,6 +68,11 @@
 
 Returns `True`  if *file* exists
 
+#### rblf_find_files(*top*, *file-pattern*, only_files = 0)
+
+Returns all the paths under *top* whose basename matches *pattern* (which is a shell's glob pattern). If *only_files* is
+not zero, only the paths to the regular files are returned. The returned paths are relative to *top*.
+
 #### rblf_wildcard(*glob*, *top* = None)
 
 Expands *glob*. If *top* is supplied, expands "*top*/*glob*", then removes
@@ -82,3 +87,7 @@
 Runs `sh -c "`*command*`"`, reads its output, converts all newlines into spaces, chops trailing newline returns this
 string. This is equivalent to Make's
 `shell` builtin function. *This function will be eventually removed*.
+
+#### rblf_log(*arg*,..., sep=' ')
+
+Same as `print` builtin but writes to stderr.
\ No newline at end of file
diff --git a/tools/rbcrun/cmd/rbcrun.go b/tools/rbcrun/cmd/rbcrun.go
index 7848562..4db6a0b 100644
--- a/tools/rbcrun/cmd/rbcrun.go
+++ b/tools/rbcrun/cmd/rbcrun.go
@@ -93,6 +93,6 @@
 }
 
 func quit(format string, s ...interface{}) {
-	fmt.Fprintln(os.Stderr, format, s)
+	fmt.Fprintf(os.Stderr, format, s...)
 	os.Exit(2)
 }
diff --git a/tools/rbcrun/host.go b/tools/rbcrun/host.go
index 1e43334..c6e89f0 100644
--- a/tools/rbcrun/host.go
+++ b/tools/rbcrun/host.go
@@ -16,10 +16,10 @@
 
 import (
 	"fmt"
+	"io/fs"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"regexp"
 	"strings"
 
 	"go.starlark.net/starlark"
@@ -118,29 +118,12 @@
 	if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &path); err != nil {
 		return starlark.None, err
 	}
-	if stat, err := os.Stat(path); err != nil || stat.IsDir() {
+	if _, err := os.Stat(path); err != nil {
 		return starlark.False, nil
 	}
 	return starlark.True, nil
 }
 
-// regexMatch(pattern, s) returns True if s matches pattern (a regex)
-func regexMatch(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
-	kwargs []starlark.Tuple) (starlark.Value, error) {
-	var pattern, s string
-	if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 2, &pattern, &s); err != nil {
-		return starlark.None, err
-	}
-	match, err := regexp.MatchString(pattern, s)
-	if err != nil {
-		return starlark.None, err
-	}
-	if match {
-		return starlark.True, nil
-	}
-	return starlark.False, nil
-}
-
 // wildcard(pattern, top=None) expands shell's glob pattern. If 'top' is present,
 // the 'top/pattern' is globbed and then 'top/' prefix is removed.
 func wildcard(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
@@ -170,6 +153,46 @@
 	return makeStringList(files), nil
 }
 
+// find(top, pattern, only_files = 0) returns all the paths under 'top'
+// whose basename matches 'pattern' (which is a shell's glob pattern).
+// If 'only_files' is non-zero, only the paths to the regular files are
+// returned. The returned paths are relative to 'top'.
+func find(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple,
+	kwargs []starlark.Tuple) (starlark.Value, error) {
+	var top, pattern string
+	var onlyFiles int
+	if err := starlark.UnpackArgs(b.Name(), args, kwargs,
+		"top", &top, "pattern", &pattern, "only_files?", &onlyFiles); err != nil {
+		return starlark.None, err
+	}
+	top = filepath.Clean(top)
+	pattern = filepath.Clean(pattern)
+	// Go's filepath.Walk is slow, consider using OS's find
+	var res []string
+	err := filepath.WalkDir(top, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			if d != nil && d.IsDir() {
+				return fs.SkipDir
+			} else {
+				return nil
+			}
+		}
+		relPath := strings.TrimPrefix(path, top)
+		if len(relPath) > 0 && relPath[0] == os.PathSeparator {
+			relPath = relPath[1:]
+		}
+		// Do not return top-level dir
+		if len(relPath) == 0 {
+			return nil
+		}
+		if matched, err := filepath.Match(pattern, d.Name()); err == nil && matched && (onlyFiles == 0 || d.Type().IsRegular()) {
+			res = append(res, relPath)
+		}
+		return nil
+	})
+	return makeStringList(res), err
+}
+
 // shell(command) runs OS shell with given command and returns back
 // its output the same way as Make's $(shell ) function. The end-of-lines
 // ("\n" or "\r\n") are replaced with " " in the result, and the trailing
@@ -218,6 +241,28 @@
 	return starlarkstruct.FromStringDict(starlarkstruct.Default, sd)
 }
 
+func log(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
+	sep := " "
+	if err := starlark.UnpackArgs("print", nil, kwargs, "sep?", &sep); err != nil {
+		return nil, err
+	}
+	for i, v := range args {
+		if i > 0 {
+			fmt.Fprint(os.Stderr, sep)
+		}
+		if s, ok := starlark.AsString(v); ok {
+			fmt.Fprint(os.Stderr, s)
+		} else if b, ok := v.(starlark.Bytes); ok {
+			fmt.Fprint(os.Stderr, string(b))
+		} else {
+			fmt.Fprintf(os.Stderr, "%s", v)
+		}
+	}
+
+	fmt.Fprintln(os.Stderr)
+	return starlark.None, nil
+}
+
 func setup(env []string) {
 	// Create the symbols that aid makefile conversion. See README.md
 	builtins = starlark.StringDict{
@@ -226,10 +271,12 @@
 		"rblf_env": structFromEnv(os.Environ()),
 		// To convert makefile's $(wildcard foo)
 		"rblf_file_exists": starlark.NewBuiltin("rblf_file_exists", fileExists),
-		// To convert makefile's $(filter ...)/$(filter-out)
-		"rblf_regex": starlark.NewBuiltin("rblf_regex", regexMatch),
+		// To convert find-copy-subdir and product-copy-files-by pattern
+		"rblf_find_files": starlark.NewBuiltin("rblf_find_files", find),
 		// To convert makefile's $(shell cmd)
 		"rblf_shell": starlark.NewBuiltin("rblf_shell", shell),
+		// Output to stderr
+		"rblf_log": starlark.NewBuiltin("rblf_log", log),
 		// To convert makefile's $(wildcard foo*)
 		"rblf_wildcard": starlark.NewBuiltin("rblf_wildcard", wildcard),
 	}
diff --git a/tools/rbcrun/host_test.go b/tools/rbcrun/host_test.go
index 3be5ee6..97f6ce9 100644
--- a/tools/rbcrun/host_test.go
+++ b/tools/rbcrun/host_test.go
@@ -147,10 +147,6 @@
 	}
 }
 
-func TestRegex(t *testing.T) {
-	exerciseStarlarkTestFile(t, "testdata/regex.star")
-}
-
 func TestShell(t *testing.T) {
 	if err := os.Setenv("TEST_DATA_DIR", dataDir()); err != nil {
 		t.Fatal(err)
diff --git a/tools/rbcrun/testdata/file_ops.star b/tools/rbcrun/testdata/file_ops.star
index e1f1ac2..50e39bf 100644
--- a/tools/rbcrun/testdata/file_ops.star
+++ b/tools/rbcrun/testdata/file_ops.star
@@ -4,15 +4,22 @@
 
 def test():
     myname = "file_ops.star"
+    assert.true(rblf_file_exists("."), "./ exists ")
     assert.true(rblf_file_exists(myname), "the file %s does exist" % myname)
     assert.true(not rblf_file_exists("no_such_file"), "the file no_such_file does not exist")
     files = rblf_wildcard("*.star")
     assert.true(myname in files, "expected %s in  %s" % (myname, files))
-    # RBCDATADIR is set by the caller to the path where this file resides
     files = rblf_wildcard("*.star", rblf_env.TEST_DATA_DIR)
     assert.true(myname in files, "expected %s in %s" % (myname, files))
     files = rblf_wildcard("*.xxx")
     assert.true(len(files) == 0, "expansion should be empty but contains %s" % files)
-
-
+    mydir = "testdata"
+    myrelname = "%s/%s" % (mydir, myname)
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*")
+    assert.true(mydir in files and myrelname in files, "expected %s and %s in %s" % (mydir, myrelname, files))
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*", only_files=1)
+    assert.true(mydir not in files, "did not expect %s in %s" % (mydir, files))
+    assert.true(myrelname in files, "expected %s  in %s" % (myrelname, files))
+    files = rblf_find_files(rblf_env.TEST_DATA_DIR + "/../", "*.star")
+    assert.true(myrelname in files, "expected %s in %s" % (myrelname, files))
 test()
diff --git a/tools/rbcrun/testdata/regex.star b/tools/rbcrun/testdata/regex.star
deleted file mode 100644
index 04e1d42..0000000
--- a/tools/rbcrun/testdata/regex.star
+++ /dev/null
@@ -1,13 +0,0 @@
-# Tests rblf_regex
-load("assert.star", "assert")
-
-
-def test():
-    pattern = "^(foo.*bar|abc.*d|1.*)$"
-    for w in ("foobar", "fooxbar", "abcxd", "123"):
-        assert.true(rblf_regex(pattern, w), "%s should match %s" % (w, pattern))
-    for w in ("afoobar", "abcde"):
-        assert.true(not rblf_regex(pattern, w), "%s should not match %s" % (w, pattern))
-
-
-test()
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 5ee53c8..d8e34b7 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -50,12 +50,15 @@
     ],
     libs: [
         "releasetools_common",
+        "releasetools_fsverity_metadata_generator",
         "releasetools_verity_utils",
     ],
     required: [
         "blk_alloc_to_base_fs",
         "e2fsck",
-        "mkerofsimage.sh",
+        "fsck.erofs",
+        "img2simg",
+        "mkfs.erofs",
         "mkuserimg_mke2fs",
         "simg2img",
         "tune2fs",
@@ -100,14 +103,6 @@
 
 python_library_host {
     name: "ota_metadata_proto",
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
     srcs: [
        "ota_metadata.proto",
     ],
@@ -164,10 +159,12 @@
         "releasetools_common",
         "releasetools_verity_utils",
         "apex_manifest",
+        "care_map_proto_py",
     ],
     required: [
         "brillo_update_payload",
         "checkvintf",
+        "generate_gki_certificate",
         "minigzip",
         "lz4",
         "toybox",
@@ -186,29 +183,15 @@
 // Host libraries.
 //
 
-python_defaults {
-    name: "releasetools_library_defaults",
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
-}
-
 python_library_host {
     name: "releasetools_add_img_to_target_files",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_add_img_to_target_files_defaults",
     ],
 }
 
 python_library_host {
     name: "releasetools_apex_utils",
-    defaults: ["releasetools_library_defaults"],
     srcs: [
         "apex_utils.py",
     ],
@@ -222,7 +205,6 @@
 python_library_host {
     name: "releasetools_build_image",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_build_image_defaults",
     ],
 }
@@ -230,7 +212,6 @@
 python_library_host {
     name: "releasetools_build_super_image",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_build_super_image_defaults",
     ],
 }
@@ -238,14 +219,12 @@
 python_library_host {
     name: "releasetools_check_target_files_vintf",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_check_target_files_vintf_defaults",
     ],
 }
 
 python_library_host {
     name: "releasetools_common",
-    defaults: ["releasetools_library_defaults"],
     srcs: [
         "blockimgdiff.py",
         "common.py",
@@ -260,6 +239,7 @@
         "boot_signer",
         "brotli",
         "bsdiff",
+        "generate_gki_certificate",
         "imgdiff",
         "minigzip",
         "lz4",
@@ -273,7 +253,6 @@
 python_library_host {
     name: "releasetools_img_from_target_files",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_img_from_target_files_defaults",
     ],
 }
@@ -281,14 +260,22 @@
 python_library_host {
     name: "releasetools_ota_from_target_files",
     defaults: [
-        "releasetools_library_defaults",
         "releasetools_ota_from_target_files_defaults",
     ],
 }
 
 python_library_host {
+    name: "releasetools_fsverity_metadata_generator",
+    srcs: [
+        "fsverity_metadata_generator.py",
+    ],
+    libs: [
+        "fsverity_digests_proto_python",
+    ],
+}
+
+python_library_host {
     name: "releasetools_verity_utils",
-    defaults: ["releasetools_library_defaults"],
     srcs: [
         "verity_utils.py",
     ],
@@ -307,13 +294,8 @@
 python_defaults {
     name: "releasetools_binary_defaults",
     version: {
-        py2: {
-            enabled: true,
-            embedded_launcher: true,
-        },
         py3: {
-            enabled: false,
-            embedded_launcher: false,
+            embedded_launcher: true,
         },
     },
     // TODO (b/140144201) Build imgdiff from releasetools_common
@@ -323,6 +305,7 @@
         "brotli",
         "bsdiff",
         "deapexer",
+        "generate_gki_certificate",
         "imgdiff",
         "minigzip",
         "lz4",
@@ -400,7 +383,7 @@
         "releasetools_common",
     ],
     required: [
-        "aapt",
+        "aapt2",
     ],
 }
 
@@ -442,7 +425,6 @@
     name: "releasetools_find_shareduid_violation",
     defaults: [
         "releasetools_find_shareduid_violation_defaults",
-        "releasetools_library_defaults",
     ],
 }
 
@@ -458,46 +440,6 @@
 }
 
 python_binary_host {
-    name: "merge_builds",
-    defaults: ["releasetools_binary_defaults"],
-    srcs: [
-        "merge_builds.py",
-    ],
-    libs: [
-        "releasetools_build_super_image",
-        "releasetools_common",
-    ],
-}
-
-python_binary_host {
-    name: "merge_target_files",
-    defaults: ["releasetools_binary_defaults"],
-    srcs: [
-        "merge_target_files.py",
-    ],
-    libs: [
-        "releasetools_add_img_to_target_files",
-        "releasetools_build_super_image",
-        "releasetools_check_target_files_vintf",
-        "releasetools_common",
-        "releasetools_find_shareduid_violation",
-        "releasetools_img_from_target_files",
-        "releasetools_ota_from_target_files",
-    ],
-    required: [
-        "checkvintf",
-        "host_init_verifier",
-        "secilc",
-    ],
-    target: {
-        darwin: {
-            // libs dep "releasetools_ota_from_target_files" is disabled on darwin
-            enabled: false,
-        },
-    },
-}
-
-python_binary_host {
     name: "ota_from_target_files",
     defaults: [
         "releasetools_binary_defaults",
@@ -576,6 +518,34 @@
     ],
 }
 
+python_binary_host {
+    name: "fsverity_manifest_generator",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [
+        "fsverity_manifest_generator.py",
+    ],
+    libs: [
+        "fsverity_digests_proto_python",
+        "releasetools_common",
+    ],
+    required: [
+        "aapt2",
+        "apksigner",
+        "fsverity",
+    ],
+}
+
+python_binary_host {
+    name: "fsverity_metadata_generator",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [
+        "fsverity_metadata_generator.py",
+    ],
+    required: [
+        "fsverity",
+    ],
+}
+
 //
 // Tests.
 //
@@ -587,11 +557,12 @@
         "check_partition_sizes.py",
         "check_target_files_signatures.py",
         "make_recovery_patch.py",
-        "merge_target_files.py",
         "ota_package_parser.py",
         "sign_apex.py",
         "sign_target_files_apks.py",
         "validate_target_files.py",
+        ":releasetools_merge_sources",
+        ":releasetools_merge_tests",
 
         "test_*.py",
     ],
@@ -627,39 +598,9 @@
     name: "releasetools_test",
     defaults: ["releasetools_test_defaults"],
     main: "test_utils.py",
-    version: {
-        py2: {
-            enabled: true,
-            // When using embedded launcher, atest will try (but may fail) to load libc++.so from
-            // host, because the test executable won't be able to find the needed libs via its
-            // runpath.
-            embedded_launcher: false,
-        },
-        py3: {
-            enabled: false,
-            embedded_launcher: false,
-        },
-    },
-    test_options: {
-        unit_test: true,
-    },
-}
-
-python_test_host {
-    name: "releasetools_py3_test",
-    defaults: ["releasetools_test_defaults"],
-    main: "test_utils.py",
-    test_suites: ["general-tests"],
-    version: {
-        py2: {
-            enabled: false,
-            embedded_launcher: false,
-        },
-        py3: {
-            enabled: true,
-            embedded_launcher: false,
-        },
-    },
+    // Don't use embedded_launcher, atest will try (but may fail) to load libc++.so from
+    // host, because the test executable won't be able to find the needed libs via its
+    // runpath.
     test_options: {
         unit_test: true,
     },
diff --git a/tools/releasetools/OWNERS b/tools/releasetools/OWNERS
index d7fc540..59235e0 100644
--- a/tools/releasetools/OWNERS
+++ b/tools/releasetools/OWNERS
@@ -1,4 +1,3 @@
 elsk@google.com
 nhdo@google.com
-xunchang@google.com
-zhaojiac@google.com
+zhangkelvin@google.com
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index babfc7d..09f69d0 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -54,6 +54,7 @@
 import stat
 import sys
 import uuid
+import tempfile
 import zipfile
 
 import build_image
@@ -63,7 +64,7 @@
 import ota_metadata_pb2
 
 from apex_utils import GetApexInfoFromTargetFiles
-from common import AddCareMapForAbOta
+from common import AddCareMapForAbOta, ZipDelete
 
 if sys.hexversion < 0x02070000:
   print("Python 2.7 or newer is required.", file=sys.stderr)
@@ -104,9 +105,10 @@
     if self._output_zip:
       self._zip_name = os.path.join(*args)
 
-  def Write(self):
+  def Write(self, compress_type=None):
     if self._output_zip:
-      common.ZipWrite(self._output_zip, self.name, self._zip_name)
+      common.ZipWrite(self._output_zip, self.name,
+                      self._zip_name, compress_type=compress_type)
 
 
 def AddSystem(output_zip, recovery_img=None, boot_img=None):
@@ -134,12 +136,13 @@
       "board_uses_vendorimage") == "true"
 
   if (OPTIONS.rebuild_recovery and not board_uses_vendorimage and
-      recovery_img is not None and boot_img is not None):
+          recovery_img is not None and boot_img is not None):
     logger.info("Building new recovery patch on system at system/vendor")
     common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
                              boot_img, info_dict=OPTIONS.info_dict)
 
-  block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.map")
+  block_list = OutputFile(output_zip, OPTIONS.input_tmp,
+                          "IMAGES", "system.map")
   CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img,
               block_list=block_list)
   return img.name
@@ -167,27 +170,28 @@
     return img.name
 
   def output_sink(fn, data):
-    ofile = open(os.path.join(OPTIONS.input_tmp, "VENDOR", fn), "w")
-    ofile.write(data)
-    ofile.close()
+    output_file = os.path.join(OPTIONS.input_tmp, "VENDOR", fn)
+    with open(output_file, "wb") as ofile:
+      ofile.write(data)
 
     if output_zip:
       arc_name = "VENDOR/" + fn
       if arc_name in output_zip.namelist():
         OPTIONS.replace_updated_files_list.append(arc_name)
       else:
-        common.ZipWrite(output_zip, ofile.name, arc_name)
+        common.ZipWrite(output_zip, output_file, arc_name)
 
   board_uses_vendorimage = OPTIONS.info_dict.get(
       "board_uses_vendorimage") == "true"
 
   if (OPTIONS.rebuild_recovery and board_uses_vendorimage and
-      recovery_img is not None and boot_img is not None):
+          recovery_img is not None and boot_img is not None):
     logger.info("Building new recovery patch on vendor")
     common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
                              boot_img, info_dict=OPTIONS.info_dict)
 
-  block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.map")
+  block_list = OutputFile(output_zip, OPTIONS.input_tmp,
+                          "IMAGES", "vendor.map")
   CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img,
               block_list=block_list)
   return img.name
@@ -275,6 +279,21 @@
       block_list=block_list)
   return img.name
 
+def AddSystemDlkm(output_zip):
+  """Turn the contents of SystemDlkm into an system_dlkm image and store it in output_zip."""
+
+  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_dlkm.img")
+  if os.path.exists(img.name):
+    logger.info("system_dlkm.img already exists; no need to rebuild...")
+    return img.name
+
+  block_list = OutputFile(
+      output_zip, OPTIONS.input_tmp, "IMAGES", "system_dlkm.map")
+  CreateImage(
+      OPTIONS.input_tmp, OPTIONS.info_dict, "system_dlkm", img,
+      block_list=block_list)
+  return img.name
+
 
 def AddDtbo(output_zip):
   """Adds the DTBO image.
@@ -374,15 +393,16 @@
       key_path, algorithm, extra_args)
 
   for img_name in OPTIONS.info_dict.get(
-      "avb_{}_image_list".format(partition_name)).split():
-    custom_image = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", img_name)
+          "avb_{}_image_list".format(partition_name)).split():
+    custom_image = OutputFile(
+        output_zip, OPTIONS.input_tmp, "IMAGES", img_name)
     if os.path.exists(custom_image.name):
       continue
 
     custom_image_prebuilt_path = os.path.join(
         OPTIONS.input_tmp, "PREBUILT_IMAGES", img_name)
     assert os.path.exists(custom_image_prebuilt_path), \
-      "Failed to find %s at %s" % (img_name, custom_image_prebuilt_path)
+        "Failed to find %s at %s" % (img_name, custom_image_prebuilt_path)
 
     shutil.copy(custom_image_prebuilt_path, custom_image.name)
 
@@ -484,7 +504,9 @@
   build_image.BuildImage(user_dir, image_props, img.name)
 
   common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
-  img.Write()
+  # Always use compression for useradata image.
+  # As it's likely huge and consist of lots of 0s.
+  img.Write(zipfile.ZIP_DEFLATED)
 
 
 def AddVBMeta(output_zip, partitions, name, needed_partitions):
@@ -589,7 +611,7 @@
     AssertionError: If it can't find an image.
   """
   for partition in ab_partitions:
-    img_name = partition.strip() + ".img"
+    img_name = partition + ".img"
 
     # Assert that the image is present under IMAGES/ now.
     if output_zip:
@@ -681,14 +703,16 @@
 
   return ((os.path.isdir(
       os.path.join(OPTIONS.input_tmp, partition_name.upper())) and
-           OPTIONS.info_dict.get(
-               "building_{}_image".format(partition_name)) == "true") or
-          os.path.exists(
-              os.path.join(OPTIONS.input_tmp, "IMAGES",
-                           "{}.img".format(partition_name))))
+      OPTIONS.info_dict.get(
+      "building_{}_image".format(partition_name)) == "true") or
+      os.path.exists(
+      os.path.join(OPTIONS.input_tmp, "IMAGES",
+                   "{}.img".format(partition_name))))
+
 
 def AddApexInfo(output_zip):
-  apex_infos = GetApexInfoFromTargetFiles(OPTIONS.input_tmp, 'system')
+  apex_infos = GetApexInfoFromTargetFiles(OPTIONS.input_tmp, 'system',
+                                          compressed_only=False)
   apex_metadata_proto = ota_metadata_pb2.ApexMetadata()
   apex_metadata_proto.apex_info.extend(apex_infos)
   apex_info_bytes = apex_metadata_proto.SerializeToString()
@@ -710,10 +734,12 @@
   # Calculate the vbmeta digest and put the result in to META/
   boot_images = OPTIONS.info_dict.get("boot_images")
   # Disable the digest calculation if the target_file is used as a container
-  # for boot images.
-  boot_container = boot_images and len(boot_images.split()) >= 2
+  # for boot images. A boot container might contain boot-5.4.img, boot-5.10.img
+  # etc., instead of just a boot.img and will fail in vbmeta digest calculation.
+  boot_container = boot_images and (
+      len(boot_images.split()) >= 2 or boot_images.split()[0] != 'boot.img')
   if (OPTIONS.info_dict.get("avb_enable") == "true" and not boot_container and
-      OPTIONS.info_dict.get("avb_building_vbmeta_image") == "true"):
+          OPTIONS.info_dict.get("avb_building_vbmeta_image") == "true"):
     avbtool = OPTIONS.info_dict["avb_avbtool"]
     digest = verity_utils.CalculateVbmetaDigest(OPTIONS.input_tmp, avbtool)
     vbmeta_digest_txt = os.path.join(OPTIONS.input_tmp, "META",
@@ -755,14 +781,17 @@
 
   has_recovery = OPTIONS.info_dict.get("no_recovery") != "true"
   has_boot = OPTIONS.info_dict.get("no_boot") != "true"
+  has_init_boot = OPTIONS.info_dict.get("init_boot") == "true"
   has_vendor_boot = OPTIONS.info_dict.get("vendor_boot") == "true"
+  has_vendor_kernel_boot = OPTIONS.info_dict.get("vendor_kernel_boot") == "true"
 
-  # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm, system, system_other}.img
+  # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm, system_dlkm, system, system_other}.img
   # can be built from source, or  dropped into target_files.zip as a prebuilt blob.
   has_vendor = HasPartition("vendor")
   has_odm = HasPartition("odm")
   has_vendor_dlkm = HasPartition("vendor_dlkm")
   has_odm_dlkm = HasPartition("odm_dlkm")
+  has_system_dlkm = HasPartition("system_dlkm")
   has_product = HasPartition("product")
   has_system_ext = HasPartition("system_ext")
   has_system = HasPartition("system")
@@ -799,7 +828,7 @@
     boot_images = OPTIONS.info_dict.get("boot_images")
     if boot_images is None:
       boot_images = "boot.img"
-    for index,b in enumerate(boot_images.split()):
+    for index, b in enumerate(boot_images.split()):
       # common.GetBootableImage() returns the image directly if present.
       boot_image = common.GetBootableImage(
           "IMAGES/" + b, b, OPTIONS.input_tmp, "BOOT")
@@ -815,6 +844,18 @@
           if output_zip:
             boot_image.AddToZip(output_zip)
 
+  if has_init_boot:
+    banner("init_boot")
+    init_boot_image = common.GetBootableImage(
+        "IMAGES/init_boot.img", "init_boot.img", OPTIONS.input_tmp, "INIT_BOOT")
+    if init_boot_image:
+      partitions['init_boot'] = os.path.join(
+          OPTIONS.input_tmp, "IMAGES", "init_boot.img")
+      if not os.path.exists(partitions['init_boot']):
+        init_boot_image.WriteToDir(OPTIONS.input_tmp)
+        if output_zip:
+          init_boot_image.AddToZip(output_zip)
+
   if has_vendor_boot:
     banner("vendor_boot")
     vendor_boot_image = common.GetVendorBootImage(
@@ -828,6 +869,19 @@
         if output_zip:
           vendor_boot_image.AddToZip(output_zip)
 
+  if has_vendor_kernel_boot:
+    banner("vendor_kernel_boot")
+    vendor_kernel_boot_image = common.GetVendorKernelBootImage(
+        "IMAGES/vendor_kernel_boot.img", "vendor_kernel_boot.img", OPTIONS.input_tmp,
+        "VENDOR_KERNEL_BOOT")
+    if vendor_kernel_boot_image:
+      partitions['vendor_kernel_boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES",
+                                               "vendor_kernel_boot.img")
+      if not os.path.exists(partitions['vendor_kernel_boot']):
+        vendor_kernel_boot_image.WriteToDir(OPTIONS.input_tmp)
+        if output_zip:
+          vendor_kernel_boot_image.AddToZip(output_zip)
+
   recovery_image = None
   if has_recovery:
     banner("recovery")
@@ -854,39 +908,24 @@
         if output_zip:
           recovery_two_step_image.AddToZip(output_zip)
 
-  if has_system:
-    banner("system")
-    partitions['system'] = AddSystem(
-        output_zip, recovery_img=recovery_image, boot_img=boot_image)
+  def add_partition(partition, has_partition, add_func, add_args):
+    if has_partition:
+      banner(partition)
+      partitions[partition] = add_func(output_zip, *add_args)
 
-  if has_vendor:
-    banner("vendor")
-    partitions['vendor'] = AddVendor(
-        output_zip, recovery_img=recovery_image, boot_img=boot_image)
-
-  if has_product:
-    banner("product")
-    partitions['product'] = AddProduct(output_zip)
-
-  if has_system_ext:
-    banner("system_ext")
-    partitions['system_ext'] = AddSystemExt(output_zip)
-
-  if has_odm:
-    banner("odm")
-    partitions['odm'] = AddOdm(output_zip)
-
-  if has_vendor_dlkm:
-    banner("vendor_dlkm")
-    partitions['vendor_dlkm'] = AddVendorDlkm(output_zip)
-
-  if has_odm_dlkm:
-    banner("odm_dlkm")
-    partitions['odm_dlkm'] = AddOdmDlkm(output_zip)
-
-  if has_system_other:
-    banner("system_other")
-    AddSystemOther(output_zip)
+  add_partition_calls = (
+      ("system", has_system, AddSystem, [recovery_image, boot_image]),
+      ("vendor", has_vendor, AddVendor, [recovery_image, boot_image]),
+      ("product", has_product, AddProduct, []),
+      ("system_ext", has_system_ext, AddSystemExt, []),
+      ("odm", has_odm, AddOdm, []),
+      ("vendor_dlkm", has_vendor_dlkm, AddVendorDlkm, []),
+      ("odm_dlkm", has_odm_dlkm, AddOdmDlkm, []),
+      ("system_dlkm", has_system_dlkm, AddSystemDlkm, []),
+      ("system_other", has_system_other, AddSystemOther, []),
+  )
+  for call in add_partition_calls:
+    add_partition(*call)
 
   AddApexInfo(output_zip)
 
@@ -900,13 +939,10 @@
     banner("partition-table")
     AddPartitionTable(output_zip)
 
-  if OPTIONS.info_dict.get("has_dtbo") == "true":
-    banner("dtbo")
-    partitions['dtbo'] = AddDtbo(output_zip)
-
-  if OPTIONS.info_dict.get("has_pvmfw") == "true":
-    banner("pvmfw")
-    partitions['pvmfw'] = AddPvmfw(output_zip)
+  add_partition("dtbo",
+                OPTIONS.info_dict.get("has_dtbo") == "true", AddDtbo, [])
+  add_partition("pvmfw",
+                OPTIONS.info_dict.get("has_pvmfw") == "true", AddPvmfw, [])
 
   # Custom images.
   custom_partitions = OPTIONS.info_dict.get(
@@ -954,7 +990,7 @@
 
   if OPTIONS.info_dict.get("build_super_partition") == "true":
     if OPTIONS.info_dict.get(
-        "build_retrofit_dynamic_partitions_ota_package") == "true":
+            "build_retrofit_dynamic_partitions_ota_package") == "true":
       banner("super split images")
       AddSuperSplit(output_zip)
 
@@ -963,7 +999,7 @@
                                    "ab_partitions.txt")
   if os.path.exists(ab_partitions_txt):
     with open(ab_partitions_txt) as f:
-      ab_partitions = f.readlines()
+      ab_partitions = f.read().splitlines()
 
     # For devices using A/B update, make sure we have all the needed images
     # ready under IMAGES/ or RADIO/.
@@ -991,6 +1027,36 @@
                           OPTIONS.replace_updated_files_list)
 
 
+def OptimizeCompressedEntries(zipfile_path):
+  """Convert files that do not compress well to uncompressed storage
+
+  EROFS images tend to be compressed already, so compressing them again
+  yields little space savings. Leaving them uncompressed will make
+  downstream tooling's job easier, and save compute time.
+  """
+  if not zipfile.is_zipfile(zipfile_path):
+    return
+  entries_to_store = []
+  with tempfile.TemporaryDirectory() as tmpdir:
+    with zipfile.ZipFile(zipfile_path, "r", allowZip64=True) as zfp:
+      for zinfo in zfp.filelist:
+        if not zinfo.filename.startswith("IMAGES/") and not zinfo.filename.startswith("META"):
+          continue
+        # Don't try to store userdata.img uncompressed, it's usually huge.
+        if zinfo.filename.endswith("userdata.img"):
+          continue
+        if zinfo.compress_size > zinfo.file_size * 0.80 and zinfo.compress_type != zipfile.ZIP_STORED:
+          entries_to_store.append(zinfo)
+          zfp.extract(zinfo, tmpdir)
+    if len(entries_to_store) == 0:
+      return
+    # Remove these entries, then re-add them as ZIP_STORED
+    ZipDelete(zipfile_path, [entry.filename for entry in entries_to_store])
+    with zipfile.ZipFile(zipfile_path, "a", allowZip64=True) as zfp:
+      for entry in entries_to_store:
+        zfp.write(os.path.join(tmpdir, entry.filename), entry.filename, compress_type=zipfile.ZIP_STORED)
+
+
 def main(argv):
   def option_handler(o, a):
     if o in ("-a", "--add_missing"):
@@ -1022,14 +1088,13 @@
   common.InitLogging()
 
   AddImagesToTargetFiles(args[0])
+  OptimizeCompressedEntries(args[0])
   logger.info("done.")
 
+
 if __name__ == '__main__':
   try:
     common.CloseInheritedPipes()
     main(sys.argv[1:])
-  except common.ExternalError:
-    logger.exception("\n   ERROR:\n")
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index ef4c69c..941edc6 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -52,9 +52,9 @@
 
 
 class ApexApkSigner(object):
-  """Class to sign the apk files in a apex payload image and repack the apex"""
+  """Class to sign the apk files and other files in an apex payload image and repack the apex"""
 
-  def __init__(self, apex_path, key_passwords, codename_to_api_level_map):
+  def __init__(self, apex_path, key_passwords, codename_to_api_level_map, avbtool=None, sign_tool=None):
     self.apex_path = apex_path
     if not key_passwords:
       self.key_passwords = dict()
@@ -63,9 +63,11 @@
     self.codename_to_api_level_map = codename_to_api_level_map
     self.debugfs_path = os.path.join(
         OPTIONS.search_path, "bin", "debugfs_static")
+    self.avbtool = avbtool if avbtool else "avbtool"
+    self.sign_tool = sign_tool
 
   def ProcessApexFile(self, apk_keys, payload_key, signing_args=None):
-    """Scans and signs the apk files and repack the apex
+    """Scans and signs the payload files and repack the apex
 
     Args:
       apk_keys: A dict that holds the signing keys for apk files.
@@ -84,7 +86,7 @@
     apk_entries = [name for name in entries_names if name.endswith('.apk')]
 
     # No need to sign and repack, return the original apex path.
-    if not apk_entries:
+    if not apk_entries and self.sign_tool is None:
       logger.info('No apk file to sign in %s', self.apex_path)
       return self.apex_path
 
@@ -99,15 +101,15 @@
         logger.warning('Apk path does not contain the intended directory name:'
                        ' %s', entry)
 
-    payload_dir, has_signed_apk = self.ExtractApexPayloadAndSignApks(
-        apk_entries, apk_keys)
-    if not has_signed_apk:
-      logger.info('No apk file has been signed in %s', self.apex_path)
+    payload_dir, has_signed_content = self.ExtractApexPayloadAndSignContents(
+        apk_entries, apk_keys, payload_key, signing_args)
+    if not has_signed_content:
+      logger.info('No contents has been signed in %s', self.apex_path)
       return self.apex_path
 
     return self.RepackApexPayload(payload_dir, payload_key, signing_args)
 
-  def ExtractApexPayloadAndSignApks(self, apk_entries, apk_keys):
+  def ExtractApexPayloadAndSignContents(self, apk_entries, apk_keys, payload_key, signing_args):
     """Extracts the payload image and signs the containing apk files."""
     if not os.path.exists(self.debugfs_path):
       raise ApexSigningError(
@@ -119,7 +121,7 @@
                    self.debugfs_path, 'extract', self.apex_path, payload_dir]
     common.RunAndCheckOutput(extract_cmd)
 
-    has_signed_apk = False
+    has_signed_content = False
     for entry in apk_entries:
       apk_path = os.path.join(payload_dir, entry)
       assert os.path.exists(self.apex_path)
@@ -137,8 +139,20 @@
       common.SignFile(
           unsigned_apk, apk_path, key_name, self.key_passwords.get(key_name),
           codename_to_api_level_map=self.codename_to_api_level_map)
-      has_signed_apk = True
-    return payload_dir, has_signed_apk
+      has_signed_content = True
+
+    if self.sign_tool:
+      logger.info('Signing payload contents in apex %s with %s', self.apex_path, self.sign_tool)
+      # Pass avbtool to the custom signing tool
+      cmd = [self.sign_tool, '--avbtool', self.avbtool]
+      # Pass signing_args verbatim which will be forwarded to avbtool (e.g. --signing_helper=...)
+      if signing_args:
+        cmd.extend(['--signing_args', '"{}"'.format(signing_args)])
+      cmd.extend([payload_key, payload_dir])
+      common.RunAndCheckOutput(cmd)
+      has_signed_content = True
+
+    return payload_dir, has_signed_content
 
   def RepackApexPayload(self, payload_dir, payload_key, signing_args=None):
     """Rebuilds the apex file with the updated payload directory."""
@@ -159,7 +173,7 @@
       if os.path.isfile(path):
         os.remove(path)
       elif os.path.isdir(path):
-        shutil.rmtree(path)
+        shutil.rmtree(path, ignore_errors=True)
 
     # TODO(xunchang) the signing process can be improved by using
     # '--unsigned_payload_only'. But we need to parse the vbmeta earlier for
@@ -310,7 +324,7 @@
 
 def SignUncompressedApex(avbtool, apex_file, payload_key, container_key,
                          container_pw, apk_keys, codename_to_api_level_map,
-                         no_hashtree, signing_args=None):
+                         no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current uncompressed APEX with the given payload/container keys.
 
   Args:
@@ -322,14 +336,16 @@
     codename_to_api_level_map: A dict that maps from codename to API level.
     no_hashtree: Don't include hashtree in the signed APEX.
     signing_args: Additional args to be passed to the payload signer.
+    sign_tool: A tool to sign the contents of the APEX.
 
   Returns:
     The path to the signed APEX file.
   """
-  # 1. Extract the apex payload image and sign the containing apk files. Repack
+  # 1. Extract the apex payload image and sign the files (e.g. APKs). Repack
   # the apex file after signing.
   apk_signer = ApexApkSigner(apex_file, container_pw,
-                             codename_to_api_level_map)
+                             codename_to_api_level_map,
+                             avbtool, sign_tool)
   apex_file = apk_signer.ProcessApexFile(apk_keys, payload_key, signing_args)
 
   # 2a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given
@@ -363,20 +379,16 @@
   common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY)
   common.ZipClose(apex_zip)
 
-  # 3. Align the files at page boundary (same as in apexer).
-  aligned_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
-  common.RunAndCheckOutput(['zipalign', '-f', '4096', apex_file, aligned_apex])
-
-  # 4. Sign the APEX container with container_key.
+  # 3. Sign the APEX container with container_key.
   signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
 
   # Specify the 4K alignment when calling SignApk.
   extra_signapk_args = OPTIONS.extra_signapk_args[:]
-  extra_signapk_args.extend(['-a', '4096'])
+  extra_signapk_args.extend(['-a', '4096', '--align-file-size'])
 
   password = container_pw.get(container_key) if container_pw else None
   common.SignFile(
-      aligned_apex,
+      apex_file,
       signed_apex,
       container_key,
       password,
@@ -388,7 +400,7 @@
 
 def SignCompressedApex(avbtool, apex_file, payload_key, container_key,
                        container_pw, apk_keys, codename_to_api_level_map,
-                       no_hashtree, signing_args=None):
+                       no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current compressed APEX with the given payload/container keys.
 
   Args:
@@ -425,7 +437,8 @@
       apk_keys,
       codename_to_api_level_map,
       no_hashtree,
-      signing_args)
+      signing_args,
+      sign_tool)
 
   # 3. Compress signed original apex.
   compressed_apex_file = common.MakeTempFile(prefix='apex-container-',
@@ -436,33 +449,24 @@
                             '--input', signed_original_apex_file,
                             '--output', compressed_apex_file])
 
-  # 4. Align apex
-  aligned_apex = common.MakeTempFile(prefix='apex-container-', suffix='.capex')
-  common.RunAndCheckOutput(['zipalign', '-f', '4096', compressed_apex_file,
-                            aligned_apex])
-
-  # 5. Sign the APEX container with container_key.
+  # 4. Sign the APEX container with container_key.
   signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.capex')
 
-  # Specify the 4K alignment when calling SignApk.
-  extra_signapk_args = OPTIONS.extra_signapk_args[:]
-  extra_signapk_args.extend(['-a', '4096'])
-
   password = container_pw.get(container_key) if container_pw else None
   common.SignFile(
-      aligned_apex,
+      compressed_apex_file,
       signed_apex,
       container_key,
       password,
       codename_to_api_level_map=codename_to_api_level_map,
-      extra_signapk_args=extra_signapk_args)
+      extra_signapk_args=OPTIONS.extra_signapk_args)
 
   return signed_apex
 
 
 def SignApex(avbtool, apex_data, payload_key, container_key, container_pw,
              apk_keys, codename_to_api_level_map,
-             no_hashtree, signing_args=None):
+             no_hashtree, signing_args=None, sign_tool=None):
   """Signs the current APEX with the given payload/container keys.
 
   Args:
@@ -498,7 +502,8 @@
           codename_to_api_level_map=codename_to_api_level_map,
           no_hashtree=no_hashtree,
           apk_keys=apk_keys,
-          signing_args=signing_args)
+          signing_args=signing_args,
+          sign_tool=sign_tool)
     elif apex_type == 'COMPRESSED':
       return SignCompressedApex(
           avbtool,
@@ -509,7 +514,8 @@
           codename_to_api_level_map=codename_to_api_level_map,
           no_hashtree=no_hashtree,
           apk_keys=apk_keys,
-          signing_args=signing_args)
+          signing_args=signing_args,
+          sign_tool=sign_tool)
     else:
       # TODO(b/172912232): support signing compressed apex
       raise ApexInfoError('Unsupported apex type {}'.format(apex_type))
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index fa4a152..9049622 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -24,6 +24,7 @@
 
 from __future__ import print_function
 
+import glob
 import logging
 import os
 import os.path
@@ -231,6 +232,22 @@
             mount_point, total_blocks, used_blocks, headroom_blocks,
             adjusted_blocks))
 
+def CalculateSizeAndReserved(prop_dict, size):
+  fs_type = prop_dict.get("fs_type", "")
+  partition_headroom = int(prop_dict.get("partition_headroom", 0))
+  # If not specified, give us 16MB margin for GetDiskUsage error ...
+  reserved_size = int(prop_dict.get("partition_reserved_size", BYTES_IN_MB * 16))
+
+  if fs_type == "erofs":
+    reserved_size = int(prop_dict.get("partition_reserved_size", 0))
+    if reserved_size == 0:
+      # give .3% margin or a minimum size for AVB footer
+      return max(size * 1003 // 1000, 256 * 1024)
+
+  if fs_type.startswith("ext4") and partition_headroom > reserved_size:
+    reserved_size = partition_headroom
+
+  return size + reserved_size
 
 def BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config):
   """Builds a pure image for the files under in_dir and writes it to out_file.
@@ -251,16 +268,19 @@
   """
   build_command = []
   fs_type = prop_dict.get("fs_type", "")
-  run_e2fsck = False
+  run_fsck = None
   needs_projid = prop_dict.get("needs_projid", 0)
   needs_casefold = prop_dict.get("needs_casefold", 0)
   needs_compress = prop_dict.get("needs_compress", 0)
 
+  disable_sparse = "disable_sparse" in prop_dict
+  manual_sparse = False
+
   if fs_type.startswith("ext"):
     build_command = [prop_dict["ext_mkuserimg"]]
-    if "extfs_sparse_flag" in prop_dict:
+    if "extfs_sparse_flag" in prop_dict and not disable_sparse:
       build_command.append(prop_dict["extfs_sparse_flag"])
-      run_e2fsck = True
+      run_e2fsck = RunE2fsck
     build_command.extend([in_dir, out_file, fs_type,
                           prop_dict["mount_point"]])
     build_command.append(prop_dict["image_size"])
@@ -287,7 +307,7 @@
     if "flash_logical_block_size" in prop_dict:
       build_command.extend(["-o", prop_dict["flash_logical_block_size"]])
     # Specify UUID and hash_seed if using mke2fs.
-    if prop_dict["ext_mkuserimg"] == "mkuserimg_mke2fs":
+    if os.path.basename(prop_dict["ext_mkuserimg"]) == "mkuserimg_mke2fs":
       if "uuid" in prop_dict:
         build_command.extend(["-U", prop_dict["uuid"]])
       if "hash_seed" in prop_dict:
@@ -301,25 +321,45 @@
     if "selinux_fc" in prop_dict:
       build_command.append(prop_dict["selinux_fc"])
   elif fs_type.startswith("erofs"):
-    build_command = ["mkerofsimage.sh"]
-    build_command.extend([in_dir, out_file])
-    if "erofs_sparse_flag" in prop_dict:
-      build_command.extend([prop_dict["erofs_sparse_flag"]])
-    build_command.extend(["-m", prop_dict["mount_point"]])
+    build_command = ["mkfs.erofs"]
+
+    compressor = None
+    if "erofs_default_compressor" in prop_dict:
+      compressor = prop_dict["erofs_default_compressor"]
+    if "erofs_compressor" in prop_dict:
+      compressor = prop_dict["erofs_compressor"]
+    if compressor:
+      build_command.extend(["-z", compressor])
+
+    build_command.extend(["--mount-point", prop_dict["mount_point"]])
     if target_out:
-      build_command.extend(["-d", target_out])
+      build_command.extend(["--product-out", target_out])
     if fs_config:
-      build_command.extend(["-C", fs_config])
+      build_command.extend(["--fs-config-file", fs_config])
     if "selinux_fc" in prop_dict:
-      build_command.extend(["-c", prop_dict["selinux_fc"]])
+      build_command.extend(["--file-contexts", prop_dict["selinux_fc"]])
     if "timestamp" in prop_dict:
       build_command.extend(["-T", str(prop_dict["timestamp"])])
     if "uuid" in prop_dict:
       build_command.extend(["-U", prop_dict["uuid"]])
+    if "block_list" in prop_dict:
+      build_command.extend(["--block-list-file", prop_dict["block_list"]])
+    if "erofs_pcluster_size" in prop_dict:
+      build_command.extend(["-C", prop_dict["erofs_pcluster_size"]])
+    if "erofs_share_dup_blocks" in prop_dict:
+      build_command.extend(["--chunksize", "4096"])
+    if "erofs_use_legacy_compression" in prop_dict:
+      build_command.extend(["-E", "legacy-compress"])
+
+    build_command.extend([out_file, in_dir])
+    if "erofs_sparse_flag" in prop_dict and not disable_sparse:
+      manual_sparse = True
+
+    run_fsck = RunErofsFsck
   elif fs_type.startswith("squash"):
     build_command = ["mksquashfsimage.sh"]
     build_command.extend([in_dir, out_file])
-    if "squashfs_sparse_flag" in prop_dict:
+    if "squashfs_sparse_flag" in prop_dict and not disable_sparse:
       build_command.extend([prop_dict["squashfs_sparse_flag"]])
     build_command.extend(["-m", prop_dict["mount_point"]])
     if target_out:
@@ -341,7 +381,7 @@
   elif fs_type.startswith("f2fs"):
     build_command = ["mkf2fsuserimg.sh"]
     build_command.extend([out_file, prop_dict["image_size"]])
-    if "f2fs_sparse_flag" in prop_dict:
+    if "f2fs_sparse_flag" in prop_dict and not disable_sparse:
       build_command.extend([prop_dict["f2fs_sparse_flag"]])
     if fs_config:
       build_command.extend(["-C", fs_config])
@@ -353,6 +393,8 @@
     build_command.extend(["-t", prop_dict["mount_point"]])
     if "timestamp" in prop_dict:
       build_command.extend(["-T", str(prop_dict["timestamp"])])
+    if "block_list" in prop_dict:
+      build_command.extend(["-B", prop_dict["block_list"]])
     build_command.extend(["-L", prop_dict["mount_point"]])
     if (needs_projid):
       build_command.append("--prjquota")
@@ -360,8 +402,9 @@
       build_command.append("--casefold")
     if (needs_compress or prop_dict.get("f2fs_compress") == "true"):
       build_command.append("--compression")
-    if (prop_dict.get("f2fs_compress") == "true"):
+    if (prop_dict.get("mount_point") != "data"):
       build_command.append("--readonly")
+    if (prop_dict.get("f2fs_compress") == "true"):
       build_command.append("--sldc")
       if (prop_dict.get("f2fs_sldc_flags") == None):
         build_command.append(str(0))
@@ -401,19 +444,38 @@
               int(prop_dict["partition_size"]) // BYTES_IN_MB))
     raise
 
-  if run_e2fsck and prop_dict.get("skip_fsck") != "true":
-    unsparse_image = UnsparseImage(out_file, replace=False)
+  if run_fsck and prop_dict.get("skip_fsck") != "true":
+    run_fsck(out_file)
 
-    # Run e2fsck on the inflated image file
-    e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
-    try:
-      common.RunAndCheckOutput(e2fsck_command)
-    finally:
-      os.remove(unsparse_image)
+  if manual_sparse:
+    temp_file = out_file + ".sparse"
+    img2simg_argv = ["img2simg", out_file, temp_file]
+    common.RunAndCheckOutput(img2simg_argv)
+    os.rename(temp_file, out_file)
 
   return mkfs_output
 
 
+def RunE2fsck(out_file):
+  unsparse_image = UnsparseImage(out_file, replace=False)
+
+  # Run e2fsck on the inflated image file
+  e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
+  try:
+    common.RunAndCheckOutput(e2fsck_command)
+  finally:
+    os.remove(unsparse_image)
+
+
+def RunErofsFsck(out_file):
+  fsck_command = ["fsck.erofs", "--extract", out_file]
+  try:
+    common.RunAndCheckOutput(fsck_command)
+  except:
+    print("Check failed for EROFS image {}".format(out_file))
+    raise
+
+
 def BuildImage(in_dir, prop_dict, out_file, target_out=None):
   """Builds an image for the files under in_dir and writes it to out_file.
 
@@ -445,27 +507,25 @@
   # or None if not applicable.
   verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict)
 
+  disable_sparse = "disable_sparse" in prop_dict
+  mkfs_output = None
   if (prop_dict.get("use_dynamic_partition_size") == "true" and
       "partition_size" not in prop_dict):
     # If partition_size is not defined, use output of `du' + reserved_size.
     # For compressed file system, it's better to use the compressed size to avoid wasting space.
     if fs_type.startswith("erofs"):
-      tmp_dict = prop_dict.copy()
-      if "erofs_sparse_flag" in tmp_dict:
-        tmp_dict.pop("erofs_sparse_flag")
-      BuildImageMkfs(in_dir, tmp_dict, out_file, target_out, fs_config)
-      size = GetDiskUsage(out_file)
-      os.remove(out_file)
+      mkfs_output = BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
+      if "erofs_sparse_flag" in prop_dict and not disable_sparse:
+        image_path = UnsparseImage(out_file, replace=False)
+        size = GetDiskUsage(image_path)
+        os.remove(image_path)
+      else:
+        size = GetDiskUsage(out_file)
     else:
       size = GetDiskUsage(in_dir)
     logger.info(
         "The tree size of %s is %d MB.", in_dir, size // BYTES_IN_MB)
-    # If not specified, give us 16MB margin for GetDiskUsage error ...
-    reserved_size = int(prop_dict.get("partition_reserved_size", BYTES_IN_MB * 16))
-    partition_headroom = int(prop_dict.get("partition_headroom", 0))
-    if fs_type.startswith("ext4") and partition_headroom > reserved_size:
-      reserved_size = partition_headroom
-    size += reserved_size
+    size = CalculateSizeAndReserved(prop_dict, size)
     # Round this up to a multiple of 4K so that avbtool works
     size = common.RoundUpTo4K(size)
     if fs_type.startswith("ext"):
@@ -478,7 +538,7 @@
           size // BYTES_IN_MB, prop_dict["extfs_inode_count"])
       BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
       sparse_image = False
-      if "extfs_sparse_flag" in prop_dict:
+      if "extfs_sparse_flag" in prop_dict and not disable_sparse:
         sparse_image = True
       fs_dict = GetFilesystemCharacteristics(fs_type, out_file, sparse_image)
       os.remove(out_file)
@@ -522,7 +582,7 @@
       prop_dict["image_size"] = str(size)
       BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
       sparse_image = False
-      if "f2fs_sparse_flag" in prop_dict:
+      if "f2fs_sparse_flag" in prop_dict and not disable_sparse:
         sparse_image = True
       fs_dict = GetFilesystemCharacteristics(fs_type, out_file, sparse_image)
       os.remove(out_file)
@@ -543,7 +603,12 @@
     max_image_size = verity_image_builder.CalculateMaxImageSize()
     prop_dict["image_size"] = str(max_image_size)
 
-  mkfs_output = BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
+  if not mkfs_output:
+    mkfs_output = BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
+
+  # Update the image (eg filesystem size). This can be different eg if mkfs
+  # rounds the requested size down due to alignment.
+  prop_dict["image_size"] = common.sparse_img.GetImagePartitionSize(out_file)
 
   # Check if there's enough headroom space available for ext4 image.
   if "partition_headroom" in prop_dict and fs_type.startswith("ext4"):
@@ -556,7 +621,6 @@
   if verity_image_builder:
     verity_image_builder.Build(out_file)
 
-
 def ImagePropFromGlobalDict(glob_dict, mount_point):
   """Build an image property dictionary from the global dictionary.
 
@@ -587,7 +651,11 @@
 
   common_props = (
       "extfs_sparse_flag",
+      "erofs_default_compressor",
+      "erofs_pcluster_size",
+      "erofs_share_dup_blocks",
       "erofs_sparse_flag",
+      "erofs_use_legacy_compression",
       "squashfs_sparse_flag",
       "system_f2fs_compress",
       "system_f2fs_sldc_flags",
@@ -606,238 +674,98 @@
   for p in common_props:
     copy_prop(p, p)
 
+  ro_mount_points = set([
+      "odm",
+      "odm_dlkm",
+      "oem",
+      "product",
+      "system",
+      "system_dlkm",
+      "system_ext",
+      "system_other",
+      "vendor",
+      "vendor_dlkm",
+  ])
+
+  # Tuple layout: (readonly, specific prop, general prop)
+  fmt_props = (
+      # Generic first, then specific file type.
+      (False, "fs_type", "fs_type"),
+      (False, "{}_fs_type", "fs_type"),
+
+      # Ordering for these doesn't matter.
+      (False, "{}_selinux_fc", "selinux_fc"),
+      (False, "{}_size", "partition_size"),
+      (True, "avb_{}_add_hashtree_footer_args", "avb_add_hashtree_footer_args"),
+      (True, "avb_{}_algorithm", "avb_algorithm"),
+      (True, "avb_{}_hashtree_enable", "avb_hashtree_enable"),
+      (True, "avb_{}_key_path", "avb_key_path"),
+      (True, "avb_{}_salt", "avb_salt"),
+      (True, "erofs_use_legacy_compression", "erofs_use_legacy_compression"),
+      (True, "ext4_share_dup_blocks", "ext4_share_dup_blocks"),
+      (True, "{}_base_fs_file", "base_fs_file"),
+      (True, "{}_disable_sparse", "disable_sparse"),
+      (True, "{}_erofs_compressor", "erofs_compressor"),
+      (True, "{}_erofs_pcluster_size", "erofs_pcluster_size"),
+      (True, "{}_erofs_share_dup_blocks", "erofs_share_dup_blocks"),
+      (True, "{}_extfs_inode_count", "extfs_inode_count"),
+      (True, "{}_f2fs_compress", "f2fs_compress"),
+      (True, "{}_f2fs_sldc_flags", "f2fs_sldc_flags"),
+      (True, "{}_reserved_size", "partition_reserved_size"),
+      (True, "{}_squashfs_block_size", "squashfs_block_size"),
+      (True, "{}_squashfs_compressor", "squashfs_compressor"),
+      (True, "{}_squashfs_compressor_opt", "squashfs_compressor_opt"),
+      (True, "{}_squashfs_disable_4k_align", "squashfs_disable_4k_align"),
+      (True, "{}_verity_block_device", "verity_block_device"),
+  )
+
+  # Translate prefixed properties into generic ones.
+  if mount_point == "data":
+    prefix = "userdata"
+  else:
+    prefix = mount_point
+
+  for readonly, src_prop, dest_prop in fmt_props:
+    if readonly and mount_point not in ro_mount_points:
+      continue
+
+    if src_prop == "fs_type":
+      # This property is legacy and only used on a few partitions. b/202600377
+      allowed_partitions = set(["system", "system_other", "data", "oem"])
+      if mount_point not in allowed_partitions:
+          continue
+
+    if (mount_point == "system_other") and (dest_prop != "partition_size"):
+      # Propagate system properties to system_other. They'll get overridden
+      # after as needed.
+      copy_prop(src_prop.format("system"), dest_prop)
+
+    copy_prop(src_prop.format(prefix), dest_prop)
+
+  # Set prefixed properties that need a default value.
+  if mount_point in ro_mount_points:
+    prop = "{}_journal_size".format(prefix)
+    if not copy_prop(prop, "journal_size"):
+      d["journal_size"] = "0"
+
+    prop = "{}_extfs_rsv_pct".format(prefix)
+    if not copy_prop(prop, "extfs_rsv_pct"):
+      d["extfs_rsv_pct"] = "0"
+
+  # Copy partition-specific properties.
   d["mount_point"] = mount_point
   if mount_point == "system":
-    copy_prop("avb_system_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_system_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_system_key_path", "avb_key_path")
-    copy_prop("avb_system_algorithm", "avb_algorithm")
-    copy_prop("avb_system_salt", "avb_salt")
-    copy_prop("fs_type", "fs_type")
-    # Copy the generic system fs type first, override with specific one if
-    # available.
-    copy_prop("system_fs_type", "fs_type")
     copy_prop("system_headroom", "partition_headroom")
-    copy_prop("system_size", "partition_size")
-    if not copy_prop("system_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("system_verity_block_device", "verity_block_device")
     copy_prop("system_root_image", "system_root_image")
     copy_prop("root_dir", "root_dir")
     copy_prop("root_fs_config", "root_fs_config")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("system_f2fs_compress", "f2fs_compress")
-    copy_prop("system_f2fs_sldc_flags", "f2fs_sldc_flags")
-    copy_prop("system_squashfs_compressor", "squashfs_compressor")
-    copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("system_squashfs_block_size", "squashfs_block_size")
-    copy_prop("system_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("system_base_fs_file", "base_fs_file")
-    copy_prop("system_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("system_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("system_reserved_size", "partition_reserved_size")
-    copy_prop("system_selinux_fc", "selinux_fc")
-  elif mount_point == "system_other":
-    # We inherit the selinux policies of /system since we contain some of its
-    # files.
-    copy_prop("avb_system_other_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_system_other_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_system_other_key_path", "avb_key_path")
-    copy_prop("avb_system_other_algorithm", "avb_algorithm")
-    copy_prop("avb_system_other_salt", "avb_salt")
-    copy_prop("fs_type", "fs_type")
-    copy_prop("system_fs_type", "fs_type")
-    copy_prop("system_other_size", "partition_size")
-    if not copy_prop("system_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("system_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("system_f2fs_compress", "f2fs_compress")
-    copy_prop("system_f2fs_sldc_flags", "f2fs_sldc_flags")
-    copy_prop("system_squashfs_compressor", "squashfs_compressor")
-    copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("system_squashfs_block_size", "squashfs_block_size")
-    copy_prop("system_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("system_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("system_reserved_size", "partition_reserved_size")
-    copy_prop("system_selinux_fc", "selinux_fc")
   elif mount_point == "data":
     # Copy the generic fs type first, override with specific one if available.
-    copy_prop("fs_type", "fs_type")
-    copy_prop("userdata_fs_type", "fs_type")
-    copy_prop("userdata_size", "partition_size")
     copy_prop("flash_logical_block_size", "flash_logical_block_size")
     copy_prop("flash_erase_block_size", "flash_erase_block_size")
-    copy_prop("userdata_selinux_fc", "selinux_fc")
     copy_prop("needs_casefold", "needs_casefold")
     copy_prop("needs_projid", "needs_projid")
     copy_prop("needs_compress", "needs_compress")
-  elif mount_point == "cache":
-    copy_prop("cache_fs_type", "fs_type")
-    copy_prop("cache_size", "partition_size")
-    copy_prop("cache_selinux_fc", "selinux_fc")
-  elif mount_point == "vendor":
-    copy_prop("avb_vendor_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_vendor_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_vendor_key_path", "avb_key_path")
-    copy_prop("avb_vendor_algorithm", "avb_algorithm")
-    copy_prop("avb_vendor_salt", "avb_salt")
-    copy_prop("vendor_fs_type", "fs_type")
-    copy_prop("vendor_size", "partition_size")
-    if not copy_prop("vendor_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("vendor_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("vendor_f2fs_compress", "f2fs_compress")
-    copy_prop("vendor_f2fs_sldc_flags", "f2fs_sldc_flags")
-    copy_prop("vendor_squashfs_compressor", "squashfs_compressor")
-    copy_prop("vendor_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("vendor_squashfs_block_size", "squashfs_block_size")
-    copy_prop("vendor_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("vendor_base_fs_file", "base_fs_file")
-    copy_prop("vendor_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("vendor_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("vendor_reserved_size", "partition_reserved_size")
-    copy_prop("vendor_selinux_fc", "selinux_fc")
-  elif mount_point == "product":
-    copy_prop("avb_product_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_product_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_product_key_path", "avb_key_path")
-    copy_prop("avb_product_algorithm", "avb_algorithm")
-    copy_prop("avb_product_salt", "avb_salt")
-    copy_prop("product_fs_type", "fs_type")
-    copy_prop("product_size", "partition_size")
-    if not copy_prop("product_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("product_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("product_f2fs_compress", "f2fs_compress")
-    copy_prop("product_f2fs_sldc_flags", "f2fs_sldc_flags")
-    copy_prop("product_squashfs_compressor", "squashfs_compressor")
-    copy_prop("product_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("product_squashfs_block_size", "squashfs_block_size")
-    copy_prop("product_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("product_base_fs_file", "base_fs_file")
-    copy_prop("product_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("product_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("product_reserved_size", "partition_reserved_size")
-    copy_prop("product_selinux_fc", "selinux_fc")
-  elif mount_point == "system_ext":
-    copy_prop("avb_system_ext_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_system_ext_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_system_ext_key_path", "avb_key_path")
-    copy_prop("avb_system_ext_algorithm", "avb_algorithm")
-    copy_prop("avb_system_ext_salt", "avb_salt")
-    copy_prop("system_ext_fs_type", "fs_type")
-    copy_prop("system_ext_size", "partition_size")
-    if not copy_prop("system_ext_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("system_ext_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("system_ext_f2fs_compress", "f2fs_compress")
-    copy_prop("system_ext_f2fs_sldc_flags", "f2fs_sldc_flags")
-    copy_prop("system_ext_squashfs_compressor", "squashfs_compressor")
-    copy_prop("system_ext_squashfs_compressor_opt",
-              "squashfs_compressor_opt")
-    copy_prop("system_ext_squashfs_block_size", "squashfs_block_size")
-    copy_prop("system_ext_squashfs_disable_4k_align",
-              "squashfs_disable_4k_align")
-    copy_prop("system_ext_base_fs_file", "base_fs_file")
-    copy_prop("system_ext_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("system_ext_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("system_ext_reserved_size", "partition_reserved_size")
-    copy_prop("system_ext_selinux_fc", "selinux_fc")
-  elif mount_point == "odm":
-    copy_prop("avb_odm_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_odm_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_odm_key_path", "avb_key_path")
-    copy_prop("avb_odm_algorithm", "avb_algorithm")
-    copy_prop("avb_odm_salt", "avb_salt")
-    copy_prop("odm_fs_type", "fs_type")
-    copy_prop("odm_size", "partition_size")
-    if not copy_prop("odm_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("odm_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("odm_squashfs_compressor", "squashfs_compressor")
-    copy_prop("odm_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("odm_squashfs_block_size", "squashfs_block_size")
-    copy_prop("odm_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("odm_base_fs_file", "base_fs_file")
-    copy_prop("odm_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("odm_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("odm_reserved_size", "partition_reserved_size")
-    copy_prop("odm_selinux_fc", "selinux_fc")
-  elif mount_point == "vendor_dlkm":
-    copy_prop("avb_vendor_dlkm_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_vendor_dlkm_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_vendor_dlkm_key_path", "avb_key_path")
-    copy_prop("avb_vendor_dlkm_algorithm", "avb_algorithm")
-    copy_prop("avb_vendor_dlkm_salt", "avb_salt")
-    copy_prop("vendor_dlkm_fs_type", "fs_type")
-    copy_prop("vendor_dlkm_size", "partition_size")
-    copy_prop("vendor_dlkm_f2fs_compress", "f2fs_compress")
-    copy_prop("vendor_dlkm_f2fs_sldc_flags", "f2fs_sldc_flags")
-    if not copy_prop("vendor_dlkm_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("vendor_dlkm_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("vendor_dlkm_squashfs_compressor", "squashfs_compressor")
-    copy_prop("vendor_dlkm_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("vendor_dlkm_squashfs_block_size", "squashfs_block_size")
-    copy_prop("vendor_dlkm_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("vendor_dlkm_base_fs_file", "base_fs_file")
-    copy_prop("vendor_dlkm_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("vendor_dlkm_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("vendor_dlkm_reserved_size", "partition_reserved_size")
-    copy_prop("vendor_dlkm_selinux_fc", "selinux_fc")
-  elif mount_point == "odm_dlkm":
-    copy_prop("avb_odm_dlkm_hashtree_enable", "avb_hashtree_enable")
-    copy_prop("avb_odm_dlkm_add_hashtree_footer_args",
-              "avb_add_hashtree_footer_args")
-    copy_prop("avb_odm_dlkm_key_path", "avb_key_path")
-    copy_prop("avb_odm_dlkm_algorithm", "avb_algorithm")
-    copy_prop("avb_odm_dlkm_salt", "avb_salt")
-    copy_prop("odm_dlkm_fs_type", "fs_type")
-    copy_prop("odm_dlkm_size", "partition_size")
-    if not copy_prop("odm_dlkm_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("odm_dlkm_verity_block_device", "verity_block_device")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    copy_prop("odm_dlkm_squashfs_compressor", "squashfs_compressor")
-    copy_prop("odm_dlkm_squashfs_compressor_opt", "squashfs_compressor_opt")
-    copy_prop("odm_dlkm_squashfs_block_size", "squashfs_block_size")
-    copy_prop("odm_dlkm_squashfs_disable_4k_align", "squashfs_disable_4k_align")
-    copy_prop("odm_dlkm_base_fs_file", "base_fs_file")
-    copy_prop("odm_dlkm_extfs_inode_count", "extfs_inode_count")
-    if not copy_prop("odm_dlkm_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("odm_dlkm_reserved_size", "partition_reserved_size")
-    copy_prop("odm_dlkm_selinux_fc", "selinux_fc")
-  elif mount_point == "oem":
-    copy_prop("fs_type", "fs_type")
-    copy_prop("oem_size", "partition_size")
-    if not copy_prop("oem_journal_size", "journal_size"):
-      d["journal_size"] = "0"
-    copy_prop("oem_extfs_inode_count", "extfs_inode_count")
-    copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
-    if not copy_prop("oem_extfs_rsv_pct", "extfs_rsv_pct"):
-      d["extfs_rsv_pct"] = "0"
-    copy_prop("oem_selinux_fc", "selinux_fc")
   d["partition_name"] = mount_point
   return d
 
@@ -876,6 +804,8 @@
     copy_prop("partition_size", "vendor_dlkm_size")
   elif mount_point == "odm_dlkm":
     copy_prop("partition_size", "odm_dlkm_size")
+  elif mount_point == "system_dlkm":
+    copy_prop("partition_size", "system_dlkm_size")
   elif mount_point == "product":
     copy_prop("partition_size", "product_size")
   elif mount_point == "system_ext":
@@ -919,6 +849,8 @@
       mount_point = "vendor_dlkm"
     elif image_filename == "odm_dlkm.img":
       mount_point = "odm_dlkm"
+    elif image_filename == "system_dlkm.img":
+      mount_point = "system_dlkm"
     elif image_filename == "oem.img":
       mount_point = "oem"
     elif image_filename == "product.img":
diff --git a/tools/releasetools/care_map_pb2.py b/tools/releasetools/care_map_pb2.py
new file mode 100644
index 0000000..06aee25
--- /dev/null
+++ b/tools/releasetools/care_map_pb2.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: bootable/recovery/update_verifier/care_map.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='bootable/recovery/update_verifier/care_map.proto',
+  package='recovery_update_verifier',
+  syntax='proto3',
+  serialized_options=_b('H\003'),
+  serialized_pb=_b('\n0bootable/recovery/update_verifier/care_map.proto\x12\x18recovery_update_verifier\"\x9e\x01\n\x07\x43\x61reMap\x12\x43\n\npartitions\x18\x01 \x03(\x0b\x32/.recovery_update_verifier.CareMap.PartitionInfo\x1aN\n\rPartitionInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06ranges\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\x12\x13\n\x0b\x66ingerprint\x18\x04 \x01(\tB\x02H\x03\x62\x06proto3')
+)
+
+
+
+
+_CAREMAP_PARTITIONINFO = _descriptor.Descriptor(
+  name='PartitionInfo',
+  full_name='recovery_update_verifier.CareMap.PartitionInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='recovery_update_verifier.CareMap.PartitionInfo.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ranges', full_name='recovery_update_verifier.CareMap.PartitionInfo.ranges', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='id', full_name='recovery_update_verifier.CareMap.PartitionInfo.id', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fingerprint', full_name='recovery_update_verifier.CareMap.PartitionInfo.fingerprint', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=159,
+  serialized_end=237,
+)
+
+_CAREMAP = _descriptor.Descriptor(
+  name='CareMap',
+  full_name='recovery_update_verifier.CareMap',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='partitions', full_name='recovery_update_verifier.CareMap.partitions', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_CAREMAP_PARTITIONINFO, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=79,
+  serialized_end=237,
+)
+
+_CAREMAP_PARTITIONINFO.containing_type = _CAREMAP
+_CAREMAP.fields_by_name['partitions'].message_type = _CAREMAP_PARTITIONINFO
+DESCRIPTOR.message_types_by_name['CareMap'] = _CAREMAP
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CareMap = _reflection.GeneratedProtocolMessageType('CareMap', (_message.Message,), {
+
+  'PartitionInfo' : _reflection.GeneratedProtocolMessageType('PartitionInfo', (_message.Message,), {
+    'DESCRIPTOR' : _CAREMAP_PARTITIONINFO,
+    '__module__' : 'bootable.recovery.update_verifier.care_map_pb2'
+    # @@protoc_insertion_point(class_scope:recovery_update_verifier.CareMap.PartitionInfo)
+    })
+  ,
+  'DESCRIPTOR' : _CAREMAP,
+  '__module__' : 'bootable.recovery.update_verifier.care_map_pb2'
+  # @@protoc_insertion_point(class_scope:recovery_update_verifier.CareMap)
+  })
+_sym_db.RegisterMessage(CareMap)
+_sym_db.RegisterMessage(CareMap.PartitionInfo)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/tools/releasetools/check_ota_package_signature.py b/tools/releasetools/check_ota_package_signature.py
index 58510a5..b395c19 100755
--- a/tools/releasetools/check_ota_package_signature.py
+++ b/tools/releasetools/check_ota_package_signature.py
@@ -181,8 +181,5 @@
 if __name__ == '__main__':
   try:
     main()
-  except AssertionError as err:
-    print('\n    ERROR: %s\n' % (err,))
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/check_partition_sizes.py b/tools/releasetools/check_partition_sizes.py
index eaed07e..738d77d 100644
--- a/tools/releasetools/check_partition_sizes.py
+++ b/tools/releasetools/check_partition_sizes.py
@@ -300,8 +300,5 @@
   try:
     common.CloseInheritedPipes()
     main(sys.argv[1:])
-  except common.ExternalError:
-    logger.exception("\n   ERROR:\n")
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/check_target_files_signatures.py b/tools/releasetools/check_target_files_signatures.py
index 6e02e4d..d935607 100755
--- a/tools/releasetools/check_target_files_signatures.py
+++ b/tools/releasetools/check_target_files_signatures.py
@@ -65,10 +65,13 @@
 # extra field anyway).
 # Issue #14315: https://bugs.python.org/issue14315, fixed in Python 2.7.8 and
 # Python 3.5.0 alpha 1.
+
+
 class MyZipInfo(zipfile.ZipInfo):
   def _decodeExtra(self):
     pass
 
+
 zipfile.ZipInfo = MyZipInfo
 
 
@@ -83,6 +86,7 @@
 
 
 def AddProblem(msg):
+  logger.error(msg)
   PROBLEMS.append(" ".join(PROBLEM_PREFIX) + " " + msg)
 
 
@@ -204,7 +208,7 @@
       for info in apk.infolist():
         filename = info.filename
         if (filename.startswith("META-INF/") and
-            info.filename.endswith((".DSA", ".RSA"))):
+                info.filename.endswith((".DSA", ".RSA"))):
           pkcs7 = apk.read(filename)
           cert = CertFromPKCS7(pkcs7, filename)
           if not cert:
@@ -233,9 +237,11 @@
     # Signer #1 certificate DN: ...
     # Signer #1 certificate SHA-256 digest: ...
     # Signer #1 certificate SHA-1 digest: ...
+    # Signer (minSdkVersion=24, maxSdkVersion=32) certificate SHA-256 digest: 56be132b780656fe2444cd34326eb5d7aac91d2096abf0fe673a99270622ec87
+    # Signer (minSdkVersion=24, maxSdkVersion=32) certificate SHA-1 digest: 19da94896ce4078c38ca695701f1dec741ec6d67
     # ...
     certs_info = {}
-    certificate_regex = re.compile(r"(Signer #[0-9]+) (certificate .*):(.*)")
+    certificate_regex = re.compile(r"(Signer (?:#[0-9]+|\(.*\))) (certificate .*):(.*)")
     for line in output.splitlines():
       m = certificate_regex.match(line)
       if not m:
@@ -266,7 +272,7 @@
                    stdout=subprocess.PIPE)
     manifest, err = p.communicate()
     if err:
-      AddProblem("failed to read manifest")
+      AddProblem("failed to read manifest " + full_filename)
       return
 
     self.shared_uid = None
@@ -279,15 +285,15 @@
         name = m.group(1)
         if name == "android:sharedUserId":
           if self.shared_uid is not None:
-            AddProblem("multiple sharedUserId declarations")
+            AddProblem("multiple sharedUserId declarations " + full_filename)
           self.shared_uid = m.group(2)
         elif name == "package":
           if self.package is not None:
-            AddProblem("multiple package declarations")
+            AddProblem("multiple package declarations " + full_filename)
           self.package = m.group(2)
 
     if self.package is None:
-      AddProblem("no package declaration")
+      AddProblem("no package declaration " + full_filename)
 
 
 class TargetFiles(object):
@@ -338,8 +344,8 @@
           apk = APK(fullname, displayname)
           self.apks[apk.filename] = apk
           self.apks_by_basename[os.path.basename(apk.filename)] = apk
-
-          self.max_pkg_len = max(self.max_pkg_len, len(apk.package))
+          if apk.package:
+            self.max_pkg_len = max(self.max_pkg_len, len(apk.package))
           self.max_fn_len = max(self.max_fn_len, len(apk.filename))
 
   def CheckSharedUids(self):
@@ -392,7 +398,8 @@
     by_digest = {}
     for apk in self.apks.values():
       for digest in apk.cert_digests:
-        by_digest.setdefault(digest, []).append((apk.package, apk))
+        if apk.package:
+          by_digest.setdefault(digest, []).append((apk.package, apk))
 
     order = [(-len(v), k) for (k, v) in by_digest.items()]
     order.sort()
@@ -400,7 +407,12 @@
     for _, digest in order:
       print("%s:" % (ALL_CERTS.Get(digest),))
       apks = by_digest[digest]
-      apks.sort()
+      apks.sort(key=lambda x: x[0])
+      for i in range(1, len(apks)):
+        pkgname, apk = apks[i]
+        if pkgname == apks[i-1][0]:
+          print("Both {} and {} have same package name {}".format(
+              apk.filename, apks[i-1][1].filename, pkgname))
       for _, apk in apks:
         if apk.shared_uid:
           print("  %-*s  %-*s  [%s]" % (self.max_fn_len, apk.filename,
@@ -527,8 +539,5 @@
   try:
     r = main(sys.argv[1:])
     sys.exit(r)
-  except common.ExternalError as e:
-    print("\n   ERROR: %s\n" % (e,))
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index a2ddfe7..4a2a905 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -46,7 +46,7 @@
     '/product': ('PRODUCT', 'SYSTEM/product'),
     '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
     '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
-    # vendor_dlkm and odm_dlkm does not have VINTF files.
+    # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
 }
 
 UNZIP_PATTERN = ['META/*', '*/build.prop']
@@ -132,7 +132,7 @@
       'checkvintf',
       '--check-compat',
   ]
-  for device_path, real_path in dirmap.items():
+  for device_path, real_path in sorted(dirmap.items()):
     common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
   common_command += kernel_args
   common_command += shipping_api_level_args
@@ -164,8 +164,16 @@
   """
   def PathToPatterns(path):
     if path[-1] == '/':
-      path += '*'
-    for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
+      path += '**'
+
+    # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
+    # is a prefix of path. In order to get find the correct prefix, sort the
+    # entries by decreasing length of their keys, so that we check if longer
+    # strings are prefixes before shorter strings. This is so that keys that
+    # are substrings of other keys (like /system vs /system_ext) are checked
+    # later, and we don't mistakenly mark a path that starts with /system_ext
+    # as starting with only /system.
+    for device_path, target_files_rel_paths in sorted(DIR_SEARCH_PATHS.items(), key=lambda i: len(i[0]), reverse=True):
       if path.startswith(device_path):
         suffix = path[len(device_path):]
         return [rel_path + suffix for rel_path in target_files_rel_paths]
@@ -278,8 +286,5 @@
   try:
     common.CloseInheritedPipes()
     main(sys.argv[1:])
-  except common.ExternalError:
-    logger.exception('\n   ERROR:\n')
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index f678d08..418d8da 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -68,8 +68,12 @@
     self.search_path = os.path.dirname(os.path.dirname(exec_path))
 
     self.signapk_path = "framework/signapk.jar"  # Relative to search_path
+    if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
+      if "ANDROID_HOST_OUT" in os.environ:
+        self.search_path = os.environ["ANDROID_HOST_OUT"]
     self.signapk_shared_library_path = "lib64"   # Relative to search_path
     self.extra_signapk_args = []
+    self.aapt2_path = "aapt2"
     self.java_path = "java"  # Use the one on the path by default.
     self.java_args = ["-Xmx2048m"]  # The default JVM args.
     self.android_jar_path = None
@@ -80,11 +84,6 @@
     self.boot_signer_args = []
     self.verity_signer_path = None
     self.verity_signer_args = []
-    self.aftl_tool_path = None
-    self.aftl_server = None
-    self.aftl_key_path = None
-    self.aftl_manufacturer_key_path = None
-    self.aftl_signer_helper = None
     self.verbose = False
     self.tempfiles = []
     self.device_specific = None
@@ -113,9 +112,9 @@
 # descriptor into vbmeta.img. When adding a new entry here, the
 # AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
 # accordingly.
-AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
-                  'system', 'system_ext', 'vendor', 'vendor_boot',
-                  'vendor_dlkm', 'odm_dlkm')
+AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
+                  'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
+                  'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
 
 # Chained VBMeta partitions.
 AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
@@ -129,10 +128,11 @@
     'odm',
     'vendor_dlkm',
     'odm_dlkm',
+    'system_dlkm',
 ]
 
 # Partitions with a build.prop file
-PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
+PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
 
 # See sysprop.mk. If file is moved, add new search paths here; don't remove
 # existing search paths.
@@ -276,6 +276,9 @@
     args = args[:]
     args[0] = FindHostToolPath(args[0])
 
+  if verbose is None:
+    verbose = OPTIONS.verbose
+
   # Don't log any if caller explicitly says so.
   if verbose:
     logger.info("  Running: \"%s\"", " ".join(args))
@@ -451,6 +454,13 @@
     return vabc_enabled
 
   @property
+  def is_vabc_xor(self):
+    vendor_prop = self.info_dict.get("vendor.build.prop")
+    vabc_xor_enabled = vendor_prop and \
+        vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
+    return vabc_xor_enabled
+
+  @property
   def vendor_suppressed_vabc(self):
     vendor_prop = self.info_dict.get("vendor.build.prop")
     vabc_suppressed = vendor_prop and \
@@ -484,8 +494,9 @@
   def GetPartitionBuildProp(self, prop, partition):
     """Returns the inquired build property for the provided partition."""
 
-    # Boot image uses ro.[product.]bootimage instead of boot.
-    prop_partition = "bootimage" if partition == "boot" else partition
+    # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
+    # This comes from the generic ramdisk
+    prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
 
     # If provided a partition for this property, only look within that
     # partition's build.prop.
@@ -788,7 +799,7 @@
 
     # Redirect {partition}_base_fs_file for each of the named partitions.
     for part_name in ["system", "vendor", "system_ext", "product", "odm",
-                      "vendor_dlkm", "odm_dlkm"]:
+                      "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
       key_name = part_name + "_base_fs_file"
       if key_name not in d:
         continue
@@ -923,9 +934,9 @@
   def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
     """Loads the build.prop file and builds the attributes."""
 
-    if name == "boot":
+    if name in ("boot", "init_boot"):
       data = PartitionBuildProps._ReadBootPropFile(
-          input_file, ramdisk_format=ramdisk_format)
+          input_file, name, ramdisk_format=ramdisk_format)
     else:
       data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
 
@@ -934,15 +945,16 @@
     return props
 
   @staticmethod
-  def _ReadBootPropFile(input_file, ramdisk_format):
+  def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
     """
     Read build.prop for boot image from input_file.
     Return empty string if not found.
     """
+    image_path = 'IMAGES/' + partition_name + '.img'
     try:
-      boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
+      boot_img = ExtractFromInputFile(input_file, image_path)
     except KeyError:
-      logger.warning('Failed to read IMAGES/boot.img')
+      logger.warning('Failed to read %s', image_path)
       return ''
     prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
     if prop_file is None:
@@ -964,6 +976,8 @@
         break
       except KeyError:
         logger.warning('Failed to read %s', prop_file)
+    if data == '':
+      logger.warning("Failed to read build.prop for partition {}".format(name))
     return data
 
   @staticmethod
@@ -1008,7 +1022,8 @@
 
     import_path = tokens[1]
     if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
-      raise ValueError('Unrecognized import path {}'.format(line))
+      logger.warn('Unrecognized import path {}'.format(line))
+      return {}
 
     # We only recognize a subset of import statement that the init process
     # supports. And we can loose the restriction based on how the dynamic
@@ -1172,10 +1187,14 @@
     return " ".join(sorted(combined))
 
   if (framework_dict.get("use_dynamic_partitions") !=
-          "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
+        "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
     raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
 
   merged_dict = {"use_dynamic_partitions": "true"}
+  # For keys-value pairs that are the same, copy to merged dict
+  for key in vendor_dict.keys():
+    if key in framework_dict and framework_dict[key] == vendor_dict[key]:
+      merged_dict[key] = vendor_dict[key]
 
   merged_dict["dynamic_partition_list"] = uniq_concat(
       framework_dict.get("dynamic_partition_list", ""),
@@ -1229,6 +1248,7 @@
           "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
       ],
       "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
+      "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
   }
   partition_map = {}
   for partition, subdirs in possible_subdirs.items():
@@ -1380,74 +1400,51 @@
   return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
 
 
-def ConstructAftlMakeImageCommands(output_image):
-  """Constructs the command to append the aftl image to vbmeta."""
-
-  # Ensure the other AFTL parameters are set as well.
-  assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
-  assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
-  assert OPTIONS.aftl_manufacturer_key_path is not None, \
-      'No AFTL manufacturer key provided.'
-
-  vbmeta_image = MakeTempFile()
-  os.rename(output_image, vbmeta_image)
-  build_info = BuildInfo(OPTIONS.info_dict, use_legacy_id=True)
-  version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
-  aftltool = OPTIONS.aftl_tool_path
-  server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
-  aftl_cmd = [aftltool, "make_icp_from_vbmeta",
-              "--vbmeta_image_path", vbmeta_image,
-              "--output", output_image,
-              "--version_incremental", version_incremental,
-              "--transparency_log_servers", ','.join(server_argument_list),
-              "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
-              "--algorithm", "SHA256_RSA4096",
-              "--padding", "4096"]
-  if OPTIONS.aftl_signer_helper:
-    aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
-  return aftl_cmd
+def _HasGkiCertificationArgs():
+  return ("gki_signing_key_path" in OPTIONS.info_dict and
+          "gki_signing_algorithm" in OPTIONS.info_dict)
 
 
-def AddAftlInclusionProof(output_image):
-  """Appends the aftl inclusion proof to the vbmeta image."""
-
-  aftl_cmd = ConstructAftlMakeImageCommands(output_image)
-  RunAndCheckOutput(aftl_cmd)
-
-  verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
-                output_image, '--transparency_log_pub_keys',
-                OPTIONS.aftl_key_path]
-  RunAndCheckOutput(verify_cmd)
-
-
-def AppendGkiSigningArgs(cmd):
-  """Append GKI signing arguments for mkbootimg."""
-  # e.g., --gki_signing_key path/to/signing_key
-  #       --gki_signing_algorithm SHA256_RSA4096"
-
+def _GenerateGkiCertificate(image, image_name):
   key_path = OPTIONS.info_dict.get("gki_signing_key_path")
-  # It's fine that a non-GKI boot.img has no gki_signing_key_path.
-  if not key_path:
-    return
+  algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
 
   if not os.path.exists(key_path) and OPTIONS.search_path:
     new_key_path = os.path.join(OPTIONS.search_path, key_path)
     if os.path.exists(new_key_path):
       key_path = new_key_path
 
-  # Checks key_path exists, before appending --gki_signing_* args.
+  # Checks key_path exists, before processing --gki_signing_* args.
   if not os.path.exists(key_path):
     raise ExternalError(
         'gki_signing_key_path: "{}" not found'.format(key_path))
 
-  algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
-  if key_path and algorithm:
-    cmd.extend(["--gki_signing_key", key_path,
-                "--gki_signing_algorithm", algorithm])
+  output_certificate = tempfile.NamedTemporaryFile()
+  cmd = [
+      "generate_gki_certificate",
+      "--name", image_name,
+      "--algorithm", algorithm,
+      "--key", key_path,
+      "--output", output_certificate.name,
+      image,
+  ]
 
-    signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
-    if signature_args:
-      cmd.extend(["--gki_signing_signature_args", signature_args])
+  signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
+  signature_args = signature_args.strip()
+  if signature_args:
+    cmd.extend(["--additional_avb_args", signature_args])
+
+  args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
+  args = args.strip()
+  if args:
+    cmd.extend(["--additional_avb_args", args])
+
+  RunAndCheckOutput(cmd)
+
+  output_certificate.seek(os.SEEK_SET, 0)
+  data = output_certificate.read()
+  output_certificate.close()
+  return data
 
 
 def BuildVBMeta(image_path, partitions, name, needed_partitions):
@@ -1513,10 +1510,6 @@
 
   RunAndCheckOutput(cmd)
 
-  # Generate the AFTL inclusion proof.
-  if OPTIONS.aftl_server is not None:
-    AddAftlInclusionProof(image_path)
-
 
 def _MakeRamdisk(sourcedir, fs_config_file=None,
                  ramdisk_format=RamdiskFormat.GZ):
@@ -1569,12 +1562,16 @@
       logger.info("Excluded kernel binary from recovery image.")
     else:
       kernel = "kernel"
+  elif partition_name == "init_boot":
+    pass
   else:
     kernel = image_name.replace("boot", "kernel")
     kernel = kernel.replace(".img", "")
   if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
     return None
 
+  kernel_path = os.path.join(sourcedir, kernel) if kernel else None
+
   if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
     return None
 
@@ -1589,8 +1586,8 @@
   mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
 
   cmd = [mkbootimg]
-  if kernel:
-    cmd += ["--kernel", os.path.join(sourcedir, kernel)]
+  if kernel_path is not None:
+    cmd.extend(["--kernel", kernel_path])
 
   fn = os.path.join(sourcedir, "second")
   if os.access(fn, os.F_OK):
@@ -1623,6 +1620,8 @@
       # Fall back to "mkbootimg_args" for recovery image
       # in case "recovery_mkbootimg_args" is not set.
       args = info_dict.get("mkbootimg_args")
+  elif partition_name == "init_boot":
+    args = info_dict.get("mkbootimg_init_args")
   else:
     args = info_dict.get("mkbootimg_args")
   if args and args.strip():
@@ -1635,8 +1634,6 @@
   if has_ramdisk:
     cmd.extend(["--ramdisk", ramdisk_img.name])
 
-  AppendGkiSigningArgs(cmd)
-
   img_unsigned = None
   if info_dict.get("vboot"):
     img_unsigned = tempfile.NamedTemporaryFile()
@@ -1654,6 +1651,29 @@
 
   RunAndCheckOutput(cmd)
 
+  if _HasGkiCertificationArgs():
+    if not os.path.exists(img.name):
+      raise ValueError("Cannot find GKI boot.img")
+    if kernel_path is None or not os.path.exists(kernel_path):
+      raise ValueError("Cannot find GKI kernel.img")
+
+    # Certify GKI images.
+    boot_signature_bytes = b''
+    boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
+    boot_signature_bytes += _GenerateGkiCertificate(
+        kernel_path, "generic_kernel")
+
+    BOOT_SIGNATURE_SIZE = 16 * 1024
+    if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
+      raise ValueError(
+          f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
+    boot_signature_bytes += (
+        b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
+    assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
+
+    with open(img.name, 'ab') as f:
+      f.write(boot_signature_bytes)
+
   if (info_dict.get("boot_signer") == "true" and
           info_dict.get("verity_key")):
     # Hard-code the path as "/boot" for two-step special recovery image (which
@@ -1724,8 +1744,8 @@
   Args:
     image_path: The full path of the image, e.g., /path/to/boot.img.
     prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
-        boot-5.10.img, recovery.img.
-    partition_name: The partition name, e.g., 'boot' or 'recovery'.
+        boot-5.10.img, recovery.img or init_boot.img.
+    partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
     info_dict: The information dict read from misc_info.txt.
   """
   if info_dict is None:
@@ -1749,6 +1769,38 @@
     RunAndCheckOutput(cmd)
 
 
+def HasRamdisk(partition_name, info_dict=None):
+  """Returns true/false to see if a bootable image should have a ramdisk.
+
+  Args:
+    partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
+    info_dict: The information dict read from misc_info.txt.
+  """
+  if info_dict is None:
+    info_dict = OPTIONS.info_dict
+
+  if partition_name != "boot":
+    return True  # init_boot.img or recovery.img has a ramdisk.
+
+  if info_dict.get("recovery_as_boot") == "true":
+    return True  # the recovery-as-boot boot.img has a RECOVERY ramdisk.
+
+  if info_dict.get("gki_boot_image_without_ramdisk") == "true":
+    return False  # A GKI boot.img has no ramdisk since Android-13.
+
+  if info_dict.get("system_root_image") == "true":
+    # The ramdisk content is merged into the system.img, so there is NO
+    # ramdisk in the boot.img or boot-<kernel version>.img.
+    return False
+
+  if info_dict.get("init_boot") == "true":
+    # The ramdisk is moved to the init_boot.img, so there is NO
+    # ramdisk in the boot.img or boot-<kernel version>.img.
+    return False
+
+  return True
+
+
 def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
                      info_dict=None, two_step_image=False):
   """Return a File object with the desired bootable image.
@@ -1770,23 +1822,18 @@
     logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
     return File.FromLocalFile(name, prebuilt_path)
 
+  partition_name = tree_subdir.lower()
   prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
   if os.path.exists(prebuilt_path):
     logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
     signed_img = MakeTempFile()
     shutil.copy(prebuilt_path, signed_img)
-    partition_name = tree_subdir.lower()
     _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
     return File.FromLocalFile(name, signed_img)
 
   logger.info("building image from target_files %s...", tree_subdir)
 
-  # With system_root_image == "true", we don't pack ramdisk into the boot image.
-  # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
-  # for recovery.
-  has_ramdisk = (info_dict.get("system_root_image") != "true" or
-                 prebuilt_name != "boot.img" or
-                 info_dict.get("recovery_as_boot") == "true")
+  has_ramdisk = HasRamdisk(partition_name, info_dict)
 
   fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
   data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
@@ -1797,7 +1844,7 @@
   return None
 
 
-def _BuildVendorBootImage(sourcedir, info_dict=None):
+def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
   """Build a vendor boot image from the specified sourcedir.
 
   Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
@@ -1822,8 +1869,13 @@
 
   fn = os.path.join(sourcedir, "dtb")
   if os.access(fn, os.F_OK):
-    cmd.append("--dtb")
-    cmd.append(fn)
+    has_vendor_kernel_boot = (info_dict.get("vendor_kernel_boot", "").lower() == "true")
+
+    # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
+    # Otherwise pack dtb into vendor_boot.
+    if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
+      cmd.append("--dtb")
+      cmd.append(fn)
 
   fn = os.path.join(sourcedir, "vendor_cmdline")
   if os.access(fn, os.F_OK):
@@ -1883,11 +1935,11 @@
   # AVB: if enabled, calculate and add hash.
   if info_dict.get("avb_enable") == "true":
     avbtool = info_dict["avb_avbtool"]
-    part_size = info_dict["vendor_boot_size"]
+    part_size = info_dict[f'{partition_name}_size']
     cmd = [avbtool, "add_hash_footer", "--image", img.name,
-           "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
-    AppendAVBSigningArgs(cmd, "vendor_boot")
-    args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
+           "--partition_size", str(part_size), "--partition_name", partition_name]
+    AppendAVBSigningArgs(cmd, partition_name)
+    args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
     if args and args.strip():
       cmd.extend(shlex.split(args))
     RunAndCheckOutput(cmd)
@@ -1921,7 +1973,31 @@
     info_dict = OPTIONS.info_dict
 
   data = _BuildVendorBootImage(
-      os.path.join(unpack_dir, tree_subdir), info_dict)
+      os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
+  if data:
+    return File(name, data)
+  return None
+
+
+def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
+                       info_dict=None):
+  """Return a File object with the desired vendor kernel boot image.
+
+  Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
+  the source files in 'unpack_dir'/'tree_subdir'."""
+
+  prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
+  if os.path.exists(prebuilt_path):
+    logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
+    return File.FromLocalFile(name, prebuilt_path)
+
+  logger.info("building image from target_files %s...", tree_subdir)
+
+  if info_dict is None:
+    info_dict = OPTIONS.info_dict
+
+  data = _BuildVendorBootImage(
+      os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
   if data:
     return File(name, data)
   return None
@@ -1960,14 +2036,14 @@
   RunAndCheckOutput(cmd)
 
 
-def UnzipTemp(filename, pattern=None):
+def UnzipTemp(filename, patterns=None):
   """Unzips the given archive into a temporary directory and returns the name.
 
   Args:
     filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
     a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
 
-    pattern: Files to unzip from the archive. If omitted, will unzip the entire
+    patterns: Files to unzip from the archive. If omitted, will unzip the entire
     archvie.
 
   Returns:
@@ -1977,11 +2053,11 @@
   tmp = MakeTempDir(prefix="targetfiles-")
   m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
   if m:
-    UnzipToDir(m.group(1), tmp, pattern)
-    UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
+    UnzipToDir(m.group(1), tmp, patterns)
+    UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
     filename = m.group(1)
   else:
-    UnzipToDir(filename, tmp, pattern)
+    UnzipToDir(filename, tmp, patterns)
 
   return tmp
 
@@ -2018,6 +2094,8 @@
     info_dict = LoadInfoDict(input_zip)
 
   is_sparse = info_dict.get("extfs_sparse_flag")
+  if info_dict.get(which + "_disable_sparse"):
+    is_sparse = False
 
   # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
   # shared blocks (i.e. some blocks will show up in multiple files' block
@@ -2138,9 +2216,11 @@
   need_passwords = []
   key_passwords = {}
   devnull = open("/dev/null", "w+b")
-  for k in sorted(keylist):
+
+  # sorted() can't compare strings to None, so convert Nones to strings
+  for k in sorted(keylist, key=lambda x: x if x is not None else ""):
     # We don't need a password for things that aren't really keys.
-    if k in SPECIAL_CERT_STRINGS:
+    if k in SPECIAL_CERT_STRINGS or k is None:
       no_passwords.append(k)
       continue
 
@@ -2181,8 +2261,8 @@
 def GetMinSdkVersion(apk_name):
   """Gets the minSdkVersion declared in the APK.
 
-  It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
-  This can be both a decimal number (API Level) or a codename.
+  It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
+  APK file. This can be both a decimal number (API Level) or a codename.
 
   Args:
     apk_name: The APK filename.
@@ -2194,13 +2274,13 @@
     ExternalError: On failing to obtain the min SDK version.
   """
   proc = Run(
-      ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
+      [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
       stderr=subprocess.PIPE)
   stdoutdata, stderrdata = proc.communicate()
   if proc.returncode != 0:
     raise ExternalError(
-        "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
-            proc.returncode, stdoutdata, stderrdata))
+        "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
+            apk_name, proc.returncode, stdoutdata, stderrdata))
 
   for line in stdoutdata.split("\n"):
     # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
@@ -2470,13 +2550,11 @@
     opts, args = getopt.getopt(
         argv, "hvp:s:x:" + extra_opts,
         ["help", "verbose", "path=", "signapk_path=",
-         "signapk_shared_library_path=", "extra_signapk_args=",
+         "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
          "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
          "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
          "verity_signer_path=", "verity_signer_args=", "device_specific=",
-         "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
-         "aftl_key_path=", "aftl_manufacturer_key_path=",
-         "aftl_signer_helper="] + list(extra_long_opts))
+         "extra=", "logfile="] + list(extra_long_opts))
   except getopt.GetoptError as err:
     Usage(docstring)
     print("**", str(err), "**")
@@ -2496,6 +2574,8 @@
       OPTIONS.signapk_shared_library_path = a
     elif o in ("--extra_signapk_args",):
       OPTIONS.extra_signapk_args = shlex.split(a)
+    elif o in ("--aapt2_path",):
+      OPTIONS.aapt2_path = a
     elif o in ("--java_path",):
       OPTIONS.java_path = a
     elif o in ("--java_args",):
@@ -2514,16 +2594,6 @@
       OPTIONS.verity_signer_path = a
     elif o in ("--verity_signer_args",):
       OPTIONS.verity_signer_args = shlex.split(a)
-    elif o in ("--aftl_tool_path",):
-      OPTIONS.aftl_tool_path = a
-    elif o in ("--aftl_server",):
-      OPTIONS.aftl_server = a
-    elif o in ("--aftl_key_path",):
-      OPTIONS.aftl_key_path = a
-    elif o in ("--aftl_manufacturer_key_path",):
-      OPTIONS.aftl_manufacturer_key_path = a
-    elif o in ("--aftl_signer_helper",):
-      OPTIONS.aftl_signer_helper = a
     elif o in ("-s", "--device_specific"):
       OPTIONS.device_specific = a
     elif o in ("-x", "--extra"):
@@ -2783,6 +2853,9 @@
   """
   if isinstance(entries, str):
     entries = [entries]
+  # If list is empty, nothing to do
+  if not entries:
+    return
   cmd = ["zip", "-d", zip_filename] + entries
   RunAndCheckOutput(cmd)
 
@@ -2991,7 +3064,7 @@
           th.join()
 
       if p.returncode != 0:
-        logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
+        logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
         self.patch = None
         return None, None, None
       diff = ptemp.read()
@@ -3898,12 +3971,17 @@
   if not image_size:
     return None
 
+  disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
+
   image_blocks = int(image_size) // 4096 - 1
-  assert image_blocks > 0, "blocks for {} must be positive".format(which)
+  # It's OK for image_blocks to be 0, because care map ranges are inclusive.
+  # So 0-0 means "just block 0", which is valid.
+  assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
+      which, image_size)
 
   # For sparse images, we will only check the blocks that are listed in the care
   # map, i.e. the ones with meaningful data.
-  if "extfs_sparse_flag" in OPTIONS.info_dict:
+  if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
     simg = sparse_img.SparseImage(imgname)
     care_map_ranges = simg.care_map.intersect(
         rangelib.RangeSet("0-{}".format(image_blocks)))
@@ -3996,3 +4074,10 @@
     OPTIONS.replace_updated_files_list.append(care_map_path)
   else:
     ZipWrite(output_file, temp_care_map, arcname=care_map_path)
+
+
+def IsSparseImage(filepath):
+  with open(filepath, 'rb') as fp:
+    # Magic for android sparse image format
+    # https://source.android.com/devices/bootloader/images
+    return fp.read(4) == b'\x3A\xFF\x26\xED'
diff --git a/tools/releasetools/fsverity_manifest_generator.py b/tools/releasetools/fsverity_manifest_generator.py
new file mode 100644
index 0000000..b8184bc
--- /dev/null
+++ b/tools/releasetools/fsverity_manifest_generator.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+`fsverity_manifest_generator` generates build manifest APK file containing
+digests of target files. The APK file is signed so the manifest inside the APK
+can be trusted.
+"""
+
+import argparse
+import common
+import os
+import subprocess
+import sys
+from fsverity_digests_pb2 import FSVerityDigests
+
+HASH_ALGORITHM = 'sha256'
+
+def _digest(fsverity_path, input_file):
+  cmd = [fsverity_path, 'digest', input_file]
+  cmd.extend(['--compact'])
+  cmd.extend(['--hash-alg', HASH_ALGORITHM])
+  out = subprocess.check_output(cmd, universal_newlines=True).strip()
+  return bytes(bytearray.fromhex(out))
+
+if __name__ == '__main__':
+  p = argparse.ArgumentParser()
+  p.add_argument(
+      '--output',
+      help='Path to the output manifest APK',
+      required=True)
+  p.add_argument(
+      '--fsverity-path',
+      help='path to the fsverity program',
+      required=True)
+  p.add_argument(
+      '--aapt2-path',
+      help='path to the aapt2 program',
+      required=True)
+  p.add_argument(
+      '--min-sdk-version',
+      help='minimum supported sdk version of the generated manifest apk',
+      required=True)
+  p.add_argument(
+      '--version-code',
+      help='version code for the generated manifest apk',
+      required=True)
+  p.add_argument(
+      '--version-name',
+      help='version name for the generated manifest apk',
+      required=True)
+  p.add_argument(
+      '--framework-res',
+      help='path to framework-res.apk',
+      required=True)
+  p.add_argument(
+      '--apksigner-path',
+      help='path to the apksigner program',
+      required=True)
+  p.add_argument(
+      '--apk-key-path',
+      help='path to the apk key',
+      required=True)
+  p.add_argument(
+      '--apk-manifest-path',
+      help='path to AndroidManifest.xml',
+      required=True)
+  p.add_argument(
+      '--base-dir',
+      help='directory to use as a relative root for the inputs',
+      required=True)
+  p.add_argument(
+      'inputs',
+      nargs='+',
+      help='input file for the build manifest')
+  args = p.parse_args(sys.argv[1:])
+
+  digests = FSVerityDigests()
+  for f in sorted(args.inputs):
+    # f is a full path for now; make it relative so it starts with {mount_point}/
+    digest = digests.digests[os.path.relpath(f, args.base_dir)]
+    digest.digest = _digest(args.fsverity_path, f)
+    digest.hash_alg = HASH_ALGORITHM
+
+  temp_dir = common.MakeTempDir()
+
+  os.mkdir(os.path.join(temp_dir, "assets"))
+  metadata_path = os.path.join(temp_dir, "assets", "build_manifest.pb")
+  with open(metadata_path, "wb") as f:
+    f.write(digests.SerializeToString())
+
+  common.RunAndCheckOutput([args.aapt2_path, "link",
+      "-A", os.path.join(temp_dir, "assets"),
+      "-o", args.output,
+      "--min-sdk-version", args.min_sdk_version,
+      "--version-code", args.version_code,
+      "--version-name", args.version_name,
+      "-I", args.framework_res,
+      "--manifest", args.apk_manifest_path])
+  common.RunAndCheckOutput([args.apksigner_path, "sign", "--in", args.output,
+      "--cert", args.apk_key_path + ".x509.pem",
+      "--key", args.apk_key_path + ".pk8"])
diff --git a/tools/releasetools/fsverity_metadata_generator.py b/tools/releasetools/fsverity_metadata_generator.py
new file mode 100644
index 0000000..fa7cd39
--- /dev/null
+++ b/tools/releasetools/fsverity_metadata_generator.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+#
+# Copyright 2021 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+`fsverity_metadata_generator` generates fsverity metadata and signature to a
+container file
+
+This actually is a simple wrapper around the `fsverity` program. A file is
+signed by the program which produces the PKCS#7 signature file, merkle tree file
+, and the fsverity_descriptor file. Then the files are packed into a single
+output file so that the information about the signing stays together.
+
+Currently, the output of this script is used by `fd_server` which is the host-
+side backend of an authfs filesystem. `fd_server` uses this file in case when
+the underlying filesystem (ext4, etc.) on the device doesn't support the
+fsverity feature natively in which case the information is read directly from
+the filesystem using ioctl.
+"""
+
+import argparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from struct import *
+
+class TempDirectory(object):
+  def __enter__(self):
+    self.name = tempfile.mkdtemp()
+    return self.name
+
+  def __exit__(self, *unused):
+    shutil.rmtree(self.name)
+
+class FSVerityMetadataGenerator:
+  def __init__(self, fsverity_path):
+    self._fsverity_path = fsverity_path
+
+    # Default values for some properties
+    self.set_hash_alg("sha256")
+    self.set_signature('none')
+
+  def set_key_format(self, key_format):
+    self._key_format = key_format
+
+  def set_key(self, key):
+    self._key = key
+
+  def set_cert(self, cert):
+    self._cert = cert
+
+  def set_hash_alg(self, hash_alg):
+    self._hash_alg = hash_alg
+
+  def set_signature(self, signature):
+    self._signature = signature
+
+  def _raw_signature(pkcs7_sig_file):
+    """ Extracts raw signature from DER formatted PKCS#7 detached signature file
+
+    Do that by parsing the ASN.1 tree to get the location of the signature
+    in the file and then read the portion.
+    """
+
+    # Note: there seems to be no public python API (even in 3p modules) that
+    # provides direct access to the raw signature at this moment. So, `openssl
+    # asn1parse` commandline tool is used instead.
+    cmd = ['openssl', 'asn1parse']
+    cmd.extend(['-inform', 'DER'])
+    cmd.extend(['-in', pkcs7_sig_file])
+    out = subprocess.check_output(cmd, universal_newlines=True)
+
+    # The signature is the last element in the tree
+    last_line = out.splitlines()[-1]
+    m = re.search('(\d+):.*hl=\s*(\d+)\s*l=\s*(\d+)\s*.*OCTET STRING', last_line)
+    if not m:
+      raise RuntimeError("Failed to parse asn1parse output: " + out)
+    offset = int(m.group(1))
+    header_len = int(m.group(2))
+    size = int(m.group(3))
+    with open(pkcs7_sig_file, 'rb') as f:
+      f.seek(offset + header_len)
+      return f.read(size)
+
+  def digest(self, input_file):
+    cmd = [self._fsverity_path, 'digest', input_file]
+    cmd.extend(['--compact'])
+    cmd.extend(['--hash-alg', self._hash_alg])
+    out = subprocess.check_output(cmd, universal_newlines=True).strip()
+    return bytes(bytearray.fromhex(out))
+
+  def generate(self, input_file, output_file=None):
+    if self._signature != 'none':
+      if not self._key:
+        raise RuntimeError("key must be specified.")
+      if not self._cert:
+        raise RuntimeError("cert must be specified.")
+
+    if not output_file:
+      output_file = input_file + '.fsv_meta'
+
+    with TempDirectory() as temp_dir:
+      self._do_generate(input_file, output_file, temp_dir)
+
+  def _do_generate(self, input_file, output_file, work_dir):
+    # temporary files
+    desc_file = os.path.join(work_dir, 'desc')
+    merkletree_file = os.path.join(work_dir, 'merkletree')
+    sig_file = os.path.join(work_dir, 'signature')
+
+    # run the fsverity util to create the temporary files
+    cmd = [self._fsverity_path]
+    if self._signature == 'none':
+      cmd.append('digest')
+      cmd.append(input_file)
+    else:
+      cmd.append('sign')
+      cmd.append(input_file)
+      cmd.append(sig_file)
+
+      # If key is DER, convert DER private key to PEM
+      if self._key_format == 'der':
+        pem_key = os.path.join(work_dir, 'key.pem')
+        key_cmd = ['openssl', 'pkcs8']
+        key_cmd.extend(['-inform', 'DER'])
+        key_cmd.extend(['-in', self._key])
+        key_cmd.extend(['-nocrypt'])
+        key_cmd.extend(['-out', pem_key])
+        subprocess.check_call(key_cmd)
+      else:
+        pem_key = self._key
+
+      cmd.extend(['--key', pem_key])
+      cmd.extend(['--cert', self._cert])
+    cmd.extend(['--hash-alg', self._hash_alg])
+    cmd.extend(['--block-size', '4096'])
+    cmd.extend(['--out-merkle-tree', merkletree_file])
+    cmd.extend(['--out-descriptor', desc_file])
+    subprocess.check_call(cmd, stdout=open(os.devnull, 'w'))
+
+    with open(output_file, 'wb') as out:
+      # 1. version
+      out.write(pack('<I', 1))
+
+      # 2. fsverity_descriptor
+      with open(desc_file, 'rb') as f:
+        out.write(f.read())
+
+      # 3. signature
+      SIG_TYPE_NONE = 0
+      SIG_TYPE_PKCS7 = 1
+      SIG_TYPE_RAW = 2
+      if self._signature == 'raw':
+        out.write(pack('<I', SIG_TYPE_RAW))
+        sig = self._raw_signature(sig_file)
+        out.write(pack('<I', len(sig)))
+        out.write(sig)
+      elif self._signature == 'pkcs7':
+        with open(sig_file, 'rb') as f:
+          out.write(pack('<I', SIG_TYPE_PKCS7))
+          sig = f.read()
+          out.write(pack('<I', len(sig)))
+          out.write(sig)
+      else:
+        out.write(pack('<I', SIG_TYPE_NONE))
+        out.write(pack('<I', 0))
+
+      # 4. merkle tree
+      with open(merkletree_file, 'rb') as f:
+        # merkle tree is placed at the next nearest page boundary to make
+        # mmapping possible
+        out.seek(next_page(out.tell()))
+        out.write(f.read())
+
+def next_page(n):
+  """ Returns the next nearest page boundary from `n` """
+  PAGE_SIZE = 4096
+  return (n + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
+
+if __name__ == '__main__':
+  p = argparse.ArgumentParser()
+  p.add_argument(
+      '--output',
+      help='output file. If omitted, print to <INPUT>.fsv_meta',
+      metavar='output',
+      default=None)
+  p.add_argument(
+      'input',
+      help='input file to be signed')
+  p.add_argument(
+      '--key-format',
+      choices=['pem', 'der'],
+      default='der',
+      help='format of the input key. Default is der')
+  p.add_argument(
+      '--key',
+      help='PKCS#8 private key file')
+  p.add_argument(
+      '--cert',
+      help='x509 certificate file in PEM format')
+  p.add_argument(
+      '--hash-alg',
+      help='hash algorithm to use to build the merkle tree',
+      choices=['sha256', 'sha512'],
+      default='sha256')
+  p.add_argument(
+      '--signature',
+      help='format for signature',
+      choices=['none', 'raw', 'pkcs7'],
+      default='none')
+  p.add_argument(
+      '--fsverity-path',
+      help='path to the fsverity program',
+      required=True)
+  args = p.parse_args(sys.argv[1:])
+
+  generator = FSVerityMetadataGenerator(args.fsverity_path)
+  generator.set_signature(args.signature)
+  if args.signature == 'none':
+    if args.key or args.cert:
+      raise ValueError("When signature is none, key and cert can't be set")
+  else:
+    if not args.key or not args.cert:
+      raise ValueError("To generate signature, key and cert must be set")
+    generator.set_key(args.key)
+    generator.set_cert(args.cert)
+  generator.set_key_format(args.key_format)
+  generator.set_hash_alg(args.hash_alg)
+  generator.generate(args.input, args.output)
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index cbb51e1..76da89c 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -124,7 +124,7 @@
 
   for image_path in [name for name in namelist if name.startswith('IMAGES/')]:
     image = os.path.basename(image_path)
-    if OPTIONS.bootable_only and image not in('boot.img', 'recovery.img', 'bootloader'):
+    if OPTIONS.bootable_only and image not in('boot.img', 'recovery.img', 'bootloader', 'init_boot.img'):
       continue
     if not image.endswith('.img') and image != 'bootloader':
       continue
@@ -251,8 +251,5 @@
   try:
     common.CloseInheritedPipes()
     main(sys.argv[1:])
-  except common.ExternalError as e:
-    logger.exception('\n   ERROR:\n')
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/merge/Android.bp b/tools/releasetools/merge/Android.bp
new file mode 100644
index 0000000..219acf8
--- /dev/null
+++ b/tools/releasetools/merge/Android.bp
@@ -0,0 +1,75 @@
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "releasetools_merge_sources",
+    srcs: [
+        "merge_compatibility_checks.py",
+        "merge_dexopt.py",
+        "merge_meta.py",
+        "merge_target_files.py",
+        "merge_utils.py",
+    ],
+}
+
+filegroup {
+    name: "releasetools_merge_tests",
+    srcs: [
+        "test_merge_compatibility_checks.py",
+        "test_merge_meta.py",
+        "test_merge_utils.py",
+    ],
+}
+
+python_binary_host {
+    name: "merge_target_files",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [":releasetools_merge_sources"],
+    libs: [
+        "releasetools_add_img_to_target_files",
+        "releasetools_build_super_image",
+        "releasetools_check_target_files_vintf",
+        "releasetools_common",
+        "releasetools_find_shareduid_violation",
+        "releasetools_img_from_target_files",
+        "releasetools_ota_from_target_files",
+    ],
+    required: [
+        "checkvintf",
+        "host_init_verifier",
+        "secilc",
+    ],
+    target: {
+        darwin: {
+            // libs dep "releasetools_ota_from_target_files" is disabled on darwin
+            enabled: false,
+        },
+    },
+}
+
+python_binary_host {
+    name: "merge_builds",
+    defaults: ["releasetools_binary_defaults"],
+    srcs: [
+        "merge_builds.py",
+    ],
+    libs: [
+        "releasetools_build_super_image",
+        "releasetools_common",
+    ],
+}
diff --git a/tools/releasetools/merge/OWNERS b/tools/releasetools/merge/OWNERS
new file mode 100644
index 0000000..9012e3a
--- /dev/null
+++ b/tools/releasetools/merge/OWNERS
@@ -0,0 +1,3 @@
+danielnorman@google.com
+jgalmes@google.com
+rseymour@google.com
diff --git a/tools/releasetools/merge_builds.py b/tools/releasetools/merge/merge_builds.py
similarity index 100%
rename from tools/releasetools/merge_builds.py
rename to tools/releasetools/merge/merge_builds.py
diff --git a/tools/releasetools/merge/merge_compatibility_checks.py b/tools/releasetools/merge/merge_compatibility_checks.py
new file mode 100644
index 0000000..207abe2
--- /dev/null
+++ b/tools/releasetools/merge/merge_compatibility_checks.py
@@ -0,0 +1,206 @@
+#!/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.
+#
+"""Compatibility checks that should be performed on merged target_files."""
+
+import json
+import logging
+import os
+from xml.etree import ElementTree
+
+import apex_utils
+import check_target_files_vintf
+import common
+import find_shareduid_violation
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def CheckCompatibility(target_files_dir, partition_map):
+  """Runs various compatibility checks.
+
+  Returns a possibly-empty list of error messages.
+  """
+  errors = []
+
+  errors.extend(CheckVintf(target_files_dir))
+  errors.extend(CheckShareduidViolation(target_files_dir, partition_map))
+  errors.extend(CheckApexDuplicatePackages(target_files_dir, partition_map))
+
+  # The remaining checks only use the following partitions:
+  partition_map = {
+      partition: path
+      for partition, path in partition_map.items()
+      if partition in ('system', 'system_ext', 'product', 'vendor', 'odm')
+  }
+
+  errors.extend(CheckInitRcFiles(target_files_dir, partition_map))
+  errors.extend(CheckCombinedSepolicy(target_files_dir, partition_map))
+
+  return errors
+
+
+def CheckVintf(target_files_dir):
+  """Check for any VINTF issues using check_vintf."""
+  errors = []
+  try:
+    if not check_target_files_vintf.CheckVintf(target_files_dir):
+      errors.append('Incompatible VINTF.')
+  except RuntimeError as err:
+    errors.append(str(err))
+  return errors
+
+
+def CheckShareduidViolation(target_files_dir, partition_map):
+  """Check for any APK sharedUserId violations across partition sets.
+
+  Writes results to META/shareduid_violation_modules.json to help
+  with followup debugging.
+  """
+  errors = []
+  violation = find_shareduid_violation.FindShareduidViolation(
+      target_files_dir, partition_map)
+  shareduid_violation_modules = os.path.join(
+      target_files_dir, 'META', 'shareduid_violation_modules.json')
+  with open(shareduid_violation_modules, 'w') as f:
+    # Write the output to a file to enable debugging.
+    f.write(violation)
+
+    # Check for violations across the partition sets.
+    shareduid_errors = common.SharedUidPartitionViolations(
+        json.loads(violation),
+        [OPTIONS.framework_partition_set, OPTIONS.vendor_partition_set])
+    if shareduid_errors:
+      for error in shareduid_errors:
+        errors.append('APK sharedUserId error: %s' % error)
+      errors.append('See APK sharedUserId violations file: %s' %
+                    shareduid_violation_modules)
+  return errors
+
+
+def CheckInitRcFiles(target_files_dir, partition_map):
+  """Check for any init.rc issues using host_init_verifier."""
+  try:
+    common.RunHostInitVerifier(
+        product_out=target_files_dir, partition_map=partition_map)
+  except RuntimeError as err:
+    return [str(err)]
+  return []
+
+
+def CheckCombinedSepolicy(target_files_dir, partition_map, execute=True):
+  """Uses secilc to compile a split sepolicy file.
+
+  Depends on various */etc/selinux/* and */etc/vintf/* files within partitions.
+  """
+  errors = []
+
+  def get_file(partition, path):
+    if partition not in partition_map:
+      logger.warning('Cannot load SEPolicy files for missing partition %s',
+                     partition)
+      return None
+    file_path = os.path.join(target_files_dir, partition_map[partition], path)
+    if os.path.exists(file_path):
+      return file_path
+    return None
+
+  # Load the kernel sepolicy version from the FCM. This is normally provided
+  # directly to selinux.cpp as a build flag, but is also available in this file.
+  fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml')
+  if not fcm_file:
+    errors.append('Missing required file for loading sepolicy: '
+                  '/system/etc/vintf/compatibility_matrix.device.xml')
+    return errors
+  kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find(
+      'sepolicy/kernel-sepolicy-version').text
+
+  # Load the vendor's plat sepolicy version. This is the version used for
+  # locating sepolicy mapping files.
+  vendor_plat_version_file = get_file('vendor',
+                                      'etc/selinux/plat_sepolicy_vers.txt')
+  if not vendor_plat_version_file:
+    errors.append('Missing required sepolicy file %s' %
+                  vendor_plat_version_file)
+    return errors
+  with open(vendor_plat_version_file) as f:
+    vendor_plat_version = f.read().strip()
+
+  # Use the same flags and arguments as selinux.cpp OpenSplitPolicy().
+  cmd = ['secilc', '-m', '-M', 'true', '-G', '-N']
+  cmd.extend(['-c', kernel_sepolicy_version])
+  cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')])
+  cmd.extend(['-f', '/dev/null'])
+
+  required_policy_files = (
+      ('system', 'etc/selinux/plat_sepolicy.cil'),
+      ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('vendor', 'etc/selinux/vendor_sepolicy.cil'),
+      ('vendor', 'etc/selinux/plat_pub_versioned.cil'),
+  )
+  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
+                     required_policy_files)):
+    if not policy:
+      errors.append('Missing required sepolicy file %s' % policy)
+      return errors
+    cmd.append(policy)
+
+  optional_policy_files = (
+      ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version),
+      ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'),
+      ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('product', 'etc/selinux/product_sepolicy.cil'),
+      ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
+      ('odm', 'etc/selinux/odm_sepolicy.cil'),
+  )
+  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
+                     optional_policy_files)):
+    if policy:
+      cmd.append(policy)
+
+  try:
+    if execute:
+      common.RunAndCheckOutput(cmd)
+    else:
+      return cmd
+  except RuntimeError as err:
+    errors.append(str(err))
+
+  return errors
+
+
+def CheckApexDuplicatePackages(target_files_dir, partition_map):
+  """Checks if the same APEX package name is provided by multiple partitions."""
+  errors = []
+
+  apex_packages = set()
+  for partition in partition_map.keys():
+    try:
+      apex_info = apex_utils.GetApexInfoFromTargetFiles(
+          target_files_dir, partition, compressed_only=False)
+    except RuntimeError as err:
+      errors.append(str(err))
+      apex_info = []
+    partition_apex_packages = set([info.package_name for info in apex_info])
+    duplicates = apex_packages.intersection(partition_apex_packages)
+    if duplicates:
+      errors.append(
+          'Duplicate APEX package_names found in multiple partitions: %s' %
+          ' '.join(duplicates))
+    apex_packages.update(partition_apex_packages)
+
+  return errors
diff --git a/tools/releasetools/merge/merge_dexopt.py b/tools/releasetools/merge/merge_dexopt.py
new file mode 100644
index 0000000..7bf9bd4
--- /dev/null
+++ b/tools/releasetools/merge/merge_dexopt.py
@@ -0,0 +1,323 @@
+#!/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.
+#
+"""Generates dexopt files for vendor apps, from a merged target_files.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import glob
+import json
+import logging
+import os
+import shutil
+import subprocess
+
+import common
+import merge_utils
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def MergeDexopt(temp_dir, output_target_files_dir):
+  """If needed, generates dexopt files for vendor apps.
+
+  Args:
+    temp_dir: Location containing an 'output' directory where target files have
+      been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES,
+      etc.
+    output_target_files_dir: The name of a directory that will be used to create
+      the output target files package after all the special cases are processed.
+  """
+  # Load vendor and framework META/misc_info.txt.
+  if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or
+      OPTIONS.framework_dexpreopt_tools is None or
+      OPTIONS.framework_dexpreopt_config is None or
+      OPTIONS.vendor_dexpreopt_config is None):
+    return
+
+  logger.info('applying dexpreopt')
+
+  # The directory structure to apply dexpreopt is:
+  #
+  # <temp_dir>/
+  #     framework_meta/
+  #         META/
+  #     vendor_meta/
+  #         META/
+  #     output/
+  #         SYSTEM/
+  #         VENDOR/
+  #         IMAGES/
+  #         <other items extracted from system and vendor target files>
+  #     tools/
+  #         <contents of dexpreopt_tools.zip>
+  #     system_config/
+  #         <contents of system dexpreopt_config.zip>
+  #     vendor_config/
+  #         <contents of vendor dexpreopt_config.zip>
+  #     system -> output/SYSTEM
+  #     vendor -> output/VENDOR
+  #     apex -> output/SYSTEM/apex (only for flattened APEX builds)
+  #     apex/ (extracted updatable APEX)
+  #         <apex 1>/
+  #             ...
+  #         <apex 2>/
+  #             ...
+  #         ...
+  #     out/dex2oat_result/vendor/
+  #         <app>
+  #             oat/arm64/
+  #                 package.vdex
+  #                 package.odex
+  #         <priv-app>
+  #             oat/arm64/
+  #                 package.vdex
+  #                 package.odex
+  dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools')
+  dexpreopt_framework_config_files_temp_dir = os.path.join(
+      temp_dir, 'system_config')
+  dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir,
+                                                        'vendor_config')
+
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_dexpreopt_tools,
+      output_dir=dexpreopt_tools_files_temp_dir,
+      extract_item_list=('*',))
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_dexpreopt_config,
+      output_dir=dexpreopt_framework_config_files_temp_dir,
+      extract_item_list=('*',))
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_dexpreopt_config,
+      output_dir=dexpreopt_vendor_config_files_temp_dir,
+      extract_item_list=('*',))
+
+  os.symlink(
+      os.path.join(output_target_files_dir, 'SYSTEM'),
+      os.path.join(temp_dir, 'system'))
+  os.symlink(
+      os.path.join(output_target_files_dir, 'VENDOR'),
+      os.path.join(temp_dir, 'vendor'))
+
+  # The directory structure for flatteded APEXes is:
+  #
+  # SYSTEM
+  #     apex
+  #         <APEX name, e.g., com.android.wifi>
+  #             apex_manifest.pb
+  #             apex_pubkey
+  #             etc/
+  #             javalib/
+  #             lib/
+  #             lib64/
+  #             priv-app/
+  #
+  # The directory structure for updatable APEXes is:
+  #
+  # SYSTEM
+  #     apex
+  #         com.android.adbd.apex
+  #         com.android.appsearch.apex
+  #         com.android.art.apex
+  #         ...
+  apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
+
+  # Check for flattended versus updatable APEX.
+  if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
+    # Extract APEX.
+    logging.info('extracting APEX')
+
+    apex_extract_root_dir = os.path.join(temp_dir, 'apex')
+    os.makedirs(apex_extract_root_dir)
+
+    for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
+                 glob.glob(os.path.join(apex_root, '*.capex'))):
+      logging.info('  apex: %s', apex)
+      # deapexer is in the same directory as the merge_target_files binary extracted
+      # from otatools.zip.
+      apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
+      logging.info('    info: %s', apex_json_info)
+      apex_info = json.loads(apex_json_info)
+      apex_name = apex_info['name']
+      logging.info('    name: %s', apex_name)
+
+      apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
+      os.makedirs(apex_extract_dir)
+
+      # deapexer uses debugfs_static, which is part of otatools.zip.
+      command = [
+          'deapexer',
+          '--debugfs_path',
+          'debugfs_static',
+          'extract',
+          apex,
+          apex_extract_dir,
+      ]
+      logging.info('    running %s', command)
+      subprocess.check_call(command)
+  else:
+    # Flattened APEXes don't need to be extracted since they have the necessary
+    # directory structure.
+    os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
+
+  # Modify system config to point to the tools that have been extracted.
+  # Absolute or .. paths are not allowed  by the dexpreopt_gen tool in
+  # dexpreopt_soong.config.
+  dexpreopt_framework_soon_config = os.path.join(
+      dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config')
+  with open(dexpreopt_framework_soon_config, 'w') as f:
+    dexpreopt_soong_config = {
+        'Profman': 'tools/profman',
+        'Dex2oat': 'tools/dex2oatd',
+        'Aapt': 'tools/aapt2',
+        'SoongZip': 'tools/soong_zip',
+        'Zip2zip': 'tools/zip2zip',
+        'ManifestCheck': 'tools/manifest_check',
+        'ConstructContext': 'tools/construct_context',
+    }
+    json.dump(dexpreopt_soong_config, f)
+
+  # TODO(b/188179859): Make *dex location configurable to vendor or system_other.
+  use_system_other_odex = False
+
+  if use_system_other_odex:
+    dex_img = 'SYSTEM_OTHER'
+  else:
+    dex_img = 'VENDOR'
+    # Open vendor_filesystem_config to append the items generated by dexopt.
+    vendor_file_system_config = open(
+        os.path.join(temp_dir, 'output', 'META',
+                     'vendor_filesystem_config.txt'), 'a')
+
+  # Dexpreopt vendor apps.
+  dexpreopt_config_suffix = '_dexpreopt.config'
+  for config in glob.glob(
+      os.path.join(dexpreopt_vendor_config_files_temp_dir,
+                   '*' + dexpreopt_config_suffix)):
+    app = os.path.basename(config)[:-len(dexpreopt_config_suffix)]
+    logging.info('dexpreopt config: %s %s', config, app)
+
+    apk_dir = 'app'
+    apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
+    if not os.path.exists(apk_path):
+      apk_dir = 'priv-app'
+      apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
+      if not os.path.exists(apk_path):
+        logging.warning(
+            'skipping dexpreopt for %s, no apk found in vendor/app '
+            'or vendor/priv-app', app)
+        continue
+
+    # Generate dexpreopting script. Note 'out_dir' is not the output directory
+    # where the script is generated, but the OUT_DIR at build time referenced
+    # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows
+    # how to adjust the path.
+    command = [
+        os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'),
+        '-global',
+        os.path.join(dexpreopt_framework_config_files_temp_dir,
+                     'dexpreopt.config'),
+        '-global_soong',
+        os.path.join(dexpreopt_framework_config_files_temp_dir,
+                     'dexpreopt_soong.config'),
+        '-module',
+        config,
+        '-dexpreopt_script',
+        'dexpreopt_app.sh',
+        '-out_dir',
+        'out',
+        '-base_path',
+        '.',
+        '--uses_target_files',
+    ]
+
+    # Run the command from temp_dir so all tool paths are its descendants.
+    logging.info('running %s', command)
+    subprocess.check_call(command, cwd=temp_dir)
+
+    # Call the generated script.
+    command = ['sh', 'dexpreopt_app.sh', apk_path]
+    logging.info('running %s', command)
+    subprocess.check_call(command, cwd=temp_dir)
+
+    # Output files are in:
+    #
+    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex
+    # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex
+    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex
+    # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex
+    #
+    # Copy the files to their destination. The structure of system_other is:
+    #
+    # system_other/
+    #     system-other-odex-marker
+    #     system/
+    #         app/
+    #             <app>/oat/arm64/
+    #                 <app>.odex
+    #                 <app>.vdex
+    #             ...
+    #         priv-app/
+    #             <app>/oat/arm64/
+    #                 <app>.odex
+    #                 <app>.vdex
+    #             ...
+
+    # TODO(b/188179859): Support for other architectures.
+    arch = 'arm64'
+
+    dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app,
+                                   'oat', arch)
+    os.makedirs(dex_destination)
+    dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor',
+                                apk_dir, app, 'oat', arch)
+    shutil.copy(
+        os.path.join(dex2oat_path, 'package.vdex'),
+        os.path.join(dex_destination, app + '.vdex'))
+    shutil.copy(
+        os.path.join(dex2oat_path, 'package.odex'),
+        os.path.join(dex_destination, app + '.odex'))
+
+    # Append entries to vendor_file_system_config.txt, such as:
+    #
+    # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
+    if not use_system_other_odex:
+      vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat'
+      selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0'
+      vendor_file_system_config.writelines([
+          vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n',
+          vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n',
+          vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' +
+          selabel + '\n',
+          vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' +
+          selabel + '\n',
+      ])
+
+  if not use_system_other_odex:
+    vendor_file_system_config.close()
+    # Delete vendor.img so that it will be regenerated.
+    # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework)
+    #                    and S(vendor) may require logic similar to that in
+    #                    rebuild_image_with_sepolicy.
+    vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img')
+    if os.path.exists(vendor_img):
+      logging.info('Deleting %s', vendor_img)
+      os.remove(vendor_img)
diff --git a/tools/releasetools/merge/merge_meta.py b/tools/releasetools/merge/merge_meta.py
new file mode 100644
index 0000000..580b3ce
--- /dev/null
+++ b/tools/releasetools/merge/merge_meta.py
@@ -0,0 +1,287 @@
+#!/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.
+#
+"""Functions for merging META/* files from partial builds.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import logging
+import os
+import re
+import shutil
+
+import build_image
+import common
+import merge_utils
+import sparse_img
+import verity_utils
+
+from common import ExternalError
+
+logger = logging.getLogger(__name__)
+
+OPTIONS = common.OPTIONS
+
+# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
+# the file. We use these partition tags to filter the entries in those files
+# from the two different target files packages to produce a merged apexkeys.txt
+# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
+# like this: 'partition="product"'. We use the group syntax grab the value of
+# the tag. We use non-greedy matching in case there are other fields on the
+# same line.
+
+PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
+
+# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
+# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
+
+MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
+
+
+def MergeMetaFiles(temp_dir, merged_dir):
+  """Merges various files in META/*."""
+
+  framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_target_files,
+      output_dir=os.path.dirname(framework_meta_dir),
+      extract_item_list=('META/*',))
+
+  vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_target_files,
+      output_dir=os.path.dirname(vendor_meta_dir),
+      extract_item_list=('META/*',))
+
+  merged_meta_dir = os.path.join(merged_dir, 'META')
+
+  # Merge META/misc_info.txt into OPTIONS.merged_misc_info,
+  # but do not write it yet. The following functions may further
+  # modify this dict.
+  OPTIONS.merged_misc_info = MergeMiscInfo(
+      framework_meta_dir=framework_meta_dir,
+      vendor_meta_dir=vendor_meta_dir,
+      merged_meta_dir=merged_meta_dir)
+
+  CopyNamedFileContexts(
+      framework_meta_dir=framework_meta_dir,
+      vendor_meta_dir=vendor_meta_dir,
+      merged_meta_dir=merged_meta_dir)
+
+  if OPTIONS.merged_misc_info.get('use_dynamic_partitions') == 'true':
+    MergeDynamicPartitionsInfo(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir)
+
+  if OPTIONS.merged_misc_info.get('ab_update') == 'true':
+    MergeAbPartitions(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir)
+    UpdateCareMapImageSizeProps(images_dir=os.path.join(merged_dir, 'IMAGES'))
+
+  for file_name in ('apkcerts.txt', 'apexkeys.txt'):
+    MergePackageKeys(
+        framework_meta_dir=framework_meta_dir,
+        vendor_meta_dir=vendor_meta_dir,
+        merged_meta_dir=merged_meta_dir,
+        file_name=file_name)
+
+  # Write the now-finalized OPTIONS.merged_misc_info.
+  merge_utils.WriteSortedData(
+      data=OPTIONS.merged_misc_info,
+      path=os.path.join(merged_meta_dir, 'misc_info.txt'))
+
+
+def MergeAbPartitions(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Merges META/ab_partitions.txt.
+
+  The output contains the union of the partition names.
+  """
+  with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
+    framework_ab_partitions = f.read().splitlines()
+
+  with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
+    vendor_ab_partitions = f.read().splitlines()
+
+  merge_utils.WriteSortedData(
+      data=set(framework_ab_partitions + vendor_ab_partitions),
+      path=os.path.join(merged_meta_dir, 'ab_partitions.txt'))
+
+
+def MergeMiscInfo(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Merges META/misc_info.txt.
+
+  The output contains a combination of key=value pairs from both inputs.
+  Most pairs are taken from the vendor input, while some are taken from
+  the framework input.
+  """
+
+  OPTIONS.framework_misc_info = common.LoadDictionaryFromFile(
+      os.path.join(framework_meta_dir, 'misc_info.txt'))
+  OPTIONS.vendor_misc_info = common.LoadDictionaryFromFile(
+      os.path.join(vendor_meta_dir, 'misc_info.txt'))
+
+  # Merged misc info is a combination of vendor misc info plus certain values
+  # from the framework misc info.
+
+  merged_dict = OPTIONS.vendor_misc_info
+  for key in OPTIONS.framework_misc_info_keys:
+    if key in OPTIONS.framework_misc_info:
+      merged_dict[key] = OPTIONS.framework_misc_info[key]
+
+  # If AVB is enabled then ensure that we build vbmeta.img.
+  # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
+  # skip building an incomplete vbmeta.img.
+  if merged_dict.get('avb_enable') == 'true':
+    merged_dict['avb_building_vbmeta_image'] = 'true'
+
+  return merged_dict
+
+
+def MergeDynamicPartitionsInfo(framework_meta_dir, vendor_meta_dir,
+                               merged_meta_dir):
+  """Merge META/dynamic_partitions_info.txt."""
+  framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
+      os.path.join(framework_meta_dir, 'dynamic_partitions_info.txt'))
+  vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
+      os.path.join(vendor_meta_dir, 'dynamic_partitions_info.txt'))
+
+  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
+      framework_dict=framework_dynamic_partitions_dict,
+      vendor_dict=vendor_dynamic_partitions_dict)
+
+  merge_utils.WriteSortedData(
+      data=merged_dynamic_partitions_dict,
+      path=os.path.join(merged_meta_dir, 'dynamic_partitions_info.txt'))
+
+  # Merge misc info keys used for Dynamic Partitions.
+  OPTIONS.merged_misc_info.update(merged_dynamic_partitions_dict)
+  # Ensure that add_img_to_target_files rebuilds super split images for
+  # devices that retrofit dynamic partitions. This flag may have been set to
+  # false in the partial builds to prevent duplicate building of super.img.
+  OPTIONS.merged_misc_info['build_super_partition'] = 'true'
+
+
+def MergePackageKeys(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
+                     file_name):
+  """Merges APK/APEX key list files."""
+
+  if file_name not in ('apkcerts.txt', 'apexkeys.txt'):
+    raise ExternalError(
+        'Unexpected file_name provided to merge_package_keys_txt: %s',
+        file_name)
+
+  def read_helper(d):
+    temp = {}
+    with open(os.path.join(d, file_name)) as f:
+      for line in f.read().splitlines():
+        line = line.strip()
+        if line:
+          name_search = MODULE_KEY_PATTERN.search(line.split()[0])
+          temp[name_search.group(1)] = line
+    return temp
+
+  framework_dict = read_helper(framework_meta_dir)
+  vendor_dict = read_helper(vendor_meta_dir)
+  merged_dict = {}
+
+  def filter_into_merged_dict(item_dict, partition_set):
+    for key, value in item_dict.items():
+      tag_search = PARTITION_TAG_PATTERN.search(value)
+
+      if tag_search is None:
+        raise ValueError('Entry missing partition tag: %s' % value)
+
+      partition_tag = tag_search.group(1)
+
+      if partition_tag in partition_set:
+        if key in merged_dict:
+          if OPTIONS.allow_duplicate_apkapex_keys:
+            # TODO(b/150582573) Always raise on duplicates.
+            logger.warning('Duplicate key %s' % key)
+            continue
+          else:
+            raise ValueError('Duplicate key %s' % key)
+
+        merged_dict[key] = value
+
+  # Prioritize framework keys first.
+  # Duplicate keys from vendor are an error, or ignored.
+  filter_into_merged_dict(framework_dict, OPTIONS.framework_partition_set)
+  filter_into_merged_dict(vendor_dict, OPTIONS.vendor_partition_set)
+
+  # The following code is similar to WriteSortedData, but different enough
+  # that we couldn't use that function. We need the output to be sorted by the
+  # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
+  # allows the sort to be consistent with the framework/vendor input data and
+  # eases comparison of input data with merged data.
+  with open(os.path.join(merged_meta_dir, file_name), 'w') as output:
+    for key, value in sorted(merged_dict.items()):
+      output.write(value + '\n')
+
+
+def CopyNamedFileContexts(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
+  """Creates named copies of each partial build's file_contexts.bin.
+
+  Used when regenerating images from the partial build.
+  """
+
+  def copy_fc_file(source_dir, file_name):
+    for name in (file_name, 'file_contexts.bin'):
+      fc_path = os.path.join(source_dir, name)
+      if os.path.exists(fc_path):
+        shutil.copyfile(fc_path, os.path.join(merged_meta_dir, file_name))
+        return
+    raise ValueError('Missing file_contexts file from %s: %s', source_dir,
+                     file_name)
+
+  copy_fc_file(framework_meta_dir, 'framework_file_contexts.bin')
+  copy_fc_file(vendor_meta_dir, 'vendor_file_contexts.bin')
+
+  # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
+  # depending on which dictionary the key came from.
+  # Only the file basename is required because all selinux_fc properties are
+  # replaced with the full path to the file under META/ when misc_info.txt is
+  # loaded from target files for repacking. See common.py LoadInfoDict().
+  for key in OPTIONS.vendor_misc_info:
+    if key.endswith('_selinux_fc'):
+      OPTIONS.merged_misc_info[key] = 'vendor_file_contexts.bin'
+  for key in OPTIONS.framework_misc_info:
+    if key.endswith('_selinux_fc'):
+      OPTIONS.merged_misc_info[key] = 'framework_file_contexts.bin'
+
+
+def UpdateCareMapImageSizeProps(images_dir):
+  """Sets <partition>_image_size props in misc_info.
+
+  add_images_to_target_files uses these props to generate META/care_map.pb.
+  Regenerated images will have this property set during regeneration.
+
+  However, images copied directly from input partial target files packages
+  need this value calculated here.
+  """
+  for partition in common.PARTITIONS_WITH_CARE_MAP:
+    image_path = os.path.join(images_dir, '{}.img'.format(partition))
+    if os.path.exists(image_path):
+      partition_size = sparse_img.GetImagePartitionSize(image_path)
+      image_props = build_image.ImagePropFromGlobalDict(
+          OPTIONS.merged_misc_info, partition)
+      verity_image_builder = verity_utils.CreateVerityImageBuilder(image_props)
+      image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
+      OPTIONS.merged_misc_info['{}_image_size'.format(partition)] = image_size
diff --git a/tools/releasetools/merge/merge_target_files.py b/tools/releasetools/merge/merge_target_files.py
new file mode 100755
index 0000000..c06fd4c
--- /dev/null
+++ b/tools/releasetools/merge/merge_target_files.py
@@ -0,0 +1,611 @@
+#!/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.
+#
+"""This script merges two partial target files packages.
+
+One input package contains framework files, and the other contains vendor files.
+
+This script produces a complete, merged target files package:
+  - This package can be used to generate a flashable IMG package.
+    See --output-img.
+  - This package can be used to generate an OTA package. See --output-ota.
+  - The merged package is checked for compatibility between the two inputs.
+
+Usage: merge_target_files [args]
+
+  --framework-target-files framework-target-files-zip-archive
+      The input target files package containing framework bits. This is a zip
+      archive.
+
+  --framework-item-list framework-item-list-file
+      The optional path to a newline-separated config file of items that
+      are extracted as-is from the framework target files package.
+
+  --framework-misc-info-keys framework-misc-info-keys-file
+      The optional path to a newline-separated config file of keys to
+      extract from the framework META/misc_info.txt file.
+
+  --vendor-target-files vendor-target-files-zip-archive
+      The input target files package containing vendor bits. This is a zip
+      archive.
+
+  --vendor-item-list vendor-item-list-file
+      The optional path to a newline-separated config file of items that
+      are extracted as-is from the vendor target files package.
+
+  --output-target-files output-target-files-package
+      If provided, the output merged target files package. Also a zip archive.
+
+  --output-dir output-directory
+      If provided, the destination directory for saving merged files. Requires
+      the --output-item-list flag.
+      Can be provided alongside --output-target-files, or by itself.
+
+  --output-item-list output-item-list-file.
+      The optional path to a newline-separated config file that specifies the
+      file patterns to copy into the --output-dir. Required if providing
+      the --output-dir flag.
+
+  --output-ota output-ota-package
+      The output ota package. This is a zip archive. Use of this flag may
+      require passing the --path common flag; see common.py.
+
+  --output-img output-img-package
+      The output img package, suitable for use with 'fastboot update'. Use of
+      this flag may require passing the --path common flag; see common.py.
+
+  --output-super-empty output-super-empty-image
+      If provided, creates a super_empty.img file from the merged target
+      files package and saves it at this path.
+
+  --rebuild_recovery
+      Copy the recovery image used by non-A/B devices, used when
+      regenerating vendor images with --rebuild-sepolicy.
+
+  --allow-duplicate-apkapex-keys
+      If provided, duplicate APK/APEX keys are ignored and the value from the
+      framework is used.
+
+  --rebuild-sepolicy
+      If provided, rebuilds odm.img or vendor.img to include merged sepolicy
+      files. If odm is present then odm is preferred.
+
+  --vendor-otatools otatools.zip
+      If provided, use this otatools.zip when recompiling the odm or vendor
+      image to include sepolicy.
+
+  --keep-tmp
+      Keep tempoary files for debugging purposes.
+
+  The following only apply when using the VSDK to perform dexopt on vendor apps:
+
+  --framework-dexpreopt-config
+      If provided, the location of framwework's dexpreopt_config.zip.
+
+  --framework-dexpreopt-tools
+      if provided, the location of framework's dexpreopt_tools.zip.
+
+  --vendor-dexpreopt-config
+      If provided, the location of vendor's dexpreopt_config.zip.
+"""
+
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+import add_img_to_target_files
+import build_image
+import build_super_image
+import common
+import img_from_target_files
+import merge_compatibility_checks
+import merge_dexopt
+import merge_meta
+import merge_utils
+import ota_from_target_files
+
+from common import ExternalError
+
+logger = logging.getLogger(__name__)
+
+OPTIONS = common.OPTIONS
+# Always turn on verbose logging.
+OPTIONS.verbose = True
+OPTIONS.framework_target_files = None
+OPTIONS.framework_item_list = []
+OPTIONS.framework_misc_info_keys = []
+OPTIONS.vendor_target_files = None
+OPTIONS.vendor_item_list = []
+OPTIONS.output_target_files = None
+OPTIONS.output_dir = None
+OPTIONS.output_item_list = []
+OPTIONS.output_ota = None
+OPTIONS.output_img = None
+OPTIONS.output_super_empty = None
+OPTIONS.rebuild_recovery = False
+# TODO(b/150582573): Remove this option.
+OPTIONS.allow_duplicate_apkapex_keys = False
+OPTIONS.vendor_otatools = None
+OPTIONS.rebuild_sepolicy = False
+OPTIONS.keep_tmp = False
+OPTIONS.framework_dexpreopt_config = None
+OPTIONS.framework_dexpreopt_tools = None
+OPTIONS.vendor_dexpreopt_config = None
+
+
+def create_merged_package(temp_dir):
+  """Merges two target files packages into one target files structure.
+
+  Returns:
+    Path to merged package under temp directory.
+  """
+  # Extract "as is" items from the input framework and vendor partial target
+  # files packages directly into the output temporary directory, since these items
+  # do not need special case processing.
+
+  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.framework_target_files,
+      output_dir=output_target_files_temp_dir,
+      extract_item_list=OPTIONS.framework_item_list)
+  merge_utils.ExtractItems(
+      input_zip=OPTIONS.vendor_target_files,
+      output_dir=output_target_files_temp_dir,
+      extract_item_list=OPTIONS.vendor_item_list)
+
+  # Perform special case processing on META/* items.
+  # After this function completes successfully, all the files we need to create
+  # the output target files package are in place.
+  merge_meta.MergeMetaFiles(
+      temp_dir=temp_dir, merged_dir=output_target_files_temp_dir)
+
+  merge_dexopt.MergeDexopt(
+      temp_dir=temp_dir, output_target_files_dir=output_target_files_temp_dir)
+
+  return output_target_files_temp_dir
+
+
+def generate_missing_images(target_files_dir):
+  """Generate any missing images from target files."""
+
+  # Regenerate IMAGES in the target directory.
+
+  add_img_args = [
+      '--verbose',
+      '--add_missing',
+  ]
+  if OPTIONS.rebuild_recovery:
+    add_img_args.append('--rebuild_recovery')
+  add_img_args.append(target_files_dir)
+
+  add_img_to_target_files.main(add_img_args)
+
+
+def rebuild_image_with_sepolicy(target_files_dir):
+  """Rebuilds odm.img or vendor.img to include merged sepolicy files.
+
+  If odm is present then odm is preferred -- otherwise vendor is used.
+  """
+  partition = 'vendor'
+  if os.path.exists(os.path.join(target_files_dir, 'ODM')) or os.path.exists(
+      os.path.join(target_files_dir, 'IMAGES/odm.img')):
+    partition = 'odm'
+  partition_img = '{}.img'.format(partition)
+  partition_map = '{}.map'.format(partition)
+
+  logger.info('Recompiling %s using the merged sepolicy files.', partition_img)
+
+  # Copy the combined SEPolicy file and framework hashes to the image that is
+  # being rebuilt.
+  def copy_selinux_file(input_path, output_filename):
+    input_filename = os.path.join(target_files_dir, input_path)
+    if not os.path.exists(input_filename):
+      input_filename = input_filename.replace('SYSTEM_EXT/', 'SYSTEM/system_ext/') \
+          .replace('PRODUCT/', 'SYSTEM/product/')
+      if not os.path.exists(input_filename):
+        logger.info('Skipping copy_selinux_file for %s', input_filename)
+        return
+    shutil.copy(
+        input_filename,
+        os.path.join(target_files_dir, partition.upper(), 'etc/selinux',
+                     output_filename))
+
+  copy_selinux_file('META/combined_sepolicy', 'precompiled_sepolicy')
+  copy_selinux_file('SYSTEM/etc/selinux/plat_sepolicy_and_mapping.sha256',
+                    'precompiled_sepolicy.plat_sepolicy_and_mapping.sha256')
+  copy_selinux_file(
+      'SYSTEM_EXT/etc/selinux/system_ext_sepolicy_and_mapping.sha256',
+      'precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256')
+  copy_selinux_file('PRODUCT/etc/selinux/product_sepolicy_and_mapping.sha256',
+                    'precompiled_sepolicy.product_sepolicy_and_mapping.sha256')
+
+  if not OPTIONS.vendor_otatools:
+    # Remove the partition from the merged target-files archive. It will be
+    # rebuilt later automatically by generate_missing_images().
+    os.remove(os.path.join(target_files_dir, 'IMAGES', partition_img))
+    return
+
+  # TODO(b/192253131): Remove the need for vendor_otatools by fixing
+  # backwards-compatibility issues when compiling images across releases.
+  if not OPTIONS.vendor_target_files:
+    raise ValueError(
+        'Expected vendor_target_files if vendor_otatools is not None.')
+  logger.info(
+      '%s recompilation will be performed using the vendor otatools.zip',
+      partition_img)
+
+  # Unzip the vendor build's otatools.zip and target-files archive.
+  vendor_otatools_dir = common.MakeTempDir(
+      prefix='merge_target_files_vendor_otatools_')
+  vendor_target_files_dir = common.MakeTempDir(
+      prefix='merge_target_files_vendor_target_files_')
+  common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
+  common.UnzipToDir(OPTIONS.vendor_target_files, vendor_target_files_dir)
+
+  # Copy the partition contents from the merged target-files archive to the
+  # vendor target-files archive.
+  shutil.rmtree(os.path.join(vendor_target_files_dir, partition.upper()))
+  shutil.copytree(
+      os.path.join(target_files_dir, partition.upper()),
+      os.path.join(vendor_target_files_dir, partition.upper()),
+      symlinks=True)
+
+  # Delete then rebuild the partition.
+  os.remove(os.path.join(vendor_target_files_dir, 'IMAGES', partition_img))
+  rebuild_partition_command = [
+      os.path.join(vendor_otatools_dir, 'bin', 'add_img_to_target_files'),
+      '--verbose',
+      '--add_missing',
+  ]
+  if OPTIONS.rebuild_recovery:
+    rebuild_partition_command.append('--rebuild_recovery')
+  rebuild_partition_command.append(vendor_target_files_dir)
+  logger.info('Recompiling %s: %s', partition_img,
+              ' '.join(rebuild_partition_command))
+  common.RunAndCheckOutput(rebuild_partition_command, verbose=True)
+
+  # Move the newly-created image to the merged target files dir.
+  if not os.path.exists(os.path.join(target_files_dir, 'IMAGES')):
+    os.makedirs(os.path.join(target_files_dir, 'IMAGES'))
+  shutil.move(
+      os.path.join(vendor_target_files_dir, 'IMAGES', partition_img),
+      os.path.join(target_files_dir, 'IMAGES', partition_img))
+  shutil.move(
+      os.path.join(vendor_target_files_dir, 'IMAGES', partition_map),
+      os.path.join(target_files_dir, 'IMAGES', partition_map))
+
+  def copy_recovery_file(filename):
+    for subdir in ('VENDOR', 'SYSTEM/vendor'):
+      source = os.path.join(vendor_target_files_dir, subdir, filename)
+      if os.path.exists(source):
+        dest = os.path.join(target_files_dir, subdir, filename)
+        shutil.copy(source, dest)
+        return
+    logger.info('Skipping copy_recovery_file for %s, file not found', filename)
+
+  if OPTIONS.rebuild_recovery:
+    copy_recovery_file('etc/recovery.img')
+    copy_recovery_file('bin/install-recovery.sh')
+    copy_recovery_file('recovery-from-boot.p')
+
+
+def generate_super_empty_image(target_dir, output_super_empty):
+  """Generates super_empty image from target package.
+
+  Args:
+    target_dir: Path to the target file package which contains misc_info.txt for
+      detailed information for super image.
+    output_super_empty: If provided, copies a super_empty.img file from the
+      target files package to this path.
+  """
+  # Create super_empty.img using the merged misc_info.txt.
+
+  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
+
+  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
+      'use_dynamic_partitions')
+
+  if use_dynamic_partitions != 'true' and output_super_empty:
+    raise ValueError(
+        'Building super_empty.img requires use_dynamic_partitions=true.')
+  elif use_dynamic_partitions == 'true':
+    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
+    build_super_image_args = [
+        misc_info_txt,
+        super_empty_img,
+    ]
+    build_super_image.main(build_super_image_args)
+
+    # Copy super_empty.img to the user-provided output_super_empty location.
+    if output_super_empty:
+      shutil.copyfile(super_empty_img, output_super_empty)
+
+
+def create_target_files_archive(output_zip, source_dir, temp_dir):
+  """Creates a target_files zip archive from the input source dir.
+
+  Args:
+    output_zip: The name of the zip archive target files package.
+    source_dir: The target directory contains package to be archived.
+    temp_dir: Path to temporary directory for any intermediate files.
+  """
+  output_target_files_list = os.path.join(temp_dir, 'output.list')
+  output_target_files_meta_dir = os.path.join(source_dir, 'META')
+
+  def files_from_path(target_path, extra_args=None):
+    """Gets files under the given path and return a sorted list."""
+    find_command = ['find', target_path] + (extra_args or [])
+    find_process = common.Run(
+        find_command, stdout=subprocess.PIPE, verbose=False)
+    return common.RunAndCheckOutput(['sort'],
+                                    stdin=find_process.stdout,
+                                    verbose=False)
+
+  # META content appears first in the zip. This is done by the
+  # standard build system for optimized extraction of those files,
+  # so we do the same step for merged target_files.zips here too.
+  meta_content = files_from_path(output_target_files_meta_dir)
+  other_content = files_from_path(
+      source_dir,
+      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
+
+  with open(output_target_files_list, 'w') as f:
+    f.write(meta_content)
+    f.write(other_content)
+
+  command = [
+      'soong_zip',
+      '-d',
+      '-o',
+      os.path.abspath(output_zip),
+      '-C',
+      source_dir,
+      '-r',
+      output_target_files_list,
+  ]
+
+  logger.info('creating %s', output_zip)
+  common.RunAndCheckOutput(command, verbose=True)
+  logger.info('finished creating %s', output_zip)
+
+
+def merge_target_files(temp_dir):
+  """Merges two target files packages together.
+
+  This function uses framework and vendor target files packages as input,
+  performs various file extractions, special case processing, and finally
+  creates a merged zip archive as output.
+
+  Args:
+    temp_dir: The name of a directory we use when we extract items from the
+      input target files packages, and also a scratch directory that we use for
+      temporary files.
+  """
+
+  logger.info('starting: merge framework %s and vendor %s into output %s',
+              OPTIONS.framework_target_files, OPTIONS.vendor_target_files,
+              OPTIONS.output_target_files)
+
+  output_target_files_temp_dir = create_merged_package(temp_dir)
+
+  partition_map = common.PartitionMapFromTargetFiles(
+      output_target_files_temp_dir)
+
+  compatibility_errors = merge_compatibility_checks.CheckCompatibility(
+      target_files_dir=output_target_files_temp_dir,
+      partition_map=partition_map)
+  if compatibility_errors:
+    for error in compatibility_errors:
+      logger.error(error)
+    raise ExternalError(
+        'Found incompatibilities in the merged target files package.')
+
+  # Include the compiled policy in an image if requested.
+  if OPTIONS.rebuild_sepolicy:
+    rebuild_image_with_sepolicy(output_target_files_temp_dir)
+
+  generate_missing_images(output_target_files_temp_dir)
+
+  generate_super_empty_image(output_target_files_temp_dir,
+                             OPTIONS.output_super_empty)
+
+  # Finally, create the output target files zip archive and/or copy the
+  # output items to the output target files directory.
+
+  if OPTIONS.output_dir:
+    merge_utils.CopyItems(output_target_files_temp_dir, OPTIONS.output_dir,
+                          OPTIONS.output_item_list)
+
+  if not OPTIONS.output_target_files:
+    return
+
+  create_target_files_archive(OPTIONS.output_target_files,
+                              output_target_files_temp_dir, temp_dir)
+
+  # Create the IMG package from the merged target files package.
+  if OPTIONS.output_img:
+    img_from_target_files.main(
+        [OPTIONS.output_target_files, OPTIONS.output_img])
+
+  # Create the OTA package from the merged target files package.
+
+  if OPTIONS.output_ota:
+    ota_from_target_files.main(
+        [OPTIONS.output_target_files, OPTIONS.output_ota])
+
+
+def main():
+  """The main function.
+
+  Process command line arguments, then call merge_target_files to
+  perform the heavy lifting.
+  """
+
+  common.InitLogging()
+
+  def option_handler(o, a):
+    if o == '--system-target-files':
+      logger.warning(
+          '--system-target-files has been renamed to --framework-target-files')
+      OPTIONS.framework_target_files = a
+    elif o == '--framework-target-files':
+      OPTIONS.framework_target_files = a
+    elif o == '--system-item-list':
+      logger.warning(
+          '--system-item-list has been renamed to --framework-item-list')
+      OPTIONS.framework_item_list = a
+    elif o == '--framework-item-list':
+      OPTIONS.framework_item_list = a
+    elif o == '--system-misc-info-keys':
+      logger.warning('--system-misc-info-keys has been renamed to '
+                     '--framework-misc-info-keys')
+      OPTIONS.framework_misc_info_keys = a
+    elif o == '--framework-misc-info-keys':
+      OPTIONS.framework_misc_info_keys = a
+    elif o == '--other-target-files':
+      logger.warning(
+          '--other-target-files has been renamed to --vendor-target-files')
+      OPTIONS.vendor_target_files = a
+    elif o == '--vendor-target-files':
+      OPTIONS.vendor_target_files = a
+    elif o == '--other-item-list':
+      logger.warning('--other-item-list has been renamed to --vendor-item-list')
+      OPTIONS.vendor_item_list = a
+    elif o == '--vendor-item-list':
+      OPTIONS.vendor_item_list = a
+    elif o == '--output-target-files':
+      OPTIONS.output_target_files = a
+    elif o == '--output-dir':
+      OPTIONS.output_dir = a
+    elif o == '--output-item-list':
+      OPTIONS.output_item_list = a
+    elif o == '--output-ota':
+      OPTIONS.output_ota = a
+    elif o == '--output-img':
+      OPTIONS.output_img = a
+    elif o == '--output-super-empty':
+      OPTIONS.output_super_empty = a
+    elif o == '--rebuild_recovery' or o == '--rebuild-recovery':
+      OPTIONS.rebuild_recovery = True
+    elif o == '--allow-duplicate-apkapex-keys':
+      OPTIONS.allow_duplicate_apkapex_keys = True
+    elif o == '--vendor-otatools':
+      OPTIONS.vendor_otatools = a
+    elif o == '--rebuild-sepolicy':
+      OPTIONS.rebuild_sepolicy = True
+    elif o == '--keep-tmp':
+      OPTIONS.keep_tmp = True
+    elif o == '--framework-dexpreopt-config':
+      OPTIONS.framework_dexpreopt_config = a
+    elif o == '--framework-dexpreopt-tools':
+      OPTIONS.framework_dexpreopt_tools = a
+    elif o == '--vendor-dexpreopt-config':
+      OPTIONS.vendor_dexpreopt_config = a
+    else:
+      return False
+    return True
+
+  args = common.ParseOptions(
+      sys.argv[1:],
+      __doc__,
+      extra_long_opts=[
+          'system-target-files=',
+          'framework-target-files=',
+          'system-item-list=',
+          'framework-item-list=',
+          'system-misc-info-keys=',
+          'framework-misc-info-keys=',
+          'other-target-files=',
+          'vendor-target-files=',
+          'other-item-list=',
+          'vendor-item-list=',
+          'output-target-files=',
+          'output-dir=',
+          'output-item-list=',
+          'output-ota=',
+          'output-img=',
+          'output-super-empty=',
+          'framework-dexpreopt-config=',
+          'framework-dexpreopt-tools=',
+          'vendor-dexpreopt-config=',
+          'rebuild_recovery',
+          'rebuild-recovery',
+          'allow-duplicate-apkapex-keys',
+          'vendor-otatools=',
+          'rebuild-sepolicy',
+          'keep-tmp',
+      ],
+      extra_option_handler=option_handler)
+
+  # pylint: disable=too-many-boolean-expressions
+  if (args or OPTIONS.framework_target_files is None or
+      OPTIONS.vendor_target_files is None or
+      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
+      (OPTIONS.output_dir is not None and not OPTIONS.output_item_list) or
+      (OPTIONS.rebuild_recovery and not OPTIONS.rebuild_sepolicy)):
+    common.Usage(__doc__)
+    sys.exit(1)
+
+  with zipfile.ZipFile(OPTIONS.framework_target_files, allowZip64=True) as fz:
+    framework_namelist = fz.namelist()
+  with zipfile.ZipFile(OPTIONS.vendor_target_files, allowZip64=True) as vz:
+    vendor_namelist = vz.namelist()
+
+  if OPTIONS.framework_item_list:
+    OPTIONS.framework_item_list = common.LoadListFromFile(
+        OPTIONS.framework_item_list)
+  else:
+    OPTIONS.framework_item_list = merge_utils.InferItemList(
+        input_namelist=framework_namelist, framework=True)
+  OPTIONS.framework_partition_set = merge_utils.ItemListToPartitionSet(
+      OPTIONS.framework_item_list)
+
+  if OPTIONS.framework_misc_info_keys:
+    OPTIONS.framework_misc_info_keys = common.LoadListFromFile(
+        OPTIONS.framework_misc_info_keys)
+  else:
+    OPTIONS.framework_misc_info_keys = merge_utils.InferFrameworkMiscInfoKeys(
+        input_namelist=framework_namelist)
+
+  if OPTIONS.vendor_item_list:
+    OPTIONS.vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
+  else:
+    OPTIONS.vendor_item_list = merge_utils.InferItemList(
+        input_namelist=vendor_namelist, framework=False)
+  OPTIONS.vendor_partition_set = merge_utils.ItemListToPartitionSet(
+      OPTIONS.vendor_item_list)
+
+  if OPTIONS.output_item_list:
+    OPTIONS.output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
+
+  if not merge_utils.ValidateConfigLists():
+    sys.exit(1)
+
+  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
+  try:
+    merge_target_files(temp_dir)
+  finally:
+    if OPTIONS.keep_tmp:
+      logger.info('Keeping temp_dir %s', temp_dir)
+    else:
+      common.Cleanup()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/releasetools/merge/merge_utils.py b/tools/releasetools/merge/merge_utils.py
new file mode 100644
index 0000000..f623ad2
--- /dev/null
+++ b/tools/releasetools/merge/merge_utils.py
@@ -0,0 +1,237 @@
+#!/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.
+#
+"""Common utility functions shared by merge_* scripts.
+
+Expects items in OPTIONS prepared by merge_target_files.py.
+"""
+
+import fnmatch
+import logging
+import os
+import re
+import shutil
+import zipfile
+
+import common
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def ExtractItems(input_zip, output_dir, extract_item_list):
+  """Extracts items in extract_item_list from a zip to a dir."""
+
+  # Filter the extract_item_list to remove any items that do not exist in the
+  # zip file. Otherwise, the extraction step will fail.
+
+  with zipfile.ZipFile(input_zip, allowZip64=True) as input_zipfile:
+    input_namelist = input_zipfile.namelist()
+
+  filtered_extract_item_list = []
+  for pattern in extract_item_list:
+    if fnmatch.filter(input_namelist, pattern):
+      filtered_extract_item_list.append(pattern)
+
+  common.UnzipToDir(input_zip, output_dir, filtered_extract_item_list)
+
+
+def CopyItems(from_dir, to_dir, patterns):
+  """Similar to ExtractItems() except uses an input dir instead of zip."""
+  file_paths = []
+  for dirpath, _, filenames in os.walk(from_dir):
+    file_paths.extend(
+        os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
+        for filename in filenames)
+
+  filtered_file_paths = set()
+  for pattern in patterns:
+    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
+
+  for file_path in filtered_file_paths:
+    original_file_path = os.path.join(from_dir, file_path)
+    copied_file_path = os.path.join(to_dir, file_path)
+    copied_file_dir = os.path.dirname(copied_file_path)
+    if not os.path.exists(copied_file_dir):
+      os.makedirs(copied_file_dir)
+    if os.path.islink(original_file_path):
+      os.symlink(os.readlink(original_file_path), copied_file_path)
+    else:
+      shutil.copyfile(original_file_path, copied_file_path)
+
+
+def WriteSortedData(data, path):
+  """Writes the sorted contents of either a list or dict to file.
+
+  This function sorts the contents of the list or dict and then writes the
+  resulting sorted contents to a file specified by path.
+
+  Args:
+    data: The list or dict to sort and write.
+    path: Path to the file to write the sorted values to. The file at path will
+      be overridden if it exists.
+  """
+  with open(path, 'w') as output:
+    for entry in sorted(data):
+      out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
+          data, dict) else '{}\n'.format(entry)
+      output.write(out_str)
+
+
+def ValidateConfigLists():
+  """Performs validations on the merge config lists.
+
+  Returns:
+    False if a validation fails, otherwise true.
+  """
+  has_error = False
+
+  # Check that partitions only come from one input.
+  for partition in _FRAMEWORK_PARTITIONS.union(_VENDOR_PARTITIONS):
+    image_path = 'IMAGES/{}.img'.format(partition.lower().replace('/', ''))
+    in_framework = (
+        any(item.startswith(partition) for item in OPTIONS.framework_item_list)
+        or image_path in OPTIONS.framework_item_list)
+    in_vendor = (
+        any(item.startswith(partition) for item in OPTIONS.vendor_item_list) or
+        image_path in OPTIONS.vendor_item_list)
+    if in_framework and in_vendor:
+      logger.error(
+          'Cannot extract items from %s for both the framework and vendor'
+          ' builds. Please ensure only one merge config item list'
+          ' includes %s.', partition, partition)
+      has_error = True
+
+  if any([
+      key in OPTIONS.framework_misc_info_keys
+      for key in ('dynamic_partition_list', 'super_partition_groups')
+  ]):
+    logger.error('Dynamic partition misc info keys should come from '
+                 'the vendor instance of META/misc_info.txt.')
+    has_error = True
+
+  return not has_error
+
+
+# In an item list (framework or vendor), we may see entries that select whole
+# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
+# system partition). The following regex matches this and extracts the
+# partition name.
+
+_PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
+
+
+def ItemListToPartitionSet(item_list):
+  """Converts a target files item list to a partition set.
+
+  The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
+  'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
+  directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
+  contents of a partition of the same name. Other items in the list, such as the
+  'OTA' example contain metadata. This function iterates such a list, returning
+  a set that contains the partition entries.
+
+  Args:
+    item_list: A list of items in a target files package.
+
+  Returns:
+    A set of partitions extracted from the list of items.
+  """
+
+  partition_set = set()
+
+  for item in item_list:
+    partition_match = _PARTITION_ITEM_PATTERN.search(item.strip())
+    partition_tag = partition_match.group(
+        1).lower() if partition_match else None
+
+    if partition_tag:
+      partition_set.add(partition_tag)
+
+  return partition_set
+
+
+# Partitions that are grabbed from the framework partial build by default.
+_FRAMEWORK_PARTITIONS = {
+    'system', 'product', 'system_ext', 'system_other', 'root', 'system_dlkm'
+}
+# Partitions that are grabbed from the vendor partial build by default.
+_VENDOR_PARTITIONS = {
+    'vendor', 'odm', 'oem', 'boot', 'vendor_boot', 'recovery',
+    'prebuilt_images', 'radio', 'data', 'vendor_dlkm', 'odm_dlkm'
+}
+
+
+def InferItemList(input_namelist, framework):
+  item_list = []
+
+  # Some META items are grabbed from partial builds directly.
+  # Others are combined in merge_meta.py.
+  if framework:
+    item_list.extend([
+        'META/liblz4.so',
+        'META/postinstall_config.txt',
+        'META/update_engine_config.txt',
+        'META/zucchini_config.txt',
+    ])
+  else:  # vendor
+    item_list.extend([
+        'META/kernel_configs.txt',
+        'META/kernel_version.txt',
+        'META/otakeys.txt',
+        'META/releasetools.py',
+        'OTA/android-info.txt',
+    ])
+
+  # Grab a set of items for the expected partitions in the partial build.
+  for partition in (_FRAMEWORK_PARTITIONS if framework else _VENDOR_PARTITIONS):
+    for namelist in input_namelist:
+      if namelist.startswith('%s/' % partition.upper()):
+        fs_config_prefix = '' if partition == 'system' else '%s_' % partition
+        item_list.extend([
+            '%s/*' % partition.upper(),
+            'IMAGES/%s.img' % partition,
+            'IMAGES/%s.map' % partition,
+            'META/%sfilesystem_config.txt' % fs_config_prefix,
+        ])
+        break
+
+  return sorted(item_list)
+
+
+def InferFrameworkMiscInfoKeys(input_namelist):
+  keys = [
+      'ab_update',
+      'avb_vbmeta_system',
+      'avb_vbmeta_system_algorithm',
+      'avb_vbmeta_system_key_path',
+      'avb_vbmeta_system_rollback_index_location',
+      'default_system_dev_certificate',
+  ]
+
+  for partition in _FRAMEWORK_PARTITIONS:
+    for namelist in input_namelist:
+      if namelist.startswith('%s/' % partition.upper()):
+        fs_type_prefix = '' if partition == 'system' else '%s_' % partition
+        keys.extend([
+            'avb_%s_hashtree_enable' % partition,
+            'avb_%s_add_hashtree_footer_args' % partition,
+            '%s_disable_sparse' % partition,
+            'building_%s_image' % partition,
+            '%sfs_type' % fs_type_prefix,
+        ])
+
+  return sorted(keys)
diff --git a/tools/releasetools/merge/test_merge_compatibility_checks.py b/tools/releasetools/merge/test_merge_compatibility_checks.py
new file mode 100644
index 0000000..0f319de
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_compatibility_checks.py
@@ -0,0 +1,114 @@
+#
+# 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.
+#
+
+import os.path
+import shutil
+
+import common
+import merge_compatibility_checks
+import merge_target_files
+import test_utils
+
+
+class MergeCompatibilityChecksTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.testdata_dir = test_utils.get_testdata_dir()
+    self.partition_map = {
+        'system': 'system',
+        'system_ext': 'system_ext',
+        'product': 'product',
+        'vendor': 'vendor',
+        'odm': 'odm',
+    }
+    self.OPTIONS = merge_target_files.OPTIONS
+    self.OPTIONS.framework_partition_set = set(
+        ['product', 'system', 'system_ext'])
+    self.OPTIONS.vendor_partition_set = set(['odm', 'vendor'])
+
+  def test_CheckCombinedSepolicy(self):
+    product_out_dir = common.MakeTempDir()
+
+    def write_temp_file(path, data=''):
+      full_path = os.path.join(product_out_dir, path)
+      if not os.path.exists(os.path.dirname(full_path)):
+        os.makedirs(os.path.dirname(full_path))
+      with open(full_path, 'w') as f:
+        f.write(data)
+
+    write_temp_file(
+        'system/etc/vintf/compatibility_matrix.device.xml', """
+      <compatibility-matrix>
+        <sepolicy>
+          <kernel-sepolicy-version>30</kernel-sepolicy-version>
+        </sepolicy>
+      </compatibility-matrix>""")
+    write_temp_file('vendor/etc/selinux/plat_sepolicy_vers.txt', '30.0')
+
+    write_temp_file('system/etc/selinux/plat_sepolicy.cil')
+    write_temp_file('system/etc/selinux/mapping/30.0.cil')
+    write_temp_file('product/etc/selinux/mapping/30.0.cil')
+    write_temp_file('vendor/etc/selinux/vendor_sepolicy.cil')
+    write_temp_file('vendor/etc/selinux/plat_pub_versioned.cil')
+
+    cmd = merge_compatibility_checks.CheckCombinedSepolicy(
+        product_out_dir, self.partition_map, execute=False)
+    self.assertEqual(' '.join(cmd),
+                     ('secilc -m -M true -G -N -c 30 '
+                      '-o {OTP}/META/combined_sepolicy -f /dev/null '
+                      '{OTP}/system/etc/selinux/plat_sepolicy.cil '
+                      '{OTP}/system/etc/selinux/mapping/30.0.cil '
+                      '{OTP}/vendor/etc/selinux/vendor_sepolicy.cil '
+                      '{OTP}/vendor/etc/selinux/plat_pub_versioned.cil '
+                      '{OTP}/product/etc/selinux/mapping/30.0.cil').format(
+                          OTP=product_out_dir))
+
+  def _copy_apex(self, source, output_dir, partition):
+    shutil.copy(
+        source,
+        os.path.join(output_dir, partition, 'apex', os.path.basename(source)))
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_CheckApexDuplicatePackages(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    self._copy_apex(
+        os.path.join(self.testdata_dir, 'has_apk.apex'), output_dir, 'SYSTEM')
+    self._copy_apex(
+        os.path.join(test_utils.get_current_dir(),
+                     'com.android.apex.compressed.v1.capex'), output_dir,
+        'VENDOR')
+    self.assertEqual(
+        len(
+            merge_compatibility_checks.CheckApexDuplicatePackages(
+                output_dir, self.partition_map)), 0)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_CheckApexDuplicatePackages_RaisesOnPackageInMultiplePartitions(self):
+    output_dir = common.MakeTempDir()
+    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
+    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
+
+    same_apex_package = os.path.join(self.testdata_dir, 'has_apk.apex')
+    self._copy_apex(same_apex_package, output_dir, 'SYSTEM')
+    self._copy_apex(same_apex_package, output_dir, 'VENDOR')
+    self.assertEqual(
+        merge_compatibility_checks.CheckApexDuplicatePackages(
+            output_dir, self.partition_map)[0],
+        'Duplicate APEX package_names found in multiple partitions: com.android.wifi'
+    )
diff --git a/tools/releasetools/merge/test_merge_meta.py b/tools/releasetools/merge/test_merge_meta.py
new file mode 100644
index 0000000..34fe580
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_meta.py
@@ -0,0 +1,110 @@
+#
+# Copyright (C) 2017 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.
+#
+
+import os.path
+import shutil
+
+import common
+import merge_meta
+import merge_target_files
+import test_utils
+
+
+class MergeMetaTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.testdata_dir = test_utils.get_testdata_dir()
+    self.OPTIONS = merge_target_files.OPTIONS
+    self.OPTIONS.framework_partition_set = set(
+        ['product', 'system', 'system_ext'])
+    self.OPTIONS.vendor_partition_set = set(['odm', 'vendor'])
+
+  def test_MergePackageKeys_ReturnsTrueIfNoConflicts(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
+        os.path.join(framework_meta_dir, 'apexkeys.txt'))
+
+    vendor_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_vendor.txt'),
+        os.path.join(vendor_meta_dir, 'apexkeys.txt'))
+
+    merge_meta.MergePackageKeys(framework_meta_dir, vendor_meta_dir,
+                                output_meta_dir, 'apexkeys.txt')
+
+    merged_entries = []
+    merged_path = os.path.join(self.testdata_dir, 'apexkeys_merge.txt')
+
+    with open(merged_path) as f:
+      merged_entries = f.read().split('\n')
+
+    output_entries = []
+    output_path = os.path.join(output_meta_dir, 'apexkeys.txt')
+
+    with open(output_path) as f:
+      output_entries = f.read().split('\n')
+
+    return self.assertEqual(merged_entries, output_entries)
+
+  def test_MergePackageKeys_ReturnsFalseIfConflictsPresent(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
+        os.path.join(framework_meta_dir, 'apexkeys.txt'))
+
+    conflict_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apexkeys_framework_conflict.txt'),
+        os.path.join(conflict_meta_dir, 'apexkeys.txt'))
+
+    self.assertRaises(ValueError, merge_meta.MergePackageKeys,
+                      framework_meta_dir, conflict_meta_dir, output_meta_dir,
+                      'apexkeys.txt')
+
+  def test_MergePackageKeys_HandlesApkCertsSyntax(self):
+    output_meta_dir = common.MakeTempDir()
+
+    framework_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apkcerts_framework.txt'),
+        os.path.join(framework_meta_dir, 'apkcerts.txt'))
+
+    vendor_meta_dir = common.MakeTempDir()
+    os.symlink(
+        os.path.join(self.testdata_dir, 'apkcerts_vendor.txt'),
+        os.path.join(vendor_meta_dir, 'apkcerts.txt'))
+
+    merge_meta.MergePackageKeys(framework_meta_dir, vendor_meta_dir,
+                                output_meta_dir, 'apkcerts.txt')
+
+    merged_entries = []
+    merged_path = os.path.join(self.testdata_dir, 'apkcerts_merge.txt')
+
+    with open(merged_path) as f:
+      merged_entries = f.read().split('\n')
+
+    output_entries = []
+    output_path = os.path.join(output_meta_dir, 'apkcerts.txt')
+
+    with open(output_path) as f:
+      output_entries = f.read().split('\n')
+
+    return self.assertEqual(merged_entries, output_entries)
diff --git a/tools/releasetools/merge/test_merge_utils.py b/tools/releasetools/merge/test_merge_utils.py
new file mode 100644
index 0000000..1949050
--- /dev/null
+++ b/tools/releasetools/merge/test_merge_utils.py
@@ -0,0 +1,197 @@
+#
+# Copyright (C) 2017 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.
+#
+
+import os.path
+
+import common
+import merge_target_files
+import merge_utils
+import test_utils
+
+
+class MergeUtilsTest(test_utils.ReleaseToolsTestCase):
+
+  def setUp(self):
+    self.OPTIONS = merge_target_files.OPTIONS
+
+  def test_CopyItems_CopiesItemsMatchingPatterns(self):
+
+    def createEmptyFile(path):
+      if not os.path.exists(os.path.dirname(path)):
+        os.makedirs(os.path.dirname(path))
+      open(path, 'a').close()
+      return path
+
+    def createSymLink(source, dest):
+      os.symlink(source, dest)
+      return dest
+
+    def getRelPaths(start, filepaths):
+      return set(
+          os.path.relpath(path=filepath, start=start) for filepath in filepaths)
+
+    input_dir = common.MakeTempDir()
+    output_dir = common.MakeTempDir()
+    expected_copied_items = []
+    actual_copied_items = []
+    patterns = ['*.cpp', 'subdir/*.txt']
+
+    # Create various files that we expect to get copied because they
+    # match one of the patterns.
+    expected_copied_items.extend([
+        createEmptyFile(os.path.join(input_dir, 'a.cpp')),
+        createEmptyFile(os.path.join(input_dir, 'b.cpp')),
+        createEmptyFile(os.path.join(input_dir, 'subdir', 'c.txt')),
+        createEmptyFile(os.path.join(input_dir, 'subdir', 'd.txt')),
+        createEmptyFile(
+            os.path.join(input_dir, 'subdir', 'subsubdir', 'e.txt')),
+        createSymLink('a.cpp', os.path.join(input_dir, 'a_link.cpp')),
+    ])
+    # Create some more files that we expect to not get copied.
+    createEmptyFile(os.path.join(input_dir, 'a.h'))
+    createEmptyFile(os.path.join(input_dir, 'b.h'))
+    createEmptyFile(os.path.join(input_dir, 'subdir', 'subsubdir', 'f.gif'))
+    createSymLink('a.h', os.path.join(input_dir, 'a_link.h'))
+
+    # Copy items.
+    merge_utils.CopyItems(input_dir, output_dir, patterns)
+
+    # Assert the actual copied items match the ones we expected.
+    for dirpath, _, filenames in os.walk(output_dir):
+      actual_copied_items.extend(
+          os.path.join(dirpath, filename) for filename in filenames)
+    self.assertEqual(
+        getRelPaths(output_dir, actual_copied_items),
+        getRelPaths(input_dir, expected_copied_items))
+    self.assertEqual(
+        os.readlink(os.path.join(output_dir, 'a_link.cpp')), 'a.cpp')
+
+  def test_ValidateConfigLists_ReturnsFalseIfSharedExtractedPartition(self):
+    self.OPTIONS.system_item_list = [
+        'SYSTEM/*',
+    ]
+    self.OPTIONS.vendor_item_list = [
+        'SYSTEM/my_system_file',
+        'VENDOR/*',
+    ]
+    self.OPTIONS.vendor_item_list.append('SYSTEM/my_system_file')
+    self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ValidateConfigLists_ReturnsFalseIfSharedExtractedPartitionImage(
+      self):
+    self.OPTIONS.system_item_list = [
+        'SYSTEM/*',
+    ]
+    self.OPTIONS.vendor_item_list = [
+        'IMAGES/system.img',
+        'VENDOR/*',
+    ]
+    self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ValidateConfigLists_ReturnsFalseIfBadSystemMiscInfoKeys(self):
+    for bad_key in ['dynamic_partition_list', 'super_partition_groups']:
+      self.OPTIONS.framework_misc_info_keys = [bad_key]
+      self.assertFalse(merge_utils.ValidateConfigLists())
+
+  def test_ItemListToPartitionSet(self):
+    item_list = [
+        'META/apexkeys.txt',
+        'META/apkcerts.txt',
+        'META/filesystem_config.txt',
+        'PRODUCT/*',
+        'SYSTEM/*',
+        'SYSTEM_EXT/*',
+    ]
+    partition_set = merge_utils.ItemListToPartitionSet(item_list)
+    self.assertEqual(set(['product', 'system', 'system_ext']), partition_set)
+
+  def test_InferItemList_Framework(self):
+    zip_namelist = [
+        'SYSTEM/my_system_file',
+        'PRODUCT/my_product_file',
+    ]
+
+    item_list = merge_utils.InferItemList(zip_namelist, framework=True)
+
+    expected_framework_item_list = [
+        'IMAGES/product.img',
+        'IMAGES/product.map',
+        'IMAGES/system.img',
+        'IMAGES/system.map',
+        'META/filesystem_config.txt',
+        'META/liblz4.so',
+        'META/postinstall_config.txt',
+        'META/product_filesystem_config.txt',
+        'META/update_engine_config.txt',
+        'META/zucchini_config.txt',
+        'PRODUCT/*',
+        'SYSTEM/*',
+    ]
+
+    self.assertEqual(item_list, expected_framework_item_list)
+
+  def test_InferItemList_Vendor(self):
+    zip_namelist = [
+        'VENDOR/my_vendor_file',
+        'ODM/my_odm_file',
+    ]
+
+    item_list = merge_utils.InferItemList(zip_namelist, framework=False)
+
+    expected_vendor_item_list = [
+        'IMAGES/odm.img',
+        'IMAGES/odm.map',
+        'IMAGES/vendor.img',
+        'IMAGES/vendor.map',
+        'META/kernel_configs.txt',
+        'META/kernel_version.txt',
+        'META/odm_filesystem_config.txt',
+        'META/otakeys.txt',
+        'META/releasetools.py',
+        'META/vendor_filesystem_config.txt',
+        'ODM/*',
+        'OTA/android-info.txt',
+        'VENDOR/*',
+    ]
+    self.assertEqual(item_list, expected_vendor_item_list)
+
+  def test_InferFrameworkMiscInfoKeys(self):
+    zip_namelist = [
+        'SYSTEM/my_system_file',
+        'SYSTEM_EXT/my_system_ext_file',
+    ]
+
+    keys = merge_utils.InferFrameworkMiscInfoKeys(zip_namelist)
+
+    expected_keys = [
+        'ab_update',
+        'avb_system_add_hashtree_footer_args',
+        'avb_system_ext_add_hashtree_footer_args',
+        'avb_system_ext_hashtree_enable',
+        'avb_system_hashtree_enable',
+        'avb_vbmeta_system',
+        'avb_vbmeta_system_algorithm',
+        'avb_vbmeta_system_key_path',
+        'avb_vbmeta_system_rollback_index_location',
+        'building_system_ext_image',
+        'building_system_image',
+        'default_system_dev_certificate',
+        'fs_type',
+        'system_disable_sparse',
+        'system_ext_disable_sparse',
+        'system_ext_fs_type',
+    ]
+    self.assertEqual(keys, expected_keys)
diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py
deleted file mode 100755
index 5e6c42d..0000000
--- a/tools/releasetools/merge_target_files.py
+++ /dev/null
@@ -1,1349 +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.
-#
-"""This script merges two partial target files packages.
-
-One input package contains framework files, and the other contains vendor files.
-
-This script produces a complete, merged target files package:
-  - This package can be used to generate a flashable IMG package.
-    See --output-img.
-  - This package can be used to generate an OTA package. See --output-ota.
-  - The merged package is checked for compatibility between the two inputs.
-
-Usage: merge_target_files [args]
-
-  --framework-target-files framework-target-files-zip-archive
-      The input target files package containing framework bits. This is a zip
-      archive.
-
-  --framework-item-list framework-item-list-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_FRAMEWORK_ITEM_LIST if provided.
-
-  --framework-misc-info-keys framework-misc-info-keys-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_FRAMEWORK_MISC_INFO_KEYS if provided.
-
-  --vendor-target-files vendor-target-files-zip-archive
-      The input target files package containing vendor bits. This is a zip
-      archive.
-
-  --vendor-item-list vendor-item-list-file
-      The optional path to a newline-separated config file that replaces the
-      contents of DEFAULT_VENDOR_ITEM_LIST if provided.
-
-  --output-target-files output-target-files-package
-      If provided, the output merged target files package. Also a zip archive.
-
-  --output-dir output-directory
-      If provided, the destination directory for saving merged files. Requires
-      the --output-item-list flag.
-      Can be provided alongside --output-target-files, or by itself.
-
-  --output-item-list output-item-list-file.
-      The optional path to a newline-separated config file that specifies the
-      file patterns to copy into the --output-dir. Required if providing
-      the --output-dir flag.
-
-  --output-ota output-ota-package
-      The output ota package. This is a zip archive. Use of this flag may
-      require passing the --path common flag; see common.py.
-
-  --output-img output-img-package
-      The output img package, suitable for use with 'fastboot update'. Use of
-      this flag may require passing the --path common flag; see common.py.
-
-  --output-super-empty output-super-empty-image
-      If provided, creates a super_empty.img file from the merged target
-      files package and saves it at this path.
-
-  --rebuild_recovery
-      Deprecated; does nothing.
-
-  --allow-duplicate-apkapex-keys
-      If provided, duplicate APK/APEX keys are ignored and the value from the
-      framework is used.
-
-  --keep-tmp
-      Keep tempoary files for debugging purposes.
-"""
-
-from __future__ import print_function
-
-import fnmatch
-import json
-import logging
-import os
-import re
-import shutil
-import subprocess
-import sys
-import zipfile
-from xml.etree import ElementTree
-
-import add_img_to_target_files
-import apex_utils
-import build_image
-import build_super_image
-import check_target_files_vintf
-import common
-import img_from_target_files
-import find_shareduid_violation
-import ota_from_target_files
-import sparse_img
-import verity_utils
-
-from common import AddCareMapForAbOta, ExternalError, PARTITIONS_WITH_CARE_MAP
-
-logger = logging.getLogger(__name__)
-
-OPTIONS = common.OPTIONS
-# Always turn on verbose logging.
-OPTIONS.verbose = True
-OPTIONS.framework_target_files = None
-OPTIONS.framework_item_list = None
-OPTIONS.framework_misc_info_keys = None
-OPTIONS.vendor_target_files = None
-OPTIONS.vendor_item_list = None
-OPTIONS.output_target_files = None
-OPTIONS.output_dir = None
-OPTIONS.output_item_list = None
-OPTIONS.output_ota = None
-OPTIONS.output_img = None
-OPTIONS.output_super_empty = None
-# TODO(b/132730255): Remove this option.
-OPTIONS.rebuild_recovery = False
-# TODO(b/150582573): Remove this option.
-OPTIONS.allow_duplicate_apkapex_keys = False
-OPTIONS.keep_tmp = False
-
-# In an item list (framework or vendor), we may see entries that select whole
-# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
-# system partition). The following regex matches this and extracts the
-# partition name.
-
-PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
-
-# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
-# the file. We use these partition tags to filter the entries in those files
-# from the two different target files packages to produce a merged apexkeys.txt
-# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
-# like this: 'partition="product"'. We use the group syntax grab the value of
-# the tag. We use non-greedy matching in case there are other fields on the
-# same line.
-
-PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
-
-# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
-# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
-
-MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
-
-# DEFAULT_FRAMEWORK_ITEM_LIST is a list of items to extract from the partial
-# framework target files package as is, meaning these items will land in the
-# output target files package exactly as they appear in the input partial
-# framework target files package.
-
-DEFAULT_FRAMEWORK_ITEM_LIST = (
-    'META/apkcerts.txt',
-    'META/filesystem_config.txt',
-    'META/root_filesystem_config.txt',
-    'META/update_engine_config.txt',
-    'PRODUCT/*',
-    'ROOT/*',
-    'SYSTEM/*',
-)
-
-# DEFAULT_FRAMEWORK_MISC_INFO_KEYS is a list of keys to obtain from the
-# framework instance of META/misc_info.txt. The remaining keys should come
-# from the vendor instance.
-
-DEFAULT_FRAMEWORK_MISC_INFO_KEYS = (
-    'avb_system_hashtree_enable',
-    'avb_system_add_hashtree_footer_args',
-    'avb_system_key_path',
-    'avb_system_algorithm',
-    'avb_system_rollback_index_location',
-    'avb_product_hashtree_enable',
-    'avb_product_add_hashtree_footer_args',
-    'avb_system_ext_hashtree_enable',
-    'avb_system_ext_add_hashtree_footer_args',
-    'system_root_image',
-    'root_dir',
-    'ab_update',
-    'default_system_dev_certificate',
-    'system_size',
-    'building_system_image',
-    'building_system_ext_image',
-    'building_product_image',
-)
-
-# DEFAULT_VENDOR_ITEM_LIST is a list of items to extract from the partial
-# vendor target files package as is, meaning these items will land in the output
-# target files package exactly as they appear in the input partial vendor target
-# files package.
-
-DEFAULT_VENDOR_ITEM_LIST = (
-    'META/boot_filesystem_config.txt',
-    'META/otakeys.txt',
-    'META/releasetools.py',
-    'META/vendor_filesystem_config.txt',
-    'BOOT/*',
-    'DATA/*',
-    'ODM/*',
-    'OTA/android-info.txt',
-    'PREBUILT_IMAGES/*',
-    'RADIO/*',
-    'VENDOR/*',
-)
-
-# The merge config lists should not attempt to extract items from both
-# builds for any of the following partitions. The partitions in
-# SINGLE_BUILD_PARTITIONS should come entirely from a single build (either
-# framework or vendor, but not both).
-
-SINGLE_BUILD_PARTITIONS = (
-    'BOOT/',
-    'DATA/',
-    'ODM/',
-    'PRODUCT/',
-    'SYSTEM_EXT/',
-    'RADIO/',
-    'RECOVERY/',
-    'ROOT/',
-    'SYSTEM/',
-    'SYSTEM_OTHER/',
-    'VENDOR/',
-    'VENDOR_DLKM/',
-    'ODM_DLKM/',
-)
-
-
-def write_sorted_data(data, path):
-  """Writes the sorted contents of either a list or dict to file.
-
-  This function sorts the contents of the list or dict and then writes the
-  resulting sorted contents to a file specified by path.
-
-  Args:
-    data: The list or dict to sort and write.
-    path: Path to the file to write the sorted values to. The file at path will
-      be overridden if it exists.
-  """
-  with open(path, 'w') as output:
-    for entry in sorted(data):
-      out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
-          data, dict) else '{}\n'.format(entry)
-      output.write(out_str)
-
-
-def extract_items(target_files, target_files_temp_dir, extract_item_list):
-  """Extracts items from target files to temporary directory.
-
-  This function extracts from the specified target files zip archive into the
-  specified temporary directory, the items specified in the extract item list.
-
-  Args:
-    target_files: The target files zip archive from which to extract items.
-    target_files_temp_dir: The temporary directory where the extracted items
-      will land.
-    extract_item_list: A list of items to extract.
-  """
-
-  logger.info('extracting from %s', target_files)
-
-  # Filter the extract_item_list to remove any items that do not exist in the
-  # zip file. Otherwise, the extraction step will fail.
-
-  with zipfile.ZipFile(target_files, allowZip64=True) as target_files_zipfile:
-    target_files_namelist = target_files_zipfile.namelist()
-
-  filtered_extract_item_list = []
-  for pattern in extract_item_list:
-    matching_namelist = fnmatch.filter(target_files_namelist, pattern)
-    if not matching_namelist:
-      logger.warning('no match for %s', pattern)
-    else:
-      filtered_extract_item_list.append(pattern)
-
-  # Extract from target_files into target_files_temp_dir the
-  # filtered_extract_item_list.
-
-  common.UnzipToDir(target_files, target_files_temp_dir,
-                    filtered_extract_item_list)
-
-
-def copy_items(from_dir, to_dir, patterns):
-  """Similar to extract_items() except uses an input dir instead of zip."""
-  file_paths = []
-  for dirpath, _, filenames in os.walk(from_dir):
-    file_paths.extend(
-        os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
-        for filename in filenames)
-
-  filtered_file_paths = set()
-  for pattern in patterns:
-    filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
-
-  for file_path in filtered_file_paths:
-    original_file_path = os.path.join(from_dir, file_path)
-    copied_file_path = os.path.join(to_dir, file_path)
-    copied_file_dir = os.path.dirname(copied_file_path)
-    if not os.path.exists(copied_file_dir):
-      os.makedirs(copied_file_dir)
-    if os.path.islink(original_file_path):
-      os.symlink(os.readlink(original_file_path), copied_file_path)
-    else:
-      shutil.copyfile(original_file_path, copied_file_path)
-
-
-def validate_config_lists(framework_item_list, framework_misc_info_keys,
-                          vendor_item_list):
-  """Performs validations on the merge config lists.
-
-  Args:
-    framework_item_list: The list of items to extract from the partial framework
-      target files package as is.
-    framework_misc_info_keys: A list of keys to obtain from the framework
-      instance of META/misc_info.txt. The remaining keys should come from the
-      vendor instance.
-    vendor_item_list: The list of items to extract from the partial vendor
-      target files package as is.
-
-  Returns:
-    False if a validation fails, otherwise true.
-  """
-  has_error = False
-
-  default_combined_item_set = set(DEFAULT_FRAMEWORK_ITEM_LIST)
-  default_combined_item_set.update(DEFAULT_VENDOR_ITEM_LIST)
-
-  combined_item_set = set(framework_item_list)
-  combined_item_set.update(vendor_item_list)
-
-  # Check that the merge config lists are not missing any item specified
-  # by the default config lists.
-  difference = default_combined_item_set.difference(combined_item_set)
-  if difference:
-    logger.error('Missing merge config items: %s', list(difference))
-    logger.error('Please ensure missing items are in either the '
-                 'framework-item-list or vendor-item-list files provided to '
-                 'this script.')
-    has_error = True
-
-  # Check that partitions only come from one input.
-  for partition in SINGLE_BUILD_PARTITIONS:
-    image_path = 'IMAGES/{}.img'.format(partition.lower().replace('/', ''))
-    in_framework = (
-        any(item.startswith(partition) for item in framework_item_list) or
-        image_path in framework_item_list)
-    in_vendor = (
-        any(item.startswith(partition) for item in vendor_item_list) or
-        image_path in vendor_item_list)
-    if in_framework and in_vendor:
-      logger.error(
-          'Cannot extract items from %s for both the framework and vendor'
-          ' builds. Please ensure only one merge config item list'
-          ' includes %s.', partition, partition)
-      has_error = True
-
-  if ('dynamic_partition_list'
-      in framework_misc_info_keys) or ('super_partition_groups'
-                                       in framework_misc_info_keys):
-    logger.error('Dynamic partition misc info keys should come from '
-                 'the vendor instance of META/misc_info.txt.')
-    has_error = True
-
-  return not has_error
-
-
-def process_ab_partitions_txt(framework_target_files_temp_dir,
-                              vendor_target_files_temp_dir,
-                              output_target_files_temp_dir):
-  """Performs special processing for META/ab_partitions.txt.
-
-  This function merges the contents of the META/ab_partitions.txt files from the
-  framework directory and the vendor directory, placing the merged result in the
-  output directory. The precondition in that the files are already extracted.
-  The post condition is that the output META/ab_partitions.txt contains the
-  merged content. The format for each ab_partitions.txt is one partition name
-  per line. The output file contains the union of the partition names.
-
-  Args:
-    framework_target_files_temp_dir: The name of a directory containing the
-      special items extracted from the framework target files package.
-    vendor_target_files_temp_dir: The name of a directory containing the special
-      items extracted from the vendor target files package.
-    output_target_files_temp_dir: The name of a directory that will be used to
-      create the output target files package after all the special cases are
-      processed.
-  """
-
-  framework_ab_partitions_txt = os.path.join(framework_target_files_temp_dir,
-                                             'META', 'ab_partitions.txt')
-
-  vendor_ab_partitions_txt = os.path.join(vendor_target_files_temp_dir, 'META',
-                                          'ab_partitions.txt')
-
-  with open(framework_ab_partitions_txt) as f:
-    framework_ab_partitions = f.read().splitlines()
-
-  with open(vendor_ab_partitions_txt) as f:
-    vendor_ab_partitions = f.read().splitlines()
-
-  output_ab_partitions = set(framework_ab_partitions + vendor_ab_partitions)
-
-  output_ab_partitions_txt = os.path.join(output_target_files_temp_dir, 'META',
-                                          'ab_partitions.txt')
-
-  write_sorted_data(data=output_ab_partitions, path=output_ab_partitions_txt)
-
-
-def process_misc_info_txt(framework_target_files_temp_dir,
-                          vendor_target_files_temp_dir,
-                          output_target_files_temp_dir,
-                          framework_misc_info_keys):
-  """Performs special processing for META/misc_info.txt.
-
-  This function merges the contents of the META/misc_info.txt files from the
-  framework directory and the vendor directory, placing the merged result in the
-  output directory. The precondition in that the files are already extracted.
-  The post condition is that the output META/misc_info.txt contains the merged
-  content.
-
-  Args:
-    framework_target_files_temp_dir: The name of a directory containing the
-      special items extracted from the framework target files package.
-    vendor_target_files_temp_dir: The name of a directory containing the special
-      items extracted from the vendor target files package.
-    output_target_files_temp_dir: The name of a directory that will be used to
-      create the output target files package after all the special cases are
-      processed.
-    framework_misc_info_keys: A list of keys to obtain from the framework
-      instance of META/misc_info.txt. The remaining keys should come from the
-      vendor instance.
-  """
-
-  misc_info_path = ['META', 'misc_info.txt']
-  framework_dict = common.LoadDictionaryFromFile(
-      os.path.join(framework_target_files_temp_dir, *misc_info_path))
-
-  # We take most of the misc info from the vendor target files.
-
-  merged_dict = common.LoadDictionaryFromFile(
-      os.path.join(vendor_target_files_temp_dir, *misc_info_path))
-
-  # Replace certain values in merged_dict with values from
-  # framework_dict.
-
-  for key in framework_misc_info_keys:
-    merged_dict[key] = framework_dict[key]
-
-  # Merge misc info keys used for Dynamic Partitions.
-  if (merged_dict.get('use_dynamic_partitions')
-      == 'true') and (framework_dict.get('use_dynamic_partitions') == 'true'):
-    merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
-        framework_dict=framework_dict, vendor_dict=merged_dict)
-    merged_dict.update(merged_dynamic_partitions_dict)
-    # Ensure that add_img_to_target_files rebuilds super split images for
-    # devices that retrofit dynamic partitions. This flag may have been set to
-    # false in the partial builds to prevent duplicate building of super.img.
-    merged_dict['build_super_partition'] = 'true'
-
-  # If AVB is enabled then ensure that we build vbmeta.img.
-  # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
-  # skip building an incomplete vbmeta.img.
-  if merged_dict.get('avb_enable') == 'true':
-    merged_dict['avb_building_vbmeta_image'] = 'true'
-
-  # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
-  # depending on which dictionary the key came from.
-  # Only the file basename is required because all selinux_fc properties are
-  # replaced with the full path to the file under META/ when misc_info.txt is
-  # loaded from target files for repacking. See common.py LoadInfoDict().
-  for key in merged_dict:
-    if key.endswith('_selinux_fc'):
-      merged_dict[key] = 'vendor_file_contexts.bin'
-  for key in framework_dict:
-    if key.endswith('_selinux_fc'):
-      merged_dict[key] = 'framework_file_contexts.bin'
-
-  output_misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
-                                      'misc_info.txt')
-  write_sorted_data(data=merged_dict, path=output_misc_info_txt)
-
-
-def process_dynamic_partitions_info_txt(framework_target_files_dir,
-                                        vendor_target_files_dir,
-                                        output_target_files_dir):
-  """Performs special processing for META/dynamic_partitions_info.txt.
-
-  This function merges the contents of the META/dynamic_partitions_info.txt
-  files from the framework directory and the vendor directory, placing the
-  merged result in the output directory.
-
-  This function does nothing if META/dynamic_partitions_info.txt from the vendor
-  directory does not exist.
-
-  Args:
-    framework_target_files_dir: The name of a directory containing the special
-      items extracted from the framework target files package.
-    vendor_target_files_dir: The name of a directory containing the special
-      items extracted from the vendor target files package.
-    output_target_files_dir: The name of a directory that will be used to create
-      the output target files package after all the special cases are processed.
-  """
-
-  if not os.path.exists(
-      os.path.join(vendor_target_files_dir, 'META',
-                   'dynamic_partitions_info.txt')):
-    return
-
-  dynamic_partitions_info_path = ['META', 'dynamic_partitions_info.txt']
-
-  framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
-      os.path.join(framework_target_files_dir, *dynamic_partitions_info_path))
-  vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
-      os.path.join(vendor_target_files_dir, *dynamic_partitions_info_path))
-
-  merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
-      framework_dict=framework_dynamic_partitions_dict,
-      vendor_dict=vendor_dynamic_partitions_dict)
-
-  output_dynamic_partitions_info_txt = os.path.join(
-      output_target_files_dir, 'META', 'dynamic_partitions_info.txt')
-  write_sorted_data(
-      data=merged_dynamic_partitions_dict,
-      path=output_dynamic_partitions_info_txt)
-
-
-def item_list_to_partition_set(item_list):
-  """Converts a target files item list to a partition set.
-
-  The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
-  'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
-  directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
-  contents of a partition of the same name. Other items in the list, such as the
-  'OTA' example contain metadata. This function iterates such a list, returning
-  a set that contains the partition entries.
-
-  Args:
-    item_list: A list of items in a target files package.
-
-  Returns:
-    A set of partitions extracted from the list of items.
-  """
-
-  partition_set = set()
-
-  for item in item_list:
-    match = PARTITION_ITEM_PATTERN.search(item.strip())
-    partition_tag = match.group(1).lower() if match else None
-
-    if partition_tag:
-      partition_set.add(partition_tag)
-
-  return partition_set
-
-
-def process_apex_keys_apk_certs_common(framework_target_files_dir,
-                                       vendor_target_files_dir,
-                                       output_target_files_dir,
-                                       framework_partition_set,
-                                       vendor_partition_set, file_name):
-  """Performs special processing for META/apexkeys.txt or META/apkcerts.txt.
-
-  This function merges the contents of the META/apexkeys.txt or
-  META/apkcerts.txt files from the framework directory and the vendor directory,
-  placing the merged result in the output directory. The precondition in that
-  the files are already extracted. The post condition is that the output
-  META/apexkeys.txt or META/apkcerts.txt contains the merged content.
-
-  Args:
-    framework_target_files_dir: The name of a directory containing the special
-      items extracted from the framework target files package.
-    vendor_target_files_dir: The name of a directory containing the special
-      items extracted from the vendor target files package.
-    output_target_files_dir: The name of a directory that will be used to create
-      the output target files package after all the special cases are processed.
-    framework_partition_set: Partitions that are considered framework
-      partitions. Used to filter apexkeys.txt and apkcerts.txt.
-    vendor_partition_set: Partitions that are considered vendor partitions. Used
-      to filter apexkeys.txt and apkcerts.txt.
-    file_name: The name of the file to merge. One of apkcerts.txt or
-      apexkeys.txt.
-  """
-
-  def read_helper(d):
-    temp = {}
-    file_path = os.path.join(d, 'META', file_name)
-    with open(file_path) as f:
-      for line in f:
-        if line.strip():
-          name = line.split()[0]
-          match = MODULE_KEY_PATTERN.search(name)
-          temp[match.group(1)] = line.strip()
-    return temp
-
-  framework_dict = read_helper(framework_target_files_dir)
-  vendor_dict = read_helper(vendor_target_files_dir)
-  merged_dict = {}
-
-  def filter_into_merged_dict(item_dict, partition_set):
-    for key, value in item_dict.items():
-      match = PARTITION_TAG_PATTERN.search(value)
-
-      if match is None:
-        raise ValueError('Entry missing partition tag: %s' % value)
-
-      partition_tag = match.group(1)
-
-      if partition_tag in partition_set:
-        if key in merged_dict:
-          if OPTIONS.allow_duplicate_apkapex_keys:
-            # TODO(b/150582573) Always raise on duplicates.
-            logger.warning('Duplicate key %s' % key)
-            continue
-          else:
-            raise ValueError('Duplicate key %s' % key)
-
-        merged_dict[key] = value
-
-  filter_into_merged_dict(framework_dict, framework_partition_set)
-  filter_into_merged_dict(vendor_dict, vendor_partition_set)
-
-  output_file = os.path.join(output_target_files_dir, 'META', file_name)
-
-  # The following code is similar to write_sorted_data, but different enough
-  # that we couldn't use that function. We need the output to be sorted by the
-  # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
-  # allows the sort to be consistent with the framework/vendor input data and
-  # eases comparison of input data with merged data.
-  with open(output_file, 'w') as output:
-    for key in sorted(merged_dict.keys()):
-      out_str = merged_dict[key] + '\n'
-      output.write(out_str)
-
-
-def copy_file_contexts(framework_target_files_dir, vendor_target_files_dir,
-                       output_target_files_dir):
-  """Creates named copies of each build's file_contexts.bin in output META/."""
-  framework_fc_path = os.path.join(framework_target_files_dir, 'META',
-                                   'framework_file_contexts.bin')
-  if not os.path.exists(framework_fc_path):
-    framework_fc_path = os.path.join(framework_target_files_dir, 'META',
-                                     'file_contexts.bin')
-    if not os.path.exists(framework_fc_path):
-      raise ValueError('Missing framework file_contexts.bin.')
-  shutil.copyfile(
-      framework_fc_path,
-      os.path.join(output_target_files_dir, 'META',
-                   'framework_file_contexts.bin'))
-
-  vendor_fc_path = os.path.join(vendor_target_files_dir, 'META',
-                                'vendor_file_contexts.bin')
-  if not os.path.exists(vendor_fc_path):
-    vendor_fc_path = os.path.join(vendor_target_files_dir, 'META',
-                                  'file_contexts.bin')
-    if not os.path.exists(vendor_fc_path):
-      raise ValueError('Missing vendor file_contexts.bin.')
-  shutil.copyfile(
-      vendor_fc_path,
-      os.path.join(output_target_files_dir, 'META', 'vendor_file_contexts.bin'))
-
-
-def compile_split_sepolicy(product_out, partition_map, output_policy):
-  """Uses secilc to compile a split sepolicy file.
-
-  Depends on various */etc/selinux/* and */etc/vintf/* files within partitions.
-
-  Args:
-    product_out: PRODUCT_OUT directory, containing partition directories.
-    partition_map: A map of partition name -> relative path within product_out.
-    output_policy: The name of the output policy created by secilc.
-
-  Returns:
-    A command list that can be executed to create the compiled sepolicy.
-  """
-
-  def get_file(partition, path):
-    if partition not in partition_map:
-      logger.warning('Cannot load SEPolicy files for missing partition %s',
-                     partition)
-      return None
-    return os.path.join(product_out, partition_map[partition], path)
-
-  # Load the kernel sepolicy version from the FCM. This is normally provided
-  # directly to selinux.cpp as a build flag, but is also available in this file.
-  fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml')
-  if not fcm_file or not os.path.exists(fcm_file):
-    raise ExternalError('Missing required file for loading sepolicy: %s', fcm)
-  kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find(
-      'sepolicy/kernel-sepolicy-version').text
-
-  # Load the vendor's plat sepolicy version. This is the version used for
-  # locating sepolicy mapping files.
-  vendor_plat_version_file = get_file('vendor',
-                                      'etc/selinux/plat_sepolicy_vers.txt')
-  if not vendor_plat_version_file or not os.path.exists(
-      vendor_plat_version_file):
-    raise ExternalError('Missing required sepolicy file %s',
-                        vendor_plat_version_file)
-  with open(vendor_plat_version_file) as f:
-    vendor_plat_version = f.read().strip()
-
-  # Use the same flags and arguments as selinux.cpp OpenSplitPolicy().
-  cmd = ['secilc', '-m', '-M', 'true', '-G', '-N']
-  cmd.extend(['-c', kernel_sepolicy_version])
-  cmd.extend(['-o', output_policy])
-  cmd.extend(['-f', '/dev/null'])
-
-  required_policy_files = (
-      ('system', 'etc/selinux/plat_sepolicy.cil'),
-      ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('vendor', 'etc/selinux/vendor_sepolicy.cil'),
-      ('vendor', 'etc/selinux/plat_pub_versioned.cil'),
-  )
-  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
-                     required_policy_files)):
-    if not policy or not os.path.exists(policy):
-      raise ExternalError('Missing required sepolicy file %s', policy)
-    cmd.append(policy)
-
-  optional_policy_files = (
-      ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version),
-      ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'),
-      ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('product', 'etc/selinux/product_sepolicy.cil'),
-      ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
-      ('odm', 'etc/selinux/odm_sepolicy.cil'),
-  )
-  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
-                     optional_policy_files)):
-    if policy and os.path.exists(policy):
-      cmd.append(policy)
-
-  return cmd
-
-
-def validate_merged_apex_info(output_target_files_dir, partitions):
-  """Validates the APEX files in the merged target files directory.
-
-  Checks the APEX files in all possible preinstalled APEX directories.
-  Depends on the <partition>/apex/* APEX files within partitions.
-
-  Args:
-    output_target_files_dir: Output directory containing merged partition directories.
-    partitions: A list of all the partitions in the output directory.
-
-  Raises:
-    RuntimeError: if apex_utils fails to parse any APEX file.
-    ExternalError: if the same APEX package is provided by multiple partitions.
-  """
-  apex_packages = set()
-
-  apex_partitions = ('system', 'system_ext', 'product', 'vendor')
-  for partition in filter(lambda p: p in apex_partitions, partitions):
-    apex_info = apex_utils.GetApexInfoFromTargetFiles(
-        output_target_files_dir, partition, compressed_only=False)
-    partition_apex_packages = set([info.package_name for info in apex_info])
-    duplicates = apex_packages.intersection(partition_apex_packages)
-    if duplicates:
-      raise ExternalError(
-          'Duplicate APEX packages found in multiple partitions: %s' %
-          ' '.join(duplicates))
-    apex_packages.update(partition_apex_packages)
-
-
-def generate_care_map(partitions, output_target_files_dir):
-  """Generates a merged META/care_map.pb file in the output target files dir.
-
-  Depends on the info dict from META/misc_info.txt, as well as built images
-  within IMAGES/.
-
-  Args:
-    partitions: A list of partitions to potentially include in the care map.
-    output_target_files_dir: The name of a directory that will be used to create
-      the output target files package after all the special cases are processed.
-  """
-  OPTIONS.info_dict = common.LoadInfoDict(output_target_files_dir)
-  partition_image_map = {}
-  for partition in partitions:
-    image_path = os.path.join(output_target_files_dir, 'IMAGES',
-                              '{}.img'.format(partition))
-    if os.path.exists(image_path):
-      partition_image_map[partition] = image_path
-      # Regenerated images should have their image_size property already set.
-      image_size_prop = '{}_image_size'.format(partition)
-      if image_size_prop not in OPTIONS.info_dict:
-        # Images copied directly from input target files packages will need
-        # their image sizes calculated.
-        partition_size = sparse_img.GetImagePartitionSize(image_path)
-        image_props = build_image.ImagePropFromGlobalDict(
-            OPTIONS.info_dict, partition)
-        verity_image_builder = verity_utils.CreateVerityImageBuilder(
-            image_props)
-        image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
-        OPTIONS.info_dict[image_size_prop] = image_size
-
-  AddCareMapForAbOta(
-      os.path.join(output_target_files_dir, 'META', 'care_map.pb'),
-      PARTITIONS_WITH_CARE_MAP, partition_image_map)
-
-
-def process_special_cases(framework_target_files_temp_dir,
-                          vendor_target_files_temp_dir,
-                          output_target_files_temp_dir,
-                          framework_misc_info_keys, framework_partition_set,
-                          vendor_partition_set):
-  """Performs special-case processing for certain target files items.
-
-  Certain files in the output target files package require special-case
-  processing. This function performs all that special-case processing.
-
-  Args:
-    framework_target_files_temp_dir: The name of a directory containing the
-      special items extracted from the framework target files package.
-    vendor_target_files_temp_dir: The name of a directory containing the special
-      items extracted from the vendor target files package.
-    output_target_files_temp_dir: The name of a directory that will be used to
-      create the output target files package after all the special cases are
-      processed.
-    framework_misc_info_keys: A list of keys to obtain from the framework
-      instance of META/misc_info.txt. The remaining keys should come from the
-      vendor instance.
-    framework_partition_set: Partitions that are considered framework
-      partitions. Used to filter apexkeys.txt and apkcerts.txt.
-    vendor_partition_set: Partitions that are considered vendor partitions. Used
-      to filter apexkeys.txt and apkcerts.txt.
-  """
-
-  if 'ab_update' in framework_misc_info_keys:
-    process_ab_partitions_txt(
-        framework_target_files_temp_dir=framework_target_files_temp_dir,
-        vendor_target_files_temp_dir=vendor_target_files_temp_dir,
-        output_target_files_temp_dir=output_target_files_temp_dir)
-
-  copy_file_contexts(
-      framework_target_files_dir=framework_target_files_temp_dir,
-      vendor_target_files_dir=vendor_target_files_temp_dir,
-      output_target_files_dir=output_target_files_temp_dir)
-
-  process_misc_info_txt(
-      framework_target_files_temp_dir=framework_target_files_temp_dir,
-      vendor_target_files_temp_dir=vendor_target_files_temp_dir,
-      output_target_files_temp_dir=output_target_files_temp_dir,
-      framework_misc_info_keys=framework_misc_info_keys)
-
-  process_dynamic_partitions_info_txt(
-      framework_target_files_dir=framework_target_files_temp_dir,
-      vendor_target_files_dir=vendor_target_files_temp_dir,
-      output_target_files_dir=output_target_files_temp_dir)
-
-  process_apex_keys_apk_certs_common(
-      framework_target_files_dir=framework_target_files_temp_dir,
-      vendor_target_files_dir=vendor_target_files_temp_dir,
-      output_target_files_dir=output_target_files_temp_dir,
-      framework_partition_set=framework_partition_set,
-      vendor_partition_set=vendor_partition_set,
-      file_name='apkcerts.txt')
-
-  process_apex_keys_apk_certs_common(
-      framework_target_files_dir=framework_target_files_temp_dir,
-      vendor_target_files_dir=vendor_target_files_temp_dir,
-      output_target_files_dir=output_target_files_temp_dir,
-      framework_partition_set=framework_partition_set,
-      vendor_partition_set=vendor_partition_set,
-      file_name='apexkeys.txt')
-
-
-def create_merged_package(temp_dir, framework_target_files, framework_item_list,
-                          vendor_target_files, vendor_item_list,
-                          framework_misc_info_keys, rebuild_recovery):
-  """Merges two target files packages into one target files structure.
-
-  Args:
-    temp_dir: The name of a directory we use when we extract items from the
-      input target files packages, and also a scratch directory that we use for
-      temporary files.
-    framework_target_files: The name of the zip archive containing the framework
-      partial target files package.
-    framework_item_list: The list of items to extract from the partial framework
-      target files package as is, meaning these items will land in the output
-      target files package exactly as they appear in the input partial framework
-      target files package.
-    vendor_target_files: The name of the zip archive containing the vendor
-      partial target files package.
-    vendor_item_list: The list of items to extract from the partial vendor
-      target files package as is, meaning these items will land in the output
-      target files package exactly as they appear in the input partial vendor
-      target files package.
-    framework_misc_info_keys: A list of keys to obtain from the framework
-      instance of META/misc_info.txt. The remaining keys should come from the
-      vendor instance.
-    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
-      devices and write it to the system image.
-
-  Returns:
-    Path to merged package under temp directory.
-  """
-  # Extract "as is" items from the input framework and vendor partial target
-  # files packages directly into the output temporary directory, since these items
-  # do not need special case processing.
-
-  output_target_files_temp_dir = os.path.join(temp_dir, 'output')
-  extract_items(
-      target_files=framework_target_files,
-      target_files_temp_dir=output_target_files_temp_dir,
-      extract_item_list=framework_item_list)
-  extract_items(
-      target_files=vendor_target_files,
-      target_files_temp_dir=output_target_files_temp_dir,
-      extract_item_list=vendor_item_list)
-
-  # Perform special case processing on META/* items.
-  # After this function completes successfully, all the files we need to create
-  # the output target files package are in place.
-  framework_target_files_temp_dir = os.path.join(temp_dir, 'framework')
-  vendor_target_files_temp_dir = os.path.join(temp_dir, 'vendor')
-  extract_items(
-      target_files=framework_target_files,
-      target_files_temp_dir=framework_target_files_temp_dir,
-      extract_item_list=('META/*',))
-  extract_items(
-      target_files=vendor_target_files,
-      target_files_temp_dir=vendor_target_files_temp_dir,
-      extract_item_list=('META/*',))
-  process_special_cases(
-      framework_target_files_temp_dir=framework_target_files_temp_dir,
-      vendor_target_files_temp_dir=vendor_target_files_temp_dir,
-      output_target_files_temp_dir=output_target_files_temp_dir,
-      framework_misc_info_keys=framework_misc_info_keys,
-      framework_partition_set=item_list_to_partition_set(framework_item_list),
-      vendor_partition_set=item_list_to_partition_set(vendor_item_list))
-
-  return output_target_files_temp_dir
-
-
-def generate_images(target_files_dir, rebuild_recovery):
-  """Generate images from target files.
-
-  This function takes merged output temporary directory and create images
-  from it.
-
-  Args:
-    target_files_dir: Path to merged temp directory.
-    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
-      devices and write it to the system image.
-  """
-
-  # Regenerate IMAGES in the target directory.
-
-  add_img_args = [
-      '--verbose',
-      '--add_missing',
-  ]
-  # TODO(b/132730255): Remove this if statement.
-  if rebuild_recovery:
-    add_img_args.append('--rebuild_recovery')
-  add_img_args.append(target_files_dir)
-
-  add_img_to_target_files.main(add_img_args)
-
-
-def generate_super_empty_image(target_dir, output_super_empty):
-  """Generates super_empty image from target package.
-
-  Args:
-    target_dir: Path to the target file package which contains misc_info.txt for
-      detailed information for super image.
-    output_super_empty: If provided, copies a super_empty.img file from the
-      target files package to this path.
-  """
-  # Create super_empty.img using the merged misc_info.txt.
-
-  misc_info_txt = os.path.join(target_dir, 'META', 'misc_info.txt')
-
-  use_dynamic_partitions = common.LoadDictionaryFromFile(misc_info_txt).get(
-      'use_dynamic_partitions')
-
-  if use_dynamic_partitions != 'true' and output_super_empty:
-    raise ValueError(
-        'Building super_empty.img requires use_dynamic_partitions=true.')
-  elif use_dynamic_partitions == 'true':
-    super_empty_img = os.path.join(target_dir, 'IMAGES', 'super_empty.img')
-    build_super_image_args = [
-        misc_info_txt,
-        super_empty_img,
-    ]
-    build_super_image.main(build_super_image_args)
-
-    # Copy super_empty.img to the user-provided output_super_empty location.
-    if output_super_empty:
-      shutil.copyfile(super_empty_img, output_super_empty)
-
-
-def create_target_files_archive(output_file, source_dir, temp_dir):
-  """Creates archive from target package.
-
-  Args:
-    output_file: The name of the zip archive target files package.
-    source_dir: The target directory contains package to be archived.
-    temp_dir: Path to temporary directory for any intermediate files.
-  """
-  output_target_files_list = os.path.join(temp_dir, 'output.list')
-  output_zip = os.path.abspath(output_file)
-  output_target_files_meta_dir = os.path.join(source_dir, 'META')
-
-  def files_from_path(target_path, extra_args=None):
-    """Gets files under the given path and return a sorted list."""
-    find_command = ['find', target_path] + (extra_args or [])
-    find_process = common.Run(
-        find_command, stdout=subprocess.PIPE, verbose=False)
-    return common.RunAndCheckOutput(['sort'],
-                                    stdin=find_process.stdout,
-                                    verbose=False)
-
-  meta_content = files_from_path(output_target_files_meta_dir)
-  other_content = files_from_path(
-      source_dir,
-      ['-path', output_target_files_meta_dir, '-prune', '-o', '-print'])
-
-  with open(output_target_files_list, 'w') as f:
-    f.write(meta_content)
-    f.write(other_content)
-
-  command = [
-      'soong_zip',
-      '-d',
-      '-o',
-      output_zip,
-      '-C',
-      source_dir,
-      '-r',
-      output_target_files_list,
-  ]
-
-  logger.info('creating %s', output_file)
-  common.RunAndCheckOutput(command, verbose=True)
-  logger.info('finished creating %s', output_file)
-
-  return output_zip
-
-
-def merge_target_files(temp_dir, framework_target_files, framework_item_list,
-                       framework_misc_info_keys, vendor_target_files,
-                       vendor_item_list, output_target_files, output_dir,
-                       output_item_list, output_ota, output_img,
-                       output_super_empty, rebuild_recovery):
-  """Merges two target files packages together.
-
-  This function takes framework and vendor target files packages as input,
-  performs various file extractions, special case processing, and finally
-  creates a merged zip archive as output.
-
-  Args:
-    temp_dir: The name of a directory we use when we extract items from the
-      input target files packages, and also a scratch directory that we use for
-      temporary files.
-    framework_target_files: The name of the zip archive containing the framework
-      partial target files package.
-    framework_item_list: The list of items to extract from the partial framework
-      target files package as is, meaning these items will land in the output
-      target files package exactly as they appear in the input partial framework
-      target files package.
-    framework_misc_info_keys: A list of keys to obtain from the framework
-      instance of META/misc_info.txt. The remaining keys should come from the
-      vendor instance.
-    vendor_target_files: The name of the zip archive containing the vendor
-      partial target files package.
-    vendor_item_list: The list of items to extract from the partial vendor
-      target files package as is, meaning these items will land in the output
-      target files package exactly as they appear in the input partial vendor
-      target files package.
-    output_target_files: The name of the output zip archive target files package
-      created by merging framework and vendor.
-    output_dir: The destination directory for saving merged files.
-    output_item_list: The list of items to copy into the output_dir.
-    output_ota: The name of the output zip archive ota package.
-    output_img: The name of the output zip archive img package.
-    output_super_empty: If provided, creates a super_empty.img file from the
-      merged target files package and saves it at this path.
-    rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
-      devices and write it to the system image.
-  """
-
-  logger.info('starting: merge framework %s and vendor %s into output %s',
-              framework_target_files, vendor_target_files, output_target_files)
-
-  output_target_files_temp_dir = create_merged_package(
-      temp_dir, framework_target_files, framework_item_list,
-      vendor_target_files, vendor_item_list, framework_misc_info_keys,
-      rebuild_recovery)
-
-  if not check_target_files_vintf.CheckVintf(output_target_files_temp_dir):
-    raise RuntimeError('Incompatible VINTF metadata')
-
-  partition_map = common.PartitionMapFromTargetFiles(
-      output_target_files_temp_dir)
-
-  # Generate and check for cross-partition violations of sharedUserId
-  # values in APKs. This requires the input target-files packages to contain
-  # *.apk files.
-  shareduid_violation_modules = os.path.join(
-      output_target_files_temp_dir, 'META', 'shareduid_violation_modules.json')
-  with open(shareduid_violation_modules, 'w') as f:
-    violation = find_shareduid_violation.FindShareduidViolation(
-        output_target_files_temp_dir, partition_map)
-
-    # Write the output to a file to enable debugging.
-    f.write(violation)
-
-    # Check for violations across the input builds' partition groups.
-    framework_partitions = item_list_to_partition_set(framework_item_list)
-    vendor_partitions = item_list_to_partition_set(vendor_item_list)
-    shareduid_errors = common.SharedUidPartitionViolations(
-        json.loads(violation), [framework_partitions, vendor_partitions])
-    if shareduid_errors:
-      for error in shareduid_errors:
-        logger.error(error)
-      raise ValueError('sharedUserId APK error. See %s' %
-                       shareduid_violation_modules)
-
-  # host_init_verifier and secilc check only the following partitions:
-  filtered_partitions = {
-      partition: path
-      for partition, path in partition_map.items()
-      if partition in ['system', 'system_ext', 'product', 'vendor', 'odm']
-  }
-
-  # Run host_init_verifier on the combined init rc files.
-  common.RunHostInitVerifier(
-      product_out=output_target_files_temp_dir,
-      partition_map=filtered_partitions)
-
-  # Check that the split sepolicy from the multiple builds can compile.
-  split_sepolicy_cmd = compile_split_sepolicy(
-      product_out=output_target_files_temp_dir,
-      partition_map=filtered_partitions,
-      output_policy=os.path.join(output_target_files_temp_dir,
-                                 'META/combined.policy'))
-  logger.info('Compiling split sepolicy: %s', ' '.join(split_sepolicy_cmd))
-  common.RunAndCheckOutput(split_sepolicy_cmd)
-  # TODO(b/178864050): Run tests on the combined.policy file.
-
-  # Run validation checks on the pre-installed APEX files.
-  validate_merged_apex_info(output_target_files_temp_dir, partition_map.keys())
-
-  generate_images(output_target_files_temp_dir, rebuild_recovery)
-
-  generate_super_empty_image(output_target_files_temp_dir, output_super_empty)
-
-  # Finally, create the output target files zip archive and/or copy the
-  # output items to the output target files directory.
-
-  if output_dir:
-    copy_items(output_target_files_temp_dir, output_dir, output_item_list)
-
-  if not output_target_files:
-    return
-
-  # Create the merged META/care_map.bp
-  generate_care_map(partition_map.keys(), output_target_files_temp_dir)
-
-  output_zip = create_target_files_archive(output_target_files,
-                                           output_target_files_temp_dir,
-                                           temp_dir)
-
-  # Create the IMG package from the merged target files package.
-  if output_img:
-    img_from_target_files.main([output_zip, output_img])
-
-  # Create the OTA package from the merged target files package.
-
-  if output_ota:
-    ota_from_target_files.main([output_zip, output_ota])
-
-
-def call_func_with_temp_dir(func, keep_tmp):
-  """Manages the creation and cleanup of the temporary directory.
-
-  This function calls the given function after first creating a temporary
-  directory. It also cleans up the temporary directory.
-
-  Args:
-    func: The function to call. Should accept one parameter, the path to the
-      temporary directory.
-    keep_tmp: Keep the temporary directory after processing is complete.
-  """
-
-  # Create a temporary directory. This will serve as the parent of directories
-  # we use when we extract items from the input target files packages, and also
-  # a scratch directory that we use for temporary files.
-
-  temp_dir = common.MakeTempDir(prefix='merge_target_files_')
-
-  try:
-    func(temp_dir)
-  finally:
-    if keep_tmp:
-      logger.info('keeping %s', temp_dir)
-    else:
-      common.Cleanup()
-
-
-def main():
-  """The main function.
-
-  Process command line arguments, then call merge_target_files to
-  perform the heavy lifting.
-  """
-
-  common.InitLogging()
-
-  def option_handler(o, a):
-    if o == '--system-target-files':
-      logger.warning(
-          '--system-target-files has been renamed to --framework-target-files')
-      OPTIONS.framework_target_files = a
-    elif o == '--framework-target-files':
-      OPTIONS.framework_target_files = a
-    elif o == '--system-item-list':
-      logger.warning(
-          '--system-item-list has been renamed to --framework-item-list')
-      OPTIONS.framework_item_list = a
-    elif o == '--framework-item-list':
-      OPTIONS.framework_item_list = a
-    elif o == '--system-misc-info-keys':
-      logger.warning('--system-misc-info-keys has been renamed to '
-                     '--framework-misc-info-keys')
-      OPTIONS.framework_misc_info_keys = a
-    elif o == '--framework-misc-info-keys':
-      OPTIONS.framework_misc_info_keys = a
-    elif o == '--other-target-files':
-      logger.warning(
-          '--other-target-files has been renamed to --vendor-target-files')
-      OPTIONS.vendor_target_files = a
-    elif o == '--vendor-target-files':
-      OPTIONS.vendor_target_files = a
-    elif o == '--other-item-list':
-      logger.warning('--other-item-list has been renamed to --vendor-item-list')
-      OPTIONS.vendor_item_list = a
-    elif o == '--vendor-item-list':
-      OPTIONS.vendor_item_list = a
-    elif o == '--output-target-files':
-      OPTIONS.output_target_files = a
-    elif o == '--output-dir':
-      OPTIONS.output_dir = a
-    elif o == '--output-item-list':
-      OPTIONS.output_item_list = a
-    elif o == '--output-ota':
-      OPTIONS.output_ota = a
-    elif o == '--output-img':
-      OPTIONS.output_img = a
-    elif o == '--output-super-empty':
-      OPTIONS.output_super_empty = a
-    elif o == '--rebuild_recovery':  # TODO(b/132730255): Warn
-      OPTIONS.rebuild_recovery = True
-    elif o == '--allow-duplicate-apkapex-keys':
-      OPTIONS.allow_duplicate_apkapex_keys = True
-    elif o == '--keep-tmp':
-      OPTIONS.keep_tmp = True
-    else:
-      return False
-    return True
-
-  args = common.ParseOptions(
-      sys.argv[1:],
-      __doc__,
-      extra_long_opts=[
-          'system-target-files=',
-          'framework-target-files=',
-          'system-item-list=',
-          'framework-item-list=',
-          'system-misc-info-keys=',
-          'framework-misc-info-keys=',
-          'other-target-files=',
-          'vendor-target-files=',
-          'other-item-list=',
-          'vendor-item-list=',
-          'output-target-files=',
-          'output-dir=',
-          'output-item-list=',
-          'output-ota=',
-          'output-img=',
-          'output-super-empty=',
-          'rebuild_recovery',
-          'allow-duplicate-apkapex-keys',
-          'keep-tmp',
-      ],
-      extra_option_handler=option_handler)
-
-  # pylint: disable=too-many-boolean-expressions
-  if (args or OPTIONS.framework_target_files is None or
-      OPTIONS.vendor_target_files is None or
-      (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
-      (OPTIONS.output_dir is not None and OPTIONS.output_item_list is None)):
-    common.Usage(__doc__)
-    sys.exit(1)
-
-  if OPTIONS.framework_item_list:
-    framework_item_list = common.LoadListFromFile(OPTIONS.framework_item_list)
-  else:
-    framework_item_list = DEFAULT_FRAMEWORK_ITEM_LIST
-
-  if OPTIONS.framework_misc_info_keys:
-    framework_misc_info_keys = common.LoadListFromFile(
-        OPTIONS.framework_misc_info_keys)
-  else:
-    framework_misc_info_keys = DEFAULT_FRAMEWORK_MISC_INFO_KEYS
-
-  if OPTIONS.vendor_item_list:
-    vendor_item_list = common.LoadListFromFile(OPTIONS.vendor_item_list)
-  else:
-    vendor_item_list = DEFAULT_VENDOR_ITEM_LIST
-
-  if OPTIONS.output_item_list:
-    output_item_list = common.LoadListFromFile(OPTIONS.output_item_list)
-  else:
-    output_item_list = None
-
-  if not validate_config_lists(
-      framework_item_list=framework_item_list,
-      framework_misc_info_keys=framework_misc_info_keys,
-      vendor_item_list=vendor_item_list):
-    sys.exit(1)
-
-  call_func_with_temp_dir(
-      lambda temp_dir: merge_target_files(
-          temp_dir=temp_dir,
-          framework_target_files=OPTIONS.framework_target_files,
-          framework_item_list=framework_item_list,
-          framework_misc_info_keys=framework_misc_info_keys,
-          vendor_target_files=OPTIONS.vendor_target_files,
-          vendor_item_list=vendor_item_list,
-          output_target_files=OPTIONS.output_target_files,
-          output_dir=OPTIONS.output_dir,
-          output_item_list=output_item_list,
-          output_ota=OPTIONS.output_ota,
-          output_img=OPTIONS.output_img,
-          output_super_empty=OPTIONS.output_super_empty,
-          rebuild_recovery=OPTIONS.rebuild_recovery), OPTIONS.keep_tmp)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/tools/releasetools/non_ab_ota.py b/tools/releasetools/non_ab_ota.py
index 471ef25..9732cda 100644
--- a/tools/releasetools/non_ab_ota.py
+++ b/tools/releasetools/non_ab_ota.py
@@ -74,7 +74,7 @@
 
   block_diff_dict = collections.OrderedDict()
   partition_names = ["system", "vendor", "product", "odm", "system_ext",
-                     "vendor_dlkm", "odm_dlkm"]
+                     "vendor_dlkm", "odm_dlkm", "system_dlkm"]
   for partition in partition_names:
     if not HasPartition(target_zip, partition):
       continue
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 42d1211..d1b9358 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -221,6 +221,29 @@
       For VABC downgrades, we must finish merging before doing data wipe, and
       since data wipe is required for downgrading OTA, this might cause long
       wait time in recovery.
+
+  --enable_vabc_xor
+      Enable the VABC xor feature. Will reduce space requirements for OTA
+
+  --force_minor_version
+      Override the update_engine minor version for delta generation.
+
+  --compressor_types
+      A colon ':' separated list of compressors. Allowed values are bz2 and brotli.
+
+  --enable_zucchini
+      Whether to enable to zucchini feature. Will generate smaller OTA but uses more memory.
+
+  --enable_lz4diff
+      Whether to enable lz4diff feature. Will generate smaller OTA for EROFS but
+      uses more memory.
+
+  --spl_downgrade
+      Force generate an SPL downgrade OTA. Only needed if target build has an
+      older SPL.
+
+  --vabc_compression_param
+      Compression algorithm to be used for VABC. Available options: gz, brotli, none
 """
 
 from __future__ import print_function
@@ -237,10 +260,12 @@
 import sys
 import zipfile
 
+import care_map_pb2
 import common
 import ota_utils
 from ota_utils import (UNZIP_PATTERN, FinalizeMetadata, GetPackageMetadata,
-                       PropertyFiles, SECURITY_PATCH_LEVEL_PROP_NAME)
+                       PropertyFiles, SECURITY_PATCH_LEVEL_PROP_NAME, GetZipEntryOffset)
+from common import IsSparseImage
 import target_files_diff
 from check_target_files_vintf import CheckVintfIfTrebleEnabled
 from non_ab_ota import GenerateNonAbOtaPackage
@@ -285,6 +310,12 @@
 OPTIONS.disable_vabc = False
 OPTIONS.spl_downgrade = False
 OPTIONS.vabc_downgrade = False
+OPTIONS.enable_vabc_xor = True
+OPTIONS.force_minor_version = None
+OPTIONS.compressor_types = None
+OPTIONS.enable_zucchini = True
+OPTIONS.enable_lz4diff = False
+OPTIONS.vabc_compression_param = None
 
 POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
 DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
@@ -293,15 +324,15 @@
 # Files to be unzipped for target diffing purpose.
 TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
                                 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*',
-                                'VENDOR_DLKM/*', 'ODM_DLKM/*']
+                                'VENDOR_DLKM/*', 'ODM_DLKM/*', 'SYSTEM_DLKM/*']
 RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
 
 # Images to be excluded from secondary payload. We essentially only keep
 # 'system_other' and bootloader partitions.
 SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
     'boot', 'dtbo', 'modem', 'odm', 'odm_dlkm', 'product', 'radio', 'recovery',
-    'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor',
-    'vendor_boot']
+    'system_dlkm', 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor',
+    'vendor', 'vendor_boot']
 
 
 class PayloadSigner(object):
@@ -512,8 +543,7 @@
 
   oem_dicts = []
   for oem_file in oem_source:
-    with open(oem_file) as fp:
-      oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
+    oem_dicts.append(common.LoadDictionaryFromFile(oem_file))
   return oem_dicts
 
 
@@ -529,6 +559,8 @@
         'payload_properties.txt',
     )
     self.optional = (
+        # apex_info.pb isn't directly used in the update flow
+        'apex_info.pb',
         # care_map is available only if dm-verity is enabled.
         'care_map.pb',
         'care_map.txt',
@@ -599,20 +631,20 @@
     payload, till the end of 'medatada_signature_message'.
     """
     payload_info = input_zip.getinfo('payload.bin')
-    payload_offset = payload_info.header_offset
-    payload_offset += zipfile.sizeFileHeader
-    payload_offset += len(payload_info.extra) + len(payload_info.filename)
-    payload_size = payload_info.file_size
+    (payload_offset, payload_size) = GetZipEntryOffset(input_zip, payload_info)
 
-    with input_zip.open('payload.bin') as payload_fp:
-      header_bin = payload_fp.read(24)
+    # Read the underlying raw zipfile at specified offset
+    payload_fp = input_zip.fp
+    payload_fp.seek(payload_offset)
+    header_bin = payload_fp.read(24)
 
     # network byte order (big-endian)
     header = struct.unpack("!IQQL", header_bin)
 
     # 'CrAU'
     magic = header[0]
-    assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
+    assert magic == 0x43724155, "Invalid magic: {:x}, computed offset {}" \
+        .format(magic, payload_offset)
 
     manifest_size = header[2]
     metadata_signature_size = header[3]
@@ -622,6 +654,24 @@
     return (payload_offset, metadata_total)
 
 
+def ModifyVABCCompressionParam(content, algo):
+  """ Update update VABC Compression Param in dynamic_partitions_info.txt
+  Args:
+    content: The string content of dynamic_partitions_info.txt
+    algo: The compression algorithm should be used for VABC. See
+          https://cs.android.com/android/platform/superproject/+/master:system/core/fs_mgr/libsnapshot/cow_writer.cpp;l=127;bpv=1;bpt=1?q=CowWriter::ParseOptions&sq=
+  Returns:
+    Updated content of dynamic_partitions_info.txt , with custom compression algo
+  """
+  output_list = []
+  for line in content.splitlines():
+    if line.startswith("virtual_ab_compression_method="):
+      continue
+    output_list.append(line)
+  output_list.append("virtual_ab_compression_method="+algo)
+  return "\n".join(output_list)
+
+
 def UpdatesInfoForSpecialUpdates(content, partitions_filter,
                                  delete_keys=None):
   """ Updates info file for secondary payload generation, partial update, etc.
@@ -776,6 +826,27 @@
     return common.LoadInfoDict(zfp)
 
 
+def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
+  """Returns a target-files.zip with a custom VABC compression param.
+  Args:
+    input_file: The input target-files.zip path
+    vabc_compression_param: Custom Virtual AB Compression algorithm
+
+  Returns:
+    The path to modified target-files.zip
+  """
+  target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
+  shutil.copyfile(input_file, target_file)
+  common.ZipDelete(target_file, DYNAMIC_PARTITION_INFO)
+  with zipfile.ZipFile(input_file, 'r', allowZip64=True) as zfp:
+    dynamic_partition_info = zfp.read(DYNAMIC_PARTITION_INFO).decode()
+    dynamic_partition_info = ModifyVABCCompressionParam(
+        dynamic_partition_info, vabc_compression_param)
+    with zipfile.ZipFile(target_file, "a", allowZip64=True) as output_zip:
+      output_zip.writestr(DYNAMIC_PARTITION_INFO, dynamic_partition_info)
+  return target_file
+
+
 def GetTargetFilesZipForPartialUpdates(input_file, ab_partitions):
   """Returns a target-files.zip for partial ota update package generation.
 
@@ -832,6 +903,17 @@
   with zipfile.ZipFile(input_file, allowZip64=True) as input_zip:
     common.ZipWriteStr(partial_target_zip, 'META/ab_partitions.txt',
                        '\n'.join(ab_partitions))
+    CARE_MAP_ENTRY = "META/care_map.pb"
+    if CARE_MAP_ENTRY in input_zip.namelist():
+      caremap = care_map_pb2.CareMap()
+      caremap.ParseFromString(input_zip.read(CARE_MAP_ENTRY))
+      filtered = [
+          part for part in caremap.partitions if part.name in ab_partitions]
+      del caremap.partitions[:]
+      caremap.partitions.extend(filtered)
+      common.ZipWriteStr(partial_target_zip, CARE_MAP_ENTRY,
+                         caremap.SerializeToString())
+
     for info_file in ['META/misc_info.txt', DYNAMIC_PARTITION_INFO]:
       if info_file not in input_zip.namelist():
         logger.warning('Cannot find %s in input zipfile', info_file)
@@ -839,9 +921,13 @@
       content = input_zip.read(info_file).decode()
       modified_info = UpdatesInfoForSpecialUpdates(
           content, lambda p: p in ab_partitions)
+      if OPTIONS.vabc_compression_param and info_file == DYNAMIC_PARTITION_INFO:
+        modified_info = ModifyVABCCompressionParam(
+            modified_info, OPTIONS.vabc_compression_param)
       common.ZipWriteStr(partial_target_zip, info_file, modified_info)
 
-    # TODO(xunchang) handle 'META/care_map.pb', 'META/postinstall_config.txt'
+    # TODO(xunchang) handle META/postinstall_config.txt'
+
   common.ZipClose(partial_target_zip)
 
   return partial_target_file
@@ -982,10 +1068,11 @@
         pre_partition_state, post_partition_state):
   assert pre_partition_state is not None
   partition_timestamps = {}
-  for part in pre_partition_state:
-    partition_timestamps[part.partition_name] = part.version
   for part in post_partition_state:
-    partition_timestamps[part.partition_name] = \
+    partition_timestamps[part.partition_name] = part.version
+  for part in pre_partition_state:
+    if part.partition_name in partition_timestamps:
+      partition_timestamps[part.partition_name] = \
         max(part.version, partition_timestamps[part.partition_name])
   return [
       "--partition_timestamps",
@@ -994,13 +1081,6 @@
   ]
 
 
-def IsSparseImage(filepath):
-  with open(filepath, 'rb') as fp:
-    # Magic for android sparse image format
-    # https://source.android.com/devices/bootloader/images
-    return fp.read(4) == b'\x3A\xFF\x26\xED'
-
-
 def SupportsMainlineGkiUpdates(target_file):
   """Return True if the build supports MainlineGKIUpdates.
 
@@ -1075,6 +1155,14 @@
   if target_info.vendor_suppressed_vabc:
     logger.info("Vendor suppressed VABC. Disabling")
     OPTIONS.disable_vabc = True
+
+  # Both source and target build need to support VABC XOR for us to use it.
+  # Source build's update_engine must be able to write XOR ops, and target
+  # build's snapuserd must be able to interpret XOR ops.
+  if not target_info.is_vabc_xor or OPTIONS.disable_vabc or \
+          (source_info is not None and not source_info.is_vabc_xor):
+    logger.info("VABC XOR Not supported, disabling")
+    OPTIONS.enable_vabc_xor = False
   additional_args = []
 
   # Prepare custom images.
@@ -1090,6 +1178,9 @@
     target_file = GetTargetFilesZipForPartialUpdates(target_file,
                                                      OPTIONS.partial)
     additional_args += ["--is_partial_update", "true"]
+  elif OPTIONS.vabc_compression_param:
+    target_file = GetTargetFilesZipForCustomVABCCompression(
+        target_file, OPTIONS.vabc_compression_param)
   elif OPTIONS.skip_postinstall:
     target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
   # Target_file may have been modified, reparse ab_partitions
@@ -1097,6 +1188,8 @@
     target_info.info_dict['ab_partitions'] = zfp.read(
         AB_PARTITIONS).decode().strip().split("\n")
 
+  CheckVintfIfTrebleEnabled(target_file, target_info)
+
   # Metadata to comply with Android OTA package format.
   metadata = GetPackageMetadata(target_info, source_info)
   # Generate payload.
@@ -1115,8 +1208,40 @@
     partition_timestamps_flags = GeneratePartitionTimestampFlags(
         metadata.postcondition.partition_state)
 
+  if not ota_utils.IsZucchiniCompatible(source_file, target_file):
+    OPTIONS.enable_zucchini = False
+
+  additional_args += ["--enable_zucchini",
+                      str(OPTIONS.enable_zucchini).lower()]
+
+  if not ota_utils.IsLz4diffCompatible(source_file, target_file):
+    logger.warning(
+        "Source build doesn't support lz4diff, or source/target don't have compatible lz4diff versions. Disabling lz4diff.")
+    OPTIONS.enable_lz4diff = False
+
+  additional_args += ["--enable_lz4diff",
+                      str(OPTIONS.enable_lz4diff).lower()]
+
+  if source_file and OPTIONS.enable_lz4diff:
+    input_tmp = common.UnzipTemp(source_file, ["META/liblz4.so"])
+    liblz4_path = os.path.join(input_tmp, "META", "liblz4.so")
+    assert os.path.exists(
+        liblz4_path), "liblz4.so not found in META/ dir of target file {}".format(liblz4_path)
+    logger.info("Enabling lz4diff %s", liblz4_path)
+    additional_args += ["--liblz4_path", liblz4_path]
+    erofs_compression_param = OPTIONS.target_info_dict.get(
+        "erofs_default_compressor")
+    assert erofs_compression_param is not None, "'erofs_default_compressor' not found in META/misc_info.txt of target build. This is required to enable lz4diff."
+    additional_args += ["--erofs_compression_param", erofs_compression_param]
+
   if OPTIONS.disable_vabc:
     additional_args += ["--disable_vabc", "true"]
+  if OPTIONS.enable_vabc_xor:
+    additional_args += ["--enable_vabc_xor", "true"]
+  if OPTIONS.force_minor_version:
+    additional_args += ["--force_minor_version", OPTIONS.force_minor_version]
+  if OPTIONS.compressor_types:
+    additional_args += ["--compressor_types", OPTIONS.compressor_types]
   additional_args += ["--max_timestamp", max_timestamp]
 
   if SupportsMainlineGkiUpdates(source_file):
@@ -1170,19 +1295,15 @@
     else:
       logger.warning("Cannot find care map file in target_file package")
 
-  # Copy apex_info.pb over to generated OTA package.
-  try:
-    apex_info_entry = target_zip.getinfo("META/apex_info.pb")
-    with target_zip.open(apex_info_entry, "r") as zfp:
-      common.ZipWriteStr(output_zip, "apex_info.pb", zfp.read(),
-                         compress_type=zipfile.ZIP_STORED)
-  except KeyError:
-    logger.warning("target_file doesn't contain apex_info.pb %s", target_file)
+  # Add the source apex version for incremental ota updates, and write the
+  # result apex info to the ota package.
+  ota_apex_info = ota_utils.ConstructOtaApexInfo(target_zip, source_file)
+  if ota_apex_info is not None:
+    common.ZipWriteStr(output_zip, "apex_info.pb", ota_apex_info,
+                       compress_type=zipfile.ZIP_STORED)
 
   common.ZipClose(target_zip)
 
-  CheckVintfIfTrebleEnabled(target_file, target_info)
-
   # We haven't written the metadata entry yet, which will be handled in
   # FinalizeMetadata().
   common.ZipClose(output_zip)
@@ -1292,6 +1413,21 @@
       OPTIONS.wipe_user_data = True
     elif o == "--vabc_downgrade":
       OPTIONS.vabc_downgrade = True
+    elif o == "--enable_vabc_xor":
+      assert a.lower() in ["true", "false"]
+      OPTIONS.enable_vabc_xor = a.lower() != "false"
+    elif o == "--force_minor_version":
+      OPTIONS.force_minor_version = a
+    elif o == "--compressor_types":
+      OPTIONS.compressor_types = a
+    elif o == "--enable_zucchini":
+      assert a.lower() in ["true", "false"]
+      OPTIONS.enable_zucchini = a.lower() != "false"
+    elif o == "--enable_lz4diff":
+      assert a.lower() in ["true", "false"]
+      OPTIONS.enable_lz4diff = a.lower() != "false"
+    elif o == "--vabc_compression_param":
+      OPTIONS.vabc_compression_param = a.lower()
     else:
       return False
     return True
@@ -1336,6 +1472,12 @@
                                  "disable_vabc",
                                  "spl_downgrade",
                                  "vabc_downgrade",
+                                 "enable_vabc_xor=",
+                                 "force_minor_version=",
+                                 "compressor_types=",
+                                 "enable_zucchini=",
+                                 "enable_lz4diff=",
+                                 "vabc_compression_param=",
                              ], extra_option_handler=option_handler)
 
   if len(args) != 2:
@@ -1367,8 +1509,8 @@
     # We should only allow downgrading incrementals (as opposed to full).
     # Otherwise the device may go back from arbitrary build with this full
     # OTA package.
-    if OPTIONS.incremental_source is None:
-      raise ValueError("Cannot generate downgradable full OTAs")
+  if OPTIONS.incremental_source is None and OPTIONS.downgrade:
+    raise ValueError("Cannot generate downgradable full OTAs")
 
   # TODO(xunchang) for retrofit and partial updates, maybe we should rebuild the
   # target-file and reload the info_dict. So the info will be consistent with
@@ -1436,13 +1578,23 @@
           "build/make/target/product/security/testkey")
     # Get signing keys
     OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
-    private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix
-    if not os.path.exists(private_key_path):
-      raise common.ExternalError(
-          "Private key {} doesn't exist. Make sure you passed the"
-          " correct key path through -k option".format(
-              private_key_path)
-      )
+
+    # Only check for existence of key file if using the default signer.
+    # Because the custom signer might not need the key file AT all.
+    # b/191704641
+    if not OPTIONS.payload_signer:
+      private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix
+      if not os.path.exists(private_key_path):
+        raise common.ExternalError(
+            "Private key {} doesn't exist. Make sure you passed the"
+            " correct key path through -k option".format(
+                private_key_path)
+        )
+      signapk_abs_path = os.path.join(
+          OPTIONS.search_path, OPTIONS.signapk_path)
+      if not os.path.exists(signapk_abs_path):
+        raise common.ExternalError(
+            "Failed to find sign apk binary {} in search path {}. Make sure the correct search path is passed via -p".format(OPTIONS.signapk_path, OPTIONS.search_path))
 
   if OPTIONS.source_info_dict:
     source_build_prop = OPTIONS.source_info_dict["build.prop"]
@@ -1494,8 +1646,5 @@
   try:
     common.CloseInheritedPipes()
     main(sys.argv[1:])
-  except common.ExternalError:
-    logger.exception("\n   ERROR:\n")
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/ota_metadata.proto b/tools/releasetools/ota_metadata.proto
index ed9d0c3..689ce80 100644
--- a/tools/releasetools/ota_metadata.proto
+++ b/tools/releasetools/ota_metadata.proto
@@ -72,6 +72,8 @@
   int64 version = 2;
   bool is_compressed = 3;
   int64 decompressed_size = 4;
+  // Used in OTA
+  int64 source_version = 5;
 }
 
 // Just a container to hold repeated apex_info, so that we can easily serialize
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 104f02f..5d403dc 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -16,6 +16,7 @@
 import itertools
 import logging
 import os
+import struct
 import zipfile
 
 import ota_metadata_pb2
@@ -153,7 +154,7 @@
                 compress_type=zipfile.ZIP_STORED)
     return
 
-  with open('{}.pb'.format(output), 'w') as f:
+  with open('{}.pb'.format(output), 'wb') as f:
     f.write(metadata_proto.SerializeToString())
   with open(output, 'w') as f:
     f.write(legacy_metadata)
@@ -399,6 +400,35 @@
   return device_names, fingerprints
 
 
+def GetZipEntryOffset(zfp, entry_info):
+  """Get offset to a beginning of a particular zip entry
+  Args:
+    fp: zipfile.ZipFile
+    entry_info: zipfile.ZipInfo
+
+  Returns:
+    (offset, size) tuple
+  """
+  # Don't use len(entry_info.extra). Because that returns size of extra
+  # fields in central directory. We need to look at local file directory,
+  # as these two might have different sizes.
+
+  # We cannot work with zipfile.ZipFile instances, we need a |fp| for the underlying file.
+  zfp = zfp.fp
+  zfp.seek(entry_info.header_offset)
+  data = zfp.read(zipfile.sizeFileHeader)
+  fheader = struct.unpack(zipfile.structFileHeader, data)
+  # Last two fields of local file header are filename length and
+  # extra length
+  filename_len = fheader[-2]
+  extra_len = fheader[-1]
+  offset = entry_info.header_offset
+  offset += zipfile.sizeFileHeader
+  offset += filename_len + extra_len
+  size = entry_info.file_size
+  return (offset, size)
+
+
 class PropertyFiles(object):
   """A class that computes the property-files string for an OTA package.
 
@@ -517,10 +547,7 @@
     def ComputeEntryOffsetSize(name):
       """Computes the zip entry offset and size."""
       info = zip_file.getinfo(name)
-      offset = info.header_offset
-      offset += zipfile.sizeFileHeader
-      offset += len(info.extra) + len(info.filename)
-      size = info.file_size
+      (offset, size) = GetZipEntryOffset(zip_file, info)
       return '%s:%d:%d' % (os.path.basename(name), offset, size)
 
     tokens = []
@@ -542,7 +569,8 @@
       tokens.append('metadata.pb:' + ' ' * 15)
     else:
       tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
-      tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
+      if METADATA_PROTO_NAME in zip_file.namelist():
+          tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
 
     return ','.join(tokens)
 
@@ -569,3 +597,102 @@
 
   SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
            whole_file=True)
+
+
+def ConstructOtaApexInfo(target_zip, source_file=None):
+  """If applicable, add the source version to the apex info."""
+
+  def _ReadApexInfo(input_zip):
+    if "META/apex_info.pb" not in input_zip.namelist():
+      logger.warning("target_file doesn't contain apex_info.pb %s", input_zip)
+      return None
+
+    with input_zip.open("META/apex_info.pb", "r") as zfp:
+      return zfp.read()
+
+  target_apex_string = _ReadApexInfo(target_zip)
+  # Return early if the target apex info doesn't exist or is empty.
+  if not target_apex_string:
+    return target_apex_string
+
+  # If the source apex info isn't available, just return the target info
+  if not source_file:
+    return target_apex_string
+
+  with zipfile.ZipFile(source_file, "r", allowZip64=True) as source_zip:
+    source_apex_string = _ReadApexInfo(source_zip)
+  if not source_apex_string:
+    return target_apex_string
+
+  source_apex_proto = ota_metadata_pb2.ApexMetadata()
+  source_apex_proto.ParseFromString(source_apex_string)
+  source_apex_versions = {apex.package_name: apex.version for apex in
+                          source_apex_proto.apex_info}
+
+  # If the apex package is available in the source build, initialize the source
+  # apex version.
+  target_apex_proto = ota_metadata_pb2.ApexMetadata()
+  target_apex_proto.ParseFromString(target_apex_string)
+  for target_apex in target_apex_proto.apex_info:
+    name = target_apex.package_name
+    if name in source_apex_versions:
+      target_apex.source_version = source_apex_versions[name]
+
+  return target_apex_proto.SerializeToString()
+
+
+def IsLz4diffCompatible(source_file: str, target_file: str):
+  """Check whether lz4diff versions in two builds are compatible
+
+  Args:
+    source_file: Path to source build's target_file.zip
+    target_file: Path to target build's target_file.zip
+
+  Returns:
+    bool true if and only if lz4diff versions are compatible
+  """
+  if source_file is None or target_file is None:
+    return False
+  # Right now we enable lz4diff as long as source build has liblz4.so.
+  # In the future we might introduce version system to lz4diff as well.
+  if zipfile.is_zipfile(source_file):
+    with zipfile.ZipFile(source_file, "r") as zfp:
+      return "META/liblz4.so" in zfp.namelist()
+  else:
+    assert os.path.isdir(source_file)
+    return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
+
+
+def IsZucchiniCompatible(source_file: str, target_file: str):
+  """Check whether zucchini versions in two builds are compatible
+
+  Args:
+    source_file: Path to source build's target_file.zip
+    target_file: Path to target build's target_file.zip
+
+  Returns:
+    bool true if and only if zucchini versions are compatible
+  """
+  if source_file is None or target_file is None:
+    return False
+  assert os.path.exists(source_file)
+  assert os.path.exists(target_file)
+
+  assert zipfile.is_zipfile(source_file) or os.path.isdir(source_file)
+  assert zipfile.is_zipfile(target_file) or os.path.isdir(target_file)
+  _ZUCCHINI_CONFIG_ENTRY_NAME = "META/zucchini_config.txt"
+
+  def ReadEntry(path, entry):
+    # Read an entry inside a .zip file or extracted dir of .zip file
+    if zipfile.is_zipfile(path):
+      with zipfile.ZipFile(path, "r", allowZip64=True) as zfp:
+        if entry in zfp.namelist():
+          return zfp.read(entry).decode()
+    else:
+      entry_path = os.path.join(entry, path)
+      if os.path.exists(entry_path):
+        with open(entry_path, "r") as fp:
+          return fp.read()
+      else:
+        return ""
+  return ReadEntry(source_file, _ZUCCHINI_CONFIG_ENTRY_NAME) == ReadEntry(target_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py
index fb947f4..6926467 100755
--- a/tools/releasetools/sign_apex.py
+++ b/tools/releasetools/sign_apex.py
@@ -39,6 +39,9 @@
   --codename_to_api_level_map Q:29,R:30,...
       A Mapping of codename to api level.  This is useful to provide sdk targeting
       information to APK Signer.
+
+  --sign_tool <sign_tool>
+      Optional flag that specifies a custom signing tool for the contents of the apex.
 """
 
 import logging
@@ -52,7 +55,7 @@
 
 
 def SignApexFile(avbtool, apex_file, payload_key, container_key, no_hashtree,
-                 apk_keys=None, signing_args=None, codename_to_api_level_map=None):
+                 apk_keys=None, signing_args=None, codename_to_api_level_map=None, sign_tool=None):
   """Signs the given apex file."""
   with open(apex_file, 'rb') as input_fp:
     apex_data = input_fp.read()
@@ -66,7 +69,8 @@
       codename_to_api_level_map=codename_to_api_level_map,
       no_hashtree=no_hashtree,
       apk_keys=apk_keys,
-      signing_args=signing_args)
+      signing_args=signing_args,
+      sign_tool=sign_tool)
 
 
 def main(argv):
@@ -100,6 +104,8 @@
         if 'extra_apks' not in options:
           options['extra_apks'] = {}
         options['extra_apks'].update({n: key})
+    elif o == '--sign_tool':
+      options['sign_tool'] = a
     else:
       return False
     return True
@@ -114,6 +120,7 @@
           'payload_extra_args=',
           'payload_key=',
           'extra_apks=',
+          'sign_tool=',
       ],
       extra_option_handler=option_handler)
 
@@ -133,7 +140,8 @@
       apk_keys=options.get('extra_apks', {}),
       signing_args=options.get('payload_extra_args'),
       codename_to_api_level_map=options.get(
-          'codename_to_api_level_map', {}))
+          'codename_to_api_level_map', {}),
+      sign_tool=options.get('sign_tool', None))
   shutil.copyfile(signed_apex, args[1])
   logger.info("done.")
 
@@ -141,8 +149,5 @@
 if __name__ == '__main__':
   try:
     main(sys.argv[1:])
-  except common.ExternalError:
-    logger.exception("\n   ERROR:\n")
-    sys.exit(1)
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 0842af9..6f96d8f 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -99,15 +99,15 @@
       The second dir will be used for lookup if BOARD_USES_RECOVERY_AS_BOOT is
       set to true.
 
-  --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
+  --avb_{boot,recovery,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
          vbmeta_vendor}_algorithm <algorithm>
-  --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
+  --avb_{boot,recovery,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
          vbmeta_vendor}_key <key>
       Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
       the specified image. Otherwise it uses the existing values in info dict.
 
-  --avb_{apex,boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
-         vbmeta_vendor}_extra_args <args>
+  --avb_{apex,boot,recovery,system,system_other,vendor,dtbo,vbmeta,
+         vbmeta_system,vbmeta_vendor}_extra_args <args>
       Specify any additional args that are needed to AVB-sign the image
       (e.g. "--signing_helper /path/to/helper"). The args will be appended to
       the existing ones in info dict.
@@ -136,6 +136,11 @@
 
   --android_jar_path <path>
       Path to the android.jar to repack the apex file.
+
+  --allow_gsi_debug_sepolicy
+      Allow the existence of the file 'userdebug_plat_sepolicy.cil' under
+      (/system/system_ext|/system_ext)/etc/selinux.
+      If not set, error out when the file exists.
 """
 
 from __future__ import print_function
@@ -189,14 +194,19 @@
 OPTIONS.gki_signing_algorithm = None
 OPTIONS.gki_signing_extra_args = None
 OPTIONS.android_jar_path = None
+OPTIONS.vendor_partitions = set()
+OPTIONS.vendor_otatools = None
+OPTIONS.allow_gsi_debug_sepolicy = False
 
 
 AVB_FOOTER_ARGS_BY_PARTITION = {
     'boot': 'avb_boot_add_hash_footer_args',
+    'init_boot': 'avb_init_boot_add_hash_footer_args',
     'dtbo': 'avb_dtbo_add_hash_footer_args',
     'product': 'avb_product_add_hashtree_footer_args',
     'recovery': 'avb_recovery_add_hash_footer_args',
     'system': 'avb_system_add_hashtree_footer_args',
+    'system_dlkm': "avb_system_dlkm_add_hashtree_footer_args",
     'system_ext': 'avb_system_ext_add_hashtree_footer_args',
     'system_other': 'avb_system_other_add_hashtree_footer_args',
     'odm': 'avb_odm_add_hashtree_footer_args',
@@ -204,6 +214,7 @@
     'pvmfw': 'avb_pvmfw_add_hash_footer_args',
     'vendor': 'avb_vendor_add_hashtree_footer_args',
     'vendor_boot': 'avb_vendor_boot_add_hash_footer_args',
+    'vendor_kernel_boot': 'avb_vendor_kernel_boot_add_hash_footer_args',
     'vendor_dlkm': "avb_vendor_dlkm_add_hashtree_footer_args",
     'vbmeta': 'avb_vbmeta_args',
     'vbmeta_system': 'avb_vbmeta_system_args',
@@ -216,6 +227,10 @@
   if partition not in AVB_FOOTER_ARGS_BY_PARTITION:
     raise RuntimeError("Missing {} in AVB_FOOTER_ARGS".format(partition))
 
+# Partitions that can be regenerated after signing using a separate
+# vendor otatools package.
+ALLOWED_VENDOR_PARTITIONS = set(["vendor", "odm"])
+
 
 def IsApexFile(filename):
   return filename.endswith(".apex") or filename.endswith(".capex")
@@ -250,7 +265,7 @@
 
   Args:
     keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
-        container_key).
+        container_key, sign_tool).
     key_map: A dict that overrides the keys, specified via command-line input.
 
   Returns:
@@ -268,11 +283,11 @@
     if apex not in keys_info:
       logger.warning('Failed to find %s in target_files; Ignored', apex)
       continue
-    keys_info[apex] = (key, keys_info[apex][1])
+    keys_info[apex] = (key, keys_info[apex][1], keys_info[apex][2])
 
   # Apply the key remapping to container keys.
-  for apex, (payload_key, container_key) in keys_info.items():
-    keys_info[apex] = (payload_key, key_map.get(container_key, container_key))
+  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
+    keys_info[apex] = (payload_key, key_map.get(container_key, container_key), sign_tool)
 
   # Apply all the --extra_apks options to override the container keys.
   for apex, key in OPTIONS.extra_apks.items():
@@ -281,13 +296,13 @@
       continue
     if not key:
       key = 'PRESIGNED'
-    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key))
+    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key), keys_info[apex][2])
 
   # A PRESIGNED container entails a PRESIGNED payload. Apply this to all the
   # APEX key pairs. However, a PRESIGNED container with non-PRESIGNED payload
   # (overridden via commandline) indicates a config error, which should not be
   # allowed.
-  for apex, (payload_key, container_key) in keys_info.items():
+  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
     if container_key != 'PRESIGNED':
       continue
     if apex in OPTIONS.extra_apex_payload_keys:
@@ -299,7 +314,7 @@
       print(
           "Setting {} payload as PRESIGNED due to PRESIGNED container".format(
               apex))
-    keys_info[apex] = ('PRESIGNED', 'PRESIGNED')
+    keys_info[apex] = ('PRESIGNED', 'PRESIGNED', None)
 
   return keys_info
 
@@ -360,7 +375,7 @@
     compressed_extension: The extension string of compressed APKs, such as
         '.gz', or None if there's no compressed APKs.
     apex_keys: A dict that contains the key mapping from APEX name to
-        (payload_key, container_key).
+        (payload_key, container_key, sign_tool).
 
   Raises:
     AssertionError: On finding unknown APKs and APEXes.
@@ -405,7 +420,7 @@
 
     name = GetApexFilename(info.filename)
 
-    (payload_key, container_key) = apex_keys[name]
+    (payload_key, container_key, _) = apex_keys[name]
     if ((payload_key in common.SPECIAL_CERT_STRINGS and
          container_key not in common.SPECIAL_CERT_STRINGS) or
         (payload_key not in common.SPECIAL_CERT_STRINGS and
@@ -507,9 +522,14 @@
                        compressed_extension):
   # maxsize measures the maximum filename length, including the ones to be
   # skipped.
-  maxsize = max(
-      [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
-       if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
+  try:
+    maxsize = max(
+        [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
+         if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
+  except ValueError:
+    # Sets this to zero for targets without APK files, e.g., gki_arm64.
+    maxsize = 0
+
   system_root_image = misc_info.get("system_root_image") == "true"
 
   for info in input_tf_zip.infolist():
@@ -557,7 +577,7 @@
     elif IsApexFile(filename):
       name = GetApexFilename(filename)
 
-      payload_key, container_key = apex_keys[name]
+      payload_key, container_key, sign_tool = apex_keys[name]
 
       # We've asserted not having a case with only one of them PRESIGNED.
       if (payload_key not in common.SPECIAL_CERT_STRINGS and
@@ -576,7 +596,8 @@
             apk_keys,
             codename_to_api_level_map,
             no_hashtree=None,  # Let apex_util determine if hash tree is needed
-            signing_args=OPTIONS.avb_extra_args.get('apex'))
+            signing_args=OPTIONS.avb_extra_args.get('apex'),
+            sign_tool=sign_tool)
         common.ZipWrite(output_tf_zip, signed_apex, filename)
 
       else:
@@ -595,7 +616,7 @@
       common.ZipWriteStr(output_tf_zip, out_info, new_data)
 
     # Replace the certs in *mac_permissions.xml (there could be multiple, such
-    # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
+    # as {system,vendor}/etc/selinux/{plat,vendor}_mac_permissions.xml).
     elif filename.endswith("mac_permissions.xml"):
       print("Rewriting %s with new keys." % (filename,))
       new_data = ReplaceCerts(data.decode())
@@ -658,7 +679,7 @@
     # Updates system_other.avbpubkey in /product/etc/.
     elif filename in (
         "PRODUCT/etc/security/avb/system_other.avbpubkey",
-            "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
+        "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
       # Only update system_other's public key, if the corresponding signing
       # key is specified via --avb_system_other_key.
       signing_key = OPTIONS.avb_keys.get("system_other")
@@ -668,12 +689,55 @@
         print("    Rewriting AVB public key of system_other in /product")
         common.ZipWrite(output_tf_zip, public_key, filename)
 
+    # Updates pvmfw embedded public key with the virt APEX payload key.
+    elif filename == "PREBUILT_IMAGES/pvmfw.img":
+      # Find the name of the virt APEX in the target files.
+      namelist = input_tf_zip.namelist()
+      apex_gen = (GetApexFilename(f) for f in namelist if IsApexFile(f))
+      virt_apex_re = re.compile("^com\.([^\.]+\.)?android\.virt\.apex$")
+      virt_apex = next((a for a in apex_gen if virt_apex_re.match(a)), None)
+      if not virt_apex:
+        print("Removing %s from ramdisk: virt APEX not found" % filename)
+      else:
+        print("Replacing %s embedded key with %s key" % (filename, virt_apex))
+        # Get the current and new embedded keys.
+        payload_key, container_key, sign_tool = apex_keys[virt_apex]
+        new_pubkey_path = common.ExtractAvbPublicKey(
+            misc_info['avb_avbtool'], payload_key)
+        with open(new_pubkey_path, 'rb') as f:
+          new_pubkey = f.read()
+        pubkey_info = copy.copy(
+            input_tf_zip.getinfo("PREBUILT_IMAGES/pvmfw_embedded.avbpubkey"))
+        old_pubkey = input_tf_zip.read(pubkey_info.filename)
+        # Validate the keys and image.
+        if len(old_pubkey) != len(new_pubkey):
+          raise common.ExternalError("pvmfw embedded public key size mismatch")
+        pos = data.find(old_pubkey)
+        if pos == -1:
+          raise common.ExternalError("pvmfw embedded public key not found")
+        # Replace the key and copy new files.
+        new_data = data[:pos] + new_pubkey + data[pos+len(old_pubkey):]
+        common.ZipWriteStr(output_tf_zip, out_info, new_data)
+        common.ZipWriteStr(output_tf_zip, pubkey_info, new_pubkey)
+    elif filename == "PREBUILT_IMAGES/pvmfw_embedded.avbpubkey":
+      pass
+
     # Should NOT sign boot-debug.img.
     elif filename in (
         "BOOT/RAMDISK/force_debuggable",
-            "BOOT/RAMDISK/first_stage_ramdisk/force_debuggable"):
+        "BOOT/RAMDISK/first_stage_ramdisk/force_debuggable"):
       raise common.ExternalError("debuggable boot.img cannot be signed")
 
+    # Should NOT sign userdebug sepolicy file.
+    elif filename in (
+        "SYSTEM_EXT/etc/selinux/userdebug_plat_sepolicy.cil",
+        "SYSTEM/system_ext/etc/selinux/userdebug_plat_sepolicy.cil"):
+      if not OPTIONS.allow_gsi_debug_sepolicy:
+        raise common.ExternalError("debug sepolicy shouldn't be included")
+      else:
+        # Copy it verbatim if we allow the file to exist.
+        common.ZipWriteStr(output_tf_zip, out_info, data)
+
     # A non-APK file; copy it verbatim.
     else:
       common.ZipWriteStr(output_tf_zip, out_info, data)
@@ -859,14 +923,27 @@
   except KeyError:
     raise common.ExternalError("can't read META/otakeys.txt from input")
 
-  extra_recovery_keys = misc_info.get("extra_recovery_keys")
-  if extra_recovery_keys:
+  extra_ota_keys_info = misc_info.get("extra_ota_keys")
+  if extra_ota_keys_info:
+    extra_ota_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
+                      for k in extra_ota_keys_info.split()]
+    print("extra ota key(s): " + ", ".join(extra_ota_keys))
+  else:
+    extra_ota_keys = []
+  for k in extra_ota_keys:
+    if not os.path.isfile(k):
+      raise common.ExternalError(k + " does not exist or is not a file")
+
+  extra_recovery_keys_info = misc_info.get("extra_recovery_keys")
+  if extra_recovery_keys_info:
     extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
-                           for k in extra_recovery_keys.split()]
-    if extra_recovery_keys:
-      print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
+                           for k in extra_recovery_keys_info.split()]
+    print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
   else:
     extra_recovery_keys = []
+  for k in extra_recovery_keys:
+    if not os.path.isfile(k):
+      raise common.ExternalError(k + " does not exist or is not a file")
 
   mapped_keys = []
   for k in keylist:
@@ -889,13 +966,20 @@
     mapped_keys.append(mapped_devkey + ".x509.pem")
     print("META/otakeys.txt has no keys; using %s for OTA package"
           " verification." % (mapped_keys[0],))
+  for k in mapped_keys:
+    if not os.path.isfile(k):
+      raise common.ExternalError(k + " does not exist or is not a file")
 
   otacerts = [info
               for info in input_tf_zip.infolist()
               if info.filename.endswith("/otacerts.zip")]
   for info in otacerts:
-    print("Rewriting OTA key:", info.filename, mapped_keys)
-    WriteOtacerts(output_tf_zip, info.filename, mapped_keys)
+    if info.filename.startswith(("BOOT/", "RECOVERY/", "VENDOR_BOOT/")):
+      extra_keys = extra_recovery_keys
+    else:
+      extra_keys = extra_ota_keys
+    print("Rewriting OTA key:", info.filename, mapped_keys + extra_keys)
+    WriteOtacerts(output_tf_zip, info.filename, mapped_keys + extra_keys)
 
 
 def ReplaceVerityPublicKey(output_zip, filename, key_path):
@@ -1125,15 +1209,16 @@
 
   Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
   dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
-  tuple of (payload_key, container_key).
+  tuple of (payload_key, container_key, sign_tool).
 
   Args:
     tf_zip: The input target_files ZipFile (already open).
 
   Returns:
-    (payload_key, container_key): payload_key contains the path to the payload
-        signing key; container_key contains the path to the container signing
-        key.
+    (payload_key, container_key, sign_tool):
+      - payload_key contains the path to the payload signing key
+      - container_key contains the path to the container signing key
+      - sign_tool is an apex-specific signing tool for its payload contents
   """
   keys = {}
   for line in tf_zip.read('META/apexkeys.txt').decode().split('\n'):
@@ -1146,7 +1231,8 @@
         r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
         r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
         r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*?)"'
-        r'(\s+partition="(?P<PARTITION>.*?)")?$',
+        r'(\s+partition="(?P<PARTITION>.*?)")?'
+        r'(\s+sign_tool="(?P<SIGN_TOOL>.*?)")?$',
         line)
     if not matches:
       continue
@@ -1175,11 +1261,113 @@
     else:
       raise ValueError("Failed to parse container keys: \n{}".format(line))
 
-    keys[name] = (payload_private_key, container_key)
+    sign_tool = matches.group("SIGN_TOOL")
+    keys[name] = (payload_private_key, container_key, sign_tool)
 
   return keys
 
 
+def BuildVendorPartitions(output_zip_path):
+  """Builds OPTIONS.vendor_partitions using OPTIONS.vendor_otatools."""
+  if OPTIONS.vendor_partitions.difference(ALLOWED_VENDOR_PARTITIONS):
+    logger.warning("Allowed --vendor_partitions: %s",
+                   ",".join(ALLOWED_VENDOR_PARTITIONS))
+    OPTIONS.vendor_partitions = ALLOWED_VENDOR_PARTITIONS.intersection(
+        OPTIONS.vendor_partitions)
+
+  logger.info("Building vendor partitions using vendor otatools.")
+  vendor_tempdir = common.UnzipTemp(output_zip_path, [
+      "META/*",
+      "SYSTEM/build.prop",
+      "RECOVERY/*",
+      "BOOT/*",
+      "OTA/",
+  ] + ["{}/*".format(p.upper()) for p in OPTIONS.vendor_partitions])
+
+  # Disable various partitions that build based on misc_info fields.
+  # Only partitions in ALLOWED_VENDOR_PARTITIONS can be rebuilt using
+  # vendor otatools. These other partitions will be rebuilt using the main
+  # otatools if necessary.
+  vendor_misc_info_path = os.path.join(vendor_tempdir, "META/misc_info.txt")
+  vendor_misc_info = common.LoadDictionaryFromFile(vendor_misc_info_path)
+  # Ignore if not rebuilding recovery
+  if not OPTIONS.rebuild_recovery:
+    vendor_misc_info["no_boot"] = "true"  # boot
+    vendor_misc_info["vendor_boot"] = "false"  # vendor_boot
+    vendor_misc_info["no_recovery"] = "true"  # recovery
+    vendor_misc_info["avb_enable"] = "false"  # vbmeta
+
+  vendor_misc_info["board_bpt_enable"] = "false"  # partition-table
+  vendor_misc_info["has_dtbo"] = "false"  # dtbo
+  vendor_misc_info["has_pvmfw"] = "false"  # pvmfw
+  vendor_misc_info["avb_custom_images_partition_list"] = ""  # custom images
+  vendor_misc_info["avb_building_vbmeta_image"] = "false" # skip building vbmeta
+  vendor_misc_info["use_dynamic_partitions"] = "false"  # super_empty
+  vendor_misc_info["build_super_partition"] = "false"  # super split
+  with open(vendor_misc_info_path, "w") as output:
+    for key in sorted(vendor_misc_info):
+      output.write("{}={}\n".format(key, vendor_misc_info[key]))
+
+  # Disable system partition by a placeholder of IMAGES/system.img,
+  # instead of removing SYSTEM folder.
+  # Because SYSTEM/build.prop is still needed for:
+  #   add_img_to_target_files.CreateImage ->
+  #   common.BuildInfo ->
+  #   common.BuildInfo.CalculateFingerprint
+  vendor_images_path = os.path.join(vendor_tempdir, "IMAGES")
+  if not os.path.exists(vendor_images_path):
+    os.makedirs(vendor_images_path)
+  with open(os.path.join(vendor_images_path, "system.img"), "w") as output:
+    pass
+
+  # Disable care_map.pb as not all ab_partitions are available when
+  # vendor otatools regenerates vendor images.
+  if os.path.exists(os.path.join(vendor_tempdir, "META/ab_partitions.txt")):
+    os.remove(os.path.join(vendor_tempdir, "META/ab_partitions.txt"))
+  # Disable RADIO images
+  if os.path.exists(os.path.join(vendor_tempdir, "META/pack_radioimages.txt")):
+    os.remove(os.path.join(vendor_tempdir, "META/pack_radioimages.txt"))
+
+  # Build vendor images using vendor otatools.
+  # Accept either a zip file or extracted directory.
+  if os.path.isfile(OPTIONS.vendor_otatools):
+    vendor_otatools_dir = common.MakeTempDir(prefix="vendor_otatools_")
+    common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
+  else:
+    vendor_otatools_dir = OPTIONS.vendor_otatools
+  cmd = [
+      os.path.join(vendor_otatools_dir, "bin", "add_img_to_target_files"),
+      "--is_signing",
+      "--add_missing",
+      "--verbose",
+      vendor_tempdir,
+  ]
+  if OPTIONS.rebuild_recovery:
+    cmd.insert(4, "--rebuild_recovery")
+
+  common.RunAndCheckOutput(cmd, verbose=True)
+
+  logger.info("Writing vendor partitions to output archive.")
+  with zipfile.ZipFile(
+      output_zip_path, "a", compression=zipfile.ZIP_DEFLATED,
+      allowZip64=True) as output_zip:
+    for p in OPTIONS.vendor_partitions:
+      img_file_path = "IMAGES/{}.img".format(p)
+      map_file_path = "IMAGES/{}.map".format(p)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, img_file_path), img_file_path)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, map_file_path), map_file_path)
+    # copy recovery.img, boot.img, recovery patch & install.sh
+    if OPTIONS.rebuild_recovery:
+      recovery_img = "IMAGES/recovery.img"
+      boot_img = "IMAGES/boot.img"
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_img), recovery_img)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, boot_img), boot_img)
+      recovery_patch_path = "VENDOR/recovery-from-boot.p"
+      recovery_sh_path = "VENDOR/bin/install-recovery.sh"
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_patch_path), recovery_patch_path)
+      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_sh_path), recovery_sh_path)
+
+
 def main(argv):
 
   key_mapping_options = []
@@ -1239,6 +1427,12 @@
       OPTIONS.avb_algorithms['dtbo'] = a
     elif o == "--avb_dtbo_extra_args":
       OPTIONS.avb_extra_args['dtbo'] = a
+    elif o == "--avb_recovery_key":
+      OPTIONS.avb_keys['recovery'] = a
+    elif o == "--avb_recovery_algorithm":
+      OPTIONS.avb_algorithms['recovery'] = a
+    elif o == "--avb_recovery_extra_args":
+      OPTIONS.avb_extra_args['recovery'] = a
     elif o == "--avb_system_key":
       OPTIONS.avb_keys['system'] = a
     elif o == "--avb_system_algorithm":
@@ -1289,6 +1483,12 @@
       OPTIONS.gki_signing_algorithm = a
     elif o == "--gki_signing_extra_args":
       OPTIONS.gki_signing_extra_args = a
+    elif o == "--vendor_otatools":
+      OPTIONS.vendor_otatools = a
+    elif o == "--vendor_partitions":
+      OPTIONS.vendor_partitions = set(a.split(","))
+    elif o == "--allow_gsi_debug_sepolicy":
+      OPTIONS.allow_gsi_debug_sepolicy = True
     else:
       return False
     return True
@@ -1318,6 +1518,9 @@
           "avb_dtbo_algorithm=",
           "avb_dtbo_key=",
           "avb_dtbo_extra_args=",
+          "avb_recovery_algorithm=",
+          "avb_recovery_key=",
+          "avb_recovery_extra_args=",
           "avb_system_algorithm=",
           "avb_system_key=",
           "avb_system_extra_args=",
@@ -1339,6 +1542,9 @@
           "gki_signing_key=",
           "gki_signing_algorithm=",
           "gki_signing_extra_args=",
+          "vendor_partitions=",
+          "vendor_otatools=",
+          "allow_gsi_debug_sepolicy",
       ],
       extra_option_handler=option_handler)
 
@@ -1384,8 +1590,11 @@
   common.ZipClose(input_zip)
   common.ZipClose(output_zip)
 
+  if OPTIONS.vendor_partitions and OPTIONS.vendor_otatools:
+    BuildVendorPartitions(args[1])
+
   # Skip building userdata.img and cache.img when signing the target files.
-  new_args = ["--is_signing"]
+  new_args = ["--is_signing", "--add_missing", "--verbose"]
   # add_img_to_target_files builds the system image from scratch, so the
   # recovery patch is guaranteed to be regenerated there.
   if OPTIONS.rebuild_recovery:
@@ -1399,8 +1608,5 @@
 if __name__ == '__main__':
   try:
     main(sys.argv[1:])
-  except common.ExternalError as e:
-    print("\n   ERROR: %s\n" % (e,))
-    raise
   finally:
     common.Cleanup()
diff --git a/tools/releasetools/target_files_diff.py b/tools/releasetools/target_files_diff.py
index 4402c8d..fa94c5b 100755
--- a/tools/releasetools/target_files_diff.py
+++ b/tools/releasetools/target_files_diff.py
@@ -82,7 +82,7 @@
         skip = True
         break
     if not skip:
-      new.write(line)
+      new.write(line.encode())
 
 
 def trim_install_recovery(original, new):
@@ -91,7 +91,7 @@
   partition.
   """
   for line in original:
-    new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
+    new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line).encode())
 
 def sort_file(original, new):
   """
@@ -101,7 +101,7 @@
   lines = original.readlines()
   lines.sort()
   for line in lines:
-    new.write(line)
+    new.write(line.encode())
 
 # Map files to the functions that will modify them for diffing
 REWRITE_RULES = {
@@ -148,7 +148,7 @@
       if stdout == 'Binary files %s and %s differ' % (f1, f2):
         print("%s: Binary files differ" % name, file=out_file)
       else:
-        for line in stdout.strip().split('\n'):
+        for line in stdout.strip().split(b'\n'):
           print("%s: %s" % (name, line), file=out_file)
 
 def recursiveDiff(prefix, dir1, dir2, out_file):
diff --git a/tools/releasetools/test_apex_utils.py b/tools/releasetools/test_apex_utils.py
index 71f6433..2aa6f6c 100644
--- a/tools/releasetools/test_apex_utils.py
+++ b/tools/releasetools/test_apex_utils.py
@@ -187,3 +187,20 @@
 
     self.payload_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key')
     signer.ProcessApexFile(apk_keys, self.payload_key)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_ApexApkSigner_invokesCustomSignTool(self):
+    apex_path = common.MakeTempFile(suffix='.apex')
+    shutil.copy(self.apex_with_apk, apex_path)
+    apk_keys = {'wifi-service-resources.apk': os.path.join(
+        self.testdata_dir, 'testkey')}
+    self.payload_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key')
+
+    # pass `false` as a sign_tool to see the invocation error
+    with self.assertRaises(common.ExternalError) as cm:
+      signer = apex_utils.ApexApkSigner(
+          apex_path, None, None, sign_tool='false')
+      signer.ProcessApexFile(apk_keys, self.payload_key)
+
+    the_exception = cm.exception
+    self.assertIn('Failed to run command \'[\'false\'', str(the_exception))
diff --git a/tools/releasetools/test_build_image.py b/tools/releasetools/test_build_image.py
index b24805f..cfae7a5 100644
--- a/tools/releasetools/test_build_image.py
+++ b/tools/releasetools/test_build_image.py
@@ -196,7 +196,7 @@
     p.communicate()
     self.assertEqual(0, p.returncode)
 
-    fs_dict = GetFilesystemCharacteristics(output_file)
+    fs_dict = GetFilesystemCharacteristics('ext4', output_file)
     self.assertEqual(int(fs_dict['Block size']), 4096)
     self.assertGreaterEqual(int(fs_dict['Free blocks']), 0) # expect ~88
     self.assertGreater(int(fs_dict['Inode count']), 0)      # expect ~64
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 1a00549..f973263 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1631,148 +1631,7 @@
     self.assertEqual('3', chained_partition_args[1])
     self.assertTrue(os.path.exists(chained_partition_args[2]))
 
-  def test_BuildVBMeta_appendAftlCommandSyntax(self):
-    testdata_dir = test_utils.get_testdata_dir()
-    common.OPTIONS.info_dict = {
-        'ab_update': 'true',
-        'avb_avbtool': 'avbtool',
-        'build.prop': common.PartitionBuildProps.FromDictionary(
-            'system', {
-                'ro.build.version.incremental': '6285659',
-                'ro.product.device': 'coral',
-                'ro.build.fingerprint':
-                'google/coral/coral:R/RP1A.200311.002/'
-                '6285659:userdebug/dev-keys'}
-        ),
-    }
-    common.OPTIONS.aftl_tool_path = 'aftltool'
-    common.OPTIONS.aftl_server = 'log.endpoints.aftl-dev.cloud.goog:9000'
-    common.OPTIONS.aftl_key_path = os.path.join(testdata_dir,
-                                                'test_transparency_key.pub')
-    common.OPTIONS.aftl_manufacturer_key_path = os.path.join(
-        testdata_dir, 'test_aftl_rsa4096.pem')
-
-    vbmeta_image = tempfile.NamedTemporaryFile(delete=False)
-    cmd = common.ConstructAftlMakeImageCommands(vbmeta_image.name)
-    expected_cmd = [
-        'aftltool', 'make_icp_from_vbmeta',
-        '--vbmeta_image_path', 'place_holder',
-        '--output', vbmeta_image.name,
-        '--version_incremental', '6285659',
-        '--transparency_log_servers',
-        'log.endpoints.aftl-dev.cloud.goog:9000,{}'.format(
-            common.OPTIONS.aftl_key_path),
-        '--manufacturer_key', common.OPTIONS.aftl_manufacturer_key_path,
-        '--algorithm', 'SHA256_RSA4096',
-        '--padding', '4096']
-
-    # ignore the place holder, i.e. path to a temp file
-    self.assertEqual(cmd[:3], expected_cmd[:3])
-    self.assertEqual(cmd[4:], expected_cmd[4:])
-
-  @unittest.skip("enable after we have a server for public")
-  def test_BuildVBMeta_appendAftlContactServer(self):
-    testdata_dir = test_utils.get_testdata_dir()
-    common.OPTIONS.info_dict = {
-        'ab_update': 'true',
-        'avb_avbtool': 'avbtool',
-        'build.prop': common.PartitionBuildProps.FromDictionary(
-            'system', {
-                'ro.build.version.incremental': '6285659',
-                'ro.product.device': 'coral',
-                'ro.build.fingerprint':
-                'google/coral/coral:R/RP1A.200311.002/'
-                '6285659:userdebug/dev-keys'}
-        )
-    }
-    common.OPTIONS.aftl_tool_path = "aftltool"
-    common.OPTIONS.aftl_server = "log.endpoints.aftl-dev.cloud.goog:9000"
-    common.OPTIONS.aftl_key_path = os.path.join(testdata_dir,
-                                                'test_transparency_key.pub')
-    common.OPTIONS.aftl_manufacturer_key_path = os.path.join(
-        testdata_dir, 'test_aftl_rsa4096.pem')
-
-    input_dir = common.MakeTempDir()
-    system_image = common.MakeTempFile()
-    build_image_cmd = ['mkuserimg_mke2fs', input_dir, system_image, 'ext4',
-                       '/system', str(4096 * 100), '-j', '0', '-s']
-    common.RunAndCheckOutput(build_image_cmd)
-
-    add_footer_cmd = ['avbtool', 'add_hashtree_footer',
-                      '--partition_size', str(4096 * 150),
-                      '--partition_name', 'system',
-                      '--image', system_image]
-    common.RunAndCheckOutput(add_footer_cmd)
-
-    vbmeta_image = common.MakeTempFile()
-    common.BuildVBMeta(vbmeta_image, {'system': system_image}, 'vbmeta',
-                       ['system'])
-
-    verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
-                  vbmeta_image, '--transparency_log_pub_keys',
-                  common.OPTIONS.aftl_key_path]
-    common.RunAndCheckOutput(verify_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_NoSigningKeyPath(self):
-    # A non-GKI boot.img has no gki_signing_key_path.
-    common.OPTIONS.info_dict = {
-        # 'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-
-    # Tests no --gki_signing_* args are appended if there is no
-    # gki_signing_key_path.
-    cmd = ['mkbootimg', '--header_version', '4']
-    expected_cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-    self.assertEqual(cmd, expected_cmd)
-
-  def test_AppendGkiSigningArgs_NoSigningAlgorithm(self):
-    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
-    with open(pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(pubkey))
-
-    # Tests no --gki_signing_* args are appended if there is no
-    # gki_signing_algorithm.
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        # 'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-
-    cmd = ['mkbootimg', '--header_version', '4']
-    expected_cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs(self):
-    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
-    with open(pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(pubkey))
-
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-    cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-
-    expected_cmd = [
-      'mkbootimg', '--header_version', '4',
-      '--gki_signing_key', pubkey,
-      '--gki_signing_algorithm', 'SHA256_RSA4096',
-      '--gki_signing_signature_args', '--prop foo:bar'
-    ]
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_KeyPathNotFound(self):
+  def test_GenerateGkiCertificate_KeyPathNotFound(self):
     pubkey = os.path.join(self.testdata_dir, 'no_testkey_gki.pem')
     self.assertFalse(os.path.exists(pubkey))
 
@@ -1781,41 +1640,11 @@
         'gki_signing_algorithm': 'SHA256_RSA4096',
         'gki_signing_signature_args': '--prop foo:bar',
     }
-    cmd = ['mkbootimg', '--header_version', '4']
-    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
+    test_file = tempfile.NamedTemporaryFile()
+    self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+                      test_file.name, 'generic_kernel')
 
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_SearchKeyPath(self):
-    pubkey = 'testkey_gki.pem'
-    self.assertFalse(os.path.exists(pubkey))
-
-    # Tests it should replace the pubkey with an existed key under
-    # OPTIONS.search_path, i.e., os.path.join(OPTIONS.search_path, pubkey).
-    search_path_dir = common.MakeTempDir()
-    search_pubkey = os.path.join(search_path_dir, pubkey)
-    with open(search_pubkey, 'wb') as f:
-      f.write(b'\x00' * 100)
-    self.assertTrue(os.path.exists(search_pubkey))
-
-    common.OPTIONS.search_path = search_path_dir
-    common.OPTIONS.info_dict = {
-        'gki_signing_key_path': pubkey,
-        'gki_signing_algorithm': 'SHA256_RSA4096',
-        'gki_signing_signature_args': '--prop foo:bar',
-    }
-    cmd = ['mkbootimg', '--header_version', '4']
-    common.AppendGkiSigningArgs(cmd)
-
-    expected_cmd = [
-      'mkbootimg', '--header_version', '4',
-      '--gki_signing_key', search_pubkey,
-      '--gki_signing_algorithm', 'SHA256_RSA4096',
-      '--gki_signing_signature_args', '--prop foo:bar'
-    ]
-    self.assertEqual(cmd, expected_cmd)
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_AppendGkiSigningArgs_SearchKeyPathNotFound(self):
+  def test_GenerateGkiCertificate_SearchKeyPathNotFound(self):
     pubkey = 'no_testkey_gki.pem'
     self.assertFalse(os.path.exists(pubkey))
 
@@ -1831,9 +1660,9 @@
         'gki_signing_algorithm': 'SHA256_RSA4096',
         'gki_signing_signature_args': '--prop foo:bar',
     }
-    cmd = ['mkbootimg', '--header_version', '4']
-    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
-
+    test_file = tempfile.NamedTemporaryFile()
+    self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+                      test_file.name, 'generic_kernel')
 
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.
diff --git a/tools/releasetools/test_merge_target_files.py b/tools/releasetools/test_merge_target_files.py
deleted file mode 100644
index 4f61472..0000000
--- a/tools/releasetools/test_merge_target_files.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-import os.path
-import shutil
-
-import common
-import test_utils
-from merge_target_files import (
-    validate_config_lists, DEFAULT_FRAMEWORK_ITEM_LIST,
-    DEFAULT_VENDOR_ITEM_LIST, DEFAULT_FRAMEWORK_MISC_INFO_KEYS, copy_items,
-    item_list_to_partition_set, process_apex_keys_apk_certs_common,
-    compile_split_sepolicy, validate_merged_apex_info)
-
-
-class MergeTargetFilesTest(test_utils.ReleaseToolsTestCase):
-
-  def setUp(self):
-    self.testdata_dir = test_utils.get_testdata_dir()
-
-  def test_copy_items_CopiesItemsMatchingPatterns(self):
-
-    def createEmptyFile(path):
-      if not os.path.exists(os.path.dirname(path)):
-        os.makedirs(os.path.dirname(path))
-      open(path, 'a').close()
-      return path
-
-    def createSymLink(source, dest):
-      os.symlink(source, dest)
-      return dest
-
-    def getRelPaths(start, filepaths):
-      return set(
-          os.path.relpath(path=filepath, start=start) for filepath in filepaths)
-
-    input_dir = common.MakeTempDir()
-    output_dir = common.MakeTempDir()
-    expected_copied_items = []
-    actual_copied_items = []
-    patterns = ['*.cpp', 'subdir/*.txt']
-
-    # Create various files that we expect to get copied because they
-    # match one of the patterns.
-    expected_copied_items.extend([
-        createEmptyFile(os.path.join(input_dir, 'a.cpp')),
-        createEmptyFile(os.path.join(input_dir, 'b.cpp')),
-        createEmptyFile(os.path.join(input_dir, 'subdir', 'c.txt')),
-        createEmptyFile(os.path.join(input_dir, 'subdir', 'd.txt')),
-        createEmptyFile(
-            os.path.join(input_dir, 'subdir', 'subsubdir', 'e.txt')),
-        createSymLink('a.cpp', os.path.join(input_dir, 'a_link.cpp')),
-    ])
-    # Create some more files that we expect to not get copied.
-    createEmptyFile(os.path.join(input_dir, 'a.h'))
-    createEmptyFile(os.path.join(input_dir, 'b.h'))
-    createEmptyFile(os.path.join(input_dir, 'subdir', 'subsubdir', 'f.gif'))
-    createSymLink('a.h', os.path.join(input_dir, 'a_link.h'))
-
-    # Copy items.
-    copy_items(input_dir, output_dir, patterns)
-
-    # Assert the actual copied items match the ones we expected.
-    for dirpath, _, filenames in os.walk(output_dir):
-      actual_copied_items.extend(
-          os.path.join(dirpath, filename) for filename in filenames)
-    self.assertEqual(
-        getRelPaths(output_dir, actual_copied_items),
-        getRelPaths(input_dir, expected_copied_items))
-    self.assertEqual(
-        os.readlink(os.path.join(output_dir, 'a_link.cpp')), 'a.cpp')
-
-  def test_validate_config_lists_ReturnsFalseIfMissingDefaultItem(self):
-    framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    framework_item_list.remove('SYSTEM/*')
-    self.assertFalse(
-        validate_config_lists(framework_item_list,
-                              DEFAULT_FRAMEWORK_MISC_INFO_KEYS,
-                              DEFAULT_VENDOR_ITEM_LIST))
-
-  def test_validate_config_lists_ReturnsTrueIfDefaultItemInDifferentList(self):
-    framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    framework_item_list.remove('ROOT/*')
-    vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    vendor_item_list.append('ROOT/*')
-    self.assertTrue(
-        validate_config_lists(framework_item_list,
-                              DEFAULT_FRAMEWORK_MISC_INFO_KEYS,
-                              vendor_item_list))
-
-  def test_validate_config_lists_ReturnsTrueIfExtraItem(self):
-    framework_item_list = list(DEFAULT_FRAMEWORK_ITEM_LIST)
-    framework_item_list.append('MY_NEW_PARTITION/*')
-    self.assertTrue(
-        validate_config_lists(framework_item_list,
-                              DEFAULT_FRAMEWORK_MISC_INFO_KEYS,
-                              DEFAULT_VENDOR_ITEM_LIST))
-
-  def test_validate_config_lists_ReturnsFalseIfSharedExtractedPartition(self):
-    vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    vendor_item_list.append('SYSTEM/my_system_file')
-    self.assertFalse(
-        validate_config_lists(DEFAULT_FRAMEWORK_ITEM_LIST,
-                              DEFAULT_FRAMEWORK_MISC_INFO_KEYS,
-                              vendor_item_list))
-
-  def test_validate_config_lists_ReturnsFalseIfSharedExtractedPartitionImage(
-      self):
-    vendor_item_list = list(DEFAULT_VENDOR_ITEM_LIST)
-    vendor_item_list.append('IMAGES/system.img')
-    self.assertFalse(
-        validate_config_lists(DEFAULT_FRAMEWORK_ITEM_LIST,
-                              DEFAULT_FRAMEWORK_MISC_INFO_KEYS,
-                              vendor_item_list))
-
-  def test_validate_config_lists_ReturnsFalseIfBadSystemMiscInfoKeys(self):
-    for bad_key in ['dynamic_partition_list', 'super_partition_groups']:
-      framework_misc_info_keys = list(DEFAULT_FRAMEWORK_MISC_INFO_KEYS)
-      framework_misc_info_keys.append(bad_key)
-      self.assertFalse(
-          validate_config_lists(DEFAULT_FRAMEWORK_ITEM_LIST,
-                                framework_misc_info_keys,
-                                DEFAULT_VENDOR_ITEM_LIST))
-
-  def test_process_apex_keys_apk_certs_ReturnsTrueIfNoConflicts(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'META'))
-
-    framework_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(framework_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
-        os.path.join(framework_dir, 'META', 'apexkeys.txt'))
-
-    vendor_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(vendor_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_vendor.txt'),
-        os.path.join(vendor_dir, 'META', 'apexkeys.txt'))
-
-    process_apex_keys_apk_certs_common(framework_dir, vendor_dir, output_dir,
-                                       set(['product', 'system', 'system_ext']),
-                                       set(['odm', 'vendor']), 'apexkeys.txt')
-
-    merged_entries = []
-    merged_path = os.path.join(self.testdata_dir, 'apexkeys_merge.txt')
-
-    with open(merged_path) as f:
-      merged_entries = f.read().split('\n')
-
-    output_entries = []
-    output_path = os.path.join(output_dir, 'META', 'apexkeys.txt')
-
-    with open(output_path) as f:
-      output_entries = f.read().split('\n')
-
-    return self.assertEqual(merged_entries, output_entries)
-
-  def test_process_apex_keys_apk_certs_ReturnsFalseIfConflictsPresent(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'META'))
-
-    framework_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(framework_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework.txt'),
-        os.path.join(framework_dir, 'META', 'apexkeys.txt'))
-
-    conflict_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(conflict_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apexkeys_framework_conflict.txt'),
-        os.path.join(conflict_dir, 'META', 'apexkeys.txt'))
-
-    self.assertRaises(ValueError, process_apex_keys_apk_certs_common,
-                      framework_dir, conflict_dir, output_dir,
-                      set(['product', 'system', 'system_ext']),
-                      set(['odm', 'vendor']), 'apexkeys.txt')
-
-  def test_process_apex_keys_apk_certs_HandlesApkCertsSyntax(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'META'))
-
-    framework_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(framework_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apkcerts_framework.txt'),
-        os.path.join(framework_dir, 'META', 'apkcerts.txt'))
-
-    vendor_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(vendor_dir, 'META'))
-    os.symlink(
-        os.path.join(self.testdata_dir, 'apkcerts_vendor.txt'),
-        os.path.join(vendor_dir, 'META', 'apkcerts.txt'))
-
-    process_apex_keys_apk_certs_common(framework_dir, vendor_dir, output_dir,
-                                       set(['product', 'system', 'system_ext']),
-                                       set(['odm', 'vendor']), 'apkcerts.txt')
-
-    merged_entries = []
-    merged_path = os.path.join(self.testdata_dir, 'apkcerts_merge.txt')
-
-    with open(merged_path) as f:
-      merged_entries = f.read().split('\n')
-
-    output_entries = []
-    output_path = os.path.join(output_dir, 'META', 'apkcerts.txt')
-
-    with open(output_path) as f:
-      output_entries = f.read().split('\n')
-
-    return self.assertEqual(merged_entries, output_entries)
-
-  def test_item_list_to_partition_set(self):
-    item_list = [
-        'META/apexkeys.txt',
-        'META/apkcerts.txt',
-        'META/filesystem_config.txt',
-        'PRODUCT/*',
-        'SYSTEM/*',
-        'SYSTEM_EXT/*',
-    ]
-    partition_set = item_list_to_partition_set(item_list)
-    self.assertEqual(set(['product', 'system', 'system_ext']), partition_set)
-
-  def test_compile_split_sepolicy(self):
-    product_out_dir = common.MakeTempDir()
-
-    def write_temp_file(path, data=''):
-      full_path = os.path.join(product_out_dir, path)
-      if not os.path.exists(os.path.dirname(full_path)):
-        os.makedirs(os.path.dirname(full_path))
-      with open(full_path, 'w') as f:
-        f.write(data)
-
-    write_temp_file(
-        'system/etc/vintf/compatibility_matrix.device.xml', """
-      <compatibility-matrix>
-        <sepolicy>
-          <kernel-sepolicy-version>30</kernel-sepolicy-version>
-        </sepolicy>
-      </compatibility-matrix>""")
-    write_temp_file('vendor/etc/selinux/plat_sepolicy_vers.txt', '30.0')
-
-    write_temp_file('system/etc/selinux/plat_sepolicy.cil')
-    write_temp_file('system/etc/selinux/mapping/30.0.cil')
-    write_temp_file('product/etc/selinux/mapping/30.0.cil')
-    write_temp_file('vendor/etc/selinux/vendor_sepolicy.cil')
-    write_temp_file('vendor/etc/selinux/plat_pub_versioned.cil')
-
-    cmd = compile_split_sepolicy(product_out_dir, {
-        'system': 'system',
-        'product': 'product',
-        'vendor': 'vendor',
-    }, os.path.join(product_out_dir, 'policy'))
-    self.assertEqual(' '.join(cmd),
-                     ('secilc -m -M true -G -N -c 30 '
-                      '-o {OTP}/policy -f /dev/null '
-                      '{OTP}/system/etc/selinux/plat_sepolicy.cil '
-                      '{OTP}/system/etc/selinux/mapping/30.0.cil '
-                      '{OTP}/vendor/etc/selinux/vendor_sepolicy.cil '
-                      '{OTP}/vendor/etc/selinux/plat_pub_versioned.cil '
-                      '{OTP}/product/etc/selinux/mapping/30.0.cil').format(
-                          OTP=product_out_dir))
-
-  def _copy_apex(self, source, output_dir, partition):
-    shutil.copy(
-        source,
-        os.path.join(output_dir, partition, 'apex', os.path.basename(source)))
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_validate_merged_apex_info(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
-    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
-
-    self._copy_apex(
-        os.path.join(self.testdata_dir, 'has_apk.apex'), output_dir, 'SYSTEM')
-    self._copy_apex(
-        os.path.join(test_utils.get_current_dir(),
-                     'com.android.apex.compressed.v1.capex'), output_dir,
-        'VENDOR')
-    validate_merged_apex_info(output_dir, ('system', 'vendor'))
-
-  @test_utils.SkipIfExternalToolsUnavailable()
-  def test_validate_merged_apex_info_RaisesOnPackageInMultiplePartitions(self):
-    output_dir = common.MakeTempDir()
-    os.makedirs(os.path.join(output_dir, 'SYSTEM/apex'))
-    os.makedirs(os.path.join(output_dir, 'VENDOR/apex'))
-
-    same_apex_package = os.path.join(self.testdata_dir, 'has_apk.apex')
-    self._copy_apex(same_apex_package, output_dir, 'SYSTEM')
-    self._copy_apex(same_apex_package, output_dir, 'VENDOR')
-    self.assertRaisesRegexp(
-        common.ExternalError,
-        'Duplicate APEX packages found in multiple partitions: com.android.wifi',
-        validate_merged_apex_info, output_dir, ('system', 'vendor'))
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index 661712a..11cfee1 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -24,7 +24,7 @@
 import test_utils
 from ota_utils import (
     BuildLegacyOtaMetadata, CalculateRuntimeDevicesAndFingerprints,
-    FinalizeMetadata, GetPackageMetadata, PropertyFiles)
+    ConstructOtaApexInfo, FinalizeMetadata, GetPackageMetadata, PropertyFiles)
 from ota_from_target_files import (
     _LoadOemDicts, AbOtaPropertyFiles,
     GetTargetFilesZipForCustomImagesUpdates,
@@ -295,6 +295,35 @@
     uncompressed_apex_size = os.path.getsize(original_apex_filepath)
     self.assertEqual(apex_infos[0].decompressed_size, uncompressed_apex_size)
 
+  @staticmethod
+  def construct_tf_with_apex_info(infos):
+    apex_metadata_proto = ota_metadata_pb2.ApexMetadata()
+    apex_metadata_proto.apex_info.extend(infos)
+
+    output = common.MakeTempFile(suffix='.zip')
+    with zipfile.ZipFile(output, 'w') as zfp:
+      common.ZipWriteStr(zfp, "META/apex_info.pb",
+                         apex_metadata_proto.SerializeToString())
+    return output
+
+  def test_ConstructOtaApexInfo_incremental_package(self):
+    infos = [ota_metadata_pb2.ApexInfo(package_name='com.android.apex.1',
+                                       version=1000, is_compressed=False),
+             ota_metadata_pb2.ApexInfo(package_name='com.android.apex.2',
+                                       version=2000, is_compressed=True)]
+    target_file = self.construct_tf_with_apex_info(infos)
+
+    with zipfile.ZipFile(target_file) as target_zip:
+      info_bytes = ConstructOtaApexInfo(target_zip, source_file=target_file)
+    apex_metadata_proto = ota_metadata_pb2.ApexMetadata()
+    apex_metadata_proto.ParseFromString(info_bytes)
+
+    info_list = apex_metadata_proto.apex_info
+    self.assertEqual(2, len(info_list))
+    self.assertEqual('com.android.apex.1', info_list[0].package_name)
+    self.assertEqual(1000, info_list[0].version)
+    self.assertEqual(1000, info_list[0].source_version)
+
   def test_GetPackageMetadata_retrofitDynamicPartitions(self):
     target_info = common.BuildInfo(self.TEST_TARGET_INFO_DICT, None)
     common.OPTIONS.retrofit_dynamic_partitions = True
@@ -834,6 +863,7 @@
         property_files.required)
     self.assertEqual(
         (
+            'apex_info.pb',
             'care_map.pb',
             'care_map.txt',
             'compatibility.zip',
@@ -933,6 +963,7 @@
         property_files.required)
     self.assertEqual(
         (
+            'apex_info.pb',
             'care_map.pb',
             'care_map.txt',
             'compatibility.zip',
diff --git a/tools/releasetools/test_ota_utils.py b/tools/releasetools/test_ota_utils.py
new file mode 100644
index 0000000..9a82e6f
--- /dev/null
+++ b/tools/releasetools/test_ota_utils.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import unittest
+import io
+import ota_utils
+import zipfile
+
+
+class TestZipEntryOffset(unittest.TestCase):
+  def test_extra_length_differ(self):
+      # This is a magic zip file such that:
+      # 1. It has 1 entry, `file.txt'`
+      # 2. The central directory entry for the entry contains an extra field of#
+      # length 24, while the local file header for the entry contains an extra#
+      # field of length 28.
+      # It is key that the entry contains extra field of different length.
+      # The sole purpose of this test case is make sure our offset computing
+      # logic works in this scenario.
+
+      # This is created by:
+      # touch file.txt
+      # zip -0 test.zip file.txt
+      # Above command may or may not work on all platforms.
+      # Some zip implementation will keep the extra field size consistent.
+      # Some don't
+    magic_zip = b'PK\x03\x04\n\x00\x00\x00\x00\x00nY\xfcR\x00\x00\x00\x00\x00\x00\x00' +\
+        b'\x00\x00\x00\x00\x00\x08\x00\x1c\x00file.txtUT\t\x00\x03' +\
+        b'\xa0s\x01a\xa0s\x01aux\x0b\x00\x01\x04\x88\xc4\t\x00\x04S_\x01\x00' +\
+        b'PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00nY\xfcR\x00\x00\x00\x00' +\
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x18\x00\x00\x00\x00\x00' +\
+        b'\x00\x00\x00\x00\x80\x81\x00\x00\x00\x00file.txt' +\
+        b'UT\x05\x00\x03\xa0s\x01aux\x0b\x00\x01\x04\x88\xc4\t\x00\x04' +\
+        b'S_\x01\x00PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00N\x00\x00' +\
+        b'\x00B\x00\x00\x00\x00\x00'
+    # Just making sure we concatenated the bytes correctly
+    self.assertEqual(len(magic_zip), 166)
+    fp = io.BytesIO(magic_zip)
+    with zipfile.ZipFile(fp, 'r') as zfp:
+      self.assertGreater(len(zfp.infolist()), 0)
+      zinfo = zfp.getinfo("file.txt")
+      (offset, size) = ota_utils.GetZipEntryOffset(zfp, zinfo)
+      self.assertEqual(size, zinfo.file_size)
+      self.assertEqual(offset, zipfile.sizeFileHeader+len(zinfo.filename) + 28)
diff --git a/tools/releasetools/test_sign_apex.py b/tools/releasetools/test_sign_apex.py
index 646b04d..8470f20 100644
--- a/tools/releasetools/test_sign_apex.py
+++ b/tools/releasetools/test_sign_apex.py
@@ -69,5 +69,5 @@
         payload_key,
         container_key,
         False,
-        codename_to_api_level_map={'S': 31})
+        codename_to_api_level_map={'S': 31, 'Tiramisu' : 32})
     self.assertTrue(os.path.exists(signed_apex))
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index ad9e657..0f13add 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -62,6 +62,9 @@
       'avb_boot_add_hash_footer_args':
           ('--prop com.android.build.boot.os_version:R '
            '--prop com.android.build.boot.security_patch:2019-09-05'),
+      'avb_init_boot_add_hash_footer_args':
+          ('--prop com.android.build.boot.os_version:R '
+           '--prop com.android.build.boot.security_patch:2019-09-05'),
       'avb_system_add_hashtree_footer_args':
           ('--prop com.android.build.system.os_version:R '
            '--prop com.android.build.system.security_patch:2019-09-05 '
@@ -77,6 +80,9 @@
       'avb_boot_add_hash_footer_args':
           ('--prop com.android.build.boot.os_version:R '
            '--prop com.android.build.boot.security_patch:2019-09-05'),
+      'avb_init_boot_add_hash_footer_args':
+          ('--prop com.android.build.boot.os_version:R '
+           '--prop com.android.build.boot.security_patch:2019-09-05'),
       'avb_system_add_hashtree_footer_args':
           ('--prop com.android.build.system.os_version:R '
            '--prop com.android.build.system.security_patch:2019-09-05 '
@@ -328,23 +334,23 @@
         'Apex3.apex' : 'key3',
     }
     apex_keys = {
-        'Apex1.apex' : ('payload-key1', 'container-key1'),
-        'Apex2.apex' : ('payload-key2', 'container-key2'),
+        'Apex1.apex' : ('payload-key1', 'container-key1', None),
+        'Apex2.apex' : ('payload-key2', 'container-key2', None),
     }
     with zipfile.ZipFile(input_file) as input_zip:
       CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)
 
       # Fine to have both keys as PRESIGNED.
-      apex_keys['Apex2.apex'] = ('PRESIGNED', 'PRESIGNED')
+      apex_keys['Apex2.apex'] = ('PRESIGNED', 'PRESIGNED', None)
       CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)
 
       # Having only one of them as PRESIGNED is not allowed.
-      apex_keys['Apex2.apex'] = ('payload-key2', 'PRESIGNED')
+      apex_keys['Apex2.apex'] = ('payload-key2', 'PRESIGNED', None)
       self.assertRaises(
           AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
           None, apex_keys)
 
-      apex_keys['Apex2.apex'] = ('PRESIGNED', 'container-key1')
+      apex_keys['Apex2.apex'] = ('PRESIGNED', 'container-key1', None)
       self.assertRaises(
           AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
           None, apex_keys)
@@ -475,10 +481,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_mismatchingContainerKeys(self):
@@ -514,10 +520,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_missingPayloadPublicKey(self):
@@ -537,10 +543,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
@@ -560,10 +566,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReadApexKeysInfo_presignedKeys(self):
@@ -583,10 +589,10 @@
     self.assertEqual({
         'apex.apexd_test.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         'apex.apexd_test_different_app.apex': (
             'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
-            'build/make/target/product/security/testkey'),
+            'build/make/target/product/security/testkey', None),
         }, keys_info)
 
   def test_ReplaceGkiSigningKey(self):
diff --git a/tools/releasetools/test_utils.py b/tools/releasetools/test_utils.py
index 808b392..e30d2b9 100755
--- a/tools/releasetools/test_utils.py
+++ b/tools/releasetools/test_utils.py
@@ -33,6 +33,8 @@
 # Some test runner doesn't like outputs from stderr.
 logging.basicConfig(stream=sys.stdout)
 
+ALLOWED_TEST_SUBDIRS = ('merge',)
+
 # Use ANDROID_BUILD_TOP as an indicator to tell if the needed tools (e.g.
 # avbtool, mke2fs) are available while running the tests, unless
 # FORCE_RUN_RELEASETOOLS is set to '1'. Not having the required vars means we
@@ -244,9 +246,12 @@
   # os walk and load them manually.
   test_modules = []
   base_path = os.path.dirname(os.path.realpath(__file__))
+  test_dirs = [base_path] + [
+      os.path.join(base_path, subdir) for subdir in ALLOWED_TEST_SUBDIRS
+  ]
   for dirpath, _, files in os.walk(base_path):
     for fn in files:
-      if dirpath == base_path and re.match('test_.*\\.py$', fn):
+      if dirpath in test_dirs and re.match('test_.*\\.py$', fn):
         test_modules.append(fn[:-3])
 
   test_suite = unittest.TestLoader().loadTestsFromNames(test_modules)
diff --git a/tools/releasetools/testdata/test_aftl_rsa4096.pem b/tools/releasetools/testdata/test_aftl_rsa4096.pem
deleted file mode 100644
index 89f1ef3..0000000
--- a/tools/releasetools/testdata/test_aftl_rsa4096.pem
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDDlhUPUgtWL6LB
-Wybp6wsEJeioV1aRLPGSA2/xIpTiJUK46cb/MD5eBTWjKENoIgX23eL/ePy2I68e
-+WvcZ5ITGOTRQqNVZIdc5qvr03wkV0BsJQMHSMAHacePpB/4xM5MzN/6Ku1wA8Dw
-uK+v/Cw4hqq8H/gP0oPVQ1bwcIePzRPX4YkkyXusoyzTIm5DJ9reVtyFucKqANCN
-aFmGxcaEc2nADtARQWJpO95joFsMvr68+JBxpCt8aWbxuSz/rLJ9Y8Z46V/++XG+
-E4QEob/WVY5pUD/RyogLrfhIf+zO7R3wJklXElSFacIX9+RzR9dgkQVbqxLfBKIP
-XWLCsF4I4EnvqUtaVjIMl8UpZpoq8pDLRqZ71Os5xZYq06x9E02M6DnvFbZEdaOX
-MCz2mmNX3g5FahvJayBhCuNhyTkd79MFR71Wp48TvWxKz3S7q0T0cWHNhtPkHSCa
-KwD93AQnqtLKYDGkHIZBzJPcs+QxbzdHyGzhXZb+qh5KmQvNA9HRBQY1RkMmzIbI
-8pzYTwpOkbCEhVoCWcRaaF1Pgl+zcpgJOMbBBUabx/dConFIhMDW/I5fHgKgwGqm
-tWUibrMPdnfS6W5MXi8jC0eDuZl0VwmdE+4dLujiOofUYnb7D+GXojf3PrSLcTw1
-PmG0f7l5xDKN9a0N+IXqvD2oAANTsQIDAQABAoICAQCW5HXw8OogHvYg2HMIKrbA
-B4McRO1baWIhtRcq4PQeGIMGaA2HmS+0l65O5uRCNWWGlJ7pW+0TlCop6mHFk/4F
-T8JQk2mxmrI4ARqIAQwYeVwRUuioOP81eO1mK0gjQ6qpY7I0reOq9KpozQN18UYo
-gfS82Kkng9EDukUbkKV1UtFJTw3gXLVWdjlB1qFcnCXmPPs7DBpbz+8V+XiAWpsS
-WnwumP77IQeMiozDLdaw2YQMBHRjyDVocWTjfmpyAkleJZjcdagC7W1MKIBElomL
-EUyigTALaYZWBGy1ekQ3TIY5XUBdtZ2RpAsDNNOCAN3v+VI565zOhCOHWRO1gh24
-vyhBFR0HYqBRoLbLAqo8bM5iLPz1EWGyaTnfxt38J8Va0TD7KihcBnphiA+dkhEF
-oc0yIp/8S2o3CfkNok7Ju8Amb7M4JJuKhuP8wxn86fAHpjjd3Y4SlZp0NrTrd7T2
-msLIneb1OUZZxFxyJG1XQGEZplLPalnGadIF4p3q/3nd1rVb491qCNl/A5QwhI9r
-ZV62O90M9fu3+cAynBLbMT09IZecNwP1gXmunlY6YH+ymM+3NFqC8q2tnzomiz8/
-Fee0ftZ2C/jK62fET0Y8LPWGkVQGHtvZH0FPg4suA0GMmYAe0tQl93A+jFltfKKZ
-RgCDrYs6Wv76E9gnWVnEdQKCAQEA8L76LjZUTKOg83Bra+hP+cXnwGsgwOwJfGBp
-OM++5HzlpYjtbD38esBZVJtwb/8xJGdsHtP2n7ZgbSDuAnRj5S50QHIApvRkz1Y+
-1hL8tAdgVP2JkYjpyG3bPk4QVKyXkKvBcp2BCidXs75+HzfOxqkazumaYOYo2guh
-azHdka2xSqxcZqo4yyORc/oue25RU4skmuNDOlP0+OTxU/uXnl7QZmlaOfT5TqO4
-s7uER4BXt/87j44mnOBdXmtqrsL49+R9bzVskx76aeuaBbwf7jnpR058E71OZwSd
-F1P3fx6hl0yLOZF/5Jnq+14rEna6jH50XtzlhB6deSZFTOw2gwKCAQEAz/qXRzwH
-I0YWISgkUG2zBJseHmfHqV4CDzb5+tTJ3B2I8cXE0m2sQJXi2s7oMhWSc1cQOHCX
-txpgWaD59uBz2lcwnGRNp27TRXv8Wo+X0+O+lGWU2cO+j8AB2Vtb7F7rCySp0+Uu
-z+dBfoQ2zhKEQlkX0YldVILGzCL3QBHVvPC4iDlwkMRbcejDoh9NsBtHL8lG+MAw
-ZXbwJjhaJkhTXJFpJpejq70naS8VVlLt8Os80iuBXe5JK/ecAHtsNcJlXO02sMNZ
-Fbcy8WosGyvRKQ/tHtTjAlxZ7Ey8usWE8BvWBdUgiIBkIcjLtE2GrA8eOGNb3v1I
-HRt8NsV8yaLWuwKCAQAR7SaT6le8nTKO7gARuOq7npDzMwbtVqYeLM+o+08rlGFF
-QjzronH6cfg05J4quMXgABN8+CuVGO91MM6IQEJv/lWJtvN1ex1GkxV6u0812JbD
-vV1RCPDfi86XhRiSNYfTrfZponDJYMSXDcg2auFqyYzFe3+TV5ATLGqIoN3uyxA4
-jz0SJ/qypaNfD3IGnuBPaD0Bi4ql/TpwjhuqNUHE+SprdczSI/usb2SBfaUL7fKa
-MNcuiVc2tz48maMIAFypmMn+TewXyGa9HF4Lr0ZxZr6IIL/8eEwuP5my8v2q6Yz+
-xyRW1Q7A5vUoYoqyhUS+0Wu45JnyjJUNQFxIrg4hAoIBAF1uBIGSvN4iwRQ6FT4w
-WahrCre8BVzXh3NQTjJZXylL91YtcwLZE/Wbn+KN6o99U2IPLZE9O1qdNcVt5Hz8
-Te87FfJbuOrLhYuEbFQ+h4U/nUDK9XhyT+wB5JLBUOU5qrtByC0Rmtr411o/iONA
-PDwWC/YskEnDygywdIRKvsr3FN7VdvUB0Na2KxRsnZjMWElmUUS0Ccm7CZ0R2aWy
-/gfqpuMYYgVnnwnIhfxWmt+MvbDorGAHCMYAoQsyZuUrpB9/zP7RcvanavI6sP+v
-ynF43xvnpOdNl3Po8SuyScsXpijOmqPXkaP/sUsZPLOUww2vzPi6raetzjpIs4td
-ZLsCggEAe42Zj3FEbruJZeDgmd9lSc0j8UF90mNw8KH44IbuA6R9fGv3WkrNHEVd
-XZOwjWqAxhOj6pFoJk8n6h5d8iS/yXFZ0AfBMc21XMecu9mnfx9E9LFAIWmv7Wut
-vy3h2BqY+crglpg5RAw+3J97HAGMYCvp+hH2il+9zzjpmCtTD21LRMkw34szY7RR
-CDy9G5FTmKVlxw5eegvyj164olQRLurEdUIfSr5UnBjrWftJHy9JW8KWCeFDSmm9
-xCl3nGDyQuZmOTngxPtrOYAhb5LoKR9BeGcy6jlom7V4nYYqm3t1IDBgMqjYGT9c
-vqQgxO2OFsQOJQ/4PRYEKd1neTlZrw==
------END PRIVATE KEY-----
diff --git a/tools/releasetools/testdata/test_transparency_key.pub b/tools/releasetools/testdata/test_transparency_key.pub
deleted file mode 100644
index 8bfd816..0000000
--- a/tools/releasetools/testdata/test_transparency_key.pub
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4ilqCNsenNA013iCdwgD
-YPxZ853nbHG9lMBp9boXiwRcqT/8bUKHIL7YX5z7s+QoRYVY3rkMKppRabclXzyx
-H59YnPMaU4uv7NqwWzjgaZo7E+vo7IF+KBjV3cJulId5Av0yIYUCsrwd7MpGtWdC
-Q3S+7Vd4zwzCKEhcvliNIhnNlp1U3wNkPCxOyCAsMEn6k8O5ar12ke5TvxDv15db
-rPDeHh8G2OYWoCkWL+lSN35L2kOJqKqVbLKWrrOd96RCYrrtbPCi580OADJRcUlG
-lgcjwmNwmypBWvQMZ6ITj0P0ksHnl1zZz1DE2rXe1goLI1doghb5KxLaezlR8c2C
-E3w/uo9KJgNmNgUVzzqZZ6FE0moyIDNOpP7KtZAL0DvEZj6jqLbB0ccPQElrg52m
-Dv2/A3nYSr0mYBKeskT4+Bg7PGgoC8p7WyLSxMyzJEDYdtrj9OFx6eZaA23oqTQx
-k3Qq5H8RfNBeeSUEeKF7pKH/7gyqZ2bNzBFMA2EBZgBozwRfaeN/HCv3qbaCnwvu
-6caacmAsK+RxiYxSL1QsJqyhCWWGxVyenmxdc1KG/u5ypi7OIioztyzR3t2tAzD3
-Nb+2t8lgHBRxbV24yiPlnvPmB1ZYEctXnlRR9Evpl1o9xA9NnybPHKr9rozN39CZ
-V/USB8K6ao1y5xPZxa8CZksCAwEAAQ==
------END PUBLIC KEY-----
-
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index 401857f..beb9e75 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -36,7 +36,9 @@
 import os.path
 import re
 import zipfile
+
 from hashlib import sha1
+from common import IsSparseImage
 
 import common
 import rangelib
@@ -71,10 +73,16 @@
 
   def CheckAllFiles(which):
     logging.info('Checking %s image.', which)
-    # Allow having shared blocks when loading the sparse image, because allowing
-    # that doesn't affect the checks below (we will have all the blocks on file,
-    # unless it's skipped due to the holes).
-    image = common.GetSparseImage(which, input_tmp, input_zip, True)
+    path = os.path.join(input_tmp, "IMAGES", which + ".img")
+    if not IsSparseImage(path):
+      logging.info("%s is non-sparse image", which)
+      image = common.GetNonSparseImage(which, input_tmp)
+    else:
+      logging.info("%s is sparse image", which)
+      # Allow having shared blocks when loading the sparse image, because allowing
+      # that doesn't affect the checks below (we will have all the blocks on file,
+      # unless it's skipped due to the holes).
+      image = common.GetSparseImage(which, input_tmp, input_zip, True)
     prefix = '/' + which
     for entry in image.file_map:
       # Skip entries like '__NONZERO-0'.
@@ -123,8 +131,10 @@
     logging.warning('Skipped due to target using non-sparse images')
     return
 
-  # Verify IMAGES/system.img.
-  CheckAllFiles('system')
+  # Verify IMAGES/system.img if applicable.
+  # Some targets, e.g., gki_arm64, gki_x86_64, etc., are system.img-less.
+  if 'IMAGES/system.img' in input_zip.namelist():
+    CheckAllFiles('system')
 
   # Verify IMAGES/vendor.img if applicable.
   if 'VENDOR/' in input_zip.namelist():
@@ -194,7 +204,8 @@
 
     # Check we have the same recovery target in the check and flash commands.
     assert check_partition == flash_partition, \
-        "Mismatching targets: {} vs {}".format(check_partition, flash_partition)
+        "Mismatching targets: {} vs {}".format(
+            check_partition, flash_partition)
 
     # Validate the SHA-1 of the recovery image.
     recovery_sha1 = flash_partition.split(':')[3]
@@ -248,6 +259,36 @@
     os.symlink(os.path.join(src, filename), os.path.join(dst, filename))
 
 
+def ValidatePartitionFingerprints(input_tmp, info_dict):
+  build_info = common.BuildInfo(info_dict)
+  # Expected format:
+  #  Prop: com.android.build.vendor.fingerprint -> 'generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7335886:userdebug/test-keys'
+  #  Prop: com.android.build.vendor_boot.fingerprint -> 'generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7335886:userdebug/test-keys'
+  p = re.compile(
+      r"Prop: com.android.build.(?P<partition>\w+).fingerprint -> '(?P<fingerprint>[\w\/:\.-]+)'")
+  for vbmeta_partition in ["vbmeta", "vbmeta_system"]:
+    image = os.path.join(input_tmp, "IMAGES", vbmeta_partition + ".img")
+    if not os.path.exists(image):
+      assert vbmeta_partition != "vbmeta",\
+          "{} is a required partition for AVB.".format(
+              vbmeta_partition)
+      logging.info("vb partition %s not present, skipping", vbmeta_partition)
+      continue
+
+    output = common.RunAndCheckOutput(
+        [info_dict["avb_avbtool"], "info_image", "--image", image])
+    matches = p.findall(output)
+    for (partition, fingerprint) in matches:
+      actual_fingerprint = build_info.GetPartitionFingerprint(
+          partition)
+      if actual_fingerprint is None:
+        logging.warning(
+            "Failed to get fingerprint for partition %s", partition)
+        continue
+      assert fingerprint == actual_fingerprint, "Fingerprint mismatch for partition {}, expected: {} actual: {}".format(
+          partition, fingerprint, actual_fingerprint)
+
+
 def ValidateVerifiedBootImages(input_tmp, info_dict, options):
   """Validates the Verified Boot related images.
 
@@ -273,7 +314,7 @@
   # longer copied from RADIO to the IMAGES folder. But avbtool assumes that
   # images are in IMAGES folder. So we symlink them.
   symlinkIfNotExists(os.path.join(input_tmp, "RADIO"),
-                    os.path.join(input_tmp, "IMAGES"))
+                     os.path.join(input_tmp, "IMAGES"))
   # Verified boot 1.0 (images signed with boot_signer and verity_signer).
   if info_dict.get('boot_signer') == 'true':
     logging.info('Verifying Verified Boot images...')
@@ -325,11 +366,12 @@
     if info_dict.get("system_root_image") != "true":
       verity_key_ramdisk = os.path.join(
           input_tmp, 'BOOT', 'RAMDISK', 'verity_key')
-      assert os.path.exists(verity_key_ramdisk), 'Missing verity_key in ramdisk'
+      assert os.path.exists(
+          verity_key_ramdisk), 'Missing verity_key in ramdisk'
 
       assert filecmp.cmp(
           verity_key_mincrypt, verity_key_ramdisk, shallow=False), \
-              'Mismatching verity_key files in root and ramdisk'
+          'Mismatching verity_key files in root and ramdisk'
       logging.info('Verified the content of /verity_key in ramdisk')
 
     # Then verify the verity signed system/vendor/product images, against the
@@ -355,13 +397,15 @@
           verity_key_mincrypt, stdoutdata.rstrip())
 
   # Handle the case of Verified Boot 2.0 (AVB).
-  if info_dict.get("avb_enable") == "true":
+  if info_dict.get("avb_building_vbmeta_image") == "true":
     logging.info('Verifying Verified Boot 2.0 (AVB) images...')
 
     key = options['verity_key']
     if key is None:
       key = info_dict['avb_vbmeta_key_path']
 
+    ValidatePartitionFingerprints(input_tmp, info_dict)
+
     # avbtool verifies all the images that have descriptors listed in vbmeta.
     # Using `--follow_chain_partitions` so it would additionally verify chained
     # vbmeta partitions (e.g. vbmeta_system).
@@ -411,7 +455,7 @@
 
     # avbtool verifies recovery image for non-A/B devices.
     if (info_dict.get('ab_update') != 'true' and
-        info_dict.get('no_recovery') != 'true'):
+            info_dict.get('no_recovery') != 'true'):
       image = os.path.join(input_tmp, 'IMAGES', 'recovery.img')
       key = info_dict['avb_recovery_key_path']
       cmd = [info_dict['avb_avbtool'], 'verify_image', '--image', image,
@@ -427,21 +471,21 @@
 
 
 def CheckDataInconsistency(lines):
-    build_prop = {}
-    for line in lines:
-      if line.startswith("import") or line.startswith("#"):
-        continue
-      if "=" not in line:
-        continue
+  build_prop = {}
+  for line in lines:
+    if line.startswith("import") or line.startswith("#"):
+      continue
+    if "=" not in line:
+      continue
 
-      key, value = line.rstrip().split("=", 1)
-      if key in build_prop:
-        logging.info("Duplicated key found for {}".format(key))
-        if value != build_prop[key]:
-          logging.error("Key {} is defined twice with different values {} vs {}"
-                        .format(key, value, build_prop[key]))
-          return key
-      build_prop[key] = value
+    key, value = line.rstrip().split("=", 1)
+    if key in build_prop:
+      logging.info("Duplicated key found for {}".format(key))
+      if value != build_prop[key]:
+        logging.error("Key {} is defined twice with different values {} vs {}"
+                      .format(key, value, build_prop[key]))
+        return key
+    build_prop[key] = value
 
 
 def CheckBuildPropDuplicity(input_tmp):
diff --git a/tools/releasetools/verity_utils.py b/tools/releasetools/verity_utils.py
index a08ddbe..d55ad88 100644
--- a/tools/releasetools/verity_utils.py
+++ b/tools/releasetools/verity_utils.py
@@ -379,6 +379,11 @@
     self.avbtool = avbtool
     self.algorithm = algorithm
     self.key_path = key_path
+    if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
+      new_key_path = os.path.join(OPTIONS.search_path, key_path)
+      if os.path.exists(new_key_path):
+        self.key_path = new_key_path
+
     self.salt = salt
     self.signing_args = signing_args
     self.image_size = None
diff --git a/tools/signapk/OWNERS b/tools/signapk/OWNERS
index 0b8d398..23cab0b 100644
--- a/tools/signapk/OWNERS
+++ b/tools/signapk/OWNERS
@@ -1,2 +1,2 @@
 cbrubaker@google.com
-klyubin@google.com
+mpgroover@google.com
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 7e5c8fc..b0c792c 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -64,12 +64,19 @@
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Key;
 import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.KeyStore.PrivateKeyEntry;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.Security;
+import java.security.UnrecoverableEntryException;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
@@ -197,26 +204,23 @@
      * If a console doesn't exist, reads the password from stdin
      * If a console exists, reads the password from console and returns it as a string.
      *
-     * @param keyFile The file containing the private key.  Used to prompt the user.
+     * @param keyFileName Name of the file containing the private key.  Used to prompt the user.
      */
-    private static String readPassword(File keyFile) {
+    private static char[] readPassword(String keyFileName) {
         Console console;
-        char[] pwd;
         if ((console = System.console()) == null) {
-            System.out.print("Enter password for " + keyFile + " (password will not be hidden): ");
+            System.out.print(
+                "Enter password for " + keyFileName + " (password will not be hidden): ");
             System.out.flush();
             BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
             try {
-                return stdin.readLine();
+                String result = stdin.readLine();
+                return result == null ? null : result.toCharArray();
             } catch (IOException ex) {
                 return null;
             }
         } else {
-            if ((pwd = console.readPassword("[%s]", "Enter password for " + keyFile)) != null) {
-                return String.valueOf(pwd);
-            } else {
-                return null;
-            }
+            return console.readPassword("[%s]", "Enter password for " + keyFileName);
         }
     }
 
@@ -239,11 +243,8 @@
             return null;
         }
 
-        char[] password = readPassword(keyFile).toCharArray();
-
         SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
-        Key key = skFactory.generateSecret(new PBEKeySpec(password));
-
+        Key key = skFactory.generateSecret(new PBEKeySpec(readPassword(keyFile.getPath())));
         Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
         cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
 
@@ -286,6 +287,32 @@
         }
     }
 
+    private static KeyStore createKeyStore(String keyStoreName, String keyStorePin) throws
+            CertificateException,
+            IOException,
+            KeyStoreException,
+            NoSuchAlgorithmException {
+        KeyStore keyStore = KeyStore.getInstance(keyStoreName);
+        keyStore.load(null, keyStorePin == null ? null : keyStorePin.toCharArray());
+        return keyStore;
+    }
+
+    /** Get a PKCS#11 private key from keyStore */
+    private static PrivateKey loadPrivateKeyFromKeyStore(
+            final KeyStore keyStore, final String keyName)
+            throws CertificateException, KeyStoreException, NoSuchAlgorithmException,
+                    UnrecoverableKeyException, UnrecoverableEntryException {
+        final Key key = keyStore.getKey(keyName, readPassword(keyName));
+        final PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) keyStore.getEntry(keyName, null);
+        if (privateKeyEntry == null) {
+        throw new Error(
+            "Key "
+                + keyName
+                + " not found in the token provided by PKCS11 library!");
+        }
+        return privateKeyEntry.getPrivateKey();
+    }
+
     /**
      * Add a copy of the public key to the archive; this should
      * exactly match one of the files in
@@ -1020,7 +1047,10 @@
     private static void usage() {
         System.err.println("Usage: signapk [-w] " +
                            "[-a <alignment>] " +
+                           "[--align-file-size] " +
                            "[-providerClass <className>] " +
+                           "[-loadPrivateKeysFromKeyStore <keyStoreName>]" +
+                           "[-keyStorePin <pin>]" +
                            "[--min-sdk-version <n>] " +
                            "[--disable-v2] " +
                            "[--enable-v4] " +
@@ -1043,11 +1073,15 @@
 
         boolean signWholeFile = false;
         String providerClass = null;
+        String keyStoreName = null;
+        String keyStorePin = null;
         int alignment = 4;
+        boolean alignFileSize = false;
         Integer minSdkVersionOverride = null;
         boolean signUsingApkSignatureSchemeV2 = true;
         boolean signUsingApkSignatureSchemeV4 = false;
         SigningCertificateLineage certLineage = null;
+        Integer rotationMinSdkVersion = null;
 
         int argstart = 0;
         while (argstart < args.length && args[argstart].startsWith("-")) {
@@ -1060,9 +1094,24 @@
                 }
                 providerClass = args[++argstart];
                 ++argstart;
+            } else if ("-loadPrivateKeysFromKeyStore".equals(args[argstart])) {
+                if (argstart + 1 >= args.length) {
+                    usage();
+                }
+                keyStoreName = args[++argstart];
+                ++argstart;
+            } else if ("-keyStorePin".equals(args[argstart])) {
+                if (argstart + 1 >= args.length) {
+                    usage();
+                }
+                keyStorePin = args[++argstart];
+                ++argstart;
             } else if ("-a".equals(args[argstart])) {
                 alignment = Integer.parseInt(args[++argstart]);
                 ++argstart;
+            } else if ("--align-file-size".equals(args[argstart])) {
+                alignFileSize = true;
+                ++argstart;
             } else if ("--min-sdk-version".equals(args[argstart])) {
                 String minSdkVersionString = args[++argstart];
                 try {
@@ -1087,6 +1136,15 @@
                             "Error reading lineage file: " + e.getMessage());
                 }
                 ++argstart;
+            } else if ("--rotation-min-sdk-version".equals(args[argstart])) {
+                String rotationMinSdkVersionString = args[++argstart];
+                try {
+                    rotationMinSdkVersion = Integer.parseInt(rotationMinSdkVersionString);
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException(
+                            "--rotation-min-sdk-version must be a decimal number: " + rotationMinSdkVersionString);
+                }
+                ++argstart;
             } else {
                 usage();
             }
@@ -1137,11 +1195,19 @@
             // timestamp using the current timezone. We thus adjust the milliseconds since epoch
             // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
             timestamp -= TimeZone.getDefault().getOffset(timestamp);
-
+            KeyStore keyStore = null;
+            if (keyStoreName != null) {
+                keyStore = createKeyStore(keyStoreName, keyStorePin);
+            }
             PrivateKey[] privateKey = new PrivateKey[numKeys];
             for (int i = 0; i < numKeys; ++i) {
                 int argNum = argstart + i*2 + 1;
-                privateKey[i] = readPrivateKey(new File(args[argNum]));
+                if (keyStore == null) {
+                    privateKey[i] = readPrivateKey(new File(args[argNum]));
+                } else {
+                    final String keyAlias = args[argNum];
+                    privateKey[i] = loadPrivateKeyFromKeyStore(keyStore, keyAlias);
+                }
             }
             inputJar = new JarFile(new File(inputFilename), false);  // Don't verify.
 
@@ -1170,15 +1236,22 @@
                     }
                 }
 
-                try (ApkSignerEngine apkSigner =
-                        new DefaultApkSignerEngine.Builder(
-                                createSignerConfigs(privateKey, publicKey), minSdkVersion)
-                                .setV1SigningEnabled(true)
-                                .setV2SigningEnabled(signUsingApkSignatureSchemeV2)
-                                .setOtherSignersSignaturesPreserved(false)
-                                .setCreatedBy("1.0 (Android SignApk)")
-                                .setSigningCertificateLineage(certLineage)
-                                .build()) {
+                DefaultApkSignerEngine.Builder builder = new DefaultApkSignerEngine.Builder(
+                    createSignerConfigs(privateKey, publicKey), minSdkVersion)
+                    .setV1SigningEnabled(true)
+                    .setV2SigningEnabled(signUsingApkSignatureSchemeV2)
+                    .setOtherSignersSignaturesPreserved(false)
+                    .setCreatedBy("1.0 (Android SignApk)");
+
+                if (certLineage != null) {
+                   builder = builder.setSigningCertificateLineage(certLineage);
+                }
+
+                if (rotationMinSdkVersion != null) {
+                   builder = builder.setMinSdkVersionForRotation(rotationMinSdkVersion);
+                }
+
+                try (ApkSignerEngine apkSigner = builder.build()) {
                     // We don't preserve the input APK's APK Signing Block (which contains v2
                     // signatures)
                     apkSigner.inputApkSigningBlock(null);
@@ -1206,12 +1279,22 @@
                     ByteBuffer[] outputChunks = new ByteBuffer[] {v1SignedApk};
 
                     ZipSections zipSections = findMainZipSections(v1SignedApk);
-                    ApkSignerEngine.OutputApkSigningBlockRequest2 addV2SignatureRequest =
-                            apkSigner.outputZipSections2(
-                                    DataSources.asDataSource(zipSections.beforeCentralDir),
-                                    DataSources.asDataSource(zipSections.centralDir),
-                                    DataSources.asDataSource(zipSections.eocd));
-                    if (addV2SignatureRequest != null) {
+
+                    ByteBuffer eocd = ByteBuffer.allocate(zipSections.eocd.remaining());
+                    eocd.put(zipSections.eocd);
+                    eocd.flip();
+                    eocd.order(ByteOrder.LITTLE_ENDIAN);
+                    // This loop is supposed to be iterated twice at most.
+                    // The second pass is to align the file size after amending EOCD comments
+                    // with assumption that re-generated signing block would be the same size.
+                    while (true) {
+                        ApkSignerEngine.OutputApkSigningBlockRequest2 addV2SignatureRequest =
+                                apkSigner.outputZipSections2(
+                                        DataSources.asDataSource(zipSections.beforeCentralDir),
+                                        DataSources.asDataSource(zipSections.centralDir),
+                                        DataSources.asDataSource(eocd));
+                        if (addV2SignatureRequest == null) break;
+
                         // Need to insert the returned APK Signing Block before ZIP Central
                         // Directory.
                         int padding = addV2SignatureRequest.getPaddingSizeBeforeApkSigningBlock();
@@ -1219,8 +1302,8 @@
                         // Because the APK Signing Block is inserted before the Central Directory,
                         // we need to adjust accordingly the offset of Central Directory inside the
                         // ZIP End of Central Directory (EoCD) record.
-                        ByteBuffer modifiedEocd = ByteBuffer.allocate(zipSections.eocd.remaining());
-                        modifiedEocd.put(zipSections.eocd);
+                        ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
+                        modifiedEocd.put(eocd);
                         modifiedEocd.flip();
                         modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
                         ApkUtils.setZipEocdCentralDirectoryOffset(
@@ -1235,6 +1318,32 @@
                                         zipSections.centralDir,
                                         modifiedEocd};
                         addV2SignatureRequest.done();
+
+                        // Exit the loop if we don't need to align the file size
+                        if (!alignFileSize || alignment < 2) {
+                            break;
+                        }
+
+                        // Calculate the file size
+                        eocd = modifiedEocd;
+                        int fileSize = 0;
+                        for (ByteBuffer buf : outputChunks) {
+                            fileSize += buf.remaining();
+                        }
+                        // Exit the loop because the file size is aligned.
+                        if (fileSize % alignment == 0) {
+                            break;
+                        }
+                        // Pad EOCD comment to align the file size.
+                        int commentLen = alignment - fileSize % alignment;
+                        modifiedEocd = ByteBuffer.allocate(eocd.remaining() + commentLen);
+                        modifiedEocd.put(eocd);
+                        modifiedEocd.rewind();
+                        modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
+                        ApkUtils.updateZipEocdCommentLen(modifiedEocd);
+                        // Since V2 signing block should cover modified EOCD,
+                        // re-iterate the loop with modified EOCD.
+                        eocd = modifiedEocd;
                     }
 
                     // This assumes outputChunks are array-backed. To avoid this assumption, the
diff --git a/tools/warn/cpp_warn_patterns.py b/tools/warn/cpp_warn_patterns.py
index 2fa9916..1e1aa43 100644
--- a/tools/warn/cpp_warn_patterns.py
+++ b/tools/warn/cpp_warn_patterns.py
@@ -91,6 +91,10 @@
          [r".*: warning: incompatible redeclaration of library function .+"]),
     high('Null passed as non-null argument',
          [r".*: warning: Null passed to a callee that requires a non-null"]),
+    medium('Unused command line argument',
+           [r".*: warning: argument unused during compilation: .+"]),
+    medium('Set but not used',
+           [r".*: warning: .* set but not used.*Wunused-but-set"]),
     medium('Unused parameter',
            [r".*: warning: unused parameter '.*'"]),
     medium('Unused function, variable, label, comparison, etc.',
@@ -166,6 +170,8 @@
            [r".*: warning: '.+' declared with greater visibility than the type of its field '.+'"]),
     medium('Shift count greater than width of type',
            [r".*: warning: (left|right) shift count >= width of type"]),
+    medium('Shift operator precedence',
+           [r".*: warning: operator .* has lower precedence .+Wshift-op-parentheses.+"]),
     medium('extern &lt;foo&gt; is initialized',
            [r".*: warning: '.+' initialized and declared 'extern'",
             r".*: warning: 'extern' variable has an initializer"]),
@@ -239,6 +245,8 @@
            [r".*: warning: ignoring #pragma .+"]),
     medium('Pragma warning messages',
            [r".*: warning: .+W#pragma-messages"]),
+    medium('Pragma once in main file',
+           [r".*: warning: #pragma once in main file .+Wpragma-once-outside-header.*"]),
     medium('Variable might be clobbered by longjmp or vfork',
            [r".*: warning: variable '.+' might be clobbered by 'longjmp' or 'vfork'"]),
     medium('Argument might be clobbered by longjmp or vfork',
@@ -289,6 +297,7 @@
            [r".*: warning: declaration 'class .+' does not declare anything"]),
     medium('Initialization order will be different',
            [r".*: warning: '.+' will be initialized after",
+            r".*: warning: initializer order does not match the declaration order",
             r".*: warning: field .+ will be initialized after .+Wreorder"]),
     skip('skip,   ....',
          [r".*: warning:   '.+'"]),
@@ -333,7 +342,7 @@
     low('Deprecated register',
         [r".*: warning: 'register' storage class specifier is deprecated"]),
     low('Converts between pointers to integer types with different sign',
-        [r".*: warning: .+ converts between pointers to integer types with different sign"]),
+        [r".*: warning: .+ converts between pointers to integer types .+Wpointer-sign\]"]),
     harmless('Extra tokens after #endif',
              [r".*: warning: extra tokens at end of #endif directive"]),
     medium('Comparison between different enums',
@@ -410,10 +419,37 @@
         [r".*: warning: missing .+Winvalid-pp-token"]),
     low('need glibc to link',
         [r".*: warning: .* requires at runtime .* glibc .* for linking"]),
+    low('Add braces to avoid dangling else',
+        [r".*: warning: add explicit braces to avoid dangling else"]),
+    low('Assigning value to self',
+        [r".*: warning: explicitly assigning value of .+ to itself"]),
+    low('Comparison of integers of different signs',
+        [r".*: warning: comparison of integers of different signs.+sign-compare"]),
+    low('Incompatible pointer types',
+        [r".*: warning: incompatible .*pointer types .*-Wincompatible-.*pointer-types"]),
+    low('Missing braces',
+        [r".*: warning: suggest braces around initialization of",
+         r".*: warning: too many braces around scalar initializer .+Wmany-braces-around-scalar-init",
+         r".*: warning: braces around scalar initializer"]),
+    low('Missing field initializers',
+        [r".*: warning: missing field '.+' initializer"]),
+    low('Typedef redefinition',
+        [r".*: warning: redefinition of typedef '.+' is a C11 feature"]),
+    low('GNU old-style field designator',
+        [r".*: warning: use of GNU old-style field designator extension"]),
+    low('Initializer overrides prior initialization',
+        [r".*: warning: initializer overrides prior initialization of this subobject"]),
+    low('GNU extension, variable sized type not at end',
+        [r".*: warning: field '.+' with variable sized type '.+' not at the end of a struct or class"]),
+    low('Comparison of constant is always false/true',
+        [r".*: comparison of .+ is always .+Wtautological-constant-out-of-range-compare"]),
+    low('Hides overloaded virtual function',
+        [r".*: '.+' hides overloaded virtual function"]),
     medium('Operator new returns NULL',
            [r".*: warning: 'operator new' must not return NULL unless it is declared 'throw\(\)' .+"]),
     medium('NULL used in arithmetic',
            [r".*: warning: NULL used in arithmetic",
+            r".*: warning: .* subtraction with a null pointer",
             r".*: warning: comparison between NULL and non-pointer"]),
     medium('Misspelled header guard',
            [r".*: warning: '.+' is used as a header guard .+ followed by .+ different macro"]),
diff --git a/tools/warn/html_writer.py b/tools/warn/html_writer.py
index ac5d4b7..3fa822a 100644
--- a/tools/warn/html_writer.py
+++ b/tools/warn/html_writer.py
@@ -63,6 +63,11 @@
 from .severity import Severity
 
 
+# Report files with this number of warnings or more.
+LIMIT_WARNINGS_PER_FILE = 100
+# Report files/directories with this percentage of total warnings or more.
+LIMIT_PERCENT_WARNINGS = 1
+
 HTML_HEAD_SCRIPTS = """\
   <script type="text/javascript">
   function expand(id) {
@@ -89,12 +94,13 @@
   </script>
   <style type="text/css">
   th,td{border-collapse:collapse; border:1px solid black;}
-  .button{color:blue;font-size:110%;font-weight:bolder;}
+  .button{color:blue;font-size:100%;font-weight:bolder;}
   .bt{color:black;background-color:transparent;border:none;outline:none;
       font-size:140%;font-weight:bolder;}
   .c0{background-color:#e0e0e0;}
   .c1{background-color:#d0d0d0;}
   .t1{border-collapse:collapse; width:100%; border:1px solid black;}
+  .box{margin:5pt; padding:5pt; border:1px solid;}
   </style>
   <script src="https://www.gstatic.com/charts/loader.js"></script>
 """
@@ -287,14 +293,14 @@
 #     sort by project, severity, warn_id, warning_message
 def emit_buttons(writer):
   """Write the button elements in HTML."""
-  writer('<button class="button" onclick="expandCollapse(1);">'
+  writer('<p><button class="button" onclick="expandCollapse(1);">'
          'Expand all warnings</button>\n'
          '<button class="button" onclick="expandCollapse(0);">'
          'Collapse all warnings</button>\n'
-         '<button class="button" onclick="groupBySeverity();">'
+         '<p><button class="button" onclick="groupBySeverity();">'
          'Group warnings by severity</button>\n'
          '<button class="button" onclick="groupByProject();">'
-         'Group warnings by project</button><br>')
+         'Group warnings by project</button>')
 
 
 def all_patterns(category):
@@ -328,7 +334,8 @@
     cur_row_class = 1 - cur_row_class
     # remove last '\n'
     out_text = text[:-1] if text[-1] == '\n' else text
-    writer('<tr><td class="c' + str(cur_row_class) + '">' + out_text + '</td></tr>')
+    writer('<tr><td class="c' + str(cur_row_class) + '">'
+           + out_text + '</td></tr>')
   writer('</table></div>')
   writer('</blockquote>')
 
@@ -355,7 +362,8 @@
   sort_warnings(warn_patterns)
   total = 0
   for severity in Severity.levels:
-    total += write_severity(csvwriter, severity, severity.column_header, warn_patterns)
+    total += write_severity(
+        csvwriter, severity, severity.column_header, warn_patterns)
   csvwriter.writerow([total, '', 'All warnings'])
 
 
@@ -557,6 +565,11 @@
 """
 
 
+# Emit a JavaScript const number
+def emit_const_number(name, value, writer):
+  writer('const ' + name + ' = ' + str(value) + ';')
+
+
 # Emit a JavaScript const string
 def emit_const_string(name, value, writer):
   writer('const ' + name + ' = "' + escape_string(value) + '";')
@@ -600,6 +613,8 @@
   emit_const_string('FlagPlatform', flags.platform, writer)
   emit_const_string('FlagURL', flags.url, writer)
   emit_const_string('FlagSeparator', flags.separator, writer)
+  emit_const_number('LimitWarningsPerFile', LIMIT_WARNINGS_PER_FILE, writer)
+  emit_const_number('LimitPercentWarnings', LIMIT_PERCENT_WARNINGS, writer)
   emit_const_string_array('SeverityColors', [s.color for s in Severity.levels],
                           writer)
   emit_const_string_array('SeverityHeaders',
@@ -622,8 +637,8 @@
 
 DRAW_TABLE_JAVASCRIPT = """
 google.charts.load('current', {'packages':['table']});
-google.charts.setOnLoadCallback(drawTable);
-function drawTable() {
+google.charts.setOnLoadCallback(genTables);
+function genSelectedProjectsTable() {
   var data = new google.visualization.DataTable();
   data.addColumn('string', StatsHeader[0]);
   for (var i=1; i<StatsHeader.length; i++) {
@@ -636,12 +651,167 @@
     }
   }
   var table = new google.visualization.Table(
-      document.getElementById('stats_table'));
+      document.getElementById('selected_projects_section'));
   table.draw(data, {allowHtml: true, alternatingRowStyle: true});
 }
+// Global TopDirs and TopFiles are computed later by genTables.
+window.TopDirs = [];
+window.TopFiles = [];
+function computeTopDirsFiles() {
+  var numWarnings = WarningMessages.length;
+  var warningsOfFiles = {};
+  var warningsOfDirs = {};
+  var subDirs = {};
+  function addOneWarning(map, key) {
+    map[key] = 1 + ((key in map) ? map[key] : 0);
+  }
+  for (var i = 0; i < numWarnings; i++) {
+    var file = WarningMessages[i].replace(/:.*/, "");
+    addOneWarning(warningsOfFiles, file);
+    var dirs = file.split("/");
+    var dir = dirs[0];
+    addOneWarning(warningsOfDirs, dir);
+    for (var d = 1; d < dirs.length - 1; d++) {
+      var subDir = dir + "/" + dirs[d];
+      if (!(dir in subDirs)) {
+        subDirs[dir] = {};
+      }
+      subDirs[dir][subDir] = 1;
+      dir = subDir;
+      addOneWarning(warningsOfDirs, dir);
+    }
+  }
+  var minDirWarnings = numWarnings*(LimitPercentWarnings/100);
+  var minFileWarnings = Math.min(LimitWarningsPerFile, minDirWarnings);
+  // Each row in TopDirs and TopFiles has
+  // [index, {v:<num_of_warnings>, f:<percent>}, file_or_dir_name]
+  function countWarnings(minWarnings, warningsOf, isDir) {
+    var rows = [];
+    for (var name in warningsOf) {
+      if (isDir && name in subDirs && Object.keys(subDirs[name]).length < 2) {
+        continue; // skip a directory if it has only one subdir
+      }
+      var count = warningsOf[name];
+      if (count >= minWarnings) {
+        name = isDir ? (name + "/...") : name;
+        var percent = (100*count/numWarnings).toFixed(1);
+        var countFormat = count + ' (' + percent + '%)';
+        rows.push([0, {v:count, f:countFormat}, name]);
+      }
+    }
+    rows.sort((a,b) => b[1].v - a[1].v);
+    for (var i=0; i<rows.length; i++) {
+      rows[i][0] = i;
+    }
+    return rows;
+  }
+  TopDirs = countWarnings(minDirWarnings, warningsOfDirs, true);
+  TopFiles = countWarnings(minFileWarnings, warningsOfFiles, false);
+}
+function genTopDirsFilesTables() {
+  computeTopDirsFiles();
+  function addTable(name, divName, rows, clickFunction) {
+    var data = new google.visualization.DataTable();
+    data.addColumn("number", "index"); // not shown in view
+    data.addColumn("number", "# of warnings");
+    data.addColumn("string", name);
+    data.addRows(rows);
+    var formatter = new google.visualization.PatternFormat(
+      '<p onclick="' + clickFunction + '({0})">{2}</p>');
+    formatter.format(data, [0, 1, 2], 2);
+    var view = new google.visualization.DataView(data);
+    view.setColumns([1,2]); // hide the index column
+    var table = new google.visualization.Table(
+        document.getElementById(divName));
+    table.draw(view, {allowHtml: true, alternatingRowStyle: true});
+  }
+  addTable("Directory", "top_dirs_table", TopDirs, "selectDir");
+  addTable("File", "top_files_table", TopFiles, "selectFile");
+}
+function selectDirFile(idx, rows, dirFile) {
+  if (rows.length <= idx) {
+    return;
+  }
+  var name = rows[idx][2];
+  var spanName = "selected_" + dirFile + "_name";
+  document.getElementById(spanName).innerHTML = name;
+  var divName = "selected_" + dirFile + "_warnings";
+  var numWarnings = rows[idx][1].v;
+  var prefix = name.replace(/\\.\\.\\.$/, "");
+  var data = new google.visualization.DataTable();
+  data.addColumn('string', numWarnings + ' warnings in ' + name);
+  var getWarningMessage = (FlagPlatform == "chrome")
+        ? ((x) => addURLToLine(WarningMessages[Warnings[x][2]],
+                               WarningLinks[Warnings[x][3]]))
+        : ((x) => addURL(WarningMessages[Warnings[x][2]]));
+  for (var i = 0; i < Warnings.length; i++) {
+    if (WarningMessages[Warnings[i][2]].startsWith(prefix)) {
+      data.addRow([getWarningMessage(i)]);
+    }
+  }
+  var table = new google.visualization.Table(
+      document.getElementById(divName));
+  table.draw(data, {allowHtml: true, alternatingRowStyle: true});
+}
+function selectDir(idx) {
+  selectDirFile(idx, TopDirs, "directory")
+}
+function selectFile(idx) {
+  selectDirFile(idx, TopFiles, "file");
+}
+function genTables() {
+  genSelectedProjectsTable();
+  if (WarningMessages.length > 1) {
+    genTopDirsFilesTables();
+  }
+}
 """
 
 
+def dump_boxed_section(writer, func):
+  writer('<div class="box">')
+  func()
+  writer('</div>')
+
+
+def dump_section_header(writer, table_name, section_title):
+  writer('<h3><b><button id="' + table_name + '_mark" class="bt"\n' +
+         ' onclick="expand(\'' + table_name + '\');">&#x2295</button></b>\n' +
+         section_title + '</h3>')
+
+
+def dump_table_section(writer, table_name, section_title):
+  dump_section_header(writer, table_name, section_title)
+  writer('<div id="' + table_name + '" style="display:none;"></div>')
+
+
+def dump_dir_file_section(writer, dir_file, table_name, section_title):
+  section_name = 'top_' + dir_file + '_section'
+  dump_section_header(writer, section_name, section_title)
+  writer('<div id="' + section_name + '" style="display:none;">')
+  writer('<div id="' + table_name + '"></div>')
+  def subsection():
+    subsection_name = 'selected_' + dir_file + '_warnings'
+    subsection_title = ('Warnings in <span id="selected_' + dir_file +
+                        '_name">(click a ' + dir_file +
+                        ' in the above table)</span>')
+    dump_section_header(writer, subsection_name, subsection_title)
+    writer('<div id="' + subsection_name + '" style="display:none;"></div>')
+  dump_boxed_section(writer, subsection)
+  writer('</div>')
+
+
+# HTML output has the following major div elements:
+#  selected_projects_section
+#  top_directory_section
+#    top_dirs_table
+#    selected_directory_warnings
+#  top_file_section
+#    top_files_table
+#    selected_file_warnings
+#  all_warnings_section
+#    warning_groups
+#    fixed_warnings
 def dump_html(flags, output_stream, warning_messages, warning_links,
               warning_records, header_str, warn_patterns, project_names):
   """Dump the flags output to output_stream."""
@@ -649,20 +819,44 @@
   dump_html_prologue('Warnings for ' + header_str, writer, warn_patterns,
                      project_names)
   dump_stats(writer, warn_patterns)
-  writer('<br><div id="stats_table"></div><br>')
-  writer('\n<script>')
-  emit_js_data(writer, flags, warning_messages, warning_links, warning_records,
-               warn_patterns, project_names)
-  writer(SCRIPTS_FOR_WARNING_GROUPS)
-  writer('</script>')
-  emit_buttons(writer)
-  # Warning messages are grouped by severities or project names.
-  writer('<br><div id="warning_groups"></div>')
-  if flags.byproject:
-    writer('<script>groupByProject();</script>')
-  else:
-    writer('<script>groupBySeverity();</script>')
-  dump_fixed(writer, warn_patterns)
+  writer('<br><br>Press &#x2295 to show section content,'
+         ' and &#x2296 to hide the content.')
+  def section1():
+    dump_table_section(writer, 'selected_projects_section',
+                       'Number of warnings in preselected project directories')
+  def section2():
+    dump_dir_file_section(
+        writer, 'directory', 'top_dirs_table',
+        'Directories with at least ' +
+        str(LIMIT_PERCENT_WARNINGS) + '% warnings')
+  def section3():
+    dump_dir_file_section(
+        writer, 'file', 'top_files_table',
+        'Files with at least ' +
+        str(LIMIT_PERCENT_WARNINGS) + '% or ' +
+        str(LIMIT_WARNINGS_PER_FILE) + ' warnings')
+  def section4():
+    writer('<script>')
+    emit_js_data(writer, flags, warning_messages, warning_links,
+                 warning_records, warn_patterns, project_names)
+    writer(SCRIPTS_FOR_WARNING_GROUPS)
+    writer('</script>')
+    dump_section_header(writer, 'all_warnings_section',
+                        'All warnings grouped by severities or projects')
+    writer('<div id="all_warnings_section" style="display:none;">')
+    emit_buttons(writer)
+    # Warning messages are grouped by severities or project names.
+    writer('<br><div id="warning_groups"></div>')
+    if flags.byproject:
+      writer('<script>groupByProject();</script>')
+    else:
+      writer('<script>groupBySeverity();</script>')
+    dump_fixed(writer, warn_patterns)
+    writer('</div>')
+  dump_boxed_section(writer, section1)
+  dump_boxed_section(writer, section2)
+  dump_boxed_section(writer, section3)
+  dump_boxed_section(writer, section4)
   dump_html_epilogue(writer)
 
 
diff --git a/tools/warn/java_warn_patterns.py b/tools/warn/java_warn_patterns.py
index 534f48d..3f5da9d 100644
--- a/tools/warn/java_warn_patterns.py
+++ b/tools/warn/java_warn_patterns.py
@@ -74,6 +74,8 @@
                 [r'.*\.class\): warning: Cannot find annotation method .+ in']),
     java_medium('No class/method in SDK ...',
                 [r'.*\.java:.*: warning: No such (class|method) .* for SDK']),
+    java_medium('Unknown enum constant',
+                [r'unknown_source_file: warning: unknown enum constant .+']),
     # Warnings generated by Error Prone
     java_medium('Non-ascii characters used, but ascii encoding specified',
                 [r".*: warning: unmappable character for encoding ascii"]),
@@ -207,6 +209,8 @@
            'Logging or rethrowing exceptions should usually be preferred to catching and calling printStackTrace'),
     medium('CatchFail',
            'Ignoring exceptions and calling fail() is unnecessary, and makes test output less useful'),
+    medium('ChangedAbstract',
+           'Method has changed \'abstract\' qualifier'),
     medium('ClassCanBeStatic',
            'Inner class is non-static but does not reference enclosing class'),
     medium('ClassNewInstance',
@@ -355,6 +359,8 @@
            'equals method doesn\'t override Object.equals'),
     medium('NotCloseable',
            'Not closeable'),
+    medium('NullableCollection',
+           'Method should not return a nullable collection'),
     medium('NullableConstructor',
            'Constructors should not be annotated with @Nullable since they cannot return null'),
     medium('NullableDereference',
@@ -801,6 +807,8 @@
                 [r".*: warning: \[path\] bad path element .*\.jar"]),
     java_medium('Supported version from annotation processor',
                 [r".*: warning: Supported source version .+ from annotation processor"]),
+    java_medium('Schema export directory is not provided',
+                [r".*\.(java|kt):.*: warning: Schema export directory is not provided"]),
 ]
 
 compile_patterns(warn_patterns)
diff --git a/tools/warn/make_warn_patterns.py b/tools/warn/make_warn_patterns.py
index a54c502..11ad5cc 100644
--- a/tools/warn/make_warn_patterns.py
+++ b/tools/warn/make_warn_patterns.py
@@ -35,6 +35,9 @@
     {'category': 'make', 'severity': Severity.HIGH,
      'description': 'System module linking to a vendor module',
      'patterns': [r".*: warning: .+ \(.+\) should not link to .+ \(partition:.+\)"]},
+    {'category': 'make', 'severity': Severity.HIGH,
+     'description': 'make: lstat file does not exist',
+     'patterns': [r".*: warning: lstat .+: file does not exist"]},
     {'category': 'make', 'severity': Severity.MEDIUM,
      'description': 'Invalid SDK/NDK linking',
      'patterns': [r".*: warning: .+ \(.+\) should not link to .+ \(.+\)"]},
@@ -56,6 +59,9 @@
     {'category': 'make', 'severity': Severity.MEDIUM,
      'description': 'make: deprecated macros',
      'patterns': [r".*\.mk:.* warning:.* [A-Z_]+ (is|has been) deprecated."]},
+    {'category': 'make', 'severity': Severity.MEDIUM,
+     'description': 'make: other Android.mk warnings',
+     'patterns': [r".*/Android.mk:.*: warning: .+"]},
 ]
 
 
diff --git a/tools/warn/other_warn_patterns.py b/tools/warn/other_warn_patterns.py
index d05c8e9..c95528c 100644
--- a/tools/warn/other_warn_patterns.py
+++ b/tools/warn/other_warn_patterns.py
@@ -75,37 +75,15 @@
     # misc warnings
     misc('Duplicate logtag',
          [r".*: warning: tag \".+\" \(.+\) duplicated in .+"]),
-    misc('Typedef redefinition',
-         [r".*: warning: redefinition of typedef '.+' is a C11 feature"]),
-    misc('GNU old-style field designator',
-         [r".*: warning: use of GNU old-style field designator extension"]),
-    misc('Missing field initializers',
-         [r".*: warning: missing field '.+' initializer"]),
-    misc('Missing braces',
-         [r".*: warning: suggest braces around initialization of",
-          r".*: warning: too many braces around scalar initializer .+Wmany-braces-around-scalar-init",
-          r".*: warning: braces around scalar initializer"]),
-    misc('Comparison of integers of different signs',
-         [r".*: warning: comparison of integers of different signs.+sign-compare"]),
-    misc('Add braces to avoid dangling else',
-         [r".*: warning: add explicit braces to avoid dangling else"]),
-    misc('Initializer overrides prior initialization',
-         [r".*: warning: initializer overrides prior initialization of this subobject"]),
-    misc('Assigning value to self',
-         [r".*: warning: explicitly assigning value of .+ to itself"]),
-    misc('GNU extension, variable sized type not at end',
-         [r".*: warning: field '.+' with variable sized type '.+' not at the end of a struct or class"]),
-    misc('Comparison of constant is always false/true',
-         [r".*: comparison of .+ is always .+Wtautological-constant-out-of-range-compare"]),
-    misc('Hides overloaded virtual function',
-         [r".*: '.+' hides overloaded virtual function"]),
-    misc('Incompatible pointer types',
-         [r".*: warning: incompatible .*pointer types .*-Wincompatible-.*pointer-types"]),
     # Assembler warnings
     asm('ASM value size does not match register size',
         [r".*: warning: value size does not match register size specified by the constraint and modifier"]),
     asm('IT instruction is deprecated',
         [r".*: warning: applying IT instruction .* is deprecated"]),
+    asm('section flags ignored',
+        [r".*: warning: section flags ignored on section redeclaration"]),
+    asm('setjmp/longjmp/vfork changed binding',
+        [r".*: warning: .*(setjmp|longjmp|vfork) changed binding to .*"]),
     # NDK warnings
     {'category': 'NDK', 'severity': Severity.HIGH,
      'description': 'NDK: Generate guard with empty availability, obsoleted',
@@ -168,6 +146,9 @@
     {'category': 'RenderScript', 'severity': Severity.LOW,
      'description': 'RenderScript warnings',
      'patterns': [r'.*\.rscript:.*: warning: ']},
+    {'category': 'RenderScript', 'severity': Severity.HIGH,
+     'description': 'RenderScript is deprecated',
+     'patterns': [r'.*: warning: Renderscript is deprecated:.+']},
     # Broken/partial warning messages will be skipped.
     {'category': 'Misc', 'severity': Severity.SKIP,
      'description': 'skip, ,',
diff --git a/tools/warn/tidy_warn_patterns.py b/tools/warn/tidy_warn_patterns.py
index 7018d10..c138f1c 100644
--- a/tools/warn/tidy_warn_patterns.py
+++ b/tools/warn/tidy_warn_patterns.py
@@ -23,15 +23,19 @@
 from .severity import Severity
 
 
-def tidy_warn_pattern(description, pattern):
+def tidy_warn(description, patterns):
   return {
       'category': 'C/C++',
       'severity': Severity.TIDY,
       'description': 'clang-tidy ' + description,
-      'patterns': [r'.*: .+\[' + pattern + r'\]$']
+      'patterns': patterns,
   }
 
 
+def tidy_warn_pattern(description, pattern):
+  return tidy_warn(description, [r'.*: .+\[' + pattern + r'\]$'])
+
+
 def simple_tidy_warn_pattern(description):
   return tidy_warn_pattern(description, description)
 
@@ -77,6 +81,7 @@
 warn_patterns = [
     # pylint does not recognize g-inconsistent-quotes
     # pylint:disable=line-too-long,bad-option-value,g-inconsistent-quotes
+    group_tidy_warn_pattern('altera'),
     group_tidy_warn_pattern('android'),
     simple_tidy_warn_pattern('abseil-string-find-startswith'),
     simple_tidy_warn_pattern('bugprone-argument-comment'),
@@ -123,8 +128,9 @@
     simple_tidy_warn_pattern('cert-oop54-cpp'),
     group_tidy_warn_pattern('cert'),
     group_tidy_warn_pattern('clang-diagnostic'),
+    group_tidy_warn_pattern('concurrency'),
     group_tidy_warn_pattern('cppcoreguidelines'),
-    group_tidy_warn_pattern('llvm'),
+    group_tidy_warn_pattern('fuchsia'),
     simple_tidy_warn_pattern('google-default-arguments'),
     simple_tidy_warn_pattern('google-runtime-int'),
     simple_tidy_warn_pattern('google-runtime-operator'),
@@ -148,8 +154,10 @@
     simple_tidy_warn_pattern('hicpp-noexcept-move'),
     simple_tidy_warn_pattern('hicpp-use-override'),
     group_tidy_warn_pattern('hicpp'),
-    group_tidy_warn_pattern('modernize'),
+    group_tidy_warn_pattern('llvm'),
+    group_tidy_warn_pattern('llvmlibc'),
     group_tidy_warn_pattern('misc'),
+    group_tidy_warn_pattern('modernize'),
     simple_tidy_warn_pattern('performance-faster-string-find'),
     simple_tidy_warn_pattern('performance-for-range-copy'),
     simple_tidy_warn_pattern('performance-implicit-cast-in-loop'),
@@ -168,6 +176,9 @@
     simple_tidy_warn_pattern('portability-simd-intrinsics'),
     group_tidy_warn_pattern('portability'),
 
+    tidy_warn('TIMEOUT', [r".*: warning: clang-tidy aborted "]),
+    tidy_warn('Long Runs', [r".*: warning: clang-tidy used "]),
+
     # warnings from clang-tidy's clang-analyzer checks
     analyzer_high('clang-analyzer-core, null pointer',
                   [r".*: warning: .+ pointer is null .*\[clang-analyzer-core"]),
@@ -213,6 +224,9 @@
     analyzer_warn_check('clang-analyzer-valist.Unterminated'),
     analyzer_group_check('clang-analyzer-core.uninitialized'),
     analyzer_group_check('clang-analyzer-deadcode'),
+    analyzer_warn_check('clang-analyzer-security.insecureAPI.bcmp'),
+    analyzer_warn_check('clang-analyzer-security.insecureAPI.bcopy'),
+    analyzer_warn_check('clang-analyzer-security.insecureAPI.bzero'),
     analyzer_warn_check('clang-analyzer-security.insecureAPI.strcpy'),
     analyzer_group_high('clang-analyzer-security.insecureAPI'),
     analyzer_group_high('clang-analyzer-security'),
diff --git a/tools/warn/warn_common.py b/tools/warn/warn_common.py
index 844f629..61c8676 100755
--- a/tools/warn/warn_common.py
+++ b/tools/warn/warn_common.py
@@ -236,7 +236,11 @@
   warning_pattern = re.compile('^/[^ ]*/[^ ]*: warning: .*')
   count = 0
   for line in buildlog:
-    if warning_pattern.match(line):
+    # We want to find android_root of a local build machine.
+    # Do not use RBE warning lines, which has '/b/f/w/' path prefix.
+    # Do not use /tmp/ file warnings.
+    if warning_pattern.match(line) and (
+        '/b/f/w' not in line and not line.startswith('/tmp/')):
       warning_lines.append(line)
       count += 1
       if count > 9999:
@@ -300,6 +304,7 @@
   architecture = 'unknown'
 
   # only handle warning lines of format 'file_path:line_no:col_no: warning: ...'
+  # Bug: http://198657613, This might need change to handle RBE output.
   chrome_warning_pattern = r'^[^ ]*/[^ ]*:[0-9]+:[0-9]+: warning: .*'
 
   warning_pattern = re.compile(chrome_warning_pattern)
@@ -347,6 +352,8 @@
   platform_version = 'unknown'
   target_product = 'unknown'
   target_variant = 'unknown'
+  build_id = 'unknown'
+  use_rbe = False
   android_root = find_android_root(infile)
   infile.seek(0)
 
@@ -363,6 +370,14 @@
   warning_without_file = re.compile('^warning: .*')
   rustc_file_position = re.compile('^[ ]+--> [^ ]*/[^ ]*:[0-9]+:[0-9]+')
 
+  # If RBE was used, try to reclaim some warning lines mixed with some
+  # leading chars from other concurrent job's stderr output .
+  # The leading characters can be any character, including digits and spaces.
+  # It's impossible to correctly identify the starting point of the source
+  # file path without the file directory name knowledge.
+  # Here we can only be sure to recover lines containing "/b/f/w/".
+  rbe_warning_pattern = re.compile('.*/b/f/w/[^ ]*: warning: .*')
+
    # Collect all unique warning lines
   # Remove the duplicated warnings save ~8% of time when parsing
   # one typical build log than before
@@ -384,6 +399,12 @@
           prev_warning, flags, android_root, unique_warnings)
       prev_warning = ''
 
+    if use_rbe and rbe_warning_pattern.match(line):
+      cleaned_up_line = re.sub('.*/b/f/w/', '', line)
+      unique_warnings = add_normalized_line_to_warnings(
+          cleaned_up_line, flags, android_root, unique_warnings)
+      continue
+
     if warning_pattern.match(line):
       if warning_without_file.match(line):
         # save this line and combine it with the next line
@@ -399,15 +420,26 @@
       result = re.search('(?<=^PLATFORM_VERSION=).*', line)
       if result is not None:
         platform_version = result.group(0)
+        continue
       result = re.search('(?<=^TARGET_PRODUCT=).*', line)
       if result is not None:
         target_product = result.group(0)
+        continue
       result = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
       if result is not None:
         target_variant = result.group(0)
+        continue
+      result = re.search('(?<=^BUILD_ID=).*', line)
+      if result is not None:
+        build_id = result.group(0)
+        continue
       result = re.search('(?<=^TOP=).*', line)
       if result is not None:
         android_root = result.group(1)
+        continue
+      if re.search('USE_RBE=', line) is not None:
+        use_rbe = True
+        continue
 
   if android_root:
     new_unique_warnings = dict()
@@ -418,8 +450,8 @@
           warning_line, flags, android_root)
     unique_warnings = new_unique_warnings
 
-  header_str = '%s - %s - %s' % (platform_version, target_product,
-                                 target_variant)
+  header_str = '%s - %s - %s (%s)' % (
+      platform_version, target_product, target_variant, build_id)
   return unique_warnings, header_str
 
 
diff --git a/tools/zipalign/ZipAlignMain.cpp b/tools/zipalign/ZipAlignMain.cpp
index 47ebd12..53fc8d4 100644
--- a/tools/zipalign/ZipAlignMain.cpp
+++ b/tools/zipalign/ZipAlignMain.cpp
@@ -20,6 +20,7 @@
 
 #include "ZipAlign.h"
 
+#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -60,69 +61,53 @@
     int alignment;
     char* endp;
 
-    if (argc < 4) {
-        wantUsage = true;
-        goto bail;
-    }
-
-    argc--;
-    argv++;
-
-    while (argc && argv[0][0] == '-') {
-        const char* cp = argv[0] +1;
-
-        while (*cp != '\0') {
-            switch (*cp) {
-            case 'c':
-                check = true;
-                break;
-            case 'f':
-                force = true;
-                break;
-            case 'v':
-                verbose = true;
-                break;
-            case 'z':
-                zopfli = true;
-                break;
-            case 'p':
-                pageAlignSharedLibs = true;
-                break;
-            default:
-                fprintf(stderr, "ERROR: unknown flag -%c\n", *cp);
-                wantUsage = true;
-                goto bail;
-            }
-
-            cp++;
+    int opt;
+    while ((opt = getopt(argc, argv, "fcpvz")) != -1) {
+        switch (opt) {
+        case 'c':
+            check = true;
+            break;
+        case 'f':
+            force = true;
+            break;
+        case 'v':
+            verbose = true;
+            break;
+        case 'z':
+            zopfli = true;
+            break;
+        case 'p':
+            pageAlignSharedLibs = true;
+            break;
+        default:
+            fprintf(stderr, "ERROR: unknown flag -%c\n", opt);
+            wantUsage = true;
+            goto bail;
         }
-
-        argc--;
-        argv++;
     }
 
-    if (!((check && argc == 2) || (!check && argc == 3))) {
+    if (!((check && (argc - optind) == 2) || (!check && (argc - optind) == 3))) {
         wantUsage = true;
         goto bail;
     }
 
-    alignment = strtol(argv[0], &endp, 10);
+    alignment = strtol(argv[optind], &endp, 10);
     if (*endp != '\0' || alignment <= 0) {
-        fprintf(stderr, "Invalid value for alignment: %s\n", argv[0]);
+        fprintf(stderr, "Invalid value for alignment: %s\n", argv[optind]);
         wantUsage = true;
         goto bail;
     }
 
     if (check) {
         /* check existing archive for correct alignment */
-        result = verify(argv[1], alignment, verbose, pageAlignSharedLibs);
+        result = verify(argv[optind + 1], alignment, verbose, pageAlignSharedLibs);
     } else {
         /* create the new archive */
-        result = process(argv[1], argv[2], alignment, force, zopfli, pageAlignSharedLibs);
+        result = process(argv[optind + 1], argv[optind + 2], alignment, force, zopfli, pageAlignSharedLibs);
 
         /* trust, but verify */
         if (result == 0) {
-            result = verify(argv[2], alignment, verbose, pageAlignSharedLibs);
+            result = verify(argv[optind + 2], alignment, verbose, pageAlignSharedLibs);
         }
     }
 
diff --git a/tools/zipalign/ZipEntry.cpp b/tools/zipalign/ZipEntry.cpp
index 5233f0a..fcad96c 100644
--- a/tools/zipalign/ZipEntry.cpp
+++ b/tools/zipalign/ZipEntry.cpp
@@ -87,7 +87,7 @@
     }
 
     /*
-     * Sanity-check the LFH.  Note that this will fail if the "kUsesDataDescr"
+     * Check the LFH.  Note that this will fail if the "kUsesDataDescr"
      * flag is set, because the LFH is incomplete.  (Not a problem, since we
      * prefer the CDE values.)
      */
diff --git a/tools/zipalign/ZipFile.cpp b/tools/zipalign/ZipFile.cpp
index 1e3c413..f2f65a6 100644
--- a/tools/zipalign/ZipFile.cpp
+++ b/tools/zipalign/ZipFile.cpp
@@ -530,7 +530,7 @@
     // If the alignment is not what was requested, add some padding in the extra
     // so the payload ends up where is requested.
     uint64_t alignDiff = alignTo - (expectedPayloadOffset % alignTo);
-    if (alignDiff == 0)
+    if (alignDiff == alignTo)
         return OK;
 
     return pEntry->addPadding(alignDiff);
@@ -654,7 +654,7 @@
 {
     ZipEntry* pEntry = NULL;
     status_t result;
-    long lfhPosn, startPosn, endPosn, uncompressedLen;
+    long lfhPosn, uncompressedLen;
 
     if (mReadOnly)
         return INVALID_OPERATION;
@@ -690,7 +690,6 @@
      */
     lfhPosn = ftell(mZipFp);
     pEntry->mLFH.write(mZipFp);
-    startPosn = ftell(mZipFp);
 
     /*
      * Copy the data over.
@@ -741,18 +740,13 @@
     }
 
     /*
-     * Update file offsets.
-     */
-    endPosn = ftell(mZipFp);
-
-    /*
      * Success!  Fill out new values.
      */
     pEntry->setLFHOffset(lfhPosn);
     mEOCD.mNumEntries++;
     mEOCD.mTotalNumEntries++;
     mEOCD.mCentralDirSize = 0;      // mark invalid; set by flush()
-    mEOCD.mCentralDirOffset = endPosn;
+    mEOCD.mCentralDirOffset = ftell(mZipFp);
 
     /*
      * Go back and write the LFH.
diff --git a/tools/zipalign/tests/src/align_test.cpp b/tools/zipalign/tests/src/align_test.cpp
index c79e791..ff45187 100644
--- a/tools/zipalign/tests/src/align_test.cpp
+++ b/tools/zipalign/tests/src/align_test.cpp
@@ -3,21 +3,29 @@
 
 #include "ZipAlign.h"
 
+#include <filesystem>
 #include <stdio.h>
 #include <string>
 
 #include <android-base/file.h>
 
 using namespace android;
+using namespace base;
 
 static std::string GetTestPath(const std::string& filename) {
   static std::string test_data_dir = android::base::GetExecutableDirectory() + "/tests/data/";
   return test_data_dir + filename;
 }
 
+static std::string GetTempPath(const std::string& filename) {
+  std::filesystem::path temp_path = std::filesystem::path(testing::TempDir());
+  temp_path += filename;
+  return temp_path.string();
+}
+
 TEST(Align, Unaligned) {
   const std::string src = GetTestPath("unaligned.zip");
-  const std::string dst = GetTestPath("unaligned_out.zip");
+  const std::string dst = GetTempPath("unaligned_out.zip");
 
   int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
   ASSERT_EQ(0, processed);
@@ -26,12 +34,40 @@
   ASSERT_EQ(0, verified);
 }
 
+TEST(Align, DoubleAligment) {
+  const std::string src = GetTestPath("unaligned.zip");
+  const std::string tmp = GetTempPath("da_aligned.zip");
+  const std::string dst = GetTempPath("da_d_aligner.zip");
+
+  int processed = process(src.c_str(), tmp.c_str(), 4, true, false, 4096);
+  ASSERT_EQ(0, processed);
+
+  int verified = verify(tmp.c_str(), 4, true, false);
+  ASSERT_EQ(0, verified);
+
+  // Align the result of the previous run. Essentially double aligning.
+  processed = process(tmp.c_str(), dst.c_str(), 4, true, false, 4096);
+  ASSERT_EQ(0, processed);
+
+  verified = verify(dst.c_str(), 4, true, false);
+  ASSERT_EQ(0, verified);
+
+  // Nothing should have changed between tmp and dst.
+  std::string tmp_content;
+  ASSERT_EQ(true, ReadFileToString(tmp, &tmp_content));
+
+  std::string dst_content;
+  ASSERT_EQ(true, ReadFileToString(dst, &dst_content));
+
+  ASSERT_EQ(tmp_content, dst_content);
+}
+
 // Align a zip featuring a hole at the beginning. The
 // hole in the archive is a delete entry in the Central
 // Directory.
 TEST(Align, Holes) {
   const std::string src = GetTestPath("holes.zip");
-  const std::string dst = GetTestPath("holes_out.zip");
+  const std::string dst = GetTempPath("holes_out.zip");
 
   int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
   ASSERT_EQ(0, processed);
@@ -43,7 +79,7 @@
 // Align a zip where LFH order and CD entries differ.
 TEST(Align, DifferenteOrders) {
   const std::string src = GetTestPath("diffOrders.zip");
-  const std::string dst = GetTestPath("diffOrders_out.zip");
+  const std::string dst = GetTempPath("diffOrders_out.zip");
 
   int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
   ASSERT_EQ(0, processed);