Merge "Configure boot image profiles for platform and unbundled ART module builds."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b8148eb..dd5c476 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -766,6 +766,9 @@
 # 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))
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
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/core/Makefile b/core/Makefile
index 486188e..44d4a9e 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -527,6 +527,16 @@
     $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,GENERIC_RAMDISK,$(TARGET_RAMDISK_OUT),,modules.load,,$(kmd)))))
 
 # -----------------------------------------------------------------
+# FSVerity metadata generation
+ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
+
+FSVERITY_APK_KEY_PATH := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
+FSVERITY_APK_OUT := system/etc/security/fsverity/BuildManifest.apk
+FSVERITY_APK_MANIFEST_PATH := system/security/fsverity/AndroidManifest.xml
+
+endif # PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
+# -----------------------------------------------------------------
 # Cert-to-package mapping.  Used by the post-build signing tools.
 # Use a macro to add newline to each echo command
 # $1 stem name of the package
@@ -575,6 +585,8 @@
 	    $(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 $@
 
@@ -1672,6 +1684,11 @@
 $(if $(filter $(2),system),\
     $(if $(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE),$(hide) echo "system_other_size=$(INTERNAL_SYSTEM_OTHER_PARTITION_SIZE)" >> $(1))
     $(if $(PRODUCT_SYSTEM_HEADROOM),$(hide) echo "system_headroom=$(PRODUCT_SYSTEM_HEADROOM)" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity=$(HOST_OUT_EXECUTABLES)/fsverity" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_generate_metadata=true" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_key=$(FSVERITY_APK_KEY_PATH)" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_manifest=$(FSVERITY_APK_MANIFEST_PATH)" >> $(1))
+    $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),$(hide) echo "fsverity_apk_out=$(FSVERITY_APK_OUT)" >> $(1))
     $(call add-common-ro-flags-to-image-props,system,$(1))
 )
 $(if $(filter $(2),system_other),\
@@ -2773,6 +2790,10 @@
 ifeq ($(BOARD_AVB_ENABLE),true)
 $(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
 endif
+ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
+$(BUILT_SYSTEMIMAGE): $(HOST_OUT_EXECUTABLES)/fsverity $(HOST_OUT_EXECUTABLES)/aapt2 \
+    $(FSVERITY_APK_MANIFEST_PATH) $(FSVERITY_APK_KEY_PATH).x509.pem $(FSVERITY_APK_KEY_PATH).pk8
+endif
 $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
 	$(call build-systemimage-target,$@)
 
@@ -5433,6 +5454,8 @@
 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)
diff --git a/core/base_rules.mk b/core/base_rules.mk
index 300fdda..02b424d 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -514,12 +514,6 @@
 ###########################################################
 
 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 (,$(LOCAL_SOONG_INSTALLED_MODULE))
   # Soong already generated the copy rule, but make the installed location depend on the Make
@@ -531,17 +525,15 @@
     $(call declare-0p-target,$(symlink)))
   $(my_all_targets) : | $(LOCAL_SOONG_INSTALL_SYMLINKS)
 else 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)
+  $(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
-	$(PRIVATE_POST_INSTALL_CMD)
   endif
+	$(PRIVATE_POST_INSTALL_CMD)
 
   # 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)))
@@ -739,8 +731,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) := ))
 
 
@@ -1075,7 +1068,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) \
@@ -1092,7 +1085,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/config.mk b/core/config.mk
index e24e957..7c93610 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -304,7 +304,7 @@
 endef
 
 # soong_config_append appends to the value of the variable in the given Soong
-# config namespace. If the varabile does not exist, it will be defined. If the
+# 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
@@ -312,6 +312,14 @@
 $(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
@@ -753,13 +761,16 @@
 endif
 .KATI_READONLY := BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
 
-min_systemsdk_version := $(firstword $(BOARD_API_LEVEL) $(BOARD_SHIPPING_API_LEVEL) $(PRODUCT_SHIPPING_API_LEVEL))
-ifneq (,$(min_systemsdk_version))
-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
-endif
 ifdef 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)
       ifneq ($(TARGET_USES_64_BIT_BINDER), true)
@@ -1212,7 +1223,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/java_common.mk b/core/java_common.mk
index 1798ca8..1073a78 100644
--- a/core/java_common.mk
+++ b/core/java_common.mk
@@ -378,9 +378,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/node_fns.mk b/core/node_fns.mk
index 8d20160..2243cd7 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/product.mk b/core/product.mk
index 23fb939..683c429 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -440,6 +440,16 @@
 # This option is only meant to be set by GSI products.
 _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
+
 .KATI_READONLY := _product_single_value_vars _product_list_vars
 _product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
 
diff --git a/core/product_config.rbc b/core/product_config.rbc
index fe6ba7c..24e38b1 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -321,6 +321,11 @@
         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(path):
     """Provided for compatibility, to be removed later."""
     return path
