Merge "Adapt to gdbrunner packaging."
diff --git a/core/Makefile b/core/Makefile
index accb05a..ccfa3f2 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -576,6 +576,24 @@
     $(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_CHARGER_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_OUT_VENDOR))))
 endef
 
+# $(1): kernel module directory name (top is an out of band value for no directory)
+define build-vendor-ramdisk-charger-load
+$(if $(filter top,$(1)),\
+  $(eval _kver :=)$(eval _sep :=),\
+  $(eval _kver := $(1))$(eval _sep :=_))\
+  $(if $(BOARD_VENDOR_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),\
+    $(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_ramdisk_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_RAMDISK_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_VENDOR_RAMDISK_OUT))))
+endef
+
+# $(1): kernel module directory name (top is an out of band value for no directory)
+define build-vendor-kernel-ramdisk-charger-load
+$(if $(filter top,$(1)),\
+  $(eval _kver :=)$(eval _sep :=),\
+  $(eval _kver := $(1))$(eval _sep :=_))\
+  $(if $(BOARD_VENDOR_KERNEL_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),\
+    $(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_kernel_ramdisk_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_KERNEL_RAMDISK_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_KERNEL_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_VENDOR_KERNEL_RAMDISK_OUT))))
+endef
+
 ifneq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
   # If there is no vendor boot partition, store vendor ramdisk kernel modules in the
   # boot ramdisk.
@@ -641,6 +659,8 @@
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-kernel-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),$(BOARD_SYSTEM_KERNEL_MODULES),system)) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-charger-load,$(kmd))) \
+  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-ramdisk-charger-load,$(kmd))) \
+  $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-kernel-ramdisk-charger-load,$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,ODM,$(if $(filter true,$(BOARD_USES_ODM_DLKMIMAGE)),$(TARGET_OUT_ODM_DLKM),$(TARGET_OUT_ODM)),odm,modules.load,,$(kmd))) \
   $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,SYSTEM,$(if $(filter true,$(BOARD_USES_SYSTEM_DLKMIMAGE)),$(TARGET_OUT_SYSTEM_DLKM),$(TARGET_OUT_SYSTEM)),system,modules.load,,$(kmd))) \
   $(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
@@ -5197,6 +5217,7 @@
   lz4 \
   make_f2fs \
   make_f2fs_casefold \
+  merge_ota \
   merge_target_files \
   minigzip \
   mk_combined_img \
@@ -7282,17 +7303,9 @@
 haiku: $(SOONG_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_FUZZ_TARGETS)
 $(call dist-for-goals,haiku,$(SOONG_FUZZ_PACKAGING_ARCH_MODULES))
 $(call dist-for-goals,haiku,$(PRODUCT_OUT)/module-info.json)
-
-.PHONY: haiku-java-device
-haiku-java-device: $(SOONG_JAVA_FUZZ_DEVICE_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_DEVICE_TARGETS)
-$(call dist-for-goals,haiku-java-device,$(SOONG_JAVA_FUZZ_DEVICE_PACKAGING_ARCH_MODULES))
-$(call dist-for-goals,haiku-java-device,$(PRODUCT_OUT)/module-info.json)
-
-.PHONY: haiku-java-host
-haiku-java-host: $(SOONG_JAVA_FUZZ_HOST_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_HOST_TARGETS)
-$(call dist-for-goals,haiku-java-host,$(SOONG_JAVA_FUZZ_HOST_PACKAGING_ARCH_MODULES))
-$(call dist-for-goals,haiku-java-host,$(PRODUCT_OUT)/module-info.json)
-
+.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))
diff --git a/core/base_rules.mk b/core/base_rules.mk
index c453469..65e80fb 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -190,18 +190,6 @@
 $(call pretty-error,unusual tags: $(filter-out tests optional samples,$(my_module_tags)))
 endif
 
-# Add implicit tags.
-#
-# If the local directory or one of its parents contains a MODULE_LICENSE_GPL
-# file, tag the module as "gnu".  Search for "*_GPL*", "*_LGPL*" and "*_MPL*"
-# so that we can also find files like MODULE_LICENSE_GPL_AND_AFL
-#
-gpl_license_file := $(call find-parent-file,$(LOCAL_PATH),MODULE_LICENSE*_GPL* MODULE_LICENSE*_MPL* MODULE_LICENSE*_LGPL*)
-ifneq ($(gpl_license_file),)
-  my_module_tags += gnu
-  ALL_GPL_MODULE_LICENSE_FILES += $(gpl_license_file)
-endif
-
 LOCAL_MODULE_CLASS := $(strip $(LOCAL_MODULE_CLASS))
 ifneq ($(words $(LOCAL_MODULE_CLASS)),1)
   $(error $(LOCAL_PATH): LOCAL_MODULE_CLASS must contain exactly one word, not "$(LOCAL_MODULE_CLASS)")
diff --git a/core/config.mk b/core/config.mk
index 396aad0..e272389 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -271,7 +271,7 @@
 # Ex: $(call add_soong_config_namespace,acme)
 
 define add_soong_config_namespace
-$(eval SOONG_CONFIG_NAMESPACES += $1) \
+$(eval SOONG_CONFIG_NAMESPACES += $(strip $1)) \
 $(eval SOONG_CONFIG_$(strip $1) :=)
 endef
 
@@ -281,8 +281,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_$(strip $1) += $2) \
-$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $($v)))
+$(eval SOONG_CONFIG_$(strip $1) += $(strip $2)) \
+$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $(strip $($v))))
 endef
 
 # The add_soong_config_var_value function defines a make variable and also adds
@@ -291,7 +291,7 @@
 # Ex: $(call add_soong_config_var_value,acme,COOL_FEATURE,true)
 
 define add_soong_config_var_value
-$(eval $2 := $3) \
+$(eval $(strip $2) := $(strip $3)) \
 $(call add_soong_config_var,$1,$2)
 endef
 
@@ -299,8 +299,8 @@
 #
 # 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))
+$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $(strip $1))) \
+$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $(strip $2)))
 endef
 
 # soong_config_set defines the variable in the given Soong config namespace
@@ -309,7 +309,7 @@
 # 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)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(strip $3))
 endef
 
 # soong_config_append appends to the value of the variable in the given Soong
@@ -318,7 +318,7 @@
 # $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)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $(strip $3))
 endef
 
 # soong_config_append gets to the value of the variable in the given Soong
diff --git a/core/definitions.mk b/core/definitions.mk
index e4cee7a..7697211 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -75,9 +75,6 @@
 # All findbugs xml files
 ALL_FINDBUGS_FILES:=
 
-# GPL module license files
-ALL_GPL_MODULE_LICENSE_FILES:=
-
 # Packages with certificate violation
 CERTIFICATE_VIOLATION_MODULES :=
 
@@ -897,7 +894,8 @@
 endef
 
 ###########################################################
-## Declare license dependencies $(2) for non-module target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module target $(1)
 ###########################################################
 define declare-license-deps
 $(strip \
@@ -909,7 +907,8 @@
 endef
 
 ###########################################################
-## Declare license dependencies $(2) for non-module container-type target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module container-type target $(1)
 ##
 ## Container-type targets are targets like .zip files that
 ## merely aggregate other files.
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 8887ddc..860ce79 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -30,13 +30,14 @@
 # In order to avoid running starlark every time the stamp file is checked, we use
 # $(KATI_shell_no_rerun). Then, to make sure that we actually do rerun kati when
 # modifying the starlark files, we add the starlark files to the kati stamp file with
-# $(KATI_extra_file_deps).
+# $(KATI_extra_file_deps). This behavior can be modified by passing a list of starlark files
+# to exclude from the dependency list as $(2)
 define run-starlark
 $(eval _starlark_results := $(OUT_DIR)/starlark_results/$(subst /,_,$(1)).mk)
 $(KATI_shell_no_rerun mkdir -p $(OUT_DIR)/starlark_results && $(OUT_DIR)/rbcrun --mode=make $(1) >$(_starlark_results) && touch -t 200001010000 $(_starlark_results))
 $(if $(filter-out 0,$(.SHELLSTATUS)),$(error Starlark failed to run))
 $(eval include $(_starlark_results))
-$(KATI_extra_file_deps $(LOADED_STARLARK_FILES))
+$(KATI_extra_file_deps $(filter-out $(2),$(LOADED_STARLARK_FILES)))
 $(eval LOADED_STARLARK_FILES :=)
 $(eval _starlark_results :=)
 endef
diff --git a/core/main.mk b/core/main.mk
index 6a24bd3..cb4dca6 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2163,10 +2163,11 @@
 $(shell rm $(PRODUCT_OUT)/sbom-metadata.csv >/dev/null 2>&1)
 $(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files)
 	rm -f $@
-	@echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated >> $@
+	@echo installed_file$(comma)module_path$(comma)soong_module_type$(comma)is_prebuilt_make_module$(comma)product_copy_files$(comma)kernel_module_copy_files$(comma)is_platform_generated,build_output_path >> $@
 	$(foreach f,$(installed_files),\
 	  $(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \
 	  $(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$f)) \
+	  $(eval _build_output_path := $(PRODUCT_OUT)/$(_path_on_device)) \
 	  $(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) \
 	  $(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) \
 	  $(eval _is_prebuilt_make_module := $(ALL_MODULES.$(_module_name).IS_PREBUILT_MAKE_MODULE)) \
@@ -2184,9 +2185,9 @@
 	  $(eval _is_linker_config := $(if $(findstring $f,$(SYSTEM_LINKER_CONFIG) $(vendor_linker_config_file)),Y)) \
 	  $(eval _is_partition_compat_symlink := $(if $(findstring $f,$(PARTITION_COMPAT_SYMLINKS)),Y)) \
 	  $(eval _is_platform_generated := $(_is_build_prop)$(_is_notice_file)$(_is_dexpreopt_image_profile)$(_is_product_system_other_avbkey)$(_is_event_log_tags_file)$(_is_system_other_odex_marker)$(_is_kernel_modules_blocklist)$(_is_fsverity_build_manifest_apk)$(_is_linker_config)$(_is_partition_compat_symlink)) \
-	  @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ $(newline) \
+	  @echo /$(_path_on_device)$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(_build_output_path) >> $@ $(newline) \
 	  $(if $(_post_installed_dexpreopt_zip), \
-	  for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated) >> $@ ; done $(newline) \
+	  for i in $$(zipinfo -1 $(_post_installed_dexpreopt_zip)); do echo /$$i$(comma)$(_module_path)$(comma)$(_soong_module_type)$(comma)$(_is_prebuilt_make_module)$(comma)$(_product_copy_files)$(comma)$(_kernel_module_copy_files)$(comma)$(_is_platform_generated)$(comma)$(PRODUCT_OUT)/$$i >> $@ ; done $(newline) \
 	  ) \
 	)
 
