Merge "Handle symlinks when extracting zipfiles"
diff --git a/core/Makefile b/core/Makefile
index 426dded..6dbbef1 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -5297,7 +5297,6 @@
 INTERNAL_OTATOOLS_MODULES += \
   apexer \
   apex_compression_tool \
-  blkid_static \
   deapexer \
   debugfs_static \
   dump_apex_info \
@@ -5887,16 +5886,25 @@
     echo "virtual_ab_cow_version=$(PRODUCT_VIRTUAL_AB_COW_VERSION)" >> $(1))
 endef
 
-# Copy an image file to a directory and generate a block list map file from the image.
+# Copy an image file to a directory and generate a block list map file from the image,
+# only if the map_file_generator supports the file system.
+# Otherwise, skip generating map files as well as copying images. The image will be
+# generated from the $(ADD_IMG_TO_TARGET_FILES) to generate the map file with it.
 # $(1): path of the image file
 # $(2): target out directory
-# $(3): name of the map file. skip generating map file if empty
+# $(3): image name to generate a map file. skip generating map file if empty
 define copy-image-and-generate-map
-  mkdir -p $(2)
-  cp $(1) $(2)
-  $(if $(3),
-    UNSQUASHFS=$(HOST_OUT_EXECUTABLES)/unsquashfs $(HOST_OUT_EXECUTABLES)/map_file_generator $(1) $(2)/$(3)
-  )
+  $(eval _supported_fs_for_map_file_generator := erofs ext%)
+  $(eval _img := $(call to-upper,$(3)))
+  $(if $(3),$(eval _map_fs_type := $(BOARD_$(_img)IMAGE_FILE_SYSTEM_TYPE)),\
+    $(eval _no_map_file := "true"))
+  $(if $(filter $(_supported_fs_for_map_file_generator),$(_map_fs_type))$(_no_map_file),\
+    mkdir -p $(2); \
+    cp $(1) $(2); \
+    $(if $(3),$(HOST_OUT_EXECUTABLES)/map_file_generator $(1) $(2)/$(3).map))
+  $(eval _img :=)
+  $(eval _map_fs_type :=)
+  $(eval _no_map_file :=)
 endef
 
 # By conditionally including the dependency of the target files package on the
@@ -6408,35 +6416,35 @@
 	@# Run fs_config on all the system, vendor, boot ramdisk,
 	@# and recovery ramdisk files in the zip, and save the output
 ifdef BUILDING_SYSTEM_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEMIMAGE),$(zip_root)/IMAGES,system.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEMIMAGE),$(zip_root)/IMAGES,system)
 	$(hide) $(call fs_config,$(zip_root)/SYSTEM,system/) > $(zip_root)/META/filesystem_config.txt
 endif
 ifdef BUILDING_VENDOR_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_VENDORIMAGE_TARGET),$(zip_root)/IMAGES,vendor.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_VENDORIMAGE_TARGET),$(zip_root)/IMAGES,vendor)
 	$(hide) $(call fs_config,$(zip_root)/VENDOR,vendor/) > $(zip_root)/META/vendor_filesystem_config.txt
 endif
 ifdef BUILDING_PRODUCT_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_PRODUCTIMAGE_TARGET),$(zip_root)/IMAGES,product.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_PRODUCTIMAGE_TARGET),$(zip_root)/IMAGES,product)
 	$(hide) $(call fs_config,$(zip_root)/PRODUCT,product/) > $(zip_root)/META/product_filesystem_config.txt
 endif
 ifdef BUILDING_SYSTEM_EXT_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEM_EXTIMAGE_TARGET),$(zip_root)/IMAGES,system_ext.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEM_EXTIMAGE_TARGET),$(zip_root)/IMAGES,system_ext)
 	$(hide) $(call fs_config,$(zip_root)/SYSTEM_EXT,system_ext/) > $(zip_root)/META/system_ext_filesystem_config.txt
 endif
 ifdef BUILDING_ODM_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_ODMIMAGE_TARGET),$(zip_root)/IMAGES,odm.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_ODMIMAGE_TARGET),$(zip_root)/IMAGES,odm)
 	$(hide) $(call fs_config,$(zip_root)/ODM,odm/) > $(zip_root)/META/odm_filesystem_config.txt
 endif
 ifdef BUILDING_VENDOR_DLKM_IMAGE
-	$(hide)$(call copy-image-and-generate-map,$(BUILT_VENDOR_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,vendor_dlkm.map)
+	$(hide)$(call copy-image-and-generate-map,$(BUILT_VENDOR_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,vendor_dlkm)
 	$(hide) $(call fs_config,$(zip_root)/VENDOR_DLKM,vendor_dlkm/) > $(zip_root)/META/vendor_dlkm_filesystem_config.txt
 endif
 ifdef BUILDING_ODM_DLKM_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_ODM_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,odm_dlkm.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_ODM_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,odm_dlkm)
 	$(hide) $(call fs_config,$(zip_root)/ODM_DLKM,odm_dlkm/) > $(zip_root)/META/odm_dlkm_filesystem_config.txt
 endif
 ifdef BUILDING_SYSTEM_DLKM_IMAGE
-	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEM_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,system_dlkm.map)
+	$(hide) $(call copy-image-and-generate-map,$(BUILT_SYSTEM_DLKMIMAGE_TARGET),$(zip_root)/IMAGES,system_dlkm)
 	$(hide) $(call fs_config,$(zip_root)/SYSTEM_DLKM,system_dlkm/) > $(zip_root)/META/system_dlkm_filesystem_config.txt
 endif
 	@# ROOT always contains the files for the root under normal boot.
diff --git a/core/board_config_wifi.mk b/core/board_config_wifi.mk
index 8289bf2..3c27d59 100644
--- a/core/board_config_wifi.mk
+++ b/core/board_config_wifi.mk
@@ -80,4 +80,7 @@
 endif
 ifeq ($(strip $(TARGET_USES_AOSP_FOR_WLAN)),true)
     $(call soong_config_set,wifi,target_uses_aosp_for_wlan,true)
-endif
\ No newline at end of file
+endif
+ifdef WIFI_FEATURE_IMU_DETECTION
+    $(call soong_config_set,wifi,feature_imu_detection,$(WIFI_FEATURE_IMU_DETECTION))
+endif
diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk
index 86ca729..6ac169b 100644
--- a/core/dex_preopt.mk
+++ b/core/dex_preopt.mk
@@ -94,6 +94,7 @@
 booclasspath_locations_arg := $(subst $(space),:,$(DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS))
 boot_images := $(subst :,$(space),$(DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE$(DEXPREOPT_INFIX)))
 boot_image_arg := $(subst $(space),:,$(patsubst /%,%,$(boot_images)))
+dex2oat_extra_args := $(if $(filter true,$(ENABLE_UFFD_GC)),--runtime-arg -Xgc:CMC)
 
 boot_zip_metadata_txt := $(dir $(boot_zip))boot_zip/METADATA.txt
 $(boot_zip_metadata_txt):
@@ -101,6 +102,7 @@
 	echo "booclasspath = $(booclasspath_arg)" >> $@
 	echo "booclasspath-locations = $(booclasspath_locations_arg)" >> $@
 	echo "boot-image = $(boot_image_arg)" >> $@
+	echo "extra-args = $(dex2oat_extra_args)" >> $@
 
 $(call dist-for-goals, droidcore, $(boot_zip_metadata_txt))
 
diff --git a/core/main.mk b/core/main.mk
index 40e690d..498cf72 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -2151,13 +2151,21 @@
 #       is_kernel_modules_blocklist: modules.blocklist created for _dlkm partitions, see macro build-image-kernel-modules-dir in Makefile.
 #       is_fsverity_build_manifest_apk: BuildManifest<part>.apk files for system and system_ext partition, see ALL_FSVERITY_BUILD_MANIFEST_APK in Makefile.
 #       is_linker_config: see SYSTEM_LINKER_CONFIG and vendor_linker_config_file in Makefile.
+#   build_output_path: the path of the built file, used to calculate checksum
+#   static_libraries/whole_static_libraries: list of module name of the static libraries the file links against, e.g. libclang_rt.builtins or libclang_rt.builtins_32
+#       Info of all static libraries of all installed files are collected in variable _all_static_libs that is used to list all the static library files in sbom-metadata.csv.
+#       See the second foreach loop in the rule of sbom-metadata.csv for the detailed info of static libraries collected in _all_static_libs.
+#   is_static_lib: whether the file is a static library
 
+metadata_list := $(OUT_DIR)/.module_paths/METADATA.list
+metadata_files := $(subst $(newline),$(space),$(file <$(metadata_list)))
 # (TODO: b/272358583 find another way of always rebuilding this target)
 # Remove the sbom-metadata.csv whenever makefile is evaluated
 $(shell rm $(PRODUCT_OUT)/sbom-metadata.csv >/dev/null 2>&1)
-$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files)
+$(PRODUCT_OUT)/sbom-metadata.csv: $(installed_files) $(metadata_list) $(metadata_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,build_output_path >> $@
+	echo installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib >> $@
+	$(eval _all_static_libs :=)
 	$(foreach f,$(installed_files),\
 	  $(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \
 	  $(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$f)) \
@@ -2179,11 +2187,25 @@
 	  $(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)$(comma)$(_build_output_path) >> $@ $(newline) \
+	  $(eval _static_libs := $(ALL_INSTALLED_FILES.$f.STATIC_LIBRARIES)) \
+	  $(eval _whole_static_libs := $(ALL_INSTALLED_FILES.$f.WHOLE_STATIC_LIBRARIES)) \
+	  $(foreach l,$(_static_libs),$(eval _all_static_libs += $l:$(strip $(sort $(ALL_MODULES.$l.PATH))):$(strip $(sort $(ALL_MODULES.$l.SOONG_MODULE_TYPE))):$(ALL_STATIC_LIBRARIES.$l.BUILT_FILE))) \
+	  $(foreach l,$(_whole_static_libs),$(eval _all_static_libs += $l:$(strip $(sort $(ALL_MODULES.$l.PATH))):$(strip $(sort $(ALL_MODULES.$l.SOONG_MODULE_TYPE))):$(ALL_STATIC_LIBRARIES.$l.BUILT_FILE))) \
+	  echo /$(_path_on_device),$(_module_path),$(_soong_module_type),$(_is_prebuilt_make_module),$(_product_copy_files),$(_kernel_module_copy_files),$(_is_platform_generated),$(_build_output_path),$(_static_libs),$(_whole_static_libs), >> $@; \
 	  $(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)$(comma)$(PRODUCT_OUT)/$$i >> $@ ; 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$(comma)$(_static_libs)$(comma)$(_whole_static_libs)$(comma) >> $@ ; done ; \
 	  ) \
 	)
+	$(foreach l,$(sort $(_all_static_libs)), \
+	  $(eval _lib_stem := $(call word-colon,1,$l)) \
+	  $(eval _module_path := $(call word-colon,2,$l)) \
+	  $(eval _soong_module_type := $(call word-colon,3,$l)) \
+	  $(eval _built_file := $(call word-colon,4,$l)) \
+	  $(eval _static_libs := $(ALL_STATIC_LIBRARIES.$l.STATIC_LIBRARIES)) \
+	  $(eval _whole_static_libs := $(ALL_STATIC_LIBRARIES.$l.WHOLE_STATIC_LIBRARIES)) \
+	  $(eval _is_static_lib := Y) \
+	  echo $(_lib_stem).a,$(_module_path),$(_soong_module_type),,,,,$(_built_file),$(_static_libs),$(_whole_static_libs),$(_is_static_lib) >> $@; \
+	)
 
 .PHONY: sbom
 ifeq ($(TARGET_BUILD_APPS),)