@@ -644,6 +649,7 @@
     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,
     addprefix = _addprefix,
     addsuffix = _addsuffix,
diff --git a/core/soong_app_prebuilt.mk b/core/soong_app_prebuilt.mk
index ee06432..dcb5a2e 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -138,13 +138,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_java_prebuilt.mk b/core/soong_java_prebuilt.mk
index 2c909ac..801a265 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -154,13 +154,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/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 06b25d4..ee63757 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
 
diff --git a/target/product/security/README b/target/product/security/README
index 4ad5236..2b161bb 100644
--- a/target/product/security/README
+++ b/target/product/security/README
@@ -16,7 +16,6 @@
   development/tools/make_key shared        '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
   development/tools/make_key media         '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
   development/tools/make_key cts_uicc_2021 '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
-  development/tools/make_key fsverity      '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
 
 signing using the openssl commandline (for boot/system images)
 --------------------------------------------------------------
diff --git a/target/product/security/fsverity.pk8 b/target/product/security/fsverity.pk8
deleted file mode 100644
index 5bb69dc..0000000
--- a/target/product/security/fsverity.pk8
+++ /dev/null
Binary files differ
diff --git a/target/product/security/fsverity.x509.pem b/target/product/security/fsverity.x509.pem
deleted file mode 100644
index b29c711..0000000
--- a/target/product/security/fsverity.x509.pem
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIECzCCAvOgAwIBAgIUDkPsN3C2kwiPnOnNZiHrK5S6oqowDQYJKoZIhvcNAQEL
-BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
-ZHJvaWQuY29tMB4XDTIxMTAxMjA0MzUyMFoXDTQ5MDIyNzA0MzUyMFowgZQxCzAJ
-BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
-biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
-VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1N8ro0RTY2Cl91daJvjo
-tDLjHrwrzSAQaVpEXGddPJYs0m8ej3Oh7Hbo4+ju36CIjgH9xDgpOb9LeTUMSXLF
-9Rlkdhz4VJlvaQuYz10FoqkvQo2/IsD2pAq3EktOHexfXCG8fhdCaVkayAuKX5ou
-+RchZWCPwVhBx6fbpZeGhkFg6f7CwPSMEJ5DNtvHUieny8OwIbml0NILQjavP4nU
-GGJxkyKgodUYCdnOSE7FCUv875Op9e0ryTPvUZhKHPoRMe5enEgfq/WXVdqLhifF
-k6gYelcfq1bFRpwBm5KntX1b39V52vYUqXM8gD8Wy5RNo+aF0msJ6aBVcYeQsMlY
-4QIDAQABo1MwUTAdBgNVHQ4EFgQURbNJabjEzJ2CZzqIrX/ppnDM9l4wHwYDVR0j
-BBgwFoAURbNJabjEzJ2CZzqIrX/ppnDM9l4wDwYDVR0TAQH/BAUwAwEB/zANBgkq
-hkiG9w0BAQsFAAOCAQEAl3eEb9xzlwAG31WKorYzflvFLX+LSuVMN3FEcZBcCXsW
-+5QPfyvbJ2AgBzJmuH4XeGH0PebgLQN3PA4p9M0ZgXcHf4KBrSOMfpwUsFiTiD+z
-9KJxr4MTyXyFxO3rVlVCg/za0V8om2cRWsOb2TPRu8qeUSIT4yIj/pOXmz66b4xL
-5fKCuI7khRADCRnwyhPD9/f2/udB6qYx2MvDRchHMLqLvCzHJPS4gjhDTJJSo/st
-/GKqHWspHl5IbpRNlQci1ncc1RLub5gxPwlkIcNlOcziD+eYWeSn5B7v+5uIqxdP
-VY+WltSg4FEEzKFMjzfNpk1Uz+J6h2bi3VS0WZXdXQ==
------END CERTIFICATE-----
diff --git a/tests/run.rbc b/tests/run.rbc
index 3bb9b55..b40d1c6 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -94,3 +94,6 @@
 
 assert_eq("S", globals["PLATFORM_VERSION"])
 assert_eq(30, 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"))
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 827aaac..a979a8e 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -50,6 +50,7 @@
     ],
     libs: [
         "releasetools_common",
+        "releasetools_fsverity_metadata_generator",
         "releasetools_verity_utils",
     ],
     required: [
@@ -260,6 +261,16 @@
 }
 
 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",
     srcs: [
         "verity_utils.py",
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 38104af..8a5d627 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
@@ -34,6 +35,9 @@
 import common
 import verity_utils
 
+from fsverity_digests_pb2 import FSVerityDigests
+from fsverity_metadata_generator import FSVerityMetadataGenerator
+
 logger = logging.getLogger(__name__)
 
 OPTIONS = common.OPTIONS
@@ -447,6 +451,68 @@
 
   return mkfs_output
 
+def GenerateFSVerityMetadata(in_dir, fsverity_path, apk_key_path, apk_manifest_path, apk_out_path):
+  """Generates fsverity metadata files.
+
+  By setting PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA := true, fsverity
+  metadata files will be generated. For the input files, see `patterns` below.
+
+  One metadata file per one input file will be generated with the suffix
+  .fsv_meta. e.g. system/framework/foo.jar -> system/framework/foo.jar.fsv_meta
+  Also a mapping file containing fsverity digests will be generated to
+  system/etc/security/fsverity/BuildManifest.apk.
+
+  Args:
+    in_dir: temporary working directory (same as BuildImage)
+    fsverity_path: path to host tool fsverity
+    apk_key_path: path to key (e.g. build/make/target/product/security/platform)
+    apk_manifest_path: path to AndroidManifest.xml for APK
+    apk_out_path: path to the output APK
+
+  Returns:
+    None. The files are generated directly under in_dir.
+  """
+
+  patterns = [
+    "system/framework/*.jar",
+    "system/framework/oat/*/*.oat",
+    "system/framework/oat/*/*.vdex",
+    "system/framework/oat/*/*.art",
+    "system/etc/boot-image.prof",
+    "system/etc/dirty-image-objects",
+  ]
+  files = []
+  for pattern in patterns:
+    files += glob.glob(os.path.join(in_dir, pattern))
+  files = sorted(set(files))
+
+  generator = FSVerityMetadataGenerator(fsverity_path)
+  generator.set_hash_alg("sha256")
+
+  digests = FSVerityDigests()
+  for f in files:
+    generator.generate(f)
+    # f is a full path for now; make it relative so it starts with {mount_point}/
+    digest = digests.digests[os.path.relpath(f, in_dir)]
+    digest.digest = generator.digest(f)
+    digest.hash_alg = "sha256"
+
+  temp_dir = common.MakeTempDir()
+
+  os.mkdir(os.path.join(temp_dir, "assets"))
+  metadata_path = os.path.join(temp_dir, "assets", "build_manifest")
+  with open(metadata_path, "wb") as f:
+    f.write(digests.SerializeToString())
+
+  apk_path = os.path.join(in_dir, apk_out_path)
+
+  common.RunAndCheckOutput(["aapt2", "link",
+      "-A", os.path.join(temp_dir, "assets"),
+      "-o", apk_path,
+      "--manifest", apk_manifest_path])
+  common.RunAndCheckOutput(["apksigner", "sign", "--in", apk_path,
+      "--cert", apk_key_path + ".x509.pem",
+      "--key", apk_key_path + ".pk8"])
 
 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.
@@ -475,6 +541,13 @@
   elif fs_type.startswith("f2fs") and prop_dict.get("f2fs_compress") == "true":
     fs_spans_partition = False
 
+  if "fsverity_generate_metadata" in prop_dict:
+    GenerateFSVerityMetadata(in_dir,
+        fsverity_path=prop_dict["fsverity"],
+        apk_key_path=prop_dict["fsverity_apk_key"],
+        apk_manifest_path=prop_dict["fsverity_apk_manifest"],
+        apk_out_path=prop_dict["fsverity_apk_out"])
+
   # Get a builder for creating an image that's to be verified by Verified Boot,
   # or None if not applicable.
   verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict)