@@ -2196,14 +2197,14 @@
 $(PRODUCT_OUT)/sbom.spdx.json: $(PRODUCT_OUT)/sbom.spdx
 $(PRODUCT_OUT)/sbom.spdx: $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --json
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json
 
 $(call dist-for-goals,droid,$(PRODUCT_OUT)/sbom.spdx.json:sbom/sbom.spdx.json)
 else
 apps_only_sbom_files := $(sort $(patsubst %,%.spdx.json,$(filter %.apk,$(apps_only_installed_files))))
 $(apps_only_sbom_files): $(PRODUCT_OUT)/sbom-metadata.csv $(GEN_SBOM)
 	rm -rf $@
-	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --product_out_dir=$(PRODUCT_OUT) --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr="$(PRODUCT_MANUFACTURER)" --unbundled
+	$(GEN_SBOM) --output_file $@ --metadata $(PRODUCT_OUT)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --unbundled_apk
 
 sbom: $(apps_only_sbom_files)
 
diff --git a/core/package_internal.mk b/core/package_internal.mk
index 2d0a569..7cfab5b 100644
--- a/core/package_internal.mk
+++ b/core/package_internal.mk
@@ -111,24 +111,26 @@
 
 # Determine whether auto-RRO is enabled for this package.
 enforce_rro_enabled :=
-ifneq (,$(filter *, $(PRODUCT_ENFORCE_RRO_TARGETS)))
-  # * means all system and system_ext APKs, so enable conditionally based on module path.
+ifeq (,$(filter tests,$(LOCAL_MODULE_TAGS)))
+  ifneq (,$(filter *, $(PRODUCT_ENFORCE_RRO_TARGETS)))
+    # * means all system and system_ext APKs, so enable conditionally based on module path.
 
-  # Note that base_rules.mk has not yet been included, so it's likely that only
-  # one of LOCAL_MODULE_PATH and the LOCAL_X_MODULE flags has been set.
-  ifeq (,$(LOCAL_MODULE_PATH))
-    non_rro_target_module := $(filter true,\
-        $(LOCAL_ODM_MODULE) \
-        $(LOCAL_OEM_MODULE) \
-        $(LOCAL_PRODUCT_MODULE) \
-        $(LOCAL_PROPRIETARY_MODULE) \
-        $(LOCAL_VENDOR_MODULE))
-    enforce_rro_enabled := $(if $(non_rro_target_module),,true)
-  else ifneq ($(filter $(TARGET_OUT)/%,$(LOCAL_MODULE_PATH)),)
+    # Note that base_rules.mk has not yet been included, so it's likely that only
+    # one of LOCAL_MODULE_PATH and the LOCAL_X_MODULE flags has been set.
+    ifeq (,$(LOCAL_MODULE_PATH))
+      non_rro_target_module := $(filter true,\
+          $(LOCAL_ODM_MODULE) \
+          $(LOCAL_OEM_MODULE) \
+          $(LOCAL_PRODUCT_MODULE) \
+          $(LOCAL_PROPRIETARY_MODULE) \
+          $(LOCAL_VENDOR_MODULE))
+      enforce_rro_enabled := $(if $(non_rro_target_module),,true)
+    else ifneq ($(filter $(TARGET_OUT)/%,$(LOCAL_MODULE_PATH)),)
+      enforce_rro_enabled := true
+    endif
+  else ifneq (,$(filter $(LOCAL_PACKAGE_NAME), $(PRODUCT_ENFORCE_RRO_TARGETS)))
     enforce_rro_enabled := true
   endif