@@ -2195,17 +2217,47 @@
 
 $(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 --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --unbundled_apk
+# Create build rules for generating SBOMs of unbundled APKs and APEXs
+# $1: sbom file
+# $2: sbom fragment file
+# $3: installed file
+# $4: sbom-metadata.csv file
+define generate-app-sbom
+$(eval _path_on_device := $(patsubst $(PRODUCT_OUT)/%,%,$(3)))
+$(eval _module_name := $(ALL_INSTALLED_FILES.$(3)))
+$(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH))))
+$(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE))))
+$(eval _dep_modules := $(filter %.$(_module_name),$(ALL_MODULES)) $(filter %.$(_module_name)$(TARGET_2ND_ARCH_MODULE_SUFFIX),$(ALL_MODULES)))
+$(eval _is_apex := $(filter %.apex,$(3)))
+
+$(4): $(3) $(metadata_list) $(metadata_files)
+	rm -rf $$@
+	echo installed_file,module_path,soong_module_type,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,build_output_path,static_libraries,whole_static_libraries,is_static_lib >> $$@
+	echo /$(_path_on_device),$(_module_path),$(_soong_module_type),,,,,$(3),,, >> $$@
+	$(if $(filter %.apex,$(3)),\
+	  $(foreach m,$(_dep_modules),\
+	    echo $(patsubst $(PRODUCT_OUT)/apex/$(_module_name)/%,%,$(ALL_MODULES.$m.INSTALLED)),$(sort $(ALL_MODULES.$m.PATH)),$(sort $(ALL_MODULES.$m.SOONG_MODULE_TYPE)),,,,,$(strip $(ALL_MODULES.$m.BUILT)),,, >> $$@;))
+
+$(2): $(1)
+$(1): $(4) $(GEN_SBOM)
+	rm -rf $$@
+	$(GEN_SBOM) --output_file $$@ --metadata $(4) --build_version $$(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --json $(if $(filter %.apk,$(3)),--unbundled_apk,--unbundled_apex)
+endef
+
+apps_only_sbom_files :=
+apps_only_fragment_files :=
+$(foreach f,$(filter %.apk %.apex,$(installed_files)), \
+  $(eval _metadata_csv_file := $(patsubst %,%-sbom-metadata.csv,$f)) \
+  $(eval _sbom_file := $(patsubst %,%.spdx.json,$f)) \
+  $(eval _fragment_file := $(patsubst %,%-fragment.spdx,$f)) \
+  $(eval apps_only_sbom_files += $(_sbom_file)) \
+  $(eval apps_only_fragment_files += $(_fragment_file)) \
+  $(eval $(call generate-app-sbom,$(_sbom_file),$(_fragment_file),$f,$(_metadata_csv_file))) \
+)
 
 sbom: $(apps_only_sbom_files)
 
-$(foreach f,$(apps_only_sbom_files),$(eval $(patsubst %.spdx.json,%-fragment.spdx,$f): $f))
-apps_only_fragment_files := $(patsubst %.spdx.json,%-fragment.spdx,$(apps_only_sbom_files))
 $(foreach f,$(apps_only_fragment_files),$(eval apps_only_fragment_dist_files += :sbom/$(notdir $f)))
-
 $(foreach f,$(apps_only_sbom_files),$(eval apps_only_sbom_dist_files += :sbom/$(notdir $f)))
 $(call dist-for-goals,apps_only,$(join $(apps_only_sbom_files),$(apps_only_sbom_dist_files)) $(join $(apps_only_fragment_files),$(apps_only_fragment_dist_files)))
 endif
diff --git a/core/product.mk b/core/product.mk
index 7e67dcd..8f4db38 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -378,8 +378,6 @@
 # a java_sdk_library module.
 _product_list_vars += PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST
 
-_product_single_value_vars += PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES
-
 # Install a copy of the debug policy to the system_ext partition, and allow
 # init-second-stage to load debug policy from system_ext.
 # This option is only meant to be set by compliance GSI targets.
diff --git a/core/sbom.mk b/core/sbom.mk
index e23bbc1..39c251a 100644
--- a/core/sbom.mk
+++ b/core/sbom.mk
@@ -3,9 +3,20 @@
 # unless a .mk file changes its installed file after including base_rules.mk.
 
 ifdef my_register_name
+  # ALL_INSTALLED_FILES.$(installed_file).STATIC_LIBRARIES: list of module name of static libraries, e.g. libc++demangle libclang_rt.builtins, for primary arch
+  # ALL_INSTALLED_FILES.$(installed_file).WHOLE_STATIC_LIBRARIES: list of module name of static libraries, e.g. libc++demangle_32 libclang_rt.builtins_32, for 2nd arch.
   ifneq (, $(strip $(ALL_MODULES.$(my_register_name).INSTALLED)))
     $(foreach installed_file,$(ALL_MODULES.$(my_register_name).INSTALLED),\
       $(eval ALL_INSTALLED_FILES.$(installed_file) := $(my_register_name))\
+      $(eval ALL_INSTALLED_FILES.$(installed_file).STATIC_LIBRARIES := $(foreach l,$(strip $(sort $(LOCAL_STATIC_LIBRARIES))),$l$(if $(LOCAL_2ND_ARCH_VAR_PREFIX),$($(my_prefix)2ND_ARCH_MODULE_SUFFIX))))\
+      $(eval ALL_INSTALLED_FILES.$(installed_file).WHOLE_STATIC_LIBRARIES := $(foreach l,$(strip $(sort $(LOCAL_WHOLE_STATIC_LIBRARIES))),$l$(if $(LOCAL_2ND_ARCH_VAR_PREFIX),$($(my_prefix)2ND_ARCH_MODULE_SUFFIX))))\
     )
   endif
+  ifeq (STATIC_LIBRARIES,$(LOCAL_MODULE_CLASS))
+  ALL_STATIC_LIBRARIES.$(my_register_name).STATIC_LIBRARIES := $(foreach l,$(strip $(sort $(LOCAL_STATIC_LIBRARIES))),$l$($(my_prefix)2ND_ARCH_MODULE_SUFFIX))
+  ALL_STATIC_LIBRARIES.$(my_register_name).WHOLE_STATIC_LIBRARIES := $(foreach l,$(strip $(sort $(LOCAL_WHOLE_STATIC_LIBRARIES))),$l$($(my_prefix)2ND_ARCH_MODULE_SUFFIX))
+  ifdef LOCAL_SOONG_MODULE_TYPE
+    ALL_STATIC_LIBRARIES.$(my_register_name).BUILT_FILE := $(LOCAL_PREBUILT_MODULE_FILE)
+  endif
+  endif
 endif
\ No newline at end of file
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 6c613d6..5f62ec6 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -265,8 +265,6 @@
 $(call add_json_bool, EnforceInterPartitionJavaSdkLibrary, $(filter true,$(PRODUCT_ENFORCE_INTER_PARTITION_JAVA_SDK_LIBRARY)))
 $(call add_json_list, InterPartitionJavaLibraryAllowList, $(PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST))
 
-$(call add_json_bool, InstallExtraFlattenedApexes, $(PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES))
-
 $(call add_json_bool, CompressedApex, $(filter true,$(PRODUCT_COMPRESSED_APEX)))
 
 ifndef APEX_BUILD_FOR_PRE_S_DEVICES
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 2df85e5..c747d89 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -4,6 +4,7 @@
 LLNDK: libGLESv3.so
 LLNDK: libRS.so
 LLNDK: libandroid_net.so
+LLNDK: libapexsupport.so
 LLNDK: libbinder_ndk.so
 LLNDK: libc.so
 LLNDK: libcgrouprc.so
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index 3b97792..375b7cb 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -47,17 +47,6 @@
 # Disable the build-time debugfs restrictions on GSI builds
 PRODUCT_SET_DEBUGFS_RESTRICTIONS := false
 
-# GSI targets should install "unflattened" APEXes in /system
-TARGET_FLATTEN_APEX := false
-
-# GSI targets should install "flattened" APEXes in /system_ext as well
-PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES := true
-
-# The flattened version of com.android.apex.cts.shim.v1 should be explicitly installed
-# because the shim apex is prebuilt one and PRODUCT_INSTALL_EXTRA_FLATTENED_APEXES is not
-# supported for prebuilt_apex modules yet.
-PRODUCT_PACKAGES += com.android.apex.cts.shim.v1_with_prebuilts.flattened
-
 # GSI specific tasks on boot
 PRODUCT_PACKAGES += \
     gsi_skip_mount.cfg \
diff --git a/target/product/updatable_apex.mk b/target/product/updatable_apex.mk
index d606e00..c19982b 100644
--- a/target/product/updatable_apex.mk
+++ b/target/product/updatable_apex.mk
@@ -20,7 +20,7 @@
   # com.android.apex.cts.shim.v1_prebuilt overrides CtsShimPrebuilt
   # and CtsShimPrivPrebuilt since they are packaged inside the APEX.
   PRODUCT_PACKAGES += com.android.apex.cts.shim.v1_prebuilt
-  PRODUCT_VENDOR_PROPERTIES := ro.apex.updatable=true
+  PRODUCT_SYSTEM_PROPERTIES := ro.apex.updatable=true
   TARGET_FLATTEN_APEX := false
   # Use compressed apexes in pre-installed partitions.
   # Note: this doesn't mean that all pre-installed apexes will be compressed.
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index 9f6424f..b59fdfc 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -35,21 +35,21 @@
 // aconfig input messages: flag declarations and values
 
 message flag_declaration {
-  required string name = 1;
-  required string namespace = 2;
-  required string description = 3;
+  optional string name = 1;
+  optional string namespace = 2;
+  optional string description = 3;
 };
 
 message flag_declarations {
-  required string package = 1;
+  optional string package = 1;
   repeated flag_declaration flag = 2;
 };
 
 message flag_value {
-  required string package = 1;
-  required string name = 2;
-  required flag_state state = 3;
-  required flag_permission permission = 4;
+  optional string package = 1;
+  optional string name = 2;
+  optional flag_state state = 3;
+  optional flag_permission permission = 4;
 };
 
 message flag_values {
@@ -60,18 +60,18 @@
 
 message tracepoint {
   // path to declaration or value file relative to $TOP
-  required string source = 1;
-  required flag_state state = 2;
-  required flag_permission permission = 3;
+  optional string source = 1;
+  optional flag_state state = 2;
+  optional flag_permission permission = 3;
 }
 
 message parsed_flag {
-  required string package = 1;
-  required string name = 2;
-  required string namespace = 3;
-  required string description = 4;
-  required flag_state state = 5;
-  required flag_permission permission = 6;
+  optional string package = 1;
+  optional string name = 2;
+  optional string namespace = 3;
+  optional string description = 4;
+  optional flag_state state = 5;
+  optional flag_permission permission = 6;
   repeated tracepoint trace = 7;
 }
 
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
deleted file mode 100644
index 5e7c861..0000000
--- a/tools/aconfig/src/aconfig.rs
+++ /dev/null
@@ -1,306 +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.
- */
-
-use anyhow::{anyhow, bail, Context, Error, Result};
-use protobuf::{Enum, EnumOrUnknown};
-use serde::{Deserialize, Serialize};
-
-use crate::cache::{Cache, Item, Tracepoint};
-use crate::protos::{
-    ProtoFlagDeclaration, ProtoFlagDeclarations, ProtoFlagPermission, ProtoFlagState,
-    ProtoFlagValue, ProtoFlagValues, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
-};
-
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
-pub enum FlagState {
-    Enabled,
-    Disabled,
-}
-
-impl TryFrom<EnumOrUnknown<ProtoFlagState>> for FlagState {
-    type Error = Error;
-
-    fn try_from(proto: EnumOrUnknown<ProtoFlagState>) -> Result<Self, Self::Error> {
-        match ProtoFlagState::from_i32(proto.value()) {
-            Some(ProtoFlagState::ENABLED) => Ok(FlagState::Enabled),
-            Some(ProtoFlagState::DISABLED) => Ok(FlagState::Disabled),
-            None => Err(anyhow!("unknown flag state enum value {}", proto.value())),
-        }
-    }
-}
-
-impl From<FlagState> for ProtoFlagState {
-    fn from(state: FlagState) -> Self {
-        match state {
-            FlagState::Enabled => ProtoFlagState::ENABLED,
-            FlagState::Disabled => ProtoFlagState::DISABLED,
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
-pub enum Permission {
-    ReadOnly,
-    ReadWrite,
-}
-
-impl TryFrom<EnumOrUnknown<ProtoFlagPermission>> for Permission {
-    type Error = Error;
-
-    fn try_from(proto: EnumOrUnknown<ProtoFlagPermission>) -> Result<Self, Self::Error> {
-        match ProtoFlagPermission::from_i32(proto.value()) {
-            Some(ProtoFlagPermission::READ_ONLY) => Ok(Permission::ReadOnly),
-            Some(ProtoFlagPermission::READ_WRITE) => Ok(Permission::ReadWrite),
-            None => Err(anyhow!("unknown permission enum value {}", proto.value())),
-        }
-    }
-}
-
-impl From<Permission> for ProtoFlagPermission {
-    fn from(permission: Permission) -> Self {
-        match permission {
-            Permission::ReadOnly => ProtoFlagPermission::READ_ONLY,
-            Permission::ReadWrite => ProtoFlagPermission::READ_WRITE,
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct FlagDeclaration {
-    pub name: String,
-    pub namespace: String,
-    pub description: String,
-}
-
-impl FlagDeclaration {
-    #[allow(dead_code)] // only used in unit tests
-    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclaration> {
-        let proto: ProtoFlagDeclaration = crate::protos::try_from_text_proto(text_proto)
-            .with_context(|| text_proto.to_owned())?;
-        proto.try_into()
-    }
-}
-
-impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
-    type Error = Error;
-
-    fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
-        let Some(name) = proto.name else {
-            bail!("missing 'name' field");
-        };
-        let Some(namespace) = proto.namespace else {
-            bail!("missing 'namespace' field");
-        };
-        let Some(description) = proto.description else {
-            bail!("missing 'description' field");
-        };
-        Ok(FlagDeclaration { name, namespace, description })
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct FlagDeclarations {
-    pub package: String,
-    pub flags: Vec<FlagDeclaration>,
-}
-
-impl FlagDeclarations {
-    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
-        let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
-            .with_context(|| text_proto.to_owned())?;
-        let Some(package) = proto.package else {
-            bail!("missing 'package' field");
-        };
-        let mut flags = vec![];
-        for proto_flag in proto.flag.into_iter() {
-            flags.push(proto_flag.try_into()?);
-        }
-        Ok(FlagDeclarations { package, flags })
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct FlagValue {
-    pub package: String,
-    pub name: String,
-    pub state: FlagState,
-    pub permission: Permission,
-}
-
-impl FlagValue {
-    #[allow(dead_code)] // only used in unit tests
-    pub fn try_from_text_proto(text_proto: &str) -> Result<FlagValue> {
-        let proto: ProtoFlagValue = crate::protos::try_from_text_proto(text_proto)?;
-        proto.try_into()
-    }
-
-    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<FlagValue>> {
-        let proto: ProtoFlagValues = crate::protos::try_from_text_proto(text_proto)?;
-        proto.flag_value.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
-    }
-}
-
-impl TryFrom<ProtoFlagValue> for FlagValue {
-    type Error = Error;
-
-    fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
-        let Some(package) = proto.package else {
-            bail!("missing 'package' field");
-        };
-        let Some(name) = proto.name else {
-            bail!("missing 'name' field");
-        };
-        let Some(proto_state) = proto.state else {
-            bail!("missing 'state' field");
-        };
-        let state = proto_state.try_into()?;
-        let Some(proto_permission) = proto.permission else {
-            bail!("missing 'permission' field");
-        };
-        let permission = proto_permission.try_into()?;
-        Ok(FlagValue { package, name, state, permission })
-    }
-}
-
-impl From<Cache> for ProtoParsedFlags {
-    fn from(cache: Cache) -> Self {
-        let mut proto = ProtoParsedFlags::new();
-        for item in cache.into_iter() {
-            proto.parsed_flag.push(item.into());
-        }
-        proto
-    }
-}
-
-impl From<Item> for ProtoParsedFlag {
-    fn from(item: Item) -> Self {
-        let mut proto = crate::protos::ProtoParsedFlag::new();
-        proto.set_package(item.package.to_owned());
-        proto.set_name(item.name.clone());
-        proto.set_namespace(item.namespace.clone());
-        proto.set_description(item.description.clone());
-        proto.set_state(item.state.into());
-        proto.set_permission(item.permission.into());
-        for trace in item.trace.into_iter() {
-            proto.trace.push(trace.into());
-        }
-        proto
-    }
-}
-
-impl From<Tracepoint> for ProtoTracepoint {
-    fn from(tracepoint: Tracepoint) -> Self {
-        let mut proto = ProtoTracepoint::new();
-        proto.set_source(format!("{}", tracepoint.source));
-        proto.set_state(tracepoint.state.into());
-        proto.set_permission(tracepoint.permission.into());
-        proto
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_flag_try_from_text_proto() {
-        let expected = FlagDeclaration {
-            name: "1234".to_owned(),
-            namespace: "ns".to_owned(),
-            description: "Description of the flag".to_owned(),
-        };
-
-        let s = r#"
-        name: "1234"
-        namespace: "ns"
-        description: "Description of the flag"
-        "#;
-        let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
-
-        assert_eq!(expected, actual);
-    }
-
-    #[test]
-    fn test_flag_try_from_text_proto_bad_input() {
-        let s = r#"
-        name: "a"
-        "#;
-        let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
-        assert!(format!("{:?}", error).contains("Message not initialized"));
-
-        let s = r#"
-        description: "Description of the flag"
-        "#;
-        let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
-        assert!(format!("{:?}", error).contains("Message not initialized"));
-    }
-
-    #[test]
-    fn test_package_try_from_text_proto() {
-        let expected = FlagDeclarations {
-            package: "com.example".to_owned(),
-            flags: vec![
-                FlagDeclaration {
-                    name: "a".to_owned(),
-                    namespace: "ns".to_owned(),
-                    description: "A".to_owned(),
-                },
-                FlagDeclaration {
-                    name: "b".to_owned(),
-                    namespace: "ns".to_owned(),
-                    description: "B".to_owned(),
-                },
-            ],
-        };
-
-        let s = r#"
-        package: "com.example"
-        flag {
-            name: "a"
-            namespace: "ns"
-            description: "A"
-        }
-        flag {
-            name: "b"
-            namespace: "ns"
-            description: "B"
-        }
-        "#;
-        let actual = FlagDeclarations::try_from_text_proto(s).unwrap();
-
-        assert_eq!(expected, actual);
-    }
-
-    #[test]
-    fn test_flag_declaration_try_from_text_proto_list() {
-        let expected = FlagValue {
-            package: "com.example".to_owned(),
-            name: "1234".to_owned(),
-            state: FlagState::Enabled,
-            permission: Permission::ReadOnly,
-        };
-
-        let s = r#"
-        package: "com.example"
-        name: "1234"
-        state: ENABLED
-        permission: READ_ONLY
-        "#;
-        let actual = FlagValue::try_from_text_proto(s).unwrap();
-
-        assert_eq!(expected, actual);
-    }
-}
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
deleted file mode 100644
index dd54480..0000000
--- a/tools/aconfig/src/cache.rs
+++ /dev/null
@@ -1,388 +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.
- */
-
-use anyhow::{bail, ensure, Result};
-use serde::{Deserialize, Serialize};
-use std::io::{Read, Write};
-
-use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
-use crate::codegen;
-use crate::commands::Source;
-
-const DEFAULT_FLAG_STATE: FlagState = FlagState::Disabled;
-const DEFAULT_FLAG_PERMISSION: Permission = Permission::ReadWrite;
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct Tracepoint {
-    pub source: Source,
-    pub state: FlagState,
-    pub permission: Permission,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct Item {
-    // TODO: duplicating the Cache.package as Item.package makes the internal representation
-    // closer to the proto message `parsed_flag`; hopefully this will enable us to replace the Item
-    // struct and use a newtype instead once aconfig has matured. Until then, package should
-    // really be a Cow<String>.
-    pub package: String,
-    pub name: String,
-    pub namespace: String,
-    pub description: String,
-    pub state: FlagState,
-    pub permission: Permission,
-    pub trace: Vec<Tracepoint>,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct Cache {
-    package: String,
-    items: Vec<Item>,
-}
-
-// TODO: replace this function with Iterator.is_sorted_by_key(...)) when that API becomes stable
-fn iter_is_sorted_by_key<'a, T: 'a, F, K>(iter: impl Iterator<Item = &'a T>, f: F) -> bool
-where
-    F: FnMut(&'a T) -> K,
-    K: PartialOrd<K>,
-{
-    let mut last: Option<K> = None;
-    for current in iter.map(f) {
-        if let Some(l) = last {
-            if l > current {
-                return false;
-            }
-        }
-        last = Some(current);
-    }
-    true
-}
-
-impl Cache {
-    pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
-        let cache: Cache = serde_json::from_reader(reader)?;
-        ensure!(
-            iter_is_sorted_by_key(cache.iter(), |item| &item.name),
-            "internal error: flags in cache file not sorted"
-        );
-        Ok(cache)
-    }
-
-    pub fn write_to_writer(&self, writer: impl Write) -> Result<()> {
-        ensure!(
-            iter_is_sorted_by_key(self.iter(), |item| &item.name),
-            "internal error: flags in cache file not sorted"
-        );
-        serde_json::to_writer(writer, self).map_err(|e| e.into())
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = &Item> {
-        self.items.iter()
-    }
-
-    pub fn into_iter(self) -> impl Iterator<Item = Item> {
-        self.items.into_iter()
-    }
-
-    pub fn package(&self) -> &str {
-        debug_assert!(!self.package.is_empty());
-        &self.package
-    }
-}
-
-#[derive(Debug)]
-pub struct CacheBuilder {
-    cache: Cache,
-}
-
-impl CacheBuilder {
-    pub fn new(package: String) -> Result<CacheBuilder> {
-        ensure!(codegen::is_valid_package_ident(&package), "bad package");
-        let cache = Cache { package, items: vec![] };
-        Ok(CacheBuilder { cache })
-    }
-
-    pub fn add_flag_declaration(
-        &mut self,
-        source: Source,
-        declaration: FlagDeclaration,
-    ) -> Result<&mut CacheBuilder> {
-        ensure!(codegen::is_valid_name_ident(&declaration.name), "bad flag name");
-        ensure!(codegen::is_valid_name_ident(&declaration.namespace), "bad namespace");
-        ensure!(!declaration.description.is_empty(), "empty flag description");
-        ensure!(
-            self.cache.items.iter().all(|item| item.name != declaration.name),
-            "failed to declare flag {} from {}: flag already declared",
-            declaration.name,
-            source
-        );
-        self.cache.items.push(Item {
-            package: self.cache.package.clone(),
-            name: declaration.name.clone(),
-            namespace: declaration.namespace.clone(),
-            description: declaration.description,
-            state: DEFAULT_FLAG_STATE,
-            permission: DEFAULT_FLAG_PERMISSION,
-            trace: vec![Tracepoint {
-                source,
-                state: DEFAULT_FLAG_STATE,
-                permission: DEFAULT_FLAG_PERMISSION,
-            }],
-        });
-        Ok(self)
-    }
-
-    pub fn add_flag_value(
-        &mut self,
-        source: Source,
-        value: FlagValue,
-    ) -> Result<&mut CacheBuilder> {
-        ensure!(codegen::is_valid_package_ident(&value.package), "bad flag package");
-        ensure!(codegen::is_valid_name_ident(&value.name), "bad flag name");
-        ensure!(
-            value.package == self.cache.package,
-            "failed to set values for flag {}/{} from {}: expected package {}",
-            value.package,
-            value.name,
-            source,
-            self.cache.package
-        );
-        let Some(existing_item) = self.cache.items.iter_mut().find(|item| item.name == value.name) else {
-            bail!("failed to set values for flag {}/{} from {}: flag not declared", value.package, value.name, source);
-        };
-        existing_item.state = value.state;
-        existing_item.permission = value.permission;
-        existing_item.trace.push(Tracepoint {
-            source,
-            state: value.state,
-            permission: value.permission,
-        });
-        Ok(self)
-    }
-
-    pub fn build(mut self) -> Cache {
-        self.cache.items.sort_by_cached_key(|item| item.name.clone());
-        self.cache
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_add_flag_declaration() {
-        let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
-        builder
-            .add_flag_declaration(
-                Source::File("first.txt".to_string()),
-                FlagDeclaration {
-                    name: "foo".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "desc".to_string(),
-                },
-            )
-            .unwrap();
-        let error = builder
-            .add_flag_declaration(
-                Source::File("second.txt".to_string()),
-                FlagDeclaration {
-                    name: "foo".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "desc".to_string(),
-                },
-            )
-            .unwrap_err();
-        assert_eq!(
-            &format!("{:?}", error),
-            "failed to declare flag foo from second.txt: flag already declared"
-        );
-        builder
-            .add_flag_declaration(
-                Source::File("first.txt".to_string()),
-                FlagDeclaration {
-                    name: "bar".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "desc".to_string(),
-                },
-            )
-            .unwrap();
-
-        let cache = builder.build();
-
-        // check flags are sorted by name
-        assert_eq!(
-            cache.into_iter().map(|item| item.name).collect::<Vec<_>>(),
-            vec!["bar".to_string(), "foo".to_string()]
-        );
-    }
-
-    #[test]
-    fn test_add_flag_value() {
-        let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
-        let error = builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "com.example".to_string(),
-                    name: "foo".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap_err();
-        assert_eq!(
-            &format!("{:?}", error),
-            "failed to set values for flag com.example/foo from <memory>: flag not declared"
-        );
-
-        builder
-            .add_flag_declaration(
-                Source::File("first.txt".to_string()),
-                FlagDeclaration {
-                    name: "foo".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "desc".to_string(),
-                },
-            )
-            .unwrap();
-
-        builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "com.example".to_string(),
-                    name: "foo".to_string(),
-                    state: FlagState::Disabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap();
-
-        builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "com.example".to_string(),
-                    name: "foo".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadWrite,
-                },
-            )
-            .unwrap();
-
-        // different package -> no-op
-        let error = builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "some_other_package".to_string(),
-                    name: "foo".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "failed to set values for flag some_other_package/foo from <memory>: expected package com.example");
-
-        let cache = builder.build();
-        let item = cache.iter().find(|&item| item.name == "foo").unwrap();
-        assert_eq!(FlagState::Enabled, item.state);
-        assert_eq!(Permission::ReadWrite, item.permission);
-    }
-
-    #[test]
-    fn test_reject_empty_cache_package() {
-        CacheBuilder::new("".to_string()).unwrap_err();
-    }
-
-    #[test]
-    fn test_reject_empty_flag_declaration_fields() {
-        let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
-
-        let error = builder
-            .add_flag_declaration(
-                Source::Memory,
-                FlagDeclaration {
-                    name: "".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "Description".to_string(),
-                },
-            )
-            .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "bad flag name");
-
-        let error = builder
-            .add_flag_declaration(
-                Source::Memory,
-                FlagDeclaration {
-                    name: "foo".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "".to_string(),
-                },
-            )
-            .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "empty flag description");
-    }
-
-    #[test]
-    fn test_reject_empty_flag_value_files() {
-        let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
-        builder
-            .add_flag_declaration(
-                Source::Memory,
-                FlagDeclaration {
-                    name: "foo".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "desc".to_string(),
-                },
-            )
-            .unwrap();
-
-        let error = builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "".to_string(),
-                    name: "foo".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "bad flag package");
-
-        let error = builder
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: "com.example".to_string(),
-                    name: "".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap_err();
-        assert_eq!(&format!("{:?}", error), "bad flag name");
-    }
-
-    #[test]
-    fn test_iter_is_sorted_by_key() {
-        assert!(iter_is_sorted_by_key(["a", "b", "c"].iter(), |s| s));
-        assert!(iter_is_sorted_by_key(Vec::<&str>::new().iter(), |s| s));
-        assert!(!iter_is_sorted_by_key(["a", "c", "b"].iter(), |s| s));
-    }
-}
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
index 37b058d..2944e8a 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -18,15 +18,16 @@
 use serde::Serialize;
 use tinytemplate::TinyTemplate;
 
-use crate::aconfig::{FlagState, Permission};
-use crate::cache::{Cache, Item};
 use crate::codegen;
 use crate::commands::OutputFile;
+use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_cpp_code(cache: &Cache) -> Result<OutputFile> {
-    let package = cache.package();
+pub fn generate_cpp_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<OutputFile>
+where
+    I: Iterator<Item = &'a ProtoParsedFlag>,
+{
     let class_elements: Vec<ClassElement> =
-        cache.iter().map(|item| create_class_element(package, item)).collect();
+        parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
     let readwrite = class_elements.iter().any(|item| item.readwrite);
     let header = package.replace('.', "_");
     let cpp_namespace = package.replace('.', "::");
@@ -63,162 +64,68 @@
     pub device_config_flag: String,
 }
 
-fn create_class_element(package: &str, item: &Item) -> ClassElement {
+fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
     ClassElement {
-        readwrite: item.permission == Permission::ReadWrite,
-        default_value: if item.state == FlagState::Enabled {
+        readwrite: pf.permission() == ProtoFlagPermission::READ_WRITE,
+        default_value: if pf.state() == ProtoFlagState::ENABLED {
             "true".to_string()
         } else {
             "false".to_string()
         },
-        flag_name: item.name.clone(),
-        device_config_namespace: item.namespace.to_string(),
-        device_config_flag: codegen::create_device_config_ident(package, &item.name)
-            .expect("values checked at cache creation time"),
+        flag_name: pf.name().to_string(),
+        device_config_namespace: pf.namespace().to_string(),
+        device_config_flag: codegen::create_device_config_ident(package, pf.name())
+            .expect("values checked at flag parse time"),
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
-    use crate::cache::CacheBuilder;
-    use crate::commands::Source;
 
     #[test]
-    fn test_cpp_codegen_build_time_flag_only() {
-        let package = "com.example";
-        let mut builder = CacheBuilder::new(package.to_string()).unwrap();
-        builder
-            .add_flag_declaration(
-                Source::File("aconfig_one.txt".to_string()),
-                FlagDeclaration {
-                    name: "my_flag_one".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "buildtime disable".to_string(),
-                },
-            )
-            .unwrap()
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: package.to_string(),
-                    name: "my_flag_one".to_string(),
-                    state: FlagState::Disabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap()
-            .add_flag_declaration(
-                Source::File("aconfig_two.txt".to_string()),
-                FlagDeclaration {
-                    name: "my_flag_two".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "buildtime enable".to_string(),
-                },
-            )
-            .unwrap()
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: package.to_string(),
-                    name: "my_flag_two".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadOnly,
-                },
-            )
-            .unwrap();
-        let cache = builder.build();
-        let expect_content = r#"#ifndef com_example_HEADER_H
-        #define com_example_HEADER_H
+    fn test_generate_cpp_code() {
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated =
+            generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
+        assert_eq!("aconfig/com_android_aconfig_test.h", format!("{}", generated.path.display()));
+        let expected = r#"
+#ifndef com_android_aconfig_test_HEADER_H
+#define com_android_aconfig_test_HEADER_H
+#include <server_configurable_flags/get_flags.h>
 
-        namespace com::example {
+using namespace server_configurable_flags;
 
-            static const bool my_flag_one() {
-                return false;
-            }
-
-            static const bool my_flag_two() {
-                return true;
-            }
-
-        }
-        #endif
-        "#;
-        let file = generate_cpp_code(&cache).unwrap();
-        assert_eq!("aconfig/com_example.h", file.path.to_str().unwrap());
-        assert_eq!(
-            expect_content.replace(' ', ""),
-            String::from_utf8(file.contents).unwrap().replace(' ', "")
-        );
+namespace com::android::aconfig::test {
+    static const bool disabled_ro() {
+        return false;
     }
 
-    #[test]
-    fn test_cpp_codegen_runtime_flag() {
-        let package = "com.example";
-        let mut builder = CacheBuilder::new(package.to_string()).unwrap();
-        builder
-            .add_flag_declaration(
-                Source::File("aconfig_one.txt".to_string()),
-                FlagDeclaration {
-                    name: "my_flag_one".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "buildtime disable".to_string(),
-                },
-            )
-            .unwrap()
-            .add_flag_declaration(
-                Source::File("aconfig_two.txt".to_string()),
-                FlagDeclaration {
-                    name: "my_flag_two".to_string(),
-                    namespace: "ns".to_string(),
-                    description: "runtime enable".to_string(),
-                },
-            )
-            .unwrap()
-            .add_flag_value(
-                Source::Memory,
-                FlagValue {
-                    package: package.to_string(),
-                    name: "my_flag_two".to_string(),
-                    state: FlagState::Enabled,
-                    permission: Permission::ReadWrite,
-                },
-            )
-            .unwrap();
-        let cache = builder.build();
-        let expect_content = r#"#ifndef com_example_HEADER_H
-        #define com_example_HEADER_H
+    static const bool disabled_rw() {
+        return GetServerConfigurableFlag(
+            "aconfig_test",
+            "com.android.aconfig.test.disabled_rw",
+            "false") == "true";
+    }
 
-        #include <server_configurable_flags/get_flags.h>
-        using namespace server_configurable_flags;
+    static const bool enabled_ro() {
+        return true;
+    }
 
-        namespace com::example {
-
-            static const bool my_flag_one() {
-                return GetServerConfigurableFlag(
-                    "ns",
-                    "com.example.my_flag_one",
-                    "false") == "true";
-            }
-
-            static const bool my_flag_two() {
-                return GetServerConfigurableFlag(
-                    "ns",
-                    "com.example.my_flag_two",
-                    "true") == "true";
-            }
-
-        }
-        #endif
-        "#;
-        let file = generate_cpp_code(&cache).unwrap();
-        assert_eq!("aconfig/com_example.h", file.path.to_str().unwrap());
+    static const bool enabled_rw() {
+        return GetServerConfigurableFlag(
+            "aconfig_test",
+            "com.android.aconfig.test.enabled_rw",
+            "true") == "true";
+    }
+}
+#endif
+"#;
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                expect_content,
-                &String::from_utf8(file.contents).unwrap()
+                expected,
+                &String::from_utf8(generated.contents).unwrap()
             )
         );
     }
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 54fa0dc..47516b7 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -19,20 +19,18 @@
 use std::path::PathBuf;
 use tinytemplate::TinyTemplate;
 
-use crate::aconfig::{FlagState, Permission};
-use crate::cache::{Cache, Item};
 use crate::codegen;
 use crate::commands::OutputFile;
+use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_java_code(cache: &Cache) -> Result<Vec<OutputFile>> {
-    let package = cache.package();
+pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>>
+where
+    I: Iterator<Item = &'a ProtoParsedFlag>,
+{
     let class_elements: Vec<ClassElement> =
-        cache.iter().map(|item| create_class_element(package, item)).collect();
-    let is_read_write = class_elements.iter().any(|item| item.is_read_write);
+        parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
+    let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
     let context = Context { package_name: package.to_string(), is_read_write, class_elements };
-
-    let java_files = vec!["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"];
-
     let mut template = TinyTemplate::new();
     template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
     template.add_template(
@@ -45,7 +43,7 @@
     )?;
 
     let path: PathBuf = package.split('.').collect();
-    java_files
+    ["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]
         .iter()
         .map(|file| {
             Ok(OutputFile {
@@ -73,23 +71,39 @@
     pub method_name: String,
 }
 
-fn create_class_element(package: &str, item: &Item) -> ClassElement {
-    let device_config_flag = codegen::create_device_config_ident(package, &item.name)
-        .expect("values checked at cache creation time");
+fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
+    let device_config_flag = codegen::create_device_config_ident(package, pf.name())
+        .expect("values checked at flag parse time");
     ClassElement {
-        default_value: if item.state == FlagState::Enabled {
+        default_value: if pf.state() == ProtoFlagState::ENABLED {
             "true".to_string()
         } else {
             "false".to_string()
         },
-        device_config_namespace: item.namespace.clone(),
+        device_config_namespace: pf.namespace().to_string(),
         device_config_flag,
-        flag_name_constant_suffix: item.name.to_ascii_uppercase(),
-        is_read_write: item.permission == Permission::ReadWrite,
-        method_name: item.name.clone(),
+        flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
+        is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
+        method_name: format_java_method_name(pf.name()),
     }
 }
 
+fn format_java_method_name(flag_name: &str) -> String {
+    flag_name
+        .split('_')
+        .filter(|&word| !word.is_empty())
+        .enumerate()
+        .map(|(index, word)| {
+            if index == 0 {
+                word.to_ascii_lowercase()
+            } else {
+                word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
+            }
+        })
+        .collect::<Vec<String>>()
+        .join("")
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -97,25 +111,30 @@
 
     #[test]
     fn test_generate_java_code() {
-        let cache = crate::test::create_cache();
-        let generated_files = generate_java_code(&cache).unwrap();
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated_files =
+            generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
         let expect_flags_content = r#"
         package com.android.aconfig.test;
         public final class Flags {
-            public static boolean disabled_ro() {
-                return FEATURE_FLAGS.disabled_ro();
+            public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+            public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
+            public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
+            public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+
+            public static boolean disabledRo() {
+                return FEATURE_FLAGS.disabledRo();
             }
-            public static boolean disabled_rw() {
-                return FEATURE_FLAGS.disabled_rw();
+            public static boolean disabledRw() {
+                return FEATURE_FLAGS.disabledRw();
             }
-            public static boolean enabled_ro() {
-                return FEATURE_FLAGS.enabled_ro();
+            public static boolean enabledRo() {
+                return FEATURE_FLAGS.enabledRo();
             }
-            public static boolean enabled_rw() {
-                return FEATURE_FLAGS.enabled_rw();
+            public static boolean enabledRw() {
+                return FEATURE_FLAGS.enabledRw();
             }
             private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
-
         }
         "#;
         let expected_featureflagsimpl_content = r#"
@@ -123,11 +142,11 @@
         import android.provider.DeviceConfig;
         public final class FeatureFlagsImpl implements FeatureFlags {
             @Override
-            public boolean disabled_ro() {
+            public boolean disabledRo() {
                 return false;
             }
             @Override
-            public boolean disabled_rw() {
+            public boolean disabledRw() {
                 return DeviceConfig.getBoolean(
                     "aconfig_test",
                     "com.android.aconfig.test.disabled_rw",
@@ -135,11 +154,11 @@
                 );
             }
             @Override
-            public boolean enabled_ro() {
+            public boolean enabledRo() {
                 return true;
             }
             @Override
-            public boolean enabled_rw() {
+            public boolean enabledRw() {
                 return DeviceConfig.getBoolean(
                     "aconfig_test",
                     "com.android.aconfig.test.enabled_rw",
@@ -151,10 +170,10 @@
         let expected_featureflags_content = r#"
         package com.android.aconfig.test;
         public interface FeatureFlags {
-            boolean disabled_ro();
-            boolean disabled_rw();
-            boolean enabled_ro();
-            boolean enabled_rw();
+            boolean disabledRo();
+            boolean disabledRw();
+            boolean enabledRo();
+            boolean enabledRw();
         }
         "#;
         let mut file_set = HashMap::from([
@@ -180,4 +199,12 @@
 
         assert!(file_set.is_empty());
     }
+
+    #[test]
+    fn test_format_java_method_name() {
+        let input = "____some_snake___name____";
+        let expected = "someSnakeName";
+        let formatted_name = format_java_method_name(input);
+        assert_eq!(expected, formatted_name);
+    }
 }
diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs
index 98caeae..f931418 100644
--- a/tools/aconfig/src/codegen_rust.rs
+++ b/tools/aconfig/src/codegen_rust.rs
@@ -18,18 +18,19 @@
 use serde::Serialize;
 use tinytemplate::TinyTemplate;
 
-use crate::aconfig::{FlagState, Permission};
-use crate::cache::{Cache, Item};
 use crate::codegen;
 use crate::commands::OutputFile;
+use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_rust_code(cache: &Cache) -> Result<OutputFile> {
-    let package = cache.package();
-    let parsed_flags: Vec<TemplateParsedFlag> =
-        cache.iter().map(|item| TemplateParsedFlag::new(package, item)).collect();
+pub fn generate_rust_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<OutputFile>
+where
+    I: Iterator<Item = &'a ProtoParsedFlag>,
+{
+    let template_flags: Vec<TemplateParsedFlag> =
+        parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, pf)).collect();
     let context = TemplateContext {
         package: package.to_string(),
-        parsed_flags,
+        template_flags,
         modules: package.split('.').map(|s| s.to_string()).collect::<Vec<_>>(),
     };
     let mut template = TinyTemplate::new();
@@ -42,7 +43,7 @@
 #[derive(Serialize)]
 struct TemplateContext {
     pub package: String,
-    pub parsed_flags: Vec<TemplateParsedFlag>,
+    pub template_flags: Vec<TemplateParsedFlag>,
     pub modules: Vec<String>,
 }
 
@@ -61,17 +62,17 @@
 
 impl TemplateParsedFlag {
     #[allow(clippy::nonminimal_bool)]
-    fn new(package: &str, item: &Item) -> Self {
+    fn new(package: &str, pf: &ProtoParsedFlag) -> Self {
         let template = TemplateParsedFlag {
-            name: item.name.clone(),
-            device_config_namespace: item.namespace.to_string(),
-            device_config_flag: codegen::create_device_config_ident(package, &item.name)
-                .expect("values checked at cache creation time"),
-            is_read_only_enabled: item.permission == Permission::ReadOnly
-                && item.state == FlagState::Enabled,
-            is_read_only_disabled: item.permission == Permission::ReadOnly
-                && item.state == FlagState::Disabled,
-            is_read_write: item.permission == Permission::ReadWrite,
+            name: pf.name().to_string(),
+            device_config_namespace: pf.namespace().to_string(),
+            device_config_flag: codegen::create_device_config_ident(package, pf.name())
+                .expect("values checked at flag parse time"),
+            is_read_only_enabled: pf.permission() == ProtoFlagPermission::READ_ONLY
+                && pf.state() == ProtoFlagState::ENABLED,
+            is_read_only_disabled: pf.permission() == ProtoFlagPermission::READ_ONLY
+                && pf.state() == ProtoFlagState::DISABLED,
+            is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
         };
         #[rustfmt::skip]
         debug_assert!(
@@ -93,8 +94,9 @@
 
     #[test]
     fn test_generate_rust_code() {
-        let cache = crate::test::create_cache();
-        let generated = generate_rust_code(&cache).unwrap();
+        let parsed_flags = crate::test::parse_test_flags();
+        let generated =
+            generate_rust_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
         assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
         let expected = r#"
 pub mod com {
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index eb860b0..f295697 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -14,105 +14,160 @@
  * limitations under the License.
  */
 
-use anyhow::{ensure, Context, Result};
+use anyhow::{bail, ensure, Context, Result};
 use clap::ValueEnum;
 use protobuf::Message;
-use serde::{Deserialize, Serialize};
-use std::fmt;
 use std::io::Read;
 use std::path::PathBuf;
 
-use crate::aconfig::{FlagDeclarations, FlagState, FlagValue, Permission};
-use crate::cache::{Cache, CacheBuilder, Item};
 use crate::codegen_cpp::generate_cpp_code;
 use crate::codegen_java::generate_java_code;
 use crate::codegen_rust::generate_rust_code;
-use crate::protos::ProtoParsedFlags;
-
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub enum Source {
-    #[allow(dead_code)] // only used in unit tests
-    Memory,
-    File(String),
-}
-
-impl fmt::Display for Source {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Self::Memory => write!(f, "<memory>"),
-            Self::File(path) => write!(f, "{}", path),
-        }
-    }
-}
+use crate::protos::{
+    ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
+};
 
 pub struct Input {
-    pub source: Source,
+    pub source: String,
     pub reader: Box<dyn Read>,
 }
 
+impl Input {
+    fn try_parse_flags(&mut self) -> Result<ProtoParsedFlags> {
+        let mut buffer = Vec::new();
+        self.reader.read_to_end(&mut buffer)?;
+        crate::protos::parsed_flags::try_from_binary_proto(&buffer)
+    }
+}
+
 pub struct OutputFile {
     pub path: PathBuf, // relative to some root directory only main knows about
     pub contents: Vec<u8>,
 }
 
-pub fn create_cache(package: &str, declarations: Vec<Input>, values: Vec<Input>) -> Result<Cache> {
-    let mut builder = CacheBuilder::new(package.to_owned())?;
+const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED;
+const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE;
+
+pub fn parse_flags(package: &str, declarations: Vec<Input>, values: Vec<Input>) -> Result<Vec<u8>> {
+    let mut parsed_flags = ProtoParsedFlags::new();
 
     for mut input in declarations {
         let mut contents = String::new();
         input.reader.read_to_string(&mut contents)?;
-        let dec_list = FlagDeclarations::try_from_text_proto(&contents)
+
+        let flag_declarations = crate::protos::flag_declarations::try_from_text_proto(&contents)
             .with_context(|| format!("Failed to parse {}", input.source))?;
         ensure!(
-            package == dec_list.package,
+            package == flag_declarations.package(),
             "Failed to parse {}: expected package {}, got {}",
             input.source,
             package,
-            dec_list.package
+            flag_declarations.package()
         );
-        for d in dec_list.flags.into_iter() {
-            builder.add_flag_declaration(input.source.clone(), d)?;
+        for mut flag_declaration in flag_declarations.flag.into_iter() {
+            crate::protos::flag_declaration::verify_fields(&flag_declaration)
+                .with_context(|| format!("Failed to parse {}", input.source))?;
+
+            // create ParsedFlag using FlagDeclaration and default values
+            let mut parsed_flag = ProtoParsedFlag::new();
+            parsed_flag.set_package(package.to_string());
+            parsed_flag.set_name(flag_declaration.take_name());
+            parsed_flag.set_namespace(flag_declaration.take_namespace());
+            parsed_flag.set_description(flag_declaration.take_description());
+            parsed_flag.set_state(DEFAULT_FLAG_STATE);
+            parsed_flag.set_permission(DEFAULT_FLAG_PERMISSION);
+            let mut tracepoint = ProtoTracepoint::new();
+            tracepoint.set_source(input.source.clone());
+            tracepoint.set_state(DEFAULT_FLAG_STATE);
+            tracepoint.set_permission(DEFAULT_FLAG_PERMISSION);
+            parsed_flag.trace.push(tracepoint);
+
+            // verify ParsedFlag looks reasonable
+            crate::protos::parsed_flag::verify_fields(&parsed_flag)?;
+
+            // verify ParsedFlag can be added
+            ensure!(
+                parsed_flags.parsed_flag.iter().all(|other| other.name() != parsed_flag.name()),
+                "failed to declare flag {} from {}: flag already declared",
+                parsed_flag.name(),
+                input.source
+            );
+
+            // add ParsedFlag to ParsedFlags
+            parsed_flags.parsed_flag.push(parsed_flag);
         }
     }
 
     for mut input in values {
         let mut contents = String::new();
         input.reader.read_to_string(&mut contents)?;
-        let values_list = FlagValue::try_from_text_proto_list(&contents)
+        let flag_values = crate::protos::flag_values::try_from_text_proto(&contents)
             .with_context(|| format!("Failed to parse {}", input.source))?;
-        for v in values_list {
-            // TODO: warn about flag values that do not take effect?
-            let _ = builder.add_flag_value(input.source.clone(), v);
+        for flag_value in flag_values.flag_value.into_iter() {
+            crate::protos::flag_value::verify_fields(&flag_value)
+                .with_context(|| format!("Failed to parse {}", input.source))?;
+
+            let Some(parsed_flag) = parsed_flags.parsed_flag.iter_mut().find(|pf| pf.package() == flag_value.package() && pf.name() == flag_value.name()) else {
+                // (silently) skip unknown flags
+                continue;
+            };
+
+            parsed_flag.set_state(flag_value.state());
+            parsed_flag.set_permission(flag_value.permission());
+            let mut tracepoint = ProtoTracepoint::new();
+            tracepoint.set_source(input.source.clone());
+            tracepoint.set_state(flag_value.state());
+            tracepoint.set_permission(flag_value.permission());
+            parsed_flag.trace.push(tracepoint);
         }
     }
 
-    Ok(builder.build())
-}
-
-pub fn create_java_lib(cache: Cache) -> Result<Vec<OutputFile>> {
-    generate_java_code(&cache)
-}
-
-pub fn create_cpp_lib(cache: Cache) -> Result<OutputFile> {
-    generate_cpp_code(&cache)
-}
-
-pub fn create_rust_lib(cache: Cache) -> Result<OutputFile> {
-    generate_rust_code(&cache)
-}
-
-pub fn create_device_config_defaults(caches: Vec<Cache>) -> Result<Vec<u8>> {
+    crate::protos::parsed_flags::verify_fields(&parsed_flags)?;
     let mut output = Vec::new();
-    for item in sort_and_iter_items(caches).filter(|item| item.permission == Permission::ReadWrite)
+    parsed_flags.write_to_vec(&mut output)?;
+    Ok(output)
+}
+
+pub fn create_java_lib(mut input: Input) -> Result<Vec<OutputFile>> {
+    let parsed_flags = input.try_parse_flags()?;
+    let Some(package) = find_unique_package(&parsed_flags) else {
+        bail!("no parsed flags, or the parsed flags use different packages");
+    };
+    generate_java_code(package, parsed_flags.parsed_flag.iter())
+}
+
+pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> {
+    let parsed_flags = input.try_parse_flags()?;
+    let Some(package) = find_unique_package(&parsed_flags) else {
+        bail!("no parsed flags, or the parsed flags use different packages");
+    };
+    generate_cpp_code(package, parsed_flags.parsed_flag.iter())
+}
+
+pub fn create_rust_lib(mut input: Input) -> Result<OutputFile> {
+    let parsed_flags = input.try_parse_flags()?;
+    let Some(package) = find_unique_package(&parsed_flags) else {
+        bail!("no parsed flags, or the parsed flags use different packages");
+    };
+    generate_rust_code(package, parsed_flags.parsed_flag.iter())
+}
+
+pub fn create_device_config_defaults(mut input: Input) -> Result<Vec<u8>> {
+    let parsed_flags = input.try_parse_flags()?;
+    let mut output = Vec::new();
+    for parsed_flag in parsed_flags
+        .parsed_flag
+        .into_iter()
+        .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE)
     {
         let line = format!(
             "{}:{}.{}={}\n",
-            item.namespace,
-            item.package,
-            item.name,
-            match item.state {
-                FlagState::Enabled => "enabled",
-                FlagState::Disabled => "disabled",
+            parsed_flag.namespace(),
+            parsed_flag.package(),
+            parsed_flag.name(),
+            match parsed_flag.state() {
+                ProtoFlagState::ENABLED => "enabled",
+                ProtoFlagState::DISABLED => "disabled",
             }
         );
         output.extend_from_slice(line.as_bytes());
@@ -120,17 +175,21 @@
     Ok(output)
 }
 
-pub fn create_device_config_sysprops(caches: Vec<Cache>) -> Result<Vec<u8>> {
+pub fn create_device_config_sysprops(mut input: Input) -> Result<Vec<u8>> {
+    let parsed_flags = input.try_parse_flags()?;
     let mut output = Vec::new();
-    for item in sort_and_iter_items(caches).filter(|item| item.permission == Permission::ReadWrite)
+    for parsed_flag in parsed_flags
+        .parsed_flag
+        .into_iter()
+        .filter(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE)
     {
         let line = format!(
             "persist.device_config.{}.{}={}\n",
-            item.package,
-            item.name,
-            match item.state {
-                FlagState::Enabled => "true",
-                FlagState::Disabled => "false",
+            parsed_flag.package(),
+            parsed_flag.name(),
+            match parsed_flag.state() {
+                ProtoFlagState::ENABLED => "true",
+                ProtoFlagState::DISABLED => "false",
             }
         );
         output.extend_from_slice(line.as_bytes());
@@ -145,177 +204,118 @@
     Protobuf,
 }
 
-pub fn dump_cache(caches: Vec<Cache>, format: DumpFormat) -> Result<Vec<u8>> {
+pub fn dump_parsed_flags(mut input: Vec<Input>, format: DumpFormat) -> Result<Vec<u8>> {
+    let individually_parsed_flags: Result<Vec<ProtoParsedFlags>> =
+        input.iter_mut().map(|i| i.try_parse_flags()).collect();
+    let parsed_flags: ProtoParsedFlags =
+        crate::protos::parsed_flags::merge(individually_parsed_flags?)?;
+
     let mut output = Vec::new();
     match format {
         DumpFormat::Text => {
-            for item in sort_and_iter_items(caches) {
+            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
                 let line = format!(
                     "{}/{}: {:?} {:?}\n",
-                    item.package, item.name, item.state, item.permission
+                    parsed_flag.package(),
+                    parsed_flag.name(),
+                    parsed_flag.state(),
+                    parsed_flag.permission()
                 );
                 output.extend_from_slice(line.as_bytes());
             }
         }
         DumpFormat::Debug => {
-            for item in sort_and_iter_items(caches) {
-                let line = format!("{:#?}\n", item);
+            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
+                let line = format!("{:#?}\n", parsed_flag);
                 output.extend_from_slice(line.as_bytes());
             }
         }
         DumpFormat::Protobuf => {
-            for cache in sort_and_iter_caches(caches) {
-                let parsed_flags: ProtoParsedFlags = cache.into();
-                parsed_flags.write_to_vec(&mut output)?;
-            }
+            parsed_flags.write_to_vec(&mut output)?;
         }
     }
     Ok(output)
 }
 
-fn sort_and_iter_items(caches: Vec<Cache>) -> impl Iterator<Item = Item> {
-    sort_and_iter_caches(caches).flat_map(|cache| cache.into_iter())
-}
-
-fn sort_and_iter_caches(mut caches: Vec<Cache>) -> impl Iterator<Item = Cache> {
-    caches.sort_by_cached_key(|cache| cache.package().to_string());
-    caches.into_iter()
+fn find_unique_package(parsed_flags: &ProtoParsedFlags) -> Option<&str> {
+    let Some(package) = parsed_flags.parsed_flag.first().map(|pf| pf.package()) else {
+        return None;
+    };
+    if parsed_flags.parsed_flag.iter().any(|pf| pf.package() != package) {
+        return None;
+    }
+    Some(package)
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::aconfig::{FlagState, Permission};
-
-    fn create_test_cache_com_example() -> Cache {
-        let s = r#"
-        package: "com.example"
-        flag {
-            name: "a"
-            namespace: "ns"
-            description: "Description of a"
-        }
-        flag {
-            name: "b"
-            namespace: "ns"
-            description: "Description of b"
-        }
-        "#;
-        let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
-        let o = r#"
-        flag_value {
-            package: "com.example"
-            name: "a"
-            state: DISABLED
-            permission: READ_ONLY
-        }
-        "#;
-        let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
-        create_cache("com.example", declarations, values).unwrap()
-    }
-
-    fn create_test_cache_com_other() -> Cache {
-        let s = r#"
-        package: "com.other"
-        flag {
-            name: "c"
-            namespace: "ns"
-            description: "Description of c"
-        }
-        "#;
-        let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
-        let o = r#"
-        flag_value {
-            package: "com.other"
-            name: "c"
-            state: DISABLED
-            permission: READ_ONLY
-        }
-        "#;
-        let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
-        create_cache("com.other", declarations, values).unwrap()
-    }
 
     #[test]
-    fn test_create_cache() {
-        let caches = create_test_cache_com_example(); // calls create_cache
-        let item = caches.iter().find(|&item| item.name == "a").unwrap();
-        assert_eq!(FlagState::Disabled, item.state);
-        assert_eq!(Permission::ReadOnly, item.permission);
+    fn test_parse_flags() {
+        let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags
+        crate::protos::parsed_flags::verify_fields(&parsed_flags).unwrap();
+
+        let enabled_ro =
+            parsed_flags.parsed_flag.iter().find(|pf| pf.name() == "enabled_ro").unwrap();
+        assert!(crate::protos::parsed_flag::verify_fields(enabled_ro).is_ok());
+        assert_eq!("com.android.aconfig.test", enabled_ro.package());
+        assert_eq!("enabled_ro", enabled_ro.name());
+        assert_eq!("This flag is ENABLED + READ_ONLY", enabled_ro.description());
+        assert_eq!(ProtoFlagState::ENABLED, enabled_ro.state());
+        assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.permission());
+        assert_eq!(3, enabled_ro.trace.len());
+        assert_eq!("tests/test.aconfig", enabled_ro.trace[0].source());
+        assert_eq!(ProtoFlagState::DISABLED, enabled_ro.trace[0].state());
+        assert_eq!(ProtoFlagPermission::READ_WRITE, enabled_ro.trace[0].permission());
+        assert_eq!("tests/first.values", enabled_ro.trace[1].source());
+        assert_eq!(ProtoFlagState::DISABLED, enabled_ro.trace[1].state());
+        assert_eq!(ProtoFlagPermission::READ_WRITE, enabled_ro.trace[1].permission());
+        assert_eq!("tests/second.values", enabled_ro.trace[2].source());
+        assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state());
+        assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission());
+
+        assert_eq!(4, parsed_flags.parsed_flag.len());
+        for pf in parsed_flags.parsed_flag.iter() {
+            let first = pf.trace.first().unwrap();
+            assert_eq!(DEFAULT_FLAG_STATE, first.state());
+            assert_eq!(DEFAULT_FLAG_PERMISSION, first.permission());
+
+            let last = pf.trace.last().unwrap();
+            assert_eq!(pf.state(), last.state());
+            assert_eq!(pf.permission(), last.permission());
+        }
     }
 
     #[test]
     fn test_create_device_config_defaults() {
-        let caches = vec![crate::test::create_cache()];
-        let bytes = create_device_config_defaults(caches).unwrap();
+        let input = parse_test_flags_as_input();
+        let bytes = create_device_config_defaults(input).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
         assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text);
     }
 
     #[test]
     fn test_create_device_config_sysprops() {
-        let caches = vec![crate::test::create_cache()];
-        let bytes = create_device_config_sysprops(caches).unwrap();
+        let input = parse_test_flags_as_input();
+        let bytes = create_device_config_sysprops(input).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
         assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text);
     }
 
     #[test]
     fn test_dump_text_format() {
-        let caches = vec![create_test_cache_com_example()];
-        let bytes = dump_cache(caches, DumpFormat::Text).unwrap();
+        let input = parse_test_flags_as_input();
+        let bytes = dump_parsed_flags(vec![input], DumpFormat::Text).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
-        assert!(text.contains("a: Disabled"));
+        assert!(text.contains("com.android.aconfig.test/disabled_ro: DISABLED READ_ONLY"));
     }
 
-    #[test]
-    fn test_dump_protobuf_format() {
-        use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoTracepoint};
-        use protobuf::Message;
-
-        let caches = vec![create_test_cache_com_example()];
-        let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
-        let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
-
-        assert_eq!(
-            vec!["a".to_string(), "b".to_string()],
-            actual.parsed_flag.iter().map(|item| item.name.clone().unwrap()).collect::<Vec<_>>()
-        );
-
-        let item =
-            actual.parsed_flag.iter().find(|item| item.name == Some("b".to_string())).unwrap();
-        assert_eq!(item.package(), "com.example");
-        assert_eq!(item.name(), "b");
-        assert_eq!(item.description(), "Description of b");
-        assert_eq!(item.state(), ProtoFlagState::DISABLED);
-        assert_eq!(item.permission(), ProtoFlagPermission::READ_WRITE);
-        let mut tp = ProtoTracepoint::new();
-        tp.set_source("<memory>".to_string());
-        tp.set_state(ProtoFlagState::DISABLED);
-        tp.set_permission(ProtoFlagPermission::READ_WRITE);
-        assert_eq!(item.trace, vec![tp]);
-    }
-
-    #[test]
-    fn test_dump_multiple_caches() {
-        let caches = vec![create_test_cache_com_example(), create_test_cache_com_other()];
-        let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
-        let dump = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
-        assert_eq!(
-            dump.parsed_flag
-                .iter()
-                .map(|parsed_flag| format!("{}/{}", parsed_flag.package(), parsed_flag.name()))
-                .collect::<Vec<_>>(),
-            vec![
-                "com.example/a".to_string(),
-                "com.example/b".to_string(),
-                "com.other/c".to_string()
-            ]
-        );
-
-        let caches = vec![create_test_cache_com_other(), create_test_cache_com_example()];
-        let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
-        let dump_reversed_input = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
-        assert_eq!(dump, dump_reversed_input);
+    fn parse_test_flags_as_input() -> Input {
+        let parsed_flags = crate::test::parse_test_flags();
+        let binary_proto = parsed_flags.write_to_bytes().unwrap();
+        let cursor = std::io::Cursor::new(binary_proto);
+        let reader = Box::new(cursor);
+        Input { source: "test.data".to_string(), reader }
     }
 }
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 3a9a573..e6a325d 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -16,7 +16,7 @@
 
 //! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
 
-use anyhow::{anyhow, ensure, Result};
+use anyhow::{anyhow, bail, ensure, Result};
 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
 use core::any::Any;
 use std::fs;
@@ -24,8 +24,6 @@
 use std::io::Write;
 use std::path::{Path, PathBuf};
 
-mod aconfig;
-mod cache;
 mod codegen;
 mod codegen_cpp;
 mod codegen_java;
@@ -36,8 +34,7 @@
 #[cfg(test)]
 mod test;
 
-use crate::cache::Cache;
-use commands::{DumpFormat, Input, OutputFile, Source};
+use commands::{DumpFormat, Input, OutputFile};
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -100,11 +97,19 @@
     let mut opened_files = vec![];
     for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
         let file = Box::new(fs::File::open(path)?);
-        opened_files.push(Input { source: Source::File(path.to_string()), reader: file });
+        opened_files.push(Input { source: path.to_string(), reader: file });
     }
     Ok(opened_files)
 }
 
+fn open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input> {
+    let Some(path) = matches.get_one::<String>(arg_name) else {
+        bail!("missing argument {}", arg_name);
+    };
+    let file = Box::new(fs::File::open(path)?);
+    Ok(Input { source: path.to_string(), reader: file })
+}
+
 fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
     ensure!(
         root.is_dir(),
@@ -137,68 +142,46 @@
             let package = get_required_arg::<String>(sub_matches, "package")?;
             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(package, declarations, values)?;
+            let output = commands::parse_flags(package, declarations, values)?;
             let path = get_required_arg::<String>(sub_matches, "cache")?;
-            let file = fs::File::create(path)?;
-            cache.write_to_writer(file)?;
+            write_output_to_file_or_stdout(path, &output)?;
         }
         Some(("create-java-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 cache = open_single_file(sub_matches, "cache")?;
             let generated_files = commands::create_java_lib(cache)?;
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
             generated_files
                 .iter()
                 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, 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 cache = open_single_file(sub_matches, "cache")?;
             let generated_file = commands::create_cpp_lib(cache)?;
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
             write_output_file_realtive_to_dir(&dir, &generated_file)?;
         }
         Some(("create-rust-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 cache = open_single_file(sub_matches, "cache")?;
             let generated_file = commands::create_rust_lib(cache)?;
+            let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
             write_output_file_realtive_to_dir(&dir, &generated_file)?;
         }
         Some(("create-device-config-defaults", sub_matches)) => {
-            let mut caches = Vec::new();
-            for path in sub_matches.get_many::<String>("cache").unwrap_or_default() {
-                let file = fs::File::open(path)?;
-                let cache = Cache::read_from_reader(file)?;
-                caches.push(cache);
-            }
-            let output = commands::create_device_config_defaults(caches)?;
+            let cache = open_single_file(sub_matches, "cache")?;
+            let output = commands::create_device_config_defaults(cache)?;
             let path = get_required_arg::<String>(sub_matches, "out")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
         Some(("create-device-config-sysprops", sub_matches)) => {
-            let mut caches = Vec::new();
-            for path in sub_matches.get_many::<String>("cache").unwrap_or_default() {
-                let file = fs::File::open(path)?;
-                let cache = Cache::read_from_reader(file)?;
-                caches.push(cache);
-            }
-            let output = commands::create_device_config_sysprops(caches)?;
+            let cache = open_single_file(sub_matches, "cache")?;
+            let output = commands::create_device_config_sysprops(cache)?;
             let path = get_required_arg::<String>(sub_matches, "out")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
         Some(("dump", sub_matches)) => {
-            let mut caches = Vec::new();
-            for path in sub_matches.get_many::<String>("cache").unwrap_or_default() {
-                let file = fs::File::open(path)?;
-                let cache = Cache::read_from_reader(file)?;
-                caches.push(cache);
-            }
+            let input = open_zero_or_more_files(sub_matches, "cache")?;
             let format = get_required_arg::<DumpFormat>(sub_matches, "format")?;
-            let output = commands::dump_cache(caches, *format)?;
+            let output = commands::dump_parsed_flags(input, *format)?;
             let path = get_required_arg::<String>(sub_matches, "out")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index cb75692..beebd93 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -28,70 +28,684 @@
 
 // ---- When building with the Android tool-chain ----
 #[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_state as ProtoFlagState;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Parsed_flag as ProtoParsedFlag;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Tracepoint as ProtoTracepoint;
+mod auto_generated {
+    pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
+    pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
+    pub use aconfig_protos::aconfig::Flag_state as ProtoFlagState;
+    pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
+    pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
+    pub use aconfig_protos::aconfig::Parsed_flag as ProtoParsedFlag;
+    pub use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
+    pub use aconfig_protos::aconfig::Tracepoint as ProtoTracepoint;
+}
 
 // ---- When building with cargo ----
 #[cfg(feature = "cargo")]
-include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_value as ProtoFlagValue;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_values as ProtoFlagValues;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_permission as ProtoFlagPermission;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_state as ProtoFlagState;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Parsed_flags as ProtoParsedFlags;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Parsed_flag as ProtoParsedFlag;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Tracepoint as ProtoTracepoint;
+mod auto_generated {
+    // include! statements should be avoided (because they import file contents verbatim), but
+    // because this is only used during local development, and only if using cargo instead of the
+    // Android tool-chain, we allow it
+    include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
+    pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
+    pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
+    pub use aconfig::Flag_permission as ProtoFlagPermission;
+    pub use aconfig::Flag_state as ProtoFlagState;
+    pub use aconfig::Flag_value as ProtoFlagValue;
+    pub use aconfig::Flag_values as ProtoFlagValues;
+    pub use aconfig::Parsed_flag as ProtoParsedFlag;
+    pub use aconfig::Parsed_flags as ProtoParsedFlags;
+    pub use aconfig::Tracepoint as ProtoTracepoint;
+}
 
 // ---- Common for both the Android tool-chain and cargo ----
+pub use auto_generated::*;
+
 use anyhow::Result;
 
-pub fn try_from_text_proto<T>(s: &str) -> Result<T>
+fn try_from_text_proto<T>(s: &str) -> Result<T>
 where
     T: protobuf::MessageFull,
 {
     // warning: parse_from_str does not check if required fields are set
     protobuf::text_format::parse_from_str(s).map_err(|e| e.into())
 }
+
+pub mod flag_declaration {
+    use super::*;
+    use crate::codegen;
+    use anyhow::ensure;
+
+    pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> {
+        ensure!(pdf.has_name(), "bad flag declaration: missing name");
+        ensure!(pdf.has_namespace(), "bad flag declaration: missing namespace");
+        ensure!(pdf.has_description(), "bad flag declaration: missing description");
+
+        ensure!(codegen::is_valid_name_ident(pdf.name()), "bad flag declaration: bad name");
+        ensure!(codegen::is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name");
+        ensure!(!pdf.description().is_empty(), "bad flag declaration: empty description");
+
+        Ok(())
+    }
+}
+
+pub mod flag_declarations {
+    use super::*;
+    use crate::codegen;
+    use anyhow::ensure;
+
+    pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagDeclarations> {
+        let pdf: ProtoFlagDeclarations = super::try_from_text_proto(s)?;
+        verify_fields(&pdf)?;
+        Ok(pdf)
+    }
+
+    pub fn verify_fields(pdf: &ProtoFlagDeclarations) -> Result<()> {
+        ensure!(pdf.has_package(), "bad flag declarations: missing package");
+
+        ensure!(
+            codegen::is_valid_package_ident(pdf.package()),
+            "bad flag declarations: bad package"
+        );
+        for flag_declaration in pdf.flag.iter() {
+            super::flag_declaration::verify_fields(flag_declaration)?;
+        }
+
+        Ok(())
+    }
+}
+
+pub mod flag_value {
+    use super::*;
+    use crate::codegen;
+    use anyhow::ensure;
+
+    pub fn verify_fields(fv: &ProtoFlagValue) -> Result<()> {
+        ensure!(fv.has_package(), "bad flag value: missing package");
+        ensure!(fv.has_name(), "bad flag value: missing name");
+        ensure!(fv.has_state(), "bad flag value: missing state");
+        ensure!(fv.has_permission(), "bad flag value: missing permission");
+
+        ensure!(codegen::is_valid_package_ident(fv.package()), "bad flag value: bad package");
+        ensure!(codegen::is_valid_name_ident(fv.name()), "bad flag value: bad name");
+
+        Ok(())
+    }
+}
+
+pub mod flag_values {
+    use super::*;
+
+    pub fn try_from_text_proto(s: &str) -> Result<ProtoFlagValues> {
+        let pfv: ProtoFlagValues = super::try_from_text_proto(s)?;
+        verify_fields(&pfv)?;
+        Ok(pfv)
+    }
+
+    pub fn verify_fields(pfv: &ProtoFlagValues) -> Result<()> {
+        for flag_value in pfv.flag_value.iter() {
+            super::flag_value::verify_fields(flag_value)?;
+        }
+        Ok(())
+    }
+}
+
+pub mod tracepoint {
+    use super::*;
+    use anyhow::ensure;
+
+    pub fn verify_fields(tp: &ProtoTracepoint) -> Result<()> {
+        ensure!(tp.has_source(), "bad tracepoint: missing source");
+        ensure!(tp.has_state(), "bad tracepoint: missing state");
+        ensure!(tp.has_permission(), "bad tracepoint: missing permission");
+
+        ensure!(!tp.source().is_empty(), "bad tracepoint: empty source");
+
+        Ok(())
+    }
+}
+
+pub mod parsed_flag {
+    use super::*;
+    use crate::codegen;
+    use anyhow::ensure;
+
+    pub fn verify_fields(pf: &ProtoParsedFlag) -> Result<()> {
+        ensure!(pf.has_package(), "bad parsed flag: missing package");
+        ensure!(pf.has_name(), "bad parsed flag: missing name");
+        ensure!(pf.has_namespace(), "bad parsed flag: missing namespace");
+        ensure!(pf.has_description(), "bad parsed flag: missing description");
+        ensure!(pf.has_state(), "bad parsed flag: missing state");
+        ensure!(pf.has_permission(), "bad parsed flag: missing permission");
+
+        ensure!(codegen::is_valid_package_ident(pf.package()), "bad parsed flag: bad package");
+        ensure!(codegen::is_valid_name_ident(pf.name()), "bad parsed flag: bad name");
+        ensure!(codegen::is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace");
+        ensure!(!pf.description().is_empty(), "bad parsed flag: empty description");
+        ensure!(!pf.trace.is_empty(), "bad parsed flag: empty trace");
+        for tp in pf.trace.iter() {
+            super::tracepoint::verify_fields(tp)?;
+        }
+
+        Ok(())
+    }
+}
+
+pub mod parsed_flags {
+    use super::*;
+    use anyhow::bail;
+    use std::cmp::Ordering;
+
+    pub fn try_from_binary_proto(bytes: &[u8]) -> Result<ProtoParsedFlags> {
+        let message: ProtoParsedFlags = protobuf::Message::parse_from_bytes(bytes)?;
+        verify_fields(&message)?;
+        Ok(message)
+    }
+
+    pub fn verify_fields(pf: &ProtoParsedFlags) -> Result<()> {
+        let mut previous: Option<&ProtoParsedFlag> = None;
+        for parsed_flag in pf.parsed_flag.iter() {
+            if let Some(prev) = previous {
+                let a = create_sorting_key(prev);
+                let b = create_sorting_key(parsed_flag);
+                match a.cmp(&b) {
+                    Ordering::Less => {}
+                    Ordering::Equal => bail!("bad parsed flags: duplicate flag {}", a),
+                    Ordering::Greater => {
+                        bail!("bad parsed flags: not sorted: {} comes before {}", a, b)
+                    }
+                }
+            }
+            super::parsed_flag::verify_fields(parsed_flag)?;
+            previous = Some(parsed_flag);
+        }
+        Ok(())
+    }
+
+    pub fn merge(parsed_flags: Vec<ProtoParsedFlags>) -> Result<ProtoParsedFlags> {
+        let mut merged = ProtoParsedFlags::new();
+        for mut pfs in parsed_flags.into_iter() {
+            merged.parsed_flag.append(&mut pfs.parsed_flag);
+        }
+        merged.parsed_flag.sort_by_cached_key(create_sorting_key);
+        verify_fields(&merged)?;
+        Ok(merged)
+    }
+
+    fn create_sorting_key(pf: &ProtoParsedFlag) -> String {
+        format!("{}.{}", pf.package(), pf.name())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_flag_declarations_try_from_text_proto() {
+        // valid input
+        let flag_declarations = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap();
+        assert_eq!(flag_declarations.package(), "com.foo.bar");
+        let first = flag_declarations.flag.iter().find(|pf| pf.name() == "first").unwrap();
+        assert_eq!(first.name(), "first");
+        assert_eq!(first.namespace(), "first_ns");
+        assert_eq!(first.description(), "This is the description of the first flag.");
+        let second = flag_declarations.flag.iter().find(|pf| pf.name() == "second").unwrap();
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.namespace(), "second_ns");
+        assert_eq!(second.description(), "This is the description of the second flag.");
+
+        // bad input: missing package in flag declarations
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag declarations: missing package");
+
+        // bad input: missing namespace in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+flag {
+    name: "first"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag declaration: missing namespace");
+
+        // bad input: bad package name in flag declarations
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "_com.FOO__BAR"
+flag {
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declarations: bad package"));
+
+        // bad input: bad name in flag declaration
+        let error = flag_declarations::try_from_text_proto(
+            r#"
+package: "com.foo.bar"
+flag {
+    name: "FIRST"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+}
+flag {
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag declaration: bad name"));
+    }
+
+    #[test]
+    fn test_flag_values_try_from_text_proto() {
+        // valid input
+        let flag_values = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    state: DISABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.second"
+    name: "second"
+    state: ENABLED
+    permission: READ_WRITE
+}
+"#,
+        )
+        .unwrap();
+        let first = flag_values.flag_value.iter().find(|fv| fv.name() == "first").unwrap();
+        assert_eq!(first.package(), "com.first");
+        assert_eq!(first.name(), "first");
+        assert_eq!(first.state(), ProtoFlagState::DISABLED);
+        assert_eq!(first.permission(), ProtoFlagPermission::READ_ONLY);
+        let second = flag_values.flag_value.iter().find(|fv| fv.name() == "second").unwrap();
+        assert_eq!(second.package(), "com.second");
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.permission(), ProtoFlagPermission::READ_WRITE);
+
+        // bad input: bad package in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "COM.FIRST"
+    name: "first"
+    state: DISABLED
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag value: bad package"));
+
+        // bad input: bad name in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "FIRST"
+    state: DISABLED
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert!(format!("{:?}", error).contains("bad flag value: bad name"));
+
+        // bad input: missing state in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    permission: READ_ONLY
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag value: missing state");
+
+        // bad input: missing permission in flag value
+        let error = flag_values::try_from_text_proto(
+            r#"
+flag_value {
+    package: "com.first"
+    name: "first"
+    state: DISABLED
+}
+"#,
+        )
+        .unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad flag value: missing permission");
+    }
+
+    fn try_from_binary_proto_from_text_proto(text_proto: &str) -> Result<ProtoParsedFlags> {
+        use protobuf::Message;
+
+        let parsed_flags: ProtoParsedFlags = try_from_text_proto(text_proto)?;
+        let mut binary_proto = Vec::new();
+        parsed_flags.write_to_vec(&mut binary_proto)?;
+        parsed_flags::try_from_binary_proto(&binary_proto)
+    }
+
+    #[test]
+    fn test_parsed_flags_try_from_text_proto() {
+        // valid input
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+    trace {
+        source: "flags.values"
+        state: ENABLED
+        permission: READ_WRITE
+    }
+}
+"#;
+        let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+        assert_eq!(parsed_flags.parsed_flag.len(), 2);
+        let second = parsed_flags.parsed_flag.iter().find(|fv| fv.name() == "second").unwrap();
+        assert_eq!(second.package(), "com.second");
+        assert_eq!(second.name(), "second");
+        assert_eq!(second.namespace(), "second_ns");
+        assert_eq!(second.description(), "This is the description of the second flag.");
+        assert_eq!(second.state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.permission(), ProtoFlagPermission::READ_WRITE);
+        assert_eq!(2, second.trace.len());
+        assert_eq!(second.trace[0].source(), "flags.declarations");
+        assert_eq!(second.trace[0].state(), ProtoFlagState::DISABLED);
+        assert_eq!(second.trace[0].permission(), ProtoFlagPermission::READ_ONLY);
+        assert_eq!(second.trace[1].source(), "flags.values");
+        assert_eq!(second.trace[1].state(), ProtoFlagState::ENABLED);
+        assert_eq!(second.trace[1].permission(), ProtoFlagPermission::READ_WRITE);
+
+        // valid input: empty
+        let parsed_flags = try_from_binary_proto_from_text_proto("").unwrap();
+        assert!(parsed_flags.parsed_flag.is_empty());
+
+        // bad input: empty trace
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flag: empty trace");
+
+        // bad input: missing namespace in parsed_flag
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flag: missing namespace");
+
+        // bad input: parsed_flag not sorted by package
+        let text_proto = r#"
+parsed_flag {
+    package: "bbb"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+parsed_flag {
+    package: "aaa"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "bad parsed flags: not sorted: bbb.first comes before aaa.second"
+        );
+
+        // bad input: parsed_flag not sorted by name
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bbb"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+parsed_flag {
+    package: "com.foo"
+    name: "aaa"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "bad parsed flags: not sorted: com.foo.bbb comes before com.foo.aaa"
+        );
+
+        // bad input: duplicate flags
+        let text_proto = r#"
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+parsed_flag {
+    package: "com.foo"
+    name: "bar"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let error = try_from_binary_proto_from_text_proto(text_proto).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.foo.bar");
+    }
+
+    #[test]
+    fn test_parsed_flags_merge() {
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let expected = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        let text_proto = r#"
+parsed_flag {
+    package: "com.first"
+    name: "first"
+    namespace: "first_ns"
+    description: "This is the description of the first flag."
+    state: DISABLED
+    permission: READ_ONLY
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let first = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        let text_proto = r#"
+parsed_flag {
+    package: "com.second"
+    name: "second"
+    namespace: "second_ns"
+    description: "This is the description of the second flag."
+    state: ENABLED
+    permission: READ_WRITE
+    trace {
+        source: "flags.declarations"
+        state: DISABLED
+        permission: READ_ONLY
+    }
+}
+"#;
+        let second = try_from_binary_proto_from_text_proto(text_proto).unwrap();
+
+        // bad cases
+        let error = parsed_flags::merge(vec![first.clone(), first.clone()]).unwrap_err();
+        assert_eq!(format!("{:?}", error), "bad parsed flags: duplicate flag com.first.first");
+
+        // valid cases
+        assert!(parsed_flags::merge(vec![]).unwrap().parsed_flag.is_empty());
+        assert_eq!(first, parsed_flags::merge(vec![first.clone()]).unwrap());
+        assert_eq!(expected, parsed_flags::merge(vec![first.clone(), second.clone()]).unwrap());
+        assert_eq!(expected, parsed_flags::merge(vec![second, first]).unwrap());
+    }
+}
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 76ef005..abe9015 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -16,29 +16,32 @@
 
 #[cfg(test)]
 pub mod test_utils {
-    use crate::cache::Cache;
-    use crate::commands::{Input, Source};
+    use crate::commands::Input;
+    use crate::protos::ProtoParsedFlags;
     use itertools;
 
-    pub fn create_cache() -> Cache {
-        crate::commands::create_cache(
+    pub const TEST_PACKAGE: &str = "com.android.aconfig.test";
+
+    pub fn parse_test_flags() -> ProtoParsedFlags {
+        let bytes = crate::commands::parse_flags(
             "com.android.aconfig.test",
             vec![Input {
-                source: Source::File("tests/test.aconfig".to_string()),
+                source: "tests/test.aconfig".to_string(),
                 reader: Box::new(include_bytes!("../tests/test.aconfig").as_slice()),
             }],
             vec![
                 Input {
-                    source: Source::File("tests/first.values".to_string()),
+                    source: "tests/first.values".to_string(),
                     reader: Box::new(include_bytes!("../tests/first.values").as_slice()),
                 },
                 Input {
-                    source: Source::File("tests/test.aconfig".to_string()),
+                    source: "tests/second.values".to_string(),
                     reader: Box::new(include_bytes!("../tests/second.values").as_slice()),
                 },
             ],
         )
-        .unwrap()
+        .unwrap();
+        crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
     }
 
     pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> {
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 752a469..62116c5 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -1,6 +1,9 @@
 package {package_name};
 
 public final class Flags \{
+    {{- for item in class_elements}}
+    public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
+    {{- endfor }}
     {{ for item in class_elements}}
     public static boolean {item.method_name}() \{
         return FEATURE_FLAGS.{item.method_name}();
diff --git a/tools/aconfig/templates/rust.template b/tools/aconfig/templates/rust.template
index d914943..960c494 100644
--- a/tools/aconfig/templates/rust.template
+++ b/tools/aconfig/templates/rust.template
@@ -1,25 +1,25 @@
 {{- for mod in modules -}}
 pub mod {mod} \{
 {{ endfor -}}
-{{- for parsed_flag in parsed_flags -}}
-{{- if parsed_flag.is_read_only_disabled -}}
+{{- for flag in template_flags -}}
+{{- if flag.is_read_only_disabled -}}
 #[inline(always)]
-pub const fn r#{parsed_flag.name}() -> bool \{
+pub const fn r#{flag.name}() -> bool \{
     false
 }
 
 {{ endif -}}
-{{- if parsed_flag.is_read_only_enabled -}}
+{{- if flag.is_read_only_enabled -}}
 #[inline(always)]
-pub const fn r#{parsed_flag.name}() -> bool \{
+pub const fn r#{flag.name}() -> bool \{
     true
 }
 
 {{ endif -}}
-{{- if parsed_flag.is_read_write -}}
+{{- if flag.is_read_write -}}
 #[inline(always)]
-pub fn r#{parsed_flag.name}() -> bool \{
-    flags_rust::GetServerConfigurableFlag("{parsed_flag.device_config_namespace}", "{parsed_flag.device_config_flag}", "false") == "true"
+pub fn r#{flag.name}() -> bool \{
+    flags_rust::GetServerConfigurableFlag("{flag.device_config_namespace}", "{flag.device_config_flag}", "false") == "true"
 }
 
 {{ endif -}}
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index 5db490b..778a4c6 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -1,7 +1,7 @@
-import static com.android.aconfig.test.Flags.disabled_ro;
-import static com.android.aconfig.test.Flags.disabled_rw;
-import static com.android.aconfig.test.Flags.enabled_ro;
-import static com.android.aconfig.test.Flags.enabled_rw;
+import static com.android.aconfig.test.Flags.disabledRo;
+import static com.android.aconfig.test.Flags.disabledRw;
+import static com.android.aconfig.test.Flags.enabledRo;
+import static com.android.aconfig.test.Flags.enabledRw;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -13,25 +13,25 @@
 public final class AconfigTest {
     @Test
     public void testDisabledReadOnlyFlag() {
-        assertFalse(disabled_ro());
+        assertFalse(disabledRo());
     }
 
     @Test
     public void testEnabledReadOnlyFlag() {
-        // TODO: change to assertTrue(enabled_ro()) when the build supports reading tests/*.values
+        // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
         // (currently all flags are assigned the default READ_ONLY + DISABLED)
-        assertFalse(enabled_ro());
+        assertFalse(enabledRo());
     }
 
     @Test
     public void testDisabledReadWriteFlag() {
-        assertFalse(disabled_rw());
+        assertFalse(disabledRw());
     }
 
     @Test
     public void testEnabledReadWriteFlag() {
-        // TODO: change to assertTrue(enabled_rw()) when the build supports reading tests/*.values
+        // TODO: change to assertTrue(enabledRw()) when the build supports reading tests/*.values
         // (currently all flags are assigned the default READ_ONLY + DISABLED)
-        assertFalse(enabled_rw());
+        assertFalse(enabledRw());
     }
 }
diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
index 667cdce..3e7e69b 100644
--- a/tools/compliance/cmd/rtrace/rtrace.go
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -93,17 +93,17 @@
 	flags.Usage = func() {
 		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
 
-Outputs a space-separated Target ActsOn Origin Condition tuple for each
-resolution in the graph. When -dot flag given, outputs nodes and edges
-in graphviz directed graph format.
+Calculates the source-sharing requirements in reverse starting at the
+-rtrace projects or metadata files that inherited source-sharing and
+working back to the targets where the source-sharing requirmements
+originate.
 
-If one or more '-c condition' conditions are given, outputs the
-resolution for the union of the conditions. Otherwise, outputs the
-resolution for all conditions.
+Outputs a space-separated pair where the first field is an originating
+target with one or more restricted conditions and where the second
+field is a colon-separated list of the restricted conditions.
 
-In plain text mode, when '-label_conditions' is requested, the Target
-and Origin have colon-separated license conditions appended:
-i.e. target:condition1:condition2 etc.
+Outputs a count of the originating targets, and if the count is zero,
+outputs a warning to check the -rtrace projects and/or filenames.
 
 Options:
 `, filepath.Base(os.Args[0]))
diff --git a/tools/compliance/projectmetadata/projectmetadata.go b/tools/compliance/projectmetadata/projectmetadata.go
index b137a12..30a6325 100644
--- a/tools/compliance/projectmetadata/projectmetadata.go
+++ b/tools/compliance/projectmetadata/projectmetadata.go
@@ -63,12 +63,12 @@
 	return pm.project
 }
 
-// ProjectName returns the name of the project.
+// Name returns the name of the project.
 func (pm *ProjectMetadata) Name() string {
 	return pm.proto.GetName()
 }
 
-// ProjectVersion returns the version of the project if available.
+// Version returns the version of the project if available.
 func (pm *ProjectMetadata) Version() string {
 	tp := pm.proto.GetThirdParty()
 	if tp != nil {
diff --git a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
index fa33986..6d13325 100755
--- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
@@ -5,14 +5,14 @@
 function apply_droidstubs_hack() {
     if ! grep -q 'STOPSHIP: RESTORE THIS LOGIC WHEN DECLARING "REL" BUILD' "$top/build/soong/java/droidstubs.go" ; then
         local build_soong_git_root="$(readlink -f $top/build/soong)"
-        git -C "$build_soong_git_root" apply --allow-empty ../../build/make/tools/finalization/build_soong_java_droidstubs.go.apply_hack.diff
+        patch --strip=1 --no-backup-if-mismatch --directory="$build_soong_git_root" --input=../../build/make/tools/finalization/build_soong_java_droidstubs.go.apply_hack.diff
     fi
 }
 
 function apply_resources_sdk_int_fix() {
     if ! grep -q 'public static final int RESOURCES_SDK_INT = SDK_INT;' "$top/frameworks/base/core/java/android/os/Build.java" ; then
         local base_git_root="$(readlink -f $top/frameworks/base)"
-        git -C "$base_git_root" apply --allow-empty ../../build/make/tools/finalization/frameworks_base.apply_resource_sdk_int.diff
+        patch --strip=1 --no-backup-if-mismatch --directory="$base_git_root" --input=../../build/make/tools/finalization/frameworks_base.apply_resource_sdk_int.diff
     fi
 }
 
diff --git a/tools/finalization/finalize-sdk-rel.sh b/tools/finalization/finalize-sdk-rel.sh
index 62e5ee5..cb7d1fc 100755
--- a/tools/finalization/finalize-sdk-rel.sh
+++ b/tools/finalization/finalize-sdk-rel.sh
@@ -4,19 +4,19 @@
 
 function revert_droidstubs_hack() {
     if grep -q 'STOPSHIP: RESTORE THIS LOGIC WHEN DECLARING "REL" BUILD' "$top/build/soong/java/droidstubs.go" ; then
-        git -C "$top/build/soong" apply --allow-empty ../../build/make/tools/finalization/build_soong_java_droidstubs.go.revert_hack.diff
+        patch --strip=1 --no-backup-if-mismatch --directory="$top/build/soong" --input=../../build/make/tools/finalization/build_soong_java_droidstubs.go.revert_hack.diff
     fi
 }
 
 function revert_resources_sdk_int_fix() {
     if grep -q 'public static final int RESOURCES_SDK_INT = SDK_INT;' "$top/frameworks/base/core/java/android/os/Build.java" ; then
-        git -C "$top/frameworks/base" apply --allow-empty ../../build/make/tools/finalization/frameworks_base.revert_resource_sdk_int.diff
+        patch --strip=1 --no-backup-if-mismatch --directory="$top/frameworks/base" --input=../../build/make/tools/finalization/frameworks_base.revert_resource_sdk_int.diff
     fi
 }
 
 function apply_prerelease_sdk_hack() {
     if ! grep -q 'STOPSHIP: hack for the pre-release SDK' "$top/frameworks/base/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java" ; then
-        git -C "$top/frameworks/base" apply --allow-empty ../../build/make/tools/finalization/frameworks_base.apply_hack.diff
+        patch --strip=1 --no-backup-if-mismatch --directory="$top/frameworks/base" --input=../../build/make/tools/finalization/frameworks_base.apply_hack.diff
     fi
 }
 
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index 59c712e..bfc87b8 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -65,8 +65,6 @@
         OPTIONS.search_path, "bin", "debugfs_static")
     self.fsckerofs_path = os.path.join(
         OPTIONS.search_path, "bin", "fsck.erofs")
-    self.blkid_path = os.path.join(
-        OPTIONS.search_path, "bin", "blkid_static")
     self.avbtool = avbtool if avbtool else "avbtool"
     self.sign_tool = sign_tool
 
@@ -129,15 +127,10 @@
           "Couldn't find location of fsck.erofs: " +
           "Path {} does not exist. ".format(self.fsckerofs_path) +
           "Make sure bin/fsck.erofs can be found in -p <path>")
-    if not os.path.exists(self.blkid_path):
-      raise ApexSigningError(
-          "Couldn't find location of blkid: " +
-          "Path {} does not exist. ".format(self.blkid_path) +
-          "Make sure bin/blkid can be found in -p <path>")
     payload_dir = common.MakeTempDir()
     extract_cmd = ['deapexer', '--debugfs_path', self.debugfs_path,
                    '--fsckerofs_path', self.fsckerofs_path,
-                   '--blkid_path', self.blkid_path, 'extract',
+                   'extract',
                    self.apex_path, payload_dir]
     common.RunAndCheckOutput(extract_cmd)
     assert os.path.exists(self.apex_path)
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index 5b71c72..906ad1f 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -232,12 +232,10 @@
 
   deapexer = 'deapexer'
   debugfs_path = 'debugfs'
-  blkid_path = 'blkid'
   fsckerofs_path = 'fsck.erofs'
   if OPTIONS.search_path:
     debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
     deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
-    blkid_path = os.path.join(OPTIONS.search_path, 'bin', 'blkid_static')
     fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
     if os.path.isfile(deapexer_path):
       deapexer = deapexer_path
@@ -263,7 +261,6 @@
         cmd = [deapexer,
                '--debugfs_path', debugfs_path,
                '--fsckerofs_path', fsckerofs_path,
-               '--blkid_path', blkid_path,
                'extract',
                apex,
                os.path.join(outp, info['name'])]
diff --git a/tools/releasetools/merge/merge_dexopt.py b/tools/releasetools/merge/merge_dexopt.py
index 16182b5..1dd591b 100644
--- a/tools/releasetools/merge/merge_dexopt.py
+++ b/tools/releasetools/merge/merge_dexopt.py
@@ -164,8 +164,6 @@
           'deapexer',
           '--debugfs_path',
           'debugfs_static',
-          '--blkid_path',
-          'blkid',
           '--fsckerofs_path',
           'fsck.erofs',
           'extract',
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 2415f7e..b19be87 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -332,14 +332,6 @@
   return external_doc_ref, packages, relationships
 
 
-def generate_package_verification_code(files):
-  checksums = [file.checksum for file in files]
-  checksums.sort()
-  h = hashlib.sha1()
-  h.update(''.join(checksums).encode(encoding='utf-8'))
-  return h.hexdigest()
-
-
 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():
@@ -487,20 +479,32 @@
       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']
+      is_static_lib = installed_file_metadata['is_static_lib']
 
       if not installed_file_has_metadata(installed_file_metadata, report):
         continue
-      if not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
+      if not is_static_lib and not (os.path.islink(build_output_path) or os.path.isfile(build_output_path)):
+        # Ignore non-existing static library files for now since they are not shipped on devices.
         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(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))
+      # TODO(b/285453664): Soong should report the information of statically linked libraries to Make.
+      # This happens when a different sanitized version of static libraries is used in linking.
+      # As a workaround, use the following SHA1 checksum for static libraries created by Soong, if .a files could not be
+      # located correctly because Soong doesn't report the information to Make.
+      sha1 = 'SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709'  # SHA1 of empty string
+      if os.path.islink(build_output_path) or os.path.isfile(build_output_path):
+        sha1 = checksum(build_output_path)
+      doc.files.append(sbom_data.File(id=file_id,
+                                      name=installed_file,
+                                      checksum=sha1))
+
+      if not is_static_lib:
+        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)
@@ -544,13 +548,21 @@
                                                     relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                                     id2=sbom_data.SPDXID_PLATFORM))
 
-  if not args.unbundled_apex:
-    product_package.verification_code = generate_package_verification_code(doc.files)
+      # Process static libraries and whole static libraries the installed file links to
+      static_libs = installed_file_metadata['static_libraries']
+      whole_static_libs = installed_file_metadata['whole_static_libraries']
+      all_static_libs = (static_libs + ' ' + whole_static_libs).strip()
+      if all_static_libs:
+        for lib in all_static_libs.split(' '):
+          doc.add_relationship(sbom_data.Relationship(id1=file_id,
+                                                      relationship=sbom_data.RelationshipType.STATIC_LINK,
+                                                      id2=new_file_id(lib + '.a')))
 
   if args.unbundled_apex:
     doc.describes = doc.files[0].id
 
   # Save SBOM records to output file
+  doc.generate_packages_verification_code()
   doc.created = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
   prefix = args.output_file
   if prefix.endswith('.spdx'):
diff --git a/tools/sbom/sbom_data.py b/tools/sbom/sbom_data.py
index 14c4eb2..ea38e36 100644
--- a/tools/sbom/sbom_data.py
+++ b/tools/sbom/sbom_data.py
@@ -25,6 +25,7 @@
 
 from dataclasses import dataclass, field
 from typing import List
+import hashlib
 
 SPDXID_DOC = 'SPDXRef-DOCUMENT'
 SPDXID_PRODUCT = 'SPDXRef-PRODUCT'
@@ -81,6 +82,7 @@
   VARIANT_OF = 'VARIANT_OF'
   GENERATED_FROM = 'GENERATED_FROM'
   CONTAINS = 'CONTAINS'
+  STATIC_LINK = 'STATIC_LINK'
 
 
 @dataclass
@@ -122,3 +124,17 @@
     if not any(rel.id1 == r.id1 and rel.id2 == r.id2 and rel.relationship == r.relationship
                for r in self.relationships):
       self.relationships.append(rel)
+
+  def generate_packages_verification_code(self):
+    for package in self.packages:
+      if not package.file_ids:
+        continue
+
+      checksums = []
+      for file in self.files:
+        if file.id in package.file_ids:
+          checksums.append(file.checksum)
+      checksums.sort()
+      h = hashlib.sha1()
+      h.update(''.join(checksums).encode(encoding='utf-8'))
+      package.verification_code = h.hexdigest()
diff --git a/tools/sbom/sbom_writers.py b/tools/sbom/sbom_writers.py
index 85dee9d..1cb864d 100644
--- a/tools/sbom/sbom_writers.py
+++ b/tools/sbom/sbom_writers.py
@@ -85,7 +85,7 @@
     return headers
 
   @staticmethod
-  def marshal_package(package):
+  def marshal_package(sbom_doc, package, fragment):
     download_location = sbom_data.VALUE_NOASSERTION
     if package.download_location:
       download_location = package.download_location
@@ -107,50 +107,32 @@
           f'{Tags.PACKAGE_EXTERNAL_REF}: {external_ref.category} {external_ref.type} {external_ref.locator}')
 
     tagvalues.append('')
+
+    if package.id == sbom_doc.describes and not fragment:
+      tagvalues.append(
+          f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
+      tagvalues.append('')
+
+    for file in sbom_doc.files:
+      if file.id in package.file_ids:
+        tagvalues += TagValueWriter.marshal_file(file)
+
     return tagvalues
 
   @staticmethod
-  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])
-      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 = 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
-
-    return None
-
-  @staticmethod
-  def marshal_packages(sbom_doc):
+  def marshal_packages(sbom_doc, fragment):
     tagvalues = []
     marshaled_relationships = []
     i = 0
     packages = sbom_doc.packages
     while i < len(packages):
-      if packages[i].id == sbom_doc.describes:
-        i += 1
-        continue
-
-      if i + 1 < len(packages) \
-          and packages[i].id.startswith('SPDXRef-SOURCE-') \
-          and packages[i + 1].id.startswith('SPDXRef-UPSTREAM-'):
-        tagvalues += TagValueWriter.marshal_package(packages[i])
-        tagvalues += TagValueWriter.marshal_package(packages[i + 1])
+      if (i + 1 < len(packages)
+          and packages[i].id.startswith('SPDXRef-SOURCE-')
+          and packages[i + 1].id.startswith('SPDXRef-UPSTREAM-')):
+        # Output SOURCE, UPSTREAM packages and their VARIANT_OF relationship together, so they are close to each other
+        # in SBOMs in tagvalue format.
+        tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i], fragment)
+        tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i + 1], fragment)
         rel = next((r for r in sbom_doc.relationships if
                     r.id1 == packages[i].id and
                     r.id2 == packages[i + 1].id and
@@ -162,7 +144,7 @@
 
         i += 2
       else:
-        tagvalues += TagValueWriter.marshal_package(packages[i])
+        tagvalues += TagValueWriter.marshal_package(sbom_doc, packages[i], fragment)
         i += 1
 
     return tagvalues, marshaled_relationships
@@ -179,12 +161,20 @@
     return tagvalues
 
   @staticmethod
-  def marshal_files(sbom_doc):
+  def marshal_files(sbom_doc, fragment):
     tagvalues = []
+    files_in_packages = []
+    for package in sbom_doc.packages:
+      files_in_packages += package.file_ids
     for file in sbom_doc.files:
-      if file.id == sbom_doc.describes:
+      if file.id in files_in_packages:
         continue
       tagvalues += TagValueWriter.marshal_file(file)
+      if file.id == sbom_doc.describes and not fragment:
+        # Fragment is not a full SBOM document so the relationship DESCRIBES is not applicable.
+        tagvalues.append(
+            f'{Tags.RELATIONSHIP}: {sbom_doc.id} {sbom_data.RelationshipType.DESCRIBES} {sbom_doc.describes}')
+        tagvalues.append('')
     return tagvalues
 
   @staticmethod
@@ -208,11 +198,8 @@
     content = []
     if not fragment:
       content += TagValueWriter.marshal_doc_headers(sbom_doc)
-    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 += TagValueWriter.marshal_files(sbom_doc, fragment)
+    tagvalues, marshaled_relationships = TagValueWriter.marshal_packages(sbom_doc, fragment)
     content += tagvalues
     content += TagValueWriter.marshal_relationships(sbom_doc, marshaled_relationships)
     file.write('\n'.join(content))
diff --git a/tools/sbom/sbom_writers_test.py b/tools/sbom/sbom_writers_test.py
index 361dae6..cf85e01 100644
--- a/tools/sbom/sbom_writers_test.py
+++ b/tools/sbom/sbom_writers_test.py
@@ -31,6 +31,7 @@
 SPDXID_FILE1 = 'SPDXRef-file1'
 SPDXID_FILE2 = 'SPDXRef-file2'
 SPDXID_FILE3 = 'SPDXRef-file3'
+SPDXID_FILE4 = 'SPDXRef-file4'
 
 
 class SBOMWritersTest(unittest.TestCase):
@@ -101,6 +102,8 @@
       sbom_data.File(id=SPDXID_FILE2, name='/bin/file2', checksum='SHA1: 22222'))
     self.sbom_doc.files.append(
       sbom_data.File(id=SPDXID_FILE3, name='/bin/file3', checksum='SHA1: 33333'))
+    self.sbom_doc.files.append(
+      sbom_data.File(id=SPDXID_FILE4, name='file4.a', checksum='SHA1: 44444'))
 
     self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1,
                                                           relationship=sbom_data.RelationshipType.GENERATED_FROM,
@@ -112,6 +115,10 @@
                                                           relationship=sbom_data.RelationshipType.GENERATED_FROM,
                                                           id2=SPDXID_SOURCE_PACKAGE1
                                                           ))
+    self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1,
+                                                          relationship=sbom_data.RelationshipType.STATIC_LINK,
+                                                          id2=SPDXID_FILE4
+                                                          ))
 
     # SBOM fragment of a APK
     self.unbundled_sbom_doc = sbom_data.Document(name='test doc',
@@ -139,6 +146,14 @@
       self.maxDiff = None
       self.assertEqual(expected_output, output.getvalue())
 
+  def test_tagvalue_writer_doc_describes_file(self):
+    with io.StringIO() as output:
+      self.sbom_doc.describes = SPDXID_FILE4
+      sbom_writers.TagValueWriter.write(self.sbom_doc, output)
+      expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_doc_describes_file.spdx').read_text()
+      self.maxDiff = None
+      self.assertEqual(expected_output, output.getvalue())
+
   def test_tagvalue_writer_unbundled(self):
     with io.StringIO() as output:
       sbom_writers.TagValueWriter.write(self.unbundled_sbom_doc, output, fragment=True)
diff --git a/tools/sbom/testdata/expected_json_sbom.spdx.json b/tools/sbom/testdata/expected_json_sbom.spdx.json
index 32715a5..53936c5 100644
--- a/tools/sbom/testdata/expected_json_sbom.spdx.json
+++ b/tools/sbom/testdata/expected_json_sbom.spdx.json
@@ -110,6 +110,16 @@
                     "checksumValue": "33333"
                 }
             ]
+        },
+        {
+            "fileName": "file4.a",
+            "SPDXID": "SPDXRef-file4",
+            "checksums": [
+                {
+                    "algorithm": "SHA1",
+                    "checksumValue": "44444"
+                }
+            ]
         }
     ],
     "relationships": [
@@ -129,6 +139,11 @@
             "relationshipType": "GENERATED_FROM"
         },
         {
+            "spdxElementId": "SPDXRef-file1",
+            "relatedSpdxElement": "SPDXRef-file4",
+            "relationshipType": "STATIC_LINK"
+        },
+        {
             "spdxElementId": "SPDXRef-SOURCE-package1",
             "relatedSpdxElement": "SPDXRef-UPSTREAM-package1",
             "relationshipType": "VARIANT_OF"
diff --git a/tools/sbom/testdata/expected_tagvalue_sbom.spdx b/tools/sbom/testdata/expected_tagvalue_sbom.spdx
index ee39e82..e6fd17e 100644
--- a/tools/sbom/testdata/expected_tagvalue_sbom.spdx
+++ b/tools/sbom/testdata/expected_tagvalue_sbom.spdx
@@ -7,6 +7,10 @@
 Created: 2023-03-31T22:17:58Z
 ExternalDocumentRef: DocumentRef-external_doc_ref external_doc_uri SHA1: 1234567890
 
+FileName: file4.a
+SPDXID: SPDXRef-file4
+FileChecksum: SHA1: 44444
+
 PackageName: PRODUCT
 SPDXID: SPDXRef-PRODUCT
 PackageDownloadLocation: NONE
@@ -63,3 +67,4 @@
 Relationship: SPDXRef-file1 GENERATED_FROM SPDXRef-PLATFORM
 Relationship: SPDXRef-file2 GENERATED_FROM SPDXRef-PREBUILT-package1
 Relationship: SPDXRef-file3 GENERATED_FROM SPDXRef-SOURCE-package1
+Relationship: SPDXRef-file1 STATIC_LINK SPDXRef-file4
diff --git a/tools/sbom/testdata/expected_tagvalue_sbom_doc_describes_file.spdx b/tools/sbom/testdata/expected_tagvalue_sbom_doc_describes_file.spdx
new file mode 100644
index 0000000..428d7e3
--- /dev/null
+++ b/tools/sbom/testdata/expected_tagvalue_sbom_doc_describes_file.spdx
@@ -0,0 +1,70 @@
+SPDXVersion: SPDX-2.3
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: test doc
+DocumentNamespace: http://www.google.com/sbom/spdx/android
+Creator: Organization: Google
+Created: 2023-03-31T22:17:58Z
+ExternalDocumentRef: DocumentRef-external_doc_ref external_doc_uri SHA1: 1234567890
+
+FileName: file4.a
+SPDXID: SPDXRef-file4
+FileChecksum: SHA1: 44444
+
+Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-file4
+
+PackageName: PRODUCT
+SPDXID: SPDXRef-PRODUCT
+PackageDownloadLocation: NONE
+FilesAnalyzed: true
+PackageVersion: build_finger_print
+PackageSupplier: Organization: Google
+PackageVerificationCode: 123456
+
+FileName: /bin/file1
+SPDXID: SPDXRef-file1
+FileChecksum: SHA1: 11111
+
+FileName: /bin/file2
+SPDXID: SPDXRef-file2
+FileChecksum: SHA1: 22222
+
+FileName: /bin/file3
+SPDXID: SPDXRef-file3
+FileChecksum: SHA1: 33333
+
+PackageName: PLATFORM
+SPDXID: SPDXRef-PLATFORM
+PackageDownloadLocation: NONE
+FilesAnalyzed: false
+PackageVersion: build_finger_print
+PackageSupplier: Organization: Google
+
+PackageName: Prebuilt package1
+SPDXID: SPDXRef-PREBUILT-package1
+PackageDownloadLocation: NONE
+FilesAnalyzed: false
+PackageVersion: build_finger_print
+PackageSupplier: Organization: Google
+
+PackageName: Source package1
+SPDXID: SPDXRef-SOURCE-package1
+PackageDownloadLocation: NONE
+FilesAnalyzed: false
+PackageVersion: build_finger_print
+PackageSupplier: Organization: Google
+ExternalRef: SECURITY cpe22Type cpe:/a:jsoncpp_project:jsoncpp:1.9.4
+
+PackageName: Upstream package1
+SPDXID: SPDXRef-UPSTREAM-package1
+PackageDownloadLocation: NOASSERTION
+FilesAnalyzed: false
+PackageVersion: 1.1
+PackageSupplier: Organization: upstream
+
+Relationship: SPDXRef-SOURCE-package1 VARIANT_OF SPDXRef-UPSTREAM-package1
+
+Relationship: SPDXRef-file1 GENERATED_FROM SPDXRef-PLATFORM
+Relationship: SPDXRef-file2 GENERATED_FROM SPDXRef-PREBUILT-package1
+Relationship: SPDXRef-file3 GENERATED_FROM SPDXRef-SOURCE-package1
+Relationship: SPDXRef-file1 STATIC_LINK SPDXRef-file4