Merge "aconfig: Print flags with namespace, and sort them" into main
diff --git a/core/Makefile b/core/Makefile
index 7d7457e..6edac1a 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4600,10 +4600,18 @@
--prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
endif
-# Append avbpubkey of microdroid-vendor partition into vendor_boot partition.
-ifdef MICRODROID_VENDOR_AVBKEY
-BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
- --prop_from_file com.android.build.microdroid-vendor.avbpubkey:$(MICRODROID_VENDOR_AVBKEY)
+# Append root digest of microdroid-vendor partition's hashtree descriptor into vendor partition.
+ifdef MICRODROID_VENDOR_IMAGE_MODULE
+MICRODROID_VENDOR_IMAGE := \
+ $(call intermediates-dir-for,ETC,$(MICRODROID_VENDOR_IMAGE_MODULE))/$(MICRODROID_VENDOR_IMAGE_MODULE)
+MICRODROID_VENDOR_ROOT_DIGEST := $(PRODUCT_OUT)/microdroid_vendor_root_digest
+BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
+ --prop_from_file com.android.build.microdroid-vendor.root_digest:$(MICRODROID_VENDOR_ROOT_DIGEST)
+$(MICRODROID_VENDOR_ROOT_DIGEST): $(AVBTOOL) $(MICRODROID_VENDOR_IMAGE)
+ $(AVBTOOL) print_partition_digests \
+ --image $(MICRODROID_VENDOR_IMAGE) \
+ | tr -d '\n' | sed -E 's/.*: //g' > $@
+$(INSTALLED_VENDORIMAGE_TARGET): $(MICRODROID_VENDOR_ROOT_DIGEST)
endif
BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
@@ -5626,7 +5634,12 @@
FASTBOOT_INFO_VERSION = 1
INSTALLED_FASTBOOT_INFO_TARGET := $(PRODUCT_OUT)/fastboot-info.txt
-
+ifdef TARGET_BOARD_FASTBOOT_INFO_FILE
+$(INSTALLED_FASTBOOT_INFO_TARGET): $(TARGET_BOARD_FASTBOOT_INFO_FILE)
+ rm -f $@
+ $(call pretty,"Target fastboot-info.txt: $@")
+ $(hide) cp $< $@
+else
$(INSTALLED_FASTBOOT_INFO_TARGET):
rm -f $@
$(call pretty,"Target fastboot-info.txt: $@")
@@ -5685,6 +5698,7 @@
ifeq ($(BOARD_USES_METADATA_PARTITION),true)
$(hide) echo "if-wipe erase metadata" >> $@
endif
+endif
# -----------------------------------------------------------------
# misc_info.txt
@@ -6744,7 +6758,7 @@
$(BUILT_TARGET_FILES_PACKAGE): $(BUILT_TARGET_FILES_DIR)
@echo "Packaging target files: $@"
- $(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list
+ $(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list -sha256
.PHONY: target-files-package
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
diff --git a/core/base_rules.mk b/core/base_rules.mk
index f533358..254bfeb 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -121,9 +121,17 @@
$(LOCAL_PROPRIETARY_MODULE))
include $(BUILD_SYSTEM)/local_vndk.mk
-include $(BUILD_SYSTEM)/local_systemsdk.mk
+
+# local_current_sdk needs to run before local_systemsdk because the former may override
+# LOCAL_SDK_VERSION which is used by the latter.
include $(BUILD_SYSTEM)/local_current_sdk.mk
+# Check if the use of System SDK is correct. Note that, for Soong modules, the system sdk version
+# check is done in Soong. No need to do it twice.
+ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+include $(BUILD_SYSTEM)/local_systemsdk.mk
+endif
+
# Ninja has an implicit dependency on the command being run, and kati will
# regenerate the ninja manifest if any read makefile changes, so there is no
# need to have dependencies on makefiles.
@@ -541,13 +549,14 @@
# Only set up copy rules once, even if another arch variant shares it
my_vintf_new_pairs := $(filter-out $(ALL_VINTF_MANIFEST_FRAGMENTS_LIST),$(my_vintf_pairs))
- my_vintf_new_installed := $(call copy-many-vintf-manifest-files-checked,$(my_vintf_new_pairs))
-
ALL_VINTF_MANIFEST_FRAGMENTS_LIST += $(my_vintf_new_pairs)
- $(my_all_targets) : $(my_vintf_installed)
- # Install fragments together with the target
- $(LOCAL_INSTALLED_MODULE) : | $(my_vintf_installed)
+ ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+ $(call copy-many-vintf-manifest-files-checked,$(my_vintf_new_pairs))
+ $(my_all_targets) : $(my_vintf_installed)
+ # Install fragments together with the target
+ $(LOCAL_INSTALLED_MODULE) : | $(my_vintf_installed)
+ endif
endif # my_vintf_fragments
# Rule to install the module's companion init.rc.
@@ -579,13 +588,14 @@
# Make sure we only set up the copy rules once, even if another arch variant
# shares a common LOCAL_INIT_RC.
my_init_rc_new_pairs := $(filter-out $(ALL_INIT_RC_INSTALLED_PAIRS),$(my_init_rc_pairs))
- my_init_rc_new_installed := $(call copy-many-init-script-files-checked,$(my_init_rc_new_pairs))
-
ALL_INIT_RC_INSTALLED_PAIRS += $(my_init_rc_new_pairs)
- $(my_all_targets) : $(my_init_rc_installed)
- # Install init_rc together with the target
- $(LOCAL_INSTALLED_MODULE) : | $(my_init_rc_installed)
+ ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+ $(call copy-many-init-script-files-checked,$(my_init_rc_new_pairs))
+ $(my_all_targets) : $(my_init_rc_installed)
+ # Install init_rc together with the target
+ $(LOCAL_INSTALLED_MODULE) : | $(my_init_rc_installed)
+ endif
endif # my_init_rc
endif # !LOCAL_IS_HOST_MODULE
@@ -1007,46 +1017,78 @@
my_required_modules += $(LOCAL_REQUIRED_MODULES_$($(my_prefix)OS))
endif
-ALL_MODULES.$(my_register_name).SHARED_LIBS := \
- $(ALL_MODULES.$(my_register_name).SHARED_LIBS) $(LOCAL_SHARED_LIBRARIES)
+ifndef LOCAL_SOONG_MODULE_INFO_JSON
+ ALL_MAKE_MODULE_INFO_JSON_MODULES += $(my_register_name)
+ ALL_MODULES.$(my_register_name).SHARED_LIBS := \
+ $(ALL_MODULES.$(my_register_name).SHARED_LIBS) $(LOCAL_SHARED_LIBRARIES)
-ALL_MODULES.$(my_register_name).STATIC_LIBS := \
- $(ALL_MODULES.$(my_register_name).STATIC_LIBS) $(LOCAL_STATIC_LIBRARIES)
+ ALL_MODULES.$(my_register_name).STATIC_LIBS := \
+ $(ALL_MODULES.$(my_register_name).STATIC_LIBS) $(LOCAL_STATIC_LIBRARIES)
-ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS := \
- $(ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS) $(LOCAL_SYSTEM_SHARED_LIBRARIES)
+ ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS := \
+ $(ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS) $(LOCAL_SYSTEM_SHARED_LIBRARIES)
-ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES := \
- $(ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES) $(LOCAL_RUNTIME_LIBRARIES) \
- $(LOCAL_JAVA_LIBRARIES)
+ ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES := \
+ $(ALL_MODULES.$(my_register_name).LOCAL_RUNTIME_LIBRARIES) $(LOCAL_RUNTIME_LIBRARIES) \
+ $(LOCAL_JAVA_LIBRARIES)
-ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES := \
- $(ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES) $(LOCAL_STATIC_JAVA_LIBRARIES)
+ ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES := \
+ $(ALL_MODULES.$(my_register_name).LOCAL_STATIC_LIBRARIES) $(LOCAL_STATIC_JAVA_LIBRARIES)
-ifneq ($(my_test_data_file_pairs),)
- # Export the list of targets that are handled as data inputs and required
- # by tests at runtime. The format of my_test_data_file_pairs is
- # is $(path):$(relative_file) but for module-info, only the string after
- # ":" is needed.
- ALL_MODULES.$(my_register_name).TEST_DATA := \
- $(strip $(ALL_MODULES.$(my_register_name).TEST_DATA) \
- $(foreach f, $(my_test_data_file_pairs),\
- $(call word-colon,2,$(f))))
+ ifneq ($(my_test_data_file_pairs),)
+ # Export the list of targets that are handled as data inputs and required
+ # by tests at runtime. The format of my_test_data_file_pairs is
+ # is $(path):$(relative_file) but for module-info, only the string after
+ # ":" is needed.
+ ALL_MODULES.$(my_register_name).TEST_DATA := \
+ $(strip $(ALL_MODULES.$(my_register_name).TEST_DATA) \
+ $(foreach f, $(my_test_data_file_pairs),\
+ $(call word-colon,2,$(f))))
+ endif
+
+ ifdef LOCAL_TEST_DATA_BINS
+ ALL_MODULES.$(my_register_name).TEST_DATA_BINS := \
+ $(ALL_MODULES.$(my_register_name).TEST_DATA_BINS) $(LOCAL_TEST_DATA_BINS)
+ endif
+
+ ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS := \
+ $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS) \
+ $(filter-out $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS),$(my_supported_variant))
+
+ ALL_MODULES.$(my_register_name).ACONFIG_FILES := \
+ $(ALL_MODULES.$(my_register_name).ACONFIG_FILES) $(LOCAL_ACONFIG_FILES)
+
+ ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := \
+ $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES) $(LOCAL_COMPATIBILITY_SUITE)
+ ALL_MODULES.$(my_register_name).MODULE_NAME := $(LOCAL_MODULE)
+ ALL_MODULES.$(my_register_name).TEST_CONFIG := $(test_config)
+ ALL_MODULES.$(my_register_name).EXTRA_TEST_CONFIGS := $(LOCAL_EXTRA_FULL_TEST_CONFIGS)
+ ALL_MODULES.$(my_register_name).TEST_MAINLINE_MODULES := $(LOCAL_TEST_MAINLINE_MODULES)
+ ifdef LOCAL_IS_UNIT_TEST
+ ALL_MODULES.$(my_register_name).IS_UNIT_TEST := $(LOCAL_IS_UNIT_TEST)
+ endif
+ ifdef LOCAL_TEST_OPTIONS_TAGS
+ ALL_MODULES.$(my_register_name).TEST_OPTIONS_TAGS := $(LOCAL_TEST_OPTIONS_TAGS)
+ endif
+
+ ##########################################################
+ # Track module-level dependencies.
+ # (b/204397180) Unlock RECORD_ALL_DEPS was acknowledged reasonable for better Atest performance.
+ ALL_MODULES.$(my_register_name).ALL_DEPS := \
+ $(ALL_MODULES.$(my_register_name).ALL_DEPS) \
+ $(LOCAL_STATIC_LIBRARIES) \
+ $(LOCAL_WHOLE_STATIC_LIBRARIES) \
+ $(LOCAL_SHARED_LIBRARIES) \
+ $(LOCAL_DYLIB_LIBRARIES) \
+ $(LOCAL_RLIB_LIBRARIES) \
+ $(LOCAL_PROC_MACRO_LIBRARIES) \
+ $(LOCAL_HEADER_LIBRARIES) \
+ $(LOCAL_STATIC_JAVA_LIBRARIES) \
+ $(LOCAL_JAVA_LIBRARIES) \
+ $(LOCAL_JNI_SHARED_LIBRARIES)
+
endif
-ifdef LOCAL_TEST_DATA_BINS
- ALL_MODULES.$(my_register_name).TEST_DATA_BINS := \
- $(ALL_MODULES.$(my_register_name).TEST_DATA_BINS) $(LOCAL_TEST_DATA_BINS)
-endif
-
-ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS := \
- $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS) \
- $(filter-out $(ALL_MODULES.$(my_register_name).SUPPORTED_VARIANTS),$(my_supported_variant))
-
-ALL_MODULES.$(my_register_name).ACONFIG_FILES := \
- $(ALL_MODULES.$(my_register_name).ACONFIG_FILES) $(LOCAL_ACONFIG_FILES)
-
-
##########################################################################
## When compiling against API imported module, use API import stub
## libraries.
@@ -1126,55 +1168,32 @@
$(call pretty-error,LOCAL_TARGET_REQUIRED_MODULES may not be used from target modules. Use LOCAL_REQUIRED_MODULES instead)
endif
endif
-ALL_MODULES.$(my_register_name).EVENT_LOG_TAGS := \
- $(ALL_MODULES.$(my_register_name).EVENT_LOG_TAGS) $(event_log_tags)
+
+ifdef event_log_tags
+ ALL_MODULES.$(my_register_name).EVENT_LOG_TAGS := \
+ $(ALL_MODULES.$(my_register_name).EVENT_LOG_TAGS) $(event_log_tags)
+endif
+
ALL_MODULES.$(my_register_name).MAKEFILE := \
$(ALL_MODULES.$(my_register_name).MAKEFILE) $(LOCAL_MODULE_MAKEFILE)
+
ifdef LOCAL_MODULE_OWNER
-ALL_MODULES.$(my_register_name).OWNER := \
- $(sort $(ALL_MODULES.$(my_register_name).OWNER) $(LOCAL_MODULE_OWNER))
+ ALL_MODULES.$(my_register_name).OWNER := \
+ $(sort $(ALL_MODULES.$(my_register_name).OWNER) $(LOCAL_MODULE_OWNER))
endif
+
ifdef LOCAL_2ND_ARCH_VAR_PREFIX
ALL_MODULES.$(my_register_name).FOR_2ND_ARCH := true
endif
ALL_MODULES.$(my_register_name).FOR_HOST_CROSS := $(my_host_cross)
-ALL_MODULES.$(my_register_name).MODULE_NAME := $(LOCAL_MODULE)
-ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := \
- $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES) \
- $(filter-out $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES),$(LOCAL_COMPATIBILITY_SUITE))
-ALL_MODULES.$(my_register_name).TEST_CONFIG := $(test_config)
-ALL_MODULES.$(my_register_name).EXTRA_TEST_CONFIGS := $(LOCAL_EXTRA_FULL_TEST_CONFIGS)
-ALL_MODULES.$(my_register_name).TEST_MAINLINE_MODULES := $(LOCAL_TEST_MAINLINE_MODULES)
ifndef LOCAL_IS_HOST_MODULE
ALL_MODULES.$(my_register_name).FILE_CONTEXTS := $(LOCAL_FILE_CONTEXTS)
ALL_MODULES.$(my_register_name).APEX_KEYS_FILE := $(LOCAL_APEX_KEY_PATH)
endif
-ifdef LOCAL_IS_UNIT_TEST
-ALL_MODULES.$(my_register_name).IS_UNIT_TEST := $(LOCAL_IS_UNIT_TEST)
-endif
-ifdef LOCAL_TEST_OPTIONS_TAGS
-ALL_MODULES.$(my_register_name).TEST_OPTIONS_TAGS := $(LOCAL_TEST_OPTIONS_TAGS)
-endif
test_config :=
INSTALLABLE_FILES.$(LOCAL_INSTALLED_MODULE).MODULE := $(my_register_name)
-##########################################################
-# Track module-level dependencies.
-# (b/204397180) Unlock RECORD_ALL_DEPS was acknowledged reasonable for better Atest performance.
-ALL_MODULES.$(my_register_name).ALL_DEPS := \
- $(ALL_MODULES.$(my_register_name).ALL_DEPS) \
- $(LOCAL_STATIC_LIBRARIES) \
- $(LOCAL_WHOLE_STATIC_LIBRARIES) \
- $(LOCAL_SHARED_LIBRARIES) \
- $(LOCAL_DYLIB_LIBRARIES) \
- $(LOCAL_RLIB_LIBRARIES) \
- $(LOCAL_PROC_MACRO_LIBRARIES) \
- $(LOCAL_HEADER_LIBRARIES) \
- $(LOCAL_STATIC_JAVA_LIBRARIES) \
- $(LOCAL_JAVA_LIBRARIES) \
- $(LOCAL_JNI_SHARED_LIBRARIES)
-
###########################################################
## umbrella targets used to verify builds
###########################################################
diff --git a/core/binary.mk b/core/binary.mk
index 8c107bd..7998a5a 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -274,6 +274,13 @@
endif
endif
+ifneq ($(LOCAL_MIN_SDK_VERSION),)
+ ifdef LOCAL_IS_HOST_MODULE
+ $(error $(LOCAL_PATH): LOCAL_MIN_SDK_VERSION cannot be used in host module)
+ endif
+ my_api_level := $(LOCAL_MIN_SDK_VERSION)
+endif
+
ifeq ($(NATIVE_COVERAGE),true)
ifndef LOCAL_IS_HOST_MODULE
my_ldflags += -Wl,--wrap,getenv
diff --git a/core/board_config.mk b/core/board_config.mk
index ae11eb6..5a1a781 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -186,6 +186,7 @@
BUILD_BROKEN_VINTF_PRODUCT_COPY_FILES \
BUILD_BROKEN_INCORRECT_PARTITION_IMAGES \
BUILD_BROKEN_GENRULE_SANDBOXING \
+ BUILD_BROKEN_DONT_CHECK_SYSTEMSDK \
_build_broken_var_list += \
$(foreach m,$(AVAILABLE_BUILD_MODULE_TYPES) \
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index b73e9b4..2b84fcd 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -265,6 +265,7 @@
LOCAL_SOONG_LICENSE_METADATA :=
LOCAL_SOONG_LINK_TYPE :=
LOCAL_SOONG_LINT_REPORTS :=
+LOCAL_SOONG_MODULE_INFO_JSON :=
LOCAL_SOONG_MODULE_TYPE :=
LOCAL_SOONG_PROGUARD_DICT :=
LOCAL_SOONG_PROGUARD_USAGE_ZIP :=
diff --git a/core/combo/arch/x86/goldmont-without-xsaves.mk b/core/combo/arch/x86/goldmont-without-xsaves.mk
new file mode 100644
index 0000000..1b93c17
--- /dev/null
+++ b/core/combo/arch/x86/goldmont-without-xsaves.mk
@@ -0,0 +1,7 @@
+# This file contains feature macro definitions specific to the
+# goldmont-without-xsaves arch variant.
+#
+# See build/make/core/combo/arch/x86/x86-atom.mk for differences.
+#
+
+ARCH_X86_HAVE_SSE4_1 := true
diff --git a/core/combo/arch/x86_64/goldmont-without-xsaves.mk b/core/combo/arch/x86_64/goldmont-without-xsaves.mk
new file mode 100644
index 0000000..1b93c17
--- /dev/null
+++ b/core/combo/arch/x86_64/goldmont-without-xsaves.mk
@@ -0,0 +1,7 @@
+# This file contains feature macro definitions specific to the
+# goldmont-without-xsaves arch variant.
+#
+# See build/make/core/combo/arch/x86/x86-atom.mk for differences.
+#
+
+ARCH_X86_HAVE_SSE4_1 := true
diff --git a/core/config.mk b/core/config.mk
index f8a9879..469be30 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -110,6 +110,7 @@
$(KATI_obsolete_var BUILD_BROKEN_ENG_DEBUG_TAGS)
$(KATI_obsolete_export It is a global setting. See $(CHANGES_URL)#export_keyword)
$(KATI_obsolete_var BUILD_BROKEN_ANDROIDMK_EXPORTS)
+$(KATI_obsolete_var PRODUCT_NOTICE_SPLIT_OVERRIDE,Stop using this, keep calm, and carry on.)
$(KATI_obsolete_var PRODUCT_STATIC_BOOT_CONTROL_HAL,Use shared library module instead. See $(CHANGES_URL)#PRODUCT_STATIC_BOOT_CONTROL_HAL)
$(KATI_obsolete_var \
ARCH_ARM_HAVE_ARMV7A \
@@ -777,16 +778,9 @@
PRODUCT_FULL_TREBLE := true
endif
-# TODO(b/69865032): Make PRODUCT_NOTICE_SPLIT the default behavior and remove
-# references to it here and below.
-ifdef PRODUCT_NOTICE_SPLIT_OVERRIDE
- $(error PRODUCT_NOTICE_SPLIT_OVERRIDE cannot be set.)
-endif
-
requirements := \
PRODUCT_TREBLE_LINKER_NAMESPACES \
- PRODUCT_ENFORCE_VINTF_MANIFEST \
- PRODUCT_NOTICE_SPLIT
+ PRODUCT_ENFORCE_VINTF_MANIFEST
# If it is overriden, then the requirement override is taken, otherwise it's
# PRODUCT_FULL_TREBLE
@@ -799,12 +793,20 @@
PRODUCT_FULL_TREBLE_OVERRIDE ?=
$(foreach req,$(requirements),$(eval $(req)_OVERRIDE ?=))
+# used to be a part of PRODUCT_FULL_TREBLE, but now always set it
+PRODUCT_NOTICE_SPLIT := true
+
# TODO(b/114488870): disallow PRODUCT_FULL_TREBLE_OVERRIDE from being used.
.KATI_READONLY := \
PRODUCT_FULL_TREBLE_OVERRIDE \
$(foreach req,$(requirements),$(req)_OVERRIDE) \
$(requirements) \
PRODUCT_FULL_TREBLE \
+ PRODUCT_NOTICE_SPLIT \
+
+ifneq ($(PRODUCT_FULL_TREBLE),true)
+ $(warning This device does not have Treble enabled. This is unsafe.)
+endif
$(KATI_obsolete_var $(foreach req,$(requirements),$(req)_OVERRIDE) \
,This should be referenced without the _OVERRIDE suffix.)
@@ -829,14 +831,10 @@
# Set BOARD_SYSTEMSDK_VERSIONS to the latest SystemSDK version starting from P-launching
# devices if unset.
ifndef BOARD_SYSTEMSDK_VERSIONS
- ifdef PRODUCT_SHIPPING_API_LEVEL
- ifneq ($(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),28),)
- ifeq (REL,$(PLATFORM_VERSION_CODENAME))
- BOARD_SYSTEMSDK_VERSIONS := $(PLATFORM_SDK_VERSION)
- else
- BOARD_SYSTEMSDK_VERSIONS := $(PLATFORM_VERSION_CODENAME)
- endif
- endif
+ ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+ BOARD_SYSTEMSDK_VERSIONS := $(PLATFORM_SDK_VERSION)
+ else
+ BOARD_SYSTEMSDK_VERSIONS := $(PLATFORM_VERSION_CODENAME)
endif
endif
@@ -890,40 +888,23 @@
# SEPolicy versions
-# PLATFORM_SEPOLICY_VERSION is a number of the form "NN.m" with "NN" mapping to
-# PLATFORM_SDK_VERSION and "m" as a minor number which allows for SELinux
-# changes independent of PLATFORM_SDK_VERSION. This value will be set to
-# 10000.0 to represent tip-of-tree development that is inherently unstable and
-# thus designed not to work with any shipping vendor policy. This is similar in
-# spirit to how DEFAULT_APP_TARGET_SDK is set.
-# The minor version ('m' component) must be updated every time a platform release
-# is made which breaks compatibility with the previous platform sepolicy version,
-# not just on every increase in PLATFORM_SDK_VERSION. The minor version should
-# be reset to 0 on every bump of the PLATFORM_SDK_VERSION.
-sepolicy_major_vers := 34
-sepolicy_minor_vers := 0
+# PLATFORM_SEPOLICY_VERSION is a number of the form "YYYYMM.0" with "YYYYMM"
+# mapping to vFRC version. This value will be set to 1000000.0 to represent
+# tip-of-tree development that is inherently unstable and thus designed not to
+# work with any shipping vendor policy. This is similar in spirit to how
+# DEFAULT_APP_TARGET_SDK is set.
+sepolicy_vers := $(BOARD_API_LEVEL).0
-ifneq ($(sepolicy_major_vers), $(PLATFORM_SDK_VERSION))
-$(error sepolicy_major_version does not match PLATFORM_SDK_VERSION, please update.)
-endif
-
-TOT_SEPOLICY_VERSION := 10000.0
-ifneq (REL,$(PLATFORM_VERSION_CODENAME))
- PLATFORM_SEPOLICY_VERSION := $(TOT_SEPOLICY_VERSION)
+TOT_SEPOLICY_VERSION := 1000000.0
+ifeq (true,$(BOARD_API_LEVEL_FROZEN))
+ PLATFORM_SEPOLICY_VERSION := $(sepolicy_vers)
else
- PLATFORM_SEPOLICY_VERSION := $(join $(addsuffix .,$(sepolicy_major_vers)), $(sepolicy_minor_vers))
+ PLATFORM_SEPOLICY_VERSION := $(TOT_SEPOLICY_VERSION)
endif
-sepolicy_major_vers :=
-sepolicy_minor_vers :=
+sepolicy_vers :=
-# BOARD_SEPOLICY_VERS must take the format "NN.m" and contain the sepolicy
-# version identifier corresponding to the sepolicy on which the non-platform
-# policy is to be based. If unspecified, this will build against the current
-# public platform policy in tree
-ifndef BOARD_SEPOLICY_VERS
-# The default platform policy version.
BOARD_SEPOLICY_VERS := $(PLATFORM_SEPOLICY_VERSION)
-endif
+.KATI_READONLY := PLATFORM_SEPOLICY_VERSION BOARD_SEPOLICY_VERS
# A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
PLATFORM_SEPOLICY_COMPAT_VERSIONS := $(filter-out $(PLATFORM_SEPOLICY_VERSION), \
diff --git a/core/definitions.mk b/core/definitions.mk
index 7a6c064..1f2d011 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -37,6 +37,8 @@
# sub-variables.
ALL_MODULES:=
+ALL_MAKE_MODULE_INFO_JSON_MODULES:=
+
# The relative paths of the non-module targets in the system.
ALL_NON_MODULES:=
NON_MODULES_WITHOUT_LICENSE_METADATA:=
@@ -3120,14 +3122,12 @@
# Copies many init script files and check they are well-formed.
# $(1): The init script files to copy. Each entry is a ':' separated src:dst pair.
-# Evaluates to the list of the dst files. (ie suitable for a dependency list.)
define copy-many-init-script-files-checked
$(foreach f, $(1), $(strip \
$(eval _cmf_tuple := $(subst :, ,$(f))) \
$(eval _cmf_src := $(word 1,$(_cmf_tuple))) \
$(eval _cmf_dest := $(word 2,$(_cmf_tuple))) \
- $(eval $(call copy-init-script-file-checked,$(_cmf_src),$(_cmf_dest))) \
- $(_cmf_dest)))
+ $(eval $(call copy-init-script-file-checked,$(_cmf_src),$(_cmf_dest)))))
endef
# Copy the file only if it's a well-formed xml file. For use via $(eval).
@@ -3165,14 +3165,12 @@
# Copies many vintf manifest files checked.
# $(1): The files to copy. Each entry is a ':' separated src:dst pair
-# Evaluates to the list of the dst files (ie suitable for a dependency list)
define copy-many-vintf-manifest-files-checked
$(foreach f, $(1), $(strip \
$(eval _cmf_tuple := $(subst :, ,$(f))) \
$(eval _cmf_src := $(word 1,$(_cmf_tuple))) \
$(eval _cmf_dest := $(word 2,$(_cmf_tuple))) \
- $(eval $(call copy-vintf-manifest-checked,$(_cmf_src),$(_cmf_dest))) \
- $(_cmf_dest)))
+ $(eval $(call copy-vintf-manifest-checked,$(_cmf_src),$(_cmf_dest)))))
endef
# Copy the file only if it's not an ELF file. For use via $(eval).
diff --git a/core/local_current_sdk.mk b/core/local_current_sdk.mk
index ea7da8a..ccdbf77 100644
--- a/core/local_current_sdk.mk
+++ b/core/local_current_sdk.mk
@@ -14,13 +14,24 @@
# limitations under the License.
#
ifdef BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
- ifneq (current,$(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES))
+ _override_to := $(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+
+ # b/314011075: apks and jars in the vendor or odm partitions cannot use
+ # system SDK 35 and beyond. In order not to suddenly break those vendor
+ # modules using current or system_current as their LOCAL_SDK_VERSION,
+ # override it to 34, which is the maximum API level allowed for them.
+ ifneq (,$(filter JAVA_LIBRARIES APPS,$(LOCAL_MODULE_CLASS)))
+ _override_to := 34
+ endif
+
+ ifneq (current,$(_override_to))
ifneq (,$(filter true,$(LOCAL_VENDOR_MODULE) $(LOCAL_ODM_MODULE) $(LOCAL_PROPRIETARY_MODULE)))
ifeq (current,$(LOCAL_SDK_VERSION))
- LOCAL_SDK_VERSION := $(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+ LOCAL_SDK_VERSION := $(_override_to)
else ifeq (system_current,$(LOCAL_SDK_VERSION))
- LOCAL_SDK_VERSION := system_$(BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES)
+ LOCAL_SDK_VERSION := system_$(_override_to)
endif
endif
endif
+ _override_to :=
endif
diff --git a/core/local_systemsdk.mk b/core/local_systemsdk.mk
index 460073d..3307e72 100644
--- a/core/local_systemsdk.mk
+++ b/core/local_systemsdk.mk
@@ -33,6 +33,9 @@
# Runtime resource overlays are exempted from building against System SDK.
# TODO(b/155027019): remove this, after no product/vendor apps rely on this behavior.
LOCAL_SDK_VERSION := system_current
+ # We have run below again since LOCAL_SDK_VERSION is newly set and the "_current"
+ # may have to be updated
+ include $(BUILD_SYSTEM)/local_current_sdk.mk
endif
endif
endif
@@ -54,10 +57,35 @@
# If not, vendor apks are treated equally to system apps
_supported_systemsdk_versions := $(PLATFORM_SYSTEMSDK_VERSIONS)
endif
+
+ # b/314011075: apks and jars in the vendor or odm partitions cannot use system SDK 35 and beyond.
+ # This is to discourage the use of Java APIs in the partitions, which hasn't been supported since
+ # the beginning of the project Treble back in Android 10. Ultimately, we'd like to completely
+ # disallow any Java API in the partitions, but it shall be done progressively.
+ ifneq (,$(filter true,$(LOCAL_VENDOR_MODULE) $(LOCAL_ODM_MODULE) $(LOCAL_PROPRIETARY_MODULE)))
+ # 28 is the API level when BOARD_SYSTEMSDK_VERSIONS was introduced. So, it's the oldset API
+ # we allow.
+ _supported_systemsdk_versions := $(call int_range_list, 28, 34)
+ endif
+
+ # Extract version number from LOCAL_SDK_VERSION (ex: system_34 -> 34)
_system_sdk_version := $(call get-numeric-sdk-version,$(LOCAL_SDK_VERSION))
+ # However, the extraction may fail if it doesn't have any number (i.e. current, core_current,
+ # system_current, or similar) Then use the latest platform SDK version number or the actual
+ # codename.
+ ifeq (,$(_system_sdk_version)
+ ifeq (REL,$(PLATFORM_VERSION_CODENAME))
+ _system_sdk_version := $(PLATFORM_SDK_VERSION)
+ else
+ _system_sdk_version := $(PLATFORM_VERSION_CODENAME)
+ endif
+ endif
+
ifneq ($(_system_sdk_version),$(filter $(_system_sdk_version),$(_supported_systemsdk_versions)))
- $(call pretty-error,Incompatible LOCAL_SDK_VERSION '$(LOCAL_SDK_VERSION)'. \
- System SDK version '$(_system_sdk_version)' is not supported. Supported versions are: $(_supported_systemsdk_versions))
+ ifneq (true,$(BUILD_BROKEN_DONT_CHECK_SYSTEMSDK)
+ $(call pretty-error,Incompatible LOCAL_SDK_VERSION '$(LOCAL_SDK_VERSION)'. \
+ System SDK version '$(_system_sdk_version)' is not supported. Supported versions are: $(_supported_systemsdk_versions))
+ endif
endif
_system_sdk_version :=
_supported_systemsdk_versions :=
diff --git a/core/main.mk b/core/main.mk
index 348a964..649c75c 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1721,10 +1721,8 @@
# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files
-ifeq ($(SOONG_COLLECT_JAVA_DEPS), true)
- $(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
- $(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
-endif
+$(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
+$(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
.PHONY: apps_only
ifeq ($(HOST_OS),darwin)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 193ac18..b6ce2a7 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -299,6 +299,7 @@
$(call add_json_bool, BuildBrokenVendorPropertyNamespace, $(filter true,$(BUILD_BROKEN_VENDOR_PROPERTY_NAMESPACE)))
$(call add_json_bool, BuildBrokenIncorrectPartitionImages, $(filter true,$(BUILD_BROKEN_INCORRECT_PARTITION_IMAGES)))
$(call add_json_list, BuildBrokenInputDirModules, $(BUILD_BROKEN_INPUT_DIR_MODULES))
+$(call add_json_bool, BuildBrokenDontCheckSystemSdk, $(filter true,$(BUILD_BROKEN_DONT_CHECK_SYSTEMSDK)))
$(call add_json_list, BuildWarningBadOptionalUsesLibsAllowlist, $(BUILD_WARNING_BAD_OPTIONAL_USES_LIBS_ALLOWLIST))
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index eb5c63c..8546828 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -13,10 +13,15 @@
$(if $(strip $(2)),'$(COMMA)$(strip $(1)): "$(strip $(2))"')
endef
-$(MODULE_INFO_JSON):
+SOONG_MODULE_INFO := $(SOONG_OUT_DIR)/module-info-$(TARGET_PRODUCT).json
+
+$(MODULE_INFO_JSON): PRIVATE_SOONG_MODULE_INFO := $(SOONG_MODULE_INFO)
+$(MODULE_INFO_JSON): PRIVATE_MERGE_JSON_OBJECTS := $(HOST_OUT_EXECUTABLES)/merge_module_info_json
+$(MODULE_INFO_JSON): $(HOST_OUT_EXECUTABLES)/merge_module_info_json
+$(MODULE_INFO_JSON): $(SOONG_MODULE_INFO)
@echo Generating $@
- $(hide) echo -ne '{\n ' > $@
- $(hide) echo -ne $(KATI_foreach_sep m,$(COMMA)$(_NEWLINE), $(sort $(ALL_MODULES)),\
+ $(hide) echo -ne '{\n ' > $@.tmp
+ $(hide) echo -ne $(KATI_foreach_sep m,$(COMMA)$(_NEWLINE), $(sort $(ALL_MAKE_MODULE_INFO_JSON_MODULES)),\
'"$(m)": {' \
'"module_name": "$(ALL_MODULES.$(m).MODULE_NAME)"' \
$(call write-optional-json-list, "class", $(sort $(ALL_MODULES.$(m).CLASS))) \
@@ -43,7 +48,9 @@
$(call write-optional-json-list, "supported_variants", $(sort $(ALL_MODULES.$(m).SUPPORTED_VARIANTS))) \
$(call write-optional-json-list, "host_dependencies", $(sort $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET))) \
$(call write-optional-json-list, "target_dependencies", $(sort $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST))) \
- '}')'\n}\n' >> $@
+ '}')'\n}\n' >> $@.tmp
+ $(PRIVATE_MERGE_JSON_OBJECTS) -o $@ $(PRIVATE_SOONG_MODULE_INFO) $@.tmp
+ rm $@.tmp
droidcore-unbundled: $(MODULE_INFO_JSON)
diff --git a/envsetup.sh b/envsetup.sh
index cc808d2..5aa11c7 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -62,7 +62,7 @@
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> ...] \
+- 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.
@@ -775,7 +775,7 @@
answer=$1
else
print_lunch_menu
- echo "Which would you like? [aosp_arm-trunk_staging-eng]"
+ echo "Which would you like? [aosp_cf_x86_64_phone-trunk_staging-eng]"
echo -n "Pick from common choices above (e.g. 13) or specify your own (e.g. aosp_barbet-trunk_staging-eng): "
read answer
used_lunch_menu=1
@@ -785,10 +785,10 @@
if [ -z "$answer" ]
then
- selection=aosp_arm-trunk_staging-eng
+ selection=aosp_cf_x86_64_phone-trunk_staging-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
- local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
+ local choices=($(TARGET_BUILD_APPS= TARGET_PRODUCT= TARGET_RELEASE= TARGET_BUILD_VARIANT= get_build_var COMMON_LUNCH_CHOICES 2>/dev/null))
if [ $answer -le ${#choices[@]} ]
then
# array in zsh starts from 1 instead of 0.
diff --git a/target/product/OWNERS b/target/product/OWNERS
index 008e4a2..48d3f2a 100644
--- a/target/product/OWNERS
+++ b/target/product/OWNERS
@@ -1,4 +1,4 @@
-per-file runtime_libart.mk = calin@google.com, mast@google.com, ngeoffray@google.com, oth@google.com, rpl@google.com, vmarko@google.com
+per-file runtime_libart.mk = mast@google.com, ngeoffray@google.com, rpl@google.com, vmarko@google.com
# GSI
per-file gsi_release.mk = file:/target/product/gsi/OWNERS
@@ -7,4 +7,4 @@
# Android Go
per-file go_defaults.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
per-file go_defaults_512.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
-per-file go_defaults_common.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
\ No newline at end of file
+per-file go_defaults_common.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 098ed27..6a101da9 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -70,7 +70,7 @@
com.android.scheduling \
com.android.sdkext \
com.android.tethering \
- com.android.tzdata \
+ $(RELEASE_PACKAGE_TZDATA_MODULE) \
com.android.uwb \
com.android.virt \
com.android.wifi \
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 53c9e0c..80aecb7 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -18,6 +18,7 @@
LLNDK: libneuralnetworks.so
LLNDK: libselinux.so
LLNDK: libsync.so
+LLNDK: libvendorsupport.so
LLNDK: libvndksupport.so
LLNDK: libvulkan.so
VNDK-SP: android.hardware.common-V2-ndk.so
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 82bfa7e..d5b5b8f 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -44,7 +44,6 @@
protos: ["protos/aconfig.proto"],
crate_name: "aconfig_protos",
source_stem: "aconfig_protos",
- use_protobuf3: true,
host_supported: true,
}
@@ -97,6 +96,12 @@
srcs: ["tests/test_exported.aconfig"],
}
+aconfig_declarations {
+ name: "aconfig.test.forcereadonly.flags",
+ package: "com.android.aconfig.test.forcereadonly",
+ srcs: ["tests/test_force_read_only.aconfig"],
+}
+
aconfig_values {
name: "aconfig.test.flag.values",
package: "com.android.aconfig.test",
@@ -126,6 +131,12 @@
mode: "exported",
}
+java_aconfig_library {
+ name: "aconfig_test_java_library_forcereadonly",
+ aconfig_declarations: "aconfig.test.forcereadonly.flags",
+ mode: "force-read-only",
+}
+
android_test {
name: "aconfig.test.java",
srcs: [
@@ -136,6 +147,7 @@
static_libs: [
"aconfig_test_java_library",
"aconfig_test_java_library_exported",
+ "aconfig_test_java_library_forcereadonly",
"androidx.test.rules",
"testng",
],
@@ -174,6 +186,18 @@
mode: "test",
}
+cc_aconfig_library {
+ name: "aconfig_test_cpp_library_exported_variant",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
+cc_aconfig_library {
+ name: "aconfig_test_cpp_library_force_read_only_variant",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "force-read-only",
+}
+
cc_test {
name: "aconfig.test.cpp",
srcs: [
@@ -204,6 +228,36 @@
test_suites: ["general-tests"],
}
+cc_test {
+ name: "aconfig.test.cpp.exported_mode",
+ srcs: [
+ "tests/aconfig_exported_mode_test.cpp",
+ ],
+ static_libs: [
+ "aconfig_test_cpp_library_exported_variant",
+ "libgmock",
+ ],
+ shared_libs: [
+ "server_configurable_flags",
+ ],
+ test_suites: ["general-tests"],
+}
+
+cc_test {
+ name: "aconfig.test.cpp.force_read_only_mode",
+ srcs: [
+ "tests/aconfig_force_read_only_mode_test.cpp",
+ ],
+ static_libs: [
+ "aconfig_test_cpp_library_force_read_only_variant",
+ "libgmock",
+ ],
+ shared_libs: [
+ "server_configurable_flags",
+ ],
+ test_suites: ["general-tests"],
+}
+
rust_aconfig_library {
name: "libaconfig_test_rust_library",
crate_name: "aconfig_test_rust_library",
@@ -238,3 +292,39 @@
],
test_suites: ["general-tests"],
}
+
+rust_aconfig_library {
+ name: "libaconfig_test_rust_library_with_exported_mode",
+ crate_name: "aconfig_test_rust_library",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
+rust_test {
+ name: "aconfig.exported_mode.test.rust",
+ srcs: [
+ "tests/aconfig_exported_mode_test.rs"
+ ],
+ rustlibs: [
+ "libaconfig_test_rust_library_with_exported_mode",
+ ],
+ test_suites: ["general-tests"],
+}
+
+rust_aconfig_library {
+ name: "libaconfig_test_rust_library_with_force_read_only_mode",
+ crate_name: "aconfig_test_rust_library",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "force-read-only",
+}
+
+rust_test {
+ name: "aconfig.force_read_only_mode.test.rust",
+ srcs: [
+ "tests/aconfig_force_read_only_mode_test.rs"
+ ],
+ rustlibs: [
+ "libaconfig_test_rust_library_with_force_read_only_mode",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 2edf4b8..7b58e94 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -20,6 +20,3 @@
[build-dependencies]
protobuf-codegen = "3.2.0"
-
-[dev-dependencies]
-itertools = "0.10.5"
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index e29918f..de8d932 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -24,6 +24,10 @@
"name": "aconfig.test"
},
{
+ // aconfig Java integration tests (host)
+ "name": "AconfigJavaHostTest"
+ },
+ {
// aconfig Java integration tests
"name": "aconfig.test.java"
},
@@ -36,14 +40,22 @@
"name": "aconfig.test.cpp.test_mode"
},
{
- // aconfig C++ integration tests (production mode auto-generated code)
+ // aconfig C++ integration tests (exported mode auto-generated code)
+ "name": "aconfig.test.cpp.exported_mode"
+ },
+ {
+ // aconfig Rust integration tests (production mode auto-generated code)
"name": "aconfig.prod_mode.test.rust"
},
{
- // aconfig C++ integration tests (test mode auto-generated code)
+ // aconfig Rust integration tests (test mode auto-generated code)
"name": "aconfig.test_mode.test.rust"
},
{
+ // aconfig Rust integration tests (exported mode auto-generated code)
+ "name": "aconfig.exported_mode.test.rust"
+ },
+ {
// printflags unit tests
"name": "printflags.test"
}
diff --git a/tools/aconfig/src/codegen/cpp.rs b/tools/aconfig/src/codegen/cpp.rs
index 000581b..1279d8e 100644
--- a/tools/aconfig/src/codegen/cpp.rs
+++ b/tools/aconfig/src/codegen/cpp.rs
@@ -20,20 +20,21 @@
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
-pub fn generate_cpp_code<'a, I>(
+pub fn generate_cpp_code<I>(
package: &str,
parsed_flags_iter: I,
codegen_mode: CodegenMode,
) -> Result<Vec<OutputFile>>
where
- I: Iterator<Item = &'a ProtoParsedFlag>,
+ I: Iterator<Item = ProtoParsedFlag>,
{
let mut readwrite_count = 0;
let class_elements: Vec<ClassElement> = parsed_flags_iter
- .map(|pf| create_class_element(package, pf, &mut readwrite_count))
+ .map(|pf| create_class_element(package, &pf, &mut readwrite_count))
.collect();
let readwrite = readwrite_count > 0;
let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only);
@@ -49,7 +50,7 @@
has_fixed_read_only,
readwrite,
readwrite_count,
- for_test: codegen_mode == CodegenMode::Test,
+ is_test_mode: codegen_mode == CodegenMode::Test,
class_elements,
};
@@ -92,7 +93,7 @@
pub has_fixed_read_only: bool,
pub readwrite: bool,
pub readwrite_count: i32,
- pub for_test: bool,
+ pub is_test_mode: bool,
pub class_elements: Vec<ClassElement>,
}
@@ -135,6 +136,7 @@
#[cfg(test)]
mod tests {
use super::*;
+ use crate::protos::ProtoParsedFlags;
use std::collections::HashMap;
const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
@@ -148,6 +150,10 @@
#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
#endif
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED true
+#endif
+
#ifdef __cplusplus
#include <memory>
@@ -168,6 +174,8 @@
virtual bool enabled_fixed_ro() = 0;
+ virtual bool enabled_fixed_ro_exported() = 0;
+
virtual bool enabled_ro() = 0;
virtual bool enabled_ro_exported() = 0;
@@ -197,6 +205,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+inline bool enabled_fixed_ro_exported() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+}
+
inline bool enabled_ro() {
return true;
}
@@ -224,6 +236,8 @@
bool com_android_aconfig_test_enabled_fixed_ro();
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
bool com_android_aconfig_test_enabled_ro();
bool com_android_aconfig_test_enabled_ro_exported();
@@ -269,6 +283,10 @@
virtual void enabled_fixed_ro(bool val) = 0;
+ virtual bool enabled_fixed_ro_exported() = 0;
+
+ virtual void enabled_fixed_ro_exported(bool val) = 0;
+
virtual bool enabled_ro() = 0;
virtual void enabled_ro(bool val) = 0;
@@ -326,6 +344,14 @@
provider_->enabled_fixed_ro(val);
}
+inline bool enabled_fixed_ro_exported() {
+ return provider_->enabled_fixed_ro_exported();
+}
+
+inline void enabled_fixed_ro_exported(bool val) {
+ provider_->enabled_fixed_ro_exported(val);
+}
+
inline bool enabled_ro() {
return provider_->enabled_ro();
}
@@ -379,6 +405,10 @@
void set_com_android_aconfig_test_enabled_fixed_ro(bool val);
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
+void set_com_android_aconfig_test_enabled_fixed_ro_exported(bool val);
+
bool com_android_aconfig_test_enabled_ro();
void set_com_android_aconfig_test_enabled_ro(bool val);
@@ -401,6 +431,138 @@
"#;
+ const EXPORTED_EXPORTED_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+ virtual ~flag_provider_interface() = default;
+
+ virtual bool disabled_rw_exported() = 0;
+
+ virtual bool enabled_fixed_ro_exported() = 0;
+
+ virtual bool enabled_ro_exported() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_rw_exported() {
+ return provider_->disabled_rw_exported();
+}
+
+inline bool enabled_fixed_ro_exported() {
+ return provider_->enabled_fixed_ro_exported();
+}
+
+inline bool enabled_ro_exported() {
+ return provider_->enabled_ro_exported();
+}
+
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_rw_exported();
+
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
+bool com_android_aconfig_test_enabled_ro_exported();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
+ const EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifndef COM_ANDROID_ACONFIG_TEST
+#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
+#endif
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+ virtual ~flag_provider_interface() = default;
+
+ virtual bool disabled_ro() = 0;
+
+ virtual bool disabled_rw() = 0;
+
+ virtual bool disabled_rw_in_other_namespace() = 0;
+
+ virtual bool enabled_fixed_ro() = 0;
+
+ virtual bool enabled_ro() = 0;
+
+ virtual bool enabled_rw() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_ro() {
+ return false;
+}
+
+inline bool disabled_rw() {
+ return false;
+}
+
+inline bool disabled_rw_in_other_namespace() {
+ return false;
+}
+
+inline bool enabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+inline bool enabled_ro() {
+ return true;
+}
+
+inline bool enabled_rw() {
+ return true;
+}
+
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_ro();
+
+bool com_android_aconfig_test_disabled_rw();
+
+bool com_android_aconfig_test_disabled_rw_in_other_namespace();
+
+bool com_android_aconfig_test_enabled_fixed_ro();
+
+bool com_android_aconfig_test_enabled_ro();
+
+bool com_android_aconfig_test_enabled_rw();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
const PROD_SOURCE_FILE_EXPECTED: &str = r#"
#include "com_android_aconfig_test.h"
#include <server_configurable_flags/get_flags.h>
@@ -449,6 +611,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+ virtual bool enabled_fixed_ro_exported() override {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+ }
+
virtual bool enabled_ro() override {
return true;
}
@@ -495,6 +661,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+}
+
bool com_android_aconfig_test_enabled_ro() {
return true;
}
@@ -600,6 +770,19 @@
overrides_["enabled_fixed_ro"] = val;
}
+ virtual bool enabled_fixed_ro_exported() override {
+ auto it = overrides_.find("enabled_fixed_ro_exported");
+ if (it != overrides_.end()) {
+ return it->second;
+ } else {
+ return true;
+ }
+ }
+
+ virtual void enabled_fixed_ro_exported(bool val) override {
+ overrides_["enabled_fixed_ro_exported"] = val;
+ }
+
virtual bool enabled_ro() override {
auto it = overrides_.find("enabled_ro");
if (it != overrides_.end()) {
@@ -696,6 +879,13 @@
com::android::aconfig::test::enabled_fixed_ro(val);
}
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return com::android::aconfig::test::enabled_fixed_ro_exported();
+}
+
+void set_com_android_aconfig_test_enabled_fixed_ro_exported(bool val) {
+ com::android::aconfig::test::enabled_fixed_ro_exported(val);
+}
bool com_android_aconfig_test_enabled_ro() {
return com::android::aconfig::test::enabled_ro();
@@ -732,10 +922,256 @@
"#;
- fn test_generate_cpp_code(mode: CodegenMode) {
- let parsed_flags = crate::test::parse_test_flags();
+ const EXPORTED_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+#include <server_configurable_flags/get_flags.h>
+#include <vector>
+
+namespace com::android::aconfig::test {
+
+ class flag_provider : public flag_provider_interface {
+ public:
+ virtual bool disabled_rw_exported() override {
+ if (cache_[0] == -1) {
+ cache_[0] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "false") == "true";
+ }
+ return cache_[0];
+ }
+
+ virtual bool enabled_fixed_ro_exported() override {
+ if (cache_[1] == -1) {
+ cache_[1] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "false") == "true";
+ }
+ return cache_[1];
+ }
+
+ virtual bool enabled_ro_exported() override {
+ if (cache_[2] == -1) {
+ cache_[2] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "false") == "true";
+ }
+ return cache_[2];
+ }
+
+ private:
+ std::vector<int8_t> cache_ = std::vector<int8_t>(3, -1);
+ };
+
+ std::unique_ptr<flag_provider_interface> provider_ =
+ std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_rw_exported() {
+ return com::android::aconfig::test::disabled_rw_exported();
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return com::android::aconfig::test::enabled_fixed_ro_exported();
+}
+
+bool com_android_aconfig_test_enabled_ro_exported() {
+ return com::android::aconfig::test::enabled_ro_exported();
+}
+
+
+"#;
+
+ const FORCE_READ_ONLY_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+
+namespace com::android::aconfig::test {
+
+ class flag_provider : public flag_provider_interface {
+ public:
+
+ virtual bool disabled_ro() override {
+ return false;
+ }
+
+ virtual bool disabled_rw() override {
+ return false;
+ }
+
+ virtual bool disabled_rw_in_other_namespace() override {
+ return false;
+ }
+
+ virtual bool enabled_fixed_ro() override {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+ }
+
+ virtual bool enabled_ro() override {
+ return true;
+ }
+
+ virtual bool enabled_rw() override {
+ return true;
+ }
+ };
+
+ std::unique_ptr<flag_provider_interface> provider_ =
+ std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_ro() {
+ return false;
+}
+
+bool com_android_aconfig_test_disabled_rw() {
+ return false;
+}
+
+bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
+ return false;
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_enabled_ro() {
+ return true;
+}
+
+bool com_android_aconfig_test_enabled_rw() {
+ return true;
+}
+
+"#;
+
+ const READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifndef COM_ANDROID_ACONFIG_TEST
+#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO false
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
+#endif
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+ virtual ~flag_provider_interface() = default;
+
+ virtual bool disabled_fixed_ro() = 0;
+
+ virtual bool disabled_ro() = 0;
+
+ virtual bool enabled_fixed_ro() = 0;
+
+ virtual bool enabled_ro() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+inline bool disabled_ro() {
+ return false;
+}
+
+inline bool enabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+inline bool enabled_ro() {
+ return true;
+}
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_fixed_ro();
+
+bool com_android_aconfig_test_disabled_ro();
+
+bool com_android_aconfig_test_enabled_fixed_ro();
+
+bool com_android_aconfig_test_enabled_ro();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
+ const READ_ONLY_PROD_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+
+namespace com::android::aconfig::test {
+
+ class flag_provider : public flag_provider_interface {
+ public:
+
+ virtual bool disabled_fixed_ro() override {
+ return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+ }
+
+ virtual bool disabled_ro() override {
+ return false;
+ }
+
+ virtual bool enabled_fixed_ro() override {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+ }
+
+ virtual bool enabled_ro() override {
+ return true;
+ }
+ };
+
+ std::unique_ptr<flag_provider_interface> provider_ =
+ std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_disabled_ro() {
+ return false;
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_enabled_ro() {
+ return true;
+}
+"#;
+
+ fn test_generate_cpp_code(
+ parsed_flags: ProtoParsedFlags,
+ mode: CodegenMode,
+ expected_header: &str,
+ expected_src: &str,
+ ) {
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
let generated =
- generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
+ generate_cpp_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
.unwrap();
let mut generated_files_map = HashMap::new();
for file in generated {
@@ -750,12 +1186,7 @@
assert_eq!(
None,
crate::test::first_significant_code_diff(
- match mode {
- CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
- CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
- CodegenMode::Exported =>
- todo!("exported mode not yet supported for cpp, see b/313894653."),
- },
+ expected_header,
generated_files_map.get(&target_file_path).unwrap()
)
);
@@ -765,12 +1196,7 @@
assert_eq!(
None,
crate::test::first_significant_code_diff(
- match mode {
- CodegenMode::Production => PROD_SOURCE_FILE_EXPECTED,
- CodegenMode::Test => TEST_SOURCE_FILE_EXPECTED,
- CodegenMode::Exported =>
- todo!("exported mode not yet supported for cpp, see b/313894653."),
- },
+ expected_src,
generated_files_map.get(&target_file_path).unwrap()
)
);
@@ -778,11 +1204,56 @@
#[test]
fn test_generate_cpp_code_for_prod() {
- test_generate_cpp_code(CodegenMode::Production);
+ let parsed_flags = crate::test::parse_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::Production,
+ EXPORTED_PROD_HEADER_EXPECTED,
+ PROD_SOURCE_FILE_EXPECTED,
+ );
}
#[test]
fn test_generate_cpp_code_for_test() {
- test_generate_cpp_code(CodegenMode::Test);
+ let parsed_flags = crate::test::parse_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::Test,
+ EXPORTED_TEST_HEADER_EXPECTED,
+ TEST_SOURCE_FILE_EXPECTED,
+ );
+ }
+
+ #[test]
+ fn test_generate_cpp_code_for_exported() {
+ let parsed_flags = crate::test::parse_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::Exported,
+ EXPORTED_EXPORTED_HEADER_EXPECTED,
+ EXPORTED_SOURCE_FILE_EXPECTED,
+ );
+ }
+
+ #[test]
+ fn test_generate_cpp_code_for_force_read_only() {
+ let parsed_flags = crate::test::parse_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::ForceReadOnly,
+ EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED,
+ FORCE_READ_ONLY_SOURCE_FILE_EXPECTED,
+ );
+ }
+
+ #[test]
+ fn test_generate_cpp_code_for_read_only_prod() {
+ let parsed_flags = crate::test::parse_read_only_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::Production,
+ READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED,
+ READ_ONLY_PROD_SOURCE_FILE_EXPECTED,
+ );
}
}
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index ae3f274..78e892b 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -14,28 +14,27 @@
* limitations under the License.
*/
-use anyhow::{anyhow, Result};
+use anyhow::Result;
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
-pub fn generate_java_code<'a, I>(
+pub fn generate_java_code<I>(
package: &str,
parsed_flags_iter: I,
codegen_mode: CodegenMode,
) -> Result<Vec<OutputFile>>
where
- I: Iterator<Item = &'a ProtoParsedFlag>,
+ I: Iterator<Item = ProtoParsedFlag>,
{
let flag_elements: Vec<FlagElement> =
- parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
- let exported_flag_elements: Vec<FlagElement> =
- flag_elements.iter().filter(|elem| elem.exported).cloned().collect();
+ parsed_flags_iter.map(|pf| create_flag_element(package, &pf)).collect();
let namespace_flags = gen_flags_by_namespace(&flag_elements);
let properties_set: BTreeSet<String> =
flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
@@ -44,13 +43,8 @@
let runtime_lookup_required =
flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
- if library_exported && exported_flag_elements.is_empty() {
- return Err(anyhow!("exported library contains no exported flags"));
- }
-
let context = Context {
flag_elements,
- exported_flag_elements,
namespace_flags,
is_test_mode,
runtime_lookup_required,
@@ -109,7 +103,6 @@
#[derive(Serialize)]
struct Context {
pub flag_elements: Vec<FlagElement>,
- pub exported_flag_elements: Vec<FlagElement>,
pub namespace_flags: Vec<NamespaceFlags>,
pub is_test_mode: bool,
pub runtime_lookup_required: bool,
@@ -133,7 +126,6 @@
pub is_read_write: bool,
pub method_name: String,
pub properties: String,
- pub exported: bool,
}
fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement {
@@ -147,7 +139,6 @@
is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
method_name: format_java_method_name(pf.name()),
properties: format_property_name(pf.namespace()),
- exported: pf.is_exported.unwrap_or(false),
}
}
@@ -202,6 +193,9 @@
boolean enabledFixedRo();
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
+ boolean enabledFixedRoExported();
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
boolean enabledRo();
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
@@ -228,6 +222,8 @@
/** @hide */
public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
/** @hide */
+ public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
+ /** @hide */
public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
/** @hide */
public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
@@ -258,6 +254,11 @@
}
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
+ public static boolean enabledFixedRoExported() {
+ return FEATURE_FLAGS.enabledFixedRoExported();
+ }
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
public static boolean enabledRo() {
return FEATURE_FLAGS.enabledRo();
}
@@ -310,6 +311,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED);
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
return getValue(Flags.FLAG_ENABLED_RO);
}
@@ -348,6 +354,7 @@
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
+ Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RO, false),
Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RW, false)
@@ -359,12 +366,12 @@
#[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 mode = CodegenMode::Production;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#"
private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
@@ -463,6 +470,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return true;
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
return true;
}
@@ -512,12 +524,12 @@
#[test]
fn test_generate_java_code_exported() {
let parsed_flags = crate::test::parse_test_flags();
- let generated_files = generate_java_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.iter(),
- CodegenMode::Exported,
- )
- .unwrap();
+ let mode = CodegenMode::Exported;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = r#"
package com.android.aconfig.test;
@@ -528,6 +540,8 @@
/** @hide */
public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
/** @hide */
+ public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
+ /** @hide */
public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
@UnsupportedAppUsage
@@ -535,6 +549,10 @@
return FEATURE_FLAGS.disabledRwExported();
}
@UnsupportedAppUsage
+ public static boolean enabledFixedRoExported() {
+ return FEATURE_FLAGS.enabledFixedRoExported();
+ }
+ @UnsupportedAppUsage
public static boolean enabledRoExported() {
return FEATURE_FLAGS.enabledRoExported();
}
@@ -551,6 +569,8 @@
@UnsupportedAppUsage
boolean disabledRwExported();
@UnsupportedAppUsage
+ boolean enabledFixedRoExported();
+ @UnsupportedAppUsage
boolean enabledRoExported();
}
"#;
@@ -564,8 +584,8 @@
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags {
private static boolean aconfig_test_is_cached = false;
- private static boolean other_namespace_is_cached = false;
private static boolean disabledRwExported = false;
+ private static boolean enabledFixedRoExported = false;
private static boolean enabledRoExported = false;
@@ -574,6 +594,8 @@
Properties properties = DeviceConfig.getProperties("aconfig_test");
disabledRwExported =
properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false);
+ enabledFixedRoExported =
+ properties.getBoolean("com.android.aconfig.test.enabled_fixed_ro_exported", false);
enabledRoExported =
properties.getBoolean("com.android.aconfig.test.enabled_ro_exported", false);
} catch (NullPointerException e) {
@@ -589,22 +611,6 @@
aconfig_test_is_cached = true;
}
- private void load_overrides_other_namespace() {
- try {
- Properties properties = DeviceConfig.getProperties("other_namespace");
- } catch (NullPointerException e) {
- throw new RuntimeException(
- "Cannot read value from namespace other_namespace "
- + "from DeviceConfig. It could be that the code using flag "
- + "executed before SettingsProvider initialization. Please use "
- + "fixed read-only flag by adding is_fixed_read_only: true in "
- + "flag declaration.",
- e
- );
- }
- other_namespace_is_cached = true;
- }
-
@Override
@UnsupportedAppUsage
public boolean disabledRwExported() {
@@ -616,6 +622,15 @@
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ if (!aconfig_test_is_cached) {
+ load_overrides_aconfig_test();
+ }
+ return enabledFixedRoExported;
+ }
+
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRoExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
@@ -642,6 +657,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED);
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRoExported() {
return getValue(Flags.FLAG_ENABLED_RO_EXPORTED);
}
@@ -666,6 +686,7 @@
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
+ Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false)
)
);
@@ -703,12 +724,12 @@
#[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 mode = CodegenMode::Test;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#"
@@ -759,6 +780,12 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ throw new UnsupportedOperationException(
+ "Method is not implemented.");
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
@@ -807,6 +834,228 @@
}
#[test]
+ fn test_generate_java_code_force_read_only() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let mode = CodegenMode::ForceReadOnly;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
+ let expect_featureflags_content = r#"
+ package com.android.aconfig.test;
+ // TODO(b/303773055): Remove the annotation after access issue is resolved.
+ import android.compat.annotation.UnsupportedAppUsage;
+ /** @hide */
+ public interface FeatureFlags {
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ boolean disabledRo();
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ boolean disabledRw();
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ boolean disabledRwInOtherNamespace();
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ boolean enabledFixedRo();
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ boolean enabledRo();
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ boolean enabledRw();
+ }"#;
+
+ let expect_featureflagsimpl_content = r#"
+ package com.android.aconfig.test;
+ // TODO(b/303773055): Remove the annotation after access issue is resolved.
+ import android.compat.annotation.UnsupportedAppUsage;
+ /** @hide */
+ public final class FeatureFlagsImpl implements FeatureFlags {
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRo() {
+ return false;
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRw() {
+ return false;
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRwInOtherNamespace() {
+ return false;
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledFixedRo() {
+ return true;
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledRo() {
+ return true;
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledRw() {
+ return true;
+ }
+ }
+ "#;
+
+ let expect_flags_content = r#"
+ package com.android.aconfig.test;
+ // TODO(b/303773055): Remove the annotation after access issue is resolved.
+ import android.compat.annotation.UnsupportedAppUsage;
+ /** @hide */
+ public final class Flags {
+ /** @hide */
+ public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
+ /** @hide */
+ public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
+ /** @hide */
+ public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace";
+ /** @hide */
+ public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
+ /** @hide */
+ public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
+ /** @hide */
+ public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
+
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ public static boolean disabledRo() {
+ return FEATURE_FLAGS.disabledRo();
+ }
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ public static boolean disabledRw() {
+ return FEATURE_FLAGS.disabledRw();
+ }
+ @com.android.aconfig.annotations.AssumeFalseForR8
+ @UnsupportedAppUsage
+ public static boolean disabledRwInOtherNamespace() {
+ return FEATURE_FLAGS.disabledRwInOtherNamespace();
+ }
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ public static boolean enabledFixedRo() {
+ return FEATURE_FLAGS.enabledFixedRo();
+ }
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ public static boolean enabledRo() {
+ return FEATURE_FLAGS.enabledRo();
+ }
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
+ public static boolean enabledRw() {
+ return FEATURE_FLAGS.enabledRw();
+ }
+ private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
+ }"#;
+
+ let expect_fakefeatureflags_content = r#"
+ package com.android.aconfig.test;
+ // TODO(b/303773055): Remove the annotation after access issue is resolved.
+ import android.compat.annotation.UnsupportedAppUsage;
+ import java.util.HashMap;
+ import java.util.Map;
+ /** @hide */
+ public class FakeFeatureFlagsImpl implements FeatureFlags {
+ public FakeFeatureFlagsImpl() {
+ resetAll();
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRo() {
+ return getValue(Flags.FLAG_DISABLED_RO);
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRw() {
+ return getValue(Flags.FLAG_DISABLED_RW);
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean disabledRwInOtherNamespace() {
+ return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE);
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledFixedRo() {
+ return getValue(Flags.FLAG_ENABLED_FIXED_RO);
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledRo() {
+ return getValue(Flags.FLAG_ENABLED_RO);
+ }
+ @Override
+ @UnsupportedAppUsage
+ public boolean enabledRw() {
+ return getValue(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 getValue(String flagName) {
+ Boolean value = this.mFlagMap.get(flagName);
+ if (value == null) {
+ throw new IllegalArgumentException(flagName + " is not set");
+ }
+ return value;
+ }
+ private Map<String, Boolean> mFlagMap = new HashMap<>(
+ Map.ofEntries(
+ Map.entry(Flags.FLAG_DISABLED_RO, false),
+ Map.entry(Flags.FLAG_DISABLED_RW, false),
+ Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
+ Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
+ Map.entry(Flags.FLAG_ENABLED_RO, false),
+ Map.entry(Flags.FLAG_ENABLED_RW, false)
+ )
+ );
+ }
+ "#;
+ let mut file_set = HashMap::from([
+ ("com/android/aconfig/test/Flags.java", expect_flags_content),
+ ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
+ ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content),
+ ("com/android/aconfig/test/FakeFeatureFlagsImpl.java", expect_fakefeatureflags_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).unwrap()
+ ),
+ "File {} content is not correct",
+ file_path
+ );
+ file_set.remove(file_path);
+ }
+
+ assert!(file_set.is_empty());
+ }
+
+ #[test]
fn test_format_java_method_name() {
let expected = "someSnakeName";
let input = "____some_snake___name____";
diff --git a/tools/aconfig/src/codegen/mod.rs b/tools/aconfig/src/codegen/mod.rs
index abc27c6..64ffa8b 100644
--- a/tools/aconfig/src/codegen/mod.rs
+++ b/tools/aconfig/src/codegen/mod.rs
@@ -19,6 +19,7 @@
pub mod rust;
use anyhow::{ensure, Result};
+use clap::ValueEnum;
pub fn is_valid_name_ident(s: &str) -> bool {
// Identifiers must match [a-z][a-z0-9_]*, except consecutive underscores are not allowed
@@ -43,7 +44,7 @@
}
pub fn is_valid_container_ident(s: &str) -> bool {
- is_valid_name_ident(s) || s.split('.').all(is_valid_name_ident)
+ s.split('.').all(is_valid_name_ident)
}
pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
@@ -52,6 +53,25 @@
Ok(format!("{}.{}", package, flag_name))
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
+pub enum CodegenMode {
+ Exported,
+ ForceReadOnly,
+ Production,
+ Test,
+}
+
+impl std::fmt::Display for CodegenMode {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ CodegenMode::Exported => write!(f, "exported"),
+ CodegenMode::ForceReadOnly => write!(f, "force-read-only"),
+ CodegenMode::Production => write!(f, "production"),
+ CodegenMode::Test => write!(f, "test"),
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/tools/aconfig/src/codegen/rust.rs b/tools/aconfig/src/codegen/rust.rs
index 04be93b..8a88ffe 100644
--- a/tools/aconfig/src/codegen/rust.rs
+++ b/tools/aconfig/src/codegen/rust.rs
@@ -19,19 +19,20 @@
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
-pub fn generate_rust_code<'a, I>(
+pub fn generate_rust_code<I>(
package: &str,
parsed_flags_iter: I,
codegen_mode: CodegenMode,
) -> Result<OutputFile>
where
- I: Iterator<Item = &'a ProtoParsedFlag>,
+ I: Iterator<Item = ProtoParsedFlag>,
{
let template_flags: Vec<TemplateParsedFlag> =
- parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, pf)).collect();
+ parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, &pf)).collect();
let has_readwrite = template_flags.iter().any(|item| item.readwrite);
let context = TemplateContext {
package: package.to_string(),
@@ -43,10 +44,9 @@
template.add_template(
"rust_code_gen",
match codegen_mode {
- CodegenMode::Production => include_str!("../../templates/rust_prod.template"),
CodegenMode::Test => include_str!("../../templates/rust_test.template"),
- CodegenMode::Exported => {
- todo!("exported mode not yet supported for rust, see b/313894653.")
+ CodegenMode::Exported | CodegenMode::ForceReadOnly | CodegenMode::Production => {
+ include_str!("../../templates/rust.template")
}
},
)?;
@@ -153,6 +153,11 @@
true
}
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ true
+ }
+
/// query flag enabled_ro
pub fn enabled_ro(&self) -> bool {
true
@@ -202,6 +207,12 @@
true
}
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ true
+}
+
/// query flag enabled_ro
#[inline(always)]
pub fn enabled_ro() -> bool {
@@ -302,6 +313,18 @@
self.overrides.insert("enabled_fixed_ro", val);
}
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ self.overrides.get("enabled_fixed_ro_exported").copied().unwrap_or(
+ true
+ )
+ }
+
+ /// set flag enabled_fixed_ro_exported
+ pub fn set_enabled_fixed_ro_exported(&mut self, val: bool) {
+ self.overrides.insert("enabled_fixed_ro_exported", val);
+ }
+
/// query flag enabled_ro
pub fn enabled_ro(&self) -> bool {
self.overrides.get("enabled_ro").copied().unwrap_or(
@@ -412,6 +435,18 @@
PROVIDER.lock().unwrap().set_enabled_fixed_ro(val);
}
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ PROVIDER.lock().unwrap().enabled_fixed_ro_exported()
+}
+
+/// set flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn set_enabled_fixed_ro_exported(val: bool) {
+ PROVIDER.lock().unwrap().set_enabled_fixed_ro_exported(val);
+}
+
/// query flag enabled_ro
#[inline(always)]
pub fn enabled_ro() -> bool {
@@ -454,10 +489,156 @@
}
"#;
+ const EXPORTED_EXPECTED: &str = r#"
+//! codegenerated rust flag lib
+
+/// flag provider
+pub struct FlagProvider;
+
+lazy_static::lazy_static! {
+ /// flag value cache for disabled_rw_exported
+ static ref CACHED_disabled_rw_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "false") == "true";
+
+ /// flag value cache for enabled_fixed_ro_exported
+ static ref CACHED_enabled_fixed_ro_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "false") == "true";
+
+ /// flag value cache for enabled_ro_exported
+ static ref CACHED_enabled_ro_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "false") == "true";
+
+}
+
+impl FlagProvider {
+ /// query flag disabled_rw_exported
+ pub fn disabled_rw_exported(&self) -> bool {
+ *CACHED_disabled_rw_exported
+ }
+
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ *CACHED_enabled_fixed_ro_exported
+ }
+
+ /// query flag enabled_ro_exported
+ pub fn enabled_ro_exported(&self) -> bool {
+ *CACHED_enabled_ro_exported
+ }
+}
+
+/// flag provider
+pub static PROVIDER: FlagProvider = FlagProvider;
+
+/// query flag disabled_rw_exported
+#[inline(always)]
+pub fn disabled_rw_exported() -> bool {
+ PROVIDER.disabled_rw_exported()
+}
+
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ PROVIDER.enabled_fixed_ro_exported()
+}
+
+/// query flag enabled_ro_exported
+#[inline(always)]
+pub fn enabled_ro_exported() -> bool {
+ PROVIDER.enabled_ro_exported()
+}
+"#;
+
+ const FORCE_READ_ONLY_EXPECTED: &str = r#"
+//! codegenerated rust flag lib
+
+/// flag provider
+pub struct FlagProvider;
+
+impl FlagProvider {
+ /// query flag disabled_ro
+ pub fn disabled_ro(&self) -> bool {
+ false
+ }
+
+ /// query flag disabled_rw
+ pub fn disabled_rw(&self) -> bool {
+ false
+ }
+
+ /// query flag disabled_rw_in_other_namespace
+ pub fn disabled_rw_in_other_namespace(&self) -> bool {
+ false
+ }
+
+ /// query flag enabled_fixed_ro
+ pub fn enabled_fixed_ro(&self) -> bool {
+ true
+ }
+
+ /// query flag enabled_ro
+ pub fn enabled_ro(&self) -> bool {
+ true
+ }
+
+ /// query flag enabled_rw
+ pub fn enabled_rw(&self) -> bool {
+ true
+ }
+}
+
+/// flag provider
+pub static PROVIDER: FlagProvider = FlagProvider;
+
+/// query flag disabled_ro
+#[inline(always)]
+pub fn disabled_ro() -> bool {
+ false
+}
+
+/// query flag disabled_rw
+#[inline(always)]
+pub fn disabled_rw() -> bool {
+ false
+}
+
+/// query flag disabled_rw_in_other_namespace
+#[inline(always)]
+pub fn disabled_rw_in_other_namespace() -> bool {
+ false
+}
+
+/// query flag enabled_fixed_ro
+#[inline(always)]
+pub fn enabled_fixed_ro() -> bool {
+ true
+}
+
+/// query flag enabled_ro
+#[inline(always)]
+pub fn enabled_ro() -> bool {
+ true
+}
+
+/// query flag enabled_rw
+#[inline(always)]
+pub fn enabled_rw() -> bool {
+ true
+}
+"#;
+
fn test_generate_rust_code(mode: CodegenMode) {
let parsed_flags = crate::test::parse_test_flags();
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
let generated =
- generate_rust_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
+ generate_rust_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
.unwrap();
assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
assert_eq!(
@@ -466,8 +647,8 @@
match mode {
CodegenMode::Production => PROD_EXPECTED,
CodegenMode::Test => TEST_EXPECTED,
- CodegenMode::Exported =>
- todo!("exported mode not yet supported for rust, see b/313894653."),
+ CodegenMode::Exported => EXPORTED_EXPECTED,
+ CodegenMode::ForceReadOnly => FORCE_READ_ONLY_EXPECTED,
},
&String::from_utf8(generated.contents).unwrap()
)
@@ -483,4 +664,14 @@
fn test_generate_rust_code_for_test() {
test_generate_rust_code(CodegenMode::Test);
}
+
+ #[test]
+ fn test_generate_rust_code_for_exported() {
+ test_generate_rust_code(CodegenMode::Exported);
+ }
+
+ #[test]
+ fn test_generate_rust_code_for_force_read_only() {
+ test_generate_rust_code(CodegenMode::ForceReadOnly);
+ }
}
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index e437c02..f7a6417 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -15,20 +15,22 @@
*/
use anyhow::{bail, ensure, Context, Result};
-use clap::ValueEnum;
+use itertools::Itertools;
use protobuf::Message;
+use std::collections::HashMap;
use std::io::Read;
use std::path::PathBuf;
use crate::codegen::cpp::generate_cpp_code;
use crate::codegen::java::generate_java_code;
use crate::codegen::rust::generate_rust_code;
-use crate::storage::generate_storage_files;
-
+use crate::codegen::CodegenMode;
+use crate::dump::{DumpFormat, DumpPredicate};
use crate::protos::{
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
ProtoParsedFlags, ProtoTracepoint,
};
+use crate::storage::generate_storage_files;
pub struct Input {
pub source: String,
@@ -188,38 +190,37 @@
Ok(output)
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum CodegenMode {
- Production,
- Test,
- Exported,
-}
-
pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
- generate_java_code(package, filtered_parsed_flags.iter(), codegen_mode)
+ let package = package.to_string();
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_java_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
- generate_cpp_code(package, filtered_parsed_flags.iter(), codegen_mode)
+ let package = package.to_string();
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_cpp_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
- generate_rust_code(package, filtered_parsed_flags.iter(), codegen_mode)
+ let package = package.to_string();
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_rust_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_storage(caches: Vec<Input>, container: &str) -> Result<Vec<OutputFile>> {
@@ -276,73 +277,28 @@
Ok(output)
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum DumpFormat {
- Text,
- Verbose,
- Protobuf,
- Textproto,
- Bool,
-}
-
pub fn dump_parsed_flags(
mut input: Vec<Input>,
format: DumpFormat,
+ filters: &[&str],
dedup: bool,
) -> 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?, dedup)?;
-
- let mut output = Vec::new();
- match format {
- DumpFormat::Text => {
- for parsed_flag in parsed_flags.parsed_flag.into_iter() {
- let line = format!(
- "{} [{}]: {:?} + {:?}\n",
- parsed_flag.fully_qualified_name(),
- parsed_flag.container(),
- parsed_flag.permission(),
- parsed_flag.state()
- );
- output.extend_from_slice(line.as_bytes());
- }
- }
- DumpFormat::Verbose => {
- for parsed_flag in parsed_flags.parsed_flag.into_iter() {
- let sources: Vec<_> =
- parsed_flag.trace.iter().map(|tracepoint| tracepoint.source()).collect();
- let line = format!(
- "{} [{}]: {:?} + {:?} ({})\n",
- parsed_flag.fully_qualified_name(),
- parsed_flag.container(),
- parsed_flag.permission(),
- parsed_flag.state(),
- sources.join(", ")
- );
- output.extend_from_slice(line.as_bytes());
- }
- }
- DumpFormat::Protobuf => {
- 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());
- }
- DumpFormat::Bool => {
- for parsed_flag in parsed_flags.parsed_flag.into_iter() {
- let line = format!(
- "{}={:?}\n",
- parsed_flag.fully_qualified_name(),
- parsed_flag.state() == ProtoFlagState::ENABLED
- );
- output.extend_from_slice(line.as_bytes());
- }
- }
- }
- Ok(output)
+ let filters: Vec<Box<DumpPredicate>> = if filters.is_empty() {
+ vec![Box::new(|_| true)]
+ } else {
+ filters
+ .iter()
+ .map(|f| crate::dump::create_filter_predicate(f))
+ .collect::<Result<Vec<_>>>()?
+ };
+ crate::dump::dump_parsed_flags(
+ parsed_flags.parsed_flag.into_iter().filter(|flag| filters.iter().any(|p| p(flag))),
+ format,
+ )
}
fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
@@ -365,16 +321,65 @@
Some(container)
}
-fn filter_parsed_flags(
+pub fn modify_parsed_flags_based_on_mode(
parsed_flags: ProtoParsedFlags,
codegen_mode: CodegenMode,
-) -> Vec<ProtoParsedFlag> {
- match codegen_mode {
- CodegenMode::Exported => {
- parsed_flags.parsed_flag.into_iter().filter(|pf| pf.is_exported()).collect()
- }
- _ => parsed_flags.parsed_flag,
+) -> Result<Vec<ProtoParsedFlag>> {
+ fn exported_mode_flag_modifier(mut parsed_flag: ProtoParsedFlag) -> ProtoParsedFlag {
+ parsed_flag.set_state(ProtoFlagState::DISABLED);
+ parsed_flag.set_permission(ProtoFlagPermission::READ_WRITE);
+ parsed_flag.set_is_fixed_read_only(false);
+ parsed_flag
}
+
+ fn force_read_only_mode_flag_modifier(mut parsed_flag: ProtoParsedFlag) -> ProtoParsedFlag {
+ parsed_flag.set_permission(ProtoFlagPermission::READ_ONLY);
+ parsed_flag
+ }
+
+ let modified_parsed_flags: Vec<_> = match codegen_mode {
+ CodegenMode::Exported => parsed_flags
+ .parsed_flag
+ .into_iter()
+ .filter(|pf| pf.is_exported())
+ .map(exported_mode_flag_modifier)
+ .collect(),
+ CodegenMode::ForceReadOnly => parsed_flags
+ .parsed_flag
+ .into_iter()
+ .filter(|pf| !pf.is_exported())
+ .map(force_read_only_mode_flag_modifier)
+ .collect(),
+ CodegenMode::Production | CodegenMode::Test => {
+ parsed_flags.parsed_flag.into_iter().collect()
+ }
+ };
+ if modified_parsed_flags.is_empty() {
+ bail!("{codegen_mode} library contains no {codegen_mode} flags");
+ }
+
+ Ok(modified_parsed_flags)
+}
+
+pub fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u16>>
+where
+ I: Iterator<Item = &'a ProtoParsedFlag> + Clone,
+{
+ assert!(parsed_flags_iter.clone().tuple_windows().all(|(a, b)| a.name() <= b.name()));
+ let mut flag_ids = HashMap::new();
+ for (id_to_assign, pf) in (0_u32..).zip(parsed_flags_iter) {
+ if package != pf.package() {
+ return Err(anyhow::anyhow!("encountered a flag not in current package"));
+ }
+
+ // put a cap on how many flags a package can contain to 65535
+ if id_to_assign > u16::MAX as u32 {
+ return Err(anyhow::anyhow!("the number of flags in a package cannot exceed 65535"));
+ }
+
+ flag_ids.insert(pf.name().to_string(), id_to_assign as u16);
+ }
+ Ok(flag_ids)
}
#[cfg(test)]
@@ -408,9 +413,9 @@
assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state());
assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission());
- assert_eq!(8, parsed_flags.parsed_flag.len());
+ assert_eq!(9, parsed_flags.parsed_flag.len());
for pf in parsed_flags.parsed_flag.iter() {
- if pf.name() == "enabled_fixed_ro" {
+ if pf.name().starts_with("enabled_fixed_ro") {
continue;
}
let first = pf.trace.first().unwrap();
@@ -619,62 +624,33 @@
}
#[test]
- fn test_dump_text_format() {
+ fn test_dump() {
let input = parse_test_flags_as_input();
- let bytes = dump_parsed_flags(vec![input], DumpFormat::Text, false).unwrap();
- let text = std::str::from_utf8(&bytes).unwrap();
- assert!(
- text.contains("com.android.aconfig.test.disabled_ro [system]: READ_ONLY + DISABLED")
- );
- }
-
- #[test]
- fn test_dump_protobuf_format() {
- let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
- crate::test::TEST_FLAGS_TEXTPROTO,
+ let bytes = dump_parsed_flags(
+ vec![input],
+ DumpFormat::Custom("{fully_qualified_name}".to_string()),
+ &[],
+ false,
)
- .unwrap()
- .write_to_bytes()
.unwrap();
-
- let input = parse_test_flags_as_input();
- let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf, false).unwrap();
-
- assert_eq!(expected, actual);
- }
-
- #[test]
- fn test_dump_textproto_format() {
- let input = parse_test_flags_as_input();
- let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto, false).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
- assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+ assert!(text.contains("com.android.aconfig.test.disabled_ro"));
}
#[test]
fn test_dump_textproto_format_dedup() {
let input = parse_test_flags_as_input();
let input2 = parse_test_flags_as_input();
- let bytes = dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, true).unwrap();
+ let bytes =
+ dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
- assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
- }
-
- #[test]
- fn test_filter_parsed_flags() {
- let mut input = parse_test_flags_as_input();
- let parsed_flags = input.try_parse_flags().unwrap();
-
- let filtered_parsed_flags =
- filter_parsed_flags(parsed_flags.clone(), CodegenMode::Exported);
- assert_eq!(2, filtered_parsed_flags.len());
-
- let filtered_parsed_flags =
- filter_parsed_flags(parsed_flags.clone(), CodegenMode::Production);
- assert_eq!(8, filtered_parsed_flags.len());
-
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags.clone(), CodegenMode::Test);
- assert_eq!(8, filtered_parsed_flags.len());
+ assert_eq!(
+ None,
+ crate::test::first_significant_code_diff(
+ crate::test::TEST_FLAGS_TEXTPROTO.trim(),
+ text.trim()
+ )
+ );
}
fn parse_test_flags_as_input() -> Input {
@@ -684,4 +660,76 @@
let reader = Box::new(cursor);
Input { source: "test.data".to_string(), reader }
}
+
+ #[test]
+ fn test_modify_parsed_flags_based_on_mode_prod() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let p_parsed_flags =
+ modify_parsed_flags_based_on_mode(parsed_flags.clone(), CodegenMode::Production)
+ .unwrap();
+ assert_eq!(parsed_flags.parsed_flag.len(), p_parsed_flags.len());
+ for (i, item) in p_parsed_flags.iter().enumerate() {
+ assert!(parsed_flags.parsed_flag[i].eq(item));
+ }
+ }
+
+ #[test]
+ fn test_modify_parsed_flags_based_on_mode_exported() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let p_parsed_flags =
+ modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap();
+ assert_eq!(3, p_parsed_flags.len());
+ for flag in p_parsed_flags.iter() {
+ assert_eq!(ProtoFlagState::DISABLED, flag.state());
+ assert_eq!(ProtoFlagPermission::READ_WRITE, flag.permission());
+ assert!(!flag.is_fixed_read_only());
+ assert!(flag.is_exported());
+ }
+
+ let mut parsed_flags = crate::test::parse_test_flags();
+ parsed_flags.parsed_flag.retain(|pf| !pf.is_exported());
+ let error =
+ modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap_err();
+ assert_eq!("exported library contains no exported flags", format!("{:?}", error));
+ }
+
+ #[test]
+ fn test_assign_flag_ids() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let package = find_unique_package(&parsed_flags.parsed_flag).unwrap().to_string();
+ let flag_ids = assign_flag_ids(&package, parsed_flags.parsed_flag.iter()).unwrap();
+ let expected_flag_ids = HashMap::from([
+ (String::from("disabled_ro"), 0_u16),
+ (String::from("disabled_rw"), 1_u16),
+ (String::from("disabled_rw_exported"), 2_u16),
+ (String::from("disabled_rw_in_other_namespace"), 3_u16),
+ (String::from("enabled_fixed_ro"), 4_u16),
+ (String::from("enabled_fixed_ro_exported"), 5_u16),
+ (String::from("enabled_ro"), 6_u16),
+ (String::from("enabled_ro_exported"), 7_u16),
+ (String::from("enabled_rw"), 8_u16),
+ ]);
+ assert_eq!(flag_ids, expected_flag_ids);
+ }
+
+ #[test]
+ fn test_modify_parsed_flags_based_on_mode_force_read_only() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let p_parsed_flags =
+ modify_parsed_flags_based_on_mode(parsed_flags.clone(), CodegenMode::ForceReadOnly)
+ .unwrap();
+ assert_eq!(6, p_parsed_flags.len());
+ for pf in p_parsed_flags {
+ assert_eq!(ProtoFlagPermission::READ_ONLY, pf.permission());
+ }
+
+ let mut parsed_flags = crate::test::parse_test_flags();
+ parsed_flags.parsed_flag.retain_mut(|pf| pf.is_exported());
+ let error = modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::ForceReadOnly)
+ .unwrap_err();
+ assert_eq!(
+ "force-read-only library contains no force-read-only flags",
+ format!("{:?}", error)
+ );
+ }
}
diff --git a/tools/aconfig/src/dump.rs b/tools/aconfig/src/dump.rs
new file mode 100644
index 0000000..37368ee
--- /dev/null
+++ b/tools/aconfig/src/dump.rs
@@ -0,0 +1,408 @@
+/*
+ * 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 crate::protos::{
+ ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint,
+};
+use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+use anyhow::{anyhow, bail, Context, Result};
+use protobuf::Message;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DumpFormat {
+ Protobuf,
+ Textproto,
+ Custom(String),
+}
+
+impl TryFrom<&str> for DumpFormat {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ match value {
+ // protobuf formats
+ "protobuf" => Ok(Self::Protobuf),
+ "textproto" => Ok(Self::Textproto),
+ // custom format
+ _ => Ok(Self::Custom(value.to_owned())),
+ }
+ }
+}
+
+pub fn dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>>
+where
+ I: Iterator<Item = ProtoParsedFlag>,
+{
+ let mut output = Vec::new();
+ match format {
+ DumpFormat::Protobuf => {
+ let parsed_flags =
+ ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+ parsed_flags.write_to_vec(&mut output)?;
+ }
+ DumpFormat::Textproto => {
+ let parsed_flags =
+ ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+ let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
+ output.extend_from_slice(s.as_bytes());
+ }
+ DumpFormat::Custom(format) => {
+ for flag in parsed_flags_iter {
+ dump_custom_format(&flag, &format, &mut output);
+ }
+ }
+ }
+ Ok(output)
+}
+
+fn dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>) {
+ fn format_trace(trace: &[ProtoTracepoint]) -> String {
+ trace
+ .iter()
+ .map(|tracepoint| {
+ format!(
+ "{}: {:?} + {:?}",
+ tracepoint.source(),
+ tracepoint.permission(),
+ tracepoint.state()
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(", ")
+ }
+
+ fn format_trace_paths(trace: &[ProtoTracepoint]) -> String {
+ trace.iter().map(|tracepoint| tracepoint.source()).collect::<Vec<_>>().join(", ")
+ }
+
+ fn format_metadata(metadata: &ProtoFlagMetadata) -> String {
+ format!("{:?}", metadata.purpose())
+ }
+
+ let mut str = format
+ // ProtoParsedFlag fields
+ .replace("{package}", flag.package())
+ .replace("{name}", flag.name())
+ .replace("{namespace}", flag.namespace())
+ .replace("{description}", flag.description())
+ .replace("{bug}", &flag.bug.join(", "))
+ .replace("{state}", &format!("{:?}", flag.state()))
+ .replace("{state:bool}", &format!("{}", flag.state() == ProtoFlagState::ENABLED))
+ .replace("{permission}", &format!("{:?}", flag.permission()))
+ .replace("{trace}", &format_trace(&flag.trace))
+ .replace("{trace:paths}", &format_trace_paths(&flag.trace))
+ .replace("{is_fixed_read_only}", &format!("{}", flag.is_fixed_read_only()))
+ .replace("{is_exported}", &format!("{}", flag.is_exported()))
+ .replace("{container}", flag.container())
+ .replace("{metadata}", &format_metadata(&flag.metadata))
+ // ParsedFlagExt functions
+ .replace("{fully_qualified_name}", &flag.fully_qualified_name());
+ str.push('\n');
+ output.extend_from_slice(str.as_bytes());
+}
+
+pub type DumpPredicate = dyn Fn(&ProtoParsedFlag) -> bool;
+
+pub fn create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>> {
+ let predicates = filter
+ .split('+')
+ .map(|sub_filter| create_filter_predicate_single(sub_filter))
+ .collect::<Result<Vec<_>>>()?;
+ Ok(Box::new(move |flag| predicates.iter().all(|p| p(flag))))
+}
+
+fn create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>> {
+ fn enum_from_str<T>(expected: &[T], s: &str) -> Result<T>
+ where
+ T: std::fmt::Debug + Copy,
+ {
+ for candidate in expected.iter() {
+ if s == format!("{:?}", candidate) {
+ return Ok(*candidate);
+ }
+ }
+ let expected =
+ expected.iter().map(|state| format!("{:?}", state)).collect::<Vec<_>>().join(", ");
+ bail!("\"{s}\": not a valid flag state, expected one of {expected}");
+ }
+
+ let error_msg = format!("\"{filter}\": filter syntax error");
+ let (what, arg) = filter.split_once(':').ok_or_else(|| anyhow!(error_msg.clone()))?;
+ match what {
+ "package" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.package() == expected))
+ }
+ "name" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.name() == expected))
+ }
+ "namespace" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.namespace() == expected))
+ }
+ // description: not supported yet
+ "bug" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.bug.join(", ") == expected))
+ }
+ "state" => {
+ let expected = enum_from_str(&[ProtoFlagState::ENABLED, ProtoFlagState::DISABLED], arg)
+ .context(error_msg)?;
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.state() == expected))
+ }
+ "permission" => {
+ let expected = enum_from_str(
+ &[ProtoFlagPermission::READ_ONLY, ProtoFlagPermission::READ_WRITE],
+ arg,
+ )
+ .context(error_msg)?;
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.permission() == expected))
+ }
+ // trace: not supported yet
+ "is_fixed_read_only" => {
+ let expected: bool = arg.parse().context(error_msg)?;
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_fixed_read_only() == expected))
+ }
+ "is_exported" => {
+ let expected: bool = arg.parse().context(error_msg)?;
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_exported() == expected))
+ }
+ "container" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
+ }
+ // metadata: not supported yet
+ "fully_qualified_name" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.fully_qualified_name() == expected))
+ }
+ _ => Err(anyhow!(error_msg)),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::protos::ProtoParsedFlags;
+ use crate::test::parse_test_flags;
+ use protobuf::Message;
+
+ fn parse_enabled_ro_flag() -> ProtoParsedFlag {
+ parse_test_flags().parsed_flag.into_iter().find(|pf| pf.name() == "enabled_ro").unwrap()
+ }
+
+ #[test]
+ fn test_dumpformat_from_str() {
+ // supported format types
+ assert_eq!(DumpFormat::try_from("protobuf").unwrap(), DumpFormat::Protobuf);
+ assert_eq!(DumpFormat::try_from("textproto").unwrap(), DumpFormat::Textproto);
+ assert_eq!(
+ DumpFormat::try_from("foobar").unwrap(),
+ DumpFormat::Custom("foobar".to_owned())
+ );
+ }
+
+ #[test]
+ fn test_dump_parsed_flags_protobuf_format() {
+ let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
+ crate::test::TEST_FLAGS_TEXTPROTO,
+ )
+ .unwrap()
+ .write_to_bytes()
+ .unwrap();
+ let parsed_flags = parse_test_flags();
+ let actual =
+ dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Protobuf).unwrap();
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn test_dump_parsed_flags_textproto_format() {
+ let parsed_flags = parse_test_flags();
+ let bytes =
+ dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Textproto).unwrap();
+ let text = std::str::from_utf8(&bytes).unwrap();
+ assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+ }
+
+ #[test]
+ fn test_dump_parsed_flags_custom_format() {
+ macro_rules! assert_dump_parsed_flags_custom_format_contains {
+ ($format:expr, $expected:expr) => {
+ let parsed_flags = parse_test_flags();
+ let bytes = dump_parsed_flags(
+ parsed_flags.parsed_flag.into_iter(),
+ $format.try_into().unwrap(),
+ )
+ .unwrap();
+ let text = std::str::from_utf8(&bytes).unwrap();
+ assert!(text.contains($expected));
+ };
+ }
+
+ // custom format
+ assert_dump_parsed_flags_custom_format_contains!(
+ "{fully_qualified_name}={permission} + {state}",
+ "com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
+ );
+ }
+
+ #[test]
+ fn test_dump_custom_format() {
+ macro_rules! assert_custom_format {
+ ($format:expr, $expected:expr) => {
+ let flag = parse_enabled_ro_flag();
+ let mut bytes = vec![];
+ dump_custom_format(&flag, $format, &mut bytes);
+ let text = std::str::from_utf8(&bytes).unwrap();
+ assert_eq!(text, $expected);
+ };
+ }
+
+ assert_custom_format!("{package}", "com.android.aconfig.test\n");
+ assert_custom_format!("{name}", "enabled_ro\n");
+ assert_custom_format!("{namespace}", "aconfig_test\n");
+ assert_custom_format!("{description}", "This flag is ENABLED + READ_ONLY\n");
+ assert_custom_format!("{bug}", "abc\n");
+ assert_custom_format!("{state}", "ENABLED\n");
+ assert_custom_format!("{state:bool}", "true\n");
+ assert_custom_format!("{permission}", "READ_ONLY\n");
+ assert_custom_format!("{trace}", "tests/test.aconfig: READ_WRITE + DISABLED, tests/first.values: READ_WRITE + DISABLED, tests/second.values: READ_ONLY + ENABLED\n");
+ assert_custom_format!(
+ "{trace:paths}",
+ "tests/test.aconfig, tests/first.values, tests/second.values\n"
+ );
+ assert_custom_format!("{is_fixed_read_only}", "false\n");
+ assert_custom_format!("{is_exported}", "false\n");
+ assert_custom_format!("{container}", "system\n");
+ assert_custom_format!("{metadata}", "PURPOSE_BUGFIX\n");
+
+ assert_custom_format!("name={name}|state={state}", "name=enabled_ro|state=ENABLED\n");
+ assert_custom_format!("{state}{state}{state}", "ENABLEDENABLEDENABLED\n");
+ }
+
+ #[test]
+ fn test_create_filter_predicate() {
+ macro_rules! assert_create_filter_predicate {
+ ($filter:expr, $expected:expr) => {
+ let parsed_flags = parse_test_flags();
+ let predicate = create_filter_predicate($filter).unwrap();
+ let mut filtered_flags: Vec<String> = parsed_flags
+ .parsed_flag
+ .into_iter()
+ .filter(predicate)
+ .map(|flag| flag.fully_qualified_name())
+ .collect();
+ filtered_flags.sort();
+ assert_eq!(&filtered_flags, $expected);
+ };
+ }
+
+ assert_create_filter_predicate!(
+ "package:com.android.aconfig.test",
+ &[
+ "com.android.aconfig.test.disabled_ro",
+ "com.android.aconfig.test.disabled_rw",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "com.android.aconfig.test.disabled_rw_in_other_namespace",
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "com.android.aconfig.test.enabled_rw",
+ ]
+ );
+ assert_create_filter_predicate!(
+ "name:disabled_rw",
+ &["com.android.aconfig.test.disabled_rw"]
+ );
+ assert_create_filter_predicate!(
+ "namespace:other_namespace",
+ &["com.android.aconfig.test.disabled_rw_in_other_namespace"]
+ );
+ // description: not supported yet
+ assert_create_filter_predicate!("bug:123", &["com.android.aconfig.test.disabled_ro",]);
+ assert_create_filter_predicate!(
+ "state:ENABLED",
+ &[
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "com.android.aconfig.test.enabled_rw",
+ ]
+ );
+ assert_create_filter_predicate!(
+ "permission:READ_ONLY",
+ &[
+ "com.android.aconfig.test.disabled_ro",
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro",
+ "com.android.aconfig.test.enabled_ro_exported",
+ ]
+ );
+ // trace: not supported yet
+ assert_create_filter_predicate!(
+ "is_fixed_read_only:true",
+ &[
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ ]
+ );
+ assert_create_filter_predicate!(
+ "is_exported:true",
+ &[
+ "com.android.aconfig.test.disabled_rw_exported",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro_exported",
+ ]
+ );
+ assert_create_filter_predicate!(
+ "container:system",
+ &[
+ "com.android.aconfig.test.disabled_ro",
+ "com.android.aconfig.test.disabled_rw",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "com.android.aconfig.test.disabled_rw_in_other_namespace",
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "com.android.aconfig.test.enabled_rw",
+ ]
+ );
+ // metadata: not supported yet
+
+ // synthesized fields
+ assert_create_filter_predicate!(
+ "fully_qualified_name:com.android.aconfig.test.disabled_rw",
+ &["com.android.aconfig.test.disabled_rw"]
+ );
+
+ // multiple sub filters
+ assert_create_filter_predicate!(
+ "permission:READ_ONLY+state:ENABLED",
+ &[
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "com.android.aconfig.test.enabled_ro",
+ "com.android.aconfig.test.enabled_ro_exported",
+ ]
+ );
+ }
+}
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 63a50c8..6c4e241 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -26,13 +26,22 @@
mod codegen;
mod commands;
+mod dump;
mod protos;
mod storage;
+use codegen::CodegenMode;
+use dump::DumpFormat;
+
#[cfg(test)]
mod test;
-use commands::{CodegenMode, DumpFormat, Input, OutputFile};
+use commands::{Input, OutputFile};
+
+const HELP_DUMP_FILTER: &str = r#"
+Limit which flags to output. If multiple --filter arguments are provided, the output will be
+limited to flags that match any of the filters.
+"#;
fn cli() -> Command {
Command::new("aconfig")
@@ -61,7 +70,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -72,7 +81,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -83,7 +92,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -98,13 +107,22 @@
.arg(Arg::new("out").long("out").default_value("-")),
)
.subcommand(
- Command::new("dump")
+ Command::new("dump-cache")
+ .alias("dump")
.arg(Arg::new("cache").long("cache").action(ArgAction::Append))
.arg(
Arg::new("format")
.long("format")
- .value_parser(EnumValueParser::<commands::DumpFormat>::new())
- .default_value("text"),
+ .value_parser(|s: &str| DumpFormat::try_from(s))
+ .default_value(
+ "{fully_qualified_name} [{container}]: {permission} + {state}",
+ ),
+ )
+ .arg(
+ Arg::new("filter")
+ .long("filter")
+ .action(ArgAction::Append)
+ .help(HELP_DUMP_FILTER.trim()),
)
.arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
.arg(Arg::new("out").long("out").default_value("-")),
@@ -245,12 +263,17 @@
let path = get_required_arg::<String>(sub_matches, "out")?;
write_output_to_file_or_stdout(path, &output)?;
}
- Some(("dump", sub_matches)) => {
+ Some(("dump-cache", sub_matches)) => {
let input = open_zero_or_more_files(sub_matches, "cache")?;
let format = get_required_arg::<DumpFormat>(sub_matches, "format")
.context("failed to dump previously parsed flags")?;
+ let filters = sub_matches
+ .get_many::<String>("filter")
+ .unwrap_or_default()
+ .map(String::as_ref)
+ .collect::<Vec<_>>();
let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
- let output = commands::dump_parsed_flags(input, *format, *dedup)?;
+ let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
let path = get_required_arg::<String>(sub_matches, "out")?;
write_output_to_file_or_stdout(path, &output)?;
}
diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/src/storage/flag_table.rs
new file mode 100644
index 0000000..595217e
--- /dev/null
+++ b/tools/aconfig/src/storage/flag_table.rs
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2024 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 crate::commands::assign_flag_ids;
+use crate::storage::{self, FlagPackage};
+use anyhow::{anyhow, Result};
+
+#[derive(PartialEq, Debug)]
+pub struct FlagTableHeader {
+ pub version: u32,
+ pub container: String,
+ pub file_size: u32,
+ pub num_flags: u32,
+ pub bucket_offset: u32,
+ pub node_offset: u32,
+}
+
+impl FlagTableHeader {
+ fn new(container: &str, num_flags: u32) -> Self {
+ Self {
+ version: storage::FILE_VERSION,
+ container: String::from(container),
+ file_size: 0,
+ num_flags,
+ bucket_offset: 0,
+ node_offset: 0,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.version.to_le_bytes());
+ let container_bytes = self.container.as_bytes();
+ result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_size.to_le_bytes());
+ result.extend_from_slice(&self.num_flags.to_le_bytes());
+ result.extend_from_slice(&self.bucket_offset.to_le_bytes());
+ result.extend_from_slice(&self.node_offset.to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug, Clone)]
+pub struct FlagTableNode {
+ pub package_id: u32,
+ pub flag_name: String,
+ pub flag_type: u16,
+ pub flag_id: u16,
+ pub next_offset: Option<u32>,
+ pub bucket_index: u32,
+}
+
+impl FlagTableNode {
+ fn new(
+ package_id: u32,
+ flag_name: &str,
+ flag_type: u16,
+ flag_id: u16,
+ num_buckets: u32,
+ ) -> Self {
+ let full_flag_name = package_id.to_string() + "/" + flag_name;
+ let bucket_index = storage::get_bucket_index(&full_flag_name, num_buckets);
+ Self {
+ package_id,
+ flag_name: flag_name.to_string(),
+ flag_type,
+ flag_id,
+ next_offset: None,
+ bucket_index,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.package_id.to_le_bytes());
+ let name_bytes = self.flag_name.as_bytes();
+ result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(name_bytes);
+ result.extend_from_slice(&self.flag_type.to_le_bytes());
+ result.extend_from_slice(&self.flag_id.to_le_bytes());
+ result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct FlagTable {
+ pub header: FlagTableHeader,
+ pub buckets: Vec<Option<u32>>,
+ pub nodes: Vec<FlagTableNode>,
+}
+
+impl FlagTable {
+ fn create_nodes(package: &FlagPackage, num_buckets: u32) -> Result<Vec<FlagTableNode>> {
+ let flag_ids =
+ assign_flag_ids(package.package_name, package.boolean_flags.iter().copied())?;
+ package
+ .boolean_flags
+ .iter()
+ .map(|&pf| {
+ let fid = flag_ids
+ .get(pf.name())
+ .ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
+ // all flags are boolean value at the moment, thus using the last bit. When more
+ // flag value types are supported, flag value type information should come from the
+ // parsed flag, and we will set the flag_type bit mask properly.
+ let flag_type = 1;
+ Ok(FlagTableNode::new(package.package_id, pf.name(), flag_type, *fid, num_buckets))
+ })
+ .collect::<Result<Vec<_>>>()
+ }
+
+ pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+ // create table
+ let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
+ let num_buckets = storage::get_table_size(num_flags)?;
+
+ let mut table = Self {
+ header: FlagTableHeader::new(container, num_flags),
+ buckets: vec![None; num_buckets as usize],
+ nodes: packages
+ .iter()
+ .map(|pkg| FlagTable::create_nodes(pkg, num_buckets))
+ .collect::<Result<Vec<_>>>()?
+ .concat(),
+ };
+
+ // initialize all header fields
+ table.header.bucket_offset = table.header.as_bytes().len() as u32;
+ table.header.node_offset = table.header.bucket_offset + num_buckets * 4;
+ table.header.file_size = table.header.node_offset
+ + table.nodes.iter().map(|x| x.as_bytes().len()).sum::<usize>() as u32;
+
+ // sort nodes by bucket index for efficiency
+ table.nodes.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
+
+ // fill all node offset
+ let mut offset = table.header.node_offset;
+ for i in 0..table.nodes.len() {
+ let node_bucket_idx = table.nodes[i].bucket_index;
+ let next_node_bucket_idx = if i + 1 < table.nodes.len() {
+ Some(table.nodes[i + 1].bucket_index)
+ } else {
+ None
+ };
+
+ if table.buckets[node_bucket_idx as usize].is_none() {
+ table.buckets[node_bucket_idx as usize] = Some(offset);
+ }
+ offset += table.nodes[i].as_bytes().len() as u32;
+
+ if let Some(index) = next_node_bucket_idx {
+ if index == node_bucket_idx {
+ table.nodes[i].next_offset = Some(offset);
+ }
+ }
+ }
+
+ Ok(table)
+ }
+
+ pub fn as_bytes(&self) -> Vec<u8> {
+ [
+ self.header.as_bytes(),
+ self.buckets.iter().map(|v| v.unwrap_or(0).to_le_bytes()).collect::<Vec<_>>().concat(),
+ self.nodes.iter().map(|v| v.as_bytes()).collect::<Vec<_>>().concat(),
+ ]
+ .concat()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::storage::{
+ group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+ tests::read_u16_from_bytes, tests::read_u32_from_bytes,
+ };
+
+ impl FlagTableHeader {
+ // test only method to deserialize back into the header struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let mut head = 0;
+ Ok(Self {
+ version: read_u32_from_bytes(bytes, &mut head)?,
+ container: read_str_from_bytes(bytes, &mut head)?,
+ file_size: read_u32_from_bytes(bytes, &mut head)?,
+ num_flags: read_u32_from_bytes(bytes, &mut head)?,
+ bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
+ node_offset: read_u32_from_bytes(bytes, &mut head)?,
+ })
+ }
+ }
+
+ impl FlagTableNode {
+ // test only method to deserialize back into the node struct
+ fn from_bytes(bytes: &[u8], num_buckets: u32) -> Result<Self> {
+ let mut head = 0;
+ let mut node = Self {
+ package_id: read_u32_from_bytes(bytes, &mut head)?,
+ flag_name: read_str_from_bytes(bytes, &mut head)?,
+ flag_type: read_u16_from_bytes(bytes, &mut head)?,
+ flag_id: read_u16_from_bytes(bytes, &mut head)?,
+ next_offset: match read_u32_from_bytes(bytes, &mut head)? {
+ 0 => None,
+ val => Some(val),
+ },
+ bucket_index: 0,
+ };
+ let full_flag_name = node.package_id.to_string() + "/" + &node.flag_name;
+ node.bucket_index = storage::get_bucket_index(&full_flag_name, num_buckets);
+ Ok(node)
+ }
+
+ // create test baseline, syntactic sugar
+ fn new_expected(
+ package_id: u32,
+ flag_name: &str,
+ flag_type: u16,
+ flag_id: u16,
+ next_offset: Option<u32>,
+ bucket_index: u32,
+ ) -> Self {
+ Self {
+ package_id,
+ flag_name: flag_name.to_string(),
+ flag_type,
+ flag_id,
+ next_offset,
+ bucket_index,
+ }
+ }
+ }
+
+ impl FlagTable {
+ // test only method to deserialize back into the table struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let header = FlagTableHeader::from_bytes(bytes)?;
+ let num_flags = header.num_flags;
+ let num_buckets = storage::get_table_size(num_flags)?;
+ let mut head = header.as_bytes().len();
+ let buckets = (0..num_buckets)
+ .map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
+ 0 => None,
+ val => Some(val),
+ })
+ .collect();
+ let nodes = (0..num_flags)
+ .map(|_| {
+ let node = FlagTableNode::from_bytes(&bytes[head..], num_buckets).unwrap();
+ head += node.as_bytes().len();
+ node
+ })
+ .collect();
+
+ let table = Self { header, buckets, nodes };
+ Ok(table)
+ }
+ }
+
+ pub fn create_test_flag_table() -> Result<FlagTable> {
+ let caches = parse_all_test_flags();
+ let packages = group_flags_by_package(caches.iter());
+ FlagTable::new("system", &packages)
+ }
+
+ #[test]
+ // this test point locks down the table creation and each field
+ fn test_table_contents() {
+ let flag_table = create_test_flag_table();
+ assert!(flag_table.is_ok());
+
+ let header: &FlagTableHeader = &flag_table.as_ref().unwrap().header;
+ let expected_header = FlagTableHeader {
+ version: storage::FILE_VERSION,
+ container: String::from("system"),
+ file_size: 320,
+ num_flags: 8,
+ bucket_offset: 30,
+ node_offset: 98,
+ };
+ assert_eq!(header, &expected_header);
+
+ println!("{:?}", &flag_table.as_ref().unwrap().nodes);
+
+ let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
+ let expected_bucket: Vec<Option<u32>> = vec![
+ Some(98),
+ Some(124),
+ None,
+ None,
+ None,
+ Some(177),
+ None,
+ Some(203),
+ None,
+ Some(261),
+ None,
+ None,
+ None,
+ None,
+ None,
+ Some(293),
+ None,
+ ];
+ assert_eq!(buckets, &expected_bucket);
+
+ let nodes: &Vec<FlagTableNode> = &flag_table.as_ref().unwrap().nodes;
+ assert_eq!(nodes.len(), 8);
+
+ assert_eq!(nodes[0], FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None, 0));
+ assert_eq!(nodes[1], FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150), 1));
+ assert_eq!(nodes[2], FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None, 1));
+ assert_eq!(nodes[3], FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None, 5));
+ assert_eq!(
+ nodes[4],
+ FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235), 7)
+ );
+ assert_eq!(nodes[5], FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None, 7));
+ assert_eq!(nodes[6], FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None, 9));
+ assert_eq!(nodes[7], FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None, 15));
+ }
+
+ #[test]
+ // this test point locks down the table serialization
+ fn test_serialization() {
+ let flag_table = create_test_flag_table();
+ assert!(flag_table.is_ok());
+ let flag_table = flag_table.unwrap();
+
+ let header: &FlagTableHeader = &flag_table.header;
+ let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
+ assert!(reinterpreted_header.is_ok());
+ assert_eq!(header, &reinterpreted_header.unwrap());
+
+ let nodes: &Vec<FlagTableNode> = &flag_table.nodes;
+ let num_buckets = storage::get_table_size(header.num_flags).unwrap();
+ for node in nodes.iter() {
+ let reinterpreted_node = FlagTableNode::from_bytes(&node.as_bytes(), num_buckets);
+ assert!(reinterpreted_node.is_ok());
+ assert_eq!(node, &reinterpreted_node.unwrap());
+ }
+
+ let reinterpreted_table = FlagTable::from_bytes(&flag_table.as_bytes());
+ assert!(reinterpreted_table.is_ok());
+ assert_eq!(&flag_table, &reinterpreted_table.unwrap());
+ }
+}
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index 90e05f5..a28fccd 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -14,17 +14,50 @@
* limitations under the License.
*/
-use anyhow::Result;
-use std::collections::{HashMap, HashSet};
+pub mod flag_table;
+pub mod package_table;
+
+use anyhow::{anyhow, Result};
+use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
+use std::hash::{Hash, Hasher};
+use std::path::PathBuf;
use crate::commands::OutputFile;
use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
+
+pub const FILE_VERSION: u32 = 1;
+
+pub const HASH_PRIMES: [u32; 29] = [
+ 7, 17, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
+ 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
+ 402653189, 805306457, 1610612741,
+];
+
+/// Get the right hash table size given number of entries in the table. Use a
+/// load factor of 0.5 for performance.
+pub fn get_table_size(entries: u32) -> Result<u32> {
+ HASH_PRIMES
+ .iter()
+ .find(|&&num| num >= 2 * entries)
+ .copied()
+ .ok_or(anyhow!("Number of packages is too large"))
+}
+
+/// Get the corresponding bucket index given the key and number of buckets
+pub fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 {
+ let mut s = DefaultHasher::new();
+ val.hash(&mut s);
+ (s.finish() % num_buckets as u64) as u32
+}
pub struct FlagPackage<'a> {
pub package_name: &'a str,
pub package_id: u32,
pub flag_names: HashSet<&'a str>,
pub boolean_flags: Vec<&'a ProtoParsedFlag>,
+ // offset of the first boolean flag in this flag package with respect to the start of
+ // boolean flag value array in the flag value file
pub boolean_offset: u32,
}
@@ -52,7 +85,7 @@
{
// group flags by package
let mut packages: Vec<FlagPackage<'a>> = Vec::new();
- let mut package_index: HashMap<&'a str, usize> = HashMap::new();
+ let mut package_index: HashMap<&str, usize> = HashMap::new();
for parsed_flags in parsed_flags_vec_iter {
for parsed_flag in parsed_flags.parsed_flag.iter() {
let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
@@ -64,26 +97,38 @@
}
// calculate package flag value start offset, in flag value file, each boolean
- // is stored as two bytes, the first byte will be the flag value. the second
- // byte is flag info byte, which is a bitmask to indicate the status of a flag
+ // is stored as a single byte
let mut boolean_offset = 0;
for p in packages.iter_mut() {
p.boolean_offset = boolean_offset;
- boolean_offset += 2 * p.boolean_flags.len() as u32;
+ boolean_offset += p.boolean_flags.len() as u32;
}
packages
}
pub fn generate_storage_files<'a, I>(
- _containser: &str,
+ container: &str,
parsed_flags_vec_iter: I,
) -> Result<Vec<OutputFile>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
{
- let _packages = group_flags_by_package(parsed_flags_vec_iter);
- Ok(vec![])
+ let packages = group_flags_by_package(parsed_flags_vec_iter);
+
+ // create and serialize package map
+ let package_table = PackageTable::new(container, &packages)?;
+ let package_table_file_path = PathBuf::from("package.map");
+ let package_table_file =
+ OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
+
+ // create and serialize flag map
+ let flag_table = FlagTable::new(container, &packages)?;
+ let flag_table_file_path = PathBuf::from("flag.map");
+ let flag_table_file =
+ OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
+
+ Ok(vec![package_table_file, flag_table_file])
}
#[cfg(test)]
@@ -91,23 +136,45 @@
use super::*;
use crate::Input;
+ /// Read and parse bytes as u16
+ pub fn read_u16_from_bytes(buf: &[u8], head: &mut usize) -> Result<u16> {
+ let val = u16::from_le_bytes(buf[*head..*head + 2].try_into()?);
+ *head += 2;
+ Ok(val)
+ }
+
+ /// Read and parse bytes as u32
+ pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
+ let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
+ *head += 4;
+ Ok(val)
+ }
+
+ /// Read and parse bytes as string
+ pub fn read_str_from_bytes(buf: &[u8], head: &mut usize) -> Result<String> {
+ let num_bytes = read_u32_from_bytes(buf, head)? as usize;
+ let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())?;
+ *head += num_bytes;
+ Ok(val)
+ }
+
pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
let aconfig_files = [
(
"com.android.aconfig.storage.test_1",
- "storage_test_1_part_1.aconfig",
- include_bytes!("../../tests/storage_test_1_part_1.aconfig").as_slice(),
- ),
- (
- "com.android.aconfig.storage.test_1",
- "storage_test_1_part_2.aconfig",
- include_bytes!("../../tests/storage_test_1_part_2.aconfig").as_slice(),
+ "storage_test_1.aconfig",
+ include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
),
(
"com.android.aconfig.storage.test_2",
"storage_test_2.aconfig",
include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
),
+ (
+ "com.android.aconfig.storage.test_4",
+ "storage_test_4.aconfig",
+ include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
+ ),
];
aconfig_files
@@ -143,16 +210,14 @@
}
}
- assert_eq!(packages.len(), 2);
+ assert_eq!(packages.len(), 3);
assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
assert_eq!(packages[0].package_id, 0);
- assert_eq!(packages[0].flag_names.len(), 5);
+ assert_eq!(packages[0].flag_names.len(), 3);
assert!(packages[0].flag_names.contains("enabled_rw"));
assert!(packages[0].flag_names.contains("disabled_rw"));
assert!(packages[0].flag_names.contains("enabled_ro"));
- assert!(packages[0].flag_names.contains("disabled_ro"));
- assert!(packages[0].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[0].boolean_offset, 0);
assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
@@ -161,6 +226,13 @@
assert!(packages[1].flag_names.contains("enabled_ro"));
assert!(packages[1].flag_names.contains("disabled_ro"));
assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
- assert_eq!(packages[1].boolean_offset, 10);
+ assert_eq!(packages[1].boolean_offset, 3);
+
+ assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
+ assert_eq!(packages[2].package_id, 2);
+ assert_eq!(packages[2].flag_names.len(), 2);
+ assert!(packages[2].flag_names.contains("enabled_ro"));
+ assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
+ assert_eq!(packages[2].boolean_offset, 6);
}
}
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
new file mode 100644
index 0000000..0ce1349
--- /dev/null
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -0,0 +1,301 @@
+/*
+ * 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 crate::storage::{self, FlagPackage};
+use anyhow::Result;
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTableHeader {
+ pub version: u32,
+ pub container: String,
+ pub file_size: u32,
+ pub num_packages: u32,
+ pub bucket_offset: u32,
+ pub node_offset: u32,
+}
+
+impl PackageTableHeader {
+ fn new(container: &str, num_packages: u32) -> Self {
+ Self {
+ version: storage::FILE_VERSION,
+ container: String::from(container),
+ file_size: 0,
+ num_packages,
+ bucket_offset: 0,
+ node_offset: 0,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.version.to_le_bytes());
+ let container_bytes = self.container.as_bytes();
+ result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_size.to_le_bytes());
+ result.extend_from_slice(&self.num_packages.to_le_bytes());
+ result.extend_from_slice(&self.bucket_offset.to_le_bytes());
+ result.extend_from_slice(&self.node_offset.to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTableNode {
+ pub package_name: String,
+ pub package_id: u32,
+ // offset of the first boolean flag in this flag package with respect to the start of
+ // boolean flag value array in the flag value file
+ pub boolean_offset: u32,
+ pub next_offset: Option<u32>,
+ pub bucket_index: u32,
+}
+
+impl PackageTableNode {
+ fn new(package: &FlagPackage, num_buckets: u32) -> Self {
+ let bucket_index =
+ storage::get_bucket_index(&package.package_name.to_string(), num_buckets);
+ Self {
+ package_name: String::from(package.package_name),
+ package_id: package.package_id,
+ boolean_offset: package.boolean_offset,
+ next_offset: None,
+ bucket_index,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ let name_bytes = self.package_name.as_bytes();
+ result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(name_bytes);
+ result.extend_from_slice(&self.package_id.to_le_bytes());
+ result.extend_from_slice(&self.boolean_offset.to_le_bytes());
+ result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTable {
+ pub header: PackageTableHeader,
+ pub buckets: Vec<Option<u32>>,
+ pub nodes: Vec<PackageTableNode>,
+}
+
+impl PackageTable {
+ pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+ // create table
+ let num_packages = packages.len() as u32;
+ let num_buckets = storage::get_table_size(num_packages)?;
+ let mut table = Self {
+ header: PackageTableHeader::new(container, num_packages),
+ buckets: vec![None; num_buckets as usize],
+ nodes: packages.iter().map(|pkg| PackageTableNode::new(pkg, num_buckets)).collect(),
+ };
+
+ // initialize all header fields
+ table.header.bucket_offset = table.header.as_bytes().len() as u32;
+ table.header.node_offset = table.header.bucket_offset + num_buckets * 4;
+ table.header.file_size = table.header.node_offset
+ + table.nodes.iter().map(|x| x.as_bytes().len()).sum::<usize>() as u32;
+
+ // sort nodes by bucket index for efficiency
+ table.nodes.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
+
+ // fill all node offset
+ let mut offset = table.header.node_offset;
+ for i in 0..table.nodes.len() {
+ let node_bucket_idx = table.nodes[i].bucket_index;
+ let next_node_bucket_idx = if i + 1 < table.nodes.len() {
+ Some(table.nodes[i + 1].bucket_index)
+ } else {
+ None
+ };
+
+ if table.buckets[node_bucket_idx as usize].is_none() {
+ table.buckets[node_bucket_idx as usize] = Some(offset);
+ }
+ offset += table.nodes[i].as_bytes().len() as u32;
+
+ if let Some(index) = next_node_bucket_idx {
+ if index == node_bucket_idx {
+ table.nodes[i].next_offset = Some(offset);
+ }
+ }
+ }
+
+ Ok(table)
+ }
+
+ pub fn as_bytes(&self) -> Vec<u8> {
+ [
+ self.header.as_bytes(),
+ self.buckets.iter().map(|v| v.unwrap_or(0).to_le_bytes()).collect::<Vec<_>>().concat(),
+ self.nodes.iter().map(|v| v.as_bytes()).collect::<Vec<_>>().concat(),
+ ]
+ .concat()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::storage::{
+ group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+ tests::read_u32_from_bytes,
+ };
+
+ impl PackageTableHeader {
+ // test only method to deserialize back into the header struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let mut head = 0;
+ Ok(Self {
+ version: read_u32_from_bytes(bytes, &mut head)?,
+ container: read_str_from_bytes(bytes, &mut head)?,
+ file_size: read_u32_from_bytes(bytes, &mut head)?,
+ num_packages: read_u32_from_bytes(bytes, &mut head)?,
+ bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
+ node_offset: read_u32_from_bytes(bytes, &mut head)?,
+ })
+ }
+ }
+
+ impl PackageTableNode {
+ // test only method to deserialize back into the node struct
+ fn from_bytes(bytes: &[u8], num_buckets: u32) -> Result<Self> {
+ let mut head = 0;
+ let mut node = Self {
+ package_name: read_str_from_bytes(bytes, &mut head)?,
+ package_id: read_u32_from_bytes(bytes, &mut head)?,
+ boolean_offset: read_u32_from_bytes(bytes, &mut head)?,
+ next_offset: match read_u32_from_bytes(bytes, &mut head)? {
+ 0 => None,
+ val => Some(val),
+ },
+ bucket_index: 0,
+ };
+ node.bucket_index = storage::get_bucket_index(&node.package_name, num_buckets);
+ Ok(node)
+ }
+ }
+
+ impl PackageTable {
+ // test only method to deserialize back into the table struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let header = PackageTableHeader::from_bytes(bytes)?;
+ let num_packages = header.num_packages;
+ let num_buckets = storage::get_table_size(num_packages)?;
+ let mut head = header.as_bytes().len();
+ let buckets = (0..num_buckets)
+ .map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
+ 0 => None,
+ val => Some(val),
+ })
+ .collect();
+ let nodes = (0..num_packages)
+ .map(|_| {
+ let node = PackageTableNode::from_bytes(&bytes[head..], num_buckets).unwrap();
+ head += node.as_bytes().len();
+ node
+ })
+ .collect();
+
+ let table = Self { header, buckets, nodes };
+ Ok(table)
+ }
+ }
+
+ pub fn create_test_package_table() -> Result<PackageTable> {
+ let caches = parse_all_test_flags();
+ let packages = group_flags_by_package(caches.iter());
+ PackageTable::new("system", &packages)
+ }
+
+ #[test]
+ // this test point locks down the table creation and each field
+ fn test_table_contents() {
+ let package_table = create_test_package_table();
+ assert!(package_table.is_ok());
+
+ let header: &PackageTableHeader = &package_table.as_ref().unwrap().header;
+ let expected_header = PackageTableHeader {
+ version: storage::FILE_VERSION,
+ container: String::from("system"),
+ file_size: 208,
+ num_packages: 3,
+ bucket_offset: 30,
+ node_offset: 58,
+ };
+ assert_eq!(header, &expected_header);
+
+ let buckets: &Vec<Option<u32>> = &package_table.as_ref().unwrap().buckets;
+ let expected: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ assert_eq!(buckets, &expected);
+
+ let nodes: &Vec<PackageTableNode> = &package_table.as_ref().unwrap().nodes;
+ assert_eq!(nodes.len(), 3);
+ let first_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_2"),
+ package_id: 1,
+ boolean_offset: 3,
+ next_offset: None,
+ bucket_index: 0,
+ };
+ assert_eq!(nodes[0], first_node_expected);
+ let second_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_1"),
+ package_id: 0,
+ boolean_offset: 0,
+ next_offset: Some(158),
+ bucket_index: 3,
+ };
+ assert_eq!(nodes[1], second_node_expected);
+ let third_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_4"),
+ package_id: 2,
+ boolean_offset: 6,
+ next_offset: None,
+ bucket_index: 3,
+ };
+ assert_eq!(nodes[2], third_node_expected);
+ }
+
+ #[test]
+ // this test point locks down the table serialization
+ fn test_serialization() {
+ let package_table = create_test_package_table();
+ assert!(package_table.is_ok());
+ let package_table = package_table.unwrap();
+
+ let header: &PackageTableHeader = &package_table.header;
+ let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());
+ assert!(reinterpreted_header.is_ok());
+ assert_eq!(header, &reinterpreted_header.unwrap());
+
+ let nodes: &Vec<PackageTableNode> = &package_table.nodes;
+ let num_buckets = storage::get_table_size(header.num_packages).unwrap();
+ for node in nodes.iter() {
+ let reinterpreted_node = PackageTableNode::from_bytes(&node.as_bytes(), num_buckets);
+ assert!(reinterpreted_node.is_ok());
+ assert_eq!(node, &reinterpreted_node.unwrap());
+ }
+
+ let reinterpreted_table = PackageTable::from_bytes(&package_table.as_bytes());
+ assert!(reinterpreted_table.is_ok());
+ assert_eq!(&package_table, &reinterpreted_table.unwrap());
+ }
+}
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 71de57e..cbb95b8 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -145,6 +145,31 @@
}
parsed_flag {
package: "com.android.aconfig.test"
+ name: "enabled_fixed_ro_exported"
+ namespace: "aconfig_test"
+ description: "This flag is fixed ENABLED + READ_ONLY and exported"
+ bug: "111"
+ state: ENABLED
+ permission: READ_ONLY
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_ONLY
+ }
+ trace {
+ source: "tests/first.values"
+ state: ENABLED
+ permission: READ_ONLY
+ }
+ is_fixed_read_only: true
+ is_exported: true
+ container: "system"
+ metadata {
+ purpose: PURPOSE_UNSPECIFIED
+ }
+}
+parsed_flag {
+ package: "com.android.aconfig.test"
name: "enabled_ro"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_ONLY"
@@ -225,6 +250,24 @@
}
"#;
+ pub fn parse_read_only_test_flags() -> ProtoParsedFlags {
+ let bytes = crate::commands::parse_flags(
+ "com.android.aconfig.test",
+ Some("system"),
+ vec![Input {
+ source: "tests/read_only_test.aconfig".to_string(),
+ reader: Box::new(include_bytes!("../tests/read_only_test.aconfig").as_slice()),
+ }],
+ vec![Input {
+ source: "tests/read_only_test.values".to_string(),
+ reader: Box::new(include_bytes!("../tests/read_only_test.values").as_slice()),
+ }],
+ crate::commands::DEFAULT_FLAG_PERMISSION,
+ )
+ .unwrap();
+ crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+ }
+
pub fn parse_test_flags() -> ProtoParsedFlags {
let bytes = crate::commands::parse_flags(
"com.android.aconfig.test",
diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
index 8010b88..933d6a7 100644
--- a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -12,23 +12,11 @@
}
{{ for item in flag_elements}}
-{{ if library_exported }}
-
-{{ if item.exported }}
@Override
@UnsupportedAppUsage
public boolean {item.method_name}() \{
return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
}
-{{ endif }}
-
-{{ else }}
- @Override
- @UnsupportedAppUsage
- public boolean {item.method_name}() \{
- return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
- }
-{{ endif }}
{{ endfor}}
public void setFlag(String flagName, boolean value) \{
if (!this.mFlagMap.containsKey(flagName)) \{
@@ -52,20 +40,11 @@
}
private Map<String, Boolean> mFlagMap = new HashMap<>(
- {{ if library_exported }}
- Map.ofEntries(
- {{-for item in exported_flag_elements}}
- Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
- {{ -if not @last }},{{ endif }}
- {{ -endfor }}
- )
- {{ else }}
Map.ofEntries(
{{-for item in flag_elements}}
Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
{{ -if not @last }},{{ endif }}
{{ -endfor }}
)
- {{ endif }}
);
}
diff --git a/tools/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/templates/FeatureFlags.java.template
index 180f882..5e67b13 100644
--- a/tools/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/templates/FeatureFlags.java.template
@@ -5,15 +5,10 @@
/** @hide */
public interface FeatureFlags \{
{{ for item in flag_elements }}
-{{ if library_exported }}
-
-{{ if item.exported }}
+{{ -if library_exported }}
@UnsupportedAppUsage
boolean {item.method_name}();
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
@@ -23,7 +18,6 @@
{{ endif }}
@UnsupportedAppUsage
boolean {item.method_name}();
-
{{ endif }}
-{{ endfor }}
+{{ -endfor }}
}
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 7a52ceb..28baa41 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,54 +1,42 @@
package {package_name};
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-{{ if not is_test_mode }}
-{{ if runtime_lookup_required- }}
+{{ -if not is_test_mode }}
+{{ -if runtime_lookup_required }}
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
{{ endif }}
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags \{
-{{- if runtime_lookup_required }}
-{{- for namespace_with_flags in namespace_flags }}
+{{ -if runtime_lookup_required }}
+{{ -for namespace_with_flags in namespace_flags }}
private static boolean {namespace_with_flags.namespace}_is_cached = false;
-{{- endfor- }}
+{{ -endfor- }}
{{ for flag in flag_elements }}
-{{ if library_exported }}
-{{ if flag.exported }}
+{{ -if library_exported }}
private static boolean {flag.method_name} = false;
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{- if flag.is_read_write }}
private static boolean {flag.method_name} = {flag.default_value};
{{- endif- }}
-{{ endif }}
-{{ endfor }}
-
+{{ -endif }}
+{{ -endfor }}
{{ for namespace_with_flags in namespace_flags }}
private void load_overrides_{namespace_with_flags.namespace}() \{
try \{
Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
-
- {{- for flag in namespace_with_flags.flags }}
- {{ if library_exported }}
-
- {{ if flag.exported }}
+{{ -for flag in namespace_with_flags.flags }}
+{{ -if library_exported }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", false);
- {{ endif }}
-
- {{ else }}
-
- {{ if flag.is_read_write }}
+{{ -else }}
+{{ -if flag.is_read_write }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", {flag.default_value});
- {{ endif }}
-
- {{ endif }}
- {{ endfor }}
+{{ -endif }}
+{{ -endif }}
+{{ -endfor }}
} catch (NullPointerException e) \{
throw new RuntimeException(
"Cannot read value from namespace {namespace_with_flags.namespace} "
@@ -62,37 +50,27 @@
{namespace_with_flags.namespace}_is_cached = true;
}
{{ endfor- }}
-{{ endif- }}
-
-{{ for flag in flag_elements }}
-{{ if library_exported }}
-
-{{ if flag.exported }}
+{{ -endif }}{#- end of runtime_lookup_required #}
+{{ -for flag in flag_elements }}
@Override
@UnsupportedAppUsage
public boolean {flag.method_name}() \{
+{{ -if library_exported }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
}
return {flag.method_name};
- }
-{{ endif }}
-
-{{ else }}
- @Override
- @UnsupportedAppUsage
- public boolean {flag.method_name}() \{
- {{ -if flag.is_read_write }}
+{{ -else }}
+{{ -if flag.is_read_write }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
}
return {flag.method_name};
- {{ else }}
+{{ -else }}
return {flag.default_value};
- {{ endif- }}
+{{ -endif- }}
+{{ -endif }}
}
-{{ endif }}
-
{{ endfor }}
}
{{ else }}
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 9f4c52f..34b8189 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -5,42 +5,30 @@
/** @hide */
public final class Flags \{
-{{- for item in flag_elements}}
- {{ if library_exported }}
- {{ if item.exported }}
+{{ -for item in flag_elements}}
/** @hide */
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
- {{ endif }}
- {{ else }}
- /** @hide */
- public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
- {{ endif }}
{{- endfor }}
-{{ for item in flag_elements}}
+{{ -for item in flag_elements}}
{{ if library_exported }}
-
-{{ if item.exported }}
@UnsupportedAppUsage
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
{{ -else }}
@com.android.aconfig.annotations.AssumeFalseForR8
-{{ -endif- }}
-{{ endif }}
+{{ -endif }}
+{{ -endif }}
@UnsupportedAppUsage
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ endif }}
-{{ endfor }}
+{{ -endif }}
+{{ -endfor }}
{{ -if is_test_mode }}
public static void setFeatureFlags(FeatureFlags featureFlags) \{
Flags.FEATURE_FLAGS = featureFlags;
@@ -49,7 +37,8 @@
public static void unsetFeatureFlags() \{
Flags.FEATURE_FLAGS = null;
}
-{{ endif }}
+{{ -endif }}
+
private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }};
}
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
index cc1b18d..0f7853e 100644
--- a/tools/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -1,16 +1,16 @@
#pragma once
-{{ if not for_test- }}
+{{ if not is_test_mode- }}
{{ if has_fixed_read_only- }}
#ifndef {package_macro}
#define {package_macro}(FLAG) {package_macro}_##FLAG
#endif
-{{ for item in class_elements- }}
-{{ if item.is_fixed_read_only- }}
+{{ for item in class_elements }}
+{{ -if item.is_fixed_read_only }}
#ifndef {package_macro}_{item.flag_macro}
#define {package_macro}_{item.flag_macro} {item.default_value}
#endif
-{{ endif }}
+{{ -endif }}
{{ -endfor }}
{{ -endif }}
{{ -endif }}
@@ -24,15 +24,15 @@
class flag_provider_interface \{
public:
virtual ~flag_provider_interface() = default;
- {{ for item in class_elements}}
+ {{ -for item in class_elements}}
virtual bool {item.flag_name}() = 0;
- {{ if for_test }}
+ {{ -if is_test_mode }}
virtual void {item.flag_name}(bool val) = 0;
{{ -endif }}
{{ -endfor }}
- {{ if for_test }}
+ {{ -if is_test_mode }}
virtual void reset_flags() \{}
{{ -endif }}
};
@@ -41,29 +41,29 @@
{{ for item in class_elements}}
inline bool {item.flag_name}() \{
- {{ if for_test }}
+ {{ -if is_test_mode }}
return provider_->{item.flag_name}();
- {{ -else- }}
- {{ if item.readwrite- }}
+ {{ -else }}
+ {{ -if item.readwrite }}
return provider_->{item.flag_name}();
- {{ -else- }}
- {{ if item.is_fixed_read_only }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
{{ -endif }}
}
-{{ if for_test }}
+{{ -if is_test_mode }}
inline void {item.flag_name}(bool val) \{
provider_->{item.flag_name}(val);
}
{{ -endif }}
{{ -endfor }}
-{{ if for_test }}
+{{ -if is_test_mode }}
inline void reset_flags() \{
return provider_->reset_flags();
}
@@ -77,12 +77,12 @@
{{ for item in class_elements }}
bool {header}_{item.flag_name}();
-{{ if for_test }}
+{{ -if is_test_mode }}
void set_{header}_{item.flag_name}(bool val);
{{ -endif }}
{{ -endfor }}
-{{ if for_test }}
+{{ -if is_test_mode }}
void {header}_reset_flags();
{{ -endif }}
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
index 1bfa4b6..4bcd1b7 100644
--- a/tools/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -1,17 +1,20 @@
#include "{header}.h"
-{{ if readwrite }}
+
+{{ if readwrite- }}
#include <server_configurable_flags/get_flags.h>
{{ endif }}
-{{ if for_test }}
+{{ if is_test_mode }}
#include <unordered_map>
#include <string>
{{ -else- }}
+{{ if readwrite- }}
#include <vector>
-{{ endif }}
+{{ -endif }}
+{{ -endif }}
namespace {cpp_namespace} \{
-{{ if for_test }}
+{{ if is_test_mode }}
class flag_provider : public flag_provider_interface \{
private:
std::unordered_map<std::string, bool> overrides_;
@@ -21,7 +24,7 @@
: overrides_()
\{}
- {{ for item in class_elements}}
+{{ for item in class_elements }}
virtual bool {item.flag_name}() override \{
auto it = overrides_.find("{item.flag_name}");
if (it != overrides_.end()) \{
@@ -32,7 +35,7 @@
"aconfig_flags.{item.device_config_namespace}",
"{item.device_config_flag}",
"{item.default_value}") == "true";
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
}
@@ -41,7 +44,7 @@
virtual void {item.flag_name}(bool val) override \{
overrides_["{item.flag_name}"] = val;
}
- {{ endfor }}
+{{ endfor }}
virtual void reset_flags() override \{
overrides_.clear();
@@ -52,9 +55,11 @@
class flag_provider : public flag_provider_interface \{
public:
- {{ for item in class_elements}}
+
+ {{ -for item in class_elements }}
+
virtual bool {item.flag_name}() override \{
- {{ if item.readwrite- }}
+ {{ -if item.readwrite }}
if (cache_[{item.readwrite_idx}] == -1) \{
cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.{item.device_config_namespace}",
@@ -62,17 +67,19 @@
"{item.default_value}") == "true";
}
return cache_[{item.readwrite_idx}];
- {{ -else- }}
- {{ if item.is_fixed_read_only }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
}
- {{ endfor }}
+ {{ -endfor }}
+ {{ if readwrite- }}
private:
std::vector<int8_t> cache_ = std::vector<int8_t>({readwrite_count}, -1);
+ {{ -endif }}
};
@@ -82,32 +89,31 @@
std::make_unique<flag_provider>();
}
-
-{{ for item in class_elements}}
+{{ for item in class_elements }}
bool {header}_{item.flag_name}() \{
- {{ if for_test }}
+ {{ -if is_test_mode }}
return {cpp_namespace}::{item.flag_name}();
- {{ -else- }}
- {{ if item.readwrite- }}
+ {{ -else }}
+ {{ -if item.readwrite }}
return {cpp_namespace}::{item.flag_name}();
- {{ -else- }}
- {{ if item.is_fixed_read_only }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
{{ -endif }}
}
-{{ if for_test }}
+{{ -if is_test_mode }}
void set_{header}_{item.flag_name}(bool val) \{
{cpp_namespace}::{item.flag_name}(val);
}
{{ -endif }}
-{{ endfor -}}
+{{ endfor }}
-{{ if for_test }}
+{{ -if is_test_mode }}
void {header}_reset_flags() \{
{cpp_namespace}::reset_flags();
}
diff --git a/tools/aconfig/templates/rust_prod.template b/tools/aconfig/templates/rust.template
similarity index 77%
rename from tools/aconfig/templates/rust_prod.template
rename to tools/aconfig/templates/rust.template
index 30ea646..f9a2829 100644
--- a/tools/aconfig/templates/rust_prod.template
+++ b/tools/aconfig/templates/rust.template
@@ -3,32 +3,32 @@
/// flag provider
pub struct FlagProvider;
-{{ if has_readwrite - }}
+{{ if has_readwrite- }}
lazy_static::lazy_static! \{
- {{ for flag in template_flags }}
- {{ if flag.readwrite -}}
+{{ -for flag in template_flags }}
+ {{ -if flag.readwrite }}
/// flag value cache for {flag.name}
static ref CACHED_{flag.name}: bool = flags_rust::GetServerConfigurableFlag(
"aconfig_flags.{flag.device_config_namespace}",
"{flag.device_config_flag}",
"{flag.default_value}") == "true";
{{ -endif }}
- {{ endfor }}
+{{ -endfor }}
}
{{ -endif }}
impl FlagProvider \{
- {{ for flag in template_flags }}
+{{ for flag in template_flags }}
/// query flag {flag.name}
pub fn {flag.name}(&self) -> bool \{
- {{ if flag.readwrite -}}
+ {{ -if flag.readwrite }}
*CACHED_{flag.name}
- {{ -else- }}
+ {{ -else }}
{flag.default_value}
{{ -endif }}
}
- {{ endfor }}
+{{ endfor }}
}
@@ -38,10 +38,10 @@
{{ for flag in template_flags }}
/// query flag {flag.name}
#[inline(always)]
-{{ if flag.readwrite -}}
+{{ -if flag.readwrite }}
pub fn {flag.name}() -> bool \{
PROVIDER.{flag.name}()
-{{ -else- }}
+{{ -else }}
pub fn {flag.name}() -> bool \{
{flag.default_value}
{{ -endif }}
diff --git a/tools/aconfig/templates/rust_test.template b/tools/aconfig/templates/rust_test.template
index fd1229b..d01f40a 100644
--- a/tools/aconfig/templates/rust_test.template
+++ b/tools/aconfig/templates/rust_test.template
@@ -9,7 +9,7 @@
}
impl FlagProvider \{
- {{ for flag in template_flags }}
+{{ for flag in template_flags }}
/// query flag {flag.name}
pub fn {flag.name}(&self) -> bool \{
self.overrides.get("{flag.name}").copied().unwrap_or(
@@ -28,7 +28,7 @@
pub fn set_{flag.name}(&mut self, val: bool) \{
self.overrides.insert("{flag.name}", val);
}
- {{ endfor }}
+{{ endfor }}
/// clear all flag overrides
pub fn reset_flags(&mut self) \{
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index bb993c4..7e76efb 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -10,6 +10,7 @@
import static com.android.aconfig.test.Flags.enabledRw;
import static com.android.aconfig.test.exported.Flags.exportedFlag;
import static com.android.aconfig.test.exported.Flags.FLAG_EXPORTED_FLAG;
+import static com.android.aconfig.test.forcereadonly.Flags.froRw;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -33,17 +34,13 @@
@Test
public void testEnabledReadOnlyFlag() {
assertEquals("com.android.aconfig.test.disabled_rw", FLAG_DISABLED_RW);
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledRo());
+ assertTrue(enabledRo());
}
@Test
public void testEnabledFixedReadOnlyFlag() {
assertEquals("com.android.aconfig.test.enabled_fixed_ro", FLAG_ENABLED_FIXED_RO);
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledFixedRo());
+ assertTrue(enabledFixedRo());
}
@Test
@@ -55,9 +52,7 @@
@Test
public void testEnabledReadWriteFlag() {
assertEquals("com.android.aconfig.test.enabled_rw", FLAG_ENABLED_RW);
- // TODO: change to assertTrue(enabledRw()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledRw());
+ assertTrue(enabledRw());
}
@Test
@@ -72,4 +67,9 @@
assertEquals("com.android.aconfig.test.exported.exported_flag", FLAG_EXPORTED_FLAG);
assertFalse(exportedFlag());
}
+
+ @Test
+ public void testForceReadOnly() {
+ assertFalse(froRw());
+ }
}
diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.cpp b/tools/aconfig/tests/aconfig_exported_mode_test.cpp
new file mode 100644
index 0000000..d6eab43
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_exported_mode_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "com_android_aconfig_test.h"
+#include "gtest/gtest.h"
+
+using namespace com::android::aconfig::test;
+
+TEST(AconfigTest, TestDisabledRwExportedFlag) {
+ ASSERT_FALSE(com_android_aconfig_test_disabled_rw_exported());
+ ASSERT_FALSE(provider_->disabled_rw_exported());
+ ASSERT_FALSE(disabled_rw_exported());
+}
+
+TEST(AconfigTest, TestEnabledFixedRoExportedFlag) {
+ // TODO: change to assertTrue(enabledFixedRoExported()) when the build supports reading tests/*.values
+ ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro_exported());
+ ASSERT_FALSE(provider_->enabled_fixed_ro_exported());
+ ASSERT_FALSE(enabled_fixed_ro_exported());
+}
+
+TEST(AconfigTest, TestEnabledRoExportedFlag) {
+ // TODO: change to assertTrue(enabledRoExported()) when the build supports reading tests/*.values
+ ASSERT_FALSE(com_android_aconfig_test_enabled_ro_exported());
+ ASSERT_FALSE(provider_->enabled_ro_exported());
+ ASSERT_FALSE(enabled_ro_exported());
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.rs b/tools/aconfig/tests/aconfig_exported_mode_test.rs
new file mode 100644
index 0000000..4b48047
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_exported_mode_test.rs
@@ -0,0 +1,7 @@
+#[cfg(not(feature = "cargo"))]
+#[test]
+fn test_flags() {
+ assert!(!aconfig_test_rust_library::disabled_rw_exported());
+ assert!(!aconfig_test_rust_library::enabled_fixed_ro_exported());
+ assert!(!aconfig_test_rust_library::enabled_ro_exported());
+}
diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp b/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp
new file mode 100644
index 0000000..0dec481
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include "com_android_aconfig_test.h"
+#include "gtest/gtest.h"
+
+using namespace com::android::aconfig::test;
+
+TEST(AconfigTest, TestDisabledReadOnlyFlag) {
+ ASSERT_FALSE(com_android_aconfig_test_disabled_ro());
+ ASSERT_FALSE(provider_->disabled_ro());
+ ASSERT_FALSE(disabled_ro());
+}
+
+TEST(AconfigTest, TestEnabledReadOnlyFlag) {
+ ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+ ASSERT_TRUE(provider_->enabled_ro());
+ ASSERT_TRUE(enabled_ro());
+}
+
+TEST(AconfigTest, TestDisabledReadWriteFlag) {
+ ASSERT_FALSE(com_android_aconfig_test_disabled_rw());
+ ASSERT_FALSE(provider_->disabled_rw());
+ ASSERT_FALSE(disabled_rw());
+}
+
+TEST(AconfigTest, TestEnabledReadWriteFlag) {
+ ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+ ASSERT_TRUE(provider_->enabled_rw());
+ ASSERT_TRUE(enabled_rw());
+}
+
+TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
+ ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+ ASSERT_TRUE(provider_->enabled_fixed_ro());
+ ASSERT_TRUE(enabled_fixed_ro());
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs b/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs
new file mode 100644
index 0000000..4f05e26
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs
@@ -0,0 +1,10 @@
+#[cfg(not(feature = "cargo"))]
+#[test]
+fn test_flags() {
+ assert!(!aconfig_test_rust_library::disabled_ro());
+ assert!(!aconfig_test_rust_library::disabled_rw());
+ assert!(!aconfig_test_rust_library::disabled_rw_in_other_namespace());
+ assert!(aconfig_test_rust_library::enabled_fixed_ro());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
+}
diff --git a/tools/aconfig/tests/aconfig_prod_mode_test.rs b/tools/aconfig/tests/aconfig_prod_mode_test.rs
index 950c441..e1fb8e5 100644
--- a/tools/aconfig/tests/aconfig_prod_mode_test.rs
+++ b/tools/aconfig/tests/aconfig_prod_mode_test.rs
@@ -3,7 +3,6 @@
fn test_flags() {
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- // TODO: Fix template to not default both disabled and enabled to false
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
}
diff --git a/tools/aconfig/tests/aconfig_test.cpp b/tools/aconfig/tests/aconfig_test.cpp
index 52651e4..0dec481 100644
--- a/tools/aconfig/tests/aconfig_test.cpp
+++ b/tools/aconfig/tests/aconfig_test.cpp
@@ -26,11 +26,9 @@
}
TEST(AconfigTest, TestEnabledReadOnlyFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
- ASSERT_FALSE(provider_->enabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+ ASSERT_TRUE(provider_->enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
TEST(AconfigTest, TestDisabledReadWriteFlag) {
@@ -40,19 +38,15 @@
}
TEST(AconfigTest, TestEnabledReadWriteFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
- ASSERT_FALSE(provider_->enabled_rw());
- ASSERT_FALSE(enabled_rw());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+ ASSERT_TRUE(provider_->enabled_rw());
+ ASSERT_TRUE(enabled_rw());
}
TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
- ASSERT_FALSE(provider_->enabled_fixed_ro());
- ASSERT_FALSE(enabled_fixed_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+ ASSERT_TRUE(provider_->enabled_fixed_ro());
+ ASSERT_TRUE(enabled_fixed_ro());
}
int main(int argc, char** argv) {
diff --git a/tools/aconfig/tests/aconfig_test_mode_test.rs b/tools/aconfig/tests/aconfig_test_mode_test.rs
index 3f56d2c..a08fbab 100644
--- a/tools/aconfig/tests/aconfig_test_mode_test.rs
+++ b/tools/aconfig/tests/aconfig_test_mode_test.rs
@@ -3,22 +3,21 @@
fn test_flags() {
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- // TODO: Fix template to not default both disabled and enabled to false
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
aconfig_test_rust_library::set_disabled_ro(true);
assert!(aconfig_test_rust_library::disabled_ro());
aconfig_test_rust_library::set_disabled_rw(true);
assert!(aconfig_test_rust_library::disabled_rw());
- aconfig_test_rust_library::set_enabled_ro(true);
- assert!(aconfig_test_rust_library::enabled_ro());
- aconfig_test_rust_library::set_enabled_rw(true);
- assert!(aconfig_test_rust_library::enabled_rw());
+ aconfig_test_rust_library::set_enabled_ro(false);
+ assert!(!aconfig_test_rust_library::enabled_ro());
+ aconfig_test_rust_library::set_enabled_rw(false);
+ assert!(!aconfig_test_rust_library::enabled_rw());
aconfig_test_rust_library::reset_flags();
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
}
diff --git a/tools/aconfig/tests/aconfig_test_test_variant.cpp b/tools/aconfig/tests/aconfig_test_test_variant.cpp
index 8a745c5..b3cf710 100644
--- a/tools/aconfig/tests/aconfig_test_test_variant.cpp
+++ b/tools/aconfig/tests/aconfig_test_test_variant.cpp
@@ -33,11 +33,9 @@
}
TEST_F(AconfigTest, TestEnabledReadOnlyFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
- ASSERT_FALSE(provider_->enabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+ ASSERT_TRUE(provider_->enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
TEST_F(AconfigTest, TestDisabledReadWriteFlag) {
@@ -47,19 +45,15 @@
}
TEST_F(AconfigTest, TestEnabledReadWriteFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
- ASSERT_FALSE(provider_->enabled_rw());
- ASSERT_FALSE(enabled_rw());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+ ASSERT_TRUE(provider_->enabled_rw());
+ ASSERT_TRUE(enabled_rw());
}
TEST_F(AconfigTest, TestEnabledFixedReadOnlyFlag) {
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
- ASSERT_FALSE(provider_->enabled_fixed_ro());
- ASSERT_FALSE(enabled_fixed_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+ ASSERT_TRUE(provider_->enabled_fixed_ro());
+ ASSERT_TRUE(enabled_fixed_ro());
}
TEST_F(AconfigTest, OverrideFlagValue) {
@@ -70,14 +64,14 @@
TEST_F(AconfigTest, ResetFlagValue) {
ASSERT_FALSE(disabled_ro());
- ASSERT_FALSE(enabled_ro());
- disabled_ro(true);
- enabled_ro(true);
- ASSERT_TRUE(disabled_ro());
ASSERT_TRUE(enabled_ro());
+ disabled_ro(true);
+ enabled_ro(false);
+ ASSERT_TRUE(disabled_ro());
+ ASSERT_FALSE(enabled_ro());
reset_flags();
ASSERT_FALSE(disabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
int main(int argc, char** argv) {
diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/tests/first.values
index 731ce84..2efb463 100644
--- a/tools/aconfig/tests/first.values
+++ b/tools/aconfig/tests/first.values
@@ -40,3 +40,9 @@
state: DISABLED
permission: READ_WRITE
}
+flag_value {
+ package: "com.android.aconfig.test"
+ name: "enabled_fixed_ro_exported"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_2.aconfig b/tools/aconfig/tests/read_only_test.aconfig
similarity index 68%
rename from tools/aconfig/tests/storage_test_1_part_2.aconfig
rename to tools/aconfig/tests/read_only_test.aconfig
index 5eb0c0c..5eb5056 100644
--- a/tools/aconfig/tests/storage_test_1_part_2.aconfig
+++ b/tools/aconfig/tests/read_only_test.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.aconfig.storage.test_1"
+package: "com.android.aconfig.test"
container: "system"
flag {
@@ -22,3 +22,11 @@
bug: ""
is_fixed_read_only: true
}
+
+flag {
+ name: "disabled_fixed_ro"
+ namespace: "aconfig_test"
+ description: "This flag is fixed READ_ONLY + DISABLED"
+ bug: ""
+ is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/read_only_test.values b/tools/aconfig/tests/read_only_test.values
new file mode 100644
index 0000000..349c7aa
--- /dev/null
+++ b/tools/aconfig/tests/read_only_test.values
@@ -0,0 +1,18 @@
+flag_value {
+ package: "com.android.aconfig.test"
+ name: "disabled_ro"
+ state: DISABLED
+ permission: READ_ONLY
+}
+flag_value {
+ package: "com.android.aconfig.test"
+ name: "enabled_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
+flag_value {
+ package: "com.android.aconfig.test"
+ name: "enabled_fixed_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_1.aconfig b/tools/aconfig/tests/storage_test_1.aconfig
similarity index 72%
rename from tools/aconfig/tests/storage_test_1_part_1.aconfig
rename to tools/aconfig/tests/storage_test_1.aconfig
index 70462cd..a122c57 100644
--- a/tools/aconfig/tests/storage_test_1_part_1.aconfig
+++ b/tools/aconfig/tests/storage_test_1.aconfig
@@ -15,3 +15,10 @@
bug: "456"
is_exported: true
}
+
+flag {
+ name: "enabled_ro"
+ namespace: "aconfig_test"
+ description: "This flag is ENABLED + READ_ONLY"
+ bug: "abc"
+}
diff --git a/tools/aconfig/tests/storage_test_1_part_2.aconfig b/tools/aconfig/tests/storage_test_4.aconfig
similarity index 64%
copy from tools/aconfig/tests/storage_test_1_part_2.aconfig
copy to tools/aconfig/tests/storage_test_4.aconfig
index 5eb0c0c..333fe09 100644
--- a/tools/aconfig/tests/storage_test_1_part_2.aconfig
+++ b/tools/aconfig/tests/storage_test_4.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.aconfig.storage.test_1"
+package: "com.android.aconfig.storage.test_4"
container: "system"
flag {
@@ -9,13 +9,6 @@
}
flag {
- name: "disabled_ro"
- namespace: "aconfig_test"
- description: "This flag is DISABLED + READ_ONLY"
- bug: "123"
-}
-
-flag {
name: "enabled_fixed_ro"
namespace: "aconfig_test"
description: "This flag is fixed READ_ONLY + ENABLED"
diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig
index 014bced..c11508a 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -78,3 +78,12 @@
bug: "111"
is_exported: true
}
+
+flag {
+ name: "enabled_fixed_ro_exported"
+ namespace: "aconfig_test"
+ description: "This flag is fixed ENABLED + READ_ONLY and exported"
+ bug: "111"
+ is_fixed_read_only: true
+ is_exported: true
+}
\ No newline at end of file
diff --git a/tools/aconfig/tests/test_force_read_only.aconfig b/tools/aconfig/tests/test_force_read_only.aconfig
new file mode 100644
index 0000000..05ab0e2
--- /dev/null
+++ b/tools/aconfig/tests/test_force_read_only.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.test.forcereadonly"
+container: "system"
+
+flag {
+ name: "fro_exported"
+ namespace: "aconfig_test"
+ description: "This is an exported flag"
+ is_exported: true
+ bug: "888"
+}
+
+flag {
+ name: "fro_rw"
+ namespace: "aconfig_test"
+ description: "This flag is not exported"
+ bug: "777"
+}
diff --git a/tools/compliance/go.mod b/tools/compliance/go.mod
index 1928189..bd04077 100644
--- a/tools/compliance/go.mod
+++ b/tools/compliance/go.mod
@@ -26,4 +26,4 @@
// Indirect dep from go-cmp
exclude golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
-go 1.18
+go 1.21
diff --git a/tools/metadata/generator.go b/tools/metadata/generator.go
index d328876..b7668be 100644
--- a/tools/metadata/generator.go
+++ b/tools/metadata/generator.go
@@ -77,9 +77,18 @@
return string(data)
}
-func writeNewlineToOutputFile(outputFile string) {
+func writeEmptyOutputProto(outputFile string, metadataRule string) {
file, err := os.Create(outputFile)
- data := "\n"
+ if err != nil {
+ log.Fatal(err)
+ }
+ var message proto.Message
+ if metadataRule == "test_spec" {
+ message = &test_spec_proto.TestSpec{}
+ } else if metadataRule == "code_metadata" {
+ message = &code_metadata_proto.CodeMetadata{}
+ }
+ data, err := proto.Marshal(message)
if err != nil {
log.Fatal(err)
}
@@ -92,8 +101,8 @@
}
func processTestSpecProtobuf(
- filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
- errCh chan error, wg *sync.WaitGroup,
+ filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+ errCh chan error, wg *sync.WaitGroup,
) {
defer wg.Done()
@@ -121,7 +130,7 @@
if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
errCh <- fmt.Errorf(
"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
- ": %s,\n%s with teamId: %s",
+ ": %s,\n%s with teamId: %s",
key,
metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
existing.GetTrendyTeamId(),
@@ -147,8 +156,8 @@
// processCodeMetadataProtobuf processes CodeMetadata protobuf files
func processCodeMetadataProtobuf(
- filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
- errCh chan error, wg *sync.WaitGroup,
+ filePath string, ownershipMetadataMap *sync.Map, sourceFileMetadataMap *sync.Map, keyLocks *keyToLocksMap,
+ errCh chan error, wg *sync.WaitGroup,
) {
defer wg.Done()
@@ -182,8 +191,8 @@
if attributes.TeamID != existing.TeamID && (!attributes.MultiOwnership || !existing.MultiOwnership) {
errCh <- fmt.Errorf(
"Conflict found for source file %s covered at %s with team ID: %s. Existing team ID: %s and path: %s."+
- " If multi-ownership is required, multiOwnership should be set to true in all test_spec modules using this target. "+
- "Multiple-ownership in general is discouraged though as it make infrastructure around android relying on this information pick up a random value when it needs only one.",
+ " If multi-ownership is required, multiOwnership should be set to true in all test_spec modules using this target. "+
+ "Multiple-ownership in general is discouraged though as it make infrastructure around android relying on this information pick up a random value when it needs only one.",
srcFile, internalMetadata.GetPath(), attributes.TeamID, existing.TeamID, existing.Path,
)
srcFileLock.Unlock()
@@ -235,7 +244,7 @@
inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
filePaths := strings.Split(inputFileData, " ")
if len(filePaths) == 1 && filePaths[0] == "" {
- writeNewlineToOutputFile(*outputFile)
+ writeEmptyOutputProto(*outputFile, *rule)
return
}
ownershipMetadataMap := &sync.Map{}
diff --git a/tools/metadata/testdata/generatedEmptyOutputFile.txt b/tools/metadata/testdata/generatedEmptyOutputFile.txt
index 8b13789..e69de29 100644
--- a/tools/metadata/testdata/generatedEmptyOutputFile.txt
+++ b/tools/metadata/testdata/generatedEmptyOutputFile.txt
@@ -1 +0,0 @@
-
diff --git a/tools/perf/benchmarks b/tools/perf/benchmarks
new file mode 100755
index 0000000..e188858
--- /dev/null
+++ b/tools/perf/benchmarks
@@ -0,0 +1,710 @@
+#!/usr/bin/env python3
+# 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.
+
+import sys
+if __name__ == "__main__":
+ sys.dont_write_bytecode = True
+
+import argparse
+import dataclasses
+import datetime
+import json
+import os
+import pathlib
+import random
+import re
+import shutil
+import subprocess
+import time
+import uuid
+
+import pretty
+import utils
+
+
+class FatalError(Exception):
+ def __init__(self):
+ pass
+
+
+class OptionsError(Exception):
+ def __init__(self, message):
+ self.message = message
+
+
+@dataclasses.dataclass(frozen=True)
+class Lunch:
+ "Lunch combination"
+
+ target_product: str
+ "TARGET_PRODUCT"
+
+ target_release: str
+ "TARGET_RELEASE"
+
+ target_build_variant: str
+ "TARGET_BUILD_VARIANT"
+
+ def ToDict(self):
+ return {
+ "TARGET_PRODUCT": self.target_product,
+ "TARGET_RELEASE": self.target_release,
+ "TARGET_BUILD_VARIANT": self.target_build_variant,
+ }
+
+ def Combine(self):
+ return f"{self.target_product}-{self.target_release}-{self.target_build_variant}"
+
+
+@dataclasses.dataclass(frozen=True)
+class Change:
+ "A change that we make to the tree, and how to undo it"
+ label: str
+ "String to print in the log when the change is made"
+
+ change: callable
+ "Function to change the source tree"
+
+ undo: callable
+ "Function to revert the source tree to its previous condition in the most minimal way possible."
+
+
+@dataclasses.dataclass(frozen=True)
+class Benchmark:
+ "Something we measure"
+
+ id: str
+ "Short ID for the benchmark, for the command line"
+
+ title: str
+ "Title for reports"
+
+ change: Change
+ "Source tree modification for the benchmark that will be measured"
+
+ modules: list[str]
+ "Build modules to build on soong command line"
+
+ preroll: int
+ "Number of times to run the build command to stabilize"
+
+ postroll: int
+ "Number of times to run the build command after reverting the action to stabilize"
+
+
+@dataclasses.dataclass(frozen=True)
+class FileSnapshot:
+ "Snapshot of a file's contents."
+
+ filename: str
+ "The file that was snapshottened"
+
+ contents: str
+ "The contents of the file"
+
+ def write(self):
+ "Write the contents back to the file"
+ with open(self.filename, "w") as f:
+ f.write(self.contents)
+
+
+def Snapshot(filename):
+ """Return a FileSnapshot with the file's current contents."""
+ with open(filename) as f:
+ contents = f.read()
+ return FileSnapshot(filename, contents)
+
+
+def Clean():
+ """Remove the out directory."""
+ def remove_out():
+ if os.path.exists("out"):
+ shutil.rmtree("out")
+ return Change(label="Remove out", change=remove_out, undo=lambda: None)
+
+
+def NoChange():
+ """No change to the source tree."""
+ return Change(label="No change", change=lambda: None, undo=lambda: None)
+
+
+def Create(filename):
+ "Create an action to create `filename`. The parent directory must exist."
+ def create():
+ with open(filename, "w") as f:
+ pass
+ def delete():
+ os.remove(filename)
+ return Change(
+ label=f"Create {filename}",
+ change=create,
+ undo=delete,
+ )
+
+
+def Modify(filename, contents, before=None):
+ """Create an action to modify `filename` by appending the result of `contents`
+ before the last instances of `before` in the file.
+
+ Raises an error if `before` doesn't appear in the file.
+ """
+ orig = Snapshot(filename)
+ if before:
+ index = orig.contents.rfind(before)
+ if index < 0:
+ report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
+ raise FatalError()
+ else:
+ index = len(orig.contents)
+ modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
+ if False:
+ print(f"Modify: {filename}")
+ x = orig.contents.replace("\n", "\n ORIG")
+ print(f" ORIG {x}")
+ x = modified.contents.replace("\n", "\n MODIFIED")
+ print(f" MODIFIED {x}")
+
+ return Change(
+ label="Modify " + filename,
+ change=lambda: modified.write(),
+ undo=lambda: orig.write()
+ )
+
+def AddJavaField(filename, prefix):
+ return Modify(filename,
+ lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
+ before="}")
+
+
+def Comment(prefix, suffix=""):
+ return lambda: prefix + " " + str(uuid.uuid4()) + suffix
+
+
+class BenchmarkReport():
+ "Information about a run of the benchmark"
+
+ lunch: Lunch
+ "lunch combo"
+
+ benchmark: Benchmark
+ "The benchmark object."
+
+ iteration: int
+ "Which iteration of the benchmark"
+
+ log_dir: str
+ "Path the the log directory, relative to the root of the reports directory"
+
+ preroll_duration_ns: [int]
+ "Durations of the in nanoseconds."
+
+ duration_ns: int
+ "Duration of the measured portion of the benchmark in nanoseconds."
+
+ postroll_duration_ns: [int]
+ "Durations of the postrolls in nanoseconds."
+
+ complete: bool
+ "Whether the benchmark made it all the way through the postrolls."
+
+ def __init__(self, lunch, benchmark, iteration, log_dir):
+ self.lunch = lunch
+ self.benchmark = benchmark
+ self.iteration = iteration
+ self.log_dir = log_dir
+ self.preroll_duration_ns = []
+ self.duration_ns = -1
+ self.postroll_duration_ns = []
+ self.complete = False
+
+ def ToDict(self):
+ return {
+ "lunch": self.lunch.ToDict(),
+ "id": self.benchmark.id,
+ "title": self.benchmark.title,
+ "modules": self.benchmark.modules,
+ "change": self.benchmark.change.label,
+ "iteration": self.iteration,
+ "log_dir": self.log_dir,
+ "preroll_duration_ns": self.preroll_duration_ns,
+ "duration_ns": self.duration_ns,
+ "postroll_duration_ns": self.postroll_duration_ns,
+ "complete": self.complete,
+ }
+
+class Runner():
+ """Runs the benchmarks."""
+
+ def __init__(self, options):
+ self._options = options
+ self._reports = []
+ self._complete = False
+
+ def Run(self):
+ """Run all of the user-selected benchmarks."""
+ # Clean out the log dir or create it if necessary
+ prepare_log_dir(self._options.LogDir())
+
+ try:
+ for lunch in self._options.Lunches():
+ print(lunch)
+ for benchmark in self._options.Benchmarks():
+ for iteration in range(self._options.Iterations()):
+ self._run_benchmark(lunch, benchmark, iteration)
+ self._complete = True
+ finally:
+ self._write_summary()
+
+
+ def _run_benchmark(self, lunch, benchmark, iteration):
+ """Run a single benchmark."""
+ benchmark_log_subdir = self._log_dir(lunch, benchmark, iteration)
+ benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
+
+ sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
+ sys.stderr.write(f" lunch: {lunch.Combine()}\n")
+ sys.stderr.write(f" iteration: {iteration}\n")
+ sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
+
+ report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
+ self._reports.append(report)
+
+ # Preroll builds
+ for i in range(benchmark.preroll):
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark.modules)
+ report.preroll_duration_ns.append(ns)
+
+ sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
+ if not self._options.DryRun():
+ benchmark.change.change()
+ try:
+
+ # Measured build
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark.modules)
+ report.duration_ns = ns
+
+ dist_one = self._options.DistOne()
+ if dist_one:
+ # If we're disting just one benchmark, save the logs and we can stop here.
+ self._dist(dist_one)
+ else:
+ # Postroll builds
+ for i in range(benchmark.preroll):
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
+ benchmark.modules)
+ report.postroll_duration_ns.append(ns)
+
+ finally:
+ # Always undo, even if we crashed or the build failed and we stopped.
+ sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
+ if not self._options.DryRun():
+ benchmark.change.undo()
+
+ self._write_summary()
+ sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
+
+ def _log_dir(self, lunch, benchmark, iteration):
+ """Construct the log directory fir a benchmark run."""
+ path = f"{lunch.Combine()}/{benchmark.id}"
+ # Zero pad to the correct length for correct alpha sorting
+ path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
+ return path
+
+ def _run_build(self, lunch, build_log_dir, modules):
+ """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
+ if the build fails.
+ """
+ sys.stderr.write(f"STARTING BUILD {modules}\n")
+
+ before_ns = time.perf_counter_ns()
+ if not self._options.DryRun():
+ cmd = [
+ "build/soong/soong_ui.bash",
+ "--build-mode",
+ "--all-modules",
+ f"--dir={self._options.root}",
+ "--skip-metrics-upload",
+ ] + modules
+ env = dict(os.environ)
+ env["TARGET_PRODUCT"] = lunch.target_product
+ env["TARGET_RELEASE"] = lunch.target_release
+ env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
+ returncode = subprocess.call(cmd, env=env)
+ if returncode != 0:
+ report_error(f"Build failed: {' '.join(cmd)}")
+ raise FatalError()
+
+ after_ns = time.perf_counter_ns()
+
+ # TODO: Copy some log files.
+
+ sys.stderr.write(f"FINISHED BUILD {modules}\n")
+
+ return after_ns - before_ns
+
+ def _dist(self, dist_dir):
+ out_dir = pathlib.Path("out")
+ dest_dir = pathlib.Path(dist_dir).joinpath("logs")
+ os.makedirs(dest_dir, exist_ok=True)
+ basenames = [
+ "build.trace.gz",
+ "soong.log",
+ "soong_build_metrics.pb",
+ "soong_metrics",
+ ]
+ for base in basenames:
+ src = out_dir.joinpath(base)
+ if src.exists():
+ sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
+ shutil.copy(src, dest_dir)
+
+ def _write_summary(self):
+ # Write the results, even if the build failed or we crashed, including
+ # whether we finished all of the benchmarks.
+ data = {
+ "start_time": self._options.Timestamp().isoformat(),
+ "branch": self._options.Branch(),
+ "tag": self._options.Tag(),
+ "benchmarks": [report.ToDict() for report in self._reports],
+ "complete": self._complete,
+ }
+ with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, sort_keys=True)
+
+
+def benchmark_table(benchmarks):
+ rows = [("ID", "DESCRIPTION", "REBUILD"),]
+ rows += [(benchmark.id, benchmark.title, " ".join(benchmark.modules)) for benchmark in
+ benchmarks]
+ return rows
+
+
+def prepare_log_dir(directory):
+ if os.path.exists(directory):
+ # If it exists and isn't a directory, fail.
+ if not os.path.isdir(directory):
+ report_error(f"Log directory already exists but isn't a directory: {directory}")
+ raise FatalError()
+ # Make sure the directory is empty. Do this rather than deleting it to handle
+ # symlinks cleanly.
+ for filename in os.listdir(directory):
+ entry = os.path.join(directory, filename)
+ if os.path.isdir(entry):
+ shutil.rmtree(entry)
+ else:
+ os.unlink(entry)
+ else:
+ # Create it
+ os.makedirs(directory)
+
+
+class Options():
+ def __init__(self):
+ self._had_error = False
+
+ # Wall time clock when we started
+ self._timestamp = datetime.datetime.now(datetime.timezone.utc)
+
+ # Move to the root of the tree right away. Everything must happen from there.
+ self.root = utils.get_root()
+ if not self.root:
+ report_error("Unable to find root of tree from cwd.")
+ raise FatalError()
+ os.chdir(self.root)
+
+ # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
+ # Doing all that here forces us to fail fast if one of them can't load a required
+ # file, at the cost of a small startup speed. Don't make this do something slow
+ # like scan the whole tree.
+ self._init_benchmarks()
+
+ # Argument parsing
+ epilog = f"""
+benchmarks:
+{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
+"""
+
+ parser = argparse.ArgumentParser(
+ prog="benchmarks",
+ allow_abbrev=False, # Don't let people write unsupportable scripts.
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=epilog,
+ description="Run build system performance benchmarks.")
+ self.parser = parser
+
+ parser.add_argument("--log-dir",
+ help="Directory for logs. Default is $TOP/../benchmarks/.")
+ parser.add_argument("--dated-logs", action="store_true",
+ help="Append timestamp to log dir.")
+ parser.add_argument("-n", action="store_true", dest="dry_run",
+ help="Dry run. Don't run the build commands but do everything else.")
+ parser.add_argument("--tag",
+ help="Variant of the run, for when there are multiple perf runs.")
+ parser.add_argument("--lunch", nargs="*",
+ help="Lunch combos to test")
+ parser.add_argument("--iterations", type=int, default=1,
+ help="Number of iterations of each test to run.")
+ parser.add_argument("--branch", type=str,
+ help="Specify branch. Otherwise a guess will be made based on repo.")
+ parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
+ metavar="BENCHMARKS",
+ help="Benchmarks to run. Default suite will be run if omitted.")
+ parser.add_argument("--dist-one", action="store_true",
+ help="Copy logs and metrics to the given dist dir. Requires that only"
+ + " one benchmark be supplied. Postroll steps will be skipped.")
+
+ self._args = parser.parse_args()
+
+ self._branch = self._branch()
+ self._log_dir = self._log_dir()
+ self._lunches = self._lunches()
+
+ # Validate the benchmark ids
+ all_ids = [benchmark.id for benchmark in self._benchmarks]
+ bad_ids = [id for id in self._args.benchmark if id not in all_ids]
+ if bad_ids:
+ for id in bad_ids:
+ self._error(f"Invalid benchmark: {id}")
+
+ # --dist-one requires that only one benchmark be supplied
+ if self._args.dist_one and len(self.Benchmarks()) != 1:
+ self._error("--dist-one requires that exactly one --benchmark.")
+
+ if self._had_error:
+ raise FatalError()
+
+ def Timestamp(self):
+ return self._timestamp
+
+ def _branch(self):
+ """Return the branch, either from the command line or by guessing from repo."""
+ if self._args.branch:
+ return self._args.branch
+ try:
+ branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
+ + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
+ shell=True, encoding="utf-8")
+ return branch.strip().split("/")[-1]
+ except subprocess.CalledProcessError as ex:
+ report_error("Can't get branch from .repo dir. Specify --branch argument")
+ report_error(str(ex))
+ raise FatalError()
+
+ def Branch(self):
+ return self._branch
+
+ def _log_dir(self):
+ "The log directory to use, based on the current options"
+ if self._args.log_dir:
+ d = pathlib.Path(self._args.log_dir).resolve().absolute()
+ else:
+ d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
+ if self._args.dated_logs:
+ d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
+ d = d.joinpath(self._branch)
+ if self._args.tag:
+ d = d.joinpath(self._args.tag)
+ return d.resolve().absolute()
+
+ def LogDir(self):
+ return self._log_dir
+
+ def Benchmarks(self):
+ return [b for b in self._benchmarks if b.id in self._args.benchmark]
+
+ def Tag(self):
+ return self._args.tag
+
+ def DryRun(self):
+ return self._args.dry_run
+
+ def _lunches(self):
+ def parse_lunch(lunch):
+ parts = lunch.split("-")
+ if len(parts) != 3:
+ raise OptionsError(f"Invalid lunch combo: {lunch}")
+ return Lunch(parts[0], parts[1], parts[2])
+ # If they gave lunch targets on the command line use that
+ if self._args.lunch:
+ result = []
+ # Split into Lunch objects
+ for lunch in self._args.lunch:
+ try:
+ result.append(parse_lunch(lunch))
+ except OptionsError as ex:
+ self._error(ex.message)
+ return result
+ # Use whats in the environment
+ product = os.getenv("TARGET_PRODUCT")
+ release = os.getenv("TARGET_RELEASE")
+ variant = os.getenv("TARGET_BUILD_VARIANT")
+ if (not product) or (not release) or (not variant):
+ # If they didn't give us anything, fail rather than guessing. There's no good
+ # default for AOSP.
+ self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
+ return []
+ return [Lunch(product, release, variant),]
+
+ def Lunches(self):
+ return self._lunches
+
+ def Iterations(self):
+ return self._args.iterations
+
+ def DistOne(self):
+ return self._args.dist_one
+
+ def _init_benchmarks(self):
+ """Initialize the list of benchmarks."""
+ # Assumes that we've already chdired to the root of the tree.
+ self._benchmarks = [
+ Benchmark(id="full",
+ title="Full build",
+ change=Clean(),
+ modules=["droid"],
+ preroll=0,
+ postroll=3,
+ ),
+ Benchmark(id="nochange",
+ title="No change",
+ change=NoChange(),
+ modules=["droid"],
+ preroll=2,
+ postroll=3,
+ ),
+ Benchmark(id="unreferenced",
+ title="Create unreferenced file",
+ change=Create("bionic/unreferenced.txt"),
+ modules=["droid"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_bp",
+ title="Modify Android.bp",
+ change=Modify("bionic/libc/Android.bp", Comment("//")),
+ modules=["droid"],
+ preroll=1,
+ postroll=3,
+ ),
+ Benchmark(id="modify_stdio",
+ title="Modify stdio.cpp",
+ change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
+ modules=["libc"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_adbd",
+ title="Modify adbd",
+ change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
+ modules=["adbd"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_private_field",
+ title="Add private field to ActivityManagerService.java",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "private"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_public_field",
+ title="Add public field to ActivityManagerService.java",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "/** @hide */ public"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_api",
+ title="Add API to ActivityManagerService.javaa",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_private_field",
+ title="Add private field to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "private"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_public_field",
+ title="Add public field to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "/** @hide */ public"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_api",
+ title="Add API to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_framework_resource",
+ title="Modify framework resource",
+ change=Modify("frameworks/base/core/res/res/values/config.xml",
+ lambda: str(uuid.uuid4()),
+ before="</string>"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="add_framework_resource",
+ title="Add framework resource",
+ change=Modify("frameworks/base/core/res/res/values/config.xml",
+ lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
+ before="</resources>"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="add_systemui_field",
+ title="Add SystemUI field",
+ change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
+ "public"),
+ modules=["SystemUI"],
+ preroll=1,
+ postroll=2,
+ ),
+ ]
+
+ def _error(self, message):
+ report_error(message)
+ self._had_error = True
+
+
+def report_error(message):
+ sys.stderr.write(f"error: {message}\n")
+
+
+def main(argv):
+ try:
+ options = Options()
+ runner = Runner(options)
+ runner.Run()
+ except FatalError:
+ sys.stderr.write(f"FAILED\n")
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tools/perf/format_benchmarks b/tools/perf/format_benchmarks
new file mode 100755
index 0000000..845d73f
--- /dev/null
+++ b/tools/perf/format_benchmarks
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# 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.
+
+import sys
+if __name__ == "__main__":
+ sys.dont_write_bytecode = True
+
+import argparse
+import dataclasses
+import datetime
+import json
+import os
+import pathlib
+import statistics
+import zoneinfo
+
+import pretty
+import utils
+
+# TODO:
+# - Flag if the last postroll build was more than 15 seconds or something. That's
+# an indicator that something is amiss.
+# - Add a mode to print all of the values for multi-iteration runs
+# - Add a flag to reorder the tags
+# - Add a flag to reorder the headers in order to show grouping more clearly.
+
+
+def FindSummaries(args):
+ def find_summaries(directory):
+ return [str(p.resolve()) for p in pathlib.Path(directory).glob("**/summary.json")]
+ if not args:
+ # If they didn't give an argument, use the default dir
+ root = utils.get_root()
+ if not root:
+ return []
+ return find_summaries(root.joinpath("..", utils.DEFAULT_REPORT_DIR))
+ results = list()
+ for arg in args:
+ if os.path.isfile(arg):
+ # If it's a file add that
+ results.append(arg)
+ elif os.path.isdir(arg):
+ # If it's a directory, find all of the files there
+ results += find_summaries(arg)
+ else:
+ sys.stderr.write(f"Invalid summary argument: {arg}\n")
+ sys.exit(1)
+ return sorted(list(results))
+
+
+def LoadSummary(filename):
+ with open(filename) as f:
+ return json.load(f)
+
+# Columns:
+# Date
+# Branch
+# Tag
+# --
+# Lunch
+# Rows:
+# Benchmark
+
+def lunch_str(d):
+ "Convert a lunch dict to a string"
+ return f"{d['TARGET_PRODUCT']}-{d['TARGET_RELEASE']}-{d['TARGET_BUILD_VARIANT']}"
+
+def group_by(l, key):
+ "Return a list of tuples, grouped by key, sorted by key"
+ result = {}
+ for item in l:
+ result.setdefault(key(item), []).append(item)
+ return [(k, v) for k, v in result.items()]
+
+
+class Table:
+ def __init__(self):
+ self._data = {}
+ self._rows = []
+ self._cols = []
+
+ def Set(self, column_key, row_key, data):
+ self._data[(column_key, row_key)] = data
+ if not column_key in self._cols:
+ self._cols.append(column_key)
+ if not row_key in self._rows:
+ self._rows.append(row_key)
+
+ def Write(self, out):
+ table = []
+ # Expand the column items
+ for row in zip(*self._cols):
+ if row.count(row[0]) == len(row):
+ continue
+ table.append([""] + [col for col in row])
+ if table:
+ table.append(pretty.SEPARATOR)
+ # Populate the data
+ for row in self._rows:
+ table.append([str(row)] + [str(self._data.get((col, row), "")) for col in self._cols])
+ out.write(pretty.FormatTable(table))
+
+
+def format_duration_sec(ns):
+ "Format a duration in ns to second precision"
+ sec = round(ns / 1000000000)
+ h, sec = divmod(sec, 60*60)
+ m, sec = divmod(sec, 60)
+ result = ""
+ if h > 0:
+ result += f"{h:2d}h "
+ if h > 0 or m > 0:
+ result += f"{m:2d}m "
+ return result + f"{sec:2d}s"
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(
+ prog="format_benchmarks",
+ allow_abbrev=False, # Don't let people write unsupportable scripts.
+ description="Print analysis tables for benchmarks")
+
+ parser.add_argument("--tags", nargs="*",
+ help="The tags to print, in order.")
+
+ parser.add_argument("summaries", nargs="*",
+ help="A summary.json file or a directory in which to look for summaries.")
+
+ args = parser.parse_args()
+
+ # Load the summaries
+ summaries = [(s, LoadSummary(s)) for s in FindSummaries(args.summaries)]
+
+ # Convert to MTV time
+ for filename, s in summaries:
+ dt = datetime.datetime.fromisoformat(s["start_time"])
+ dt = dt.astimezone(zoneinfo.ZoneInfo("America/Los_Angeles"))
+ s["datetime"] = dt
+ s["date"] = datetime.date(dt.year, dt.month, dt.day)
+
+ # Filter out tags we don't want
+ if args.tags:
+ summaries = [(f, s) for f, s in summaries if s.get("tag", "") in args.tags]
+
+ # If they supplied tags, sort in that order, otherwise sort by tag
+ if args.tags:
+ tagsort = lambda tag: args.tags.index(tag)
+ else:
+ tagsort = lambda tag: tag
+
+ # Sort the summaries
+ summaries.sort(key=lambda s: (s[1]["date"], s[1]["branch"], tagsort(s[1]["tag"])))
+
+ # group the benchmarks by column and iteration
+ def bm_key(b):
+ return (
+ lunch_str(b["lunch"]),
+ )
+ for filename, summary in summaries:
+ summary["columns"] = [(key, group_by(bms, lambda b: b["id"])) for key, bms
+ in group_by(summary["benchmarks"], bm_key)]
+
+ # Build the table
+ table = Table()
+ for filename, summary in summaries:
+ for key, column in summary["columns"]:
+ for id, cell in column:
+ duration_ns = statistics.median([b["duration_ns"] for b in cell])
+ table.Set(tuple([summary["date"].strftime("%Y-%m-%d"),
+ summary["branch"],
+ summary["tag"]]
+ + list(key)),
+ cell[0]["title"], format_duration_sec(duration_ns))
+
+ table.Write(sys.stdout)
+
+if __name__ == "__main__":
+ main(sys.argv)
+
diff --git a/tools/perf/pretty.py b/tools/perf/pretty.py
new file mode 100644
index 0000000..1b59098
--- /dev/null
+++ b/tools/perf/pretty.py
@@ -0,0 +1,52 @@
+# 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.
+
+# Formatting utilities
+
+class Sentinel():
+ pass
+
+SEPARATOR = Sentinel()
+
+def FormatTable(data, prefix=""):
+ """Pretty print a table.
+
+ Prefixes each row with `prefix`.
+ """
+ if not data:
+ return ""
+ widths = [max([len(x) if x else 0 for x in col]) for col
+ in zip(*[d for d in data if not isinstance(d, Sentinel)])]
+ result = ""
+ colsep = " "
+ for row in data:
+ result += prefix
+ if row == SEPARATOR:
+ for w in widths:
+ result += "-" * w
+ result += colsep
+ result += "\n"
+ else:
+ for i in range(len(row)):
+ cell = row[i] if row[i] else ""
+ if i != 0:
+ result += " " * (widths[i] - len(cell))
+ result += cell
+ if i == 0:
+ result += " " * (widths[i] - len(cell))
+ result += colsep
+ result += "\n"
+ return result
+
+
diff --git a/tools/perf/utils.py b/tools/perf/utils.py
new file mode 100644
index 0000000..08e393f
--- /dev/null
+++ b/tools/perf/utils.py
@@ -0,0 +1,30 @@
+# 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.
+
+import os
+import pathlib
+
+DEFAULT_REPORT_DIR = "benchmarks"
+
+def get_root():
+ top_dir = os.environ.get("ANDROID_BUILD_TOP")
+ if top_dir:
+ return pathlib.Path(top_dir).resolve()
+ d = pathlib.Path.cwd()
+ while True:
+ if d.joinpath("build", "soong", "soong_ui.bash").exists():
+ return d.resolve().absolute()
+ d = d.parent
+ if d == pathlib.Path("/"):
+ return None
diff --git a/tools/rbcrun/go.mod b/tools/rbcrun/go.mod
index 5ae2972..6e99ce9 100644
--- a/tools/rbcrun/go.mod
+++ b/tools/rbcrun/go.mod
@@ -4,4 +4,4 @@
replace go.starlark.net => ../../../../external/starlark-go
-go 1.15
+go 1.21
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 4a5facd..29042a5 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -259,6 +259,9 @@
--vabc_cow_version
Specify the VABC cow version to be used
+
+ --compression_factor
+ Specify the maximum block size to be compressed at once during OTA. supported options: 4k, 8k, 16k, 32k, 64k, 128k
"""
from __future__ import print_function
@@ -331,6 +334,7 @@
OPTIONS.security_patch_level = None
OPTIONS.max_threads = None
OPTIONS.vabc_cow_version = None
+OPTIONS.compression_factor = None
POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
@@ -393,17 +397,6 @@
"""
return ModifyKeyvalueList(content, "virtual_ab_compression_method", algo)
-def SetVABCCowVersion(content, cow_version):
- """ Update virtual_ab_cow_version in dynamic_partitions_info.txt
- Args:
- content: The string content of dynamic_partitions_info.txt
- algo: The cow version be used for VABC. See
- https://cs.android.com/android/platform/superproject/main/+/main:system/core/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h;l=36
- Returns:
- Updated content of dynamic_partitions_info.txt , updated cow version
- """
- return ModifyKeyvalueList(content, "virtual_ab_cow_version", cow_version)
-
def UpdatesInfoForSpecialUpdates(content, partitions_filter,
delete_keys=None):
@@ -1020,6 +1013,8 @@
target_file, vabc_compression_param)
if OPTIONS.vabc_cow_version:
target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_cow_version", OPTIONS.vabc_cow_version)
+ if OPTIONS.compression_factor:
+ target_file = ModifyTargetFilesDynamicPartitionInfo(target_file, "virtual_ab_compression_factor", OPTIONS.compression_factor)
if OPTIONS.skip_postinstall:
target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
# Target_file may have been modified, reparse ab_partitions
@@ -1038,7 +1033,11 @@
partition_timestamps_flags = []
# Enforce a max timestamp this payload can be applied on top of.
if OPTIONS.downgrade:
- max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
+ # When generating ota between merged target-files, partition build date can
+ # decrease in target, at the same time as ro.build.date.utc increases,
+ # so always pick largest value.
+ max_timestamp = max(source_info.GetBuildProp("ro.build.date.utc"),
+ str(metadata.postcondition.timestamp))
partition_timestamps_flags = GeneratePartitionTimestampFlagsDowngrade(
metadata.precondition.partition_state,
metadata.postcondition.partition_state
@@ -1276,6 +1275,13 @@
else:
raise ValueError("Cannot parse value %r for option %r - only "
"integers are allowed." % (a, o))
+ elif o in ("--compression_factor"):
+ values = ["4k", "8k", "16k", "32k", "64k", "128k"]
+ if a[:-1].isdigit() and a in values and a.endswith("k"):
+ OPTIONS.compression_factor = str(int(a[:-1]) * 1024)
+ else:
+ raise ValueError("Please specify value from following options: 4k, 8k, 16k, 32k, 64k, 128k")
+
elif o == "--vabc_cow_version":
if a.isdigit():
OPTIONS.vabc_cow_version = a
@@ -1331,6 +1337,7 @@
"security_patch_level=",
"max_threads=",
"vabc_cow_version=",
+ "compression_factor=",
], extra_option_handler=[option_handler, payload_signer.signer_options])
common.InitLogging()
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index ddd2d36..048a497 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -364,26 +364,66 @@
# Only incremental OTAs are allowed to reach here.
assert OPTIONS.incremental_source is not None
+ # used for logging upon errors
+ log_downgrades = []
+ log_upgrades = []
+
post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
- is_downgrade = int(post_timestamp) < int(pre_timestamp)
+ if int(post_timestamp) < int(pre_timestamp):
+ logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}. Downgrade detected.")
+ log_downgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+ else:
+ logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}.")
+ log_upgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+
+ # When merging system and vendor target files, it is not enough
+ # to check ro.build.date.utc, the timestamp for each partition must
+ # be checked.
+ if source_info.is_ab:
+ ab_partitions = set(source_info.get("ab_partitions"))
+ for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
+
+ partition_prop = source_info.get('{}.build.prop'.format(partition))
+ # Skip if the partition is missing, or it doesn't have a build.prop
+ if not partition_prop or not partition_prop.build_props:
+ continue
+ partition_prop = target_info.get('{}.build.prop'.format(partition))
+ # Skip if the partition is missing, or it doesn't have a build.prop
+ if not partition_prop or not partition_prop.build_props:
+ continue
+
+ post_timestamp = target_info.GetPartitionBuildProp(
+ 'ro.build.date.utc', partition)
+ pre_timestamp = source_info.GetPartitionBuildProp(
+ 'ro.build.date.utc', partition)
+ if int(post_timestamp) < int(pre_timestamp):
+ logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+ f"post time: {post_timestamp}. Downgrade detected.")
+ log_downgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
+ else:
+ logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}.")
+ log_upgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
if OPTIONS.spl_downgrade:
metadata_proto.spl_downgrade = True
if OPTIONS.downgrade:
- if not is_downgrade:
+ if len(log_downgrades) == 0:
raise RuntimeError(
"--downgrade or --override_timestamp specified but no downgrade "
- "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
+ "detected. Current values for ro.build.date.utc: " + ', '.join(log_upgrades))
metadata_proto.downgrade = True
else:
- if is_downgrade:
+ if len(log_downgrades) != 0:
raise RuntimeError(
- "Downgrade detected based on timestamp check: pre: %s, post: %s. "
+ "Downgrade detected based on timestamp check in ro.build.date.utc. "
"Need to specify --override_timestamp OR --downgrade to allow "
- "building the incremental." % (pre_timestamp, post_timestamp))
-
+ "building the incremental. Downgrades detected for: "
+ + ', '.join(log_downgrades))
def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
"""Returns a set of build info objects that may exist during runtime."""
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index ad0f7a8..d1e76b9 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -163,6 +163,20 @@
'oem_fingerprint_properties': 'ro.product.device ro.product.brand',
}
+ TEST_TARGET_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+ 'vendor', {
+ 'ro.vendor.build.date.utc' : '87654321',
+ 'ro.product.vendor.device':'vendor-device',
+ 'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+ )
+
+ TEST_SOURCE_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+ 'vendor', {
+ 'ro.vendor.build.date.utc' : '12345678',
+ 'ro.product.vendor.device':'vendor-device',
+ 'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+ )
+
def setUp(self):
self.testdata_dir = test_utils.get_testdata_dir()
self.assertTrue(os.path.exists(self.testdata_dir))
@@ -351,6 +365,13 @@
source_info['build.prop'].build_props['ro.build.date.utc'],
target_info['build.prop'].build_props['ro.build.date.utc'])
+ @staticmethod
+ def _test_GetPackageMetadata_swapVendorBuildTimestamps(target_info, source_info):
+ (target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+ source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc']) = (
+ source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+ target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'])
+
def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -363,6 +384,24 @@
self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
source_info)
+ def test_GetPackageMetadata_unintentionalVendorDowngradeDetected(self):
+ target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+ target_info_dict['ab_update'] = 'true'
+ target_info_dict['ab_partitions'] = ['vendor']
+ target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+ source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+ source_info_dict['ab_update'] = 'true'
+ source_info_dict['ab_partitions'] = ['vendor']
+ source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+ self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+ target_info_dict, source_info_dict)
+
+ target_info = common.BuildInfo(target_info_dict, None)
+ source_info = common.BuildInfo(source_info_dict, None)
+ common.OPTIONS.incremental_source = ''
+ self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
+ source_info)
+
def test_GetPackageMetadata_downgrade(self):
target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -397,6 +436,55 @@
},
metadata)
+ def test_GetPackageMetadata_vendorDowngrade(self):
+ target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+ target_info_dict['ab_update'] = 'true'
+ target_info_dict['ab_partitions'] = ['vendor']
+ target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+ source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+ source_info_dict['ab_update'] = 'true'
+ source_info_dict['ab_partitions'] = ['vendor']
+ source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+ self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+ target_info_dict, source_info_dict)
+
+ target_info = common.BuildInfo(target_info_dict, None)
+ source_info = common.BuildInfo(source_info_dict, None)
+ common.OPTIONS.incremental_source = ''
+ common.OPTIONS.downgrade = True
+ common.OPTIONS.wipe_user_data = True
+ common.OPTIONS.spl_downgrade = True
+ metadata = self.GetLegacyOtaMetadata(target_info, source_info)
+ # Reset spl_downgrade so other tests are unaffected
+ common.OPTIONS.spl_downgrade = False
+
+ self.assertDictEqual(
+ {
+ 'ota-downgrade': 'yes',
+ 'ota-type': 'AB',
+ 'ota-required-cache': '0',
+ 'ota-wipe': 'yes',
+ 'post-build': 'build-fingerprint-target',
+ 'post-build-incremental': 'build-version-incremental-target',
+ 'post-sdk-level': '27',
+ 'post-security-patch-level': '2017-12-01',
+ 'post-timestamp': '1500000000',
+ 'pre-device': 'product-device',
+ 'pre-build': 'build-fingerprint-source',
+ 'pre-build-incremental': 'build-version-incremental-source',
+ 'spl-downgrade': 'yes',
+ },
+ metadata)
+
+ post_build = GetPackageMetadata(target_info, source_info).postcondition
+ self.assertEqual('vendor', post_build.partition_state[0].partition_name)
+ self.assertEqual('12345678', post_build.partition_state[0].version)
+
+ pre_build = GetPackageMetadata(target_info, source_info).precondition
+ self.assertEqual('vendor', pre_build.partition_state[0].partition_name)
+ self.assertEqual('87654321', pre_build.partition_state[0].version)
+
+
@test_utils.SkipIfExternalToolsUnavailable()
def test_GetTargetFilesZipForSecondaryImages(self):
input_file = construct_target_files(secondary=True)
diff --git a/tools/sbom/generate-sbom-framework_res.py b/tools/sbom/generate-sbom-framework_res.py
index e637d53..d0d232d 100644
--- a/tools/sbom/generate-sbom-framework_res.py
+++ b/tools/sbom/generate-sbom-framework_res.py
@@ -52,8 +52,19 @@
filename = 'data/framework_res.jar'
file_id = f'SPDXRef-{sbom_data.encode_for_spdxid(filename)}'
file = sbom_data.File(id=file_id, name=filename, checksum='SHA1: <checksum>')
+
+ package_name = 'framework_res'
+ package_id = f'SPDXRef-PREBUILT-{sbom_data.encode_for_spdxid(package_name)}'
+ package = sbom_data.Package(id=package_id, name=package_name, version='<package_version>',
+ download_location=sbom_data.VALUE_NONE,
+ supplier='Organization: <organization>',
+ files_analyzed=True,
+ verification_code='<package_verification_code>')
+ package.file_ids.append(file_id)
+
+ doc.packages.append(package)
doc.files.append(file)
- doc.describes = file_id
+ doc.describes = package_id
with open(args.layoutlib_sbom, 'r', encoding='utf-8') as f:
layoutlib_sbom = json.load(f)
@@ -72,7 +83,9 @@
if file[sbom_writers.PropNames.FILE_NAME].startswith('data/res/'):
resource_file_spdxids.append(file[sbom_writers.PropNames.SPDXID])
- doc.relationships = []
+ doc.relationships = [
+ sbom_data.Relationship(package_id, sbom_data.RelationshipType.CONTAINS, file_id)
+ ]
for spdxid in resource_file_spdxids:
doc.relationships.append(
sbom_data.Relationship(file_id, sbom_data.RelationshipType.GENERATED_FROM,