-else ifneq (,$(filter $(LOCAL_PACKAGE_NAME), $(PRODUCT_ENFORCE_RRO_TARGETS)))
-  enforce_rro_enabled := true
 endif
 
 product_package_overlays := $(strip \
diff --git a/core/product_config.rbc b/core/product_config.rbc
index e594894..921f068 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -379,11 +379,7 @@
 def _soong_config_set(g, nsname, var, value):
     """Assigns the value to the variable in the namespace."""
     _soong_config_namespace(g, nsname)
-    if type(value) == "string":
-        # Trim right spaces, because in make the variable is set in an $(eval),
-        # which will ignore trailing spaces.
-        value = value.rstrip(" ")
-    g[_soong_config_namespaces_key][nsname][var]=value
+    g[_soong_config_namespaces_key][nsname][var]=_mkstrip(value)
 
 def _soong_config_append(g, nsname, var, value):
     """Appends to the value of the variable in the namespace."""
@@ -391,9 +387,9 @@
     ns = g[_soong_config_namespaces_key][nsname]
     oldv = ns.get(var)
     if oldv == None:
-        ns[var] = value
+        ns[var] = _mkstrip(value)
     else:
-        ns[var] += " " + value
+        ns[var] += " " + _mkstrip(value)
 
 
 def _soong_config_get(g, nsname, var):
diff --git a/core/release_config.bzl b/core/release_config.bzl
new file mode 100644
index 0000000..e73c90f
--- /dev/null
+++ b/core/release_config.bzl
@@ -0,0 +1,90 @@
+# Copyright (C) 2023 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.
+
+# Partitions that get build system flag summaries
+_flag_partitions = [
+    "product",
+    "system",
+    "system_ext",
+    "vendor",
+]
+
+def _combine_dicts_no_duplicate_keys(dicts):
+    result = {}
+    for d in dicts:
+        for k, v in d.items():
+            if k in result:
+                fail("Duplicate key: " + k)
+            result[k] = v
+    return result
+
+def release_config(target_release, flag_definitions, config_maps, fail_if_no_release_config = True):
+    result = {
+        "_ALL_RELEASE_FLAGS": [flag.name for flag in flag_definitions],
+    }
+    all_flags = {}
+    for flag in flag_definitions:
+        if sorted(dir(flag)) != ["default", "name", "partitions"]:
+            fail("Flag structs must contain 3 fields: name, partitions, and default")
+        if not flag.partitions:
+            fail("At least 1 partition is required")
+        for partition in flag.partitions:
+            if partition == "all":
+                if len(flag.partitions) > 1:
+                    fail("\"all\" can't be combined with other partitions: " + str(flag.partitions))
+            elif partition not in _flag_partitions:
+                fail("Invalid partition: " + flag.partition + ", allowed partitions: " + str(_flag_partitions))
+        if not flag.name.startswith("RELEASE_"):
+            fail("Release flag names must start with RELEASE_")
+        if " " in flag.name or "\t" in flag.name or "\n" in flag.name:
+            fail("Flag names must not contain whitespace.")
+        if flag.name in all_flags:
+            fail("Duplicate declaration of flag " + flag.name)
+        all_flags[flag.name] = True
+
+        default = flag.default
+        if type(default) == "bool":
+            default = "true" if default else ""
+
+        result["_ALL_RELEASE_FLAGS." + flag.name + ".PARTITIONS"] = flag.partitions
+        result["_ALL_RELEASE_FLAGS." + flag.name + ".DEFAULT"] = default
+        result["_ALL_RELEASE_FLAGS." + flag.name + ".VALUE"] = default
+
+    # If TARGET_RELEASE is set, fail if there is no matching release config
+    # If it isn't set, no release config files will be included and all flags
+    # will get their default values.
+    if target_release:
+        config_map = _combine_dicts_no_duplicate_keys(config_maps)
+        if target_release not in config_map:
+            fail("No release config found for TARGET_RELEASE: " + target_release + ". Available releases are: " + str(config_map.keys()))
+        release_config = config_map[target_release]
+        if sorted(dir(release_config)) != ["flags", "release_version"]:
+            fail("A release config must be a struct with a flags and release_version fields")
+        result["_RELEASE_VERSION"] = release_config.release_version
+        for flag in release_config.flags:
+            if sorted(dir(release_config)) != ["name", "value"]:
+                fail("A flag be a struct with name and value fields")
+            if flag.name not in all_flags:
+                fail("Undeclared build flag: " + flag.name)
+            value = flag.value
+            if type(value) == "bool":
+                value = "true" if value else ""
+            result["_ALL_RELEASE_FLAGS." + flag.name + ".VALUE"] = value
+    elif fail_if_no_release_config:
+        fail("FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not")
+    else:
+        # No TARGET_RELEASE means release version 0
+        result["_RELEASE_VERSION"] = 0
+
+    return result
diff --git a/core/release_config.mk b/core/release_config.mk
index 0ad87fc..621c7d1 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -12,76 +12,45 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Partitions that get build system flag summaries
-_FLAG_PARTITIONS := system vendor system_ext product
-
-# All possible release flags. Defined in the flags.mk files
-# throughout the tree
-_ALL_RELEASE_FLAGS :=
-
-# -----------------------------------------------------------------
-# Choose the flag files
-# Do this first, because we're going to unset TARGET_RELEASE before
-# including anyone, so they don't start making conditionals based on it.
-
 # If this is a google source tree, restrict it to only the one file
 # which has OWNERS control.  If it isn't let others define their own.
-config_map_files := build/make/release/release_config_map.mk \
-    $(if $(wildcard vendor/google/release/release_config_map.mk), \
-        vendor/google/release/release_config_map.mk, \
+# TODO: Remove wildcard for build/release one when all branch manifests
+# have updated.
+flag_declaration_files := $(wildcard build/release/build_flags.bzl) \
+    $(if $(wildcard vendor/google/release/build_flags.bzl), \
+        vendor/google/release/build_flags.bzl, \
         $(sort \
-            $(wildcard device/*/release/release_config_map.mk) \
-            $(wildcard device/*/*/release/release_config_map.mk) \
-            $(wildcard vendor/*/release/release_config_map.mk) \
-            $(wildcard vendor/*/*/release/release_config_map.mk) \
+            $(wildcard device/*/release/build_flags.bzl) \
+            $(wildcard device/*/*/release/build_flags.bzl) \
+            $(wildcard vendor/*/release/build_flags.bzl) \
+            $(wildcard vendor/*/*/release/build_flags.bzl) \
+        ) \
+    )
+config_map_files := $(wildcard build/release/release_config_map.bzl) \
+    $(if $(wildcard vendor/google/release/release_config_map.bzl), \
+        vendor/google/release/release_config_map.bzl, \
+        $(sort \
+            $(wildcard device/*/release/release_config_map.bzl) \
+            $(wildcard device/*/*/release/release_config_map.bzl) \
+            $(wildcard vendor/*/release/release_config_map.bzl) \
+            $(wildcard vendor/*/*/release/release_config_map.bzl) \
         ) \
     )
 
-# $1 config name
-# $2 release config files
-define declare-release-config
-    $(eval # No duplicates)
-    $(if $(filter $(_all_release_configs), $(strip $(1))), \
-        $(error declare-release-config: config $(strip $(1)) declared in: $(_included) Previously declared here: $(_all_release_configs.$(strip $(1)).DECLARED_IN)) \
-    )
-    $(eval # Must have release config files)
-    $(if $(strip $(2)),,  \
-        $(error declare-release-config: config $(strip $(1)) must have release config files) \
-    )
-    $(eval _all_release_configs := $(sort $(_all_release_configs) $(strip $(1))))
-    $(eval _all_release_configs.$(strip $(1)).DECLARED_IN := $(_included))
-    $(eval _all_release_configs.$(strip $(1)).FILES := $(strip $(2)))
-endef
-
-# Include the config map files
-$(foreach f, $(config_map_files), \
-    $(eval _included := $(f)) \
-    $(eval include $(f)) \
-)
-
-# If TARGET_RELEASE is set, fail if there is no matching release config
-# If it isn't set, no release config files will be included and all flags
-# will get their default values.
-ifneq ($(TARGET_RELEASE),)
-ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
-    $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE))
-else
-    # Choose flag files
-    # Don't sort this, use it in the order they gave us.
-    _release_config_files := $(_all_release_configs.$(TARGET_RELEASE).FILES)
-endif
-else
-# Useful for finding scripts etc that aren't passing or setting TARGET_RELEASE
-ifneq ($(FAIL_IF_NO_RELEASE_CONFIG),)
-    $(error FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not)
-endif
-_release_config_files :=
-endif
-
-# Unset variables so they can't use it
-define declare-release-config
-$(error declare-release-config can only be called from inside release_config_map.mk files)
-endef
+# Because starlark can't find files with $(wildcard), write an entrypoint starlark script that
+# contains the result of the above wildcards for the starlark code to use.
+filename_to_starlark=$(subst /,_,$(subst .,_,$(1)))
+_c:=load("//build/make/core/release_config.bzl", "release_config")
+_c+=$(foreach f,$(flag_declaration_files),$(newline)load("//$(f)", flags_$(call filename_to_starlark,$(f)) = "flags"))
+_c+=$(foreach f,$(config_map_files),$(newline)load("//$(f)", config_maps_$(call filename_to_starlark,$(f)) = "config_maps"))
+_c+=$(newline)all_flags = [] $(foreach f,$(flag_declaration_files),+ flags_$(call filename_to_starlark,$(f)))
+_c+=$(newline)all_config_maps = [$(foreach f,$(config_map_files),config_maps_$(call filename_to_starlark,$(f))$(comma))]
+_c+=$(newline)target_release = "$(TARGET_RELEASE)"
+_c+=$(newline)fail_if_no_release_config = True if "$(FAIL_IF_NO_RELEASE_CONFIG)" else False
+_c+=$(newline)variables_to_export_to_make = release_config(target_release, all_flags, all_config_maps, fail_if_no_release_config)
+$(file >$(OUT_DIR)/release_config_entrypoint.bzl,$(_c))
+_c:=
+filename_to_starlark:=
 
 # TODO: Remove this check after enough people have sourced lunch that we don't
 # need to worry about it trying to do get_build_vars TARGET_RELEASE. Maybe after ~9/2023
@@ -94,105 +63,7 @@
 endif
 .KATI_READONLY := TARGET_RELEASE
 
-$(foreach config, $(_all_release_configs), \
-    $(eval _all_release_configs.$(config).DECLARED_IN:= ) \
-    $(eval _all_release_configs.$(config).FILES:= ) \
-)
-_all_release_configs:=
-config_map_files:=
-
-# -----------------------------------------------------------------
-# Declare the flags
-
-# $1 partition(s)
-# $2 flag name. Must start with RELEASE_
-# $3 default. True or false
-define declare-build-flag
-    $(if $(filter-out all $(_FLAG_PARTITIONS), $(strip $(1))), \
-        $(error declare-build-flag: invalid partitions: $(strip $(1))) \
-    )
-    $(if $(and $(filter all,$(strip $(1))),$(filter-out all, $(strip $(1)))), \
-        $(error declare-build-flag: "all" can't be combined with other partitions: $(strip $(1))), \
-        $(eval declare-build-flag.partition := $(_FLAG_PARTITIONS)) \
-    )
-    $(if $(filter-out RELEASE_%, $(strip $(2))), \
-        $(error declare-build-flag: Release flag names must start with RELEASE_: $(strip $(2))) \
-    )
-    $(eval _ALL_RELEASE_FLAGS += $(strip $(2)))
-    $(foreach partition, $(declare-build-flag.partition), \
-        $(eval _ALL_RELEASE_FLAGS.PARTITIONS.$(partition) := $(sort \
-            $(_ALL_RELEASE_FLAGS.PARTITIONS.$(partition)) $(strip $(2)))) \
-    )
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).PARTITIONS := $(declare-build-flag.partition))
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DEFAULT := $(strip $(3)))
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DECLARED_IN := $(_included))
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).VALUE := $(strip $(3)))
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).SET_IN := $(_included))
-    $(eval declare-build-flag.partition:=)
-endef
-
-
-# Choose the files
-# If this is a google source tree, restrict it to only the one file
-# which has OWNERS control.  If it isn't let others define their own.
-flag_declaration_files := build/make/release/flags.mk \
-    $(if $(wildcard vendor/google/release/flags.mk), \
-        vendor/google/release/flags.mk, \
-        $(sort \
-            $(wildcard device/*/release/flags.mk) \
-            $(wildcard device/*/*/release/flags.mk) \
-            $(wildcard vendor/*/release/flags.mk) \
-            $(wildcard vendor/*/*/release/flags.mk) \
-        ) \
-    )
-
-# Include the files
-$(foreach f, $(flag_declaration_files), \
-    $(eval _included := $(f)) \
-    $(eval include $(f)) \
-)
-
-# Don't let anyone declare build flags after here
-define declare-build-flag
-$(error declare-build-flag can only be called from inside flag definition files.)
-endef
-
-# No more flags from here on
-.KATI_READONLY := _ALL_RELEASE_FLAGS
-
-# -----------------------------------------------------------------
-# Set the flags
-
-# $(1): Flag name. Must start with RELEASE_ and have been defined by declare-build-flag
-# $(2): Value. True or false
-define set-build-flag
-    $(if $(filter-out $(_ALL_RELEASE_FLAGS), $(strip $(1))), \
-        $(error set-build-flag: Undeclared build flag: $(strip $(1))) \
-    )
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).VALUE := $(strip $(2)))
-    $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).SET_IN := $(_included))
-endef
-
-# Include the files (if there are any)
-$(foreach f, $(_release_config_files), \
-    $(eval _included := $(f)) \
-    $(eval include $(f)) \
-)
-
-# Don't let anyone declare build flags after here
-define set-build-flag
-$(error set-build-flag can only be called from inside release config files.)
-endef
-
-# Set the flag values, and don't allow any one to modify them.
-$(foreach flag, $(_ALL_RELEASE_FLAGS), \
-    $(eval $(flag) := $(_ALL_RELEASE_FLAGS.$(flag).VALUE)) \
-    $(eval .KATI_READONLY := $(flag)) \
-)
-
-# -----------------------------------------------------------------
-# Clear out vars
-flag_declaration_files:=
-flag_files:=
-_included:=
-_release_config_files:=
+# Exclude the entrypoint file as a dependency (by passing it as the 2nd argument) so that we don't
+# rerun kati every build. Kati will replay the $(file) command that generates it every build,
+# updating its timestamp.
+$(call run-starlark,$(OUT_DIR)/release_config_entrypoint.bzl,$(OUT_DIR)/release_config_entrypoint.bzl)
diff --git a/core/soong_app_prebuilt.mk b/core/soong_app_prebuilt.mk
index dd550b5..ccc5449 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -239,26 +239,28 @@
 include $(BUILD_SYSTEM)/link_type.mk
 endif # !LOCAL_IS_HOST_MODULE
 
