Merge "add com.android.threadnetwork to base_system.mk"
diff --git a/core/Makefile b/core/Makefile
index 6dbbef1..00ab3f8 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4827,25 +4827,24 @@
intermediates := $(call intermediates-dir-for,PACKAGING,check_vintf_all)
check_vintf_all_deps :=
-APEX_OUT := $(PRODUCT_OUT)/apex
# -----------------------------------------------------------------
-# Create apex-info-file.xml
+# Activate vendor APEXes for checkvintf
apex_dirs := \
- $(TARGET_OUT)/apex/% \
- $(TARGET_OUT_SYSTEM_EXT)/apex/% \
$(TARGET_OUT_VENDOR)/apex/% \
- $(TARGET_OUT_ODM)/apex/% \
- $(TARGET_OUT_PRODUCT)/apex/% \
apex_files := $(sort $(filter $(apex_dirs), $(INTERNAL_ALLIMAGES_FILES)))
+
+APEX_OUT := $(intermediates)/apex
APEX_INFO_FILE := $(APEX_OUT)/apex-info-list.xml
-# dump_apex_info scans $(PRODUCT_OUT)/apex and writes apex-info-list.xml there.
-# This relies on the fact that rules for .apex files install the contents in $(PRODUCT_OUT)/apex.
-$(APEX_INFO_FILE): $(HOST_OUT_EXECUTABLES)/dump_apex_info $(apex_files)
- @echo "Creating apex-info-file in $(PRODUCT_OUT) "
- $< --root_dir $(PRODUCT_OUT)
+# apexd_host scans/activates APEX files and writes /apex/apex-info-list.xml
+$(APEX_INFO_FILE): $(HOST_OUT_EXECUTABLES)/apexd_host $(apex_files)
+ @echo "Extracting apexes..."
+ @rm -rf $(APEX_OUT)
+ @mkdir -p $(APEX_OUT)
+ $< --vendor_path $(TARGET_OUT_VENDOR) \
+ --apex_path $(APEX_OUT)
apex_files :=
apex_dirs :=
@@ -4893,6 +4892,8 @@
check_vintf_all_deps += $(vintffm_log)
$(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps)
@( $< --check --dirmap /system:$(TARGET_OUT) \
+ --dirmap /system_ext:$(TARGET_OUT_SYSTEM_EXT) \
+ --dirmap /product:$(TARGET_OUT_PRODUCT) \
$(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
$(call declare-1p-target,$(vintffm_log))
@@ -5295,11 +5296,11 @@
# Additional tools to unpack and repack the apex file.
INTERNAL_OTATOOLS_MODULES += \
+ apexd_host \
apexer \
apex_compression_tool \
deapexer \
debugfs_static \
- dump_apex_info \
fsck.erofs \
make_erofs \
merge_zips \
@@ -5408,6 +5409,9 @@
ifneq ($(INSTALLED_VENDOR_KERNEL_BOOTIMAGE_TARGET),)
$(hide) echo "flash vendor_kernel_boot" >> $@
endif
+ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
+ $(hide) echo "flash recovery" >> $@
+endif
ifeq ($(BOARD_USES_PVMFWIMAGE),true)
$(hide) echo "flash pvmfw" >> $@
endif
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 5dba2d1..f132d13 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -114,7 +114,7 @@
# are controlled by the MODULE_BUILD_FROM_SOURCE environment variable by
# default.
INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES := \
- bluetooth \
+ btservices \
permission \
rkpd \
uwb \
diff --git a/core/base_rules.mk b/core/base_rules.mk
index c61c653..9ad1cc5 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -450,6 +450,12 @@
$(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_IS_HOST_MODULE := $(LOCAL_IS_HOST_MODULE)
$(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_HOST:= $(my_host)
$(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_PREFIX := $(my_prefix)
+$(LOCAL_INTERMEDIATE_TARGETS) : .KATI_TAGS += ;module_name=$(LOCAL_MODULE)
+ifeq ($(LOCAL_MODULE_CLASS),)
+$(error "$(LOCAL_MODULE) in $(LOCAL_PATH) does not set $(LOCAL_MODULE_CLASS)")
+else
+$(LOCAL_INTERMEDIATE_TARGETS) : .KATI_TAGS += ;module_type=$(LOCAL_MODULE_CLASS)
+endif
$(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_INTERMEDIATES_DIR:= $(intermediates)
$(LOCAL_INTERMEDIATE_TARGETS) : PRIVATE_2ND_ARCH_VAR_PREFIX := $(LOCAL_2ND_ARCH_VAR_PREFIX)
diff --git a/core/config.mk b/core/config.mk
index 5191917..c166ef7 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -899,6 +899,7 @@
31.0 \
32.0 \
33.0 \
+ 34.0 \
.KATI_READONLY := \
PLATFORM_SEPOLICY_COMPAT_VERSIONS \
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 252e812..7fa190f 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -180,6 +180,7 @@
ifneq ($(filter address,$(my_sanitize)),)
my_sanitize := $(filter-out cfi,$(my_sanitize))
my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+ my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
my_sanitize_diag := $(filter-out cfi,$(my_sanitize_diag))
endif
@@ -187,8 +188,8 @@
# Disable memtag for host targets. Host executables in AndroidMk files are
# deprecated, but some partners still have them floating around.
ifdef LOCAL_IS_HOST_MODULE
- my_sanitize := $(filter-out memtag_heap memtag_stack,$(my_sanitize))
- my_sanitize_diag := $(filter-out memtag_heap memtag_stack,$(my_sanitize_diag))
+ my_sanitize := $(filter-out memtag_heap memtag_stack memtag_globals,$(my_sanitize))
+ my_sanitize_diag := $(filter-out memtag_heap memtag_stack memtag_globals,$(my_sanitize_diag))
endif
# Disable sanitizers which need the UBSan runtime for host targets.
@@ -223,11 +224,13 @@
my_sanitize := $(filter-out hwaddress,$(my_sanitize))
my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+ my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
endif
ifneq ($(filter hwaddress,$(my_sanitize)),)
my_sanitize := $(filter-out address,$(my_sanitize))
my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
+ my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
my_sanitize := $(filter-out memtag_heap,$(my_sanitize))
my_sanitize := $(filter-out thread,$(my_sanitize))
my_sanitize := $(filter-out cfi,$(my_sanitize))
@@ -244,7 +247,7 @@
endif
endif
-ifneq ($(filter memtag_heap memtag_stack,$(my_sanitize)),)
+ifneq ($(filter memtag_heap memtag_stack memtag_globals,$(my_sanitize)),)
ifneq ($(filter memtag_heap,$(my_sanitize_diag)),)
my_cflags += -fsanitize-memtag-mode=sync
my_sanitize_diag := $(filter-out memtag_heap,$(my_sanitize_diag))
@@ -273,6 +276,14 @@
my_sanitize := $(filter-out memtag_stack,$(my_sanitize))
endif
+ifneq ($(filter memtag_globals,$(my_sanitize)),)
+ my_cflags += -fsanitize=memtag-globals
+ # TODO(mitchp): For now, enable memtag-heap with memtag-globals because the
+ # linker isn't new enough
+ # (https://reviews.llvm.org/differential/changeset/?ref=4243566).
+ my_sanitize := $(filter-out memtag_globals,$(my_sanitize))
+endif
+
# TSAN is not supported on 32-bit architectures. For non-multilib cases, make
# its use an error. For multilib cases, don't use it for the 32-bit case.
ifneq ($(filter thread,$(my_sanitize)),)
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/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index bdd47a8..288f81f 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -152,6 +152,9 @@
$(LOCAL_USES_LIBRARIES) \
$(my_filtered_optional_uses_libraries)
+# The order needs to be deterministic.
+my_dexpreopt_libs_all := $(sort $(my_dexpreopt_libs) $(my_dexpreopt_libs_compat))
+
# Module dexpreopt.config depends on dexpreopt.config files of each
# <uses-library> dependency, because these libraries may be processed after
# the current module by Make (there's no topological order), so the dependency
@@ -442,6 +445,28 @@
@cp $(PRIVATE_BUILT_MODULE) $@
endif
+ # The root "product_packages.txt" is generated by `build/make/core/Makefile`. It contains a list
+ # of all packages that are installed on the device. We use `grep` to filter the list by the app's
+ # dependencies to create a per-app list, and use `rsync --checksum` to prevent the file's mtime
+ # from being changed if the contents don't change. This avoids unnecessary dexpreopt reruns.
+ my_dexpreopt_product_packages := $(intermediates)/product_packages.txt
+ .KATI_RESTAT: $(my_dexpreopt_product_packages)
+ $(my_dexpreopt_product_packages): PRIVATE_MODULE := $(LOCAL_MODULE)
+ $(my_dexpreopt_product_packages): PRIVATE_LIBS := $(my_dexpreopt_libs_all)
+ $(my_dexpreopt_product_packages): PRIVATE_STAGING := $(my_dexpreopt_product_packages).tmp
+ $(my_dexpreopt_product_packages): $(PRODUCT_OUT)/product_packages.txt
+ @echo "$(PRIVATE_MODULE) dexpreopt product_packages"
+ ifneq (,$(my_dexpreopt_libs_all))
+ grep -F -x \
+ $(addprefix -e ,$(PRIVATE_LIBS)) \
+ $(PRODUCT_OUT)/product_packages.txt \
+ > $(PRIVATE_STAGING) \
+ || true
+ else
+ rm -f $(PRIVATE_STAGING) && touch $(PRIVATE_STAGING)
+ endif
+ rsync --checksum $(PRIVATE_STAGING) $@
+
my_dexpreopt_script := $(intermediates)/dexpreopt.sh
my_dexpreopt_zip := $(intermediates)/dexpreopt.zip
DEXPREOPT.$(LOCAL_MODULE).POST_INSTALLED_DEXPREOPT_ZIP := $(my_dexpreopt_zip)
@@ -450,9 +475,10 @@
$(my_dexpreopt_script): PRIVATE_GLOBAL_SOONG_CONFIG := $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE)
$(my_dexpreopt_script): PRIVATE_GLOBAL_CONFIG := $(DEX_PREOPT_CONFIG_FOR_MAKE)
$(my_dexpreopt_script): PRIVATE_MODULE_CONFIG := $(my_dexpreopt_config)
+ $(my_dexpreopt_script): PRIVATE_PRODUCT_PACKAGES := $(my_dexpreopt_product_packages)
$(my_dexpreopt_script): $(DEXPREOPT_GEN)
$(my_dexpreopt_script): $(my_dexpreopt_jar_copy)
- $(my_dexpreopt_script): $(my_dexpreopt_config) $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(DEX_PREOPT_CONFIG_FOR_MAKE)
+ $(my_dexpreopt_script): $(my_dexpreopt_config) $(DEX_PREOPT_SOONG_CONFIG_FOR_MAKE) $(DEX_PREOPT_CONFIG_FOR_MAKE) $(my_dexpreopt_product_packages)
@echo "$(PRIVATE_MODULE) dexpreopt gen"
$(DEXPREOPT_GEN) \
-global_soong $(PRIVATE_GLOBAL_SOONG_CONFIG) \
@@ -460,12 +486,12 @@
-module $(PRIVATE_MODULE_CONFIG) \
-dexpreopt_script $@ \
-out_dir $(OUT_DIR) \
- -product_packages $(PRODUCT_OUT)/product_packages.txt
+ -product_packages $(PRIVATE_PRODUCT_PACKAGES)
my_dexpreopt_deps := $(my_dex_jar)
my_dexpreopt_deps += $(if $(my_process_profile),$(LOCAL_DEX_PREOPT_PROFILE))
my_dexpreopt_deps += \
- $(foreach lib, $(my_dexpreopt_libs) $(my_dexpreopt_libs_compat), \
+ $(foreach lib, $(my_dexpreopt_libs_all), \
$(call intermediates-dir-for,JAVA_LIBRARIES,$(lib),,COMMON)/javalib.jar)
my_dexpreopt_deps += $(my_dexpreopt_images_deps)
my_dexpreopt_deps += $(DEXPREOPT_BOOTCLASSPATH_DEX_FILES)
@@ -501,8 +527,11 @@
$(my_all_targets): $(my_dexpreopt_zip)
my_dexpreopt_config :=
+ my_dexpreopt_product_packages :=
my_dexpreopt_script :=
my_dexpreopt_zip :=
my_dexpreopt_config_for_postprocessing :=
endif # LOCAL_DEX_PREOPT
endif # my_create_dexpreopt_config
+
+my_dexpreopt_libs_all :=
diff --git a/core/main.mk b/core/main.mk
index 40e690d..5a591f9 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -4,7 +4,7 @@
$(error done)
endif
-$(info [1/1] initializing build system ...)
+$(info [1/1] initializing legacy Make module parser ...)
# Absolute path of the present working direcotry.
# This overrides the shell variable $PWD, which does not necessarily points to
@@ -554,7 +554,7 @@
subdir_makefiles_total := $(words init post finish)
endif
-$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing build rules ...)
+$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing legacy Make module parsing ...)
# -------------------------------------------------------------------
# All module makefiles have been included at this point.
@@ -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,21 +2217,51 @@
$(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
$(call dist-write-file,$(KATI_PACKAGE_MK_DIR)/dist.mk)
-$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing build rules ...)
+$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] writing legacy Make module rules ...)
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/release_config.mk b/core/release_config.mk
index 1a2d480..b72ee89 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -21,7 +21,7 @@
# defining the build flag values.
#
# (If you're thinking about aconfig flags, there is one build flag,
-# RELEASE_DEVICE_CONFIG_VALUE_SETS, that sets which device_config_value_set
+# RELEASE_ACONFIG_VALUE_SETS, that sets which aconfig_value_set
# module to use to set the aconfig flag values.)
#
# The short release config names *can* appear multiple times, to allow
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..0d5799c 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -226,7 +226,6 @@
$(call add_json_str, TotSepolicyVersion, $(TOT_SEPOLICY_VERSION))
$(call add_json_list, PlatformSepolicyCompatVersions, $(PLATFORM_SEPOLICY_COMPAT_VERSIONS))
-$(call add_json_bool, Flatten_apex, $(filter true,$(TARGET_FLATTEN_APEX)))
$(call add_json_bool, ForceApexSymlinkOptimization, $(filter true,$(TARGET_FORCE_APEX_SYMLINK_OPTIMIZATION)))
$(call add_json_str, DexpreoptGlobalConfig, $(DEX_PREOPT_CONFIG))
@@ -265,8 +264,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
@@ -319,7 +316,7 @@
$(call add_json_list, BuildVersionTags, $(BUILD_VERSION_TAGS))
$(call add_json_str, ReleaseVersion, $(_RELEASE_VERSION))
-$(call add_json_list, ReleaseDeviceConfigValueSets, $(RELEASE_DEVICE_CONFIG_VALUE_SETS))
+$(call add_json_list, ReleaseAconfigValueSets, $(RELEASE_ACONFIG_VALUE_SETS))
$(call json_end)
diff --git a/core/tasks/art-host-tests.mk b/core/tasks/art-host-tests.mk
index ff9eb09..c95f6e7 100644
--- a/core/tasks/art-host-tests.mk
+++ b/core/tasks/art-host-tests.mk
@@ -50,7 +50,8 @@
grep $(TARGET_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/target.list || true
$(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host.list \
-P target -C $(PRODUCT_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/target.list \
- -P host/testcases -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list
+ -P host/testcases -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list \
+ -sha256
grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/host.list > $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list || true
grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/target.list > $(PRIVATE_INTERMEDIATES_DIR)/target-test-configs.list || true
$(hide) $(SOONG_ZIP) -d -o $(PRIVATE_art_host_tests_configs_zip) \
diff --git a/core/tasks/sts-lite.mk b/core/tasks/sts-lite.mk
index dee25d4..65c65c3 100644
--- a/core/tasks/sts-lite.mk
+++ b/core/tasks/sts-lite.mk
@@ -29,7 +29,8 @@
$(ZIP2ZIP) -i $(STS_LITE_ZIP) -o $(STS_LITE_ZIP)_filtered \
-x android-sts-lite/tools/sts-tradefed-tests.jar \
'android-sts-lite/tools/*:sts-test/libs/' \
- 'android-sts-lite/testcases/*:sts-test/utils/'
+ 'android-sts-lite/testcases/*:sts-test/utils/' \
+ 'android-sts-lite/jdk/**/*:sts-test/jdk/'
$(MERGE_ZIPS) $@ $(STS_LITE_ZIP)_filtered $(STS_SDK_SAMPLES)
rm -f $(STS_LITE_ZIP)_filtered
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 4a42783..c107254 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -103,7 +103,7 @@
# It must be of the form "YYYY-MM-DD" on production devices.
# It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
# If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
- PLATFORM_SECURITY_PATCH := 2023-05-05
+ PLATFORM_SECURITY_PATCH := 2023-06-05
endif
include $(BUILD_SYSTEM)/version_util.mk
diff --git a/envsetup.sh b/envsetup.sh
index d292dbb..f4755dd 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -62,8 +62,8 @@
invocations of 'm' etc.
- tapas: tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
Sets up the build environment for building unbundled apps (APKs).
-- banchan: banchan <module1> [<module2> ...] [arm|x86|arm64|x86_64|arm64_only|x86_64only] \
- [eng|userdebug|user]
+- banchan: banchan <module1> [<module2> ...] \
+ [arm|x86|arm64|riscv64|x86_64|arm64_only|x86_64only] [eng|userdebug|user]
Sets up the build environment for building unbundled modules (APEXes).
- croot: Changes directory to the top of the tree, or a subdirectory thereof.
- m: Makes from the top of the tree.
@@ -952,9 +952,9 @@
function banchan()
{
local showHelp="$(echo $* | xargs -n 1 echo | \grep -E '^(help)$' | xargs)"
- local product="$(echo $* | xargs -n 1 echo | \grep -E '^(.*_)?(arm|x86|arm64|x86_64|arm64only|x86_64only)$' | xargs)"
+ local product="$(echo $* | xargs -n 1 echo | \grep -E '^(.*_)?(arm|x86|arm64|riscv64|x86_64|arm64only|x86_64only)$' | xargs)"
local variant="$(echo $* | xargs -n 1 echo | \grep -E '^(user|userdebug|eng)$' | xargs)"
- local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|(.*_)?(arm|x86|arm64|x86_64))$' | xargs)"
+ local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|(.*_)?(arm|x86|arm64|riscv64|x86_64))$' | xargs)"
if [ "$showHelp" != "" ]; then
$(gettop)/build/make/banchanHelp.sh
@@ -980,6 +980,7 @@
arm) product=module_arm;;
x86) product=module_x86;;
arm64) product=module_arm64;;
+ riscv64) product=module_riscv64;;
x86_64) product=module_x86_64;;
arm64only) product=module_arm64only;;
x86_64only) product=module_x86_64only;;
diff --git a/packaging/distdir.mk b/packaging/distdir.mk
index c9508af..153ecf6 100644
--- a/packaging/distdir.mk
+++ b/packaging/distdir.mk
@@ -30,6 +30,7 @@
$(eval _dist_$$(goal):)))
define copy-one-dist-file
+$(2): .KATI_TAGS += ;rule_name=dist-cp
$(2): $(1)
@echo "Dist: $$@"
rm -f $$@
diff --git a/target/board/module_riscv64/BoardConfig.mk b/target/board/module_riscv64/BoardConfig.mk
new file mode 100644
index 0000000..8bc1999
--- /dev/null
+++ b/target/board/module_riscv64/BoardConfig.mk
@@ -0,0 +1,22 @@
+# 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.
+#
+
+TARGET_ARCH := riscv64
+TARGET_ARCH_VARIANT :=
+TARGET_CPU_VARIANT := generic
+TARGET_CPU_ABI := riscv64
+
+# Temporary hack while prebuilt modules are missing riscv64.
+ALLOW_MISSING_DEPENDENCIES := true
diff --git a/target/board/module_riscv64/README.md b/target/board/module_riscv64/README.md
new file mode 100644
index 0000000..edebaa9
--- /dev/null
+++ b/target/board/module_riscv64/README.md
@@ -0,0 +1,2 @@
+This device is suitable for an unbundled module targeted specifically to a
+riscv64 device. This is a 64-bit only device (no 32-bit support).
diff --git a/target/product/AndroidProducts.mk b/target/product/AndroidProducts.mk
index 133dc73..473a275 100644
--- a/target/product/AndroidProducts.mk
+++ b/target/product/AndroidProducts.mk
@@ -83,6 +83,7 @@
$(LOCAL_DIR)/module_arm.mk \
$(LOCAL_DIR)/module_arm64.mk \
$(LOCAL_DIR)/module_arm64only.mk \
+ $(LOCAL_DIR)/module_riscv64.mk \
$(LOCAL_DIR)/module_x86.mk \
$(LOCAL_DIR)/module_x86_64.mk \
$(LOCAL_DIR)/module_x86_64only.mk \
diff --git a/target/product/cfi-common.mk b/target/product/cfi-common.mk
index 11c01a2..5cc7ae5 100644
--- a/target/product/cfi-common.mk
+++ b/target/product/cfi-common.mk
@@ -28,11 +28,12 @@
hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib \
hardware/synaptics/wlan/synadhd/wpa_supplicant_8_lib \
hardware/interfaces/nfc \
+ hardware/qcom/wlan/qcwcn/wpa_supplicant_8_lib \
hardware/qcom/wlan/legacy/qcwcn/wpa_supplicant_8_lib \
hardware/qcom/wlan/wcn6740/qcwcn/wpa_supplicant_8_lib \
hardware/interfaces/keymaster \
hardware/interfaces/security \
- packages/modules/Bluetooth/system \
+ packages/modules/Bluetooth \
system/chre \
system/core/libnetutils \
system/libziparchive \
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index 3b97792..e39af92 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 \
@@ -91,3 +80,6 @@
# Additional settings used in all GSI builds
PRODUCT_PRODUCT_PROPERTIES += \
ro.crypto.metadata_init_delete_all_keys.enabled=false \
+
+# Window Extensions
+$(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
\ No newline at end of file
diff --git a/target/product/module_riscv64.mk b/target/product/module_riscv64.mk
new file mode 100644
index 0000000..4fd38c0
--- /dev/null
+++ b/target/product/module_riscv64.mk
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/module_common.mk)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
+
+PRODUCT_NAME := module_riscv64
+PRODUCT_DEVICE := module_riscv64
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/Android.bp b/tools/aconfig/Android.bp
index 25424c5..5b7234e 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -27,6 +27,9 @@
"libserde_json",
"libtinytemplate",
],
+ proc_macros: [
+ "libpaste",
+ ]
}
rust_binary_host {
@@ -44,31 +47,31 @@
// integration tests: java
-device_config_definitions {
+aconfig_declarations {
name: "aconfig.test.flags",
- namespace: "com.android.aconfig.test",
+ package: "com.android.aconfig.test",
srcs: ["tests/test.aconfig"],
}
-device_config_values {
+aconfig_values {
name: "aconfig.test.flag.values",
- namespace: "com.android.aconfig.test",
+ package: "com.android.aconfig.test",
srcs: [
"tests/first.values",
"tests/second.values",
],
}
-device_config_value_set {
+aconfig_value_set {
name: "aconfig.test.flag.value_set",
values: [
"aconfig.test.flag.values",
],
}
-java_device_config_definitions_library {
+java_aconfig_library {
name: "aconfig_test_java",
- device_config_definitions: "aconfig.test.flags",
+ aconfig_declarations: "aconfig.test.flags",
}
android_test {
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index b3c73b8..941b30d 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -11,6 +11,7 @@
[dependencies]
anyhow = "1.0.69"
clap = { version = "4.1.8", features = ["derive"] }
+paste = "1.0.11"
protobuf = "3.2.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
new file mode 100644
index 0000000..86124dd
--- /dev/null
+++ b/tools/aconfig/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ // Ensure changes on aconfig auto generated library is compatible with
+ // test testing filtering logic. Breakage on this test means all tests
+ // that using the flag annotations to do filtering will get affected.
+ "name": "FlagAnnotationTests",
+ "options": [
+ {
+ "include-filter": "android.cts.flags.tests.FlagAnnotationTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index 9f6424f..4cad69a 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -35,21 +35,22 @@
// 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;
+ repeated string bug = 4;
};
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,19 +61,20 @@
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;
- repeated tracepoint trace = 7;
+ optional string package = 1;
+ optional string name = 2;
+ optional string namespace = 3;
+ optional string description = 4;
+ repeated string bug = 5;
+ optional flag_state state = 6;
+ optional flag_permission permission = 7;
+ repeated tracepoint trace = 8;
}
message parsed_flags {
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.rs b/tools/aconfig/src/codegen.rs
index d96d4f9..b7fb08f 100644
--- a/tools/aconfig/src/codegen.rs
+++ b/tools/aconfig/src/codegen.rs
@@ -32,12 +32,15 @@
}
pub fn is_valid_package_ident(s: &str) -> bool {
+ if !s.contains('.') {
+ return false;
+ }
s.split('.').all(is_valid_name_ident)
}
pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
ensure!(is_valid_package_ident(package), "bad package");
- ensure!(is_valid_package_ident(flag_name), "bad flag name");
+ ensure!(is_valid_name_ident(flag_name), "bad flag name");
Ok(format!("{}.{}", package, flag_name))
}
@@ -61,12 +64,13 @@
#[test]
fn test_is_valid_package_ident() {
- assert!(is_valid_package_ident("foo"));
- assert!(is_valid_package_ident("foo_bar_123"));
assert!(is_valid_package_ident("foo.bar"));
+ assert!(is_valid_package_ident("foo.bar_baz"));
assert!(is_valid_package_ident("foo.bar.a123"));
- assert!(!is_valid_package_ident("foo._bar"));
+ assert!(!is_valid_package_ident("foo_bar_123"));
+ assert!(!is_valid_package_ident("foo"));
+ assert!(!is_valid_package_ident("foo._bar"));
assert!(!is_valid_package_ident(""));
assert!(!is_valid_package_ident("123_foo"));
assert!(!is_valid_package_ident("foo-bar"));
@@ -75,6 +79,7 @@
assert!(!is_valid_package_ident(".foo.bar"));
assert!(!is_valid_package_ident("foo.bar."));
assert!(!is_valid_package_ident("."));
+ assert!(!is_valid_package_ident(".."));
assert!(!is_valid_package_ident("foo..bar"));
assert!(!is_valid_package_ident("foo.__bar"));
}
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 4f82220..8ab6ffa 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -19,20 +19,24 @@
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::commands::{CodegenMode, 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,
+ codegen_mode: CodegenMode,
+) -> 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);
- let context = Context { package_name: package.to_string(), is_read_write, class_elements };
-
- let java_files = vec!["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"];
-
+ 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 is_test_mode = codegen_mode == CodegenMode::Test;
+ let context =
+ Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() };
let mut template = TinyTemplate::new();
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
template.add_template(
@@ -45,7 +49,7 @@
)?;
let path: PathBuf = package.split('.').collect();
- java_files
+ ["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]
.iter()
.map(|file| {
Ok(OutputFile {
@@ -58,14 +62,15 @@
#[derive(Serialize)]
struct Context {
- pub package_name: String,
- pub is_read_write: bool,
pub class_elements: Vec<ClassElement>,
+ pub is_test_mode: bool,
+ pub is_read_write: bool,
+ pub package_name: String,
}
#[derive(Serialize)]
struct ClassElement {
- pub default_value: String,
+ pub default_value: bool,
pub device_config_namespace: String,
pub device_config_flag: String,
pub flag_name_constant_suffix: String,
@@ -73,20 +78,16 @@
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 {
- "true".to_string()
- } else {
- "false".to_string()
- },
- device_config_namespace: item.namespace.clone(),
+ default_value: pf.state() == ProtoFlagState::ENABLED,
+ 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: format_java_method_name(&item.name),
+ 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()),
}
}
@@ -111,29 +112,50 @@
use super::*;
use std::collections::HashMap;
- #[test]
- fn test_generate_java_code() {
- let cache = crate::test::create_cache();
- let generated_files = generate_java_code(&cache).unwrap();
- let expect_flags_content = r#"
- package com.android.aconfig.test;
- public final class Flags {
- public static boolean disabledRo() {
- return FEATURE_FLAGS.disabledRo();
- }
- public static boolean disabledRw() {
- return FEATURE_FLAGS.disabledRw();
- }
- public static boolean enabledRo() {
- return FEATURE_FLAGS.enabledRo();
- }
- public static boolean enabledRw() {
- return FEATURE_FLAGS.enabledRw();
- }
- private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
+ package com.android.aconfig.test;
+ public interface FeatureFlags {
+ boolean disabledRo();
+ boolean disabledRw();
+ boolean enabledRo();
+ boolean enabledRw();
+ }"#;
+ const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
+ package com.android.aconfig.test;
+ public final class Flags {
+ 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 disabledRw() {
+ return FEATURE_FLAGS.disabledRw();
+ }
+ public static boolean enabledRo() {
+ return FEATURE_FLAGS.enabledRo();
+ }
+ public static boolean enabledRw() {
+ return FEATURE_FLAGS.enabledRw();
+ }
+ "#;
+
+ #[test]
+ fn test_generate_java_code_production() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let generated_files = generate_java_code(
+ crate::test::TEST_PACKAGE,
+ parsed_flags.parsed_flag.iter(),
+ CodegenMode::Production,
+ )
+ .unwrap();
+ let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ + r#"
+ private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ }"#;
let expected_featureflagsimpl_content = r#"
package com.android.aconfig.test;
import android.provider.DeviceConfig;
@@ -164,19 +186,108 @@
}
}
"#;
- let expected_featureflags_content = r#"
+ let mut file_set = HashMap::from([
+ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
+ ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
+ ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
+ ]);
+
+ for file in generated_files {
+ let file_path = file.path.to_str().unwrap();
+ assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
+ assert_eq!(
+ None,
+ crate::test::first_significant_code_diff(
+ file_set.get(file_path).unwrap(),
+ &String::from_utf8(file.contents.clone()).unwrap()
+ ),
+ "File {} content is not correct",
+ file_path
+ );
+ file_set.remove(file_path);
+ }
+
+ assert!(file_set.is_empty());
+ }
+
+ #[test]
+ fn test_generate_java_code_test() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let generated_files = generate_java_code(
+ crate::test::TEST_PACKAGE,
+ parsed_flags.parsed_flag.iter(),
+ CodegenMode::Test,
+ )
+ .unwrap();
+ let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ + r#"
+ public static void setFeatureFlagsImpl(FeatureFlags featureFlags) {
+ Flags.FEATURE_FLAGS = featureFlags;
+ }
+ public static void unsetFeatureFlagsImpl() {
+ Flags.FEATURE_FLAGS = null;
+ }
+ private static FeatureFlags FEATURE_FLAGS;
+ }
+ "#;
+ let expected_featureflagsimpl_content = r#"
package com.android.aconfig.test;
- public interface FeatureFlags {
- boolean disabledRo();
- boolean disabledRw();
- boolean enabledRo();
- boolean enabledRw();
+ import static java.util.stream.Collectors.toMap;
+ import java.util.HashMap;
+ import java.util.Map;
+ import java.util.stream.Stream;
+ public final class FeatureFlagsImpl implements FeatureFlags {
+ @Override
+ public boolean disabledRo() {
+ return getFlag(Flags.FLAG_DISABLED_RO);
+ }
+ @Override
+ public boolean disabledRw() {
+ return getFlag(Flags.FLAG_DISABLED_RW);
+ }
+ @Override
+ public boolean enabledRo() {
+ return getFlag(Flags.FLAG_ENABLED_RO);
+ }
+ @Override
+ public boolean enabledRw() {
+ return getFlag(Flags.FLAG_ENABLED_RW);
+ }
+ public void setFlag(String flagName, boolean value) {
+ if (!this.mFlagMap.containsKey(flagName)) {
+ throw new IllegalArgumentException("no such flag" + flagName);
+ }
+ this.mFlagMap.put(flagName, value);
+ }
+ public void resetAll() {
+ for (Map.Entry entry : mFlagMap.entrySet()) {
+ entry.setValue(null);
+ }
+ }
+ private boolean getFlag(String flagName) {
+ Boolean value = this.mFlagMap.get(flagName);
+ if (value == null) {
+ throw new IllegalArgumentException(flagName + " is not set");
+ }
+ return value;
+ }
+ private HashMap<String, Boolean> mFlagMap = Stream.of(
+ Flags.FLAG_DISABLED_RO,
+ Flags.FLAG_DISABLED_RW,
+ Flags.FLAG_ENABLED_RO,
+ Flags.FLAG_ENABLED_RW
+ )
+ .collect(
+ HashMap::new,
+ (map, elem) -> map.put(elem, null),
+ HashMap::putAll
+ );
}
"#;
let mut file_set = HashMap::from([
- ("com/android/aconfig/test/Flags.java", expect_flags_content),
+ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
- ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content),
+ ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
]);
for file in generated_files {
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..dd2087b 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -14,105 +14,167 @@
* 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.bug.append(&mut flag_declaration.bug);
+ 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)
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
+pub enum CodegenMode {
+ Production,
+ Test,
+}
+
+pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> 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(), codegen_mode)
+}
+
+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 +182,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());
@@ -143,179 +209,148 @@
Text,
Debug,
Protobuf,
+ Textproto,
}
-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)?;
+ }
+ DumpFormat::Textproto => {
+ let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
+ output.extend_from_slice(s.as_bytes());
}
}
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 expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
+ crate::test::TEST_FLAGS_TEXTPROTO,
+ )
+ .unwrap()
+ .write_to_bytes()
+ .unwrap();
- let caches = vec![create_test_cache_com_example()];
- let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
- let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
+ let input = parse_test_flags_as_input();
+ let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf).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]);
+ assert_eq!(expected, actual);
}
#[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()
- ]
- );
+ fn test_dump_textproto_format() {
+ let input = parse_test_flags_as_input();
+ let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto).unwrap();
+ let text = std::str::from_utf8(&bytes).unwrap();
+ assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+ }
- 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..e20c60c 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::{CodegenMode, DumpFormat, Input, OutputFile};
fn cli() -> Command {
Command::new("aconfig")
@@ -52,7 +49,13 @@
.subcommand(
Command::new("create-java-lib")
.arg(Arg::new("cache").long("cache").required(true))
- .arg(Arg::new("out").long("out").required(true)),
+ .arg(Arg::new("out").long("out").required(true))
+ .arg(
+ Arg::new("mode")
+ .long("mode")
+ .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .default_value("production"),
+ ),
)
.subcommand(
Command::new("create-cpp-lib")
@@ -100,11 +103,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 +148,47 @@
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 cache = open_single_file(sub_matches, "cache")?;
+ let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
+ let generated_files = commands::create_java_lib(cache, *mode)?;
let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
- let generated_files = commands::create_java_lib(cache)?;
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..17019be 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -28,70 +28,701 @@
// ---- 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 ----
-use anyhow::Result;
+pub use auto_generated::*;
-pub fn try_from_text_proto<T>(s: &str) -> Result<T>
+use anyhow::Result;
+use paste::paste;
+
+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())
}
+
+macro_rules! ensure_required_fields {
+ ($type:expr, $struct:expr, $($field:expr),+) => {
+ $(
+ paste! {
+ ensure!($struct.[<has_ $field>](), "bad {}: missing {}", $type, $field);
+ }
+ )+
+ };
+}
+
+pub mod flag_declaration {
+ use super::*;
+ use crate::codegen;
+ use anyhow::ensure;
+
+ pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> {
+ ensure_required_fields!("flag declaration", pdf, "name", "namespace", "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");
+
+ // ProtoFlagDeclaration.bug: Vec<String>: may be empty, no checks needed
+
+ 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_required_fields!("flag declarations", pdf, "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_required_fields!("flag value", fv, "package", "name", "state", "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_required_fields!("tracepoint", tp, "source", "state", "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_required_fields!(
+ "parsed flag",
+ pf,
+ "package",
+ "name",
+ "namespace",
+ "description",
+ "state",
+ "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)?;
+ }
+
+ // ProtoParsedFlag.bug: Vec<String>: may be empty, no checks needed
+
+ 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."
+ bug: "123"
+ bug: "abc"
+}
+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.");
+ assert_eq!(first.bug.len(), 2);
+ assert_eq!(first.bug[0], "123");
+ assert_eq!(first.bug[1], "abc");
+ 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.");
+ assert_eq!(second.bug.len(), 0);
+
+ // 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.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.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.bbb.first comes before aaa.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..04bbe28 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -16,29 +16,111 @@
#[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 const TEST_FLAGS_TEXTPROTO: &str = r#"
+parsed_flag {
+ package: "com.android.aconfig.test"
+ name: "disabled_ro"
+ namespace: "aconfig_test"
+ description: "This flag is DISABLED + READ_ONLY"
+ bug: "123"
+ state: DISABLED
+ permission: READ_ONLY
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_WRITE
+ }
+ trace {
+ source: "tests/first.values"
+ state: DISABLED
+ permission: READ_ONLY
+ }
+}
+parsed_flag {
+ package: "com.android.aconfig.test"
+ name: "disabled_rw"
+ namespace: "aconfig_test"
+ description: "This flag is DISABLED + READ_WRITE"
+ bug: "456"
+ state: DISABLED
+ permission: READ_WRITE
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_WRITE
+ }
+}
+parsed_flag {
+ package: "com.android.aconfig.test"
+ name: "enabled_ro"
+ namespace: "aconfig_test"
+ description: "This flag is ENABLED + READ_ONLY"
+ bug: "789"
+ bug: "abc"
+ state: ENABLED
+ permission: READ_ONLY
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_WRITE
+ }
+ trace {
+ source: "tests/first.values"
+ state: DISABLED
+ permission: READ_WRITE
+ }
+ trace {
+ source: "tests/second.values"
+ state: ENABLED
+ permission: READ_ONLY
+ }
+}
+parsed_flag {
+ package: "com.android.aconfig.test"
+ name: "enabled_rw"
+ namespace: "aconfig_test"
+ description: "This flag is ENABLED + READ_WRITE"
+ state: ENABLED
+ permission: READ_WRITE
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_WRITE
+ }
+ trace {
+ source: "tests/first.values"
+ state: ENABLED
+ permission: READ_WRITE
+ }
+}
+"#;
+
+ 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/FeatureFlags.java.template b/tools/aconfig/templates/FeatureFlags.java.template
index b9e2cc7..e0f201f 100644
--- a/tools/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/templates/FeatureFlags.java.template
@@ -1,7 +1,7 @@
package {package_name};
public interface FeatureFlags \{
- {{ for item in class_elements}}
+{{ for item in class_elements}}
boolean {item.method_name}();
- {{ endfor }}
-}
\ No newline at end of file
+{{ endfor }}
+}
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 2b031f1..082d476 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,20 +1,65 @@
package {package_name};
-{{ if is_read_write }}
+{{ -if is_test_mode }}
+import static java.util.stream.Collectors.toMap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+{{ else}}
+{{ if is_read_write- }}
import android.provider.DeviceConfig;
+{{ -endif- }}
{{ endif }}
public final class FeatureFlagsImpl implements FeatureFlags \{
- {{ for item in class_elements}}
+{{ for item in class_elements}}
@Override
public boolean {item.method_name}() \{
- {{ if item.is_read_write- }}
+ {{ -if not is_test_mode- }}
+ {{ if item.is_read_write }}
return DeviceConfig.getBoolean(
"{item.device_config_namespace}",
"{item.device_config_flag}",
{item.default_value}
);
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
+ {{ -endif- }}
+ {{ else }}
+ return getFlag(Flags.FLAG_{item.flag_name_constant_suffix});
{{ -endif }}
}
- {{ endfor }}
-}
\ No newline at end of file
+{{ endfor- }}
+{{ if is_test_mode }}
+ public void setFlag(String flagName, boolean value) \{
+ if (!this.mFlagMap.containsKey(flagName)) \{
+ throw new IllegalArgumentException("no such flag" + flagName);
+ }
+ this.mFlagMap.put(flagName, value);
+ }
+
+ public void resetAll() \{
+ for (Map.Entry entry : mFlagMap.entrySet()) \{
+ entry.setValue(null);
+ }
+ }
+
+ private boolean getFlag(String flagName) \{
+ Boolean value = this.mFlagMap.get(flagName);
+ if (value == null) \{
+ throw new IllegalArgumentException(flagName + " is not set");
+ }
+ return value;
+ }
+
+ private HashMap<String, Boolean> mFlagMap = Stream.of(
+ {{-for item in class_elements}}
+ Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }}
+ {{ -endfor }}
+ )
+ .collect(
+ HashMap::new,
+ (map, elem) -> map.put(elem, null),
+ HashMap::putAll
+ );
+{{ -endif }}
+}
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 752a469..c244b15 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -1,11 +1,23 @@
package {package_name};
public final class Flags \{
- {{ for item in class_elements}}
+{{- 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}();
}
- {{ endfor }}
- private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+{{ endfor }}
+{{ -if is_test_mode }}
+ public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{
+ Flags.FEATURE_FLAGS = featureFlags;
+ }
+
+ public static void unsetFeatureFlagsImpl() \{
+ Flags.FEATURE_FLAGS = null;
+ }
+{{ endif}}
+ private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }};
}
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/test.aconfig b/tools/aconfig/tests/test.aconfig
index d09396a..a8f6652 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -7,6 +7,7 @@
name: "disabled_ro"
namespace: "aconfig_test"
description: "This flag is DISABLED + READ_ONLY"
+ bug: "123"
}
# This flag's final value is calculated from:
@@ -15,6 +16,7 @@
name: "disabled_rw"
namespace: "aconfig_test"
description: "This flag is DISABLED + READ_WRITE"
+ bug: "456"
}
# This flag's final value is calculated from:
@@ -25,6 +27,8 @@
name: "enabled_ro"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_ONLY"
+ bug: "789"
+ bug: "abc"
}
# This flag's final value is calculated from:
@@ -34,4 +38,5 @@
name: "enabled_rw"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_WRITE"
+ # no bug field: bug is not mandatory
}
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/list_files.py b/tools/list_files.py
index 3afa81f..4f666aa 100644
--- a/tools/list_files.py
+++ b/tools/list_files.py
@@ -18,6 +18,7 @@
from glob import glob
from pathlib import Path
from os.path import join, relpath
+from itertools import chain
import argparse
class FileLister:
@@ -27,7 +28,8 @@
self.folder_dir = args.dir
self.extensions = [e if e.startswith(".") else "." + e for e in args.extensions]
self.root = args.root
- self.files_list = list()
+ self.files_list : List[str] = list()
+ self.classes = args.classes
def get_files(self) -> None:
"""Get all files directory in the input directory including the files in the subdirectories
@@ -61,6 +63,26 @@
def list(self) -> None:
self.get_files()
self.files_list = [f for f in self.files_list if not self.extensions or Path(f).suffix in self.extensions]
+
+ # If files_list is as below:
+ # A/B/C.java
+ # A/B/D.java
+ # A/B/E.txt
+ # --classes flag converts files_list in the following format:
+ # A/B/C.class
+ # A/B/C$*.class
+ # A/B/D.class
+ # A/B/D$*.class
+ # Additional `$*`-suffixed line is appended after each line
+ # to take multiple top level classes in a single java file into account.
+ # Note that non-java files in files_list are filtered out.
+ if self.classes:
+ self.files_list = list(chain.from_iterable([
+ (class_files := str(Path(ff).with_suffix(".class")),
+ class_files.replace(".class", "$*.class"))
+ for ff in self.files_list if ff.endswith(".java")
+ ]))
+
self.write()
def write(self) -> None:
@@ -95,6 +117,10 @@
help="optional directory to replace the root directories of output.")
parser.add_argument('--extensions', nargs='*', default=list(), dest='extensions',
help="Extensions to include in the output. If not set, all files are included")
+ parser.add_argument('--classes', dest='classes', action=argparse.BooleanOptionalAction,
+ help="Optional flag. If passed, outputs a list of pattern of class files \
+ that will be produced by compiling java files in the input dir. \
+ Non-java files in the input directory will be ignored.")
args = parser.parse_args()
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index a76dc8a..7a2dcb7 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -99,9 +99,8 @@
"releasetools_common",
],
required: [
+ "apexd_host",
"checkvintf",
- "deapexer",
- "dump_apex_info",
],
}
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index 906ad1f..33624f5 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -129,8 +129,9 @@
dirmap = GetDirmap(input_tmp)
- # Simulate apexd from target-files.
- dirmap['/apex'] = PrepareApexDirectory(input_tmp)
+ # Simulate apexd with target-files.
+ # add a mapping('/apex' => ${input_tmp}/APEX) to dirmap
+ PrepareApexDirectory(input_tmp, dirmap)
args_for_skus = GetArgsForSkus(info_dict)
shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
@@ -204,7 +205,8 @@
return patterns
-def PrepareApexDirectory(inp):
+
+def PrepareApexDirectory(inp, dirmap):
""" Prepare /apex directory before running checkvintf
Apex binaries do not support dirmaps, in order to use these binaries we
@@ -212,93 +214,25 @@
expected device locations.
This simulates how apexd activates APEXes.
- 1. create {inp}/APEX which is treated as a "/" on device.
- 2. copy apexes from target-files to {root}/{partition}/apex.
- 3. mount apexes under {root}/{partition}/apex at {root}/apex.
- 4. generate info files with dump_apex_info.
-
- We'll get the following layout
- {inp}/APEX/apex # Activated APEXes + some info files
- {inp}/APEX/system/apex # System APEXes
- {inp}/APEX/vendor/apex # Vendor APEXes
- ...
-
- Args:
- inp: path to the directory that contains the extracted target files archive.
-
- Returns:
- directory representing /apex on device
+ 1. create {inp}/APEX which is treated as a "/apex" on device.
+ 2. invoke apexd_host with vendor APEXes.
"""
- deapexer = 'deapexer'
- debugfs_path = 'debugfs'
- 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')
- fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
- if os.path.isfile(deapexer_path):
- deapexer = deapexer_path
-
- def ExtractApexes(path, outp):
- # Extract all APEXes found in input path.
- logger.info('Extracting APEXs in %s', path)
- for f in os.listdir(path):
- logger.info(' adding APEX %s', os.path.basename(f))
- apex = os.path.join(path, f)
- if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
- info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
- # Flattened APEXes may have symlinks for libs (linked to /system/lib)
- # We need to blindly copy them all.
- shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
- elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
- cmd = [deapexer,
- '--debugfs_path', debugfs_path,
- 'info',
- apex]
- info = json.loads(common.RunAndCheckOutput(cmd))
-
- cmd = [deapexer,
- '--debugfs_path', debugfs_path,
- '--fsckerofs_path', fsckerofs_path,
- 'extract',
- apex,
- os.path.join(outp, info['name'])]
- common.RunAndCheckOutput(cmd)
- else:
- logger.info(' .. skipping %s (is it APEX?)', path)
-
- root_dir_name = 'APEX'
- root_dir = os.path.join(inp, root_dir_name)
- extracted_root = os.path.join(root_dir, 'apex')
+ apex_dir = os.path.join(inp, 'APEX')
+ # checkvintf needs /apex dirmap
+ dirmap['/apex'] = apex_dir
# Always create /apex directory for dirmap
- os.makedirs(extracted_root)
+ os.makedirs(apex_dir)
- create_info_file = False
+ # Invoke apexd_host to activate vendor APEXes for checkvintf
+ apex_host = os.path.join(OPTIONS.search_path, 'bin', 'apexd_host')
+ cmd = [apex_host, '--tool_path', OPTIONS.search_path]
+ cmd += ['--apex_path', dirmap['/apex']]
+ if '/vendor' in dirmap:
+ cmd += ['--vendor_path', dirmap['/vendor']]
+ common.RunAndCheckOutput(cmd)
- # Loop through search path looking for and processing apex/ directories.
- for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
- # checkvintf only needs vendor apexes. skip other partitions for efficiency
- if device_path not in ['/vendor', '/odm']:
- continue
- # First, copy VENDOR/apex/foo.apex to APEX/vendor/apex/foo.apex
- # Then, extract the contents to APEX/apex/foo/
- for target_files_rel_path in target_files_rel_paths:
- inp_partition = os.path.join(inp, target_files_rel_path,"apex")
- if os.path.exists(inp_partition):
- apex_dir = root_dir + os.path.join(device_path + "/apex");
- os.makedirs(root_dir + device_path)
- shutil.copytree(inp_partition, apex_dir, symlinks=True)
- ExtractApexes(apex_dir, extracted_root)
- create_info_file = True
-
- if create_info_file:
- ### Dump apex info files
- dump_cmd = ['dump_apex_info', '--root_dir', root_dir]
- common.RunAndCheckOutput(dump_cmd)
-
- return extracted_root
def CheckVintfFromTargetFiles(inp, info_dict=None):
"""
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index f00c1a9..091121f 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -35,6 +35,7 @@
import shutil
import subprocess
import sys
+import stat
import tempfile
import threading
import time
@@ -1423,9 +1424,10 @@
def ResolveBinaryPath(path):
if os.path.exists(path):
return path
- new_path = os.path.join(OPTIONS.search_path, path)
- if os.path.exists(new_path):
- return new_path
+ if OPTIONS.search_path:
+ new_path = os.path.join(OPTIONS.search_path, path)
+ if os.path.exists(new_path):
+ return new_path
raise ExternalError(
"Failed to find {}".format(new_path))
@@ -2114,6 +2116,26 @@
shutil.copyfileobj(in_file, out_file)
+def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
+ # According to https://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip/6297838#6297838
+ # higher bits of |external_attr| are unix file permission and types
+ unix_filetype = info.external_attr >> 16
+
+ def CheckMask(a, mask):
+ return (a & mask) == mask
+
+ def IsSymlink(a):
+ return CheckMask(a, stat.S_IFLNK)
+ # python3.11 zipfile implementation doesn't handle symlink correctly
+ if not IsSymlink(unix_filetype):
+ return input_zip.extract(info, dirname)
+ if dirname is None:
+ dirname = os.getcwd()
+ target = os.path.join(dirname, info.filename)
+ os.makedirs(os.path.dirname(target), exist_ok=True)
+ os.symlink(input_zip.read(info).decode(), target)
+
+
def UnzipToDir(filename, dirname, patterns=None):
"""Unzips the archive to the given directory.
@@ -2159,9 +2181,11 @@
# There isn't any matching files. Don't unzip anything.
if not filtered:
return
- input_zip.extractall(dirname, filtered)
+ for info in filtered:
+ UnzipSingleFile(input_zip, info, dirname)
else:
- input_zip.extractall(dirname, entries)
+ for info in entries:
+ UnzipSingleFile(input_zip, info, dirname)
def UnzipTemp(filename, patterns=None):
@@ -2427,12 +2451,22 @@
try:
return int(version)
except ValueError:
- # Not a decimal number. Codename?
- if version in codename_to_api_level_map:
- return codename_to_api_level_map[version]
+ # Not a decimal number.
+ #
+ # It could be either a straight codename, e.g.
+ # UpsideDownCake
+ #
+ # Or a codename with API fingerprint SHA, e.g.
+ # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
+ #
+ # Extract the codename and try and map it to a version number.
+ split = version.split(".")
+ codename = split[0]
+ if codename in codename_to_api_level_map:
+ return codename_to_api_level_map[codename]
raise ExternalError(
- "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
- version, codename_to_api_level_map))
+ "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
+ codename, version, codename_to_api_level_map))
def SignFile(input_name, output_name, key, password, min_api_level=None,
diff --git a/tools/releasetools/merge/Android.bp b/tools/releasetools/merge/Android.bp
index 219acf8..96ec73e 100644
--- a/tools/releasetools/merge/Android.bp
+++ b/tools/releasetools/merge/Android.bp
@@ -50,6 +50,7 @@
"releasetools_ota_from_target_files",
],
required: [
+ "apexd_host",
"checkvintf",
"host_init_verifier",
"secilc",
diff --git a/tools/releasetools/merge/merge_dexopt.py b/tools/releasetools/merge/merge_dexopt.py
index 1dd591b..1c0c743 100644
--- a/tools/releasetools/merge/merge_dexopt.py
+++ b/tools/releasetools/merge/merge_dexopt.py
@@ -72,7 +72,6 @@
# <contents of vendor dexpreopt_config.zip>
# system -> output/SYSTEM
# vendor -> output/VENDOR
- # apex -> output/SYSTEM/apex (only for flattened APEX builds)
# apex/ (extracted updatable APEX)
# <apex 1>/
# ...
@@ -114,68 +113,20 @@
os.path.join(output_target_files_dir, 'VENDOR'),
os.path.join(temp_dir, 'vendor'))
- # The directory structure for flatteded APEXes is:
- #
- # SYSTEM
- # apex
- # <APEX name, e.g., com.android.wifi>
- # apex_manifest.pb
- # apex_pubkey
- # etc/
- # javalib/
- # lib/
- # lib64/
- # priv-app/
- #
- # The directory structure for updatable APEXes is:
- #
- # SYSTEM
- # apex
- # com.android.adbd.apex
- # com.android.appsearch.apex
- # com.android.art.apex
- # ...
- apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
+ # Extract APEX.
+ logging.info('extracting APEX')
+ apex_extract_root_dir = os.path.join(temp_dir, 'apex')
+ os.makedirs(apex_extract_root_dir)
- # Check for flattended versus updatable APEX.
- if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
- # Extract APEX.
- logging.info('extracting APEX')
-
- apex_extract_root_dir = os.path.join(temp_dir, 'apex')
- os.makedirs(apex_extract_root_dir)
-
- for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
- glob.glob(os.path.join(apex_root, '*.capex'))):
- logging.info(' apex: %s', apex)
- # deapexer is in the same directory as the merge_target_files binary extracted
- # from otatools.zip.
- apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
- logging.info(' info: %s', apex_json_info)
- apex_info = json.loads(apex_json_info)
- apex_name = apex_info['name']
- logging.info(' name: %s', apex_name)
-
- apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
- os.makedirs(apex_extract_dir)
-
- # deapexer uses debugfs_static, which is part of otatools.zip.
- command = [
- 'deapexer',
- '--debugfs_path',
- 'debugfs_static',
- '--fsckerofs_path',
- 'fsck.erofs',
- 'extract',
- apex,
- apex_extract_dir,
- ]
- logging.info(' running %s', command)
- subprocess.check_call(command)
- else:
- # Flattened APEXes don't need to be extracted since they have the necessary
- # directory structure.
- os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
+ command = [
+ 'apexd_host',
+ '--system_path',
+ os.path.join(temp_dir, 'system'),
+ '--apex_path',
+ apex_extract_root_dir,
+ ]
+ logging.info(' running %s', command)
+ subprocess.check_call(command)
# Modify system config to point to the tools that have been extracted.
# Absolute or .. paths are not allowed by the dexpreopt_gen tool in
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 2dfd8c7..86fb480 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1642,6 +1642,7 @@
'gki_signing_algorithm': 'SHA256_RSA4096',
'gki_signing_signature_args': '--prop foo:bar',
}
+ common.OPTIONS.search_path = None
test_file = tempfile.NamedTemporaryFile()
self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
test_file.name, 'generic_kernel')
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