@@ -589,7 +662,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.
 
@@ -725,6 +797,11 @@
     copy_prop("system_root_image", "system_root_image")
     copy_prop("root_dir", "root_dir")
     copy_prop("root_fs_config", "root_fs_config")
+    copy_prop("fsverity", "fsverity")
+    copy_prop("fsverity_generate_metadata", "fsverity_generate_metadata")
+    copy_prop("fsverity_apk_key","fsverity_apk_key")
+    copy_prop("fsverity_apk_manifest","fsverity_apk_manifest")
+    copy_prop("fsverity_apk_out","fsverity_apk_out")
   elif mount_point == "data":
     # Copy the generic fs type first, override with specific one if available.
     copy_prop("flash_logical_block_size", "flash_logical_block_size")
diff --git a/tools/releasetools/fsverity_metadata_generator.py b/tools/releasetools/fsverity_metadata_generator.py
new file mode 100644
index 0000000..666efd5
--- /dev/null
+++ b/tools/releasetools/fsverity_metadata_generator.py
@@ -0,0 +1,231 @@
+#!/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(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)
+
+      # convert DER private key to PEM
+      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)
+
+      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))
+
+      # 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',
+      help='PKCS#8 private key file in DER format')
+  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_hash_alg(args.hash_alg)
+  generator.generate(args.input, args.output)
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):