-ifdef LOCAL_SOONG_DEVICE_RRO_DIRS
-  $(call append_enforce_rro_sources, \
-      $(my_register_name), \
-      false, \
-      $(LOCAL_FULL_MANIFEST_FILE), \
-      $(if $(LOCAL_EXPORT_PACKAGE_RESOURCES),true,false), \
-      $(LOCAL_SOONG_DEVICE_RRO_DIRS), \
-      vendor \
-  )
-endif
+ifeq (,$(filter tests,$(LOCAL_MODULE_TAGS)))
+  ifdef LOCAL_SOONG_DEVICE_RRO_DIRS
+    $(call append_enforce_rro_sources, \
+        $(my_register_name), \
+        false, \
+        $(LOCAL_FULL_MANIFEST_FILE), \
+        $(if $(LOCAL_EXPORT_PACKAGE_RESOURCES),true,false), \
+        $(LOCAL_SOONG_DEVICE_RRO_DIRS), \
+        vendor \
+    )
+  endif
 
-ifdef LOCAL_SOONG_PRODUCT_RRO_DIRS
-  $(call append_enforce_rro_sources, \
-      $(my_register_name), \
-      false, \
-      $(LOCAL_FULL_MANIFEST_FILE), \
-      $(if $(LOCAL_EXPORT_PACKAGE_RESOURCES),true,false), \
-      $(LOCAL_SOONG_PRODUCT_RRO_DIRS), \
-      product \
-  )
+  ifdef LOCAL_SOONG_PRODUCT_RRO_DIRS
+    $(call append_enforce_rro_sources, \
+        $(my_register_name), \
+        false, \
+        $(LOCAL_FULL_MANIFEST_FILE), \
+        $(if $(LOCAL_EXPORT_PACKAGE_RESOURCES),true,false), \
+        $(LOCAL_SOONG_PRODUCT_RRO_DIRS), \
+        product \
+    )
+  endif
 endif
 
 ifdef LOCAL_PREBUILT_COVERAGE_ARCHIVE
@@ -273,4 +275,4 @@
 ###########################################################
 ## SBOM generation
 ###########################################################
-include $(BUILD_SBOM_GEN)
\ No newline at end of file
+include $(BUILD_SBOM_GEN)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 034d044..8b9ba4f 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -319,6 +319,9 @@
 $(call add_json_str,  ProductBrand,        $(PRODUCT_BRAND))
 $(call add_json_list, BuildVersionTags,    $(BUILD_VERSION_TAGS))
 
+$(call add_json_str, ReleaseVersion,    $(_RELEASE_VERSION))
+$(call add_json_list, ReleaseDeviceConfigValueSets,    $(RELEASE_DEVICE_CONFIG_VALUE_SETS))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/tasks/collect_gpl_sources.mk b/core/tasks/collect_gpl_sources.mk
deleted file mode 100644
index 9e9ab8e..0000000
--- a/core/tasks/collect_gpl_sources.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2011 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.
-
-# The rule below doesn't have dependenices on the files that it copies,
-# so manually generate into a PACKAGING intermediate dir, which is wiped
-# in installclean between incremental builds on build servers.
-gpl_source_tgz := $(call intermediates-dir-for,PACKAGING,gpl_source)/gpl_source.tgz
-
-ALL_GPL_MODULE_LICENSE_FILES := $(sort $(ALL_GPL_MODULE_LICENSE_FILES))
-
-# FORCE since we can't know whether any of the sources changed
-$(gpl_source_tgz): PRIVATE_PATHS := $(sort $(patsubst %/, %, $(dir $(ALL_GPL_MODULE_LICENSE_FILES))))
-$(gpl_source_tgz) : $(ALL_GPL_MODULE_LICENSE_FILES)
-	@echo Package GPL sources: $@
-	$(hide) tar cfz $@ --exclude ".git*" $(PRIVATE_PATHS)
-
-# Dist the tgz only if we are doing a full build
-$(call dist-for-goals,droidcore-unbundled,$(gpl_source_tgz))
diff --git a/release/flags.mk b/release/flags.mk
deleted file mode 100644
index eb97dc1..0000000
--- a/release/flags.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) 2023 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 defines the build system flags that can be set based on the
-# release configuration.  If at all possible, use aconfig flags instead.
-# This is for things that must be decided at compile time.
-
-# Flag names should be alphabetical by flag name.
-
-$(call declare-build-flag, system, RELEASE_THE_FIRST_FLAG, true)
-$(call declare-build-flag, system, RELEASE_THE_SECOND_FLAG, true)
-
diff --git a/release/release_config_map.mk b/release/release_config_map.mk
deleted file mode 100644
index 190cb89..0000000
--- a/release/release_config_map.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2023 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.
-
-# AOSP doesn't define any release configs yet.
-
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 0f5b8a4..d65e5a4 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -226,7 +226,7 @@
     mtpd \
     ndc \
     netd \
-    NetworkStackNext \
+    NetworkStack \
     odsign \
     org.apache.http.legacy \
     otacerts \
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
index b09648d..b9fa324 100644
--- a/tools/aconfig/src/aconfig.rs
+++ b/tools/aconfig/src/aconfig.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{anyhow, Context, Error, Result};
+use anyhow::{anyhow, bail, Context, Error, Result};
 use protobuf::{Enum, EnumOrUnknown};
 use serde::{Deserialize, Serialize};
 
@@ -98,10 +98,10 @@
 
     fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
         let Some(name) = proto.name else {
-            return Err(anyhow!("missing 'name' field"));
+            bail!("missing 'name' field");
         };
         let Some(description) = proto.description else {
-            return Err(anyhow!("missing 'description' field"));
+            bail!("missing 'description' field");
         };
         Ok(FlagDeclaration { name, description })
     }
@@ -118,7 +118,7 @@
         let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
             .with_context(|| text_proto.to_owned())?;
         let Some(namespace) = proto.namespace else {
-            return Err(anyhow!("missing 'namespace' field"));
+            bail!("missing 'namespace' field");
         };
         let mut flags = vec![];
         for proto_flag in proto.flag.into_iter() {
@@ -154,17 +154,17 @@
 
     fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
         let Some(namespace) = proto.namespace else {
-            return Err(anyhow!("missing 'namespace' field"));
+            bail!("missing 'namespace' field");
         };
         let Some(name) = proto.name else {
-            return Err(anyhow!("missing 'name' field"));
+            bail!("missing 'name' field");
         };
         let Some(proto_state) = proto.state else {
-            return Err(anyhow!("missing 'state' field"));
+            bail!("missing 'state' field");
         };
         let state = proto_state.try_into()?;
         let Some(proto_permission) = proto.permission else {
-            return Err(anyhow!("missing 'permission' field"));
+            bail!("missing 'permission' field");
         };
         let permission = proto_permission.try_into()?;
         Ok(FlagValue { namespace, name, state, permission })
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index 3ecadc9..c546f7b 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{anyhow, bail, ensure, Result};
+use anyhow::{bail, ensure, Result};
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 
@@ -52,8 +52,9 @@
 }
 
 impl Cache {
-    pub fn new(namespace: String) -> Cache {
-        Cache { namespace, items: vec![] }
+    pub fn new(namespace: String) -> Result<Cache> {
+        ensure!(!namespace.is_empty(), "empty namespace");
+        Ok(Cache { namespace, items: vec![] })
     }
 
     pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -69,13 +70,14 @@
         source: Source,
         declaration: FlagDeclaration,
     ) -> Result<()> {
-        if self.items.iter().any(|item| item.name == declaration.name) {
-            return Err(anyhow!(
-                "failed to declare flag {} from {}: flag already declared",
-                declaration.name,
-                source,
-            ));
-        }
+        ensure!(!declaration.name.is_empty(), "empty flag name");
+        ensure!(!declaration.description.is_empty(), "empty flag description");
+        ensure!(
+            self.items.iter().all(|item| item.name != declaration.name),
+            "failed to declare flag {} from {}: flag already declared",
+            declaration.name,
+            source
+        );
         self.items.push(Item {
             namespace: self.namespace.clone(),
             name: declaration.name.clone(),
@@ -92,6 +94,8 @@
     }
 
     pub fn add_flag_value(&mut self, source: Source, value: FlagValue) -> Result<()> {
+        ensure!(!value.namespace.is_empty(), "empty flag namespace");
+        ensure!(!value.name.is_empty(), "empty flag name");
         ensure!(
             value.namespace == self.namespace,
             "failed to set values for flag {}/{} from {}: expected namespace {}",
@@ -120,6 +124,11 @@
     pub fn into_iter(self) -> impl Iterator<Item = Item> {
         self.items.into_iter()
     }
+
+    pub fn namespace(&self) -> &str {
+        debug_assert!(!self.namespace.is_empty());
+        &self.namespace
+    }
 }
 
 #[cfg(test)]
@@ -129,7 +138,7 @@
 
     #[test]
     fn test_add_flag_declaration() {
-        let mut cache = Cache::new("ns".to_string());
+        let mut cache = Cache::new("ns".to_string()).unwrap();
         cache
             .add_flag_declaration(
                 Source::File("first.txt".to_string()),
@@ -155,7 +164,7 @@
             item.state == expected.0 && item.permission == expected.1
         }
 
-        let mut cache = Cache::new("ns".to_string());
+        let mut cache = Cache::new("ns".to_string()).unwrap();
         let error = cache
             .add_flag_value(
                 Source::Memory,
@@ -221,4 +230,67 @@
         assert_eq!(&format!("{:?}", error), "failed to set values for flag some-other-namespace/foo from <memory>: expected namespace ns");
         assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
     }
+
+    #[test]
+    fn test_reject_empty_cache_namespace() {
+        Cache::new("".to_string()).unwrap_err();
+    }
+
+    #[test]
+    fn test_reject_empty_flag_declaration_fields() {
+        let mut cache = Cache::new("ns".to_string()).unwrap();
+
+        let error = cache
+            .add_flag_declaration(
+                Source::Memory,
+                FlagDeclaration { name: "".to_string(), description: "Description".to_string() },
+            )
+            .unwrap_err();
+        assert_eq!(&format!("{:?}", error), "empty flag name");
+
+        let error = cache
+            .add_flag_declaration(
+                Source::Memory,
+                FlagDeclaration { name: "foo".to_string(), description: "".to_string() },
+            )
+            .unwrap_err();
+        assert_eq!(&format!("{:?}", error), "empty flag description");
+    }
+
+    #[test]
+    fn test_reject_empty_flag_value_files() {
+        let mut cache = Cache::new("ns".to_string()).unwrap();
+        cache
+            .add_flag_declaration(
+                Source::Memory,
+                FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
+            )
+            .unwrap();
+
+        let error = cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: "".to_string(),
+                    name: "foo".to_string(),
+                    state: FlagState::Enabled,
+                    permission: Permission::ReadOnly,
+                },
+            )
+            .unwrap_err();
+        assert_eq!(&format!("{:?}", error), "empty flag namespace");
+
+        let error = cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: "ns".to_string(),
+                    name: "".to_string(),
+                    state: FlagState::Enabled,
+                    permission: Permission::ReadOnly,
+                },
+            )
+            .unwrap_err();
+        assert_eq!(&format!("{:?}", error), "empty flag name");
+    }
 }
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
new file mode 100644
index 0000000..cb266f1
--- /dev/null
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+use anyhow::Result;
+use serde::Serialize;
+use tinytemplate::TinyTemplate;
+
+use crate::aconfig::{FlagState, Permission};
+use crate::cache::{Cache, Item};
+use crate::commands::OutputFile;
+
+pub fn generate_cpp_code(cache: &Cache) -> Result<OutputFile> {
+    let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
+    let readwrite = class_elements.iter().any(|item| item.readwrite);
+    let namespace = cache.namespace().to_lowercase();
+    let context = Context { namespace: namespace.clone(), readwrite, class_elements };
+    let mut template = TinyTemplate::new();
+    template.add_template("cpp_code_gen", include_str!("../templates/cpp.template"))?;
+    let contents = template.render("cpp_code_gen", &context)?;
+    let path = ["aconfig", &(namespace + ".h")].iter().collect();
+    Ok(OutputFile { contents: contents.into(), path })
+}
+
+#[derive(Serialize)]
+struct Context {
+    pub namespace: String,
+    pub readwrite: bool,
+    pub class_elements: Vec<ClassElement>,
+}
+
+#[derive(Serialize)]
+struct ClassElement {
+    pub readwrite: bool,
+    pub default_value: String,
+    pub flag_name: String,
+}
+
+fn create_class_element(item: &Item) -> ClassElement {
+    ClassElement {
+        readwrite: item.permission == Permission::ReadWrite,
+        default_value: if item.state == FlagState::Enabled {
+            "true".to_string()
+        } else {
+            "false".to_string()
+        },
+        flag_name: item.name.clone(),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
+    use crate::commands::Source;
+
+    #[test]
+    fn test_cpp_codegen_build_time_flag_only() {
+        let namespace = "my_namespace";
+        let mut cache = Cache::new(namespace.to_string()).unwrap();
+        cache
+            .add_flag_declaration(
+                Source::File("aconfig_one.txt".to_string()),
+                FlagDeclaration {
+                    name: "my_flag_one".to_string(),
+                    description: "buildtime disable".to_string(),
+                },
+            )
+            .unwrap();
+        cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: namespace.to_string(),
+                    name: "my_flag_one".to_string(),
+                    state: FlagState::Disabled,
+                    permission: Permission::ReadOnly,
+                },
+            )
+            .unwrap();
+        cache
+            .add_flag_declaration(
+                Source::File("aconfig_two.txt".to_string()),
+                FlagDeclaration {
+                    name: "my_flag_two".to_string(),
+                    description: "buildtime enable".to_string(),
+                },
+            )
+            .unwrap();
+        cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: namespace.to_string(),
+                    name: "my_flag_two".to_string(),
+                    state: FlagState::Enabled,
+                    permission: Permission::ReadOnly,
+                },
+            )
+            .unwrap();
+        let expect_content = r#"#ifndef my_namespace_HEADER_H
+        #define my_namespace_HEADER_H
+        #include "my_namespace.h"
+
+        namespace my_namespace {
+
+            class my_flag_one {
+                public:
+                    virtual const bool value() {
+                        return false;
+                    }
+            }
+
+            class my_flag_two {
+                public:
+                    virtual const bool value() {
+                        return true;
+                    }
+            }
+
+        }
+        #endif
+        "#;
+        let file = generate_cpp_code(&cache).unwrap();
+        assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap());
+        assert_eq!(
+            expect_content.replace(' ', ""),
+            String::from_utf8(file.contents).unwrap().replace(' ', "")
+        );
+    }
+
+    #[test]
+    fn test_cpp_codegen_runtime_flag() {
+        let namespace = "my_namespace";
+        let mut cache = Cache::new(namespace.to_string()).unwrap();
+        cache
+            .add_flag_declaration(
+                Source::File("aconfig_one.txt".to_string()),
+                FlagDeclaration {
+                    name: "my_flag_one".to_string(),
+                    description: "buildtime disable".to_string(),
+                },
+            )
+            .unwrap();
+        cache
+            .add_flag_declaration(
+                Source::File("aconfig_two.txt".to_string()),
+                FlagDeclaration {
+                    name: "my_flag_two".to_string(),
+                    description: "runtime enable".to_string(),
+                },
+            )
+            .unwrap();
+        cache
+            .add_flag_value(
+                Source::Memory,
+                FlagValue {
+                    namespace: namespace.to_string(),
+                    name: "my_flag_two".to_string(),
+                    state: FlagState::Enabled,
+                    permission: Permission::ReadWrite,
+                },
+            )
+            .unwrap();
+        let expect_content = r#"#ifndef my_namespace_HEADER_H
+        #define my_namespace_HEADER_H
+        #include "my_namespace.h"
+
+        #include <server_configurable_flags/get_flags.h>
+        using namespace server_configurable_flags;
+
+        namespace my_namespace {
+
+            class my_flag_one {
+                public:
+                    virtual const bool value() {
+                        return GetServerConfigurableFlag(
+                            "my_namespace",
+                            "my_flag_one",
+                            "false") == "true";
+                    }
+            }
+
+            class my_flag_two {
+                public:
+                    virtual const bool value() {
+                        return GetServerConfigurableFlag(
+                            "my_namespace",
+                            "my_flag_two",
+                            "true") == "true";
+                    }
+            }
+
+        }
+        #endif
+        "#;
+        let file = generate_cpp_code(&cache).unwrap();
+        assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap());
+        assert_eq!(
+            expect_content.replace(' ', ""),
+            String::from_utf8(file.contents).unwrap().replace(' ', "")
+        );
+    }
+}
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index bbf1272..476a89d 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -16,6 +16,7 @@
 
 use anyhow::Result;
 use serde::Serialize;
+use std::path::PathBuf;
 use tinytemplate::TinyTemplate;
 
 use crate::aconfig::{FlagState, Permission};
@@ -25,14 +26,14 @@
 pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
     let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
     let readwrite = class_elements.iter().any(|item| item.readwrite);
-    let namespace = uppercase_first_letter(
-        cache.iter().find(|item| !item.namespace.is_empty()).unwrap().namespace.as_str(),
-    );
-    let context = Context { namespace: namespace.clone(), readwrite, class_elements };
+    let namespace = cache.namespace();
+    let context = Context { namespace: namespace.to_string(), readwrite, class_elements };
     let mut template = TinyTemplate::new();
     template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
     let contents = template.render("java_code_gen", &context)?;
-    let path = ["com", "android", "internal", "aconfig", &(namespace + ".java")].iter().collect();
+    let mut path: PathBuf = namespace.split('.').collect();
+    // TODO: Allow customization of the java class name
+    path.push("Flags.java");
     Ok(OutputFile { contents: contents.into(), path })
 }
 
@@ -66,21 +67,6 @@
     }
 }
 
-fn uppercase_first_letter(s: &str) -> String {
-    s.chars()
-        .enumerate()
-        .map(
-            |(index, ch)| {
-                if index == 0 {
-                    ch.to_ascii_uppercase()
-                } else {
-                    ch.to_ascii_lowercase()
-                }
-            },
-        )
-        .collect()
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -89,8 +75,8 @@
 
     #[test]
     fn test_generate_java_code() {
-        let namespace = "TeSTFlaG";
-        let mut cache = Cache::new(namespace.to_string());
+        let namespace = "com.example";
+        let mut cache = Cache::new(namespace.to_string()).unwrap();
         cache
             .add_flag_declaration(
                 Source::File("test.txt".to_string()),
@@ -120,11 +106,11 @@
                 },
             )
             .unwrap();
-        let expect_content = r#"package com.android.internal.aconfig;
+        let expect_content = r#"package com.example;
 
         import android.provider.DeviceConfig;
 
-        public final class Testflag {
+        public final class Flags {
 
             public static boolean test() {
                 return false;
@@ -132,7 +118,7 @@
 
             public static boolean test2() {
                 return DeviceConfig.getBoolean(
-                    "Testflag",
+                    "com.example",
                     "test2__test2",
                     false
                 );
@@ -141,7 +127,7 @@
         }
         "#;
         let file = generate_java_code(&cache).unwrap();
-        assert_eq!("com/android/internal/aconfig/Testflag.java", file.path.to_str().unwrap());
+        assert_eq!("com/example/Flags.java", file.path.to_str().unwrap());
         assert_eq!(
             expect_content.replace(' ', ""),
             String::from_utf8(file.contents).unwrap().replace(' ', "")
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 475d9b8..0bdb0b5 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -24,6 +24,7 @@
 
 use crate::aconfig::{FlagDeclarations, FlagValue};
 use crate::cache::Cache;
+use crate::codegen_cpp::generate_cpp_code;
 use crate::codegen_java::generate_java_code;
 use crate::protos::ProtoParsedFlags;
 
@@ -58,7 +59,7 @@
     declarations: Vec<Input>,
     values: Vec<Input>,
 ) -> Result<Cache> {
-    let mut cache = Cache::new(namespace.to_owned());
+    let mut cache = Cache::new(namespace.to_owned())?;
 
     for mut input in declarations {
         let mut contents = String::new();
@@ -91,34 +92,38 @@
     Ok(cache)
 }
 
-pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
+pub fn create_java_lib(cache: &Cache) -> Result<OutputFile> {
     generate_java_code(cache)
 }
 
+pub fn create_cpp_lib(cache: &Cache) -> Result<OutputFile> {
+    generate_cpp_code(cache)
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum Format {
+pub enum DumpFormat {
     Text,
     Debug,
     Protobuf,
 }
 
-pub fn dump_cache(cache: Cache, format: Format) -> Result<Vec<u8>> {
+pub fn dump_cache(cache: Cache, format: DumpFormat) -> Result<Vec<u8>> {
     match format {
-        Format::Text => {
+        DumpFormat::Text => {
             let mut lines = vec![];
             for item in cache.iter() {
                 lines.push(format!("{}: {:?}\n", item.name, item.state));
             }
             Ok(lines.concat().into())
         }
-        Format::Debug => {
+        DumpFormat::Debug => {
             let mut lines = vec![];
             for item in cache.iter() {
                 lines.push(format!("{:?}\n", item));
             }
             Ok(lines.concat().into())
         }
-        Format::Protobuf => {
+        DumpFormat::Protobuf => {
             let parsed_flags: ProtoParsedFlags = cache.into();
             let mut output = vec![];
             parsed_flags.write_to_vec(&mut output)?;
@@ -168,7 +173,7 @@
     #[test]
     fn test_dump_text_format() {
         let cache = create_test_cache();
-        let bytes = dump_cache(cache, Format::Text).unwrap();
+        let bytes = dump_cache(cache, DumpFormat::Text).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
         assert!(text.contains("a: Disabled"));
     }
@@ -179,7 +184,7 @@
         use protobuf::Message;
 
         let cache = create_test_cache();
-        let bytes = dump_cache(cache, Format::Protobuf).unwrap();
+        let bytes = dump_cache(cache, DumpFormat::Protobuf).unwrap();
         let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
 
         assert_eq!(
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 513e313..6db5948 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -18,6 +18,7 @@
 
 use anyhow::{anyhow, ensure, Result};
 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
+use core::any::Any;
 use std::fs;
 use std::io;
 use std::io::Write;
@@ -25,12 +26,13 @@
 
 mod aconfig;
 mod cache;
+mod codegen_cpp;
 mod codegen_java;
 mod commands;
 mod protos;
 
 use crate::cache::Cache;
-use commands::{Input, OutputFile, Source};
+use commands::{DumpFormat, Input, OutputFile, Source};
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -48,18 +50,32 @@
                 .arg(Arg::new("out").long("out").required(true)),
         )
         .subcommand(
+            Command::new("create-cpp-lib")
+                .arg(Arg::new("cache").long("cache").required(true))
+                .arg(Arg::new("out").long("out").required(true)),
+        )
+        .subcommand(
             Command::new("dump")
                 .arg(Arg::new("cache").long("cache").required(true))
                 .arg(
                     Arg::new("format")
                         .long("format")
-                        .value_parser(EnumValueParser::<commands::Format>::new())
+                        .value_parser(EnumValueParser::<commands::DumpFormat>::new())
                         .default_value("text"),
                 )
                 .arg(Arg::new("out").long("out").default_value("-")),
         )
 }
 
+fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
+where
+    T: Any + Clone + Send + Sync + 'static,
+{
+    matches
+        .get_one::<T>(arg_name)
+        .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
+}
+
 fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
     let mut opened_files = vec![];
     for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
@@ -89,30 +105,38 @@
     let matches = cli().get_matches();
     match matches.subcommand() {
         Some(("create-cache", sub_matches)) => {
-            let namespace = sub_matches.get_one::<String>("namespace").unwrap();
+            let namespace = get_required_arg::<String>(sub_matches, "namespace")?;
             let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
             let values = open_zero_or_more_files(sub_matches, "values")?;
             let cache = commands::create_cache(namespace, declarations, values)?;
-            let path = sub_matches.get_one::<String>("cache").unwrap();
+            let path = get_required_arg::<String>(sub_matches, "cache")?;
             let file = fs::File::create(path)?;
             cache.write_to_writer(file)?;
         }
         Some(("create-java-lib", sub_matches)) => {
-            let path = sub_matches.get_one::<String>("cache").unwrap();
+            let path = get_required_arg::<String>(sub_matches, "cache")?;
             let file = fs::File::open(path)?;
             let cache = Cache::read_from_reader(file)?;
-            let dir = PathBuf::from(sub_matches.get_one::<String>("out").unwrap());
-            let generated_file = commands::generate_code(&cache).unwrap();
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+            let generated_file = commands::create_java_lib(&cache)?;
+            write_output_file_realtive_to_dir(&dir, &generated_file)?;
+        }
+        Some(("create-cpp-lib", sub_matches)) => {
+            let path = get_required_arg::<String>(sub_matches, "cache")?;
+            let file = fs::File::open(path)?;
+            let cache = Cache::read_from_reader(file)?;
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+            let generated_file = commands::create_cpp_lib(&cache)?;
             write_output_file_realtive_to_dir(&dir, &generated_file)?;
         }
         Some(("dump", sub_matches)) => {
-            let path = sub_matches.get_one::<String>("cache").unwrap();
+            let path = get_required_arg::<String>(sub_matches, "cache")?;
             let file = fs::File::open(path)?;
             let cache = Cache::read_from_reader(file)?;
-            let format = sub_matches.get_one("format").unwrap();
+            let format = get_required_arg::<DumpFormat>(sub_matches, "format")?;
             let output = commands::dump_cache(cache, *format)?;
-            let path = sub_matches.get_one::<String>("out").unwrap();
-            let mut file: Box<dyn Write> = if path == "-" {
+            let path = get_required_arg::<String>(sub_matches, "out")?;
+            let mut file: Box<dyn Write> = if *path == "-" {
                 Box::new(io::stdout())
             } else {
                 Box::new(fs::File::create(path)?)
diff --git a/tools/aconfig/templates/cpp.template b/tools/aconfig/templates/cpp.template
new file mode 100644
index 0000000..ae8b59f
--- /dev/null
+++ b/tools/aconfig/templates/cpp.template
@@ -0,0 +1,25 @@
+#ifndef {namespace}_HEADER_H
+#define {namespace}_HEADER_H
+#include "{namespace}.h"
+{{ if readwrite }}
+#include <server_configurable_flags/get_flags.h>
+using namespace server_configurable_flags;
+{{ endif }}
+namespace {namespace} \{
+    {{ for item in class_elements}}
+    class {item.flag_name} \{
+        public:
+            virtual const bool value() \{
+                {{ if item.readwrite- }}
+                return GetServerConfigurableFlag(
+                    "{namespace}",
+                    "{item.flag_name}",
+                    "{item.default_value}") == "true";
+                {{ -else- }}
+                return {item.default_value};
+                {{ -endif }}
+            }
+    }
+    {{ endfor }}
+}
+#endif
diff --git a/tools/aconfig/templates/java.template b/tools/aconfig/templates/java.template
index ebcd607..89da18b 100644
--- a/tools/aconfig/templates/java.template
+++ b/tools/aconfig/templates/java.template
@@ -1,8 +1,8 @@
-package com.android.internal.aconfig;
+package {namespace};
 {{ if readwrite }}
 import android.provider.DeviceConfig;
 {{ endif }}
-public final class {namespace} \{
+public final class Flags \{
     {{ for item in class_elements}}
     public static boolean {item.method_name}() \{
         {{ if item.readwrite- }}
diff --git a/tools/releasetools/merge_ota.py b/tools/releasetools/merge_ota.py
index 7d3d3a3..441312c 100644
--- a/tools/releasetools/merge_ota.py
+++ b/tools/releasetools/merge_ota.py
@@ -14,6 +14,7 @@
 
 import argparse
 import logging
+import shlex
 import struct
 import sys
 import update_payload
@@ -34,6 +35,7 @@
 logger = logging.getLogger(__name__)
 
 CARE_MAP_ENTRY = "care_map.pb"
+APEX_INFO_ENTRY = "apex_info.pb"
 
 
 def WriteDataBlob(payload: Payload, outfp: BinaryIO, read_size=1024*64):
@@ -188,6 +190,22 @@
               f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
         partition_to_ota[part] = payload
 
+def ApexInfo(file_paths):
+  if len(file_paths) > 1:
+    logger.info("More than one target file specified, will ignore "
+                "apex_info.pb (if any)")
+    return None
+  with zipfile.ZipFile(file_paths[0], "r", allowZip64=True) as zfp:
+    if APEX_INFO_ENTRY in zfp.namelist():
+      apex_info_bytes = zfp.read(APEX_INFO_ENTRY)
+      return apex_info_bytes
+  return None
+
+def ParseSignerArgs(args):
+  if args is None:
+    return None
+  return shlex.split(args)
+
 def main(argv):
   parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
   parser.add_argument('packages', type=str, nargs='+',
@@ -196,6 +214,13 @@
                       help='Paths to private key for signing payload')
   parser.add_argument('--search_path', type=str,
                       help='Search path for framework/signapk.jar')
+  parser.add_argument('--payload_signer', type=str,
+                      help='Path to custom payload signer')
+  parser.add_argument('--payload_signer_args', type=ParseSignerArgs,
+                      help='Arguments for payload signer if necessary')
+  parser.add_argument('--payload_signer_maximum_signature_size', type=str,
+                      help='Maximum signature size (in bytes) that would be '
+                      'generated by the given payload signer')
   parser.add_argument('--output', type=str,
                       help='Paths to output merged ota', required=True)
   parser.add_argument('--metadata_ota', type=str,
@@ -203,6 +228,9 @@
   parser.add_argument('--private_key_suffix', type=str,
                       help='Suffix to be appended to package_key path', default=".pk8")
   parser.add_argument('-v', action="store_true", help="Enable verbose logging", dest="verbose")
+  parser.epilog = ('This tool can also be used to resign a regular OTA. For a single regular OTA, '
+                   'apex_info.pb will be written to output. When merging multiple OTAs, '
+                   'apex_info.pb will not be written.')
   args = parser.parse_args(argv[1:])
   file_paths = args.packages
 
@@ -225,6 +253,13 @@
 
   merged_manifest = MergeManifests(payloads)
 
+  # Get signing keys
+  key_passwords = common.GetKeyPasswords([args.package_key])
+
+  generator = PayloadGenerator()
+
+  apex_info_bytes = ApexInfo(file_paths)
+
   with tempfile.NamedTemporaryFile() as unsigned_payload:
     WriteHeaderAndManifest(merged_manifest, unsigned_payload)
     ConcatBlobs(payloads, unsigned_payload)
@@ -236,20 +271,31 @@
 
     if args.package_key:
       logger.info("Signing payload...")
-      signer = PayloadSigner(args.package_key, args.private_key_suffix)
+      # TODO: remove OPTIONS when no longer used as fallback in payload_signer
+      common.OPTIONS.payload_signer_args = None
+      common.OPTIONS.payload_signer_maximum_signature_size = None
+      signer = PayloadSigner(args.package_key, args.private_key_suffix,
+                             key_passwords[args.package_key],
+                             payload_signer=args.payload_signer,
+                             payload_signer_args=args.payload_signer_args,
+                             payload_signer_maximum_signature_size=args.payload_signer_maximum_signature_size)
       generator.payload_file = unsigned_payload.name
       generator.Sign(signer)
 
     logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
 
     logger.info("Writing to %s", args.output)
+
     key_passwords = common.GetKeyPasswords([args.package_key])
     with tempfile.NamedTemporaryFile(prefix="signed_ota", suffix=".zip") as signed_ota:
       with zipfile.ZipFile(signed_ota, "w") as zfp:
         generator.WriteToZip(zfp)
         care_map_bytes = MergeCareMap(args.packages)
         if care_map_bytes:
-          zfp.writestr(CARE_MAP_ENTRY, care_map_bytes)
+          common.ZipWriteStr(zfp, CARE_MAP_ENTRY, care_map_bytes)
+        if apex_info_bytes:
+          logger.info("Writing %s", APEX_INFO_ENTRY)
+          common.ZipWriteStr(zfp, APEX_INFO_ENTRY, apex_info_bytes)
       AddOtaMetadata(signed_ota.name, metadata_ota,
                      args.output, args.package_key, key_passwords[args.package_key])
   return 0
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 04ef5ef..afbe81a 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -525,8 +525,7 @@
 
 
 def ParseInfoDict(target_file_path):
-  with zipfile.ZipFile(target_file_path, 'r', allowZip64=True) as zfp:
-    return common.LoadInfoDict(zfp)
+  return common.LoadInfoDict(target_file_path)
 
 
 def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index 4f342ac..9933aef 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -36,11 +36,16 @@
   (OPTIONS.package_key) and calls openssl for the signing works.
   """
 
-  def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None):
+  def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None,
+               payload_signer_args=None, payload_signer_maximum_signature_size=None):
     if package_key is None:
       package_key = OPTIONS.package_key
     if private_key_suffix is None:
       private_key_suffix = OPTIONS.private_key_suffix
+    if payload_signer_args is None:
+      payload_signer_args = OPTIONS.payload_signer_args
+    if payload_signer_maximum_signature_size is None:
+      payload_signer_maximum_signature_size = OPTIONS.payload_signer_maximum_signature_size
 
     if payload_signer is None:
       # Prepare the payload signing key.
@@ -59,10 +64,10 @@
           signing_key)
     else:
       self.signer = payload_signer
-      self.signer_args = OPTIONS.payload_signer_args
-      if OPTIONS.payload_signer_maximum_signature_size:
+      self.signer_args = payload_signer_args
+      if payload_signer_maximum_signature_size:
         self.maximum_signature_size = int(
-            OPTIONS.payload_signer_maximum_signature_size)
+            payload_signer_maximum_signature_size)
       else:
         # The legacy config uses RSA2048 keys.
         logger.warning("The maximum signature size for payload signer is not"
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 56509c9..2415f7e 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -19,7 +19,6 @@
 Usage example:
   generate-sbom.py --output_file out/target/product/vsoc_x86_64/sbom.spdx \
                    --metadata out/target/product/vsoc_x86_64/sbom-metadata.csv \
-                   --product_out_dir=out/target/product/vsoc_x86_64 \
                    --build_version $(cat out/target/product/vsoc_x86_64/build_fingerprint.txt) \
                    --product_mfr=Google
 """
@@ -89,11 +88,11 @@
   parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Print more information.')
   parser.add_argument('--output_file', required=True, help='The generated SBOM file in SPDX format.')
   parser.add_argument('--metadata', required=True, help='The SBOM metadata file path.')
-  parser.add_argument('--product_out_dir', required=True, help='The parent directory of all the installed files.')
   parser.add_argument('--build_version', required=True, help='The build version.')
   parser.add_argument('--product_mfr', required=True, help='The product manufacturer.')
   parser.add_argument('--json', action='store_true', default=False, help='Generated SBOM file in SPDX JSON format')
-  parser.add_argument('--unbundled', action='store_true', default=False, help='Generate SBOM file for unbundled module')
+  parser.add_argument('--unbundled_apk', action='store_true', default=False, help='Generate SBOM for unbundled APKs')
+  parser.add_argument('--unbundled_apex', action='store_true', default=False, help='Generate SBOM for unbundled APEXs')
 
   return parser.parse_args()
 
@@ -127,7 +126,6 @@
 
 
 def checksum(file_path):
-  file_path = args.product_out_dir + '/' + file_path
   h = hashlib.sha1()
   if os.path.islink(file_path):
     h.update(os.readlink(file_path).encode('utf-8'))
@@ -265,8 +263,8 @@
 
 def get_sbom_fragments(installed_file_metadata, metadata_file_path):
   """Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
-  package, a UPSTREAM package if it's a source package and a external SBOM document reference if
-  it's a prebuilt package with sbom_ref defined in its METADATA file.
+  package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
+  METADATA file.
 
   See go/android-spdx and go/android-sbom-gen for more details.
   """
@@ -303,25 +301,33 @@
     prebuilt_package = sbom_data.Package(id=prebuilt_package_id,
                                          name=name,
                                          download_location=sbom_data.VALUE_NONE,
-                                         version=args.build_version,
+                                         version=version if version else args.build_version,
                                          supplier='Organization: ' + args.product_mfr)
-    packages.append(prebuilt_package)
 
-    if metadata_file_path:
-      metadata_proto = metadata_file_protos[metadata_file_path]
-      if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
-        sbom_url = metadata_proto.third_party.sbom_ref.url
-        sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
-        upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
-        if sbom_url and sbom_checksum and upstream_element_id:
-          doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
-          external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
-                                                                 uri=sbom_url,
-                                                                 checksum=sbom_checksum)
-          relationships.append(
-            sbom_data.Relationship(id1=prebuilt_package_id,
-                                   relationship=sbom_data.RelationshipType.VARIANT_OF,
-                                   id2=doc_ref_id + ':' + upstream_element_id))
+    upstream_package_id = new_package_id(name, PKG_UPSTREAM)
+    upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version = version,
+                                         supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
+                                         download_location=download_location)
+    packages += [prebuilt_package, upstream_package]
+    relationships.append(sbom_data.Relationship(id1=prebuilt_package_id,
+                                                relationship=sbom_data.RelationshipType.VARIANT_OF,
+                                                id2=upstream_package_id))
+
+  if metadata_file_path:
+    metadata_proto = metadata_file_protos[metadata_file_path]
+    if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
+      sbom_url = metadata_proto.third_party.sbom_ref.url
+      sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
+      upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
+      if sbom_url and sbom_checksum and upstream_element_id:
+        doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+        external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
+                                                               uri=sbom_url,
+                                                               checksum=sbom_checksum)
+        relationships.append(
+          sbom_data.Relationship(id1=upstream_package_id,
+                                 relationship=sbom_data.RelationshipType.VARIANT_OF,
+                                 id2=doc_ref_id + ':' + upstream_element_id))
 
   return external_doc_ref, packages, relationships
 
@@ -334,9 +340,8 @@
   return h.hexdigest()
 
 
-def save_report(report):
-  prefix, _ = os.path.splitext(args.output_file)
-  with open(prefix + '-gen-report.txt', 'w', encoding='utf-8') as report_file:
+def save_report(report_file_path, report):
+  with open(report_file_path, 'w', encoding='utf-8') as report_file:
     for type, issues in report.items():
       report_file.write(type + '\n')
       for issue in issues:
@@ -394,7 +399,7 @@
             installed_file_metadata['installed_file'], installed_file_metadata['module_path']))
 
 
-def generate_sbom_for_unbundled():
+def generate_sbom_for_unbundled_apk():
   with open(args.metadata, newline='') as sbom_metadata_file:
     reader = csv.DictReader(sbom_metadata_file)
     doc = sbom_data.Document(name=args.build_version,
@@ -402,7 +407,7 @@
                              creators=['Organization: ' + args.product_mfr])
     for installed_file_metadata in reader:
       installed_file = installed_file_metadata['installed_file']
-      if args.output_file != args.product_out_dir + installed_file + '.spdx.json':
+      if args.output_file != installed_file_metadata['build_output_path'] + '.spdx.json':
         continue
 
       module_path = installed_file_metadata['module_path']
@@ -412,7 +417,9 @@
                                   version=args.build_version,
                                   supplier='Organization: ' + args.product_mfr)
       file_id = new_file_id(installed_file)
-      file = sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file))
+      file = sbom_data.File(id=file_id,
+                            name=installed_file,
+                            checksum=checksum(installed_file_metadata['build_output_path']))
       relationship = sbom_data.Relationship(id1=file_id,
                                             relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                             id2=package_id)
@@ -435,24 +442,25 @@
   args = get_args()
   log('Args:', vars(args))
 
-  if args.unbundled:
-    generate_sbom_for_unbundled()
+  if args.unbundled_apk:
+    generate_sbom_for_unbundled_apk()
     return
 
   global metadata_file_protos
   metadata_file_protos = {}
 
-  doc = sbom_data.Document(name=args.build_version,
-                           namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
-                           creators=['Organization: ' + args.product_mfr])
-
   product_package = sbom_data.Package(id=sbom_data.SPDXID_PRODUCT,
                                       name=sbom_data.PACKAGE_NAME_PRODUCT,
                                       download_location=sbom_data.VALUE_NONE,
                                       version=args.build_version,
                                       supplier='Organization: ' + args.product_mfr,
                                       files_analyzed=True)
-  doc.packages.append(product_package)
+
+  doc = sbom_data.Document(name=args.build_version,
+                           namespace=f'https://www.google.com/sbom/spdx/android/{args.build_version}',
+                           creators=['Organization: ' + args.product_mfr])
+  if not args.unbundled_apex:
+    doc.packages.append(product_package)
 
   doc.packages.append(sbom_data.Package(id=sbom_data.SPDXID_PLATFORM,
                                         name=sbom_data.PACKAGE_NAME_PLATFORM,
@@ -478,18 +486,21 @@
       module_path = installed_file_metadata['module_path']
       product_copy_files = installed_file_metadata['product_copy_files']
       kernel_module_copy_files = installed_file_metadata['kernel_module_copy_files']
+      build_output_path = installed_file_metadata['build_output_path']
 
       if not installed_file_has_metadata(installed_file_metadata, report):
         continue
-      file_path = args.product_out_dir + '/' + installed_file
-      if not (os.path.islink(file_path) or os.path.isfile(file_path)):
+      if not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
         report[ISSUE_INSTALLED_FILE_NOT_EXIST].append(installed_file)
         continue
 
       file_id = new_file_id(installed_file)
       doc.files.append(
-        sbom_data.File(id=file_id, name=installed_file, checksum=checksum(installed_file)))
-      product_package.file_ids.append(file_id)
+        sbom_data.File(id=file_id, name=installed_file, checksum=checksum(build_output_path)))
+      if not args.unbundled_apex:
+        product_package.file_ids.append(file_id)
+      elif len(doc.files) > 1:
+          doc.add_relationship(sbom_data.Relationship(doc.files[0].id, sbom_data.RelationshipType.CONTAINS, file_id))
 
       if is_source_package(installed_file_metadata) or is_prebuilt_package(installed_file_metadata):
         metadata_file_path = get_metadata_file_path(installed_file_metadata)
@@ -533,16 +544,31 @@
                                                     relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                                     id2=sbom_data.SPDXID_PLATFORM))
 
-  product_package.verification_code = generate_package_verification_code(doc.files)
+  if not args.unbundled_apex:
+    product_package.verification_code = generate_package_verification_code(doc.files)
+
+  if args.unbundled_apex:
+    doc.describes = doc.files[0].id
 
   # Save SBOM records to output file
   doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
-  with open(args.output_file, 'w', encoding="utf-8") as file:
-    sbom_writers.TagValueWriter.write(doc, file)
+  prefix = args.output_file
+  if prefix.endswith('.spdx'):
+    prefix = prefix.removesuffix('.spdx')
+  elif prefix.endswith('.spdx.json'):
+    prefix = prefix.removesuffix('.spdx.json')
+
+  output_file = prefix + '.spdx'
+  if args.unbundled_apex:
+    output_file = prefix + '-fragment.spdx'
+  with open(output_file, 'w', encoding="utf-8") as file:
+    sbom_writers.TagValueWriter.write(doc, file, fragment=args.unbundled_apex)
   if args.json:
-    with open(args.output_file+'.json', 'w', encoding="utf-8") as file:
+    with open(prefix + '.spdx.json', 'w', encoding="utf-8") as file:
       sbom_writers.JSONWriter.write(doc, file)
 
+  save_report(prefix + '-gen-report.txt', report)
+
 
 if __name__ == '__main__':
   main()
diff --git a/tools/sbom/sbom_data.py b/tools/sbom/sbom_data.py
index d2ef48d..14c4eb2 100644
--- a/tools/sbom/sbom_data.py
+++ b/tools/sbom/sbom_data.py
@@ -80,6 +80,7 @@
   DESCRIBES = 'DESCRIBES'
   VARIANT_OF = 'VARIANT_OF'
   GENERATED_FROM = 'GENERATED_FROM'
+  CONTAINS = 'CONTAINS'
 
 
 @dataclass
diff --git a/tools/sbom/sbom_writers.py b/tools/sbom/sbom_writers.py
index b1c66c5..85dee9d 100644
--- a/tools/sbom/sbom_writers.py
+++ b/tools/sbom/sbom_writers.py
@@ -110,24 +110,26 @@
     return tagvalues
 
   @staticmethod
-  def marshal_described_element(sbom_doc):
+  def marshal_described_element(sbom_doc, fragment):
     if not sbom_doc.describes:
       return None
 
     product_package = [p for p in sbom_doc.packages if p.id == sbom_doc.describes]
     if product_package:
       tagvalues = TagValueWriter.marshal_package(product_package[0])
-      tagvalues.append(
-        f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
+      if not fragment:
+        tagvalues.append(
+            f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
 
       tagvalues.append('')
       return tagvalues
 
     file = [f for f in sbom_doc.files if f.id == sbom_doc.describes]
     if file:
-      tagvalues = [
-        f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}'
-      ]
+      tagvalues = TagValueWriter.marshal_file(file[0])
+      if not fragment:
+        tagvalues.append(
+            f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
 
       return tagvalues
 
@@ -180,6 +182,8 @@
   def marshal_files(sbom_doc):
     tagvalues = []
     for file in sbom_doc.files:
+      if file.id == sbom_doc.describes:
+        continue
       tagvalues += TagValueWriter.marshal_file(file)
     return tagvalues
 
@@ -204,9 +208,9 @@
     content = []
     if not fragment:
       content += TagValueWriter.marshal_doc_headers(sbom_doc)
-      described_element = TagValueWriter.marshal_described_element(sbom_doc)
-      if described_element:
-        content += described_element
+    described_element = TagValueWriter.marshal_described_element(sbom_doc, fragment)
+    if described_element:
+      content += described_element
     content += TagValueWriter.marshal_files(sbom_doc)
     tagvalues, marshaled_relationships = TagValueWriter.marshal_packages(sbom_doc)
     content += tagvalues