Merge "Revert "Turn on V3 cow"" into main
diff --git a/ci/build_test_suites b/ci/build_test_suites
index 89ecefe..03f6731 100755
--- a/ci/build_test_suites
+++ b/ci/build_test_suites
@@ -1,4 +1,4 @@
-#!prebuilts/build-tools/linux-x86/bin/py3-cmd
+#!prebuilts/build-tools/linux-x86/bin/py3-cmd -B
# Copyright 2024, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,10 +14,6 @@
# limitations under the License.
import sys
-
import build_test_suites
-if __name__ == '__main__':
- sys.dont_write_bytecode = True
-
- build_test_suites.main(sys.argv)
+build_test_suites.main(sys.argv)
diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py
index 5798f2b..1d5b377 100644
--- a/ci/build_test_suites.py
+++ b/ci/build_test_suites.py
@@ -39,13 +39,12 @@
def build_test_suites(argv):
args = parse_args(argv)
- if not os.environ.get('BUILD_NUMBER')[0] == 'P':
+ if is_optimization_enabled():
+ # Call the class to map changed files to modules to build.
+ # TODO(lucafarsi): Move this into a replaceable class.
+ build_affected_modules(args)
+ else:
build_everything(args)
- return
-
- # Call the class to map changed files to modules to build.
- # TODO(lucafarsi): Move this into a replaceable class.
- build_affected_modules(args)
def parse_args(argv):
@@ -58,15 +57,21 @@
argparser.add_argument(
'--with_dexpreopt_boot_img_and_system_server_only', action='store_true'
)
- argparser.add_argument('--dist_dir')
argparser.add_argument('--change_info', nargs='?')
- argparser.add_argument('--extra_required_modules', nargs='*')
return argparser.parse_args()
+def is_optimization_enabled() -> bool:
+ # TODO(lucafarsi): switch back to building only affected general-tests modules
+ # in presubmit once ready.
+ # if os.environ.get('BUILD_NUMBER')[0] == 'P':
+ # return True
+ return False
+
+
def build_everything(args: argparse.Namespace):
- build_command = base_build_command(args)
+ build_command = base_build_command(args, args.extra_targets)
build_command.append('general-tests')
run_command(build_command, print_output=True)
@@ -78,7 +83,7 @@
)
# Call the build command with everything.
- build_command = base_build_command(args)
+ build_command = base_build_command(args, args.extra_targets)
build_command.extend(modules_to_build)
# When not building general-tests we also have to build the general tests
# shared libs.
@@ -86,21 +91,22 @@
run_command(build_command, print_output=True)
- zip_build_outputs(modules_to_build, args.dist_dir, args.target_release)
+ zip_build_outputs(modules_to_build, args.target_release)
-def base_build_command(args: argparse.Namespace) -> list:
+def base_build_command(
+ args: argparse.Namespace, extra_targets: set[str]
+) -> list:
build_command = []
build_command.append('time')
build_command.append('./build/soong/soong_ui.bash')
build_command.append('--make-mode')
build_command.append('dist')
- build_command.append('DIST_DIR=' + args.dist_dir)
build_command.append('TARGET_PRODUCT=' + args.target_product)
build_command.append('TARGET_RELEASE=' + args.target_release)
if args.with_dexpreopt_boot_img_and_system_server_only:
build_command.append('WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY=true')
- build_command.extend(args.extra_targets)
+ build_command.extend(extra_targets)
return build_command
@@ -214,7 +220,7 @@
def zip_build_outputs(
- modules_to_build: set[str], dist_dir: str, target_release: str
+ modules_to_build: set[str], target_release: str
):
src_top = os.environ.get('TOP', os.getcwd())
@@ -230,6 +236,7 @@
product_out = pathlib.Path(get_soong_var('PRODUCT_OUT', target_release))
soong_host_out = pathlib.Path(get_soong_var('SOONG_HOST_OUT', target_release))
host_out = pathlib.Path(get_soong_var('HOST_OUT', target_release))
+ dist_dir = pathlib.Path(get_soong_var('DIST_DIR', target_release))
# Call the class to package the outputs.
# TODO(lucafarsi): Move this code into a replaceable class.
@@ -405,4 +412,4 @@
def main(argv):
- build_test_suites(sys.argv)
+ build_test_suites(argv)
diff --git a/cogsetup.sh b/cogsetup.sh
index 6439af0..44538f2 100644
--- a/cogsetup.sh
+++ b/cogsetup.sh
@@ -21,18 +21,21 @@
OUT_DIR="out"
fi
- if [[ -L "${OUT_DIR}" ]]; then
+ # getoutdir ensures paths are absolute. envsetup could be called from a
+ # directory other than the root of the source tree
+ local outdir=$(getoutdir)
+ if [[ -L "${outdir}" ]]; then
return
fi
- if [ -d "${OUT_DIR}" ]; then
- echo -e "\tOutput directory ${OUT_DIR} cannot be present in a Cog workspace."
- echo -e "\tDelete \"${OUT_DIR}\" or create a symlink from \"${OUT_DIR}\" to a directory outside your workspace."
+ if [ -d "${outdir}" ]; then
+ echo -e "\tOutput directory ${outdir} cannot be present in a Cog workspace."
+ echo -e "\tDelete \"${outdir}\" or create a symlink from \"${outdir}\" to a directory outside your workspace."
return 1
fi
DEFAULT_OUTPUT_DIR="${HOME}/.cog/android-build-out"
mkdir -p ${DEFAULT_OUTPUT_DIR}
- ln -s ${DEFAULT_OUTPUT_DIR} `pwd`/out
+ ln -s ${DEFAULT_OUTPUT_DIR} ${outdir}
}
# This function sets up the build environment to be appropriate for Cog.
@@ -63,4 +66,4 @@
echo -e "\e[01;31mERROR:\e[0m This script must be run from a Cog workspace."
fi
-_setup_cog_env
\ No newline at end of file
+_setup_cog_env
diff --git a/core/Makefile b/core/Makefile
index d4e241e..b245d32 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -465,9 +465,7 @@
)
$(if $(1),\
cp $$(PRIVATE_MODULES) $$(PRIVATE_MODULE_DIR)/; \
- for MODULE in $$(PRIVATE_LOAD_MODULES); do \
- basename $$$$MODULE >> $$(PRIVATE_LOAD_FILE); \
- done; \
+ if [ -n "$$(PRIVATE_LOAD_MODULES)" ]; then basename -a $$(PRIVATE_LOAD_MODULES); fi > $$(PRIVATE_LOAD_FILE); \
)
# The ln -sf + find -delete sequence is to remove any modules in
# PRIVATE_EXTRA_MODULES which have same basename as MODULES in PRIVATE_MODULES
@@ -1471,8 +1469,13 @@
boototapackage_4k: $(BUILT_BOOT_OTA_PACKAGE_4K)
.PHONY: boototapackage_4k
+ifeq ($(BOARD_16K_OTA_MOVE_VENDOR),true)
+$(eval $(call copy-one-file,$(BUILT_BOOT_OTA_PACKAGE_4K),$(TARGET_OUT_VENDOR)/boot_otas/boot_ota_4k.zip))
+$(eval $(call copy-one-file,$(BUILT_BOOT_OTA_PACKAGE_16K),$(TARGET_OUT_VENDOR)/boot_otas/boot_ota_16k.zip))
+else
$(eval $(call copy-one-file,$(BUILT_BOOT_OTA_PACKAGE_4K),$(TARGET_OUT)/boot_otas/boot_ota_4k.zip))
$(eval $(call copy-one-file,$(BUILT_BOOT_OTA_PACKAGE_16K),$(TARGET_OUT)/boot_otas/boot_ota_16k.zip))
+endif # BOARD_16K_OTA_MOVE_VENDOR == true
ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_4k.zip
ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_16k.zip
@@ -2727,6 +2730,8 @@
$(if $(strip $(recovery_wipe)), \
cp -f $(recovery_wipe) $(TARGET_RECOVERY_ROOT_OUT)/system/etc/recovery.wipe)
ln -sf prop.default $(TARGET_RECOVERY_ROOT_OUT)/default.prop
+ # Silence warnings in first_stage_console.
+ touch $(TARGET_RECOVERY_ROOT_OUT)/linkerconfig/ld.config.txt
$(BOARD_RECOVERY_IMAGE_PREPARE)
$(hide) touch $@
@@ -3395,6 +3400,14 @@
FULL_SYSTEMIMAGE_DEPS += $(INTERNAL_ROOT_FILES) $(INSTALLED_FILES_FILE_ROOT)
+define write-file-lines
+$(1):
+ @echo Writing $$@
+ rm -f $$@
+ echo -n > $$@
+ $$(foreach f,$(2),echo "$$(f)" >> $$@$$(newline))
+endef
+
# -----------------------------------------------------------------
ifdef BUILDING_SYSTEM_IMAGE
@@ -3440,22 +3453,9 @@
.PHONY: installed-file-list
installed-file-list: $(INSTALLED_FILES_FILE)
-systemimage_intermediates :=$= $(call intermediates-dir-for,PACKAGING,systemimage)
+systemimage_intermediates :=$= $(call intermediates-dir-for,PACKAGING,system)
BUILT_SYSTEMIMAGE :=$= $(systemimage_intermediates)/system.img
-
-# Used by the bazel sandwich to request the staging dir be built
-$(systemimage_intermediates)/staging_dir.stamp: $(FULL_SYSTEMIMAGE_DEPS)
- touch $@
-
-define write-file-lines
-$(1):
- @echo Writing $$@
- rm -f $$@
- echo -n > $$@
- $$(foreach f,$(2),echo "$$(f)" >> $$@$$(newline))
-endef
-
# $(1): output file
define build-systemimage-target
@echo "Target system fs image: $(1)"
@@ -3472,6 +3472,9 @@
endef
$(eval $(call write-file-lines,$(systemimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT)/,,$(filter $(TARGET_OUT)/%,$(FULL_SYSTEMIMAGE_DEPS)))))
+# Used by soong sandwich to request the staging dir be built
+$(systemimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT)/%,$(FULL_SYSTEMIMAGE_DEPS))
+ touch $@
ifeq ($(BOARD_AVB_ENABLE),true)
$(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
@@ -3586,6 +3589,9 @@
$(INTERNAL_USERDATAIMAGE_FILES)
$(eval $(call write-file-lines,$(userdataimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_DATA)/,,$(filter $(TARGET_OUT_DATA)/%,$(INSTALLED_USERDATAIMAGE_TARGET_DEPS)))))
+# Used by soong sandwich to request the staging dir be built
+$(userdataimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_DATA)/%,$(INSTALLED_USERDATAIMAGE_TARGET_DEPS))
+ touch $@
$(INSTALLED_USERDATAIMAGE_TARGET): $(INSTALLED_USERDATAIMAGE_TARGET_DEPS) $(userdataimage_intermediates)/file_list.txt
$(build-userdataimage-target)
@@ -3639,6 +3645,9 @@
endef
$(eval $(call write-file-lines,$(cacheimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_CACHE)/,,$(filter $(TARGET_OUT_CACHE)/%,$(INTERNAL_CACHEIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(cacheimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_CACHE)/%,$(INTERNAL_CACHEIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET)
@@ -3723,6 +3732,9 @@
endef
$(eval $(call write-file-lines,$(systemotherimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_SYSTEM_OTHER)/,,$(filter $(TARGET_OUT_SYSTEM_OTHER)/%,$(INTERNAL_SYSTEMOTHERIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(systemotherimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_SYSTEM_OTHER)/%,$(INTERNAL_SYSTEMOTHERIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_SYSTEMOTHERIMAGE_TARGET := $(BUILT_SYSTEMOTHERIMAGE_TARGET)
@@ -3826,6 +3838,9 @@
endef
$(eval $(call write-file-lines,$(vendorimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_VENDOR)/,,$(filter $(TARGET_OUT_VENDOR)/%,$(INTERNAL_VENDORIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(vendorimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_VENDOR)/%,$(INTERNAL_VENDORIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_VENDORIMAGE_TARGET := $(BUILT_VENDORIMAGE_TARGET)
@@ -3896,6 +3911,9 @@
endef
$(eval $(call write-file-lines,$(productimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_PRODUCT)/,,$(filter $(TARGET_OUT_PRODUCT)/%,$(INTERNAL_PRODUCTIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(productimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_PRODUCT)/%,$(INTERNAL_PRODUCTIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_PRODUCTIMAGE_TARGET := $(BUILT_PRODUCTIMAGE_TARGET)
@@ -3963,6 +3981,9 @@
endef
$(eval $(call write-file-lines,$(system_extimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_SYSTEM_EXT)/,,$(filter $(TARGET_OUT_SYSTEM_EXT)/%,$(INTERNAL_SYSTEM_EXTIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(system_extimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_SYSTEM_EXT)/%,$(INTERNAL_SYSTEM_EXTIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_SYSTEM_EXTIMAGE_TARGET := $(BUILT_SYSTEM_EXTIMAGE_TARGET)
@@ -4049,6 +4070,9 @@
endef
$(eval $(call write-file-lines,$(odmimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_ODM)/,,$(filter $(TARGET_OUT_ODM)/%,$(INTERNAL_ODMIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(odmimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_ODM)/%,$(INTERNAL_ODMIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_ODMIMAGE_TARGET := $(BUILT_ODMIMAGE_TARGET)
@@ -4115,6 +4139,9 @@
endef
$(eval $(call write-file-lines,$(vendor_dlkmimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_VENDOR_DLKM)/,,$(filter $(TARGET_OUT_VENDOR_DLKM)/%,$(INTERNAL_VENDOR_DLKMIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(vendor_dlkmimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_VENDOR_DLKM)/%,$(INTERNAL_VENDOR_DLKMIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_VENDOR_DLKMIMAGE_TARGET := $(BUILT_VENDOR_DLKMIMAGE_TARGET)
@@ -4181,6 +4208,9 @@
endef
$(eval $(call write-file-lines,$(odm_dlkmimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_ODM_DLKM)/,,$(filter $(TARGET_OUT_ODM_DLKM)/%,$(INTERNAL_ODM_DLKMIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(odm_dlkmimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_ODM_DLKM)/%,$(INTERNAL_ODM_DLKMIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_ODM_DLKMIMAGE_TARGET := $(BUILT_ODM_DLKMIMAGE_TARGET)
@@ -4249,6 +4279,9 @@
endef
$(eval $(call write-file-lines,$(system_dlkmimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_SYSTEM_DLKM)/,,$(filter $(TARGET_OUT_SYSTEM_DLKM)/%,$(INTERNAL_SYSTEM_DLKMIMAGE_FILES)))))
+# Used by soong sandwich to request the staging dir be built
+$(system_dlkmimage_intermediates)/staging_dir.stamp: $(filter $(TARGET_OUT_SYSTEM_DLKM)/%,$(INTERNAL_SYSTEM_DLKMIMAGE_FILES))
+ touch $@
# We just build this directly to the install location.
INSTALLED_SYSTEM_DLKMIMAGE_TARGET := $(BUILT_SYSTEM_DLKMIMAGE_TARGET)
@@ -5061,9 +5094,12 @@
check_vintf_all_deps :=
# -----------------------------------------------------------------
-# Activate vendor APEXes for checkvintf
+# Activate APEXes for checkvintf
apex_dirs := \
+ $(TARGET_OUT)/apex/% \
+ $(TARGET_OUT_PRODUCT)/apex/% \
+ $(TARGET_OUT_SYSTEM_EXT)/apex/% \
$(TARGET_OUT_VENDOR)/apex/% \
apex_files := $(sort $(filter $(apex_dirs), $(INTERNAL_ALLIMAGES_FILES)))
@@ -5079,7 +5115,10 @@
@echo $(PRIVATE_APEX_FILES) > /dev/null
@rm -rf $(APEX_OUT)
@mkdir -p $(APEX_OUT)
- $< --vendor_path $(TARGET_OUT_VENDOR) \
+ $< --system_path $(TARGET_OUT) \
+ --system_ext_path $(TARGET_OUT_SYSTEM_EXT) \
+ --product_path $(TARGET_OUT_PRODUCT) \
+ --vendor_path $(TARGET_OUT_VENDOR) \
--apex_path $(APEX_OUT)
apex_files :=
@@ -5087,14 +5126,14 @@
# The build system only writes VINTF metadata to */etc/vintf paths. Legacy paths aren't needed here
# because they are only used for prebuilt images.
-# APEX files in /vendor/apex can have VINTF fragments as well.
+# APEX files in /$partition/apex can have VINTF fragments as well.
check_vintf_common_srcs_patterns := \
$(TARGET_OUT)/etc/vintf/% \
$(TARGET_OUT_VENDOR)/etc/vintf/% \
$(TARGET_OUT_ODM)/etc/vintf/% \
$(TARGET_OUT_PRODUCT)/etc/vintf/% \
$(TARGET_OUT_SYSTEM_EXT)/etc/vintf/% \
- $(TARGET_OUT_VENDOR)/apex/% \
+ $(apex_dirs)
check_vintf_common_srcs := $(sort $(filter $(check_vintf_common_srcs_patterns),$(INTERNAL_ALLIMAGES_FILES)))
check_vintf_common_srcs_patterns :=
@@ -5110,16 +5149,19 @@
endif
# -- Check system and system_ext manifests / matrices including fragments (excluding other framework manifests / matrices, e.g. product);
+ifdef BUILDING_SYSTEM_IMAGE
check_vintf_system_deps := $(filter $(TARGET_OUT)/etc/vintf/% \
- $(TARGET_OUT_SYSTEM_EXT)/etc/vintf/%, \
+ $(TARGET_OUT_SYSTEM_EXT)/etc/vintf/% \
+ $(TARGET_OUT)/apex/% \
+ $(TARGET_OUT_SYSTEM_EXT)/apex/%, \
$(check_vintf_common_srcs))
ifneq ($(check_vintf_system_deps),)
check_vintf_has_system := true
check_vintf_system_log := $(intermediates)/check_vintf_system.log
check_vintf_all_deps += $(check_vintf_system_log)
-$(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps)
- @( $< --check-one --dirmap /system:$(TARGET_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
+$(check_vintf_system_log): $(HOST_OUT_EXECUTABLES)/checkvintf $(check_vintf_system_deps) $(APEX_INFO_FILE)
+ @( $< --check-one --dirmap /system:$(TARGET_OUT) --dirmap /apex:$(APEX_OUT) > $@ 2>&1 ) || ( cat $@ && exit 1 )
$(call declare-1p-target,$(check_vintf_system_log))
check_vintf_system_log :=
@@ -5128,10 +5170,11 @@
vintffm_log := $(intermediates)/vintffm.log
endif
check_vintf_all_deps += $(vintffm_log)
-$(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps)
+$(vintffm_log): $(HOST_OUT_EXECUTABLES)/vintffm $(check_vintf_system_deps) $(APEX_INFO_FILE)
@( $< --check --dirmap /system:$(TARGET_OUT) \
--dirmap /system_ext:$(TARGET_OUT_SYSTEM_EXT) \
--dirmap /product:$(TARGET_OUT_PRODUCT) \
+ --dirmap /apex:$(APEX_OUT) \
$(VINTF_FRAMEWORK_MANIFEST_FROZEN_DIR) > $@ 2>&1 ) || ( cat $@ && exit 1 )
$(call declare-1p-target,$(vintffm_log))
@@ -5139,6 +5182,8 @@
endif # check_vintf_system_deps
check_vintf_system_deps :=
+endif # BUILDING_SYSTEM_IMAGE
+
# -- Check vendor manifest / matrix including fragments (excluding other device manifests / matrices)
check_vintf_vendor_deps := $(filter $(TARGET_OUT_VENDOR)/etc/vintf/% \
$(TARGET_OUT_VENDOR)/apex/%, \
@@ -5714,6 +5759,10 @@
echo "flash vbmeta_$(partition)" >> $@;)
endif
endif # BOARD_AVB_ENABLE
+ifneq (,$(strip $(BOARD_CUSTOMIMAGES_PARTITION_LIST)))
+ $(hide) $(foreach partition,$(BOARD_CUSTOMIMAGES_PARTITION_LIST), \
+ echo "flash $(partition)" >> $@;)
+endif
$(hide) echo "reboot fastboot" >> $@
$(hide) echo "update-super" >> $@
$(hide) $(foreach partition,$(BOARD_SUPER_PARTITION_PARTITION_LIST), \
@@ -5915,6 +5964,9 @@
ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
$(hide) echo "allow_non_ab=true" >> $@
endif
+ifeq ($(BOARD_NON_AB_OTA_DISABLE_COMPRESSION),true)
+ $(hide) echo "board_non_ab_ota_disable_compression=true" >> $@
+endif
ifdef BOARD_PREBUILT_DTBOIMAGE
$(hide) echo "has_dtbo=true" >> $@
ifeq ($(BOARD_AVB_ENABLE),true)
@@ -6075,7 +6127,7 @@
# $1: root directory
# $2: add prefix
define fs_config
-(cd $(1); find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,$(2),' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) -R "$(2)"
+(cd $(1); find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,$(2),' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -R "$(2)"
endef
define filter-out-missing-vendor
@@ -7555,6 +7607,24 @@
sdk_atree_files += $(atree_dir)/sdk.atree
endif
+SDK_METADATA_DIR :=$= $(call intermediates-dir-for,PACKAGING,framework-doc-stubs-metadata,,COMMON)
+SDK_METADATA_FILES :=$= $(addprefix $(SDK_METADATA_DIR)/,\
+ activity_actions.txt \
+ broadcast_actions.txt \
+ categories.txt \
+ features.txt \
+ service_actions.txt \
+ widgets.txt)
+SDK_METADATA :=$= $(firstword $(SDK_METADATA_FILES))
+$(SDK_METADATA): .KATI_IMPLICIT_OUTPUTS := $(filter-out $(SDK_METADATA),$(SDK_METADATA_FILES))
+$(SDK_METADATA): $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/framework-doc-stubs-metadata.zip
+ rm -rf $(SDK_METADATA_DIR)
+ mkdir -p $(SDK_METADATA_DIR)
+ unzip -DDqo $< -d $(SDK_METADATA_DIR)
+
+.PHONY: framework-doc-stubs
+framework-doc-stubs: $(SDK_METADATA)
+
deps := \
$(OUT_DOCS)/offline-sdk-timestamp \
$(SDK_METADATA_FILES) \
diff --git a/core/OWNERS b/core/OWNERS
index 36951a9..35ea83d 100644
--- a/core/OWNERS
+++ b/core/OWNERS
@@ -10,3 +10,4 @@
# For Ravenwood test configs
per-file ravenwood_test_config_template.xml = jsharkey@google.com,omakoto@google.com
+
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 9f43a3e..70b3744 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -37,49 +37,16 @@
# Default behavior for the tree wrt building modules or using prebuilts. This
# can always be overridden by setting the environment variable
# MODULE_BUILD_FROM_SOURCE.
-BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
-
-ifneq ($(SANITIZE_TARGET)$(EMMA_INSTRUMENT_FRAMEWORK),)
- # Always use sources when building the framework with Java coverage or
- # sanitized builds as they both require purpose built prebuilts which we do
- # not provide.
- BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
-endif
-
-# ART does not provide linux_bionic variants needed for products that
-# set HOST_CROSS_OS=linux_bionic.
-ifeq (linux_bionic,${HOST_CROSS_OS})
- BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
-endif
-
-# ART does not provide host side arm64 variants needed for products that
-# set HOST_CROSS_ARCH=arm64.
-ifeq (arm64,${HOST_CROSS_ARCH})
- BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
-endif
-
-# TV based devices do not seem to work with prebuilts, so build from source
-# for now and fix in a follow up.
-ifneq (,$(filter tv,$(subst $(comma),$(space),${PRODUCT_CHARACTERISTICS})))
- BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
-endif
-
-# ATV based devices do not seem to work with prebuilts, so build from source
-# for now and fix in a follow up.
-ifneq (,${PRODUCT_IS_ATV})
- BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := true
+BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := $(RELEASE_DEFAULT_MODULE_BUILD_FROM_SOURCE)
+# TODO(b/301454934): The value from build flag is set to empty when use `False`
+# The condition below can be removed after the issue get sorted.
+ifeq (,$(BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE))
+ BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE := false
endif
ifneq (,$(MODULE_BUILD_FROM_SOURCE))
# Keep an explicit setting.
-else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS))$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES))$(findstring com.google.android.go.conscrypt,$(PRODUCT_PACKAGES)))
- # Prebuilt module SDKs require prebuilt modules to work, and currently
- # prebuilt modules are only provided for com.google.android(.go)?.xxx. If we can't
- # find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
- # and disable prebuilt SDKs. In particular this applies to AOSP builds.
- #
- # However, docs/sdk/win_sdk/sdk_addon builds might not include com.google.android.xxx
- # packages, so for those we respect the default behavior.
+else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS)))
MODULE_BUILD_FROM_SOURCE := true
else ifneq (,$(PRODUCT_MODULE_BUILD_FROM_SOURCE))
# Let products override the branch default.
@@ -111,10 +78,23 @@
# are controlled by the MODULE_BUILD_FROM_SOURCE environment variable by
# default.
INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES := \
+ adservices \
+ appsearch \
btservices \
devicelock \
+ configinfrastructure \
+ conscrypt \
+ healthfitness \
+ ipsec \
+ media \
+ mediaprovider \
+ ondevicepersonalization \
permission \
rkpd \
+ scheduling \
+ sdkext \
+ statsd \
+ tethering \
uwb \
wifi \
@@ -144,8 +124,8 @@
SYSTEMUI_OPTIMIZE_JAVA ?= true
$(call add_soong_config_var,ANDROID,SYSTEMUI_OPTIMIZE_JAVA)
-# Disable Compose in SystemUI by default.
-SYSTEMUI_USE_COMPOSE ?= false
+# Enable Compose in SystemUI by default.
+SYSTEMUI_USE_COMPOSE ?= true
$(call add_soong_config_var,ANDROID,SYSTEMUI_USE_COMPOSE)
ifdef PRODUCT_AVF_ENABLED
@@ -164,6 +144,10 @@
$(call add_soong_config_var_value,ANDROID,release_binder_death_recipient_weak_from_jni,$(RELEASE_BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI))
+$(call add_soong_config_var_value,ANDROID,release_package_libandroid_runtime_punch_holes,$(RELEASE_PACKAGE_LIBANDROID_RUNTIME_PUNCH_HOLES))
+
+$(call add_soong_config_var_value,ANDROID,release_selinux_data_data_ignore,$(RELEASE_SELINUX_DATA_DATA_IGNORE))
+
# Enable system_server optimizations by default unless explicitly set or if
# there may be dependent runtime jars.
# TODO(b/240588226): Remove the off-by-default exceptions after handling
diff --git a/core/art_config.mk b/core/art_config.mk
index 54bfd6b..47b4bcf 100644
--- a/core/art_config.mk
+++ b/core/art_config.mk
@@ -27,8 +27,11 @@
# soong variables indicate whether the prebuilt is enabled:
# - $(m)_module/source_build for art and TOGGLEABLE_PREBUILT_MODULES
# - ANDROID/module_build_from_source for other mainline modules
+# Note that RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST is the list of module names
+# and library names of jars that need to be removed. We have to keep separated list per
+# release config due to possibility of different prebuilt content.
APEX_BOOT_JARS_EXCLUDED :=
-$(foreach pair, $(PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY),\
+$(foreach pair, $(RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST),\
$(eval m := $(subst com.android.,,$(call word-colon,1,$(pair)))) \
$(if $(call soong_config_get,$(m)_module,source_build), \
$(if $(filter true,$(call soong_config_get,$(m)_module,source_build)),, \
diff --git a/core/binary.mk b/core/binary.mk
index 6dab49c..b17ab00 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -481,6 +481,34 @@
my_cflags += $(CLANG_EXTERNAL_CFLAGS)
endif
+# Extra cflags for projects under hardware/ directory.
+# This should match the definition of `thirdPartyDirPrefixExceptions`
+# in build/soong/android/paths.go.
+# Get the second element of LOCAL_PATH
+ifneq ($(filter hardware/%,$(LOCAL_PATH)),)
+ my_subdir := $(word 2,$(subst /,$(space),$(LOCAL_PATH)))
+ must_compile_hardware_subdirs := \
+ hardware/google/% \
+ hardware/interfaces/% \
+ hardware/libhardware/% \
+ hardware/libhardware_legacy/% \
+ hardware/ril/%
+ ifeq ($(filter $(must_compile_hardware_subdirs),$(my_subdir)),)
+ my_cflags += $(CLANG_EXTERNAL_CFLAGS)
+ endif
+endif
+
+# Extra cflags for projects under vendor/ directory.
+# This should match the definition of `thirdPartyDirPrefixExceptions`
+# in build/soong/android/paths.go.
+ifneq ($(filter vendor/%,$(LOCAL_PATH)),)
+ my_subdir := $(word 2,$(subst /,$(space),$(LOCAL_PATH)))
+ # Do not add the flags for any subdir that contains the string "google".
+ ifneq ($(findstring google,$(my_subdir)),)
+ my_cflags += $(CLANG_EXTERNAL_CFLAGS)
+ endif
+endif
+
# arch-specific static libraries go first so that generic ones can depend on them
my_static_libraries := $(LOCAL_STATIC_LIBRARIES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)) $(LOCAL_STATIC_LIBRARIES_$(my_32_64_bit_suffix)) $(my_static_libraries)
my_whole_static_libraries := $(LOCAL_WHOLE_STATIC_LIBRARIES_$($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)) $(LOCAL_WHOLE_STATIC_LIBRARIES_$(my_32_64_bit_suffix)) $(my_whole_static_libraries)
diff --git a/core/board_config.mk b/core/board_config.mk
index 8c23f93..633303f 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -287,6 +287,9 @@
include $(BUILD_SYSTEM)/board_config_wifi.mk
+# Set up soong config for "soong_config_value_variable".
+-include vendor/google/build/soong/soong_config_namespace/camera.mk
+
# Default *_CPU_VARIANT_RUNTIME to CPU_VARIANT if unspecified.
TARGET_CPU_VARIANT_RUNTIME := $(or $(TARGET_CPU_VARIANT_RUNTIME),$(TARGET_CPU_VARIANT))
TARGET_2ND_CPU_VARIANT_RUNTIME := $(or $(TARGET_2ND_CPU_VARIANT_RUNTIME),$(TARGET_2ND_CPU_VARIANT))
@@ -946,6 +949,11 @@
endif
endif
+# For Non A/B full OTA, disable brotli compression.
+ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
+ BOARD_NON_AB_OTA_DISABLE_COMPRESSION := true
+endif
+
# Quick check for building generic OTA packages. Currently it only supports A/B OTAs.
ifeq ($(PRODUCT_BUILD_GENERIC_OTA_PACKAGE),true)
ifneq ($(AB_OTA_UPDATER),true)
@@ -965,15 +973,6 @@
$(if $(wildcard $(vndk_path)/*/Android.bp),,$(error VNDK version $(1) not found))
endef
-ifeq ($(KEEP_VNDK),true)
-ifeq ($(BOARD_VNDK_VERSION),$(PLATFORM_VNDK_VERSION))
- $(error BOARD_VNDK_VERSION is equal to PLATFORM_VNDK_VERSION; use BOARD_VNDK_VERSION := current)
-endif
-ifneq ($(BOARD_VNDK_VERSION),current)
- $(call check_vndk_version,$(BOARD_VNDK_VERSION))
-endif
-endif
-
TARGET_VENDOR_TEST_SUFFIX := /vendor
ifeq (,$(TARGET_BUILD_UNBUNDLED))
@@ -993,7 +992,7 @@
# BOARD_API_LEVEL for vendor API surface
ifdef RELEASE_BOARD_API_LEVEL
ifdef BOARD_API_LEVEL
- $(error BOARD_API_LEVEL must not set manully. The build system automatically sets this value.)
+ $(error BOARD_API_LEVEL must not be set manually. The build system automatically sets this value.)
endif
BOARD_API_LEVEL := $(RELEASE_BOARD_API_LEVEL)
.KATI_READONLY := BOARD_API_LEVEL
diff --git a/core/config.mk b/core/config.mk
index dbee0a0..22ec292 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -172,6 +172,7 @@
$(KATI_obsolete_var BOARD_PREBUILT_PVMFWIMAGE,pvmfw.bin is now built in AOSP and custom versions are no longer supported)
$(KATI_obsolete_var BUILDING_PVMFW_IMAGE,BUILDING_PVMFW_IMAGE is no longer used)
$(KATI_obsolete_var BOARD_BUILD_SYSTEM_ROOT_IMAGE)
+$(KATI_obsolete_var FS_GET_STATS)
# Used to force goals to build. Only use for conditionally defined goals.
.PHONY: FORCE
@@ -406,35 +407,25 @@
else ifeq ($(strip $(call is-low-mem-device)),true)
# Low memory device will have 4096 binary alignment.
TARGET_MAX_PAGE_SIZE_SUPPORTED := 4096
-else
- # The default binary alignment for userspace is 4096.
+else ifeq ($(call math_lt,$(VSR_VENDOR_API_LEVEL),34),true)
TARGET_MAX_PAGE_SIZE_SUPPORTED := 4096
- # When VSR vendor API level >= 34, binary alignment will be 65536.
- ifeq ($(call math_gt_or_eq,$(VSR_VENDOR_API_LEVEL),34),true)
- ifeq ($(TARGET_ARCH),arm64)
- TARGET_MAX_PAGE_SIZE_SUPPORTED := 65536
- endif
- endif
+else ifeq (,$(filter arm64 x86_64,$(TARGET_ARCH)))
+ # TARGET_MAX_PAGE_SIZE_SUPPORTED > 4096 is only supported in arm64 and
+ # x86_64 targets.
+ TARGET_MAX_PAGE_SIZE_SUPPORTED := 4096
+else
+ # The default binary alignment for userspace is 16384.
+ TARGET_MAX_PAGE_SIZE_SUPPORTED := 16384
endif
.KATI_READONLY := TARGET_MAX_PAGE_SIZE_SUPPORTED
-# Only arm64 and x86_64 archs supports TARGET_MAX_PAGE_SIZE_SUPPORTED greater than 4096.
-ifneq ($(TARGET_MAX_PAGE_SIZE_SUPPORTED),4096)
- ifeq (,$(filter arm64 x86_64,$(TARGET_ARCH)))
- $(error TARGET_MAX_PAGE_SIZE_SUPPORTED=$(TARGET_MAX_PAGE_SIZE_SUPPORTED) is greater than 4096. Only supported in arm64 and x86_64 archs)
- endif
-endif
-
-# Boolean variable determining if AOSP is page size agnostic. This means
-# that AOSP can use a kernel configured with 4k/16k/64k PAGE SIZES.
-TARGET_NO_BIONIC_PAGE_SIZE_MACRO := false
+# Boolean variable determining if AOSP relies on bionic's PAGE_SIZE macro.
ifdef PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO
TARGET_NO_BIONIC_PAGE_SIZE_MACRO := $(PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO)
- ifeq ($(TARGET_NO_BIONIC_PAGE_SIZE_MACRO),true)
- ifneq ($(TARGET_MAX_PAGE_SIZE_SUPPORTED),65536)
- $(error TARGET_MAX_PAGE_SIZE_SUPPORTED has to be 65536 to support page size agnostic)
- endif
- endif
+else ifeq ($(call math_lt,$(VSR_VENDOR_API_LEVEL),35),true)
+ TARGET_NO_BIONIC_PAGE_SIZE_MACRO := false
+else
+ TARGET_NO_BIONIC_PAGE_SIZE_MACRO := true
endif
.KATI_READONLY := TARGET_NO_BIONIC_PAGE_SIZE_MACRO
@@ -679,18 +670,12 @@
else
MKBOOTIMG := $(BOARD_CUSTOM_MKBOOTIMG)
endif
-ifeq (,$(strip $(BOARD_CUSTOM_BPTTOOL)))
-BPTTOOL := $(HOST_OUT_EXECUTABLES)/bpttool$(HOST_EXECUTABLE_SUFFIX)
-else
-BPTTOOL := $(BOARD_CUSTOM_BPTTOOL)
-endif
ifeq (,$(strip $(BOARD_CUSTOM_AVBTOOL)))
AVBTOOL := $(HOST_OUT_EXECUTABLES)/avbtool$(HOST_EXECUTABLE_SUFFIX)
else
AVBTOOL := $(BOARD_CUSTOM_AVBTOOL)
endif
APICHECK := $(HOST_OUT_JAVA_LIBRARIES)/metalava$(COMMON_JAVA_PACKAGE_SUFFIX)
-FS_GET_STATS := $(HOST_OUT_EXECUTABLES)/fs_get_stats$(HOST_EXECUTABLE_SUFFIX)
MKEXTUSERIMG := $(HOST_OUT_EXECUTABLES)/mkuserimg_mke2fs
MKE2FS_CONF := system/extras/ext4_utils/mke2fs.conf
MKEROFS := $(HOST_OUT_EXECUTABLES)/mkfs.erofs
@@ -885,6 +870,7 @@
32.0 \
33.0 \
34.0 \
+ 202404 \
)
.KATI_READONLY := \
@@ -1273,7 +1259,7 @@
endif
ifeq (true,$(FULL_SYSTEM_OPTIMIZE_JAVA))
-ifeq (,$(SYSTEM_OPTIMIZE_JAVA))
+ifeq (false,$(SYSTEM_OPTIMIZE_JAVA))
$(error SYSTEM_OPTIMIZE_JAVA must be enabled when FULL_SYSTEM_OPTIMIZE_JAVA is enabled)
endif
endif
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 30a6c06..1c3a1b3 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -57,18 +57,6 @@
KEEP_VNDK ?= true
endif
-ifeq ($(KEEP_VNDK),true)
- # Starting in Android U, non-VNDK devices not supported
- # WARNING: DO NOT CHANGE: if you are downstream of AOSP, and you change this, without
- # letting upstream know it's important to you, we may do cleanup which breaks this
- # significantly. Please let us know if you are changing this.
- ifndef BOARD_VNDK_VERSION
- # READ WARNING - DO NOT CHANGE
- BOARD_VNDK_VERSION := current
- # READ WARNING - DO NOT CHANGE
- endif
-endif
-
# ---------------------------------------------------------------
# Set up version information
include $(BUILD_SYSTEM)/version_util.mk
@@ -789,6 +777,7 @@
TARGET_OUT_ODM_APPS := $(target_out_odm_app_base)/app
TARGET_OUT_ODM_APPS_PRIVILEGED := $(target_out_odm_app_base)/priv-app
TARGET_OUT_ODM_ETC := $(TARGET_OUT_ODM)/etc
+TARGET_OUT_ODM_FAKE := $(PRODUCT_OUT)/odm_fake_packages
.KATI_READONLY := \
TARGET_OUT_ODM \
TARGET_OUT_ODM_EXECUTABLES \
@@ -798,7 +787,8 @@
TARGET_OUT_ODM_JAVA_LIBRARIES \
TARGET_OUT_ODM_APPS \
TARGET_OUT_ODM_APPS_PRIVILEGED \
- TARGET_OUT_ODM_ETC
+ TARGET_OUT_ODM_ETC \
+ TARGET_OUT_ODM_FAKE
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_ODM_EXECUTABLES := $(TARGET_OUT_ODM_EXECUTABLES)
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_ODM_SHARED_LIBRARIES := $(target_out_odm_shared_libraries_base)/lib
@@ -936,13 +926,15 @@
TARGET_OUT_PRODUCT_APPS := $(target_out_product_app_base)/app
TARGET_OUT_PRODUCT_APPS_PRIVILEGED := $(target_out_product_app_base)/priv-app
TARGET_OUT_PRODUCT_ETC := $(TARGET_OUT_PRODUCT)/etc
+TARGET_OUT_PRODUCT_FAKE := $(TARGET_OUT_PRODUCT)/product_fake_packages
.KATI_READONLY := \
TARGET_OUT_PRODUCT_EXECUTABLES \
TARGET_OUT_PRODUCT_SHARED_LIBRARIES \
TARGET_OUT_PRODUCT_JAVA_LIBRARIES \
TARGET_OUT_PRODUCT_APPS \
TARGET_OUT_PRODUCT_APPS_PRIVILEGED \
- TARGET_OUT_PRODUCT_ETC
+ TARGET_OUT_PRODUCT_ETC \
+ TARGET_OUT_PRODUCT_FAKE
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_PRODUCT_EXECUTABLES := $(TARGET_OUT_PRODUCT_EXECUTABLES)
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_PRODUCT_SHARED_LIBRARIES := $(target_out_product_shared_libraries_base)/lib
@@ -979,13 +971,15 @@
TARGET_OUT_SYSTEM_EXT_APPS_PRIVILEGED := $(target_out_system_ext_app_base)/priv-app
TARGET_OUT_SYSTEM_EXT_ETC := $(TARGET_OUT_SYSTEM_EXT)/etc
TARGET_OUT_SYSTEM_EXT_EXECUTABLES := $(TARGET_OUT_SYSTEM_EXT)/bin
+TARGET_OUT_SYSTEM_EXT_FAKE := $(PRODUCT_OUT)/system_ext_fake_packages
.KATI_READONLY := \
TARGET_OUT_SYSTEM_EXT_EXECUTABLES \
TARGET_OUT_SYSTEM_EXT_SHARED_LIBRARIES \
TARGET_OUT_SYSTEM_EXT_JAVA_LIBRARIES \
TARGET_OUT_SYSTEM_EXT_APPS \
TARGET_OUT_SYSTEM_EXT_APPS_PRIVILEGED \
- TARGET_OUT_SYSTEM_EXT_ETC
+ TARGET_OUT_SYSTEM_EXT_ETC \
+ TARGET_OUT_SYSTEM_EXT_FAKE
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_EXT_EXECUTABLES := $(TARGET_OUT_SYSTEM_EXT_EXECUTABLES)
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_EXT_SHARED_LIBRARIES := $(target_out_system_ext_shared_libraries_base)/lib
diff --git a/core/main.mk b/core/main.mk
index 9b7382b..b798b49 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -221,20 +221,6 @@
# mount system_other partition.
ADDITIONAL_SYSTEM_PROPERTIES += ro.postinstall.fstab.prefix=/system
-# -----------------------------------------------------------------
-# ADDITIONAL_VENDOR_PROPERTIES will be installed in vendor/build.prop if
-# property_overrides_split_enabled is true. Otherwise it will be installed in
-# /system/build.prop
-ifeq ($(KEEP_VNDK),true)
-ifdef BOARD_VNDK_VERSION
- ifeq ($(BOARD_VNDK_VERSION),current)
- ADDITIONAL_VENDOR_PROPERTIES := ro.vndk.version=$(PLATFORM_VNDK_VERSION)
- else
- ADDITIONAL_VENDOR_PROPERTIES := ro.vndk.version=$(BOARD_VNDK_VERSION)
- endif
-endif
-endif
-
# Add cpu properties for bionic and ART.
ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.arch=$(TARGET_ARCH)
ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.cpu_variant=$(TARGET_CPU_VARIANT_RUNTIME)
@@ -284,6 +270,11 @@
ro.product.first_api_level=$(PRODUCT_SHIPPING_API_LEVEL)
endif
+ifdef PRODUCT_SHIPPING_VENDOR_API_LEVEL
+ADDITIONAL_VENDOR_PROPERTIES += \
+ ro.vendor.api_level=$(PRODUCT_SHIPPING_VENDOR_API_LEVEL)
+endif
+
ifneq ($(TARGET_BUILD_VARIANT),user)
ifdef PRODUCT_SET_DEBUGFS_RESTRICTIONS
ADDITIONAL_VENDOR_PROPERTIES += \
@@ -341,18 +332,6 @@
ro.build.ab_update=$(AB_OTA_UPDATER)
endif
-# Set ro.product.vndk.version to PLATFORM_VNDK_VERSION only if
-# KEEP_VNDK is true, PRODUCT_PRODUCT_VNDK_VERSION is current and
-# PLATFORM_VNDK_VERSION is less than or equal to 35.
-# ro.product.vndk.version must be removed for the other future builds.
-ifeq ($(KEEP_VNDK)|$(PRODUCT_PRODUCT_VNDK_VERSION),true|current)
-ifeq ($(call math_is_number,$(PLATFORM_VNDK_VERSION)),true)
-ifeq ($(call math_lt_or_eq,$(PLATFORM_VNDK_VERSION),35),true)
-ADDITIONAL_PRODUCT_PROPERTIES += ro.product.vndk.version=$(PLATFORM_VNDK_VERSION)
-endif
-endif
-endif
-
ADDITIONAL_PRODUCT_PROPERTIES += ro.build.characteristics=$(TARGET_AAPT_CHARACTERISTICS)
ifeq ($(AB_OTA_UPDATER),true)
@@ -1243,8 +1222,7 @@
# Returns modules included automatically as a result of certain BoardConfig
# variables being set.
define auto-included-modules
- $(if $(and $(BOARD_VNDK_VERSION),$(filter true,$(KEEP_VNDK))),vndk_package) \
- $(if $(filter true,$(KEEP_VNDK)),,llndk_in_system) \
+ llndk_in_system \
$(if $(DEVICE_MANIFEST_FILE),vendor_manifest.xml) \
$(if $(DEVICE_MANIFEST_SKUS),$(foreach sku, $(DEVICE_MANIFEST_SKUS),vendor_manifest_$(sku).xml)) \
$(if $(ODM_MANIFEST_FILES),odm_manifest.xml) \
@@ -1672,6 +1650,7 @@
$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
$(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
$(INSTALLED_PRODUCTIMAGE_TARGET) \
+ $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) \
$(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET) \
$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET) \
@@ -1853,12 +1832,12 @@
$(INSTALLED_FILES_JSON_SYSTEMOTHER) \
$(INSTALLED_FILES_FILE_RECOVERY) \
$(INSTALLED_FILES_JSON_RECOVERY) \
- $(INSTALLED_BUILD_PROP_TARGET):build.prop \
- $(INSTALLED_VENDOR_BUILD_PROP_TARGET):build.prop-vendor \
- $(INSTALLED_PRODUCT_BUILD_PROP_TARGET):build.prop-product \
- $(INSTALLED_ODM_BUILD_PROP_TARGET):build.prop-odm \
- $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET):build.prop-system_ext \
- $(INSTALLED_RAMDISK_BUILD_PROP_TARGET):build.prop-ramdisk \
+ $(if $(BUILDING_SYSTEM_IMAGE), $(INSTALLED_BUILD_PROP_TARGET):build.prop) \
+ $(if $(BUILDING_VENDOR_IMAGE), $(INSTALLED_VENDOR_BUILD_PROP_TARGET):build.prop-vendor) \
+ $(if $(BUILDING_PRODUCT_IMAGE), $(INSTALLED_PRODUCT_BUILD_PROP_TARGET):build.prop-product) \
+ $(if $(BUILDING_ODM_IMAGE), $(INSTALLED_ODM_BUILD_PROP_TARGET):build.prop-odm) \
+ $(if $(BUILDING_SYSTEM_EXT_IMAGE), $(INSTALLED_SYSTEM_EXT_BUILD_PROP_TARGET):build.prop-system_ext) \
+ $(if $(BUILDING_RAMDISK_IMAGE), $(INSTALLED_RAMDISK_BUILD_PROP_TARGET):build.prop-ramdisk) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(INSTALLED_MISC_INFO_TARGET) \
$(INSTALLED_RAMDISK_TARGET) \
diff --git a/core/packaging/flags.mk b/core/packaging/flags.mk
index 62ef3df..e715fd1 100644
--- a/core/packaging/flags.mk
+++ b/core/packaging/flags.mk
@@ -119,13 +119,14 @@
# $(5): installed aconfig flags storage flag map file (out)
# $(6): installed aconfig flags storage flag value file (out)
# $(7): input aconfig files for the partition (in)
+# $(8): partition name
define generate-partition-aconfig-storage-file
$(eval $(strip $(1)): PRIVATE_OUT := $(strip $(1)))
$(eval $(strip $(1)): PRIVATE_IN := $(strip $(7)))
$(strip $(1)): $(ACONFIG) $(strip $(7))
mkdir -p $$(dir $$(PRIVATE_OUT))
$$(if $$(PRIVATE_IN), \
- $$(ACONFIG) create-storage --container "" --file package_map --out $$(PRIVATE_OUT) \
+ $$(ACONFIG) create-storage --container $(8) --file package_map --out $$(PRIVATE_OUT) \
$$(addprefix --cache ,$$(PRIVATE_IN)), \
)
touch $$(PRIVATE_OUT)
@@ -134,7 +135,7 @@
$(strip $(2)): $(ACONFIG) $(strip $(7))
mkdir -p $$(dir $$(PRIVATE_OUT))
$$(if $$(PRIVATE_IN), \
- $$(ACONFIG) create-storage --container "" --file flag_map --out $$(PRIVATE_OUT) \
+ $$(ACONFIG) create-storage --container $(8) --file flag_map --out $$(PRIVATE_OUT) \
$$(addprefix --cache ,$$(PRIVATE_IN)), \
)
touch $$(PRIVATE_OUT)
@@ -143,7 +144,7 @@
$(strip $(3)): $(ACONFIG) $(strip $(7))
mkdir -p $$(dir $$(PRIVATE_OUT))
$$(if $$(PRIVATE_IN), \
- $$(ACONFIG) create-storage --container "" --file flag_val --out $$(PRIVATE_OUT) \
+ $$(ACONFIG) create-storage --container $(8) --file flag_val --out $$(PRIVATE_OUT) \
$$(addprefix --cache ,$$(PRIVATE_IN)), \
)
touch $$(PRIVATE_OUT)
@@ -154,9 +155,9 @@
ifeq ($(RELEASE_CREATE_ACONFIG_STORAGE_FILE),true)
$(foreach partition, $(_FLAG_PARTITIONS), \
- $(eval aconfig_storage_package_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/package.map) \
- $(eval aconfig_storage_flag_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.map) \
- $(eval aconfig_storage_flag_val.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.val) \
+ $(eval aconfig_storage_package_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/package.map) \
+ $(eval aconfig_storage_flag_map.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/flag.map) \
+ $(eval aconfig_storage_flag_val.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/aconfig/flag.val) \
$(eval $(call generate-partition-aconfig-storage-file, \
$(TARGET_OUT_FLAGS)/$(partition)/package.map, \
$(TARGET_OUT_FLAGS)/$(partition)/flag.map, \
@@ -167,6 +168,7 @@
$(sort $(foreach m,$(call register-names-for-partition, $(partition)), \
$(ALL_MODULES.$(m).ACONFIG_FILES) \
)), \
+ $(partition), \
)) \
)
endif
diff --git a/core/product.mk b/core/product.mk
index 4250253..9752f32 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -33,8 +33,7 @@
# 4096, 16384 and 65536.
_product_single_value_vars += PRODUCT_MAX_PAGE_SIZE_SUPPORTED
-# Indicates that AOSP can use a kernel configured with 4k/16k/64k page sizes.
-# The possible values are true or false.
+# Boolean variable determining if AOSP relies on bionic's PAGE_SIZE macro.
_product_single_value_vars += PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO
# The resource configuration options to use for this product.
@@ -230,6 +229,9 @@
# The first API level this product shipped with
_product_single_value_vars += PRODUCT_SHIPPING_API_LEVEL
+# The first vendor API level this product shipped with
+_product_single_value_vars += PRODUCT_SHIPPING_VENDOR_API_LEVEL
+
_product_list_vars += VENDOR_PRODUCT_RESTRICT_VENDOR_FILES
_product_list_vars += VENDOR_EXCEPTION_MODULES
_product_list_vars += VENDOR_EXCEPTION_PATHS
@@ -323,6 +325,13 @@
# set this variable to prevent OTA failures.
_product_list_vars += PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS
+# If set to true, this product forces HIDL to be enabled by declaring android.hidl.manager
+# and android.hidl.token in the framework manifest. The product will also need to add the
+# 'hwservicemanager' service to PRODUCT_PACKAGES if its SHIPPING_API_LEVEL is greater than 34.
+# This should only be used during bringup for devices that are targeting FCM 202404 and still
+# have partner-owned HIDL interfaces that are being converted to AIDL.
+_product_single_value_vars += PRODUCT_HIDL_ENABLED
+
# If set to true, this product builds a generic OTA package, which installs generic system images
# onto matching devices. The product may only build a subset of system images (e.g. only
# system.img), so devices need to install the package in a system-only OTA manner.
@@ -454,7 +463,11 @@
_product_single_value_vars += PRODUCT_BUILD_FROM_SOURCE_STUB
-_product_list_vars += PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS
+_product_single_value_vars += PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS
+
+_product_single_value_vars += PRODUCT_HIDDEN_API_EXPORTABLE_STUBS
+
+_product_single_value_vars += PRODUCT_EXPORT_RUNTIME_APIS
.KATI_READONLY := _product_single_value_vars _product_list_vars
_product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
diff --git a/core/product_config.mk b/core/product_config.mk
index 500735e..d16c38d 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -301,6 +301,23 @@
PRODUCT_INCLUDE_TAGS += com.android.mainline mainline_module_prebuilt_nightly
endif
+# AOSP and Google products currently share the same `apex_contributions` in next.
+# This causes issues when building <aosp_product>-next-userdebug in main.
+# Create a temporary allowlist to ignore the google apexes listed in `contents` of apex_contributions of `next`
+# *for aosp products*.
+# TODO(b/308187268): Remove this denylist mechanism
+# Use PRODUCT_PACKAGES to determine if this is an aosp product. aosp products do not use google signed apexes.
+ignore_apex_contributions :=
+ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES))$(findstring com.google.android.go.conscrypt,$(PRODUCT_PACKAGES)))
+ ignore_apex_contributions := true
+endif
+ifeq (true,$(PRODUCT_MODULE_BUILD_FROM_SOURCE))
+ ignore_apex_contributions := true
+endif
+ifeq (true, $(ignore_apex_contributions))
+PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS := true
+endif
+
#############################################################################
# Quick check and assign default values
@@ -552,20 +569,27 @@
$(PRODUCT_ENFORCE_RRO_EXEMPTED_TARGETS))
endif
-# Get the board API level.
-board_api_level := $(PLATFORM_SDK_VERSION)
-ifdef BOARD_API_LEVEL
- board_api_level := $(BOARD_API_LEVEL)
-else ifdef BOARD_SHIPPING_API_LEVEL
- # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
- board_api_level := $(BOARD_SHIPPING_API_LEVEL)
-endif
+# This table maps sdk version 35 to vendor api level 202404 and assumes yearly
+# release for the same month.
+define sdk-to-vendor-api-level
+ $(if $(call math_lt_or_eq,$(1),34),$(1),20$(call int_subtract,$(1),11)04)
+endef
-# Calculate the VSR vendor API level.
-VSR_VENDOR_API_LEVEL := $(board_api_level)
-
-ifdef PRODUCT_SHIPPING_API_LEVEL
- VSR_VENDOR_API_LEVEL := $(call math_min,$(PRODUCT_SHIPPING_API_LEVEL),$(board_api_level))
+ifdef PRODUCT_SHIPPING_VENDOR_API_LEVEL
+# Follow the version that is set manually.
+ VSR_VENDOR_API_LEVEL := $(PRODUCT_SHIPPING_VENDOR_API_LEVEL)
+else
+ # VSR API level is the vendor api level of the product shipping API level.
+ VSR_VENDOR_API_LEVEL := $(call sdk-to-vendor-api-level,$(PLATFORM_SDK_VERSION))
+ ifdef PRODUCT_SHIPPING_API_LEVEL
+ VSR_VENDOR_API_LEVEL := $(call sdk-to-vendor-api-level,$(PRODUCT_SHIPPING_API_LEVEL))
+ endif
+ ifdef BOARD_SHIPPING_API_LEVEL
+ # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
+ # In this case, the VSR API level is the minimum of the PRODUCT_SHIPPING_API_LEVEL
+ # and RELEASE_BOARD_API_LEVEL
+ VSR_VENDOR_API_LEVEL := $(call math_min,$(VSR_VENDOR_API_LEVEL),$(RELEASE_BOARD_API_LEVEL))
+ endif
endif
.KATI_READONLY := VSR_VENDOR_API_LEVEL
@@ -580,7 +604,7 @@
# Boolean variable determining if selinux labels of /dev are enforced
CHECK_DEV_TYPE_VIOLATIONS := false
-ifneq ($(call math_gt,$(VSR_VENDOR_API_LEVEL),35),)
+ifneq ($(call math_gt,$(VSR_VENDOR_API_LEVEL),202404),)
CHECK_DEV_TYPE_VIOLATIONS := true
else ifneq ($(PRODUCT_CHECK_DEV_TYPE_VIOLATIONS),)
CHECK_DEV_TYPE_VIOLATIONS := $(PRODUCT_CHECK_DEV_TYPE_VIOLATIONS)
diff --git a/core/release_config.mk b/core/release_config.mk
index 8d19bc7..3e51af5 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -42,6 +42,7 @@
# TODO: Remove wildcard for build/release one when all branch manifests
# have updated.
config_map_files := $(wildcard build/release/release_config_map.mk) \
+ $(wildcard vendor/google_shared/build/release/release_config_map.mk) \
$(if $(wildcard vendor/google/release/release_config_map.mk), \
vendor/google/release/release_config_map.mk, \
$(sort \
@@ -117,15 +118,32 @@
endef
# Include the config map files and populate _flag_declaration_files.
+# If the file is found more than once, only include it the first time.
_flag_declaration_files :=
+_included_config_map_files :=
$(foreach f, $(config_map_files), \
$(eval FLAG_DECLARATION_FILES:= ) \
- $(eval _included := $(f)) \
- $(eval include $(f)) \
- $(eval _flag_declaration_files += $(FLAG_DECLARATION_FILES)) \
+ $(if $(filter $(_included_config_map_files),$(f)),,\
+ $(eval _included := $(f)) \
+ $(eval include $(f)) \
+ $(eval _flag_declaration_files += $(FLAG_DECLARATION_FILES)) \
+ $(eval _included_config_map_files += $(f)) \
+ ) \
)
FLAG_DECLARATION_FILES :=
+# Verify that all inherited/overridden release configs are declared.
+$(foreach config,$(_all_release_configs),\
+ $(foreach r,$(all_release_configs.$(r).OVERRIDES),\
+ $(if $(strip $(_all_release_configs.$(r).FILES)$(_all_release_configs.$(r).OVERRIDES)),,\
+ $(error Release config $(config) [declared in: $(_all_release_configs.$(r).DECLARED_IN)] inherits from non-existent $(r).)\
+)))
+# Verify that alias configs do not have config files.
+$(foreach r,$(_all_release_configs),\
+ $(if $(_all_release_configs.$(r).ALIAS),$(if $(_all_release_configs.$(r).FILES),\
+ $(error Alias release config "$(r)" may not specify release config files $(_all_release_configs.$(r).FILES))\
+)))
+
ifeq ($(TARGET_RELEASE),)
# We allow some internal paths to explicitly set TARGET_RELEASE to the
# empty string. For the most part, 'make' treats unset and empty string as
@@ -141,8 +159,12 @@
TARGET_RELEASE = trunk_staging
endif
-ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
- $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE). Available releases are: $(_all_release_configs))
+# During pass 1 of product config, using a non-existent release config is not an error.
+# We can safely assume that we are doing pass 1 if DUMP_MANY_VARS=="PRODUCT_RELEASE_CONFIG_MAPS".
+ifneq (PRODUCT_RELEASE_CONFIG_MAPS,$(DUMP_MANY_VARS))
+ ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
+ $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE). Available releases are: $(_all_release_configs))
+ endif
endif
# Choose flag files
@@ -170,7 +192,7 @@
define declare-release-config
$(error declare-release-config can only be called from inside release_config_map.mk files)
endef
-define apply-release-config-overrides
+define _apply-release-config-overrides
$(error invalid use of apply-release-config-overrides)
endef
@@ -185,12 +207,6 @@
endif
.KATI_READONLY := TARGET_RELEASE
-# Verify that alias configs do not have config files.
-$(foreach r,$(_all_release_configs),\
- $(if $(_all_release_configs.$(r).ALIAS),$(if $(_all_release_configs.$(r).FILES),\
- $(error Alias release config "$(r)" may not specify release config files $(_all_release_configs.$(r).FILES))\
-)))
-
$(foreach config, $(_all_release_configs), \
$(eval _all_release_configs.$(config).DECLARED_IN:= ) \
$(eval _all_release_configs.$(config).FILES:= ) \
diff --git a/core/release_config.scl b/core/release_config.scl
index 662d155..c5815df 100644
--- a/core/release_config.scl
+++ b/core/release_config.scl
@@ -53,6 +53,7 @@
for t in _valid_types
],
},
+ "origin": {"type": "string"},
"declared_in": {"type": "string"},
},
"optional_keys": {
@@ -80,13 +81,14 @@
},
}
-def flag(name, partitions, default, *, appends = False):
+def flag(name, partitions, default, *, origin = "Unknown", appends = False):
"""Declare a flag.
Args:
name: name of the flag
partitions: the partitions where this should be recorded.
default: the default value of the flag.
+ origin: The origin of this flag.
appends: Whether new values should be append (not replace) the old.
Returns:
@@ -112,6 +114,7 @@
"partitions": partitions,
"default": default,
"appends": appends,
+ "origin": origin,
}
def value(name, value):
@@ -145,6 +148,24 @@
else:
return val
+def equal_flag_declaration(flag, other):
+ """Return true if the flag declarations are equal.
+
+ Args:
+ flag: This flag declaration.
+ other: Another flag declaration.
+
+ Returns:
+ Whether the declarations are the same.
+ """
+ for key in "name", "partitions", "default", "appends":
+ if flag[key] != other[key]:
+ return False
+ # For now, allow Unknown to match any other origin.
+ if flag["origin"] == "Unknown" or other["origin"] == "Unknown":
+ return True
+ return flag["origin"] == other["origin"]
+
def release_config(all_flags, all_values):
"""Return the make variables that should be set for this release config.
@@ -158,14 +179,23 @@
validate(all_flags, _all_flags_schema)
validate(all_values, _all_values_schema)
+ # Final values.
+ values = {}
# Validate flags
flag_names = []
flags_dict = {}
for flag in all_flags:
- if flag["name"] in flag_names:
- fail(flag["declared_in"] + ": Duplicate declaration of flag " + flag["name"])
- flag_names.append(flag["name"])
- flags_dict[flag["name"]] = flag
+ name = flag["name"]
+ if name in flag_names:
+ if equal_flag_declaration(flag, flags_dict[name]):
+ continue
+ else:
+ fail(flag["declared_in"] + ": Duplicate declaration of flag " + name +
+ " (declared first in " + flags_dict[name]["declared_in"] + ")")
+ flag_names.append(name)
+ flags_dict[name] = flag
+ # Set the flag value to the default value.
+ values[name] = {"name": name, "value": _format_value(flag["default"]), "set_in": flag["declared_in"]}
# Record which flags go on which partition
partitions = {}
@@ -181,7 +211,6 @@
# Generate final values.
# Only declared flags may have a value.
- values = {}
for value in all_values:
name = value["name"]
if name not in flag_names:
@@ -202,18 +231,13 @@
for partition, names in partitions.items():
result["_ALL_RELEASE_FLAGS.PARTITIONS." + partition] = names
for flag in all_flags:
- if flag["name"] in values:
- val = values[flag["name"]]["value"]
- set_in = values[flag["name"]]["set_in"]
- else:
- val = flag["default"]
- set_in = flag["declared_in"]
- val = _format_value(val)
+ val = _format_value(values[flag["name"]]["value"])
result[flag["name"]] = val
result["_ALL_RELEASE_FLAGS." + flag["name"] + ".PARTITIONS"] = flag["partitions"]
result["_ALL_RELEASE_FLAGS." + flag["name"] + ".DEFAULT"] = _format_value(flag["default"])
result["_ALL_RELEASE_FLAGS." + flag["name"] + ".VALUE"] = val
result["_ALL_RELEASE_FLAGS." + flag["name"] + ".DECLARED_IN"] = flag["declared_in"]
- result["_ALL_RELEASE_FLAGS." + flag["name"] + ".SET_IN"] = set_in
+ result["_ALL_RELEASE_FLAGS." + flag["name"] + ".SET_IN"] = values[flag["name"]]["set_in"]
+ result["_ALL_RELEASE_FLAGS." + flag["name"] + ".ORIGIN"] = flag["origin"]
return result
diff --git a/core/soong_cc_rust_prebuilt.mk b/core/soong_cc_rust_prebuilt.mk
index 943ed30..a1c6478 100644
--- a/core/soong_cc_rust_prebuilt.mk
+++ b/core/soong_cc_rust_prebuilt.mk
@@ -38,14 +38,6 @@
endif
endif
-# Don't install modules of current VNDK when it is told so
-ifeq ($(TARGET_SKIP_CURRENT_VNDK),true)
- ifeq ($(LOCAL_SOONG_VNDK_VERSION),$(PLATFORM_VNDK_VERSION))
- LOCAL_UNINSTALLABLE_MODULE := true
- endif
-endif
-
-
# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
# to avoid checkbuilds making an extra copy of every module.
LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index ec0c70e..3cffef2 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -31,7 +31,11 @@
$(call add_json_str, BuildId, $(BUILD_ID))
$(call add_json_str, BuildNumberFile, build_number.txt)
+$(call add_json_str, BuildHostnameFile, build_hostname.txt)
+$(call add_json_str, BuildThumbprintFile, build_thumbprint.txt)
+$(call add_json_bool, DisplayBuildNumber, $(filter true,$(DISPLAY_BUILD_NUMBER)))
+$(call add_json_str, Platform_display_version_name, $(PLATFORM_DISPLAY_VERSION))
$(call add_json_str, Platform_version_name, $(PLATFORM_VERSION))
$(call add_json_val, Platform_sdk_version, $(PLATFORM_SDK_VERSION))
$(call add_json_str, Platform_sdk_codename, $(PLATFORM_VERSION_CODENAME))
@@ -58,6 +62,7 @@
$(call add_json_bool, Debuggable, $(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
$(call add_json_bool, Eng, $(filter eng,$(TARGET_BUILD_VARIANT)))
+$(call add_json_str, BuildType, $(TARGET_BUILD_TYPE))
$(call add_json_str, DeviceName, $(TARGET_DEVICE))
$(call add_json_str, DeviceProduct, $(TARGET_PRODUCT))
@@ -147,10 +152,6 @@
$(call add_json_str, BtConfigIncludeDir, $(BOARD_BLUETOOTH_BDROID_BUILDCFG_INCLUDE_DIR))
$(call add_json_list, DeviceKernelHeaders, $(TARGET_DEVICE_KERNEL_HEADERS) $(TARGET_BOARD_KERNEL_HEADERS) $(TARGET_PRODUCT_KERNEL_HEADERS))
$(call add_json_str, VendorApiLevel, $(BOARD_API_LEVEL))
-ifeq ($(KEEP_VNDK),true)
-$(call add_json_str, DeviceVndkVersion, $(BOARD_VNDK_VERSION))
-$(call add_json_str, Platform_vndk_version, $(PLATFORM_VNDK_VERSION))
-endif
$(call add_json_list, ExtraVndkVersions, $(PRODUCT_EXTRA_VNDK_VERSIONS))
$(call add_json_list, DeviceSystemSdkVersions, $(BOARD_SYSTEMSDK_VERSIONS))
$(call add_json_str, RecoverySnapshotVersion, $(RECOVERY_SNAPSHOT_VERSION))
@@ -323,7 +324,6 @@
$(call add_json_str, ProductManufacturer, $(PRODUCT_MANUFACTURER))
$(call add_json_str, ProductBrand, $(PRODUCT_BRAND))
-$(call add_json_list, BuildVersionTags, $(BUILD_VERSION_TAGS))
$(call add_json_str, ReleaseVersion, $(_RELEASE_VERSION))
$(call add_json_list, ReleaseAconfigValueSets, $(RELEASE_ACONFIG_VALUE_SETS))
@@ -335,7 +335,7 @@
$(call add_json_bool, CheckVendorSeappViolations, $(filter true,$(CHECK_VENDOR_SEAPP_VIOLATIONS)))
-$(call add_json_list, BuildIgnoreApexContributionContents, $(sort $(PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS)))
+$(call add_json_bool, BuildIgnoreApexContributionContents, $(PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS))
$(call add_json_map, PartitionVarsForBazelMigrationOnlyDoNotUse)
$(call add_json_str, ProductDirectory, $(dir $(INTERNAL_PRODUCT)))
@@ -400,6 +400,20 @@
$(call add_json_bool, BuildFromSourceStub, $(findstring true,$(PRODUCT_BUILD_FROM_SOURCE_STUB) $(BUILD_FROM_SOURCE_STUB)))
+$(call add_json_bool, HiddenapiExportableStubs, $(filter true,$(PRODUCT_HIDDEN_API_EXPORTABLE_STUBS)))
+
+$(call add_json_bool, ExportRuntimeApis, $(filter true,$(PRODUCT_EXPORT_RUNTIME_APIS)))
+
+$(call add_json_str, AconfigContainerValidation, $(ACONFIG_CONTAINER_VALIDATION))
+
+$(call add_json_list, ProductLocales, $(subst _,-,$(PRODUCT_LOCALES)))
+
+$(call add_json_list, ProductDefaultWifiChannels, $(PRODUCT_DEFAULT_WIFI_CHANNELS))
+
+$(call add_json_bool, BoardUseVbmetaDigestInFingerprint, $(filter true,$(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT)))
+
+$(call add_json_list, OemProperties, $(PRODUCT_OEM_PROPERTIES))
+
$(call json_end)
$(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/sysprop.mk b/core/sysprop.mk
index 652ca97..47d8a41 100644
--- a/core/sysprop.mk
+++ b/core/sysprop.mk
@@ -23,7 +23,6 @@
property_overrides_split_enabled := true
endif
-BUILDINFO_SH := build/make/tools/buildinfo.sh
POST_PROCESS_PROPS := $(HOST_OUT_EXECUTABLES)/post_process_props$(HOST_EXECUTABLE_SUFFIX)
# Emits a set of sysprops common to all partitions to a file.
@@ -212,44 +211,10 @@
ifneq (,$(shell mkdir -p $(PRODUCT_OUT) && echo $(BUILD_THUMBPRINT) >$(BUILD_THUMBPRINT_FILE) && grep " " $(BUILD_THUMBPRINT_FILE)))
$(error BUILD_THUMBPRINT cannot contain spaces: "$(file <$(BUILD_THUMBPRINT_FILE))")
endif
-BUILD_THUMBPRINT_FROM_FILE := $$(cat $(BUILD_THUMBPRINT_FILE))
# unset it for safety.
+BUILD_THUMBPRINT_FILE :=
BUILD_THUMBPRINT :=
-# -----------------------------------------------------------------
-# Define human readable strings that describe this build
-#
-
-# BUILD_ID: detail info; has the same info as the build fingerprint
-BUILD_DESC := $(TARGET_PRODUCT)-$(TARGET_BUILD_VARIANT) $(PLATFORM_VERSION) $(BUILD_ID) $(BUILD_NUMBER_FROM_FILE) $(BUILD_VERSION_TAGS)
-
-# BUILD_DISPLAY_ID is shown under Settings -> About Phone
-ifeq ($(TARGET_BUILD_VARIANT),user)
- # User builds should show:
- # release build number or branch.buld_number non-release builds
-
- # Dev. branches should have DISPLAY_BUILD_NUMBER set
- ifeq (true,$(DISPLAY_BUILD_NUMBER))
- BUILD_DISPLAY_ID := $(BUILD_ID).$(BUILD_NUMBER_FROM_FILE) $(BUILD_KEYS)
- else
- BUILD_DISPLAY_ID := $(BUILD_ID) $(BUILD_KEYS)
- endif
-else
- # Non-user builds should show detailed build information
- BUILD_DISPLAY_ID := $(BUILD_DESC)
-endif
-
-# TARGET_BUILD_FLAVOR and ro.build.flavor are used only by the test
-# harness to distinguish builds. Only add _asan for a sanitized build
-# if it isn't already a part of the flavor (via a dedicated lunch
-# config for example).
-TARGET_BUILD_FLAVOR := $(TARGET_PRODUCT)-$(TARGET_BUILD_VARIANT)
-ifneq (, $(filter address, $(SANITIZE_TARGET)))
-ifeq (,$(findstring _asan,$(TARGET_BUILD_FLAVOR)))
-TARGET_BUILD_FLAVOR := $(TARGET_BUILD_FLAVOR)_asan
-endif
-endif
-
KNOWN_OEM_THUMBPRINT_PROPERTIES := \
ro.product.brand \
ro.product.name \
@@ -264,54 +229,7 @@
# Note: parts of this file that can't be generated by the build-properties
# macro are manually created as separate files and then fed into the macro
-# Accepts a whitespace separated list of product locales such as
-# (en_US en_AU en_GB...) and returns the first locale in the list with
-# underscores replaced with hyphens. In the example above, this will
-# return "en-US".
-define get-default-product-locale
-$(strip $(subst _,-, $(firstword $(1))))
-endef
-
-gen_from_buildinfo_sh := $(call intermediates-dir-for,PACKAGING,system_build_prop)/buildinfo.prop
-
-ifeq ($(strip $(HAS_BUILD_NUMBER)),true)
-$(gen_from_buildinfo_sh): $(BUILD_NUMBER_FILE)
-endif
-$(gen_from_buildinfo_sh): $(INTERNAL_BUILD_ID_MAKEFILE) $(API_FINGERPRINT) $(BUILD_HOSTNAME_FILE) | $(BUILD_DATETIME_FILE)
- $(hide) TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
- TARGET_BUILD_FLAVOR="$(TARGET_BUILD_FLAVOR)" \
- TARGET_DEVICE="$(TARGET_DEVICE)" \
- PRODUCT_DEFAULT_LOCALE="$(call get-default-product-locale,$(PRODUCT_LOCALES))" \
- PRODUCT_DEFAULT_WIFI_CHANNELS="$(PRODUCT_DEFAULT_WIFI_CHANNELS)" \
- PRIVATE_BUILD_DESC="$(BUILD_DESC)" \
- BUILD_ID="$(BUILD_ID)" \
- BUILD_DISPLAY_ID="$(BUILD_DISPLAY_ID)" \
- DATE="$(DATE_FROM_FILE)" \
- BUILD_USERNAME="$(BUILD_USERNAME)" \
- BUILD_HOSTNAME="$(BUILD_HOSTNAME_FROM_FILE)" \
- BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
- BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT="$(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT)" \
- PLATFORM_VERSION="$(PLATFORM_VERSION)" \
- PLATFORM_DISPLAY_VERSION="$(PLATFORM_DISPLAY_VERSION)" \
- PLATFORM_VERSION_LAST_STABLE="$(PLATFORM_VERSION_LAST_STABLE)" \
- PLATFORM_SECURITY_PATCH="$(PLATFORM_SECURITY_PATCH)" \
- PLATFORM_BASE_OS="$(PLATFORM_BASE_OS)" \
- PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
- PLATFORM_PREVIEW_SDK_VERSION="$(PLATFORM_PREVIEW_SDK_VERSION)" \
- PLATFORM_PREVIEW_SDK_FINGERPRINT="$$(cat $(API_FINGERPRINT))" \
- PLATFORM_VERSION_CODENAME="$(PLATFORM_VERSION_CODENAME)" \
- PLATFORM_VERSION_ALL_CODENAMES="$(PLATFORM_VERSION_ALL_CODENAMES)" \
- PLATFORM_VERSION_KNOWN_CODENAMES="$(PLATFORM_VERSION_KNOWN_CODENAMES)" \
- PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION="$(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)" \
- BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
- $(if $(OEM_THUMBPRINT_PROPERTIES),BUILD_THUMBPRINT="$(BUILD_THUMBPRINT_FROM_FILE)") \
- TARGET_CPU_ABI_LIST="$(TARGET_CPU_ABI_LIST)" \
- TARGET_CPU_ABI_LIST_32_BIT="$(TARGET_CPU_ABI_LIST_32_BIT)" \
- TARGET_CPU_ABI_LIST_64_BIT="$(TARGET_CPU_ABI_LIST_64_BIT)" \
- TARGET_CPU_ABI="$(TARGET_CPU_ABI)" \
- TARGET_CPU_ABI2="$(TARGET_CPU_ABI2)" \
- ZYGOTE_FORCE_64_BIT="$(ZYGOTE_FORCE_64_BIT)" \
- bash $(BUILDINFO_SH) > $@
+buildinfo_prop := $(call intermediates-dir-for,ETC,buildinfo.prop)/buildinfo.prop
ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
@@ -320,7 +238,7 @@
endif
_prop_files_ := \
- $(gen_from_buildinfo_sh) \
+ $(buildinfo_prop) \
$(system_prop_file)
# Order matters here. When there are duplicates, the last one wins.
diff --git a/core/tasks/automotive-sdv-tests.mk b/core/tasks/automotive-sdv-tests.mk
new file mode 100644
index 0000000..12706ce
--- /dev/null
+++ b/core/tasks/automotive-sdv-tests.mk
@@ -0,0 +1,61 @@
+# Copyright (C) 2022 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.
+
+
+.PHONY: automotive-sdv-tests
+
+automotive-sdv-tests-zip := $(PRODUCT_OUT)/automotive-sdv-tests.zip
+# Create an artifact to include a list of test config files in automotive-sdv-tests.
+automotive-sdv-tests-list-zip := $(PRODUCT_OUT)/automotive-sdv-tests_list.zip
+# Create an artifact to include all test config files in automotive-sdv-tests.
+automotive-sdv-tests-configs-zip := $(PRODUCT_OUT)/automotive-sdv-tests_configs.zip
+my_host_shared_lib_for_automotive_sdv_tests := $(call copy-many-files,$(COMPATIBILITY.automotive-sdv-tests.HOST_SHARED_LIBRARY.FILES))
+automotive_sdv_tests_host_shared_libs_zip := $(PRODUCT_OUT)/automotive-sdv-tests_host-shared-libs.zip
+
+$(automotive-sdv-tests-zip) : .KATI_IMPLICIT_OUTPUTS := $(automotive-sdv-tests-list-zip) $(automotive-sdv-tests-configs-zip) $(automotive_sdv_tests_host_shared_libs_zip)
+$(automotive-sdv-tests-zip) : PRIVATE_automotive_sdv_tests_list := $(PRODUCT_OUT)/automotive-sdv-tests_list
+$(automotive-sdv-tests-zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_automotive_sdv_tests)
+$(automotive-sdv-tests-zip) : PRIVATE_automotive_host_shared_libs_zip := $(automotive_sdv_tests_host_shared_libs_zip)
+$(automotive-sdv-tests-zip) : $(COMPATIBILITY.automotive-sdv-tests.FILES) $(my_host_shared_lib_for_automotive_sdv_tests) $(SOONG_ZIP)
+ rm -f $@-shared-libs.list
+ echo $(sort $(COMPATIBILITY.automotive-sdv-tests.FILES)) | tr " " "\n" > $@.list
+ grep $(HOST_OUT_TESTCASES) $@.list > $@-host.list || true
+ grep -e .*\\.config$$ $@-host.list > $@-host-test-configs.list || true
+ $(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
+ echo $$shared_lib >> $@-host.list; \
+ echo $$shared_lib >> $@-shared-libs.list; \
+ done
+ grep $(HOST_OUT_TESTCASES) $@-shared-libs.list > $@-host-shared-libs.list || true
+ grep $(TARGET_OUT_TESTCASES) $@.list > $@-target.list || true
+ grep -e .*\\.config$$ $@-target.list > $@-target-test-configs.list || true
+ $(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list -P target -C $(PRODUCT_OUT) -l $@-target.list
+ $(hide) $(SOONG_ZIP) -d -o $(automotive-sdv-tests-configs-zip) \
+ -P host -C $(HOST_OUT) -l $@-host-test-configs.list \
+ -P target -C $(PRODUCT_OUT) -l $@-target-test-configs.list
+ $(SOONG_ZIP) -d -o $(PRIVATE_automotive_host_shared_libs_zip) \
+ -P host -C $(HOST_OUT) -l $@-host-shared-libs.list
+ rm -f $(PRIVATE_automotive_sdv_tests_list)
+ $(hide) grep -e .*\\.config$$ $@-host.list | sed s%$(HOST_OUT)%host%g > $(PRIVATE_automotive_sdv_tests_list)
+ $(hide) grep -e .*\\.config$$ $@-target.list | sed s%$(PRODUCT_OUT)%target%g >> $(PRIVATE_automotive_sdv_tests_list)
+ $(hide) $(SOONG_ZIP) -d -o $(automotive-sdv-tests-list-zip) -C $(dir $@) -f $(PRIVATE_automotive_sdv_tests_list)
+ rm -f $@.list $@-host.list $@-target.list $@-host-test-configs.list $@-target-test-configs.list \
+ $@-shared-libs.list $@-host-shared-libs.list $(PRIVATE_automotive_sdv_tests_list)
+
+automotive-sdv-tests: $(automotive-sdv-tests-zip)
+$(call dist-for-goals, automotive-sdv-tests, $(automotive-sdv-tests-zip) $(automotive-sdv-tests-list-zip) $(automotive-sdv-tests-configs-zip) $(automotive_sdv_tests_host_shared_libs_zip))
+
+$(call declare-1p-container,$(automotive-sdv-tests-zip),)
+$(call declare-container-license-deps,$(automotive-sdv-tests-zip),$(COMPATIBILITY.automotive-sdv-tests.FILES) $(my_host_shared_lib_for_automotive_sdv_tests),$(PRODUCT_OUT)/:/)
+
+tests: automotive-sdv-tests
diff --git a/core/tasks/berberis_test.mk b/core/tasks/berberis_test.mk
new file mode 100644
index 0000000..8604709
--- /dev/null
+++ b/core/tasks/berberis_test.mk
@@ -0,0 +1,25 @@
+#
+# 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.
+#
+
+BERBERIS_DIR := frameworks/libs/binary_translation
+
+# Berberis includes some components which may conflict with other packages.
+# Only build it when requested explicitly.
+ifeq ($(BUILD_BERBERIS),true)
+
+include $(BERBERIS_DIR)/tests/run_host_tests.mk
+
+endif # BUILD_BERBERIS
diff --git a/core/tasks/cts.mk b/core/tasks/cts.mk
index 91cb2c9..b9f0988 100644
--- a/core/tasks/cts.mk
+++ b/core/tasks/cts.mk
@@ -144,30 +144,30 @@
$(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
$(PRIVATE_TEST_CASES),xml)
-$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
+$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(verifier-dir), $(c))
$(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-verifier-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-verifier-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Verifier API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts) $(verifier-dir), $(c))
$(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-combined-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Combined API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
+$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts) $(verifier-dir), $(c))
$(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
$(cts-combined-xml-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
+$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(verifier-zip) $(cts_api_coverage_dependencies) | $(ACP)
$(call generate-coverage-report-cts,"CTS Combined API Coverage Report - XML",\
$(PRIVATE_TEST_CASES),xml)
@@ -236,3 +236,8 @@
cts_api_coverage_exe :=
cts_verifier_apk :=
android_cts_zip :=
+cts-dir :=
+verifier-dir-name :=
+verifier-dir :=
+verifier-zip-name :=
+verifier-zip :=
diff --git a/core/tasks/meta-lic.mk b/core/tasks/meta-lic.mk
new file mode 100644
index 0000000..0348844
--- /dev/null
+++ b/core/tasks/meta-lic.mk
@@ -0,0 +1,35 @@
+# 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.
+
+# Declare license metadata for non-module files released with products.
+
+# Moved here from device/sample/Android.mk
+$(eval $(call declare-1p-copy-files,device/sample,))
+
+# Moved here from frameworks/av/media/Android.mk
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libeffects,audio_effects.conf))
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libeffects,audio_effects.xml))
+$(eval $(call declare-1p-copy-files,frameworks/av/media/libstagefright,))
+
+# Moved here from frameworks/av/services/Android.mk
+$(eval $(call declare-1p-copy-files,frameworks/av/services/audiopolicy,))
+
+# Moved here from frameworks/base/Android.mk
+$(eval $(call declare-1p-copy-files,frameworks/base,.ogg))
+$(eval $(call declare-1p-copy-files,frameworks/base,.kl))
+$(eval $(call declare-1p-copy-files,frameworks/base,.kcm))
+$(eval $(call declare-1p-copy-files,frameworks/base,.idc))
+$(eval $(call declare-1p-copy-files,frameworks/base,dirty-image-objects))
+$(eval $(call declare-1p-copy-files,frameworks/base/config,))
+$(eval $(call declare-1p-copy-files,frameworks/native/data,))
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index 8546828..aa695eb 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -39,7 +39,7 @@
$(call write-optional-json-list, "srcjars", $(sort $(ALL_MODULES.$(m).SRCJARS))) \
$(call write-optional-json-list, "classes_jar", $(sort $(ALL_MODULES.$(m).CLASSES_JAR))) \
$(call write-optional-json-list, "test_mainline_modules", $(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES))) \
- $(call write-optional-json-bool, $(ALL_MODULES.$(m).IS_UNIT_TEST)) \
+ $(call write-optional-json-bool, "is_unit_test", $(ALL_MODULES.$(m).IS_UNIT_TEST)) \
$(call write-optional-json-list, "test_options_tags", $(sort $(ALL_MODULES.$(m).TEST_OPTIONS_TAGS))) \
$(call write-optional-json-list, "data", $(sort $(ALL_MODULES.$(m).TEST_DATA))) \
$(call write-optional-json-list, "runtime_dependencies", $(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES))) \
diff --git a/core/tasks/offline-sdk-docs.mk b/core/tasks/offline-sdk-docs.mk
new file mode 100644
index 0000000..d9e8006
--- /dev/null
+++ b/core/tasks/offline-sdk-docs.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2008 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.
+#
+
+# sdk.atree needs to copy the whole dir: $(OUT_DOCS)/offline-sdk to the final zip.
+# So keep offline-sdk-timestamp target here, and unzip offline-sdk-docs.zip to
+# $(OUT_DOCS)/offline-sdk.
+$(OUT_DOCS)/offline-sdk-timestamp: $(OUT_DOCS)/offline-sdk-docs-docs.zip
+ $(hide) rm -rf $(OUT_DOCS)/offline-sdk
+ $(hide) mkdir -p $(OUT_DOCS)/offline-sdk
+ ( unzip -qo $< -d $(OUT_DOCS)/offline-sdk && touch -f $@ ) || exit 1
+
+.PHONY: docs offline-sdk-docs
+docs offline-sdk-docs: $(OUT_DOCS)/offline-sdk-timestamp
diff --git a/core/tasks/tools/vts_package_utils.mk b/core/tasks/tools/vts_package_utils.mk
index 06161f0..1a819f2 100644
--- a/core/tasks/tools/vts_package_utils.mk
+++ b/core/tasks/tools/vts_package_utils.mk
@@ -21,7 +21,7 @@
$(foreach m,$(1),\
$(eval _built_files := $(strip $(ALL_MODULES.$(m).BUILT_INSTALLED)\
$(ALL_MODULES.$(m)$(TARGET_2ND_ARCH_MODULE_SUFFIX).BUILT_INSTALLED)))\
- $(foreach i, $(_built_files),\
+ $(foreach i, $(sort $(_built_files)),\
$(eval bui_ins := $(subst :,$(space),$(i)))\
$(eval ins := $(word 2,$(bui_ins)))\
$(if $(filter $(TARGET_OUT_ROOT)/%,$(ins)),\
diff --git a/core/tasks/vndk.mk b/core/tasks/vndk.mk
deleted file mode 100644
index ebe9bd4..0000000
--- a/core/tasks/vndk.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2017 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.
-
-current_makefile := $(lastword $(MAKEFILE_LIST))
-
-# BOARD_VNDK_VERSION must be set to 'current' in order to generate a VNDK snapshot.
-ifeq ($(BOARD_VNDK_VERSION),current)
-
-# PLATFORM_VNDK_VERSION must be set.
-ifneq (,$(PLATFORM_VNDK_VERSION))
-
-.PHONY: vndk
-vndk: $(SOONG_VNDK_SNAPSHOT_ZIP)
-
-$(call dist-for-goals, vndk, $(SOONG_VNDK_SNAPSHOT_ZIP))
-
-else # PLATFORM_VNDK_VERSION is NOT set
-error_msg := "CANNOT generate VNDK snapshot. PLATFORM_VNDK_VERSION must be set."
-endif # PLATFORM_VNDK_VERSION
-
-else # BOARD_VNDK_VERSION is NOT set to 'current'
-error_msg := "CANNOT generate VNDK snapshot. BOARD_VNDK_VERSION must be set to 'current'."
-endif # BOARD_VNDK_VERSION
-
-ifneq (,$(error_msg))
-
-.PHONY: vndk
-vndk: PRIVATE_MAKEFILE := $(current_makefile)
-vndk:
- $(call echo-error,$(PRIVATE_MAKEFILE),$(error_msg))
- exit 1
-
-endif
diff --git a/core/version_util.mk b/core/version_util.mk
index 6cda0fc..610cdaf 100644
--- a/core/version_util.mk
+++ b/core/version_util.mk
@@ -28,7 +28,6 @@
# BUILD_ID
# BUILD_NUMBER
# PLATFORM_SECURITY_PATCH
-# PLATFORM_VNDK_VERSION
# PLATFORM_SYSTEMSDK_VERSIONS
# PLATFORM_VERSION_LAST_STABLE
# PLATFORM_VERSION_KNOWN_CODENAMES
@@ -151,24 +150,6 @@
endif
.KATI_READONLY := DEFAULT_APP_TARGET_SDK
-ifeq ($(KEEP_VNDK),true)
- ifndef PLATFORM_VNDK_VERSION
- # This is the definition of the VNDK version for the current VNDK libraries.
- # With trunk stable, VNDK will not be frozen but deprecated.
- # This version will be removed with the VNDK deprecation.
- ifeq (REL,$(PLATFORM_VERSION_CODENAME))
- ifdef RELEASE_PLATFORM_VNDK_VERSION
- PLATFORM_VNDK_VERSION := $(RELEASE_PLATFORM_VNDK_VERSION)
- else
- PLATFORM_VNDK_VERSION := $(PLATFORM_SDK_VERSION)
- endif
- else
- PLATFORM_VNDK_VERSION := $(PLATFORM_VERSION_CODENAME)
- endif
- endif
- .KATI_READONLY := PLATFORM_VNDK_VERSION
-endif
-
ifndef PLATFORM_SYSTEMSDK_MIN_VERSION
# This is the oldest version of system SDK that the platform supports. Contrary
# to the public SDK where platform essentially supports all previous SDK versions,
diff --git a/envsetup.sh b/envsetup.sh
index db21188..fbe522d 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1084,8 +1084,23 @@
echo "can't find Android.mk"
}
+# Ensure that we're always using the adb in the tree. This works around the fact
+# that bash caches $PATH lookups, so if you use adb before lunching/building the
+# one in your tree, you'll continue to get /usr/bin/adb or whatever even after
+# you have the one from your current tree on your path. Historically this would
+# cause confusion because glinux had adb in /usr/bin/ by default, though that
+# doesn't appear to be the case on my rodete hosts; it is however still the case
+# that my Mac has /usr/local/bin/adb installed by default and on the default
+# path.
function adb() {
- command adb "${@}"
+ # We need `command which` because zsh has a built-in `which` that's more
+ # like `type`.
+ local ADB=$(command which adb)
+ if [ -z "$ADB" ]; then
+ echo "Command adb not found; try lunch (and building) first?"
+ return 1
+ fi
+ $ADB "${@}"
}
# simplified version of ps; output in the form
diff --git a/target/board/BoardConfigMainlineCommon.mk b/target/board/BoardConfigMainlineCommon.mk
index c3878b8..b5e3dc2 100644
--- a/target/board/BoardConfigMainlineCommon.mk
+++ b/target/board/BoardConfigMainlineCommon.mk
@@ -2,6 +2,9 @@
#
# Common compile-time definitions for mainline images.
+# Ensure all trunk-stable flags are available.
+include build/make/target/product/build_variables.mk
+
# The generic product target doesn't have any hardware-specific pieces.
TARGET_NO_BOOTLOADER := true
TARGET_NO_RECOVERY := true
@@ -21,11 +24,6 @@
# the devices with metadata parition
BOARD_USES_METADATA_PARTITION := true
-ifeq ($(KEEP_VNDK),true)
-# Default is current, but allow devices to override vndk version if needed.
-BOARD_VNDK_VERSION ?= current
-endif
-
# 64 bit mediadrmserver
TARGET_ENABLE_MEDIADRM_64 := true
diff --git a/target/board/mainline_sdk/BoardConfig.mk b/target/board/mainline_sdk/BoardConfig.mk
index 84f8b2d..e4c6a8c 100644
--- a/target/board/mainline_sdk/BoardConfig.mk
+++ b/target/board/mainline_sdk/BoardConfig.mk
@@ -13,6 +13,9 @@
# limitations under the License.
#
+# Ensure all trunk-stable flags are available.
+include build/make/target/product/build_variables.mk
+
TARGET_ARCH_SUITE := mainline_sdk
HOST_CROSS_OS := linux_bionic
diff --git a/target/product/AndroidProducts.mk b/target/product/AndroidProducts.mk
index 76b1c58..07eb96d 100644
--- a/target/product/AndroidProducts.mk
+++ b/target/product/AndroidProducts.mk
@@ -67,6 +67,7 @@
$(LOCAL_DIR)/mainline_system_x86_arm.mk \
$(LOCAL_DIR)/ndk.mk \
$(LOCAL_DIR)/sdk.mk \
+ $(LOCAL_DIR)/sdk_with_runtime_apis.mk \
endif
diff --git a/target/product/angle_default.mk b/target/product/angle_default.mk
index fdfc7f5..72846d3 100644
--- a/target/product/angle_default.mk
+++ b/target/product/angle_default.mk
@@ -17,7 +17,5 @@
# To enable ANGLE as the default system GLES drivers, add
# $(call inherit-product, $(SRC_TARGET_DIR)/product/angle_default.mk) to the Makefile.
-$(call inherit-product, $(SRC_TARGET_DIR)/product/angle_supported.mk)
-
PRODUCT_SYSTEM_PROPERTIES += \
persist.graphics.egl=angle
diff --git a/target/product/angle_supported.mk b/target/product/angle_supported.mk
deleted file mode 100644
index 59e6ea3..0000000
--- a/target/product/angle_supported.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright 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.
-#
-
-# To include ANGLE into the image build, add
-# $(call inherit-product, $(SRC_TARGET_DIR)/product/angle_supported.mk) to the Makefile.
-# By default, this will allow ANGLE binaries to coexist with native GLES drivers.
-
-ifneq ($(RELEASE_ANGLE_ON_SYSTEM),true)
-PRODUCT_PACKAGES += \
- libEGL_angle \
- libGLESv1_CM_angle \
- libGLESv2_angle
-
-# Set ro.gfx.angle.supported based on if ANGLE is installed in vendor partition
-PRODUCT_VENDOR_PROPERTIES += ro.gfx.angle.supported=true
-endif
diff --git a/target/product/aosp_arm64.mk b/target/product/aosp_arm64.mk
index d3514a5..d944615 100644
--- a/target/product/aosp_arm64.mk
+++ b/target/product/aosp_arm64.mk
@@ -72,3 +72,5 @@
PRODUCT_DEVICE := generic_arm64
PRODUCT_BRAND := Android
PRODUCT_MODEL := AOSP on ARM64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/aosp_x86_64.mk b/target/product/aosp_x86_64.mk
index 3040dd3..4344f50 100644
--- a/target/product/aosp_x86_64.mk
+++ b/target/product/aosp_x86_64.mk
@@ -74,3 +74,5 @@
PRODUCT_DEVICE := generic_x86_64
PRODUCT_BRAND := Android
PRODUCT_MODEL := AOSP on x86_64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 3840e1f..884af4f 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -17,6 +17,7 @@
# Base modules and settings for the system partition.
PRODUCT_PACKAGES += \
abx \
+ aconfigd \
adbd_system_api \
aflags \
am \
@@ -93,15 +94,16 @@
ExtShared \
flags_health_check \
framework-graphics \
+ framework-location \
framework-minus-apex \
framework-minus-apex-install-dependencies \
- framework-res \
framework-sysconfig.xml \
fsck.erofs \
fsck_msdos \
fsverity-release-cert-der \
fs_config_files_system \
fs_config_dirs_system \
+ gpu_counter_producer \
group_system \
gsid \
gsi_tool \
@@ -235,12 +237,15 @@
PackageInstaller \
passwd_system \
perfetto \
+ perfetto-extras \
ping \
ping6 \
+ pintool \
platform.xml \
pm \
preinstalled-packages-asl-files.xml \
preinstalled-packages-platform.xml \
+ preinstalled-packages-strict-signature.xml \
printflags \
privapp-permissions-platform.xml \
prng_seeder \
@@ -261,6 +266,7 @@
services \
settings \
SettingsProvider \
+ sfdo \
sgdisk \
Shell \
shell_and_utilities_system \
@@ -283,7 +289,6 @@
uncrypt \
usbd \
vdc \
- viewcompiler \
voip-common \
vold \
watchdogd \
@@ -370,14 +375,10 @@
WallpaperBackup
endif
-# Moving angle from vendor to system
-ifeq ($(RELEASE_ANGLE_ON_SYSTEM),true)
PRODUCT_PACKAGES += \
libEGL_angle \
libGLESv1_CM_angle \
libGLESv2_angle
-$(call soong_config_set,angle,angle_on_system,true)
-endif
# For testing purposes
ifeq ($(FORCE_AUDIO_SILENT), true)
@@ -414,20 +415,18 @@
unwind_info \
unwind_reg_info \
unwind_symbols \
- viewcompiler \
tzdata_host \
tzdata_host_tzdata_apex \
tzlookup.xml_host_tzdata_apex \
tz_version_host \
tz_version_host_tzdata_apex \
+PRODUCT_PACKAGES += init.usb.rc init.usb.configfs.rc
PRODUCT_COPY_FILES += \
- system/core/rootdir/init.usb.rc:system/etc/init/hw/init.usb.rc \
- system/core/rootdir/init.usb.configfs.rc:system/etc/init/hw/init.usb.configfs.rc \
system/core/rootdir/etc/hosts:system/etc/hosts
-PRODUCT_COPY_FILES += system/core/rootdir/init.zygote32.rc:system/etc/init/hw/init.zygote32.rc
+PRODUCT_PACKAGES += init.zygote32.rc
PRODUCT_VENDOR_PROPERTIES += ro.zygote?=zygote32
PRODUCT_SYSTEM_PROPERTIES += debug.atrace.tags.enableflags=0
@@ -439,6 +438,7 @@
adevice_fingerprint \
arping \
dmuserd \
+ evemu-record \
idlcli \
init-debug.rc \
iotop \
@@ -491,5 +491,8 @@
$(call inherit-product, $(SRC_TARGET_DIR)/product/runtime_libart.mk)
+# Ensure all trunk-stable flags are available.
+$(call inherit-product, $(SRC_TARGET_DIR)/product/build_variables.mk)
+
# Use "image" APEXes always.
$(call inherit-product,$(SRC_TARGET_DIR)/product/updatable_apex.mk)
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index a0c5929..ec3de75 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -51,7 +51,6 @@
dumpsys_vendor \
fs_config_files_nonsystem \
fs_config_dirs_nonsystem \
- gpu_counter_producer \
gralloc.default \
group_odm \
group_vendor \
diff --git a/target/product/build_variables.mk b/target/product/build_variables.mk
new file mode 100644
index 0000000..5fe5333
--- /dev/null
+++ b/target/product/build_variables.mk
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# This file contains the trunk-stable flags that should be exported to all
+# Android targets.
+
+# Use the configured release of sqlite
+$(call soong_config_set, libsqlite3, release_package_libsqlite3, $(RELEASE_PACKAGE_LIBSQLITE3))
diff --git a/target/product/core_64_bit.mk b/target/product/core_64_bit.mk
index e0c4d53..790f57b 100644
--- a/target/product/core_64_bit.mk
+++ b/target/product/core_64_bit.mk
@@ -23,9 +23,7 @@
# for 32-bit only.
# Copy the 64-bit primary, 32-bit secondary zygote startup script
-PRODUCT_COPY_FILES += \
- system/core/rootdir/init.zygote64.rc:system/etc/init/hw/init.zygote64.rc \
- system/core/rootdir/init.zygote64_32.rc:system/etc/init/hw/init.zygote64_32.rc \
+PRODUCT_PACKAGES += init.zygote64.rc init.zygote64_32.rc
# Set the zygote property to select the 64-bit primary, 32-bit secondary script
# This line must be parsed before the one in core_minimal.mk
diff --git a/target/product/core_64_bit_only.mk b/target/product/core_64_bit_only.mk
index fc2b8e5..ffa5567 100644
--- a/target/product/core_64_bit_only.mk
+++ b/target/product/core_64_bit_only.mk
@@ -20,7 +20,7 @@
# to core_minimal.mk.
# Copy the 64-bit zygote startup script
-PRODUCT_COPY_FILES += system/core/rootdir/init.zygote64.rc:system/etc/init/hw/init.zygote64.rc
+PRODUCT_PACKAGES += init.zygote64.rc
# Set the zygote property to select the 64-bit script.
# This line must be parsed before the one in core_minimal.mk
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index dca9baa..4a968d7 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -50,6 +50,7 @@
PRODUCT_BOOT_JARS += \
framework-minus-apex \
framework-graphics \
+ framework-location \
ext \
telephony-common \
voip-common \
@@ -104,13 +105,6 @@
com.android.nfcservices:framework-nfc
endif
-# TODO(b/308174306): Adjust this after multiple prebuilts version is supported.
-# APEX boot jars that are not in prebuilt apexes.
-# Keep the list sorted by module names and then library names.
-PRODUCT_APEX_BOOT_JARS_FOR_SOURCE_BUILD_ONLY := \
- com.android.mediaprovider:framework-pdf \
- com.android.mediaprovider:framework-pdf-v \
-
# List of system_server classpath jars delivered via apex.
# Keep the list sorted by module names and then library names.
# Note: For modules available in Q, DO NOT add new entries here.
diff --git a/target/product/generic_system.mk b/target/product/generic_system.mk
index 19ec86d..fa31e04 100644
--- a/target/product/generic_system.mk
+++ b/target/product/generic_system.mk
@@ -111,10 +111,10 @@
$(PRODUCT_PACKAGES_SHIPPING_API_LEVEL_34)
# Include all zygote init scripts. "ro.zygote" will select one of them.
-PRODUCT_COPY_FILES += \
- system/core/rootdir/init.zygote32.rc:system/etc/init/hw/init.zygote32.rc \
- system/core/rootdir/init.zygote64.rc:system/etc/init/hw/init.zygote64.rc \
- system/core/rootdir/init.zygote64_32.rc:system/etc/init/hw/init.zygote64_32.rc \
+PRODUCT_PACKAGES += \
+ init.zygote32.rc \
+ init.zygote64.rc \
+ init.zygote64_32.rc
# Enable dynamic partition size
PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true
diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk
index f348fbb..fc6cc68 100644
--- a/target/product/gsi/Android.mk
+++ b/target/product/gsi/Android.mk
@@ -1,112 +1,18 @@
LOCAL_PATH:= $(call my-dir)
#####################################################################
-# list of vndk libraries from the source code.
-INTERNAL_VNDK_LIB_LIST := $(SOONG_VNDK_LIBRARIES_FILE)
-
-#####################################################################
-# This is the up-to-date list of vndk libs.
-LATEST_VNDK_LIB_LIST := $(LOCAL_PATH)/current.txt
-ifeq ($(KEEP_VNDK),true)
-UNFROZEN_VNDK := true
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
- # Use frozen vndk lib list only if "34 >= PLATFORM_VNDK_VERSION"
- ifeq ($(call math_gt_or_eq,34,$(PLATFORM_VNDK_VERSION)),true)
- LATEST_VNDK_LIB_LIST := $(LOCAL_PATH)/$(PLATFORM_VNDK_VERSION).txt
- ifeq ($(wildcard $(LATEST_VNDK_LIB_LIST)),)
- $(error $(LATEST_VNDK_LIB_LIST) file not found. Please copy "$(LOCAL_PATH)/current.txt" to "$(LATEST_VNDK_LIB_LIST)" and commit a CL for release branch)
- endif
- UNFROZEN_VNDK :=
- endif
-endif
-endif
-
-#####################################################################
# Check the generate list against the latest list stored in the
# source tree
-.PHONY: check-vndk-list
+.PHONY: check-abi-dump-list
# Check if vndk list is changed
-droidcore: check-vndk-list
+droidcore: check-abi-dump-list
-check-vndk-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-list-timestamp
check-vndk-abi-dump-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-abi-dump-list-timestamp
-ifeq ($(TARGET_IS_64_BIT)|$(TARGET_2ND_ARCH),true|)
-# TODO(b/110429754) remove this condition when we support 64-bit-only device
-check-vndk-list: ;
-else ifeq ($(TARGET_SKIP_CURRENT_VNDK),true)
-check-vndk-list: ;
-else ifeq ($(BOARD_VNDK_VERSION),)
-check-vndk-list: ;
-else
-check-vndk-list: $(check-vndk-list-timestamp)
ifneq ($(SKIP_ABI_CHECKS),true)
-check-vndk-list: $(check-vndk-abi-dump-list-timestamp)
+check-abi-dump-list: $(check-abi-dump-list-timestamp)
endif
-endif
-
-_vndk_check_failure_message := " error: VNDK library list has been changed.\n"
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-_vndk_check_failure_message += " Changing the VNDK library list is not allowed in API locked branches."
-else
-_vndk_check_failure_message += " Run \`update-vndk-list.sh\` to update $(LATEST_VNDK_LIB_LIST)"
-endif
-
-# The *-ndk_platform.so libraries no longer exist and are removed from the VNDK set. However, they
-# can exist if NEED_AIDL_NDK_PLATFORM_BACKEND is set to true for legacy devices. Don't be bothered
-# with the extraneous libraries.
-ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
- _READ_INTERNAL_VNDK_LIB_LIST := sed /ndk_platform.so/d $(INTERNAL_VNDK_LIB_LIST)
-else
- _READ_INTERNAL_VNDK_LIB_LIST := cat $(INTERNAL_VNDK_LIB_LIST)
-endif
-
-$(check-vndk-list-timestamp): $(INTERNAL_VNDK_LIB_LIST) $(LATEST_VNDK_LIB_LIST) $(HOST_OUT_EXECUTABLES)/update-vndk-list.sh
- $(hide) ($(_READ_INTERNAL_VNDK_LIB_LIST) | sort | \
- diff --old-line-format="Removed %L" \
- --new-line-format="Added %L" \
- --unchanged-line-format="" \
- <(cat $(LATEST_VNDK_LIB_LIST) | sort) - \
- || ( echo -e $(_vndk_check_failure_message); exit 1 ))
- $(hide) mkdir -p $(dir $@)
- $(hide) touch $@
-
-#####################################################################
-# Script to update the latest VNDK lib list
-include $(CLEAR_VARS)
-LOCAL_MODULE := update-vndk-list.sh
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE_STEM := $(LOCAL_MODULE)
-LOCAL_IS_HOST_MODULE := true
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_INTERNAL_VNDK_LIB_LIST := $(INTERNAL_VNDK_LIB_LIST)
-$(LOCAL_BUILT_MODULE): PRIVATE_LATEST_VNDK_LIB_LIST := $(LATEST_VNDK_LIB_LIST)
-$(LOCAL_BUILT_MODULE):
- @echo "Generate: $@"
- @mkdir -p $(dir $@)
- @rm -f $@
- $(hide) echo "#!/bin/bash" > $@
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
- $(hide) echo "echo Updating VNDK library list is NOT allowed in API locked branches." >> $@; \
- echo "exit 1" >> $@
-else
- $(hide) echo "if [ -z \"\$${ANDROID_BUILD_TOP}\" ]; then" >> $@; \
- echo " echo Run lunch or choosecombo first" >> $@; \
- echo " exit 1" >> $@; \
- echo "fi" >> $@; \
- echo "cd \$${ANDROID_BUILD_TOP}" >> $@
-ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
- $(hide) echo "sed /ndk_platform.so/d $(PRIVATE_INTERNAL_VNDK_LIB_LIST) > $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
-else
- $(hide) echo "cp $(PRIVATE_INTERNAL_VNDK_LIB_LIST) $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
-endif
- $(hide) echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@
-endif
- @chmod a+x $@
#####################################################################
# ABI reference dumps.
@@ -159,20 +65,13 @@
$(notdir $(call filter-abi-dump-paths,$(1),$(2)))
endef
-ifdef RELEASE_BOARD_API_LEVEL
- VNDK_ABI_DUMP_DIR := prebuilts/abi-dumps/vndk/$(RELEASE_BOARD_API_LEVEL)
-else
- VNDK_ABI_DUMP_DIR := prebuilts/abi-dumps/vndk/$(PLATFORM_VNDK_VERSION)
-endif
+VNDK_ABI_DUMP_DIR := prebuilts/abi-dumps/vndk/$(RELEASE_BOARD_API_LEVEL)
ifeq (REL,$(PLATFORM_VERSION_CODENAME))
- NDK_ABI_DUMP_DIR := prebuilts/abi-dumps/ndk/$(PLATFORM_SDK_VERSION)
PLATFORM_ABI_DUMP_DIR := prebuilts/abi-dumps/platform/$(PLATFORM_SDK_VERSION)
else
- NDK_ABI_DUMP_DIR := prebuilts/abi-dumps/ndk/current
PLATFORM_ABI_DUMP_DIR := prebuilts/abi-dumps/platform/current
endif
VNDK_ABI_DUMPS := $(call find-abi-dump-paths,$(VNDK_ABI_DUMP_DIR))
-NDK_ABI_DUMPS := $(call find-abi-dump-paths,$(NDK_ABI_DUMP_DIR))
PLATFORM_ABI_DUMPS := $(call find-abi-dump-paths,$(PLATFORM_ABI_DUMP_DIR))
# Check for superfluous lsdump files. Since LSDUMP_PATHS only covers the
@@ -180,22 +79,15 @@
# Mainline modules may be in use, we also allow the libs in STUB_LIBRARIES for
# NDK and platform ABIs.
-$(check-vndk-abi-dump-list-timestamp): PRIVATE_LSDUMP_PATHS := $(LSDUMP_PATHS)
-$(check-vndk-abi-dump-list-timestamp): PRIVATE_STUB_LIBRARIES := $(STUB_LIBRARIES)
-$(check-vndk-abi-dump-list-timestamp):
+$(check-abi-dump-list-timestamp): PRIVATE_LSDUMP_PATHS := $(LSDUMP_PATHS)
+$(check-abi-dump-list-timestamp): PRIVATE_STUB_LIBRARIES := $(STUB_LIBRARIES)
+$(check-abi-dump-list-timestamp):
$(eval added_vndk_abi_dumps := $(strip $(sort $(filter-out \
- $(call filter-abi-dump-names,LLNDK VNDK-SP VNDK-core,$(PRIVATE_LSDUMP_PATHS)), \
+ $(call filter-abi-dump-names,LLNDK,$(PRIVATE_LSDUMP_PATHS)), \
$(notdir $(VNDK_ABI_DUMPS))))))
$(if $(added_vndk_abi_dumps), \
echo -e "Found unexpected ABI reference dump files under $(VNDK_ABI_DUMP_DIR). It is caused by mismatch between Android.bp and the dump files. Run \`find \$${ANDROID_BUILD_TOP}/$(VNDK_ABI_DUMP_DIR) '(' -name $(subst $(space), -or -name ,$(added_vndk_abi_dumps)) ')' -delete\` to delete the dump files.")
- $(eval added_ndk_abi_dumps := $(strip $(sort $(filter-out \
- $(call filter-abi-dump-names,NDK,$(PRIVATE_LSDUMP_PATHS)) \
- $(addsuffix .lsdump,$(PRIVATE_STUB_LIBRARIES)), \
- $(notdir $(NDK_ABI_DUMPS))))))
- $(if $(added_ndk_abi_dumps), \
- echo -e "Found unexpected ABI reference dump files under $(NDK_ABI_DUMP_DIR). It is caused by mismatch between Android.bp and the dump files. Run \`find \$${ANDROID_BUILD_TOP}/$(NDK_ABI_DUMP_DIR) '(' -name $(subst $(space), -or -name ,$(added_ndk_abi_dumps)) ')' -delete\` to delete the dump files.")
-
# TODO(b/314010764): Remove LLNDK tag after PLATFORM_SDK_VERSION is upgraded to 35.
$(eval added_platform_abi_dumps := $(strip $(sort $(filter-out \
$(call filter-abi-dump-names,LLNDK PLATFORM,$(PRIVATE_LSDUMP_PATHS)) \
@@ -204,7 +96,7 @@
$(if $(added_platform_abi_dumps), \
echo -e "Found unexpected ABI reference dump files under $(PLATFORM_ABI_DUMP_DIR). It is caused by mismatch between Android.bp and the dump files. Run \`find \$${ANDROID_BUILD_TOP}/$(PLATFORM_ABI_DUMP_DIR) '(' -name $(subst $(space), -or -name ,$(added_platform_abi_dumps)) ')' -delete\` to delete the dump files.")
- $(if $(added_vndk_abi_dumps)$(added_ndk_abi_dumps)$(added_platform_abi_dumps),exit 1)
+ $(if $(added_vndk_abi_dumps)$(added_platform_abi_dumps),exit 1)
$(hide) mkdir -p $(dir $@)
$(hide) touch $@
@@ -212,33 +104,6 @@
# VNDK package and snapshot.
include $(CLEAR_VARS)
-LOCAL_MODULE := vndk_package
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
-# Filter LLNDK libs moved to APEX to avoid pulling them into /system/LIB
-LOCAL_REQUIRED_MODULES := llndk_in_system
-
-ifneq ($(TARGET_SKIP_CURRENT_VNDK),true)
-LOCAL_REQUIRED_MODULES += \
- vndkcorevariant.libraries.txt \
- $(addsuffix .vendor,$(VNDK_CORE_LIBRARIES)) \
- $(addsuffix .vendor,$(VNDK_SAMEPROCESS_LIBRARIES)) \
- $(VNDK_USING_CORE_VARIANT_LIBRARIES) \
- com.android.vndk.current
-
-# Install VNDK apex on vendor partition if VNDK is unfrozen
-ifdef UNFROZEN_VNDK
-LOCAL_REQUIRED_MODULES += com.android.vndk.current.on_vendor
-endif
-
-LOCAL_ADDITIONAL_DEPENDENCIES += $(call module-built-files,\
- $(addsuffix .vendor,$(VNDK_CORE_LIBRARIES) $(VNDK_SAMEPROCESS_LIBRARIES)))
-
-endif
-include $(BUILD_PHONY_PACKAGE)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := vndk_apex_snapshot_package
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 80aecb7..f771916 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -24,7 +24,7 @@
VNDK-SP: android.hardware.common-V2-ndk.so
VNDK-SP: android.hardware.common.fmq-V1-ndk.so
VNDK-SP: android.hardware.graphics.allocator-V2-ndk.so
-VNDK-SP: android.hardware.graphics.common-V4-ndk.so
+VNDK-SP: android.hardware.graphics.common-V5-ndk.so
VNDK-SP: android.hardware.graphics.common@1.0.so
VNDK-SP: android.hardware.graphics.common@1.1.so
VNDK-SP: android.hardware.graphics.common@1.2.so
@@ -60,8 +60,8 @@
VNDK-SP: libutilscallstack.so
VNDK-SP: libz.so
VNDK-core: android.frameworks.cameraservice.common-V1-ndk.so
-VNDK-core: android.frameworks.cameraservice.device-V1-ndk.so
-VNDK-core: android.frameworks.cameraservice.service-V1-ndk.so
+VNDK-core: android.frameworks.cameraservice.device-V2-ndk.so
+VNDK-core: android.frameworks.cameraservice.service-V2-ndk.so
VNDK-core: android.hardware.audio.common@2.0.so
VNDK-core: android.hardware.configstore-utils.so
VNDK-core: android.hardware.configstore@1.0.so
@@ -138,6 +138,7 @@
VNDK-core: libxml2.so
VNDK-core: libyuv.so
VNDK-core: libziparchive.so
+VNDK-core: server_configurable_flags.so
VNDK-private: libblas.so
VNDK-private: libcompiler_rt.so
VNDK-private: libft2.so
@@ -208,3 +209,4 @@
VNDK-product: libyuv.so
VNDK-product: libz.so
VNDK-product: libziparchive.so
+VNDK-product: server_configurable_flags.so
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index a581324..2e37366 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -31,11 +31,7 @@
PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
system/etc/init/config \
system/product/% \
- system/system_ext/% \
- system/lib/vndk-29 \
- system/lib/vndk-sp-29 \
- system/lib64/vndk-29 \
- system/lib64/vndk-sp-29
+ system/system_ext/%
# GSI should always support up-to-date platform features.
# Keep this value at the latest API level to ensure latest build system
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index b5292d2..bf9aa41 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -74,6 +74,7 @@
VpnDialogs \
vr \
+PRODUCT_PACKAGES += $(RELEASE_PACKAGE_VIRTUAL_CAMERA)
PRODUCT_SYSTEM_SERVER_APPS += \
FusedLocation \
diff --git a/target/product/handheld_system_ext.mk b/target/product/handheld_system_ext.mk
index 1218f7a..187b627 100644
--- a/target/product/handheld_system_ext.mk
+++ b/target/product/handheld_system_ext.mk
@@ -29,8 +29,3 @@
StorageManager \
SystemUI \
WallpaperCropper \
-
-# Base modules when shipping api level is less than or equal to 34
-PRODUCT_PACKAGES_SHIPPING_API_LEVEL_34 += \
- hwservicemanager \
- android.hidl.allocator@1.0-service \
diff --git a/target/product/module_common.mk b/target/product/module_common.mk
index 53b2ca6..bf146a0 100644
--- a/target/product/module_common.mk
+++ b/target/product/module_common.mk
@@ -14,6 +14,7 @@
# limitations under the License.
#
+$(call inherit-product, $(SRC_TARGET_DIR)/product/build_variables.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/default_art_config.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_default.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/cfi-common.mk)
diff --git a/target/product/sdk.mk b/target/product/sdk.mk
index 650f8e9..1a07363 100644
--- a/target/product/sdk.mk
+++ b/target/product/sdk.mk
@@ -17,6 +17,9 @@
# This is a simple product that uses configures the minimum amount
# needed to build the SDK (without the emulator).
+# Ensure all trunk-stable flags are available.
+$(call inherit-product, $(SRC_TARGET_DIR)/product/build_variables.mk)
+
# In order to build the bootclasspath sources, the bootclasspath needs to
# be setup via default_art_config.mk. The sources only really make sense
# together with a device (e.g. the emulator). So if the SDK sources change
@@ -29,4 +32,11 @@
PRODUCT_BRAND := Android
PRODUCT_DEVICE := mainline_x86
-PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
+PRODUCT_BUILD_FROM_SOURCE_STUB := true
+
+# Use sources of mainline modules
+PRODUCT_MODULE_BUILD_FROM_SOURCE := true
+
+ifeq ($(WITHOUT_CHECK_API),true)
+ $(error WITHOUT_CHECK_API cannot be set to true for SDK product builds)
+endif
diff --git a/target/product/sdk_with_runtime_apis.mk b/target/product/sdk_with_runtime_apis.mk
new file mode 100644
index 0000000..e80b4fb
--- /dev/null
+++ b/target/product/sdk_with_runtime_apis.mk
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+$(call inherit-product, $(SRC_TARGET_DIR)/product/sdk.mk)
+
+PRODUCT_NAME := sdk_with_runtime_apis
+
+PRODUCT_HIDDEN_API_EXPORTABLE_STUBS := true
+PRODUCT_EXPORT_RUNTIME_APIS := true
\ No newline at end of file
diff --git a/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml b/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
index 1295e1c..d3e2808 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-aosp-product.xml
@@ -27,5 +27,6 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml b/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
index e2482e1..ef8056f 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-generic-system.xml
@@ -24,6 +24,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml b/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
index 54add22..536c35b 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-handheld-product.xml
@@ -29,6 +29,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Camera (Camera2) -->
@@ -42,6 +43,7 @@
<install-in-user-type package="com.android.deskclock">
<install-in user-type="FULL" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Contacts -->
@@ -56,6 +58,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- Search (QuickSearchBox) TODO(b/258055479) -->
@@ -64,6 +67,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
<!-- WallpaperCropper -->
diff --git a/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml b/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
index cc1c135..67a2a01 100644
--- a/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
+++ b/target/product/sysconfig/preinstalled-packages-platform-telephony-product.xml
@@ -24,6 +24,7 @@
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+ <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
</config>
diff --git a/teams/Android.bp b/teams/Android.bp
index bae8e80..a02a573 100644
--- a/teams/Android.bp
+++ b/teams/Android.bp
@@ -732,7 +732,7 @@
}
team {
- name: "trendy_team_deprecated_systemui_gfx",
+ name: "trendy_team_ailabs",
// go/trendy/manage/engineers/6673470538285056
trendy_team_id: "6673470538285056",
@@ -4364,3 +4364,10 @@
// go/trendy/manage/engineers/4943966050844672
trendy_team_id: "4943966050844672",
}
+
+team {
+ name: "trendy_team_android_platform_performance_testing",
+
+ // go/trendy/manage/engineers/5810097836621824
+ trendy_team_id: "5810097836621824",
+}
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 95f1215..6bd0d06 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -4,6 +4,8 @@
"aconfig",
"aconfig_protos",
"aconfig_storage_file",
+ "aconfig_storage_read_api",
+ "aconfig_storage_write_api",
"aflags",
"printflags"
]
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index e42b5d3..b7ff8ef 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -20,10 +20,11 @@
// aconfig C++ integration tests (test mode auto-generated code)
"name": "aconfig.test.cpp.test_mode"
},
- {
- // aconfig C++ integration tests (exported mode auto-generated code)
- "name": "aconfig.test.cpp.exported_mode"
- },
+ // TODO(b/327420679): Enable export mode for native flag library
+ // {
+ // // 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"
@@ -32,9 +33,14 @@
// aconfig Rust integration tests (test mode auto-generated code)
"name": "aconfig.test_mode.test.rust"
},
+ // TODO(b/327420679): Enable export mode for native flag library
+ // {
+ // // aconfig Rust integration tests (exported mode auto-generated code)
+ // "name": "aconfig.exported_mode.test.rust"
+ // },
{
- // aconfig Rust integration tests (exported mode auto-generated code)
- "name": "aconfig.exported_mode.test.rust"
+ // aflags CLI unit tests
+ "name": "aflags.test"
},
{
// printflags unit tests
@@ -64,21 +70,32 @@
// test testing filtering logic. Breakage on this test means all tests
// that using the flag macros to do filtering will get affected.
"name": "FlagMacrosTests"
- }
- ],
- "postsubmit": [
+ },
+ {
+ // aconfig_storage_write_api unit tests
+ "name": "aconfig_storage_write_api.test"
+ },
+ {
+ // aconfig_storage_read_api unit tests
+ "name": "aconfig_storage_read_api.test"
+ },
+ {
+ // aconfig_storage write api rust integration tests
+ "name": "aconfig_storage_write_api.test.rust"
+ },
+ {
+ // aconfig_storage write api cpp integration tests
+ "name": "aconfig_storage_write_api.test.cpp"
+ },
{
// aconfig_storage read api rust integration tests
- "name": "aconfig_storage.test.rust"
+ "name": "aconfig_storage_read_api.test.rust"
},
{
// aconfig_storage read api cpp integration tests
- "name": "aconfig_storage.test.cpp"
- },
- {
- // aflags CLI unit tests
- // TODO(b/326062088): add to presubmit once proven in postsubmit.
- "name": "aflags.test"
+ "name": "aconfig_storage_read_api.test.cpp"
}
+ ],
+ "postsubmit": [
]
}
diff --git a/tools/aconfig/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp
index 164bfe7..00a6fee 100644
--- a/tools/aconfig/aconfig/Android.bp
+++ b/tools/aconfig/aconfig/Android.bp
@@ -47,6 +47,7 @@
aconfig_declarations {
name: "aconfig.test.exported.flags",
package: "com.android.aconfig.test.exported",
+ exportable: true,
container: "system",
srcs: ["tests/test_exported.aconfig"],
}
@@ -143,12 +144,6 @@
}
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",
@@ -184,6 +179,14 @@
test_suites: ["general-tests"],
}
+// TODO(327420679): Enable export mode for native flag library
+/*
+cc_aconfig_library {
+ name: "aconfig_test_cpp_library_exported_variant",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
cc_test {
name: "aconfig.test.cpp.exported_mode",
srcs: [
@@ -198,6 +201,7 @@
],
test_suites: ["general-tests"],
}
+*/
cc_test {
name: "aconfig.test.cpp.force_read_only_mode",
@@ -249,6 +253,8 @@
test_suites: ["general-tests"],
}
+// TODO(327420679): Enable export mode for native flag library
+/*
rust_aconfig_library {
name: "libaconfig_test_rust_library_with_exported_mode",
crate_name: "aconfig_test_rust_library",
@@ -266,6 +272,7 @@
],
test_suites: ["general-tests"],
}
+*/
rust_aconfig_library {
name: "libaconfig_test_rust_library_with_force_read_only_mode",
diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs
index a18f9a8..18a4be5 100644
--- a/tools/aconfig/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/aconfig/src/codegen/java.rs
@@ -181,26 +181,35 @@
/** @hide */
public interface FeatureFlags {
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRo();
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRw();
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRwExported();
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRwInOtherNamespace();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledFixedRo();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledFixedRoExported();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledRo();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledRoExported();
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledRw();
}
@@ -232,42 +241,51 @@
public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRo() {
return FEATURE_FLAGS.disabledRo();
}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRw() {
return FEATURE_FLAGS.disabledRw();
}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRwExported() {
return FEATURE_FLAGS.disabledRwExported();
}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRwInOtherNamespace() {
return FEATURE_FLAGS.disabledRwInOtherNamespace();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledFixedRo() {
return FEATURE_FLAGS.enabledFixedRo();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledFixedRoExported() {
return FEATURE_FLAGS.enabledFixedRoExported();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledRo() {
return FEATURE_FLAGS.enabledRo();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledRoExported() {
return FEATURE_FLAGS.enabledRoExported();
}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledRw() {
return FEATURE_FLAGS.enabledRw();
@@ -351,6 +369,10 @@
}
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() {
+ return false;
+ }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -358,10 +380,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RO, false),
@@ -458,13 +476,14 @@
other_namespace_is_cached = true;
}
-
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRo() {
return false;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRw() {
if (!aconfig_test_is_cached) {
@@ -473,6 +492,7 @@
return disabledRw;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRwExported() {
if (!aconfig_test_is_cached) {
@@ -481,6 +501,7 @@
return disabledRwExported;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRwInOtherNamespace() {
if (!other_namespace_is_cached) {
@@ -489,26 +510,31 @@
return disabledRwInOtherNamespace;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledFixedRo() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledFixedRoExported() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRo() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRoExported() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRw() {
if (!aconfig_test_is_cached) {
@@ -558,8 +584,6 @@
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 */
@@ -568,16 +592,12 @@
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
public static boolean disabledRwExported() {
return FEATURE_FLAGS.disabledRwExported();
}
- @UnsupportedAppUsage
public static boolean enabledFixedRoExported() {
return FEATURE_FLAGS.enabledFixedRoExported();
}
- @UnsupportedAppUsage
public static boolean enabledRoExported() {
return FEATURE_FLAGS.enabledRoExported();
}
@@ -587,23 +607,16 @@
let expect_feature_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 interface FeatureFlags {
- @UnsupportedAppUsage
boolean disabledRwExported();
- @UnsupportedAppUsage
boolean enabledFixedRoExported();
- @UnsupportedAppUsage
boolean enabledRoExported();
}
"#;
let expect_feature_flags_impl_content = r#"
package com.android.aconfig.test;
- // TODO(b/303773055): Remove the annotation after access issue is resolved.
- import android.compat.annotation.UnsupportedAppUsage;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
/** @hide */
@@ -635,27 +648,21 @@
}
aconfig_test_is_cached = true;
}
-
@Override
- @UnsupportedAppUsage
public boolean disabledRwExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
}
return disabledRwExported;
}
-
@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();
@@ -666,8 +673,6 @@
let expect_fake_feature_flags_impl_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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -679,17 +684,14 @@
resetAll();
}
@Override
- @UnsupportedAppUsage
public boolean disabledRwExported() {
return getValue(Flags.FLAG_DISABLED_RW_EXPORTED);
}
@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);
}
@@ -704,13 +706,6 @@
entry.setValue(null);
}
}
- public boolean isFlagReadOnlyOptimized(String flagName) {
- if (mReadOnlyFlagsSet.contains(flagName) &&
- isOptimizationEnabled()) {
- return true;
- }
- return false;
- }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -718,10 +713,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
@@ -793,54 +784,63 @@
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags {
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRw() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRwExported() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRwInOtherNamespace() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledFixedRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledFixedRoExported() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRoExported() {
throw new UnsupportedOperationException(
"Method is not implemented.");
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRw() {
throw new UnsupportedOperationException(
@@ -893,21 +893,27 @@
/** @hide */
public interface FeatureFlags {
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRo();
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRw();
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean disabledRwInOtherNamespace();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledFixedRo();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledRo();
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
boolean enabledRw();
}"#;
@@ -919,31 +925,37 @@
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags {
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRo() {
return false;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRw() {
return false;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean disabledRwInOtherNamespace() {
return false;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledFixedRo() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRo() {
return true;
}
@Override
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public boolean enabledRw() {
return true;
@@ -969,33 +981,38 @@
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
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRo() {
return FEATURE_FLAGS.disabledRo();
}
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRw() {
return FEATURE_FLAGS.disabledRw();
}
@com.android.aconfig.annotations.AssumeFalseForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean disabledRwInOtherNamespace() {
return FEATURE_FLAGS.disabledRwInOtherNamespace();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledFixedRo() {
return FEATURE_FLAGS.enabledFixedRo();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledRo() {
return FEATURE_FLAGS.enabledRo();
}
@com.android.aconfig.annotations.AssumeTrueForR8
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
public static boolean enabledRw() {
return FEATURE_FLAGS.enabledRw();
@@ -1065,6 +1082,10 @@
}
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() {
+ return false;
+ }
private boolean getValue(String flagName) {
Boolean value = this.mFlagMap.get(flagName);
if (value == null) {
@@ -1072,10 +1093,6 @@
}
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() {
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RO, false),
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index 59f349b..7736ce7 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -31,7 +31,7 @@
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
ProtoParsedFlags, ProtoTracepoint,
};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
pub struct Input {
pub source: String,
@@ -203,6 +203,11 @@
}
pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
+ // TODO(327420679): Enable export mode for native flag library
+ ensure!(
+ codegen_mode != CodegenMode::Exported,
+ "Exported mode for generated c/c++ flag library is disabled"
+ );
let parsed_flags = input.try_parse_flags()?;
let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
let Some(package) = find_unique_package(&modified_parsed_flags) else {
@@ -214,6 +219,11 @@
}
pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
+ // // TODO(327420679): Enable export mode for native flag library
+ ensure!(
+ codegen_mode != CodegenMode::Exported,
+ "Exported mode for generated rust flag library is disabled"
+ );
let parsed_flags = input.try_parse_flags()?;
let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
let Some(package) = find_unique_package(&modified_parsed_flags) else {
@@ -227,7 +237,7 @@
pub fn create_storage(
caches: Vec<Input>,
container: &str,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>> {
let parsed_flags_vec: Vec<ProtoParsedFlags> = caches
.into_iter()
@@ -307,9 +317,7 @@
}
fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
- let Some(package) = parsed_flags.first().map(|pf| pf.package()) else {
- return None;
- };
+ let package = parsed_flags.first().map(|pf| pf.package())?;
if parsed_flags.iter().any(|pf| pf.package() != package) {
return None;
}
@@ -317,9 +325,7 @@
}
fn find_unique_container(parsed_flags: &ProtoParsedFlags) -> Option<&str> {
- let Some(container) = parsed_flags.parsed_flag.first().map(|pf| pf.container()) else {
- return None;
- };
+ let container = parsed_flags.parsed_flag.first().map(|pf| pf.container())?;
if parsed_flags.parsed_flag.iter().any(|pf| pf.container() != container) {
return None;
}
diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs
index 5a4f23c..69f5458 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -29,7 +29,7 @@
mod dump;
mod storage;
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
use codegen::CodegenMode;
use dump::DumpFormat;
@@ -138,7 +138,7 @@
.arg(
Arg::new("file")
.long("file")
- .value_parser(|s: &str| StorageFileSelection::try_from(s)),
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
)
.arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
.arg(Arg::new("out").long("out").required(true)),
@@ -285,7 +285,7 @@
write_output_to_file_or_stdout(path, &output)?;
}
Some(("create-storage", sub_matches)) => {
- let file = get_required_arg::<StorageFileSelection>(sub_matches, "file")
+ let file = get_required_arg::<StorageFileType>(sub_matches, "file")
.context("Invalid storage file selection")?;
let cache = open_zero_or_more_files(sub_matches, "cache")?;
let container = get_required_arg::<String>(sub_matches, "container")?;
diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs
index 1381e89..a971211 100644
--- a/tools/aconfig/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_table.rs
@@ -16,8 +16,10 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
+use aconfig_protos::ProtoFlagPermission;
use aconfig_storage_file::{
- get_table_size, FlagTable, FlagTableHeader, FlagTableNode, FILE_VERSION,
+ get_table_size, FlagTable, FlagTableHeader, FlagTableNode, StorageFileType, StoredFlagType,
+ FILE_VERSION,
};
use anyhow::{anyhow, Result};
@@ -25,6 +27,7 @@
FlagTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagMap as u8,
file_size: 0,
num_flags,
bucket_offset: 0,
@@ -44,8 +47,8 @@
fn new(
package_id: u32,
flag_name: &str,
- flag_type: u16,
- flag_id: u16,
+ flag_type: StoredFlagType,
+ flag_index: u16,
num_buckets: u32,
) -> Self {
let bucket_index = FlagTableNode::find_bucket_index(package_id, flag_name, num_buckets);
@@ -53,7 +56,7 @@
package_id,
flag_name: flag_name.to_string(),
flag_type,
- flag_id,
+ flag_index,
next_offset: None,
};
Self { node, bucket_index }
@@ -69,11 +72,14 @@
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;
+ let flag_type = if pf.is_fixed_read_only() {
+ StoredFlagType::FixedReadOnlyBoolean
+ } else {
+ match pf.permission() {
+ ProtoFlagPermission::READ_WRITE => StoredFlagType::ReadWriteBoolean,
+ ProtoFlagPermission::READ_ONLY => StoredFlagType::ReadOnlyBoolean,
+ }
+ };
Ok(Self::new(package.package_id, pf.name(), flag_type, *fid, num_buckets))
})
.collect::<Result<Vec<_>>>()
@@ -94,10 +100,10 @@
.concat();
// initialize all header fields
- header.bucket_offset = header.as_bytes().len() as u32;
+ header.bucket_offset = header.into_bytes().len() as u32;
header.node_offset = header.bucket_offset + num_buckets * 4;
header.file_size = header.node_offset
- + node_wrappers.iter().map(|x| x.node.as_bytes().len()).sum::<usize>() as u32;
+ + node_wrappers.iter().map(|x| x.node.into_bytes().len()).sum::<usize>() as u32;
// sort nodes by bucket index for efficiency
node_wrappers.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
@@ -115,7 +121,7 @@
if buckets[node_bucket_idx as usize].is_none() {
buckets[node_bucket_idx as usize] = Some(offset);
}
- offset += node_wrappers[i].node.as_bytes().len() as u32;
+ offset += node_wrappers[i].node.into_bytes().len() as u32;
if let Some(index) = next_node_bucket_idx {
if index == node_bucket_idx {
@@ -135,78 +141,18 @@
use super::*;
use crate::storage::{group_flags_by_package, tests::parse_all_test_flags};
- // create test baseline, syntactic sugar
- fn new_expected_node(
- package_id: u32,
- flag_name: &str,
- flag_type: u16,
- flag_id: u16,
- next_offset: Option<u32>,
- ) -> FlagTableNode {
- FlagTableNode {
- package_id,
- flag_name: flag_name.to_string(),
- flag_type,
- flag_id,
- next_offset,
- }
- }
-
- fn create_test_flag_table() -> Result<FlagTable> {
+ fn create_test_flag_table_from_source() -> Result<FlagTable> {
let caches = parse_all_test_flags();
let packages = group_flags_by_package(caches.iter());
- create_flag_table("system", &packages)
+ create_flag_table("mockup", &packages)
}
#[test]
// this test point locks down the table creation and each field
fn test_table_contents() {
- let flag_table = create_test_flag_table();
+ let flag_table = create_test_flag_table_from_source();
assert!(flag_table.is_ok());
-
- let header: &FlagTableHeader = &flag_table.as_ref().unwrap().header;
- let expected_header = FlagTableHeader {
- version: FILE_VERSION,
- container: String::from("system"),
- file_size: 320,
- num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
- };
- assert_eq!(header, &expected_header);
-
- 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], new_expected_node(0, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[1], new_expected_node(0, "enabled_rw", 1, 2, Some(150)));
- assert_eq!(nodes[2], new_expected_node(1, "disabled_ro", 1, 0, None));
- assert_eq!(nodes[3], new_expected_node(2, "enabled_ro", 1, 1, None));
- assert_eq!(nodes[4], new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(235)));
- assert_eq!(nodes[5], new_expected_node(1, "enabled_ro", 1, 2, None));
- assert_eq!(nodes[6], new_expected_node(2, "enabled_fixed_ro", 1, 0, None));
- assert_eq!(nodes[7], new_expected_node(0, "disabled_rw", 1, 0, None));
+ let expected_flag_table = aconfig_storage_file::test_utils::create_test_flag_table();
+ assert_eq!(flag_table.unwrap(), expected_flag_table);
}
}
diff --git a/tools/aconfig/aconfig/src/storage/flag_value.rs b/tools/aconfig/aconfig/src/storage/flag_value.rs
index 0d4b5b4..c15ba54 100644
--- a/tools/aconfig/aconfig/src/storage/flag_value.rs
+++ b/tools/aconfig/aconfig/src/storage/flag_value.rs
@@ -17,13 +17,14 @@
use crate::commands::assign_flag_ids;
use crate::storage::FlagPackage;
use aconfig_protos::ProtoFlagState;
-use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION};
+use aconfig_storage_file::{FlagValueHeader, FlagValueList, StorageFileType, FILE_VERSION};
use anyhow::{anyhow, Result};
fn new_header(container: &str, num_flags: u32) -> FlagValueHeader {
FlagValueHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::FlagVal as u8,
file_size: 0,
num_flags,
boolean_value_offset: 0,
@@ -40,19 +41,19 @@
};
for pkg in packages.iter() {
- let start_offset = pkg.boolean_offset as usize;
+ let start_index = pkg.boolean_start_index as usize;
let flag_ids = assign_flag_ids(pkg.package_name, pkg.boolean_flags.iter().copied())?;
for pf in pkg.boolean_flags.iter() {
let fid = flag_ids
.get(pf.name())
.ok_or(anyhow!(format!("missing flag id for {}", pf.name())))?;
- list.booleans[start_offset + (*fid as usize)] = pf.state() == ProtoFlagState::ENABLED;
+ list.booleans[start_index + (*fid as usize)] = pf.state() == ProtoFlagState::ENABLED;
}
}
// initialize all header fields
- list.header.boolean_value_offset = list.header.as_bytes().len() as u32;
+ list.header.boolean_value_offset = list.header.into_bytes().len() as u32;
list.header.file_size = list.header.boolean_value_offset + num_flags;
Ok(list)
@@ -63,30 +64,19 @@
use super::*;
use crate::storage::{group_flags_by_package, tests::parse_all_test_flags};
- pub fn create_test_flag_value_list() -> Result<FlagValueList> {
+ pub fn create_test_flag_value_list_from_source() -> Result<FlagValueList> {
let caches = parse_all_test_flags();
let packages = group_flags_by_package(caches.iter());
- create_flag_value("system", &packages)
+ create_flag_value("mockup", &packages)
}
#[test]
// this test point locks down the flag value creation and each field
fn test_list_contents() {
- let flag_value_list = create_test_flag_value_list();
+ let flag_value_list = create_test_flag_value_list_from_source();
assert!(flag_value_list.is_ok());
-
- let header: &FlagValueHeader = &flag_value_list.as_ref().unwrap().header;
- let expected_header = FlagValueHeader {
- version: FILE_VERSION,
- container: String::from("system"),
- file_size: 34,
- num_flags: 8,
- boolean_value_offset: 26,
- };
- assert_eq!(header, &expected_header);
-
- let booleans: &Vec<bool> = &flag_value_list.as_ref().unwrap().booleans;
- let expected_booleans: Vec<bool> = vec![false; header.num_flags as usize];
- assert_eq!(booleans, &expected_booleans);
+ let expected_flag_value_list =
+ aconfig_storage_file::test_utils::create_test_flag_value_list();
+ assert_eq!(flag_value_list.unwrap(), expected_flag_value_list);
}
}
diff --git a/tools/aconfig/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs
index 29eb9c8..855ed02 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -18,7 +18,7 @@
pub mod flag_value;
pub mod package_table;
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use std::collections::{HashMap, HashSet};
use crate::storage::{
@@ -26,16 +26,16 @@
package_table::create_package_table,
};
use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags};
-use aconfig_storage_file::StorageFileSelection;
+use aconfig_storage_file::StorageFileType;
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,
+ // The index of the first boolean flag in this aconfig package among all boolean
+ // flags in this container.
+ pub boolean_start_index: u32,
}
impl<'a> FlagPackage<'a> {
@@ -45,7 +45,7 @@
package_id,
flag_names: HashSet::new(),
boolean_flags: vec![],
- boolean_offset: 0,
+ boolean_start_index: 0,
}
}
@@ -73,12 +73,11 @@
}
}
- // calculate package flag value start offset, in flag value file, each boolean
- // is stored as a single byte
- let mut boolean_offset = 0;
+ // cacluate boolean flag start index for each package
+ let mut boolean_start_index = 0;
for p in packages.iter_mut() {
- p.boolean_offset = boolean_offset;
- boolean_offset += p.boolean_flags.len() as u32;
+ p.boolean_start_index = boolean_start_index;
+ boolean_start_index += p.boolean_flags.len() as u32;
}
packages
@@ -87,7 +86,7 @@
pub fn generate_storage_file<'a, I>(
container: &str,
parsed_flags_vec_iter: I,
- file: &StorageFileSelection,
+ file: &StorageFileType,
) -> Result<Vec<u8>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
@@ -95,18 +94,19 @@
let packages = group_flags_by_package(parsed_flags_vec_iter);
match file {
- StorageFileSelection::PackageMap => {
+ StorageFileType::PackageMap => {
let package_table = create_package_table(container, &packages)?;
- Ok(package_table.as_bytes())
+ Ok(package_table.into_bytes())
}
- StorageFileSelection::FlagMap => {
+ StorageFileType::FlagMap => {
let flag_table = create_flag_table(container, &packages)?;
- Ok(flag_table.as_bytes())
+ Ok(flag_table.into_bytes())
}
- StorageFileSelection::FlagVal => {
+ StorageFileType::FlagVal => {
let flag_value = create_flag_value(container, &packages)?;
- Ok(flag_value.as_bytes())
+ Ok(flag_value.into_bytes())
}
+ _ => Err(anyhow!("aconfig does not support the creation of this storage file type")),
}
}
@@ -121,30 +121,38 @@
"com.android.aconfig.storage.test_1",
"storage_test_1.aconfig",
include_bytes!("../../tests/storage_test_1.aconfig").as_slice(),
+ "storage_test_1.value",
+ include_bytes!("../../tests/storage_test_1.values").as_slice(),
),
(
"com.android.aconfig.storage.test_2",
"storage_test_2.aconfig",
include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
+ "storage_test_2.value",
+ include_bytes!("../../tests/storage_test_2.values").as_slice(),
),
(
"com.android.aconfig.storage.test_4",
"storage_test_4.aconfig",
include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
+ "storage_test_4.value",
+ include_bytes!("../../tests/storage_test_4.values").as_slice(),
),
];
-
aconfig_files
.into_iter()
- .map(|(pkg, file, content)| {
+ .map(|(pkg, aconfig_file, aconfig_content, value_file, value_content)| {
let bytes = crate::commands::parse_flags(
pkg,
Some("system"),
vec![Input {
- source: format!("tests/{}", file).to_string(),
- reader: Box::new(content),
+ source: format!("tests/{}", aconfig_file).to_string(),
+ reader: Box::new(aconfig_content),
}],
- vec![],
+ vec![Input {
+ source: format!("tests/{}", value_file).to_string(),
+ reader: Box::new(value_content),
+ }],
crate::commands::DEFAULT_FLAG_PERMISSION,
)
.unwrap();
@@ -175,7 +183,7 @@
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_eq!(packages[0].boolean_offset, 0);
+ assert_eq!(packages[0].boolean_start_index, 0);
assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
assert_eq!(packages[1].package_id, 1);
@@ -183,13 +191,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, 3);
+ assert_eq!(packages[1].boolean_start_index, 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);
+ assert_eq!(packages[2].boolean_start_index, 6);
}
}
diff --git a/tools/aconfig/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs
index 4c08129..c53602f 100644
--- a/tools/aconfig/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/aconfig/src/storage/package_table.rs
@@ -17,7 +17,8 @@
use anyhow::Result;
use aconfig_storage_file::{
- get_table_size, PackageTable, PackageTableHeader, PackageTableNode, FILE_VERSION,
+ get_table_size, PackageTable, PackageTableHeader, PackageTableNode, StorageFileType,
+ FILE_VERSION,
};
use crate::storage::FlagPackage;
@@ -26,6 +27,7 @@
PackageTableHeader {
version: FILE_VERSION,
container: String::from(container),
+ file_type: StorageFileType::PackageMap as u8,
file_size: 0,
num_packages,
bucket_offset: 0,
@@ -46,7 +48,7 @@
let node = PackageTableNode {
package_name: String::from(package.package_name),
package_id: package.package_id,
- boolean_offset: package.boolean_offset,
+ boolean_start_index: package.boolean_start_index,
next_offset: None,
};
let bucket_index = PackageTableNode::find_bucket_index(package.package_name, num_buckets);
@@ -64,10 +66,10 @@
packages.iter().map(|pkg| PackageTableNodeWrapper::new(pkg, num_buckets)).collect();
// initialize all header fields
- header.bucket_offset = header.as_bytes().len() as u32;
+ header.bucket_offset = header.into_bytes().len() as u32;
header.node_offset = header.bucket_offset + num_buckets * 4;
header.file_size = header.node_offset
- + node_wrappers.iter().map(|x| x.node.as_bytes().len()).sum::<usize>() as u32;
+ + node_wrappers.iter().map(|x| x.node.into_bytes().len()).sum::<usize>() as u32;
// sort node_wrappers by bucket index for efficiency
node_wrappers.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
@@ -85,7 +87,7 @@
if buckets[node_bucket_idx as usize].is_none() {
buckets[node_bucket_idx as usize] = Some(offset);
}
- offset += node_wrappers[i].node.as_bytes().len() as u32;
+ offset += node_wrappers[i].node.into_bytes().len() as u32;
if let Some(index) = next_node_bucket_idx {
if index == node_bucket_idx {
@@ -107,55 +109,18 @@
use super::*;
use crate::storage::{group_flags_by_package, tests::parse_all_test_flags};
- pub fn create_test_package_table() -> Result<PackageTable> {
+ pub fn create_test_package_table_from_source() -> Result<PackageTable> {
let caches = parse_all_test_flags();
let packages = group_flags_by_package(caches.iter());
- create_package_table("system", &packages)
+ create_package_table("mockup", &packages)
}
#[test]
// this test point locks down the table creation and each field
fn test_table_contents() {
- let package_table = create_test_package_table();
+ let package_table = create_test_package_table_from_source();
assert!(package_table.is_ok());
-
- let header: &PackageTableHeader = &package_table.as_ref().unwrap().header;
- let expected_header = PackageTableHeader {
- version: 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,
- };
- 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),
- };
- 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,
- };
- assert_eq!(nodes[2], third_node_expected);
+ let expected_package_table = aconfig_storage_file::test_utils::create_test_package_table();
+ assert_eq!(package_table.unwrap(), expected_package_table);
}
}
diff --git a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
index 28dddd8..177e711 100644
--- a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -1,7 +1,8 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -16,7 +17,7 @@
{{ for item in flag_elements}}
@Override
- @UnsupportedAppUsage
+{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }}
public boolean {item.method_name}() \{
return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
}
@@ -33,7 +34,7 @@
entry.setValue(null);
}
}
-
+{{ if not library_exported }}
public boolean isFlagReadOnlyOptimized(String flagName) \{
if (mReadOnlyFlagsSet.contains(flagName) &&
isOptimizationEnabled()) \{
@@ -42,6 +43,11 @@
return false;
}
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ private boolean isOptimizationEnabled() \{
+ return false;
+ }
+{{ -endif }}
private boolean getValue(String flagName) \{
Boolean value = this.mFlagMap.get(flagName);
if (value == null) \{
@@ -50,10 +56,6 @@
return value;
}
- @com.android.aconfig.annotations.AssumeTrueForR8
- private boolean isOptimizationEnabled() \{
- return false;
- }
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
diff --git a/tools/aconfig/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/aconfig/templates/FeatureFlags.java.template
index 5e67b13..38c8f13 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlags.java.template
@@ -1,23 +1,22 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
/** @hide */
public interface FeatureFlags \{
{{ for item in flag_elements }}
-{{ -if library_exported }}
- @UnsupportedAppUsage
- boolean {item.method_name}();
-{{ -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 }}
+{{ -if not library_exported }}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
+{{ -endif }}
boolean {item.method_name}();
-{{ endif }}
{{ -endfor }}
-}
+}
\ No newline at end of file
diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
index 28baa41..6235e69 100644
--- a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,6 +1,8 @@
package {package_name};
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
+{{ -endif }}
{{ -if not is_test_mode }}
{{ -if runtime_lookup_required }}
import android.provider.DeviceConfig;
@@ -14,12 +16,8 @@
{{ -endfor- }}
{{ for flag in flag_elements }}
-{{ -if library_exported }}
- private static boolean {flag.method_name} = false;
-{{ -else }}
{{- if flag.is_read_write }}
private static boolean {flag.method_name} = {flag.default_value};
-{{- endif- }}
{{ -endif }}
{{ -endfor }}
{{ for namespace_with_flags in namespace_flags }}
@@ -27,15 +25,10 @@
try \{
Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
{{ -for flag in namespace_with_flags.flags }}
-{{ -if library_exported }}
- {flag.method_name} =
- properties.getBoolean("{flag.device_config_flag}", false);
-{{ -else }}
{{ -if flag.is_read_write }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", {flag.default_value});
{{ -endif }}
-{{ -endif }}
{{ -endfor }}
} catch (NullPointerException e) \{
throw new RuntimeException(
@@ -53,14 +46,11 @@
{{ -endif }}{#- end of runtime_lookup_required #}
{{ -for flag in flag_elements }}
@Override
+{{ -if not library_exported }}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
+{{ -endif }}
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};
-{{ -else }}
{{ -if flag.is_read_write }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
@@ -68,7 +58,6 @@
return {flag.method_name};
{{ -else }}
return {flag.default_value};
-{{ -endif- }}
{{ -endif }}
}
{{ endfor }}
@@ -79,7 +68,10 @@
public final class FeatureFlagsImpl implements FeatureFlags \{
{{ for flag in flag_elements }}
@Override
+{{ -if not library_exported }}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
+{{ -endif }}
public boolean {flag.method_name}() \{
throw new UnsupportedOperationException(
"Method is not implemented.");
diff --git a/tools/aconfig/aconfig/templates/Flags.java.template b/tools/aconfig/aconfig/templates/Flags.java.template
index 34b8189..e2f70b9 100644
--- a/tools/aconfig/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/aconfig/templates/Flags.java.template
@@ -1,8 +1,8 @@
package {package_name};
-
+{{ if not library_exported- }}
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-
+{{ -endif }}
/** @hide */
public final class Flags \{
{{ -for item in flag_elements}}
@@ -10,12 +10,6 @@
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
{{- endfor }}
{{ -for item in flag_elements}}
-{{ if library_exported }}
- @UnsupportedAppUsage
- public static boolean {item.method_name}() \{
- return FEATURE_FLAGS.{item.method_name}();
- }
-{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
@@ -23,11 +17,13 @@
@com.android.aconfig.annotations.AssumeFalseForR8
{{ -endif }}
{{ -endif }}
+{{ -if not library_exported }}
+ @com.android.aconfig.annotations.AconfigFlagAccessor
@UnsupportedAppUsage
+{{ -endif }}
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ -endif }}
{{ -endfor }}
{{ -if is_test_mode }}
public static void setFeatureFlags(FeatureFlags featureFlags) \{
diff --git a/tools/aconfig/aconfig/tests/storage_test_1.values b/tools/aconfig/aconfig/tests/storage_test_1.values
new file mode 100644
index 0000000..35548ae
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/storage_test_1.values
@@ -0,0 +1,18 @@
+flag_value {
+ package: "com.android.aconfig.storage.test_1"
+ name: "enabled_rw"
+ state: ENABLED
+ permission: READ_WRITE
+}
+flag_value {
+ package: "com.android.aconfig.storage.test_1"
+ name: "disabled_rw"
+ state: DISABLED
+ permission: READ_WRITE
+}
+flag_value {
+ package: "com.android.aconfig.storage.test_1"
+ name: "enabled_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/aconfig/tests/storage_test_2.values b/tools/aconfig/aconfig/tests/storage_test_2.values
new file mode 100644
index 0000000..a7bb0b1
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/storage_test_2.values
@@ -0,0 +1,18 @@
+flag_value {
+ package: "com.android.aconfig.storage.test_2"
+ name: "enabled_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
+flag_value {
+ package: "com.android.aconfig.storage.test_2"
+ name: "disabled_ro"
+ state: DISABLED
+ permission: READ_ONLY
+}
+flag_value {
+ package: "com.android.aconfig.storage.test_2"
+ name: "enabled_fixed_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/aconfig/tests/storage_test_4.values b/tools/aconfig/aconfig/tests/storage_test_4.values
new file mode 100644
index 0000000..fa21317
--- /dev/null
+++ b/tools/aconfig/aconfig/tests/storage_test_4.values
@@ -0,0 +1,12 @@
+flag_value {
+ package: "com.android.aconfig.storage.test_4"
+ name: "enabled_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
+flag_value {
+ package: "com.android.aconfig.storage.test_4"
+ name: "enabled_fixed_ro"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/aconfig_protos/protos/aconfig.proto b/tools/aconfig/aconfig_protos/protos/aconfig.proto
index 8833722..9d1b8cb 100644
--- a/tools/aconfig/aconfig_protos/protos/aconfig.proto
+++ b/tools/aconfig/aconfig_protos/protos/aconfig.proto
@@ -22,16 +22,38 @@
// This protobuf file defines messages used to represent and manage flags in the "aconfig" system
// The following format requirements apply across various message fields:
-// # name: a lowercase string in snake_case format, no consecutive underscores, and no leading digit
-// For example adjust_rate is a valid name, while AdjustRate, adjust__rate, and
-// 2adjust_rate are invalid
//
-// # namespace: a lowercase string in snake_case format, no consecutive underscores, and no leading
-// digit. For example android_bar_system
+// # name: name of the flag
//
-// # package: lowercase strings in snake_case format, delimited by dots, no consecutive underscores
-// and no leading digit in each string. For example com.android.mypackage is a valid name
-// while com.android.myPackage, com.android.1mypackage are invalid
+// format: a lowercase string in snake_case format, no consecutive underscores, and no leading
+// digit. For example adjust_rate is a valid name, while AdjustRate, adjust__rate, and
+// adjust_rate are invalid
+//
+// # namespace: namespace the flag belongs to
+//
+// format: a lowercase string in snake_case format, no consecutive underscores, and no leading
+// digit. For example android_bar_system
+//
+// # package: package to which the flag belongs
+//
+// format: lowercase strings in snake_case format, delimited by dots, no consecutive underscores
+// and no leading digit in each string. For example com.android.mypackage is a valid name
+// while com.android.myPackage, com.android.1mypackage are invalid
+//
+// # container: container as software built in its entirety using the same build environment and
+// always installed as a single unit
+//
+// For example the following are all separate containers:
+// * the system partition
+// * the vendor partition
+// * apexes: each APEX is its own container
+// * APKs: for APKs which are released independently via Play, each APK is its own container.
+// If an APK is released as part of a Mainline module, or as part of the system partition
+// via OTA, then they are part of the apex or the system partition container
+//
+// format: lowercase strings in snake_case format, delimited by dots if multiple, no consecutive
+// underscores or leading digits in each string. The recommended container values are the
+// partition names or the module names
// messages used in both aconfig input and output
@@ -98,6 +120,7 @@
repeated flag_declaration flag = 2;
// Container the flag belongs to (optional)
+ // See # container for format detail
optional string container = 3;
};
@@ -160,6 +183,7 @@
optional bool is_exported = 10;
// Container the flag belongs to (optional)
+ // See # container for format detail
optional string container = 11;
// Additional information about the flag, including its purpose and form factors (optional)
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index 8922ba4..b590312 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -6,16 +6,13 @@
name: "aconfig_storage_file.defaults",
edition: "2021",
lints: "none",
- srcs: ["src/lib.rs"],
rustlibs: [
"libanyhow",
- "libaconfig_storage_protos",
- "libonce_cell",
- "libprotobuf",
- "libtempfile",
- "libmemmap2",
- "libcxx",
"libthiserror",
+ "libtempfile",
+ "libprotobuf",
+ "libclap",
+ "libaconfig_storage_protos",
],
}
@@ -24,38 +21,26 @@
crate_name: "aconfig_storage_file",
host_supported: true,
defaults: ["aconfig_storage_file.defaults"],
+ srcs: ["src/lib.rs"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ min_sdk_version: "29",
}
-genrule {
- name: "ro.package.map",
- out: ["tests/tmp.ro.package.map"],
- srcs: ["tests/package.map"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
- name: "ro.flag.map",
- out: ["tests/tmp.ro.flag.map"],
- srcs: ["tests/flag.map"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
-}
-
-genrule {
- name: "ro.flag.val",
- out: ["tests/tmp.ro.flag.val"],
- srcs: ["tests/flag.val"],
- cmd: "rm -f $(out);cp -f $(in) $(out);chmod -w $(out)",
+rust_binary_host {
+ name: "aconfig-storage",
+ defaults: ["aconfig_storage_file.defaults"],
+ srcs: ["src/main.rs"],
+ rustlibs: ["libaconfig_storage_file"],
}
rust_test_host {
name: "aconfig_storage_file.test",
test_suites: ["general-tests"],
defaults: ["aconfig_storage_file.defaults"],
- data: [
- "tests/package.map",
- "tests/flag.map",
- "tests/flag.val",
- ],
+ srcs: ["src/lib.rs"],
}
rust_protobuf {
@@ -64,6 +49,11 @@
crate_name: "aconfig_storage_protos",
source_stem: "aconfig_storage_protos",
host_supported: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ min_sdk_version: "29",
}
cc_library_static {
@@ -79,38 +69,3 @@
],
host_supported: true,
}
-
-genrule {
- name: "libcxx_aconfig_storage_bridge_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) > $(out)",
- srcs: ["src/lib.rs"],
- out: ["aconfig_storage/lib.rs.cc"],
-}
-
-genrule {
- name: "libcxx_aconfig_storage_bridge_header",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) --header > $(out)",
- srcs: ["src/lib.rs"],
- out: ["aconfig_storage/lib.rs.h"],
-}
-
-rust_ffi_static {
- name: "libaconfig_storage_cxx_bridge",
- crate_name: "aconfig_storage_cxx_bridge",
- host_supported: true,
- defaults: ["aconfig_storage_file.defaults"],
-}
-
-cc_library_static {
- name: "libaconfig_storage_cc",
- srcs: ["aconfig_storage.cpp"],
- generated_headers: [
- "cxx-bridge-header",
- "libcxx_aconfig_storage_bridge_header"
- ],
- generated_sources: ["libcxx_aconfig_storage_bridge_code"],
- whole_static_libs: ["libaconfig_storage_cxx_bridge"],
- export_include_dirs: ["include"],
-}
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index c4e2670..641f481 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -9,12 +9,14 @@
[dependencies]
anyhow = "1.0.69"
-memmap2 = "0.8.0"
protobuf = "3.2.0"
-once_cell = "1.19.0"
tempfile = "3.9.0"
-cxx = "1.0"
thiserror = "1.0.56"
+clap = { version = "4.1.8", features = ["derive"] }
+
+[[bin]]
+name = "aconfig-storage"
+path = "src/main.rs"
[build-dependencies]
protobuf-codegen = "3.2.0"
diff --git a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp b/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp
deleted file mode 100644
index ac64093..0000000
--- a/tools/aconfig/aconfig_storage_file/aconfig_storage.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-#include "aconfig_storage/aconfig_storage.hpp"
-
-#include "rust/cxx.h"
-#include "aconfig_storage/lib.rs.h"
-
-namespace aconfig_storage {
-
-/// Get package offset
-PackageOffsetQuery get_package_offset(
- std::string const& container,
- std::string const& package) {
- auto offset_cxx = get_package_offset_cxx(
- rust::Str(container.c_str()),
- rust::Str(package.c_str()));
- auto offset = PackageOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.package_exists = offset_cxx.package_exists;
- offset.package_id = offset_cxx.package_id;
- offset.boolean_offset = offset_cxx.boolean_offset;
- return offset;
-}
-
-/// Get flag offset
-FlagOffsetQuery get_flag_offset(
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name) {
- auto offset_cxx = get_flag_offset_cxx(
- rust::Str(container.c_str()),
- package_id,
- rust::Str(flag_name.c_str()));
- auto offset = FlagOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.flag_exists = offset_cxx.flag_exists;
- offset.flag_offset = offset_cxx.flag_offset;
- return offset;
-}
-
-/// Get boolean flag value
-BooleanFlagValueQuery get_boolean_flag_value(
- std::string const& container,
- uint32_t offset) {
- auto value_cxx = get_boolean_flag_value_cxx(
- rust::Str(container.c_str()),
- offset);
- auto value = BooleanFlagValueQuery();
- value.query_success = value_cxx.query_success;
- value.error_message = std::string(value_cxx.error_message.c_str());
- value.flag_value = value_cxx.flag_value;
- return value;
-}
-
-namespace test_only_api {
-PackageOffsetQuery get_package_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- std::string const& package) {
- auto offset_cxx = get_package_offset_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- rust::Str(package.c_str()));
- auto offset = PackageOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.package_exists = offset_cxx.package_exists;
- offset.package_id = offset_cxx.package_id;
- offset.boolean_offset = offset_cxx.boolean_offset;
- return offset;
-}
-
-FlagOffsetQuery get_flag_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name) {
- auto offset_cxx = get_flag_offset_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- package_id,
- rust::Str(flag_name.c_str()));
- auto offset = FlagOffsetQuery();
- offset.query_success = offset_cxx.query_success;
- offset.error_message = std::string(offset_cxx.error_message.c_str());
- offset.flag_exists = offset_cxx.flag_exists;
- offset.flag_offset = offset_cxx.flag_offset;
- return offset;
-}
-
-BooleanFlagValueQuery get_boolean_flag_value_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t offset) {
- auto value_cxx = get_boolean_flag_value_cxx_impl(
- rust::Str(pb_file.c_str()),
- rust::Str(container.c_str()),
- offset);
- auto value = BooleanFlagValueQuery();
- value.query_success = value_cxx.query_success;
- value.error_message = std::string(value_cxx.error_message.c_str());
- value.flag_value = value_cxx.flag_value;
- return value;
-}
-} // namespace test_only_api
-} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_file/build.rs b/tools/aconfig/aconfig_storage_file/build.rs
index 894b71c..1feeb60 100644
--- a/tools/aconfig/aconfig_storage_file/build.rs
+++ b/tools/aconfig/aconfig_storage_file/build.rs
@@ -14,7 +14,4 @@
.inputs(proto_files)
.cargo_out_dir("aconfig_storage_protos")
.run_from_script();
-
- let _ = cxx_build::bridge("src/lib.rs");
- println!("cargo:rerun-if-changed=src/lib.rs");
}
diff --git a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp b/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp
deleted file mode 100644
index 636fb7e..0000000
--- a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <string>
-
-namespace aconfig_storage {
-
-/// Package offset query result
-struct PackageOffsetQuery {
- bool query_success;
- std::string error_message;
- bool package_exists;
- uint32_t package_id;
- uint32_t boolean_offset;
-};
-
-/// Flag offset query result
-struct FlagOffsetQuery {
- bool query_success;
- std::string error_message;
- bool flag_exists;
- uint16_t flag_offset;
-};
-
-/// Boolean flag value query result
-struct BooleanFlagValueQuery {
- bool query_success;
- std::string error_message;
- bool flag_value;
-};
-
-/// Get package offset
-/// \input container: the flag container name
-/// \input package: the flag package name
-/// \returns a PackageOffsetQuery
-PackageOffsetQuery get_package_offset(
- std::string const& container,
- std::string const& package);
-
-/// Get flag offset
-/// \input container: the flag container name
-/// \input package_id: the flag package id obtained from package offset query
-/// \input flag_name: flag name
-/// \returns a FlagOffsetQuery
-FlagOffsetQuery get_flag_offset(
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name);
-
-/// Get boolean flag value
-/// \input container: the flag container name
-/// \input offset: the boolean flag value byte offset in the file
-/// \returns a BooleanFlagValueQuery
-BooleanFlagValueQuery get_boolean_flag_value(
- std::string const& container,
- uint32_t offset);
-
-/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE, TEST ONLY
-namespace test_only_api {
-PackageOffsetQuery get_package_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- std::string const& package);
-
-FlagOffsetQuery get_flag_offset_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t package_id,
- std::string const& flag_name);
-
-BooleanFlagValueQuery get_boolean_flag_value_impl(
- std::string const& pb_file,
- std::string const& container,
- uint32_t offset);
-} // namespace test_only_api
-} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
index c6728bd..e1c1c7f 100644
--- a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
+++ b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
@@ -26,7 +26,8 @@
optional string package_map = 3;
optional string flag_map = 4;
optional string flag_val = 5;
- optional int64 timestamp = 6;
+ optional string flag_info = 6;
+ optional int64 timestamp = 7;
}
message storage_files {
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
new file mode 100644
index 0000000..dc2a8d6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+//! flag info module defines the flag info file format and methods for serialization
+//! and deserialization
+
+use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
+use anyhow::anyhow;
+use std::fmt;
+
+/// Flag info header struct
+#[derive(PartialEq)]
+pub struct FlagInfoHeader {
+ pub version: u32,
+ pub container: String,
+ pub file_type: u8,
+ pub file_size: u32,
+ pub num_flags: u32,
+ pub boolean_flag_offset: u32,
+}
+
+/// Implement debug print trait for header
+impl fmt::Debug for FlagInfoHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Flags: {}, Boolean Flag Offset:{}",
+ self.num_flags, self.boolean_flag_offset
+ )?;
+ Ok(())
+ }
+}
+
+impl FlagInfoHeader {
+ /// Serialize to bytes
+ pub fn into_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_type.to_le_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.boolean_flag_offset.to_le_bytes());
+ result
+ }
+
+ /// Deserialize from bytes
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
+ let mut head = 0;
+ let list = Self {
+ version: read_u32_from_bytes(bytes, &mut head)?,
+ container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
+ file_size: read_u32_from_bytes(bytes, &mut head)?,
+ num_flags: read_u32_from_bytes(bytes, &mut head)?,
+ boolean_flag_offset: read_u32_from_bytes(bytes, &mut head)?,
+ };
+ if list.file_type != StorageFileType::FlagInfo as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag info file"
+ )));
+ }
+ Ok(list)
+ }
+}
+
+/// bit field for flag info
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum FlagInfoBit {
+ IsSticky = 1 << 0,
+ IsReadWrite = 1 << 1,
+ HasOverride = 1 << 2,
+}
+
+/// Flag info node struct
+#[derive(PartialEq, Clone)]
+pub struct FlagInfoNode {
+ pub attributes: u8,
+}
+
+/// Implement debug print trait for node
+impl fmt::Debug for FlagInfoNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "sticky: {}, readwrite: {}, override: {}",
+ self.attributes & (FlagInfoBit::IsSticky as u8) != 0,
+ self.attributes & (FlagInfoBit::IsReadWrite as u8) != 0,
+ self.attributes & (FlagInfoBit::HasOverride as u8) != 0,
+ )?;
+ Ok(())
+ }
+}
+
+impl FlagInfoNode {
+ /// Serialize to bytes
+ pub fn into_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.attributes.to_le_bytes());
+ result
+ }
+
+ /// Deserialize from bytes
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
+ let mut head = 0;
+ let node = Self { attributes: read_u8_from_bytes(bytes, &mut head)? };
+ Ok(node)
+ }
+
+ /// Create flag info node
+ pub fn create(is_flag_rw: bool) -> Self {
+ Self { attributes: if is_flag_rw { FlagInfoBit::IsReadWrite as u8 } else { 0u8 } }
+ }
+}
+
+/// Flag info list struct
+#[derive(PartialEq)]
+pub struct FlagInfoList {
+ pub header: FlagInfoHeader,
+ pub nodes: Vec<FlagInfoNode>,
+}
+
+/// Implement debug print trait for flag info list
+impl fmt::Debug for FlagInfoList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Nodes:")?;
+ for node in self.nodes.iter() {
+ write!(f, "{:?}", node)?;
+ }
+ Ok(())
+ }
+}
+
+impl FlagInfoList {
+ /// Serialize to bytes
+ pub fn into_bytes(&self) -> Vec<u8> {
+ [
+ self.header.into_bytes(),
+ self.nodes.iter().map(|v| v.into_bytes()).collect::<Vec<_>>().concat(),
+ ]
+ .concat()
+ }
+
+ /// Deserialize from bytes
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
+ let header = FlagInfoHeader::from_bytes(bytes)?;
+ let num_flags = header.num_flags;
+ let mut head = header.into_bytes().len();
+ let nodes = (0..num_flags)
+ .map(|_| {
+ let node = FlagInfoNode::from_bytes(&bytes[head..])?;
+ head += node.into_bytes().len();
+ Ok(node)
+ })
+ .collect::<Result<Vec<_>, AconfigStorageError>>()
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!(
+ "fail to parse flag info list: {}",
+ errmsg
+ ))
+ })?;
+ let list = Self { header, nodes };
+ Ok(list)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::create_test_flag_info_list;
+
+ #[test]
+ // this test point locks down the value list serialization
+ fn test_serialization() {
+ let flag_info_list = create_test_flag_info_list();
+
+ let header: &FlagInfoHeader = &flag_info_list.header;
+ let reinterpreted_header = FlagInfoHeader::from_bytes(&header.into_bytes());
+ assert!(reinterpreted_header.is_ok());
+ assert_eq!(header, &reinterpreted_header.unwrap());
+
+ let nodes: &Vec<FlagInfoNode> = &flag_info_list.nodes;
+ for node in nodes.iter() {
+ let reinterpreted_node = FlagInfoNode::from_bytes(&node.into_bytes()).unwrap();
+ assert_eq!(node, &reinterpreted_node);
+ }
+
+ let flag_info_bytes = flag_info_list.into_bytes();
+ let reinterpreted_info_list = FlagInfoList::from_bytes(&flag_info_bytes);
+ assert!(reinterpreted_info_list.is_ok());
+ assert_eq!(&flag_info_list, &reinterpreted_info_list.unwrap());
+ assert_eq!(flag_info_bytes.len() as u32, header.file_size);
+ }
+
+ #[test]
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let flag_info_list = create_test_flag_info_list();
+ let bytes = &flag_info_list.into_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1);
+ }
+
+ #[test]
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_info_list = create_test_flag_info_list();
+ flag_info_list.header.file_type = 123u8;
+ let error = FlagInfoList::from_bytes(&flag_info_list.into_bytes()).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!("BytesParseFail(binary file is not a flag info file)")
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_table.rs b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
index 108804e..64b90ea 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs
@@ -17,30 +17,55 @@
//! flag table module defines the flag table file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion};
-use crate::{get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes};
+use crate::{
+ get_bucket_index, read_str_from_bytes, read_u16_from_bytes, read_u32_from_bytes,
+ read_u8_from_bytes,
+};
+use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
use anyhow::anyhow;
-pub type FlagOffset = u16;
+use std::fmt;
/// Flag table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub bucket_offset: u32,
pub node_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for FlagTableHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Flags: {}, Bucket Offset:{}, Node Offset: {}",
+ self.num_flags, self.bucket_offset, self.node_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagTableHeader {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_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_type.to_le_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());
@@ -51,37 +76,57 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_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)?,
- })
+ };
+ if table.file_type != StorageFileType::FlagMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag map"
+ )));
+ }
+ Ok(table)
}
}
/// Flag table node struct
-#[derive(PartialEq, Debug, Clone)]
+#[derive(PartialEq, Clone)]
pub struct FlagTableNode {
pub package_id: u32,
pub flag_name: String,
- pub flag_type: u16,
- pub flag_id: u16,
+ pub flag_type: StoredFlagType,
+ // within package flag index of this flag type
+ pub flag_index: u16,
pub next_offset: Option<u32>,
}
+/// Implement debug print trait for node
+impl fmt::Debug for FlagTableNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Package Id: {}, Flag: {}, Type: {:?}, Index: {}, Next: {:?}",
+ self.package_id, self.flag_name, self.flag_type, self.flag_index, self.next_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagTableNode {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_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.flag_type as u16).to_le_bytes());
+ result.extend_from_slice(&self.flag_index.to_le_bytes());
result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
result
}
@@ -92,8 +137,8 @@
let 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)?,
+ flag_type: StoredFlagType::try_from(read_u16_from_bytes(bytes, &mut head)?)?,
+ flag_index: read_u16_from_bytes(bytes, &mut head)?,
next_offset: match read_u32_from_bytes(bytes, &mut head)? {
0 => None,
val => Some(val),
@@ -109,21 +154,36 @@
}
}
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagTable {
pub header: FlagTableHeader,
pub buckets: Vec<Option<u32>>,
pub nodes: Vec<FlagTableNode>,
}
+/// Implement debug print trait for flag table
+impl fmt::Debug for FlagTable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Buckets:")?;
+ writeln!(f, "{:?}", self.buckets)?;
+ writeln!(f, "Nodes:")?;
+ for node in self.nodes.iter() {
+ write!(f, "{:?}", node)?;
+ }
+ Ok(())
+ }
+}
+
/// Flag table struct
impl FlagTable {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_bytes(&self) -> Vec<u8> {
[
- self.header.as_bytes(),
+ self.header.into_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(),
+ self.nodes.iter().map(|v| v.into_bytes()).collect::<Vec<_>>().concat(),
]
.concat()
}
@@ -133,7 +193,7 @@
let header = FlagTableHeader::from_bytes(bytes)?;
let num_flags = header.num_flags;
let num_buckets = crate::get_table_size(num_flags)?;
- let mut head = header.as_bytes().len();
+ let mut head = header.into_bytes().len();
let buckets = (0..num_buckets)
.map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
0 => None,
@@ -143,112 +203,23 @@
let nodes = (0..num_flags)
.map(|_| {
let node = FlagTableNode::from_bytes(&bytes[head..])?;
- head += node.as_bytes().len();
+ head += node.into_bytes().len();
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!("fail to parse flag table: {}", errmsg))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
}
}
-/// Query flag within package offset
-pub fn find_flag_offset(
- buf: &[u8],
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- let interpreted_header = FlagTableHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
- let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets);
-
- let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
- let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
- if flag_node_offset < interpreted_header.node_offset as usize
- || flag_node_offset >= interpreted_header.file_size as usize
- {
- return Ok(None);
- }
-
- loop {
- let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?;
- if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag {
- return Ok(Some(interpreted_node.flag_id));
- }
- match interpreted_node.next_offset {
- Some(offset) => flag_node_offset = offset as usize,
- None => return Ok(None),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- impl FlagTableNode {
- // create test baseline, syntactic sugar
- fn new_expected(
- package_id: u32,
- flag_name: &str,
- flag_type: u16,
- flag_id: u16,
- next_offset: Option<u32>,
- ) -> Self {
- Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset }
- }
- }
-
- pub fn create_test_flag_table() -> FlagTable {
- let header = FlagTableHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 320,
- num_flags: 8,
- bucket_offset: 30,
- node_offset: 98,
- };
- let buckets: 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,
- ];
- let nodes = vec![
- FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(0, "enabled_rw", 1, 2, Some(150)),
- FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
- FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
- FlagTableNode::new_expected(1, "enabled_fixed_ro", 1, 1, Some(235)),
- FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
- FlagTableNode::new_expected(2, "enabled_fixed_ro", 1, 0, None),
- FlagTableNode::new_expected(0, "disabled_rw", 1, 0, None),
- ];
- FlagTable { header, buckets, nodes }
- }
+ use crate::test_utils::create_test_flag_table;
#[test]
// this test point locks down the table serialization
@@ -256,66 +227,43 @@
let flag_table = create_test_flag_table();
let header: &FlagTableHeader = &flag_table.header;
- let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
+ let reinterpreted_header = FlagTableHeader::from_bytes(&header.into_bytes());
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
let nodes: &Vec<FlagTableNode> = &flag_table.nodes;
for node in nodes.iter() {
- let reinterpreted_node = FlagTableNode::from_bytes(&node.as_bytes()).unwrap();
+ let reinterpreted_node = FlagTableNode::from_bytes(&node.into_bytes()).unwrap();
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = FlagTable::from_bytes(&flag_table.as_bytes());
+ let flag_table_bytes = flag_table.into_bytes();
+ let reinterpreted_table = FlagTable::from_bytes(&flag_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&flag_table, &reinterpreted_table.unwrap());
+ assert_eq!(flag_table_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down table query
- fn test_flag_query() {
- let flag_table = create_test_flag_table().as_bytes();
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
- ];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- find_flag_offset(&flag_table[..], package_id, flag_name).unwrap().unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let flag_table = create_test_flag_table();
+ let bytes = &flag_table.into_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1);
}
#[test]
- // this test point locks down table query of a non exist flag
- fn test_not_existed_flag_query() {
- let flag_table = create_test_flag_table().as_bytes();
- let flag_offset = find_flag_offset(&flag_table[..], 1, "disabled_fixed_ro").unwrap();
- assert_eq!(flag_offset, None);
- let flag_offset = find_flag_offset(&flag_table[..], 2, "disabled_rw").unwrap();
- assert_eq!(flag_offset, None);
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut table = create_test_flag_table();
- table.header.version = crate::FILE_VERSION + 1;
- let flag_table = table.as_bytes();
- let error = find_flag_offset(&flag_table[..], 0, "enabled_ro").unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_table = create_test_flag_table();
+ flag_table.header.file_type = 123u8;
+ let error = FlagTable::from_bytes(&flag_table.into_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a flag map)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_value.rs b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
index 0a6a37f..506924b 100644
--- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs
+++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs
@@ -17,28 +17,51 @@
//! flag value module defines the flag value file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, HigherStorageFileVersion, InvalidStorageFileOffset};
use crate::{read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
+use std::fmt;
/// Flag value header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagValueHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_flags: u32,
pub boolean_value_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for FlagValueHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Flags: {}, Value Offset:{}",
+ self.num_flags, self.boolean_value_offset
+ )?;
+ Ok(())
+ }
+}
+
impl FlagValueHeader {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_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_type.to_le_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.boolean_value_offset.to_le_bytes());
@@ -48,28 +71,46 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let list = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_from_bytes(bytes, &mut head)?,
file_size: read_u32_from_bytes(bytes, &mut head)?,
num_flags: read_u32_from_bytes(bytes, &mut head)?,
boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
- })
+ };
+ if list.file_type != StorageFileType::FlagVal as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a flag value file"
+ )));
+ }
+ Ok(list)
}
}
/// Flag value list struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct FlagValueList {
pub header: FlagValueHeader,
pub booleans: Vec<bool>,
}
+/// Implement debug print trait for flag value
+impl fmt::Debug for FlagValueList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Values:")?;
+ writeln!(f, "{:?}", self.booleans)?;
+ Ok(())
+ }
+}
+
impl FlagValueList {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_bytes(&self) -> Vec<u8> {
[
- self.header.as_bytes(),
+ self.header.into_bytes(),
self.booleans.iter().map(|&v| u8::from(v).to_le_bytes()).collect::<Vec<_>>().concat(),
]
.concat()
@@ -79,7 +120,7 @@
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let header = FlagValueHeader::from_bytes(bytes)?;
let num_flags = header.num_flags;
- let mut head = header.as_bytes().len();
+ let mut head = header.into_bytes().len();
let booleans =
(0..num_flags).map(|_| read_u8_from_bytes(bytes, &mut head).unwrap() == 1).collect();
let list = Self { header, booleans };
@@ -87,46 +128,10 @@
}
}
-/// Query flag value
-pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, AconfigStorageError> {
- let interpreted_header = FlagValueHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let mut head = (interpreted_header.boolean_value_offset + flag_offset) as usize;
-
- // TODO: right now, there is only boolean flags, with more flag value types added
- // later, the end of boolean flag value section should be updated (b/322826265).
- if head >= interpreted_header.file_size as usize {
- return Err(InvalidStorageFileOffset(anyhow!(
- "Flag value offset goes beyond the end of the file."
- )));
- }
-
- let val = read_u8_from_bytes(buf, &mut head)?;
- Ok(val == 1)
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- pub fn create_test_flag_value_list() -> FlagValueList {
- let header = FlagValueHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 34,
- num_flags: 8,
- boolean_value_offset: 26,
- };
- let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true];
- FlagValueList { header, booleans }
- }
+ use crate::test_utils::create_test_flag_value_list;
#[test]
// this test point locks down the value list serialization
@@ -134,51 +139,37 @@
let flag_value_list = create_test_flag_value_list();
let header: &FlagValueHeader = &flag_value_list.header;
- let reinterpreted_header = FlagValueHeader::from_bytes(&header.as_bytes());
+ let reinterpreted_header = FlagValueHeader::from_bytes(&header.into_bytes());
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
- let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+ let flag_value_bytes = flag_value_list.into_bytes();
+ let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_bytes);
assert!(reinterpreted_value_list.is_ok());
assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+ assert_eq!(flag_value_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down flag value query
- fn test_flag_value_query() {
- let flag_value_list = create_test_flag_value_list().as_bytes();
- let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let flag_value_list = create_test_flag_value_list();
+ let bytes = &flag_value_list.into_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1);
}
#[test]
- // this test point locks down query beyond the end of boolean section
- fn test_boolean_out_of_range() {
- let flag_value_list = create_test_flag_value_list().as_bytes();
- let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut flag_value_list = create_test_flag_value_list();
+ flag_value_list.header.file_type = 123u8;
+ let error = FlagValueList::from_bytes(&flag_value_list.into_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
- );
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut value_list = create_test_flag_value_list();
- value_list.header.version = crate::FILE_VERSION + 1;
- let flag_value = value_list.as_bytes();
- let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a flag value file)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index 84e0e90..070a3cf 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -32,25 +32,28 @@
//! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
//! please refer to the g3doc go/android-flags
+pub mod flag_info;
pub mod flag_table;
pub mod flag_value;
-pub mod mapped_file;
pub mod package_table;
pub mod protos;
-
-#[cfg(test)]
-mod test_utils;
+pub mod test_utils;
use anyhow::anyhow;
+use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher;
+use std::fs::File;
use std::hash::{Hash, Hasher};
+use std::io::Read;
-pub use crate::flag_table::{FlagOffset, FlagTable, FlagTableHeader, FlagTableNode};
+pub use crate::flag_info::{FlagInfoBit, FlagInfoHeader, FlagInfoList, FlagInfoNode};
+pub use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
pub use crate::flag_value::{FlagValueHeader, FlagValueList};
-pub use crate::package_table::{PackageOffset, PackageTable, PackageTableHeader, PackageTableNode};
-pub use crate::protos::ProtoStorageFiles;
+pub use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
-use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit};
+use crate::AconfigStorageError::{
+ BytesParseFail, HashTableSizeLimit, InvalidFlagValueType, InvalidStoredFlagType,
+};
/// Storage file version
pub const FILE_VERSION: u32 = 1;
@@ -62,18 +65,16 @@
402653189, 805306457, 1610612741,
];
-/// Storage file location pb file
-pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/storage_files.pb";
-
/// Storage file type enum
#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum StorageFileSelection {
- PackageMap,
- FlagMap,
- FlagVal,
+pub enum StorageFileType {
+ PackageMap = 0,
+ FlagMap = 1,
+ FlagVal = 2,
+ FlagInfo = 3,
}
-impl TryFrom<&str> for StorageFileSelection {
+impl TryFrom<&str> for StorageFileType {
type Error = anyhow::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
@@ -81,11 +82,124 @@
"package_map" => Ok(Self::PackageMap),
"flag_map" => Ok(Self::FlagMap),
"flag_val" => Ok(Self::FlagVal),
- _ => Err(anyhow!("Invalid storage file to create")),
+ "flag_info" => Ok(Self::FlagInfo),
+ _ => Err(anyhow!(
+ "Invalid storage file type, valid types are package_map|flag_map|flag_val|flag_info"
+ )),
}
}
}
+impl TryFrom<u8> for StorageFileType {
+ type Error = anyhow::Error;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ x if x == Self::PackageMap as u8 => Ok(Self::PackageMap),
+ x if x == Self::FlagMap as u8 => Ok(Self::FlagMap),
+ x if x == Self::FlagVal as u8 => Ok(Self::FlagVal),
+ x if x == Self::FlagInfo as u8 => Ok(Self::FlagInfo),
+ _ => Err(anyhow!("Invalid storage file type")),
+ }
+ }
+}
+
+/// Flag type enum as stored by storage file
+/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StoredFlagType {
+ ReadWriteBoolean = 0,
+ ReadOnlyBoolean = 1,
+ FixedReadOnlyBoolean = 2,
+}
+
+impl TryFrom<u16> for StoredFlagType {
+ type Error = AconfigStorageError;
+
+ fn try_from(value: u16) -> Result<Self, Self::Error> {
+ match value {
+ x if x == Self::ReadWriteBoolean as u16 => Ok(Self::ReadWriteBoolean),
+ x if x == Self::ReadOnlyBoolean as u16 => Ok(Self::ReadOnlyBoolean),
+ x if x == Self::FixedReadOnlyBoolean as u16 => Ok(Self::FixedReadOnlyBoolean),
+ _ => Err(InvalidStoredFlagType(anyhow!("Invalid stored flag type"))),
+ }
+ }
+}
+
+/// Flag value type enum, one FlagValueType maps to many StoredFlagType
+/// ONLY APPEND, NEVER REMOVE FOR BACKWARD COMPATIBILITY. THE MAX IS U16
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum FlagValueType {
+ Boolean = 0,
+}
+
+impl TryFrom<StoredFlagType> for FlagValueType {
+ type Error = AconfigStorageError;
+
+ fn try_from(value: StoredFlagType) -> Result<Self, Self::Error> {
+ match value {
+ StoredFlagType::ReadWriteBoolean => Ok(Self::Boolean),
+ StoredFlagType::ReadOnlyBoolean => Ok(Self::Boolean),
+ StoredFlagType::FixedReadOnlyBoolean => Ok(Self::Boolean),
+ }
+ }
+}
+
+impl TryFrom<u16> for FlagValueType {
+ type Error = AconfigStorageError;
+
+ fn try_from(value: u16) -> Result<Self, Self::Error> {
+ match value {
+ x if x == Self::Boolean as u16 => Ok(Self::Boolean),
+ _ => Err(InvalidFlagValueType(anyhow!("Invalid flag value type"))),
+ }
+ }
+}
+
+/// Storage query api error
+#[non_exhaustive]
+#[derive(thiserror::Error, Debug)]
+pub enum AconfigStorageError {
+ #[error("failed to read the file")]
+ FileReadFail(#[source] anyhow::Error),
+
+ #[error("fail to parse protobuf")]
+ ProtobufParseFail(#[source] anyhow::Error),
+
+ #[error("storage files not found for this container")]
+ StorageFileNotFound(#[source] anyhow::Error),
+
+ #[error("fail to map storage file")]
+ MapFileFail(#[source] anyhow::Error),
+
+ #[error("fail to get mapped file")]
+ ObtainMappedFileFail(#[source] anyhow::Error),
+
+ #[error("fail to flush mapped storage file")]
+ MapFlushFail(#[source] anyhow::Error),
+
+ #[error("number of items in hash table exceed limit")]
+ HashTableSizeLimit(#[source] anyhow::Error),
+
+ #[error("failed to parse bytes into data")]
+ BytesParseFail(#[source] anyhow::Error),
+
+ #[error("cannot parse storage files with a higher version")]
+ HigherStorageFileVersion(#[source] anyhow::Error),
+
+ #[error("invalid storage file byte offset")]
+ InvalidStorageFileOffset(#[source] anyhow::Error),
+
+ #[error("failed to create file")]
+ FileCreationFail(#[source] anyhow::Error),
+
+ #[error("invalid stored flag type")]
+ InvalidStoredFlagType(#[source] anyhow::Error),
+
+ #[error("invalid flag value type")]
+ InvalidFlagValueType(#[source] anyhow::Error),
+}
+
/// 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, AconfigStorageError> {
@@ -104,7 +218,7 @@
}
/// Read and parse bytes as u8
-pub(crate) fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
+pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8, AconfigStorageError> {
let val =
u8::from_le_bytes(buf[*head..*head + 1].try_into().map_err(|errmsg| {
BytesParseFail(anyhow!("fail to parse u8 from bytes: {}", errmsg))
@@ -127,10 +241,7 @@
}
/// Read and parse bytes as u32
-pub(crate) fn read_u32_from_bytes(
- buf: &[u8],
- head: &mut usize,
-) -> Result<u32, AconfigStorageError> {
+pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32, AconfigStorageError> {
let val =
u32::from_le_bytes(buf[*head..*head + 4].try_into().map_err(|errmsg| {
BytesParseFail(anyhow!("fail to parse u32 from bytes: {}", errmsg))
@@ -151,420 +262,130 @@
Ok(val)
}
-/// Storage query api error
-#[non_exhaustive]
-#[derive(thiserror::Error, Debug)]
-pub enum AconfigStorageError {
- #[error("failed to read the file")]
- FileReadFail(#[source] anyhow::Error),
-
- #[error("fail to parse protobuf")]
- ProtobufParseFail(#[source] anyhow::Error),
-
- #[error("storage files not found for this container")]
- StorageFileNotFound(#[source] anyhow::Error),
-
- #[error("fail to map storage file")]
- MapFileFail(#[source] anyhow::Error),
-
- #[error("number of items in hash table exceed limit")]
- HashTableSizeLimit(#[source] anyhow::Error),
-
- #[error("failed to parse bytes into data")]
- BytesParseFail(#[source] anyhow::Error),
-
- #[error("cannot parse storage files with a higher version")]
- HigherStorageFileVersion(#[source] anyhow::Error),
-
- #[error("invalid storage file byte offset")]
- InvalidStorageFileOffset(#[source] anyhow::Error),
+/// Read in storage file as bytes
+pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
+ let mut file = File::open(file_path).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!(
+ "Failed to read bytes from file {}: {}",
+ file_path,
+ errmsg
+ ))
+ })?;
+ Ok(buffer)
}
-/// Get package start offset implementation
-pub fn get_package_offset_impl(
- pb_file: &str,
- container: &str,
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::PackageMap)?;
- crate::package_table::find_package_offset(&mapped_file, package)
-}
+/// List flag values from storage files
+pub fn list_flags(
+ package_map: &str,
+ flag_map: &str,
+ flag_val: &str,
+) -> Result<Vec<(String, String, StoredFlagType, bool)>, AconfigStorageError> {
+ let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
+ let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
+ let flag_value_list = FlagValueList::from_bytes(&read_file_to_bytes(flag_val)?)?;
-/// Get flag offset implementation
-pub fn get_flag_offset_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagMap)?;
- crate::flag_table::find_flag_offset(&mapped_file, package_id, flag)
-}
-
-/// Get boolean flag value implementation
-pub fn get_boolean_flag_value_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
-) -> Result<bool, AconfigStorageError> {
- let mapped_file =
- crate::mapped_file::get_mapped_file(pb_file, container, StorageFileSelection::FlagVal)?;
- crate::flag_value::find_boolean_flag_value(&mapped_file, offset)
-}
-
-/// Get package start offset for flags given the container and package name.
-///
-/// This function would map the corresponding package map file if has not been mapped yet,
-/// and then look for the target package in this mapped file.
-///
-/// If a package is found, it returns Ok(Some(PackageOffset))
-/// If a package is not found, it returns Ok(None)
-/// If errors out such as no such package map file is found, it returns an Err(errmsg)
-pub fn get_package_offset(
- container: &str,
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- get_package_offset_impl(STORAGE_LOCATION_FILE, container, package)
-}
-
-/// Get flag offset within a package given the container name, package id and flag name.
-///
-/// This function would map the corresponding flag map file if has not been mapped yet,
-/// and then look for the target flag in this mapped file.
-///
-/// If a flag is found, it returns Ok(Some(u16))
-/// If a flag is not found, it returns Ok(None)
-/// If errors out such as no such flag map file is found, it returns an Err(errmsg)
-pub fn get_flag_offset(
- container: &str,
- package_id: u32,
- flag: &str,
-) -> Result<Option<FlagOffset>, AconfigStorageError> {
- get_flag_offset_impl(STORAGE_LOCATION_FILE, container, package_id, flag)
-}
-
-/// Get the boolean flag value given the container name and flag global offset
-///
-/// This function would map the corresponding flag value file if has not been mapped yet,
-/// and then look for the target flag value at the specified offset.
-///
-/// If flag value file is successfully mapped and the provide offset is valid, it returns
-/// the boolean flag value, otherwise it returns the error message.
-pub fn get_boolean_flag_value(container: &str, offset: u32) -> Result<bool, AconfigStorageError> {
- get_boolean_flag_value_impl(STORAGE_LOCATION_FILE, container, offset)
-}
-
-#[cxx::bridge]
-mod ffi {
- // Package table query return for cc interlop
- pub struct PackageOffsetQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub package_exists: bool,
- pub package_id: u32,
- pub boolean_offset: u32,
+ let mut package_info = vec![("", 0); package_table.header.num_packages as usize];
+ for node in package_table.nodes.iter() {
+ package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index);
}
- // Flag table query return for cc interlop
- pub struct FlagOffsetQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub flag_exists: bool,
- pub flag_offset: u16,
+ let mut flags = Vec::new();
+ for node in flag_table.nodes.iter() {
+ let (package_name, package_offset) = package_info[node.package_id as usize];
+ let flag_offset = package_offset + node.flag_index as u32;
+ let flag_value = flag_value_list.booleans[flag_offset as usize];
+ flags.push((
+ String::from(package_name),
+ node.flag_name.clone(),
+ node.flag_type,
+ flag_value,
+ ));
}
- // Flag value query return for cc interlop
- pub struct BooleanFlagValueQueryCXX {
- pub query_success: bool,
- pub error_message: String,
- pub flag_value: bool,
- }
-
- // Rust export to c++
- extern "Rust" {
- pub fn get_package_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package: &str,
- ) -> PackageOffsetQueryCXX;
-
- pub fn get_flag_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
- ) -> FlagOffsetQueryCXX;
-
- pub fn get_boolean_flag_value_cxx_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
- ) -> BooleanFlagValueQueryCXX;
-
- pub fn get_package_offset_cxx(container: &str, package: &str) -> PackageOffsetQueryCXX;
-
- pub fn get_flag_offset_cxx(
- container: &str,
- package_id: u32,
- flag: &str,
- ) -> FlagOffsetQueryCXX;
-
- pub fn get_boolean_flag_value_cxx(container: &str, offset: u32)
- -> BooleanFlagValueQueryCXX;
- }
-}
-
-/// Get package start offset impl cc interlop
-pub fn get_package_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package: &str,
-) -> ffi::PackageOffsetQueryCXX {
- ffi::PackageOffsetQueryCXX::new(get_package_offset_impl(pb_file, container, package))
-}
-
-/// Get flag start offset impl cc interlop
-pub fn get_flag_offset_cxx_impl(
- pb_file: &str,
- container: &str,
- package_id: u32,
- flag: &str,
-) -> ffi::FlagOffsetQueryCXX {
- ffi::FlagOffsetQueryCXX::new(get_flag_offset_impl(pb_file, container, package_id, flag))
-}
-
-/// Get boolean flag value impl cc interlop
-pub fn get_boolean_flag_value_cxx_impl(
- pb_file: &str,
- container: &str,
- offset: u32,
-) -> ffi::BooleanFlagValueQueryCXX {
- ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value_impl(pb_file, container, offset))
-}
-
-/// Get package start offset cc interlop
-pub fn get_package_offset_cxx(container: &str, package: &str) -> ffi::PackageOffsetQueryCXX {
- ffi::PackageOffsetQueryCXX::new(get_package_offset(container, package))
-}
-
-/// Get flag start offset cc interlop
-pub fn get_flag_offset_cxx(
- container: &str,
- package_id: u32,
- flag: &str,
-) -> ffi::FlagOffsetQueryCXX {
- ffi::FlagOffsetQueryCXX::new(get_flag_offset(container, package_id, flag))
-}
-
-/// Get boolean flag value cc interlop
-pub fn get_boolean_flag_value_cxx(container: &str, offset: u32) -> ffi::BooleanFlagValueQueryCXX {
- ffi::BooleanFlagValueQueryCXX::new(get_boolean_flag_value(container, offset))
-}
-
-impl ffi::PackageOffsetQueryCXX {
- pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self {
- match offset_result {
- Ok(offset_opt) => match offset_opt {
- Some(offset) => Self {
- query_success: true,
- error_message: String::from(""),
- package_exists: true,
- package_id: offset.package_id,
- boolean_offset: offset.boolean_offset,
- },
- None => Self {
- query_success: true,
- error_message: String::from(""),
- package_exists: false,
- package_id: 0,
- boolean_offset: 0,
- },
- },
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- package_exists: false,
- package_id: 0,
- boolean_offset: 0,
- },
- }
- }
-}
-
-impl ffi::FlagOffsetQueryCXX {
- pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self {
- match offset_result {
- Ok(offset_opt) => match offset_opt {
- Some(offset) => Self {
- query_success: true,
- error_message: String::from(""),
- flag_exists: true,
- flag_offset: offset,
- },
- None => Self {
- query_success: true,
- error_message: String::from(""),
- flag_exists: false,
- flag_offset: 0,
- },
- },
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- flag_exists: false,
- flag_offset: 0,
- },
- }
- }
-}
-
-impl ffi::BooleanFlagValueQueryCXX {
- pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
- match value_result {
- Ok(value) => {
- Self { query_success: true, error_message: String::from(""), flag_value: value }
- }
- Err(errmsg) => Self {
- query_success: false,
- error_message: format!("{:?}", errmsg),
- flag_value: false,
- },
- }
- }
+ flags.sort_by(|v1, v2| match v1.0.cmp(&v2.0) {
+ Ordering::Equal => v1.1.cmp(&v2.1),
+ other => other,
+ });
+ Ok(flags)
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet};
-
- fn create_test_storage_files(read_only: bool) -> TestStorageFileSet {
- TestStorageFileSet::new(
- "./tests/package.map",
- "./tests/flag.map",
- "./tests/flag.val",
- read_only,
- )
- .unwrap()
- }
+ use crate::test_utils::{
+ create_test_flag_table, create_test_flag_value_list, create_test_package_table,
+ write_bytes_to_temp_file,
+ };
#[test]
- // this test point locks down flag package offset query
- fn test_package_offset_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
+ // this test point locks down the flag list api
+ fn test_list_flag() {
+ let package_table =
+ write_bytes_to_temp_file(&create_test_package_table().into_bytes()).unwrap();
+ let flag_table = write_bytes_to_temp_file(&create_test_flag_table().into_bytes()).unwrap();
+ let flag_value_list =
+ write_bytes_to_temp_file(&create_test_flag_value_list().into_bytes()).unwrap();
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
+ let package_table_path = package_table.path().display().to_string();
+ let flag_table_path = flag_table.path().display().to_string();
+ let flag_value_list_path = flag_value_list.path().display().to_string();
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_2",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_4",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
- }
-
- #[test]
- // this test point locks down flag offset query
- fn test_flag_offset_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
+ let flags =
+ list_flags(&package_table_path, &flag_table_path, &flag_value_list_path).unwrap();
+ let expected = [
+ (
+ String::from("com.android.aconfig.storage.test_1"),
+ String::from("disabled_rw"),
+ StoredFlagType::ReadWriteBoolean,
+ false,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_1"),
+ String::from("enabled_ro"),
+ StoredFlagType::ReadOnlyBoolean,
+ true,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_1"),
+ String::from("enabled_rw"),
+ StoredFlagType::ReadWriteBoolean,
+ true,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_2"),
+ String::from("disabled_ro"),
+ StoredFlagType::ReadOnlyBoolean,
+ false,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_2"),
+ String::from("enabled_fixed_ro"),
+ StoredFlagType::FixedReadOnlyBoolean,
+ true,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_2"),
+ String::from("enabled_ro"),
+ StoredFlagType::ReadOnlyBoolean,
+ true,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_4"),
+ String::from("enabled_fixed_ro"),
+ StoredFlagType::FixedReadOnlyBoolean,
+ true,
+ ),
+ (
+ String::from("com.android.aconfig.storage.test_4"),
+ String::from("enabled_ro"),
+ StoredFlagType::ReadOnlyBoolean,
+ true,
+ ),
];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- get_flag_offset_impl(&file_full_path, "system", package_id, flag_name)
- .unwrap()
- .unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
- }
-
- #[test]
- // this test point locks down flag offset query
- fn test_flag_value_query() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let baseline: Vec<bool> = vec![false; 8];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value =
- get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
+ assert_eq!(flags, expected);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs
new file mode 100644
index 0000000..b686274
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/main.rs
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+//! `aconfig-storage` is a debugging tool to parse storage files
+
+use aconfig_storage_file::{
+ list_flags, read_file_to_bytes, AconfigStorageError, FlagInfoList, FlagTable, FlagValueList,
+ PackageTable, StorageFileType,
+};
+
+use clap::{builder::ArgAction, Arg, Command};
+
+fn cli() -> Command {
+ Command::new("aconfig-storage")
+ .subcommand_required(true)
+ .subcommand(
+ Command::new("print")
+ .arg(Arg::new("file").long("file").required(true).action(ArgAction::Set))
+ .arg(
+ Arg::new("type")
+ .long("type")
+ .required(true)
+ .value_parser(|s: &str| StorageFileType::try_from(s)),
+ ),
+ )
+ .subcommand(
+ Command::new("list")
+ .arg(
+ Arg::new("package-map")
+ .long("package-map")
+ .required(true)
+ .action(ArgAction::Set),
+ )
+ .arg(Arg::new("flag-map").long("flag-map").required(true).action(ArgAction::Set))
+ .arg(Arg::new("flag-val").long("flag-val").required(true).action(ArgAction::Set)),
+ )
+}
+
+fn print_storage_file(
+ file_path: &str,
+ file_type: &StorageFileType,
+) -> Result<(), AconfigStorageError> {
+ let bytes = read_file_to_bytes(file_path)?;
+ match file_type {
+ StorageFileType::PackageMap => {
+ let package_table = PackageTable::from_bytes(&bytes)?;
+ println!("{:?}", package_table);
+ }
+ StorageFileType::FlagMap => {
+ let flag_table = FlagTable::from_bytes(&bytes)?;
+ println!("{:?}", flag_table);
+ }
+ StorageFileType::FlagVal => {
+ let flag_value = FlagValueList::from_bytes(&bytes)?;
+ println!("{:?}", flag_value);
+ }
+ StorageFileType::FlagInfo => {
+ let flag_info = FlagInfoList::from_bytes(&bytes)?;
+ println!("{:?}", flag_info);
+ }
+ }
+ Ok(())
+}
+
+fn main() -> Result<(), AconfigStorageError> {
+ let matches = cli().get_matches();
+ match matches.subcommand() {
+ Some(("print", sub_matches)) => {
+ let file_path = sub_matches.get_one::<String>("file").unwrap();
+ let file_type = sub_matches.get_one::<StorageFileType>("type").unwrap();
+ print_storage_file(file_path, file_type)?
+ }
+ Some(("list", sub_matches)) => {
+ let package_map = sub_matches.get_one::<String>("package-map").unwrap();
+ let flag_map = sub_matches.get_one::<String>("flag-map").unwrap();
+ let flag_val = sub_matches.get_one::<String>("flag-val").unwrap();
+ let flags = list_flags(package_map, flag_map, flag_val)?;
+ for (package_name, flag_name, flag_type, flag_value) in flags.iter() {
+ println!("{} {} {:?} {}", package_name, flag_name, flag_type, flag_value);
+ }
+ }
+ _ => unreachable!(),
+ }
+ Ok(())
+}
diff --git a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs b/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
deleted file mode 100644
index d8f2570..0000000
--- a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * 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 std::collections::HashMap;
-use std::fs::File;
-use std::io::{BufReader, Read};
-use std::sync::{Arc, Mutex};
-
-use anyhow::anyhow;
-use memmap2::Mmap;
-use once_cell::sync::Lazy;
-
-use crate::protos::{
- storage_files::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
-};
-use crate::AconfigStorageError::{
- self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
-};
-use crate::StorageFileSelection;
-
-/// Cache for already mapped files
-static ALL_MAPPED_FILES: Lazy<Mutex<HashMap<String, MappedStorageFileSet>>> = Lazy::new(|| {
- let mapped_files = HashMap::new();
- Mutex::new(mapped_files)
-});
-
-/// Mapped storage files for a particular container
-#[derive(Debug)]
-struct MappedStorageFileSet {
- package_map: Arc<Mmap>,
- flag_map: Arc<Mmap>,
- flag_val: Arc<Mmap>,
-}
-
-/// Find where storage files are stored for a particular container
-fn find_container_storage_location(
- location_pb_file: &str,
- container: &str,
-) -> Result<ProtoStorageFileInfo, AconfigStorageError> {
- let file = File::open(location_pb_file).map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
- })?;
- let mut reader = BufReader::new(file);
- let mut bytes = Vec::new();
- reader.read_to_end(&mut bytes).map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
- })?;
- let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
- ProtobufParseFail(anyhow!(
- "Failed to parse storage location pb file {}: {}",
- location_pb_file,
- errmsg
- ))
- })?;
- for location_info in storage_locations.files.iter() {
- if location_info.container() == container {
- return Ok(location_info.clone());
- }
- }
- Err(StorageFileNotFound(anyhow!("Storage file does not exist for {}", container)))
-}
-
-/// Verify the file is read only and then map it
-fn verify_read_only_and_map(file_path: &str) -> Result<Mmap, AconfigStorageError> {
- let file = File::open(file_path)
- .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
- let metadata = file.metadata().map_err(|errmsg| {
- FileReadFail(anyhow!("Failed to find metadata for {}: {}", file_path, errmsg))
- })?;
-
- // ensure storage file is read only
- if !metadata.permissions().readonly() {
- return Err(MapFileFail(anyhow!("fail to map non read only storage file {}", file_path)));
- }
-
- // SAFETY:
- //
- // Mmap constructors are unsafe as it would have undefined behaviors if the file
- // is modified after mapped (https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html).
- //
- // We either have to make this api unsafe or ensure that the file will not be modified
- // which means it is read only. Here in the code, we check explicitly that the file
- // being mapped must only have read permission, otherwise, error out, thus making sure
- // it is safe.
- //
- // We should remove this restriction if we need to support mmap non read only file in
- // the future (by making this api unsafe). But for now, all flags are boot stable, so
- // the boot flag file copy should be readonly.
- unsafe {
- let mapped_file = Mmap::map(&file).map_err(|errmsg| {
- MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
- })?;
- Ok(mapped_file)
- }
-}
-
-/// Map all storage files for a particular container
-fn map_container_storage_files(
- location_pb_file: &str,
- container: &str,
-) -> Result<MappedStorageFileSet, AconfigStorageError> {
- let files_location = find_container_storage_location(location_pb_file, container)?;
- let package_map = Arc::new(verify_read_only_and_map(files_location.package_map())?);
- let flag_map = Arc::new(verify_read_only_and_map(files_location.flag_map())?);
- let flag_val = Arc::new(verify_read_only_and_map(files_location.flag_val())?);
- Ok(MappedStorageFileSet { package_map, flag_map, flag_val })
-}
-
-/// Get a mapped storage file given the container and file type
-pub(crate) fn get_mapped_file(
- location_pb_file: &str,
- container: &str,
- file_selection: StorageFileSelection,
-) -> Result<Arc<Mmap>, AconfigStorageError> {
- let mut all_mapped_files = ALL_MAPPED_FILES.lock().unwrap();
- match all_mapped_files.get(container) {
- Some(mapped_files) => Ok(match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
- }),
- None => {
- let mapped_files = map_container_storage_files(location_pb_file, container)?;
- let file_ptr = match file_selection {
- StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
- StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
- StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
- };
- all_mapped_files.insert(container.to_string(), mapped_files);
- Ok(file_ptr)
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::test_utils::{write_storage_text_to_temp_file, TestStorageFileSet};
-
- #[test]
- fn test_find_storage_file_location() {
- let text_proto = r#"
-files {
- version: 0
- container: "system"
- package_map: "/system/etc/package.map"
- flag_map: "/system/etc/flag.map"
- flag_val: "/metadata/aconfig/system.val"
- timestamp: 12345
-}
-files {
- version: 1
- container: "product"
- package_map: "/product/etc/package.map"
- flag_map: "/product/etc/flag.map"
- flag_val: "/metadata/aconfig/product.val"
- timestamp: 54321
-}
-"#;
- let file = write_storage_text_to_temp_file(text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let file_info = find_container_storage_location(&file_full_path, "system").unwrap();
- assert_eq!(file_info.version(), 0);
- assert_eq!(file_info.container(), "system");
- assert_eq!(file_info.package_map(), "/system/etc/package.map");
- assert_eq!(file_info.flag_map(), "/system/etc/flag.map");
- assert_eq!(file_info.flag_val(), "/metadata/aconfig/system.val");
- assert_eq!(file_info.timestamp(), 12345);
-
- let file_info = find_container_storage_location(&file_full_path, "product").unwrap();
- assert_eq!(file_info.version(), 1);
- assert_eq!(file_info.container(), "product");
- assert_eq!(file_info.package_map(), "/product/etc/package.map");
- assert_eq!(file_info.flag_map(), "/product/etc/flag.map");
- assert_eq!(file_info.flag_val(), "/metadata/aconfig/product.val");
- assert_eq!(file_info.timestamp(), 54321);
-
- let err = find_container_storage_location(&file_full_path, "vendor").unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- fn map_and_verify(
- location_pb_file: &str,
- file_selection: StorageFileSelection,
- actual_file: &str,
- ) {
- let mut opened_file = File::open(actual_file).unwrap();
- let mut content = Vec::new();
- opened_file.read_to_end(&mut content).unwrap();
-
- let mmaped_file = get_mapped_file(location_pb_file, "system", file_selection).unwrap();
- assert_eq!(mmaped_file[..], content[..]);
- }
-
- fn create_test_storage_files(read_only: bool) -> TestStorageFileSet {
- TestStorageFileSet::new(
- "./tests/package.map",
- "./tests/flag.map",
- "./tests/flag.val",
- read_only,
- )
- .unwrap()
- }
-
- #[test]
- fn test_mapped_file_contents() {
- let ro_files = create_test_storage_files(true);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- map_and_verify(
- &file_full_path,
- StorageFileSelection::PackageMap,
- &ro_files.package_map.name,
- );
- map_and_verify(&file_full_path, StorageFileSelection::FlagMap, &ro_files.flag_map.name);
- map_and_verify(&file_full_path, StorageFileSelection::FlagVal, &ro_files.flag_val.name);
- }
-
- #[test]
- fn test_map_non_read_only_file() {
- let ro_files = create_test_storage_files(true);
- let rw_files = create_test_storage_files(false);
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- rw_files.package_map.name, ro_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.package_map.name
- )
- );
-
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, rw_files.flag_map.name, ro_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.flag_map.name
- )
- );
-
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "{}"
- flag_map: "{}"
- flag_val: "{}"
- timestamp: 12345
-}}
-"#,
- ro_files.package_map.name, ro_files.flag_map.name, rw_files.flag_val.name
- );
-
- let file = write_storage_text_to_temp_file(&text_proto).unwrap();
- let file_full_path = file.path().display().to_string();
- let error = map_container_storage_files(&file_full_path, "system").unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read only storage file {})",
- rw_files.flag_val.name
- )
- );
- }
-}
diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs
index 7308d7b..b734972 100644
--- a/tools/aconfig/aconfig_storage_file/src/package_table.rs
+++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs
@@ -17,29 +17,52 @@
//! package table module defines the package table file format and methods for serialization
//! and deserialization
-use crate::AconfigStorageError::{self, BytesParseFail, HigherStorageFileVersion};
-use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes};
+use crate::{get_bucket_index, read_str_from_bytes, read_u32_from_bytes, read_u8_from_bytes};
+use crate::{AconfigStorageError, StorageFileType};
use anyhow::anyhow;
+use std::fmt;
/// Package table header struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct PackageTableHeader {
pub version: u32,
pub container: String,
+ pub file_type: u8,
pub file_size: u32,
pub num_packages: u32,
pub bucket_offset: u32,
pub node_offset: u32,
}
+/// Implement debug print trait for header
+impl fmt::Debug for PackageTableHeader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Version: {}, Container: {}, File Type: {:?}, File Size: {}",
+ self.version,
+ self.container,
+ StorageFileType::try_from(self.file_type),
+ self.file_size
+ )?;
+ writeln!(
+ f,
+ "Num of Packages: {}, Bucket Offset:{}, Node Offset: {}",
+ self.num_packages, self.bucket_offset, self.node_offset
+ )?;
+ Ok(())
+ }
+}
+
impl PackageTableHeader {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_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_type.to_le_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());
@@ -50,37 +73,56 @@
/// Deserialize from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AconfigStorageError> {
let mut head = 0;
- Ok(Self {
+ let table = Self {
version: read_u32_from_bytes(bytes, &mut head)?,
container: read_str_from_bytes(bytes, &mut head)?,
+ file_type: read_u8_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)?,
- })
+ };
+ if table.file_type != StorageFileType::PackageMap as u8 {
+ return Err(AconfigStorageError::BytesParseFail(anyhow!(
+ "binary file is not a package map"
+ )));
+ }
+ Ok(table)
}
}
/// Package table node struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
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,
+ // The index of the first boolean flag in this aconfig package among all boolean
+ // flags in this container.
+ pub boolean_start_index: u32,
pub next_offset: Option<u32>,
}
+/// Implement debug print trait for node
+impl fmt::Debug for PackageTableNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ "Package: {}, Id: {}, Boolean flag start index: {}, Next: {:?}",
+ self.package_name, self.package_id, self.boolean_start_index, self.next_offset
+ )?;
+ Ok(())
+ }
+}
+
impl PackageTableNode {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_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.boolean_start_index.to_le_bytes());
result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
result
}
@@ -91,7 +133,7 @@
let 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)?,
+ boolean_start_index: read_u32_from_bytes(bytes, &mut head)?,
next_offset: match read_u32_from_bytes(bytes, &mut head)? {
0 => None,
val => Some(val),
@@ -109,20 +151,35 @@
}
/// Package table struct
-#[derive(PartialEq, Debug)]
+#[derive(PartialEq)]
pub struct PackageTable {
pub header: PackageTableHeader,
pub buckets: Vec<Option<u32>>,
pub nodes: Vec<PackageTableNode>,
}
+/// Implement debug print trait for package table
+impl fmt::Debug for PackageTable {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Header:")?;
+ write!(f, "{:?}", self.header)?;
+ writeln!(f, "Buckets:")?;
+ writeln!(f, "{:?}", self.buckets)?;
+ writeln!(f, "Nodes:")?;
+ for node in self.nodes.iter() {
+ write!(f, "{:?}", node)?;
+ }
+ Ok(())
+ }
+}
+
impl PackageTable {
/// Serialize to bytes
- pub fn as_bytes(&self) -> Vec<u8> {
+ pub fn into_bytes(&self) -> Vec<u8> {
[
- self.header.as_bytes(),
+ self.header.into_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(),
+ self.nodes.iter().map(|v| v.into_bytes()).collect::<Vec<_>>().concat(),
]
.concat()
}
@@ -132,7 +189,7 @@
let header = PackageTableHeader::from_bytes(bytes)?;
let num_packages = header.num_packages;
let num_buckets = crate::get_table_size(num_packages)?;
- let mut head = header.as_bytes().len();
+ let mut head = header.into_bytes().len();
let buckets = (0..num_buckets)
.map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
0 => None,
@@ -142,173 +199,69 @@
let nodes = (0..num_packages)
.map(|_| {
let node = PackageTableNode::from_bytes(&bytes[head..])?;
- head += node.as_bytes().len();
+ head += node.into_bytes().len();
Ok(node)
})
.collect::<Result<Vec<_>, AconfigStorageError>>()
- .map_err(|errmsg| BytesParseFail(anyhow!("fail to parse package table: {}", errmsg)))?;
+ .map_err(|errmsg| {
+ AconfigStorageError::BytesParseFail(anyhow!(
+ "fail to parse package table: {}",
+ errmsg
+ ))
+ })?;
let table = Self { header, buckets, nodes };
Ok(table)
}
}
-/// Package table query return
-#[derive(PartialEq, Debug)]
-pub struct PackageOffset {
- pub package_id: u32,
- pub boolean_offset: u32,
-}
-
-/// Query package id and start offset
-pub fn find_package_offset(
- buf: &[u8],
- package: &str,
-) -> Result<Option<PackageOffset>, AconfigStorageError> {
- let interpreted_header = PackageTableHeader::from_bytes(buf)?;
- if interpreted_header.version > crate::FILE_VERSION {
- return Err(HigherStorageFileVersion(anyhow!(
- "Cannot read storage file with a higher version of {} with lib version {}",
- interpreted_header.version,
- crate::FILE_VERSION
- )));
- }
-
- let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
- let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets);
-
- let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
- let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
- if package_node_offset < interpreted_header.node_offset as usize
- || package_node_offset >= interpreted_header.file_size as usize
- {
- return Ok(None);
- }
-
- loop {
- let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?;
- if interpreted_node.package_name == package {
- return Ok(Some(PackageOffset {
- package_id: interpreted_node.package_id,
- boolean_offset: interpreted_node.boolean_offset,
- }));
- }
- match interpreted_node.next_offset {
- Some(offset) => package_node_offset = offset as usize,
- None => return Ok(None),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
-
- pub fn create_test_package_table() -> PackageTable {
- let header = PackageTableHeader {
- version: crate::FILE_VERSION,
- container: String::from("system"),
- file_size: 208,
- num_packages: 3,
- bucket_offset: 30,
- node_offset: 58,
- };
- let buckets: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
- let first_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_2"),
- package_id: 1,
- boolean_offset: 3,
- next_offset: None,
- };
- let second_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_1"),
- package_id: 0,
- boolean_offset: 0,
- next_offset: Some(158),
- };
- let third_node = PackageTableNode {
- package_name: String::from("com.android.aconfig.storage.test_4"),
- package_id: 2,
- boolean_offset: 6,
- next_offset: None,
- };
- let nodes = vec![first_node, second_node, third_node];
- PackageTable { header, buckets, nodes }
- }
+ use crate::test_utils::create_test_package_table;
#[test]
// this test point locks down the table serialization
fn test_serialization() {
let package_table = create_test_package_table();
let header: &PackageTableHeader = &package_table.header;
- let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());
+ let reinterpreted_header = PackageTableHeader::from_bytes(&header.into_bytes());
assert!(reinterpreted_header.is_ok());
assert_eq!(header, &reinterpreted_header.unwrap());
let nodes: &Vec<PackageTableNode> = &package_table.nodes;
for node in nodes.iter() {
- let reinterpreted_node = PackageTableNode::from_bytes(&node.as_bytes()).unwrap();
+ let reinterpreted_node = PackageTableNode::from_bytes(&node.into_bytes()).unwrap();
assert_eq!(node, &reinterpreted_node);
}
- let reinterpreted_table = PackageTable::from_bytes(&package_table.as_bytes());
+ let package_table_bytes = package_table.into_bytes();
+ let reinterpreted_table = PackageTable::from_bytes(&package_table_bytes);
assert!(reinterpreted_table.is_ok());
assert_eq!(&package_table, &reinterpreted_table.unwrap());
+ assert_eq!(package_table_bytes.len() as u32, header.file_size);
}
#[test]
- // this test point locks down table query
- fn test_package_query() {
- let package_table = create_test_package_table().as_bytes();
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_2")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_4")
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
+ // this test point locks down that version number should be at the top of serialized
+ // bytes
+ fn test_version_number() {
+ let package_table = create_test_package_table();
+ let bytes = &package_table.into_bytes();
+ let mut head = 0;
+ let version = read_u32_from_bytes(bytes, &mut head).unwrap();
+ assert_eq!(version, 1);
}
#[test]
- // this test point locks down table query of a non exist package
- fn test_not_existed_package_query() {
- // this will land at an empty bucket
- let package_table = create_test_package_table().as_bytes();
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap();
- assert_eq!(package_offset, None);
- // this will land at the end of a linked list
- let package_offset =
- find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap();
- assert_eq!(package_offset, None);
- }
-
- #[test]
- // this test point locks down query error when file has a higher version
- fn test_higher_version_storage_file() {
- let mut table = create_test_package_table();
- table.header.version = crate::FILE_VERSION + 1;
- let package_table = table.as_bytes();
- let error = find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1")
- .unwrap_err();
+ // this test point locks down file type check
+ fn test_file_type_check() {
+ let mut package_table = create_test_package_table();
+ package_table.header.file_type = 123u8;
+ let error = PackageTable::from_bytes(&package_table.into_bytes()).unwrap_err();
assert_eq!(
format!("{:?}", error),
- format!(
- "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
- crate::FILE_VERSION + 1,
- crate::FILE_VERSION
- )
+ format!("BytesParseFail(binary file is not a package map)")
);
}
}
diff --git a/tools/aconfig/aconfig_storage_file/src/protos.rs b/tools/aconfig/aconfig_storage_file/src/protos.rs
index 37df3e1..8b86205 100644
--- a/tools/aconfig/aconfig_storage_file/src/protos.rs
+++ b/tools/aconfig/aconfig_storage_file/src/protos.rs
@@ -49,8 +49,11 @@
pub use auto_generated::*;
use anyhow::Result;
+use protobuf::Message;
+use std::io::Write;
+use tempfile::NamedTempFile;
-pub mod storage_files {
+pub mod storage_record_pb {
use super::*;
use anyhow::ensure;
@@ -80,15 +83,28 @@
}
Ok(())
}
+
+ pub fn get_binary_proto_from_text_proto(text_proto: &str) -> Result<Vec<u8>> {
+ let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
+ let mut binary_proto = Vec::new();
+ storage_files.write_to_vec(&mut binary_proto)?;
+ Ok(binary_proto)
+ }
+
+ pub fn write_proto_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
+ let bytes = get_binary_proto_from_text_proto(text_proto).unwrap();
+ let mut file = NamedTempFile::new()?;
+ let _ = file.write_all(&bytes);
+ Ok(file)
+ }
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::test_utils::get_binary_storage_proto_bytes;
#[test]
- fn test_parse_storage_files() {
+ fn test_parse_storage_record_pb() {
let text_proto = r#"
files {
version: 0
@@ -107,8 +123,9 @@
timestamp: 54321
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let storage_files = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let storage_files = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap();
assert_eq!(storage_files.files.len(), 2);
let system_file = &storage_files.files[0];
assert_eq!(system_file.version(), 0);
@@ -127,7 +144,7 @@
}
#[test]
- fn test_parse_invalid_storage_files() {
+ fn test_parse_invalid_storage_record_pb() {
let text_proto = r#"
files {
version: 0
@@ -138,8 +155,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing package map file for container system"
@@ -155,8 +173,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing flag map file for container system"
@@ -172,8 +191,9 @@
timestamp: 12345
}
"#;
- let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let err = storage_files::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
+ let binary_proto_bytes =
+ storage_record_pb::get_binary_proto_from_text_proto(text_proto).unwrap();
+ let err = storage_record_pb::try_from_binary_proto(&binary_proto_bytes).unwrap_err();
assert_eq!(
format!("{:?}", err),
"invalid storage file record: missing flag val file for container system"
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 7905d51..608563c 100644
--- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs
+++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,79 +14,141 @@
* limitations under the License.
*/
-use crate::protos::ProtoStorageFiles;
-use anyhow::Result;
-use protobuf::Message;
-use std::fs;
+use crate::flag_info::{FlagInfoHeader, FlagInfoList, FlagInfoNode};
+use crate::flag_table::{FlagTable, FlagTableHeader, FlagTableNode};
+use crate::flag_value::{FlagValueHeader, FlagValueList};
+use crate::package_table::{PackageTable, PackageTableHeader, PackageTableNode};
+use crate::{AconfigStorageError, StorageFileType, StoredFlagType};
+
+use anyhow::anyhow;
use std::io::Write;
use tempfile::NamedTempFile;
-pub(crate) fn get_binary_storage_proto_bytes(text_proto: &str) -> Result<Vec<u8>> {
- let storage_files: ProtoStorageFiles = protobuf::text_format::parse_from_str(text_proto)?;
- let mut binary_proto = Vec::new();
- storage_files.write_to_vec(&mut binary_proto)?;
- Ok(binary_proto)
+pub fn create_test_package_table() -> PackageTable {
+ let header = PackageTableHeader {
+ version: 1,
+ container: String::from("mockup"),
+ file_type: StorageFileType::PackageMap as u8,
+ file_size: 209,
+ num_packages: 3,
+ bucket_offset: 31,
+ node_offset: 59,
+ };
+ let buckets: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), None, None, None];
+ let first_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_2"),
+ package_id: 1,
+ boolean_start_index: 3,
+ next_offset: None,
+ };
+ let second_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_1"),
+ package_id: 0,
+ boolean_start_index: 0,
+ next_offset: Some(159),
+ };
+ let third_node = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_4"),
+ package_id: 2,
+ boolean_start_index: 6,
+ next_offset: None,
+ };
+ let nodes = vec![first_node, second_node, third_node];
+ PackageTable { header, buckets, nodes }
}
-pub(crate) fn write_storage_text_to_temp_file(text_proto: &str) -> Result<NamedTempFile> {
- let bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
- let mut file = NamedTempFile::new()?;
+impl FlagTableNode {
+ // create test baseline, syntactic sugar
+ fn new_expected(
+ package_id: u32,
+ flag_name: &str,
+ flag_type: u16,
+ flag_index: u16,
+ next_offset: Option<u32>,
+ ) -> Self {
+ Self {
+ package_id,
+ flag_name: flag_name.to_string(),
+ flag_type: StoredFlagType::try_from(flag_type).unwrap(),
+ flag_index,
+ next_offset,
+ }
+ }
+}
+
+pub fn create_test_flag_table() -> FlagTable {
+ let header = FlagTableHeader {
+ version: 1,
+ container: String::from("mockup"),
+ file_type: StorageFileType::FlagMap as u8,
+ file_size: 321,
+ num_flags: 8,
+ bucket_offset: 31,
+ node_offset: 99,
+ };
+ let buckets: Vec<Option<u32>> = vec![
+ Some(99),
+ Some(125),
+ None,
+ None,
+ None,
+ Some(178),
+ None,
+ Some(204),
+ None,
+ Some(262),
+ None,
+ None,
+ None,
+ None,
+ None,
+ Some(294),
+ None,
+ ];
+ let nodes = vec![
+ FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None),
+ FlagTableNode::new_expected(0, "enabled_rw", 0, 2, Some(151)),
+ FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None),
+ FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None),
+ FlagTableNode::new_expected(1, "enabled_fixed_ro", 2, 1, Some(236)),
+ FlagTableNode::new_expected(1, "enabled_ro", 1, 2, None),
+ FlagTableNode::new_expected(2, "enabled_fixed_ro", 2, 0, None),
+ FlagTableNode::new_expected(0, "disabled_rw", 0, 0, None),
+ ];
+ FlagTable { header, buckets, nodes }
+}
+
+pub fn create_test_flag_value_list() -> FlagValueList {
+ let header = FlagValueHeader {
+ version: 1,
+ container: String::from("mockup"),
+ file_type: StorageFileType::FlagVal as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_value_offset: 27,
+ };
+ let booleans: Vec<bool> = vec![false, true, true, false, true, true, true, true];
+ FlagValueList { header, booleans }
+}
+
+pub fn create_test_flag_info_list() -> FlagInfoList {
+ let header = FlagInfoHeader {
+ version: 1,
+ container: String::from("mockup"),
+ file_type: StorageFileType::FlagInfo as u8,
+ file_size: 35,
+ num_flags: 8,
+ boolean_flag_offset: 27,
+ };
+ let is_flag_rw = [true, false, true, false, false, false, false, false];
+ let nodes = is_flag_rw.iter().map(|&rw| FlagInfoNode::create(rw)).collect();
+ FlagInfoList { header, nodes }
+}
+
+pub fn write_bytes_to_temp_file(bytes: &[u8]) -> Result<NamedTempFile, AconfigStorageError> {
+ let mut file = NamedTempFile::new().map_err(|_| {
+ AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file"))
+ })?;
let _ = file.write_all(&bytes);
Ok(file)
}
-
-fn set_file_read_only(file: &NamedTempFile) {
- let mut perms = fs::metadata(file.path()).unwrap().permissions();
- if !perms.readonly() {
- perms.set_readonly(true);
- fs::set_permissions(file.path(), perms).unwrap();
- }
-}
-
-fn set_file_read_write(file: &NamedTempFile) {
- let mut perms = fs::metadata(file.path()).unwrap().permissions();
- if perms.readonly() {
- perms.set_readonly(false);
- fs::set_permissions(file.path(), perms).unwrap();
- }
-}
-
-pub(crate) struct TestStorageFile {
- pub file: NamedTempFile,
- pub name: String,
-}
-
-impl TestStorageFile {
- pub(crate) fn new(source_file: &str, read_only: bool) -> Result<Self> {
- let file = NamedTempFile::new()?;
- fs::copy(source_file, file.path())?;
- if read_only {
- set_file_read_only(&file);
- } else {
- set_file_read_write(&file);
- }
- let name = file.path().display().to_string();
- Ok(Self { file, name })
- }
-}
-
-pub(crate) struct TestStorageFileSet {
- pub package_map: TestStorageFile,
- pub flag_map: TestStorageFile,
- pub flag_val: TestStorageFile,
-}
-
-impl TestStorageFileSet {
- pub(crate) fn new(
- package_map_path: &str,
- flag_map_path: &str,
- flag_val_path: &str,
- read_only: bool,
- ) -> Result<Self> {
- Ok(Self {
- package_map: TestStorageFile::new(package_map_path, read_only)?,
- flag_map: TestStorageFile::new(flag_map_path, read_only)?,
- flag_val: TestStorageFile::new(flag_val_path, read_only)?,
- })
- }
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_file/tests/Android.bp
deleted file mode 100644
index b951273..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/Android.bp
+++ /dev/null
@@ -1,42 +0,0 @@
-rust_test {
- name: "aconfig_storage.test.rust",
- srcs: [
- "storage_lib_rust_test.rs"
- ],
- rustlibs: [
- "libanyhow",
- "libaconfig_storage_file",
- "libprotobuf",
- "libtempfile",
- ],
- data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
- ],
- test_suites: ["general-tests"],
-}
-
-cc_test {
- name: "aconfig_storage.test.cpp",
- srcs: [
- "storage_lib_cc_test.cpp",
- ],
- static_libs: [
- "libgmock",
- "libaconfig_storage_protos_cc",
- "libprotobuf-cpp-lite",
- "libaconfig_storage_cc",
- "libbase",
- "liblog",
- ],
- data: [
- ":ro.package.map",
- ":ro.flag.map",
- ":ro.flag.val",
- ],
- test_suites: [
- "device-tests",
- "general-tests",
- ],
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_file/tests/flag.map
deleted file mode 100644
index 43b6f9a..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/flag.map
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_file/tests/flag.val
deleted file mode 100644
index f39f8d3..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/flag.val
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_file/tests/package.map
deleted file mode 100644
index 8ed4767..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/package.map
+++ /dev/null
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp b/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp
deleted file mode 100644
index 7d5ba0a..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_cc_test.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.
- */
-
-#include <string>
-#include <vector>
-
-#include "aconfig_storage/aconfig_storage.hpp"
-#include <gtest/gtest.h>
-#include <protos/aconfig_storage_metadata.pb.h>
-#include <android-base/file.h>
-
-using android::aconfig_storage_metadata::storage_files;
-using ::android::base::WriteStringToFile;
-using ::aconfig_storage::test_only_api::get_package_offset_impl;
-using ::aconfig_storage::test_only_api::get_flag_offset_impl;
-using ::aconfig_storage::test_only_api::get_boolean_flag_value_impl;
-
-void write_storage_location_pb_to_file(std::string const& file_path) {
- auto const test_dir = android::base::GetExecutableDirectory();
- auto proto = storage_files();
- auto* info = proto.add_files();
- info->set_version(0);
- info->set_container("system");
- info->set_package_map(test_dir + "/tests/tmp.ro.package.map");
- info->set_flag_map(test_dir + "/tests/tmp.ro.flag.map");
- info->set_flag_val(test_dir + "/tests/tmp.ro.flag.val");
- info->set_timestamp(12345);
-
- auto content = std::string();
- proto.SerializeToString(&content);
- ASSERT_TRUE(WriteStringToFile(content, file_path))
- << "Failed to write a file: " << file_path;
-}
-
-TEST(AconfigStorageTest, test_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_1");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 0);
- ASSERT_EQ(query.boolean_offset, 0);
-
- query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_2");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 1);
- ASSERT_EQ(query.boolean_offset, 3);
-
- query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_4");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.package_exists);
- ASSERT_EQ(query.package_id, 2);
- ASSERT_EQ(query.boolean_offset, 6);
-}
-
-TEST(AconfigStorageTest, test_invalid_package_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_package_offset_impl(
- pb_file, "system", "com.android.aconfig.storage.test_3");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.package_exists);
-
- query = get_package_offset_impl(
- pb_file, "vendor", "com.android.aconfig.storage.test_1");
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-}
-
-TEST(AconfigStorageTest, test_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto baseline = std::vector<std::tuple<int, std::string, int>>{
- {0, "enabled_ro", 1},
- {0, "enabled_rw", 2},
- {1, "disabled_ro", 0},
- {2, "enabled_ro", 1},
- {1, "enabled_fixed_ro", 1},
- {1, "enabled_ro", 2},
- {2, "enabled_fixed_ro", 0},
- {0, "disabled_rw", 0},
- };
- for (auto const&[package_id, flag_name, expected_offset] : baseline) {
- auto query = get_flag_offset_impl(pb_file, "system", package_id, flag_name);
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_TRUE(query.flag_exists);
- ASSERT_EQ(query.flag_offset, expected_offset);
- }
-}
-
-TEST(AconfigStorageTest, test_invalid_flag_offset_query) {
- auto pb_file = std::string("/tmp/test_invalid_package_offset_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_flag_offset_impl(pb_file, "system", 0, "none_exist");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_exists);
-
- query = get_flag_offset_impl(pb_file, "system", 3, "enabled_ro");
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_exists);
-
- query = get_flag_offset_impl(pb_file, "vendor", 0, "enabled_ro");
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-}
-
-TEST(AconfigStorageTest, test_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
- for (int offset = 0; offset < 8; ++offset) {
- auto query = get_boolean_flag_value_impl(pb_file, "system", offset);
- ASSERT_EQ(query.error_message, std::string());
- ASSERT_TRUE(query.query_success);
- ASSERT_FALSE(query.flag_value);
- }
-}
-
-TEST(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
- auto pb_file = std::string("/tmp/test_invalid_boolean_flag_value_query.pb");
- write_storage_location_pb_to_file(pb_file);
-
- auto query = get_boolean_flag_value_impl(pb_file, "vendor", 0);
- ASSERT_EQ(query.error_message,
- std::string("StorageFileNotFound(Storage file does not exist for vendor)"));
- ASSERT_FALSE(query.query_success);
-
- query = get_boolean_flag_value_impl(pb_file, "system", 8);
- ASSERT_EQ(query.error_message,
- std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
- ASSERT_FALSE(query.query_success);
-}
diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs b/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs
deleted file mode 100644
index 9916915..0000000
--- a/tools/aconfig/aconfig_storage_file/tests/storage_lib_rust_test.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-#[cfg(not(feature = "cargo"))]
-mod aconfig_storage_rust_test {
- use aconfig_storage_file::{
- get_boolean_flag_value_impl, get_flag_offset_impl, get_package_offset_impl, PackageOffset,
- ProtoStorageFiles,
- };
- use protobuf::Message;
- use std::io::Write;
- use tempfile::NamedTempFile;
-
- fn write_storage_location_file() -> NamedTempFile {
- let text_proto = r#"
-files {
- version: 0
- container: "system"
- package_map: "./tests/tmp.ro.package.map"
- flag_map: "./tests/tmp.ro.flag.map"
- flag_val: "./tests/tmp.ro.flag.val"
- timestamp: 12345
-}
-"#;
- let storage_files: ProtoStorageFiles =
- protobuf::text_format::parse_from_str(text_proto).unwrap();
- let mut binary_proto_bytes = Vec::new();
- storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
- let mut file = NamedTempFile::new().unwrap();
- file.write_all(&binary_proto_bytes).unwrap();
- file
- }
-
- #[test]
- fn test_package_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 0, boolean_offset: 0 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_2",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 1, boolean_offset: 3 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_4",
- )
- .unwrap()
- .unwrap();
- let expected_package_offset = PackageOffset { package_id: 2, boolean_offset: 6 };
- assert_eq!(package_offset, expected_package_offset);
-
- let package_offset = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_3",
- )
- .unwrap();
- assert_eq!(package_offset, None);
- }
-
- #[test]
- fn test_invalid_package_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let package_offset_option = get_package_offset_impl(
- &file_full_path,
- "system",
- "com.android.aconfig.storage.test_3",
- )
- .unwrap();
- assert_eq!(package_offset_option, None);
-
- let err = get_package_offset_impl(
- &file_full_path,
- "vendor",
- "com.android.aconfig.storage.test_1",
- )
- .unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- #[test]
- fn test_flag_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let baseline = vec![
- (0, "enabled_ro", 1u16),
- (0, "enabled_rw", 2u16),
- (1, "disabled_ro", 0u16),
- (2, "enabled_ro", 1u16),
- (1, "enabled_fixed_ro", 1u16),
- (1, "enabled_ro", 2u16),
- (2, "enabled_fixed_ro", 0u16),
- (0, "disabled_rw", 0u16),
- ];
- for (package_id, flag_name, expected_offset) in baseline.into_iter() {
- let flag_offset =
- get_flag_offset_impl(&file_full_path, "system", package_id, flag_name)
- .unwrap()
- .unwrap();
- assert_eq!(flag_offset, expected_offset);
- }
- }
-
- #[test]
- fn test_invalid_flag_offset_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let flag_offset_option =
- get_flag_offset_impl(&file_full_path, "system", 0, "none_exist").unwrap();
- assert_eq!(flag_offset_option, None);
-
- let flag_offset_option =
- get_flag_offset_impl(&file_full_path, "system", 3, "enabled_ro").unwrap();
- assert_eq!(flag_offset_option, None);
-
- let err = get_flag_offset_impl(&file_full_path, "vendor", 0, "enabled_ro").unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
- }
-
- #[test]
- fn test_boolean_flag_value_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let baseline: Vec<bool> = vec![false; 8];
- for (offset, expected_value) in baseline.into_iter().enumerate() {
- let flag_value =
- get_boolean_flag_value_impl(&file_full_path, "system", offset as u32).unwrap();
- assert_eq!(flag_value, expected_value);
- }
- }
-
- #[test]
- fn test_invalid_boolean_flag_value_query() {
- let file = write_storage_location_file();
- let file_full_path = file.path().display().to_string();
-
- let err = get_boolean_flag_value_impl(&file_full_path, "vendor", 0u32).unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "StorageFileNotFound(Storage file does not exist for vendor)"
- );
-
- let err = get_boolean_flag_value_impl(&file_full_path, "system", 8u32).unwrap_err();
- assert_eq!(
- format!("{:?}", err),
- "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
- );
- }
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
new file mode 100644
index 0000000..3746d17
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -0,0 +1,87 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "aconfig_storage_read_api.defaults",
+ edition: "2021",
+ lints: "none",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libonce_cell",
+ "libtempfile",
+ "libmemmap2",
+ "libcxx",
+ "libthiserror",
+ "libaconfig_storage_file",
+ ],
+}
+
+rust_library {
+ name: "libaconfig_storage_read_api",
+ crate_name: "aconfig_storage_read_api",
+ host_supported: true,
+ defaults: ["aconfig_storage_read_api.defaults"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ min_sdk_version: "29",
+}
+
+rust_test_host {
+ name: "aconfig_storage_read_api.test",
+ test_suites: ["general-tests"],
+ defaults: ["aconfig_storage_read_api.defaults"],
+ data: [
+ "tests/package.map",
+ "tests/flag.map",
+ "tests/flag.val",
+ "tests/flag.info",
+ ],
+}
+
+// cxx source codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_read_api_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.cc"],
+}
+
+// cxx header codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_read_api_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.h"],
+}
+
+// a static cc lib based on generated code
+rust_ffi_static {
+ name: "libaconfig_storage_read_api_cxx_bridge",
+ crate_name: "aconfig_storage_read_api_cxx_bridge",
+ host_supported: true,
+ defaults: ["aconfig_storage_read_api.defaults"],
+}
+
+// flag read api cc interface
+cc_library_static {
+ name: "libaconfig_storage_read_api_cc",
+ srcs: ["aconfig_storage_read_api.cpp"],
+ generated_headers: [
+ "cxx-bridge-header",
+ "libcxx_aconfig_storage_read_api_bridge_header",
+ ],
+ generated_sources: ["libcxx_aconfig_storage_read_api_bridge_code"],
+ whole_static_libs: ["libaconfig_storage_read_api_cxx_bridge"],
+ export_include_dirs: ["include"],
+ static_libs: [
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/Cargo.toml b/tools/aconfig/aconfig_storage_read_api/Cargo.toml
new file mode 100644
index 0000000..30a4298
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "aconfig_storage_read_api"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+memmap2 = "0.8.0"
+once_cell = "1.19.0"
+tempfile = "3.9.0"
+cxx = "1.0"
+thiserror = "1.0.56"
+aconfig_storage_file = { path = "../aconfig_storage_file" }
+
+[build-dependencies]
+protobuf-codegen = "3.2.0"
+cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
new file mode 100644
index 0000000..ff2f38e
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp
@@ -0,0 +1,208 @@
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "rust/cxx.h"
+#include "aconfig_storage/lib.rs.h"
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+
+using storage_records_pb = android::aconfig_storage_metadata::storage_files;
+using storage_record_pb = android::aconfig_storage_metadata::storage_file_info;
+using namespace android::base;
+
+namespace aconfig_storage {
+
+/// Storage location pb file
+static constexpr char kAvailableStorageRecordsPb[] =
+ "/metadata/aconfig/boot/available_storage_file_records.pb";
+
+/// Read aconfig storage records pb file
+static Result<storage_records_pb> read_storage_records_pb(std::string const& pb_file) {
+ auto records = storage_records_pb();
+ auto content = std::string();
+ if (!ReadFileToString(pb_file, &content)) {
+ return ErrnoError() << "ReadFileToString failed";
+ }
+
+ if (!records.ParseFromString(content)) {
+ return ErrnoError() << "Unable to parse persistent storage records protobuf";
+ }
+ return records;
+}
+
+/// Get storage file path
+static Result<std::string> find_storage_file(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ auto records_pb = read_storage_records_pb(pb_file);
+ if (!records_pb.ok()) {
+ return Error() << "Unable to read storage records from " << pb_file
+ << " : " << records_pb.error();
+ }
+
+ for (auto& entry : records_pb->files()) {
+ if (entry.container() == container) {
+ switch(file_type) {
+ case StorageFileType::package_map:
+ return entry.package_map();
+ case StorageFileType::flag_map:
+ return entry.flag_map();
+ case StorageFileType::flag_val:
+ return entry.flag_val();
+ case StorageFileType::flag_info:
+ return entry.flag_info();
+ default:
+ return Error() << "Invalid file type " << file_type;
+ }
+ }
+ }
+
+ return Error() << "Unable to find storage files for container " << container;;
+}
+
+/// Map a storage file
+static Result<MappedStorageFile> map_storage_file(std::string const& file) {
+ int fd = open(file.c_str(), O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
+ if (fd == -1) {
+ return ErrnoError() << "failed to open " << file;
+ };
+
+ struct stat fd_stat;
+ if (fstat(fd, &fd_stat) < 0) {
+ return ErrnoError() << "fstat failed";
+ }
+ size_t file_size = fd_stat.st_size;
+
+ void* const map_result = mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (map_result == MAP_FAILED) {
+ return ErrnoError() << "mmap failed";
+ }
+
+ auto mapped_file = MappedStorageFile();
+ mapped_file.file_ptr = map_result;
+ mapped_file.file_size = file_size;
+
+ return mapped_file;
+}
+
+namespace private_internal_api {
+
+/// Get mapped file implementation.
+Result<MappedStorageFile> get_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ auto file_result = find_storage_file(pb_file, container, file_type);
+ if (!file_result.ok()) {
+ return Error() << file_result.error();
+ }
+ return map_storage_file(*file_result);
+}
+
+} // namespace private internal api
+
+/// Map from StoredFlagType to FlagValueType
+android::base::Result<FlagValueType> map_to_flag_value_type(
+ StoredFlagType stored_type) {
+ switch (stored_type) {
+ case StoredFlagType::ReadWriteBoolean:
+ case StoredFlagType::ReadOnlyBoolean:
+ case StoredFlagType::FixedReadOnlyBoolean:
+ return FlagValueType::Boolean;
+ default:
+ return Error() << "Unsupported stored flag type";
+ }
+}
+
+/// Get mapped storage file
+Result<MappedStorageFile> get_mapped_file(
+ std::string const& container,
+ StorageFileType file_type) {
+ return private_internal_api::get_mapped_file_impl(
+ kAvailableStorageRecordsPb, container, file_type);
+}
+
+/// Get storage file version number
+Result<uint32_t> get_storage_file_version(
+ std::string const& file_path) {
+ auto version_cxx = get_storage_file_version_cxx(
+ rust::Str(file_path.c_str()));
+ if (version_cxx.query_success) {
+ return version_cxx.version_number;
+ } else {
+ return Error() << version_cxx.error_message;
+ }
+}
+
+/// Get package context
+Result<PackageReadContext> get_package_read_context(
+ MappedStorageFile const& file,
+ std::string const& package) {
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto context_cxx = get_package_read_context_cxx(content, rust::Str(package.c_str()));
+ if (context_cxx.query_success) {
+ auto context = PackageReadContext();
+ context.package_exists = context_cxx.package_exists;
+ context.package_id = context_cxx.package_id;
+ context.boolean_start_index = context_cxx.boolean_start_index;
+ return context;
+ } else {
+ return Error() << context_cxx.error_message;
+ }
+}
+
+/// Get flag read context
+Result<FlagReadContext> get_flag_read_context(
+ MappedStorageFile const& file,
+ uint32_t package_id,
+ std::string const& flag_name){
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto context_cxx = get_flag_read_context_cxx(content, package_id, rust::Str(flag_name.c_str()));
+ if (context_cxx.query_success) {
+ auto context = FlagReadContext();
+ context.flag_exists = context_cxx.flag_exists;
+ context.flag_type = static_cast<StoredFlagType>(context_cxx.flag_type);
+ context.flag_index = context_cxx.flag_index;
+ return context;
+ } else {
+ return Error() << context_cxx.error_message;
+ }
+}
+
+/// Get boolean flag value
+Result<bool> get_boolean_flag_value(
+ MappedStorageFile const& file,
+ uint32_t index) {
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto value_cxx = get_boolean_flag_value_cxx(content, index);
+ if (value_cxx.query_success) {
+ return value_cxx.flag_value;
+ } else {
+ return Error() << value_cxx.error_message;
+ }
+}
+
+/// Get boolean flag attribute
+Result<uint8_t> get_flag_attribute(
+ MappedStorageFile const& file,
+ FlagValueType value_type,
+ uint32_t index) {
+ auto content = rust::Slice<const uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto info_cxx = get_flag_attribute_cxx(
+ content, static_cast<uint16_t>(value_type), index);
+ if (info_cxx.query_success) {
+ return info_cxx.flag_attribute;
+ } else {
+ return Error() << info_cxx.error_message;
+ }
+}
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_read_api/build.rs b/tools/aconfig/aconfig_storage_read_api/build.rs
new file mode 100644
index 0000000..7b1aa53
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let _ = cxx_build::bridge("src/lib.rs");
+ println!("cargo:rerun-if-changed=src/lib.rs");
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
new file mode 100644
index 0000000..7c63ef2
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include <android-base/result.h>
+
+namespace aconfig_storage {
+
+/// Storage file type enum, to be consistent with the one defined in
+/// aconfig_storage_file/src/lib.rs
+enum StorageFileType {
+ package_map,
+ flag_map,
+ flag_val,
+ flag_info
+};
+
+/// Flag type enum, to be consistent with the one defined in
+/// aconfig_storage_file/src/lib.rs
+enum StoredFlagType {
+ ReadWriteBoolean = 0,
+ ReadOnlyBoolean = 1,
+ FixedReadOnlyBoolean = 2,
+};
+
+/// Flag value type enum, to be consistent with the one defined in
+/// aconfig_storage_file/src/lib.rs
+enum FlagValueType {
+ Boolean = 0,
+};
+
+/// Flag info enum, to be consistent with the one defined in
+/// aconfig_storage_file/src/flag_info.rs
+enum FlagInfoBit {
+ IsSticky = 1<<0,
+ IsReadWrite = 1<<1,
+ HasOverride = 1<<2,
+};
+
+/// Mapped storage file
+struct MappedStorageFile {
+ void* file_ptr;
+ size_t file_size;
+};
+
+/// Package read context query result
+struct PackageReadContext {
+ bool package_exists;
+ uint32_t package_id;
+ uint32_t boolean_start_index;
+};
+
+/// Flag read context query result
+struct FlagReadContext {
+ bool flag_exists;
+ StoredFlagType flag_type;
+ uint16_t flag_index;
+};
+
+/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY
+namespace private_internal_api {
+
+android::base::Result<MappedStorageFile> get_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type);
+
+} // namespace private_internal_api
+
+/// Map from StoredFlagType to FlagValueType
+/// \input stored_type: stored flag type in the storage file
+/// \returns the flag value type enum
+android::base::Result<FlagValueType> map_to_flag_value_type(
+ StoredFlagType stored_type);
+
+/// Get mapped storage file
+/// \input container: stoarge container name
+/// \input file_type: storage file type enum
+/// \returns a MappedStorageFileQuery
+android::base::Result<MappedStorageFile> get_mapped_file(
+ std::string const& container,
+ StorageFileType file_type);
+
+/// Get storage file version number
+/// \input file_path: the path to the storage file
+/// \returns the storage file version
+android::base::Result<uint32_t> get_storage_file_version(
+ std::string const& file_path);
+
+/// Get package read context
+/// \input file: mapped storage file
+/// \input package: the flag package name
+/// \returns a package read context
+android::base::Result<PackageReadContext> get_package_read_context(
+ MappedStorageFile const& file,
+ std::string const& package);
+
+/// Get flag read context
+/// \input file: mapped storage file
+/// \input package_id: the flag package id obtained from package offset query
+/// \input flag_name: flag name
+/// \returns the flag read context
+android::base::Result<FlagReadContext> get_flag_read_context(
+ MappedStorageFile const& file,
+ uint32_t package_id,
+ std::string const& flag_name);
+
+/// Get boolean flag value
+/// \input file: mapped storage file
+/// \input index: the boolean flag index in the file
+/// \returns the boolean flag value
+android::base::Result<bool> get_boolean_flag_value(
+ MappedStorageFile const& file,
+ uint32_t index);
+
+/// Get boolean flag attribute
+/// \input file: mapped storage file
+/// \input value_type: flag value type
+/// \input index: the boolean flag index in the file
+/// \returns the boolean flag attribute
+android::base::Result<uint8_t> get_flag_attribute(
+ MappedStorageFile const& file,
+ FlagValueType value_type,
+ uint32_t index);
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_info_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_info_query.rs
new file mode 100644
index 0000000..e593418
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_info_query.rs
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+//! flag value query module defines the flag value file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{flag_info::FlagInfoHeader, read_u8_from_bytes, FlagValueType};
+use anyhow::anyhow;
+
+/// Get flag attribute bitfield
+pub fn find_flag_attribute(
+ buf: &[u8],
+ flag_type: FlagValueType,
+ flag_index: u32,
+) -> Result<u8, AconfigStorageError> {
+ let interpreted_header = FlagInfoHeader::from_bytes(buf)?;
+ if interpreted_header.version > crate::FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ // get byte offset to the flag info
+ let mut head = match flag_type {
+ FlagValueType::Boolean => (interpreted_header.boolean_flag_offset + flag_index) as usize,
+ };
+
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag info offset goes beyond the end of the file."
+ )));
+ }
+
+ let val = read_u8_from_bytes(buf, &mut head)?;
+ Ok(val)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::{test_utils::create_test_flag_info_list, FlagInfoBit};
+
+ #[test]
+ // this test point locks down query if flag is sticky
+ fn test_is_flag_sticky() {
+ let flag_info_list = create_test_flag_info_list().into_bytes();
+ for offset in 0..8 {
+ let attribute =
+ find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
+ assert_eq!((attribute & FlagInfoBit::IsSticky as u8) != 0u8, false);
+ }
+ }
+
+ #[test]
+ // this test point locks down query if flag is readwrite
+ fn test_is_flag_readwrite() {
+ let flag_info_list = create_test_flag_info_list().into_bytes();
+ let baseline: Vec<bool> = vec![true, false, true, false, false, false, false, false];
+ for offset in 0..8 {
+ let attribute =
+ find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
+ assert_eq!(
+ (attribute & FlagInfoBit::IsReadWrite as u8) != 0u8,
+ baseline[offset as usize]
+ );
+ }
+ }
+
+ #[test]
+ // this test point locks down query if flag has override
+ fn test_flag_has_override() {
+ let flag_info_list = create_test_flag_info_list().into_bytes();
+ for offset in 0..8 {
+ let attribute =
+ find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
+ assert_eq!((attribute & FlagInfoBit::HasOverride as u8) != 0u8, false);
+ }
+ }
+
+ #[test]
+ // this test point locks down query beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let flag_info_list = create_test_flag_info_list().into_bytes();
+ let error =
+ find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, 8).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "InvalidStorageFileOffset(Flag info offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut info_list = create_test_flag_info_list();
+ info_list.header.version = crate::FILE_VERSION + 1;
+ let flag_info = info_list.into_bytes();
+ let error = find_flag_attribute(&flag_info[..], FlagValueType::Boolean, 4).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
new file mode 100644
index 0000000..55fdcb7
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+//! flag table query module defines the flag table file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{
+ flag_table::FlagTableHeader, flag_table::FlagTableNode, read_u32_from_bytes, StoredFlagType,
+};
+use anyhow::anyhow;
+
+/// Flag table query return
+#[derive(PartialEq, Debug)]
+pub struct FlagReadContext {
+ pub flag_type: StoredFlagType,
+ pub flag_index: u16,
+}
+
+/// Query flag read context: flag type and within package flag index
+pub fn find_flag_read_context(
+ buf: &[u8],
+ package_id: u32,
+ flag: &str,
+) -> Result<Option<FlagReadContext>, AconfigStorageError> {
+ let interpreted_header = FlagTableHeader::from_bytes(buf)?;
+ if interpreted_header.version > crate::FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
+ let bucket_index = FlagTableNode::find_bucket_index(package_id, flag, num_buckets);
+
+ let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
+ let mut flag_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
+ if flag_node_offset < interpreted_header.node_offset as usize
+ || flag_node_offset >= interpreted_header.file_size as usize
+ {
+ return Ok(None);
+ }
+
+ loop {
+ let interpreted_node = FlagTableNode::from_bytes(&buf[flag_node_offset..])?;
+ if interpreted_node.package_id == package_id && interpreted_node.flag_name == flag {
+ return Ok(Some(FlagReadContext {
+ flag_type: interpreted_node.flag_type,
+ flag_index: interpreted_node.flag_index,
+ }));
+ }
+ match interpreted_node.next_offset {
+ Some(offset) => flag_node_offset = offset as usize,
+ None => return Ok(None),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::test_utils::create_test_flag_table;
+
+ #[test]
+ // this test point locks down table query
+ fn test_flag_query() {
+ let flag_table = create_test_flag_table().into_bytes();
+ let baseline = vec![
+ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
+ (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16),
+ (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
+ (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
+ (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
+ (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
+ ];
+ for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
+ let flag_context =
+ find_flag_read_context(&flag_table[..], package_id, flag_name).unwrap().unwrap();
+ assert_eq!(flag_context.flag_type, flag_type);
+ assert_eq!(flag_context.flag_index, flag_index);
+ }
+ }
+
+ #[test]
+ // this test point locks down table query of a non exist flag
+ fn test_not_existed_flag_query() {
+ let flag_table = create_test_flag_table().into_bytes();
+ let flag_context = find_flag_read_context(&flag_table[..], 1, "disabled_fixed_ro").unwrap();
+ assert_eq!(flag_context, None);
+ let flag_context = find_flag_read_context(&flag_table[..], 2, "disabled_rw").unwrap();
+ assert_eq!(flag_context, None);
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut table = create_test_flag_table();
+ table.header.version = crate::FILE_VERSION + 1;
+ let flag_table = table.into_bytes();
+ let error = find_flag_read_context(&flag_table[..], 0, "enabled_ro").unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
new file mode 100644
index 0000000..9d32a16
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+//! flag value query module defines the flag value file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{flag_value::FlagValueHeader, read_u8_from_bytes};
+use anyhow::anyhow;
+
+/// Query flag value
+pub fn find_boolean_flag_value(buf: &[u8], flag_index: u32) -> Result<bool, AconfigStorageError> {
+ let interpreted_header = FlagValueHeader::from_bytes(buf)?;
+ if interpreted_header.version > crate::FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ // Find byte offset to the flag value, each boolean flag cost one byte to store
+ let mut head = (interpreted_header.boolean_value_offset + flag_index) as usize;
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag value offset goes beyond the end of the file."
+ )));
+ }
+
+ let val = read_u8_from_bytes(buf, &mut head)?;
+ Ok(val == 1)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::test_utils::create_test_flag_value_list;
+
+ #[test]
+ // this test point locks down flag value query
+ fn test_flag_value_query() {
+ let flag_value_list = create_test_flag_value_list().into_bytes();
+ let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value = find_boolean_flag_value(&flag_value_list[..], offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ // this test point locks down query beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let flag_value_list = create_test_flag_value_list().into_bytes();
+ let error = find_boolean_flag_value(&flag_value_list[..], 8).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut value_list = create_test_flag_value_list();
+ value_list.header.version = crate::FILE_VERSION + 1;
+ let flag_value = value_list.into_bytes();
+ let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
new file mode 100644
index 0000000..bc09112
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -0,0 +1,525 @@
+/*
+ * 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.
+ */
+
+//! `aconfig_storage_read_api` is a crate that defines read apis to read flags from storage
+//! files. It provides four apis to interface with storage files:
+//!
+//! 1, function to get package read context
+//! pub fn get_packager_read_context(container: &str, package: &str)
+//! -> `Result<Option<PackageReadContext>>>`
+//!
+//! 2, function to get flag read context
+//! pub fn get_flag_read_context(container: &str, package_id: u32, flag: &str)
+//! -> `Result<Option<FlagReadContext>>>`
+//!
+//! 3, function to get the actual flag value given the global index (combined package and
+//! flag index).
+//! pub fn get_boolean_flag_value(container: &str, offset: u32) -> `Result<bool>`
+//!
+//! 4, function to get storage file version without mmapping the file.
+//! pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError>
+//!
+//! Note these are low level apis that are expected to be only used in auto generated flag
+//! apis. DO NOT DIRECTLY USE THESE APIS IN YOUR SOURCE CODE. For auto generated flag apis
+//! please refer to the g3doc go/android-flags
+
+pub mod flag_info_query;
+pub mod flag_table_query;
+pub mod flag_value_query;
+pub mod mapped_file;
+pub mod package_table_query;
+
+#[cfg(test)]
+mod test_utils;
+
+pub use aconfig_storage_file::{AconfigStorageError, FlagValueType, StorageFileType};
+pub use flag_table_query::FlagReadContext;
+pub use package_table_query::PackageReadContext;
+
+use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION};
+use flag_info_query::find_flag_attribute;
+use flag_table_query::find_flag_read_context;
+use flag_value_query::find_boolean_flag_value;
+use package_table_query::find_package_read_context;
+
+use anyhow::anyhow;
+use memmap2::Mmap;
+use std::fs::File;
+use std::io::Read;
+
+/// Storage file location pb file
+pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/boot/available_storage_file_records.pb";
+
+/// Get read only mapped storage files.
+///
+/// \input container: the flag package container
+/// \input file_type: stoarge file type enum
+/// \return a result of read only mapped file
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file after being mapped. Ensure no writes can happen to this file while this
+/// mapping stays alive.
+pub unsafe fn get_mapped_storage_file(
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<Mmap, AconfigStorageError> {
+ unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container, file_type) }
+}
+
+/// Get package read context for a specific package.
+///
+/// \input file: mapped package file
+/// \input package: package name
+///
+/// \return
+/// If a package is found, it returns Ok(Some(PackageReadContext))
+/// If a package is not found, it returns Ok(None)
+/// If errors out, it returns an Err(errmsg)
+pub fn get_package_read_context(
+ file: &Mmap,
+ package: &str,
+) -> Result<Option<PackageReadContext>, AconfigStorageError> {
+ find_package_read_context(file, package)
+}
+
+/// Get flag read context for a specific flag.
+///
+/// \input file: mapped flag file
+/// \input package_id: package id obtained from package mapping file
+/// \input flag: flag name
+///
+/// \return
+/// If a flag is found, it returns Ok(Some(FlagReadContext))
+/// If a flag is not found, it returns Ok(None)
+/// If errors out, it returns an Err(errmsg)
+pub fn get_flag_read_context(
+ file: &Mmap,
+ package_id: u32,
+ flag: &str,
+) -> Result<Option<FlagReadContext>, AconfigStorageError> {
+ find_flag_read_context(file, package_id, flag)
+}
+
+/// Get the boolean flag value.
+///
+/// \input file: mapped flag file
+/// \input index: boolean flag offset
+///
+/// \return
+/// If the provide offset is valid, it returns the boolean flag value, otherwise it
+/// returns the error message.
+pub fn get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError> {
+ find_boolean_flag_value(file, index)
+}
+
+/// Get storage file version number
+///
+/// This function would read the first four bytes of the file and interpret it as the
+/// version number of the file. There are unit tests in aconfig_storage_file crate to
+/// lock down that for all storage files, the first four bytes will be the version
+/// number of the storage file
+pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageError> {
+ let mut file = File::open(file_path).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+ let mut buffer = [0; 4];
+ file.read(&mut buffer).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!(
+ "Failed to read 4 bytes from file {}: {}",
+ file_path,
+ errmsg
+ ))
+ })?;
+ let mut head = 0;
+ read_u32_from_bytes(&buffer, &mut head)
+}
+
+/// Get the flag attribute.
+///
+/// \input file: mapped flag info file
+/// \input flag_type: flag value type
+/// \input flag_index: flag index
+///
+/// \return
+/// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it
+/// returns the error message.
+pub fn get_flag_attribute(
+ file: &Mmap,
+ flag_type: FlagValueType,
+ flag_index: u32,
+) -> Result<u8, AconfigStorageError> {
+ find_flag_attribute(file, flag_type, flag_index)
+}
+
+// *************************************** //
+// CC INTERLOP
+// *************************************** //
+
+// Exported rust data structure and methods, c++ code will be generated
+#[cxx::bridge]
+mod ffi {
+ // Storage file version query return for cc interlop
+ pub struct VersionNumberQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub version_number: u32,
+ }
+
+ // Package table query return for cc interlop
+ pub struct PackageReadContextQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub package_exists: bool,
+ pub package_id: u32,
+ pub boolean_start_index: u32,
+ }
+
+ // Flag table query return for cc interlop
+ pub struct FlagReadContextQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub flag_exists: bool,
+ pub flag_type: u16,
+ pub flag_index: u16,
+ }
+
+ // Flag value query return for cc interlop
+ pub struct BooleanFlagValueQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub flag_value: bool,
+ }
+
+ // Flag info query return for cc interlop
+ pub struct FlagAttributeQueryCXX {
+ pub query_success: bool,
+ pub error_message: String,
+ pub flag_attribute: u8,
+ }
+
+ // Rust export to c++
+ extern "Rust" {
+ pub fn get_storage_file_version_cxx(file_path: &str) -> VersionNumberQueryCXX;
+
+ pub fn get_package_read_context_cxx(
+ file: &[u8],
+ package: &str,
+ ) -> PackageReadContextQueryCXX;
+
+ pub fn get_flag_read_context_cxx(
+ file: &[u8],
+ package_id: u32,
+ flag: &str,
+ ) -> FlagReadContextQueryCXX;
+
+ pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX;
+
+ pub fn get_flag_attribute_cxx(
+ file: &[u8],
+ flag_type: u16,
+ flag_index: u32,
+ ) -> FlagAttributeQueryCXX;
+ }
+}
+
+/// Implement the package offset interlop return type, create from actual package offset api return type
+impl ffi::PackageReadContextQueryCXX {
+ pub(crate) fn new(
+ offset_result: Result<Option<PackageReadContext>, AconfigStorageError>,
+ ) -> Self {
+ match offset_result {
+ Ok(offset_opt) => match offset_opt {
+ Some(offset) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ package_exists: true,
+ package_id: offset.package_id,
+ boolean_start_index: offset.boolean_start_index,
+ },
+ None => Self {
+ query_success: true,
+ error_message: String::from(""),
+ package_exists: false,
+ package_id: 0,
+ boolean_start_index: 0,
+ },
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ package_exists: false,
+ package_id: 0,
+ boolean_start_index: 0,
+ },
+ }
+ }
+}
+
+/// Implement the flag offset interlop return type, create from actual flag offset api return type
+impl ffi::FlagReadContextQueryCXX {
+ pub(crate) fn new(offset_result: Result<Option<FlagReadContext>, AconfigStorageError>) -> Self {
+ match offset_result {
+ Ok(offset_opt) => match offset_opt {
+ Some(offset) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ flag_exists: true,
+ flag_type: offset.flag_type as u16,
+ flag_index: offset.flag_index,
+ },
+ None => Self {
+ query_success: true,
+ error_message: String::from(""),
+ flag_exists: false,
+ flag_type: 0u16,
+ flag_index: 0u16,
+ },
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ flag_exists: false,
+ flag_type: 0u16,
+ flag_index: 0u16,
+ },
+ }
+ }
+}
+
+/// Implement the flag value interlop return type, create from actual flag value api return type
+impl ffi::BooleanFlagValueQueryCXX {
+ pub(crate) fn new(value_result: Result<bool, AconfigStorageError>) -> Self {
+ match value_result {
+ Ok(value) => {
+ Self { query_success: true, error_message: String::from(""), flag_value: value }
+ }
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ flag_value: false,
+ },
+ }
+ }
+}
+
+/// Implement the flag info interlop return type, create from actual flag info api return type
+impl ffi::FlagAttributeQueryCXX {
+ pub(crate) fn new(info_result: Result<u8, AconfigStorageError>) -> Self {
+ match info_result {
+ Ok(info) => {
+ Self { query_success: true, error_message: String::from(""), flag_attribute: info }
+ }
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ flag_attribute: 0u8,
+ },
+ }
+ }
+}
+
+/// Implement the storage version number interlop return type, create from actual version number
+/// api return type
+impl ffi::VersionNumberQueryCXX {
+ pub(crate) fn new(version_result: Result<u32, AconfigStorageError>) -> Self {
+ match version_result {
+ Ok(version) => Self {
+ query_success: true,
+ error_message: String::from(""),
+ version_number: version,
+ },
+ Err(errmsg) => Self {
+ query_success: false,
+ error_message: format!("{:?}", errmsg),
+ version_number: 0,
+ },
+ }
+ }
+}
+
+/// Get package read context cc interlop
+pub fn get_package_read_context_cxx(file: &[u8], package: &str) -> ffi::PackageReadContextQueryCXX {
+ ffi::PackageReadContextQueryCXX::new(find_package_read_context(file, package))
+}
+
+/// Get flag read context cc interlop
+pub fn get_flag_read_context_cxx(
+ file: &[u8],
+ package_id: u32,
+ flag: &str,
+) -> ffi::FlagReadContextQueryCXX {
+ ffi::FlagReadContextQueryCXX::new(find_flag_read_context(file, package_id, flag))
+}
+
+/// Get boolean flag value cc interlop
+pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagValueQueryCXX {
+ ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset))
+}
+
+/// Get flag attribute cc interlop
+pub fn get_flag_attribute_cxx(
+ file: &[u8],
+ flag_type: u16,
+ flag_index: u32,
+) -> ffi::FlagAttributeQueryCXX {
+ match FlagValueType::try_from(flag_type) {
+ Ok(value_type) => {
+ ffi::FlagAttributeQueryCXX::new(find_flag_attribute(file, value_type, flag_index))
+ }
+ Err(errmsg) => ffi::FlagAttributeQueryCXX::new(Err(errmsg)),
+ }
+}
+
+/// Get storage version number cc interlop
+pub fn get_storage_file_version_cxx(file_path: &str) -> ffi::VersionNumberQueryCXX {
+ ffi::VersionNumberQueryCXX::new(get_storage_file_version(file_path))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::mapped_file::get_mapped_file;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_file::{FlagInfoBit, StoredFlagType};
+ use tempfile::NamedTempFile;
+
+ fn create_test_storage_files() -> [NamedTempFile; 5] {
+ let package_map = copy_to_temp_file("./tests/package.map").unwrap();
+ let flag_map = copy_to_temp_file("./tests/flag.map").unwrap();
+ let flag_val = copy_to_temp_file("./tests/flag.val").unwrap();
+ let flag_info = copy_to_temp_file("./tests/flag.info").unwrap();
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "mockup"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ flag_info: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display(),
+ flag_info.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, flag_info, pb_file]
+ }
+
+ #[test]
+ // this test point locks down flag package read context query
+ fn test_package_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let package_mapped_file = unsafe {
+ get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap()
+ };
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 };
+ assert_eq!(package_context, expected_package_context);
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 };
+ assert_eq!(package_context, expected_package_context);
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 };
+ assert_eq!(package_context, expected_package_context);
+ }
+
+ #[test]
+ // this test point locks down flag read context query
+ fn test_flag_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_mapped_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagMap).unwrap() };
+
+ let baseline = vec![
+ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
+ (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16),
+ (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
+ (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
+ (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
+ (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
+ ];
+ for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
+ let flag_context =
+ get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
+ assert_eq!(flag_context.flag_type, flag_type);
+ assert_eq!(flag_context.flag_index, flag_index);
+ }
+ }
+
+ #[test]
+ // this test point locks down flag value query
+ fn test_flag_value_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_value_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagVal).unwrap() };
+ let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ // this test point locks donw flag info query
+ fn test_flag_info_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ let flag_info_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagInfo).unwrap() };
+ let is_rw: Vec<bool> = vec![true, false, true, false, false, false, false, false];
+ for (offset, expected_value) in is_rw.into_iter().enumerate() {
+ let attribute =
+ get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
+ assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8);
+ assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
+ assert!((attribute & FlagInfoBit::HasOverride as u8) == 0u8);
+ }
+ }
+
+ #[test]
+ // this test point locks down flag storage file version number query api
+ fn test_storage_version_query() {
+ assert_eq!(get_storage_file_version("./tests/package.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./tests/flag.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./tests/flag.val").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./tests/flag.info").unwrap(), 1);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
new file mode 100644
index 0000000..3786443
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs
@@ -0,0 +1,204 @@
+/*
+ * 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 std::fs::File;
+use std::io::{BufReader, Read};
+
+use anyhow::anyhow;
+use memmap2::Mmap;
+
+use crate::AconfigStorageError::{
+ self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound,
+};
+use crate::StorageFileType;
+use aconfig_storage_file::protos::{
+ storage_record_pb::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
+};
+
+/// Find where storage files are stored for a particular container
+pub fn find_container_storage_location(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<ProtoStorageFileInfo, AconfigStorageError> {
+ let file = File::open(location_pb_file).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", location_pb_file, errmsg))
+ })?;
+ let mut reader = BufReader::new(file);
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to read file {}: {}", location_pb_file, errmsg))
+ })?;
+ let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes).map_err(|errmsg| {
+ ProtobufParseFail(anyhow!(
+ "Failed to parse storage location pb file {}: {}",
+ location_pb_file,
+ errmsg
+ ))
+ })?;
+ for location_info in storage_locations.files.iter() {
+ if location_info.container() == container {
+ return Ok(location_info.clone());
+ }
+ }
+ Err(StorageFileNotFound(anyhow!("Storage file does not exist for {}", container)))
+}
+
+/// Get the read only memory mapping of a storage file
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file after being mapped. Ensure no writes can happen to this file while this
+/// mapping stays alive.
+unsafe fn map_file(file_path: &str) -> Result<Mmap, AconfigStorageError> {
+ let file = File::open(file_path)
+ .map_err(|errmsg| FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)))?;
+ unsafe {
+ let mapped_file = Mmap::map(&file).map_err(|errmsg| {
+ MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
+ })?;
+ Ok(mapped_file)
+ }
+}
+
+/// Get a mapped storage file given the container and file type
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file after being mapped. Ensure no writes can happen to this file while this
+/// mapping stays alive.
+pub unsafe fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<Mmap, AconfigStorageError> {
+ let files_location = find_container_storage_location(location_pb_file, container)?;
+ match file_type {
+ StorageFileType::PackageMap => unsafe { map_file(files_location.package_map()) },
+ StorageFileType::FlagMap => unsafe { map_file(files_location.flag_map()) },
+ StorageFileType::FlagVal => unsafe { map_file(files_location.flag_val()) },
+ StorageFileType::FlagInfo => unsafe { map_file(files_location.flag_info()) },
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn test_find_storage_file_location() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "/system/etc/package.map"
+ flag_map: "/system/etc/flag.map"
+ flag_val: "/metadata/aconfig/system.val"
+ timestamp: 12345
+}
+files {
+ version: 1
+ container: "product"
+ package_map: "/product/etc/package.map"
+ flag_map: "/product/etc/flag.map"
+ flag_val: "/metadata/aconfig/product.val"
+ timestamp: 54321
+}
+"#;
+ let file = write_proto_to_temp_file(&text_proto).unwrap();
+ let file_full_path = file.path().display().to_string();
+ let file_info = find_container_storage_location(&file_full_path, "system").unwrap();
+ assert_eq!(file_info.version(), 0);
+ assert_eq!(file_info.container(), "system");
+ assert_eq!(file_info.package_map(), "/system/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/system/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/system.val");
+ assert_eq!(file_info.timestamp(), 12345);
+
+ let file_info = find_container_storage_location(&file_full_path, "product").unwrap();
+ assert_eq!(file_info.version(), 1);
+ assert_eq!(file_info.container(), "product");
+ assert_eq!(file_info.package_map(), "/product/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/product/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/product.val");
+ assert_eq!(file_info.timestamp(), 54321);
+
+ let err = find_container_storage_location(&file_full_path, "vendor").unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Storage file does not exist for vendor)"
+ );
+ }
+
+ fn map_and_verify(location_pb_file: &str, file_type: StorageFileType, actual_file: &str) {
+ let mut opened_file = File::open(actual_file).unwrap();
+ let mut content = Vec::new();
+ opened_file.read_to_end(&mut content).unwrap();
+
+ let mmaped_file =
+ unsafe { get_mapped_file(location_pb_file, "system", file_type).unwrap() };
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+
+ fn create_test_storage_files() -> [NamedTempFile; 4] {
+ let package_map = copy_to_temp_file("./tests/package.map").unwrap();
+ let flag_map = copy_to_temp_file("./tests/flag.map").unwrap();
+ let flag_val = copy_to_temp_file("./tests/package.map").unwrap();
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, pb_file]
+ }
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let [package_map, flag_map, flag_val, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::PackageMap,
+ &package_map.path().display().to_string(),
+ );
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::FlagMap,
+ &flag_map.path().display().to_string(),
+ );
+ map_and_verify(
+ &pb_file_path,
+ StorageFileType::FlagVal,
+ &flag_val.path().display().to_string(),
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
new file mode 100644
index 0000000..2cb854b
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+//! package table query module defines the package table file read from mapped bytes
+
+use crate::{AconfigStorageError, FILE_VERSION};
+use aconfig_storage_file::{
+ package_table::PackageTableHeader, package_table::PackageTableNode, read_u32_from_bytes,
+};
+use anyhow::anyhow;
+
+/// Package table query return
+#[derive(PartialEq, Debug)]
+pub struct PackageReadContext {
+ pub package_id: u32,
+ pub boolean_start_index: u32,
+}
+
+/// Query package read context: package id and start index
+pub fn find_package_read_context(
+ buf: &[u8],
+ package: &str,
+) -> Result<Option<PackageReadContext>, AconfigStorageError> {
+ let interpreted_header = PackageTableHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot read storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ let num_buckets = (interpreted_header.node_offset - interpreted_header.bucket_offset) / 4;
+ let bucket_index = PackageTableNode::find_bucket_index(package, num_buckets);
+
+ let mut pos = (interpreted_header.bucket_offset + 4 * bucket_index) as usize;
+ let mut package_node_offset = read_u32_from_bytes(buf, &mut pos)? as usize;
+ if package_node_offset < interpreted_header.node_offset as usize
+ || package_node_offset >= interpreted_header.file_size as usize
+ {
+ return Ok(None);
+ }
+
+ loop {
+ let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?;
+ if interpreted_node.package_name == package {
+ return Ok(Some(PackageReadContext {
+ package_id: interpreted_node.package_id,
+ boolean_start_index: interpreted_node.boolean_start_index,
+ }));
+ }
+ match interpreted_node.next_offset {
+ Some(offset) => package_node_offset = offset as usize,
+ None => return Ok(None),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::test_utils::create_test_package_table;
+
+ #[test]
+ // this test point locks down table query
+ fn test_package_query() {
+ let package_table = create_test_package_table().into_bytes();
+ let package_context =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 };
+ assert_eq!(package_context, expected_package_context);
+ let package_context =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 };
+ assert_eq!(package_context, expected_package_context);
+ let package_context =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 };
+ assert_eq!(package_context, expected_package_context);
+ }
+
+ #[test]
+ // this test point locks down table query of a non exist package
+ fn test_not_existed_package_query() {
+ // this will land at an empty bucket
+ let package_table = create_test_package_table().into_bytes();
+ let package_context =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_3")
+ .unwrap();
+ assert_eq!(package_context, None);
+ // this will land at the end of a linked list
+ let package_context =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_5")
+ .unwrap();
+ assert_eq!(package_context, None);
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut table = create_test_package_table();
+ table.header.version = crate::FILE_VERSION + 1;
+ let package_table = table.into_bytes();
+ let error =
+ find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_1")
+ .unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot read storage file with a higher version of {} with lib version {})",
+ crate::FILE_VERSION + 1,
+ crate::FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
new file mode 100644
index 0000000..84f31aa
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/src/test_utils.rs
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use anyhow::Result;
+use std::fs;
+use tempfile::NamedTempFile;
+
+/// Create temp file copy
+pub(crate) fn copy_to_temp_file(source_file: &str) -> Result<NamedTempFile> {
+ let file = NamedTempFile::new()?;
+ fs::copy(source_file, file.path())?;
+ Ok(file)
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
new file mode 100644
index 0000000..6b05ca6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
@@ -0,0 +1,45 @@
+rust_test {
+ name: "aconfig_storage_read_api.test.rust",
+ srcs: [
+ "storage_read_api_test.rs"
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ "libprotobuf",
+ "libtempfile",
+ ],
+ data: [
+ "package.map",
+ "flag.map",
+ "flag.val",
+ "flag.info",
+ ],
+ test_suites: ["general-tests"],
+}
+
+cc_test {
+ name: "aconfig_storage_read_api.test.cpp",
+ srcs: [
+ "storage_read_api_test.cpp",
+ ],
+ static_libs: [
+ "libgmock",
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libaconfig_storage_read_api_cc",
+ "libbase",
+ "liblog",
+ ],
+ data: [
+ "package.map",
+ "flag.map",
+ "flag.val",
+ "flag.info",
+ ],
+ test_suites: [
+ "device-tests",
+ "general-tests",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.info b/tools/aconfig/aconfig_storage_read_api/tests/flag.info
new file mode 100644
index 0000000..820d839
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.info
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
new file mode 100644
index 0000000..d26e00f
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
new file mode 100644
index 0000000..ed203d4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map
new file mode 100644
index 0000000..6c46a03
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
new file mode 100644
index 0000000..10f71a5
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#include <string>
+#include <vector>
+#include <cstdio>
+
+#include <sys/stat.h>
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+#include <gtest/gtest.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+using android::aconfig_storage_metadata::storage_files;
+using namespace android::base;
+
+namespace api = aconfig_storage;
+namespace private_api = aconfig_storage::private_internal_api;
+
+class AconfigStorageTest : public ::testing::Test {
+ protected:
+ Result<std::string> copy_to_temp_file(std::string const& source_file) {
+ auto temp_file = std::string(std::tmpnam(nullptr));
+ auto content = std::string();
+ if (!ReadFileToString(source_file, &content)) {
+ return Error() << "failed to read file: " << source_file;
+ }
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to copy file: " << source_file;
+ }
+ return temp_file;
+ }
+
+ Result<std::string> write_storage_location_pb_file(std::string const& package_map,
+ std::string const& flag_map,
+ std::string const& flag_val,
+ std::string const& flag_info) {
+ auto temp_file = std::tmpnam(nullptr);
+ auto proto = storage_files();
+ auto* info = proto.add_files();
+ info->set_version(0);
+ info->set_container("mockup");
+ info->set_package_map(package_map);
+ info->set_flag_map(flag_map);
+ info->set_flag_val(flag_val);
+ info->set_flag_info(flag_info);
+ info->set_timestamp(12345);
+
+ auto content = std::string();
+ proto.SerializeToString(&content);
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to write storage records pb file";
+ }
+ return temp_file;
+ }
+
+ void SetUp() override {
+ auto const test_dir = android::base::GetExecutableDirectory();
+ package_map = *copy_to_temp_file(test_dir + "/package.map");
+ flag_map = *copy_to_temp_file(test_dir + "/flag.map");
+ flag_val = *copy_to_temp_file(test_dir + "/flag.val");
+ flag_info = *copy_to_temp_file(test_dir + "/flag.info");
+ storage_record_pb = *write_storage_location_pb_file(
+ package_map, flag_map, flag_val, flag_info);
+ }
+
+ void TearDown() override {
+ std::remove(package_map.c_str());
+ std::remove(flag_map.c_str());
+ std::remove(flag_val.c_str());
+ std::remove(flag_info.c_str());
+ std::remove(storage_record_pb.c_str());
+ }
+
+ std::string package_map;
+ std::string flag_map;
+ std::string flag_val;
+ std::string flag_info;
+ std::string storage_record_pb;
+};
+
+/// Test to lock down storage file version query api
+TEST_F(AconfigStorageTest, test_storage_version_query) {
+ auto version = api::get_storage_file_version(package_map);
+ ASSERT_TRUE(version.ok());
+ ASSERT_EQ(*version, 1);
+ version = api::get_storage_file_version(flag_map);
+ ASSERT_TRUE(version.ok());
+ ASSERT_EQ(*version, 1);
+ version = api::get_storage_file_version(flag_val);
+ ASSERT_TRUE(version.ok());
+ ASSERT_EQ(*version, 1);
+ version = api::get_storage_file_version(flag_info);
+ ASSERT_TRUE(version.ok());
+ ASSERT_EQ(*version, 1);
+}
+
+/// Negative test to lock down the error when mapping none exist storage files
+TEST_F(AconfigStorageTest, test_none_exist_storage_file_mapping) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "vendor", api::StorageFileType::package_map);
+ ASSERT_FALSE(mapped_file.ok());
+ ASSERT_EQ(mapped_file.error().message(),
+ "Unable to find storage files for container vendor");
+}
+
+/// Test to lock down storage package context query api
+TEST_F(AconfigStorageTest, test_package_context_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::package_map);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto context = api::get_package_read_context(
+ *mapped_file, "com.android.aconfig.storage.test_1");
+ ASSERT_TRUE(context.ok());
+ ASSERT_TRUE(context->package_exists);
+ ASSERT_EQ(context->package_id, 0);
+ ASSERT_EQ(context->boolean_start_index, 0);
+
+ context = api::get_package_read_context(
+ *mapped_file, "com.android.aconfig.storage.test_2");
+ ASSERT_TRUE(context.ok());
+ ASSERT_TRUE(context->package_exists);
+ ASSERT_EQ(context->package_id, 1);
+ ASSERT_EQ(context->boolean_start_index, 3);
+
+ context = api::get_package_read_context(
+ *mapped_file, "com.android.aconfig.storage.test_4");
+ ASSERT_TRUE(context.ok());
+ ASSERT_TRUE(context->package_exists);
+ ASSERT_EQ(context->package_id, 2);
+ ASSERT_EQ(context->boolean_start_index, 6);
+}
+
+/// Test to lock down when querying none exist package
+TEST_F(AconfigStorageTest, test_none_existent_package_context_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::package_map);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto context = api::get_package_read_context(
+ *mapped_file, "com.android.aconfig.storage.test_3");
+ ASSERT_TRUE(context.ok());
+ ASSERT_FALSE(context->package_exists);
+}
+
+/// Test to lock down storage flag context query api
+TEST_F(AconfigStorageTest, test_flag_context_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_map);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto baseline = std::vector<std::tuple<int, std::string, api::StoredFlagType, int>>{
+ {0, "enabled_ro", api::StoredFlagType::ReadOnlyBoolean, 1},
+ {0, "enabled_rw", api::StoredFlagType::ReadWriteBoolean, 2},
+ {1, "disabled_ro", api::StoredFlagType::ReadOnlyBoolean, 0},
+ {2, "enabled_ro", api::StoredFlagType::ReadOnlyBoolean, 1},
+ {1, "enabled_fixed_ro", api::StoredFlagType::FixedReadOnlyBoolean, 1},
+ {1, "enabled_ro", api::StoredFlagType::ReadOnlyBoolean, 2},
+ {2, "enabled_fixed_ro", api::StoredFlagType::FixedReadOnlyBoolean, 0},
+ {0, "disabled_rw", api::StoredFlagType::ReadWriteBoolean, 0},
+ };
+ for (auto const&[package_id, flag_name, flag_type, flag_index] : baseline) {
+ auto context = api::get_flag_read_context(*mapped_file, package_id, flag_name);
+ ASSERT_TRUE(context.ok());
+ ASSERT_TRUE(context->flag_exists);
+ ASSERT_EQ(context->flag_type, flag_type);
+ ASSERT_EQ(context->flag_index, flag_index);
+ }
+}
+
+/// Test to lock down when querying none exist flag
+TEST_F(AconfigStorageTest, test_none_existent_flag_context_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_map);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto context = api::get_flag_read_context(*mapped_file, 0, "none_exist");
+ ASSERT_TRUE(context.ok());
+ ASSERT_FALSE(context->flag_exists);
+
+ context = api::get_flag_read_context(*mapped_file, 3, "enabled_ro");
+ ASSERT_TRUE(context.ok());
+ ASSERT_FALSE(context->flag_exists);
+}
+
+/// Test to lock down storage flag value query api
+TEST_F(AconfigStorageTest, test_boolean_flag_value_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto expected_value = std::vector<bool>{
+ false, true, true, false, true, true, true, true};
+ for (int index = 0; index < 8; ++index) {
+ auto value = api::get_boolean_flag_value(*mapped_file, index);
+ ASSERT_TRUE(value.ok());
+ ASSERT_EQ(*value, expected_value[index]);
+ }
+}
+
+/// Negative test to lock down the error when querying flag value out of range
+TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto value = api::get_boolean_flag_value(*mapped_file, 8);
+ ASSERT_FALSE(value.ok());
+ ASSERT_EQ(value.error().message(),
+ std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
+}
+
+/// Test to lock down storage flag info query api
+TEST_F(AconfigStorageTest, test_boolean_flag_info_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_info);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto expected_value = std::vector<bool>{
+ true, false, true, false, false, false, false, false};
+ for (int index = 0; index < 8; ++index) {
+ auto attribute = api::get_flag_attribute(*mapped_file, api::FlagValueType::Boolean, index);
+ ASSERT_TRUE(attribute.ok());
+ ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::IsSticky), 0);
+ ASSERT_EQ((*attribute & static_cast<uint8_t>(api::FlagInfoBit::IsReadWrite)) != 0,
+ expected_value[index]);
+ ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::HasOverride), 0);
+ }
+}
+
+/// Negative test to lock down the error when querying flag info out of range
+TEST_F(AconfigStorageTest, test_invalid_boolean_flag_info_query) {
+ auto mapped_file = private_api::get_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_info);
+ ASSERT_TRUE(mapped_file.ok());
+
+ auto attribute = api::get_flag_attribute(*mapped_file, api::FlagValueType::Boolean, 8);
+ ASSERT_FALSE(attribute.ok());
+ ASSERT_EQ(attribute.error().message(),
+ std::string("InvalidStorageFileOffset(Flag info offset goes beyond the end of the file.)"));
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
new file mode 100644
index 0000000..212f734
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.rs
@@ -0,0 +1,223 @@
+#[cfg(not(feature = "cargo"))]
+mod aconfig_storage_rust_test {
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_file::{FlagInfoBit, FlagValueType, StorageFileType, StoredFlagType};
+ use aconfig_storage_read_api::{
+ get_boolean_flag_value, get_flag_attribute, get_flag_read_context,
+ get_package_read_context, get_storage_file_version, mapped_file::get_mapped_file,
+ PackageReadContext,
+ };
+ use std::fs;
+ use tempfile::NamedTempFile;
+
+ pub fn copy_to_temp_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ file
+ }
+
+ fn create_test_storage_files() -> [NamedTempFile; 5] {
+ let package_map = copy_to_temp_file("./package.map");
+ let flag_map = copy_to_temp_file("./flag.map");
+ let flag_val = copy_to_temp_file("./flag.val");
+ let flag_info = copy_to_temp_file("./flag.info");
+
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "mockup"
+ package_map: "{}"
+ flag_map: "{}"
+ flag_val: "{}"
+ flag_info: "{}"
+ timestamp: 12345
+}}
+"#,
+ package_map.path().display(),
+ flag_map.path().display(),
+ flag_val.path().display(),
+ flag_info.path().display()
+ );
+ let pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ [package_map, flag_map, flag_val, flag_info, pb_file]
+ }
+
+ #[test]
+ fn test_unavailable_stoarge() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let err = unsafe {
+ get_mapped_file(&pb_file_path, "vendor", StorageFileType::PackageMap).unwrap_err()
+ };
+ assert_eq!(
+ format!("{:?}", err),
+ "StorageFileNotFound(Storage file does not exist for vendor)"
+ );
+ }
+
+ #[test]
+ fn test_package_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let package_mapped_file = unsafe {
+ get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap()
+ };
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_1")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 };
+ assert_eq!(package_context, expected_package_context);
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_2")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 };
+ assert_eq!(package_context, expected_package_context);
+
+ let package_context =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_4")
+ .unwrap()
+ .unwrap();
+ let expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 };
+ assert_eq!(package_context, expected_package_context);
+ }
+
+ #[test]
+ fn test_none_exist_package_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let package_mapped_file = unsafe {
+ get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap()
+ };
+
+ let package_context_option =
+ get_package_read_context(&package_mapped_file, "com.android.aconfig.storage.test_3")
+ .unwrap();
+ assert_eq!(package_context_option, None);
+ }
+
+ #[test]
+ fn test_flag_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_mapped_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagMap).unwrap() };
+
+ let baseline = vec![
+ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16),
+ (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16),
+ (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16),
+ (1, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 1u16),
+ (1, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 2u16),
+ (2, "enabled_fixed_ro", StoredFlagType::FixedReadOnlyBoolean, 0u16),
+ (0, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16),
+ ];
+ for (package_id, flag_name, flag_type, flag_index) in baseline.into_iter() {
+ let flag_context =
+ get_flag_read_context(&flag_mapped_file, package_id, flag_name).unwrap().unwrap();
+ assert_eq!(flag_context.flag_type, flag_type);
+ assert_eq!(flag_context.flag_index, flag_index);
+ }
+ }
+
+ #[test]
+ fn test_none_exist_flag_context_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_mapped_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagMap).unwrap() };
+ let flag_context_option =
+ get_flag_read_context(&flag_mapped_file, 0, "none_exist").unwrap();
+ assert_eq!(flag_context_option, None);
+
+ let flag_context_option =
+ get_flag_read_context(&flag_mapped_file, 3, "enabled_ro").unwrap();
+ assert_eq!(flag_context_option, None);
+ }
+
+ #[test]
+ fn test_boolean_flag_value_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_value_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagVal).unwrap() };
+ let baseline: Vec<bool> = vec![false, true, true, false, true, true, true, true];
+ for (offset, expected_value) in baseline.into_iter().enumerate() {
+ let flag_value = get_boolean_flag_value(&flag_value_file, offset as u32).unwrap();
+ assert_eq!(flag_value, expected_value);
+ }
+ }
+
+ #[test]
+ fn test_invalid_boolean_flag_value_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_value_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagVal).unwrap() };
+ let err = get_boolean_flag_value(&flag_value_file, 8u32).unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ fn test_flag_info_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_info_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagInfo).unwrap() };
+ let is_rw: Vec<bool> = vec![true, false, true, false, false, false, false, false];
+ for (offset, expected_value) in is_rw.into_iter().enumerate() {
+ let attribute =
+ get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
+ assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8);
+ assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
+ assert!((attribute & FlagInfoBit::HasOverride as u8) == 0u8);
+ }
+ }
+
+ #[test]
+ fn test_invalid_boolean_flag_info_query() {
+ let [_package_map, _flag_map, _flag_val, _flag_info, pb_file] = create_test_storage_files();
+ let pb_file_path = pb_file.path().display().to_string();
+ // SAFETY:
+ // The safety here is ensured as the test process will not write to temp storage file
+ let flag_info_file =
+ unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagInfo).unwrap() };
+ let err = get_flag_attribute(&flag_info_file, FlagValueType::Boolean, 8u32).unwrap_err();
+ assert_eq!(
+ format!("{:?}", err),
+ "InvalidStorageFileOffset(Flag info offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ fn test_storage_version_query() {
+ assert_eq!(get_storage_file_version("./package.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.map").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.val").unwrap(), 1);
+ assert_eq!(get_storage_file_version("./flag.info").unwrap(), 1);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Android.bp b/tools/aconfig/aconfig_storage_write_api/Android.bp
new file mode 100644
index 0000000..4dbdbbf
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Android.bp
@@ -0,0 +1,84 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "aconfig_storage_write_api.defaults",
+ edition: "2021",
+ lints: "none",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libtempfile",
+ "libmemmap2",
+ "libcxx",
+ "libthiserror",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ ],
+}
+
+rust_library {
+ name: "libaconfig_storage_write_api",
+ crate_name: "aconfig_storage_write_api",
+ host_supported: true,
+ defaults: ["aconfig_storage_write_api.defaults"],
+}
+
+rust_test_host {
+ name: "aconfig_storage_write_api.test",
+ test_suites: ["general-tests"],
+ defaults: ["aconfig_storage_write_api.defaults"],
+ data: [
+ "tests/flag.val",
+ "tests/flag.info",
+ ],
+ rustlibs: [
+ "libaconfig_storage_read_api",
+ ],
+}
+
+// cxx source codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_write_api_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.cc"],
+}
+
+// cxx header codegen from rust api
+genrule {
+ name: "libcxx_aconfig_storage_write_api_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header > $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["aconfig_storage/lib.rs.h"],
+}
+
+// a static cc lib based on generated code
+rust_ffi_static {
+ name: "libaconfig_storage_write_api_cxx_bridge",
+ crate_name: "aconfig_storage_write_api_cxx_bridge",
+ host_supported: true,
+ defaults: ["aconfig_storage_write_api.defaults"],
+}
+
+// flag write api cc interface
+cc_library_static {
+ name: "libaconfig_storage_write_api_cc",
+ srcs: ["aconfig_storage_write_api.cpp"],
+ generated_headers: [
+ "cxx-bridge-header",
+ "libcxx_aconfig_storage_write_api_bridge_header",
+ ],
+ generated_sources: ["libcxx_aconfig_storage_write_api_bridge_code"],
+ whole_static_libs: ["libaconfig_storage_write_api_cxx_bridge"],
+ export_include_dirs: ["include"],
+ static_libs: [
+ "libaconfig_storage_read_api_cc",
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libbase",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/Cargo.toml b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
new file mode 100644
index 0000000..eaa55f2
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "aconfig_storage_write_api"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+anyhow = "1.0.69"
+cxx = "1.0"
+memmap2 = "0.8.0"
+tempfile = "3.9.0"
+thiserror = "1.0.56"
+protobuf = "3.2.0"
+aconfig_storage_file = { path = "../aconfig_storage_file" }
+aconfig_storage_read_api = { path = "../aconfig_storage_read_api" }
+
+[build-dependencies]
+cxx-build = "1.0"
diff --git a/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp
new file mode 100644
index 0000000..01785e1
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp
@@ -0,0 +1,190 @@
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "rust/cxx.h"
+#include "aconfig_storage/lib.rs.h"
+#include "aconfig_storage/aconfig_storage_write_api.hpp"
+
+using storage_records_pb = android::aconfig_storage_metadata::storage_files;
+using storage_record_pb = android::aconfig_storage_metadata::storage_file_info;
+using namespace android::base;
+
+namespace aconfig_storage {
+
+/// Storage location pb file
+static constexpr char kPersistStorageRecordsPb[] =
+ "/metadata/aconfig/persistent_storage_file_records.pb";
+
+/// Read aconfig storage records pb file
+static Result<storage_records_pb> read_storage_records_pb(std::string const& pb_file) {
+ auto records = storage_records_pb();
+ auto content = std::string();
+ if (!ReadFileToString(pb_file, &content)) {
+ return ErrnoError() << "ReadFileToString failed";
+ }
+
+ if (!records.ParseFromString(content)) {
+ return ErrnoError() << "Unable to parse persistent storage records protobuf";
+ }
+ return records;
+}
+
+/// Get storage file path
+static Result<std::string> find_storage_file(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ auto records_pb = read_storage_records_pb(pb_file);
+ if (!records_pb.ok()) {
+ return Error() << "Unable to read storage records from " << pb_file
+ << " : " << records_pb.error();
+ }
+
+ for (auto& entry : records_pb->files()) {
+ if (entry.container() == container) {
+ switch(file_type) {
+ case StorageFileType::package_map:
+ return entry.package_map();
+ case StorageFileType::flag_map:
+ return entry.flag_map();
+ case StorageFileType::flag_val:
+ return entry.flag_val();
+ case StorageFileType::flag_info:
+ return entry.flag_info();
+ default:
+ return Error() << "Invalid file type " << file_type;
+ }
+ }
+ }
+
+ return Error() << "Unable to find storage files for container " << container;
+}
+
+/// Map a storage file
+static Result<MutableMappedStorageFile> map_storage_file(std::string const& file) {
+ struct stat file_stat;
+ if (stat(file.c_str(), &file_stat) < 0) {
+ return ErrnoError() << "stat failed";
+ }
+
+ if ((file_stat.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) {
+ return Error() << "cannot map nonwriteable file";
+ }
+
+ size_t file_size = file_stat.st_size;
+
+ const int fd = open(file.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC);
+ if (fd == -1) {
+ return ErrnoError() << "failed to open " << file;
+ };
+
+ void* const map_result =
+ mmap(nullptr, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (map_result == MAP_FAILED) {
+ return ErrnoError() << "mmap failed";
+ }
+
+ auto mapped_file = MutableMappedStorageFile();
+ mapped_file.file_ptr = map_result;
+ mapped_file.file_size = file_size;
+
+ return mapped_file;
+}
+
+namespace private_internal_api {
+
+/// Get mutable mapped file implementation.
+Result<MutableMappedStorageFile> get_mutable_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type) {
+ if (file_type != StorageFileType::flag_val &&
+ file_type != StorageFileType::flag_info) {
+ return Error() << "Cannot create mutable mapped file for this file type";
+ }
+
+ auto file_result = find_storage_file(pb_file, container, file_type);
+ if (!file_result.ok()) {
+ return Error() << file_result.error();
+ }
+
+ return map_storage_file(*file_result);
+}
+
+} // namespace private internal api
+
+/// Get mutable mapped file
+Result<MutableMappedStorageFile> get_mutable_mapped_file(
+ std::string const& container,
+ StorageFileType file_type) {
+ return private_internal_api::get_mutable_mapped_file_impl(
+ kPersistStorageRecordsPb, container, file_type);
+}
+
+/// Set boolean flag value
+Result<void> set_boolean_flag_value(
+ const MutableMappedStorageFile& file,
+ uint32_t offset,
+ bool value) {
+ auto content = rust::Slice<uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto update_cxx = update_boolean_flag_value_cxx(content, offset, value);
+ if (!update_cxx.update_success) {
+ return Error() << std::string(update_cxx.error_message.c_str());
+ }
+ return {};
+}
+
+/// Set if flag is sticky
+Result<void> set_flag_is_sticky(
+ const MutableMappedStorageFile& file,
+ FlagValueType value_type,
+ uint32_t offset,
+ bool value) {
+ auto content = rust::Slice<uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto update_cxx = update_flag_is_sticky_cxx(
+ content, static_cast<uint16_t>(value_type), offset, value);
+ if (!update_cxx.update_success) {
+ return Error() << std::string(update_cxx.error_message.c_str());
+ }
+ return {};
+}
+
+/// Set if flag has override
+Result<void> set_flag_has_override(
+ const MutableMappedStorageFile& file,
+ FlagValueType value_type,
+ uint32_t offset,
+ bool value) {
+ auto content = rust::Slice<uint8_t>(
+ static_cast<uint8_t*>(file.file_ptr), file.file_size);
+ auto update_cxx = update_flag_has_override_cxx(
+ content, static_cast<uint16_t>(value_type), offset, value);
+ if (!update_cxx.update_success) {
+ return Error() << std::string(update_cxx.error_message.c_str());
+ }
+ return {};
+}
+
+Result<void> create_flag_info(
+ std::string const& package_map,
+ std::string const& flag_map,
+ std::string const& flag_info_out) {
+ auto creation_cxx = create_flag_info_cxx(
+ rust::Str(package_map.c_str()),
+ rust::Str(flag_map.c_str()),
+ rust::Str(flag_info_out.c_str()));
+ if (creation_cxx.success) {
+ return {};
+ } else {
+ return android::base::Error() << creation_cxx.error_message;
+ }
+}
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_write_api/build.rs b/tools/aconfig/aconfig_storage_write_api/build.rs
new file mode 100644
index 0000000..7b1aa53
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+ let _ = cxx_build::bridge("src/lib.rs");
+ println!("cargo:rerun-if-changed=src/lib.rs");
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp b/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp
new file mode 100644
index 0000000..8699b88
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+#include <android-base/result.h>
+#include <aconfig_storage/aconfig_storage_read_api.hpp>
+
+using namespace android::base;
+
+namespace aconfig_storage {
+
+/// Mapped flag value file
+struct MutableMappedStorageFile{
+ void* file_ptr;
+ size_t file_size;
+};
+
+/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY
+namespace private_internal_api {
+
+Result<MutableMappedStorageFile> get_mutable_mapped_file_impl(
+ std::string const& pb_file,
+ std::string const& container,
+ StorageFileType file_type);
+
+} // namespace private_internal_api
+
+/// Get mapped writeable storage file
+Result<MutableMappedStorageFile> get_mutable_mapped_file(
+ std::string const& container,
+ StorageFileType file_type);
+
+/// Set boolean flag value
+Result<void> set_boolean_flag_value(
+ const MutableMappedStorageFile& file,
+ uint32_t offset,
+ bool value);
+
+/// Set if flag is sticky
+Result<void> set_flag_is_sticky(
+ const MutableMappedStorageFile& file,
+ FlagValueType value_type,
+ uint32_t offset,
+ bool value);
+
+/// Set if flag has override
+Result<void> set_flag_has_override(
+ const MutableMappedStorageFile& file,
+ FlagValueType value_type,
+ uint32_t offset,
+ bool value);
+
+/// Create flag info file based on package and flag map
+/// \input package_map: package map file
+/// \input flag_map: flag map file
+/// \input flag_info_out: flag info file to be created
+Result<void> create_flag_info(
+ std::string const& package_map,
+ std::string const& flag_map,
+ std::string const& flag_info_out);
+
+} // namespace aconfig_storage
diff --git a/tools/aconfig/aconfig_storage_write_api/src/flag_info_update.rs b/tools/aconfig/aconfig_storage_write_api/src/flag_info_update.rs
new file mode 100644
index 0000000..3f38705
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/flag_info_update.rs
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+//! flag info update module defines the flag info file write to mapped bytes
+
+use aconfig_storage_file::{
+ read_u8_from_bytes, AconfigStorageError, FlagInfoBit, FlagInfoHeader, FlagValueType,
+ FILE_VERSION,
+};
+use anyhow::anyhow;
+
+fn get_flag_info_offset(
+ buf: &mut [u8],
+ flag_type: FlagValueType,
+ flag_index: u32,
+) -> Result<usize, AconfigStorageError> {
+ let interpreted_header = FlagInfoHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot write to storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ // get byte offset to the flag info
+ let head = match flag_type {
+ FlagValueType::Boolean => (interpreted_header.boolean_flag_offset + flag_index) as usize,
+ };
+
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag value offset goes beyond the end of the file."
+ )));
+ }
+
+ Ok(head)
+}
+
+fn get_flag_attribute_and_offset(
+ buf: &mut [u8],
+ flag_type: FlagValueType,
+ flag_index: u32,
+) -> Result<(u8, usize), AconfigStorageError> {
+ let head = get_flag_info_offset(buf, flag_type, flag_index)?;
+ let mut pos = head;
+ let attribute = read_u8_from_bytes(buf, &mut pos)?;
+ Ok((attribute, head))
+}
+
+/// Set if flag is sticky
+pub fn update_flag_is_sticky(
+ buf: &mut [u8],
+ flag_type: FlagValueType,
+ flag_index: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ let (attribute, head) = get_flag_attribute_and_offset(buf, flag_type, flag_index)?;
+ let is_sticky = (attribute & (FlagInfoBit::IsSticky as u8)) != 0;
+ if is_sticky != value {
+ buf[head] = (attribute ^ FlagInfoBit::IsSticky as u8).to_le_bytes()[0];
+ }
+ Ok(())
+}
+
+/// Set if flag has override
+pub fn update_flag_has_override(
+ buf: &mut [u8],
+ flag_type: FlagValueType,
+ flag_index: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ let (attribute, head) = get_flag_attribute_and_offset(buf, flag_type, flag_index)?;
+ let has_override = (attribute & (FlagInfoBit::HasOverride as u8)) != 0;
+ if has_override != value {
+ buf[head] = (attribute ^ FlagInfoBit::HasOverride as u8).to_le_bytes()[0];
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::test_utils::create_test_flag_info_list;
+ use aconfig_storage_read_api::flag_info_query::find_flag_attribute;
+
+ #[test]
+ // this test point locks down is sticky update
+ fn test_update_flag_is_sticky() {
+ let flag_info_list = create_test_flag_info_list();
+ let mut buf = flag_info_list.into_bytes();
+ for i in 0..flag_info_list.header.num_flags {
+ update_flag_is_sticky(&mut buf, FlagValueType::Boolean, i, true).unwrap();
+ let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap();
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0);
+ update_flag_is_sticky(&mut buf, FlagValueType::Boolean, i, false).unwrap();
+ let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap();
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0);
+ }
+ }
+
+ #[test]
+ // this test point locks down has override update
+ fn test_update_flag_has_override() {
+ let flag_info_list = create_test_flag_info_list();
+ let mut buf = flag_info_list.into_bytes();
+ for i in 0..flag_info_list.header.num_flags {
+ update_flag_has_override(&mut buf, FlagValueType::Boolean, i, true).unwrap();
+ let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap();
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0);
+ update_flag_has_override(&mut buf, FlagValueType::Boolean, i, false).unwrap();
+ let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap();
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0);
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
new file mode 100644
index 0000000..0938715
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+//! flag value update module defines the flag value file write to mapped bytes
+
+use aconfig_storage_file::{AconfigStorageError, FlagValueHeader, FILE_VERSION};
+use anyhow::anyhow;
+
+/// Set flag value
+pub fn update_boolean_flag_value(
+ buf: &mut [u8],
+ flag_index: u32,
+ flag_value: bool,
+) -> Result<(), AconfigStorageError> {
+ let interpreted_header = FlagValueHeader::from_bytes(buf)?;
+ if interpreted_header.version > FILE_VERSION {
+ return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!(
+ "Cannot write to storage file with a higher version of {} with lib version {}",
+ interpreted_header.version,
+ FILE_VERSION
+ )));
+ }
+
+ // get byte offset to the flag
+ let head = (interpreted_header.boolean_value_offset + flag_index) as usize;
+ if head >= interpreted_header.file_size as usize {
+ return Err(AconfigStorageError::InvalidStorageFileOffset(anyhow!(
+ "Flag value offset goes beyond the end of the file."
+ )));
+ }
+
+ buf[head] = u8::from(flag_value).to_le_bytes()[0];
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aconfig_storage_file::test_utils::create_test_flag_value_list;
+
+ #[test]
+ // this test point locks down flag value update
+ fn test_boolean_flag_value_update() {
+ let flag_value_list = create_test_flag_value_list();
+ let value_offset = flag_value_list.header.boolean_value_offset;
+ let mut content = flag_value_list.into_bytes();
+ let true_byte = u8::from(true).to_le_bytes()[0];
+ let false_byte = u8::from(false).to_le_bytes()[0];
+
+ for i in 0..flag_value_list.header.num_flags {
+ let offset = (value_offset + i) as usize;
+ update_boolean_flag_value(&mut content, i, true).unwrap();
+ assert_eq!(content[offset], true_byte);
+ update_boolean_flag_value(&mut content, i, false).unwrap();
+ assert_eq!(content[offset], false_byte);
+ }
+ }
+
+ #[test]
+ // this test point locks down update beyond the end of boolean section
+ fn test_boolean_out_of_range() {
+ let mut flag_value_list = create_test_flag_value_list().into_bytes();
+ let error = update_boolean_flag_value(&mut flag_value_list[..], 8, true).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"
+ );
+ }
+
+ #[test]
+ // this test point locks down query error when file has a higher version
+ fn test_higher_version_storage_file() {
+ let mut value_list = create_test_flag_value_list();
+ value_list.header.version = FILE_VERSION + 1;
+ let mut flag_value = value_list.into_bytes();
+ let error = update_boolean_flag_value(&mut flag_value[..], 4, true).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "HigherStorageFileVersion(Cannot write to storage file with a higher version of {} with lib version {})",
+ FILE_VERSION + 1,
+ FILE_VERSION
+ )
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
new file mode 100644
index 0000000..8b7e459
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs
@@ -0,0 +1,523 @@
+/*
+ * 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.
+ */
+
+//! `aconfig_storage_write_api` is a crate that defines write apis to update flag value
+//! in storage file. It provides one api to interface with storage files.
+
+pub mod flag_info_update;
+pub mod flag_value_update;
+pub mod mapped_file;
+
+#[cfg(test)]
+mod test_utils;
+
+use aconfig_storage_file::{
+ AconfigStorageError, FlagInfoHeader, FlagInfoList, FlagInfoNode, FlagTable, FlagValueType,
+ PackageTable, StorageFileType, StoredFlagType, FILE_VERSION,
+};
+
+use anyhow::anyhow;
+use memmap2::MmapMut;
+use std::fs::File;
+use std::io::{Read, Write};
+
+/// Storage file location pb file
+pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/persistent_storage_file_records.pb";
+
+/// Get read write mapped storage files.
+///
+/// \input container: the flag package container
+/// \input file_type: storage file type enum
+/// \return a result of read write mapped file
+///
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_storage_file(
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<MmapMut, AconfigStorageError> {
+ unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container, file_type) }
+}
+
+/// Set boolean flag value thru mapped file and flush the change to file
+///
+/// \input mapped_file: the mapped flag value file
+/// \input index: flag index
+/// \input value: updated flag value
+/// \return a result of ()
+///
+pub fn set_boolean_flag_value(
+ file: &mut MmapMut,
+ index: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ crate::flag_value_update::update_boolean_flag_value(file, index, value)?;
+ file.flush().map_err(|errmsg| {
+ AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg))
+ })
+}
+
+/// Set if flag is sticky thru mapped file and flush the change to file
+///
+/// \input mapped_file: the mapped flag info file
+/// \input index: flag index
+/// \input value: updated flag sticky value
+/// \return a result of ()
+///
+pub fn set_flag_is_sticky(
+ file: &mut MmapMut,
+ flag_type: FlagValueType,
+ index: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ crate::flag_info_update::update_flag_is_sticky(file, flag_type, index, value)?;
+ file.flush().map_err(|errmsg| {
+ AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg))
+ })
+}
+
+/// Set if flag has override thru mapped file and flush the change to file
+///
+/// \input mapped_file: the mapped flag info file
+/// \input index: flag index
+/// \input value: updated flag has override value
+/// \return a result of ()
+///
+pub fn set_flag_has_override(
+ file: &mut MmapMut,
+ flag_type: FlagValueType,
+ index: u32,
+ value: bool,
+) -> Result<(), AconfigStorageError> {
+ crate::flag_info_update::update_flag_has_override(file, flag_type, index, value)?;
+ file.flush().map_err(|errmsg| {
+ AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg))
+ })
+}
+
+/// Read in storage file as bytes
+fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageError> {
+ let mut file = File::open(file_path).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer).map_err(|errmsg| {
+ AconfigStorageError::FileReadFail(anyhow!(
+ "Failed to read bytes from file {}: {}",
+ file_path,
+ errmsg
+ ))
+ })?;
+ Ok(buffer)
+}
+
+/// Create flag info file given package map file and flag map file
+/// \input package_map: package map file
+/// \input flag_map: flag map file
+/// \output flag_info_out: created flag info file
+pub fn create_flag_info(
+ package_map: &str,
+ flag_map: &str,
+ flag_info_out: &str,
+) -> Result<(), AconfigStorageError> {
+ let package_table = PackageTable::from_bytes(&read_file_to_bytes(package_map)?)?;
+ let flag_table = FlagTable::from_bytes(&read_file_to_bytes(flag_map)?)?;
+
+ if package_table.header.container != flag_table.header.container {
+ return Err(AconfigStorageError::FileCreationFail(anyhow!(
+ "container for package map {} and flag map {} does not match",
+ package_table.header.container,
+ flag_table.header.container,
+ )));
+ }
+
+ let mut package_start_index = vec![0; package_table.header.num_packages as usize];
+ for node in package_table.nodes.iter() {
+ package_start_index[node.package_id as usize] = node.boolean_start_index;
+ }
+
+ let mut is_flag_rw = vec![false; flag_table.header.num_flags as usize];
+ for node in flag_table.nodes.iter() {
+ let flag_index = package_start_index[node.package_id as usize] + node.flag_index as u32;
+ is_flag_rw[flag_index as usize] = node.flag_type == StoredFlagType::ReadWriteBoolean;
+ }
+
+ let mut list = FlagInfoList {
+ header: FlagInfoHeader {
+ version: FILE_VERSION,
+ container: flag_table.header.container,
+ file_type: StorageFileType::FlagInfo as u8,
+ file_size: 0,
+ num_flags: flag_table.header.num_flags,
+ boolean_flag_offset: 0,
+ },
+ nodes: is_flag_rw.iter().map(|&rw| FlagInfoNode::create(rw)).collect(),
+ };
+
+ list.header.boolean_flag_offset = list.header.into_bytes().len() as u32;
+ list.header.file_size = list.into_bytes().len() as u32;
+
+ let mut file = File::create(flag_info_out).map_err(|errmsg| {
+ AconfigStorageError::FileCreationFail(anyhow!(
+ "fail to create file {}: {}",
+ flag_info_out,
+ errmsg
+ ))
+ })?;
+ file.write_all(&list.into_bytes()).map_err(|errmsg| {
+ AconfigStorageError::FileCreationFail(anyhow!(
+ "fail to write to file {}: {}",
+ flag_info_out,
+ errmsg
+ ))
+ })?;
+
+ Ok(())
+}
+
+// *************************************** //
+// CC INTERLOP
+// *************************************** //
+
+// Exported rust data structure and methods, c++ code will be generated
+#[cxx::bridge]
+mod ffi {
+ // Flag value update return for cc interlop
+ pub struct BooleanFlagValueUpdateCXX {
+ pub update_success: bool,
+ pub error_message: String,
+ }
+
+ // Flag is sticky update return for cc interlop
+ pub struct FlagIsStickyUpdateCXX {
+ pub update_success: bool,
+ pub error_message: String,
+ }
+
+ // Flag has override update return for cc interlop
+ pub struct FlagHasOverrideUpdateCXX {
+ pub update_success: bool,
+ pub error_message: String,
+ }
+
+ // Flag info file creation return for cc interlop
+ pub struct FlagInfoCreationCXX {
+ pub success: bool,
+ pub error_message: String,
+ }
+
+ // Rust export to c++
+ extern "Rust" {
+ pub fn update_boolean_flag_value_cxx(
+ file: &mut [u8],
+ offset: u32,
+ value: bool,
+ ) -> BooleanFlagValueUpdateCXX;
+
+ pub fn update_flag_is_sticky_cxx(
+ file: &mut [u8],
+ flag_type: u16,
+ offset: u32,
+ value: bool,
+ ) -> FlagIsStickyUpdateCXX;
+
+ pub fn update_flag_has_override_cxx(
+ file: &mut [u8],
+ flag_type: u16,
+ offset: u32,
+ value: bool,
+ ) -> FlagHasOverrideUpdateCXX;
+
+ pub fn create_flag_info_cxx(
+ package_map: &str,
+ flag_map: &str,
+ flag_info_out: &str,
+ ) -> FlagInfoCreationCXX;
+ }
+}
+
+pub(crate) fn update_boolean_flag_value_cxx(
+ file: &mut [u8],
+ offset: u32,
+ value: bool,
+) -> ffi::BooleanFlagValueUpdateCXX {
+ match crate::flag_value_update::update_boolean_flag_value(file, offset, value) {
+ Ok(()) => {
+ ffi::BooleanFlagValueUpdateCXX { update_success: true, error_message: String::from("") }
+ }
+ Err(errmsg) => ffi::BooleanFlagValueUpdateCXX {
+ update_success: false,
+ error_message: format!("{:?}", errmsg),
+ },
+ }
+}
+
+pub(crate) fn update_flag_is_sticky_cxx(
+ file: &mut [u8],
+ flag_type: u16,
+ offset: u32,
+ value: bool,
+) -> ffi::FlagIsStickyUpdateCXX {
+ match FlagValueType::try_from(flag_type) {
+ Ok(value_type) => {
+ match crate::flag_info_update::update_flag_is_sticky(file, value_type, offset, value) {
+ Ok(()) => ffi::FlagIsStickyUpdateCXX {
+ update_success: true,
+ error_message: String::from(""),
+ },
+ Err(errmsg) => ffi::FlagIsStickyUpdateCXX {
+ update_success: false,
+ error_message: format!("{:?}", errmsg),
+ },
+ }
+ }
+ Err(errmsg) => ffi::FlagIsStickyUpdateCXX {
+ update_success: false,
+ error_message: format!("{:?}", errmsg),
+ },
+ }
+}
+
+pub(crate) fn update_flag_has_override_cxx(
+ file: &mut [u8],
+ flag_type: u16,
+ offset: u32,
+ value: bool,
+) -> ffi::FlagHasOverrideUpdateCXX {
+ match FlagValueType::try_from(flag_type) {
+ Ok(value_type) => {
+ match crate::flag_info_update::update_flag_has_override(file, value_type, offset, value)
+ {
+ Ok(()) => ffi::FlagHasOverrideUpdateCXX {
+ update_success: true,
+ error_message: String::from(""),
+ },
+ Err(errmsg) => ffi::FlagHasOverrideUpdateCXX {
+ update_success: false,
+ error_message: format!("{:?}", errmsg),
+ },
+ }
+ }
+ Err(errmsg) => ffi::FlagHasOverrideUpdateCXX {
+ update_success: false,
+ error_message: format!("{:?}", errmsg),
+ },
+ }
+}
+
+/// Create flag info file cc interlop
+pub(crate) fn create_flag_info_cxx(
+ package_map: &str,
+ flag_map: &str,
+ flag_info_out: &str,
+) -> ffi::FlagInfoCreationCXX {
+ match create_flag_info(package_map, flag_map, flag_info_out) {
+ Ok(()) => ffi::FlagInfoCreationCXX { success: true, error_message: String::from("") },
+ Err(errmsg) => {
+ ffi::FlagInfoCreationCXX { success: false, error_message: format!("{:?}", errmsg) }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+ use aconfig_storage_file::test_utils::{
+ create_test_flag_info_list, create_test_flag_table, create_test_package_table,
+ write_bytes_to_temp_file,
+ };
+ use aconfig_storage_file::FlagInfoBit;
+ use aconfig_storage_read_api::flag_info_query::find_flag_attribute;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use std::fs::File;
+ use std::io::Read;
+ use tempfile::NamedTempFile;
+
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(&file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ #[test]
+ fn test_set_boolean_flag_value() {
+ let flag_value_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ flag_info: "some_flag.info"
+ timestamp: 12345
+}}
+"#,
+ flag_value_path
+ );
+ let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed as only this single threaded test process will
+ // write to this file
+ unsafe {
+ let mut file = crate::mapped_file::get_mapped_file(
+ &record_pb_path,
+ "system",
+ StorageFileType::FlagVal,
+ )
+ .unwrap();
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, true);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert_eq!(value, false);
+ }
+ }
+ }
+
+ fn get_flag_attribute_at_offset(file: &str, value_type: FlagValueType, offset: u32) -> u8 {
+ let mut f = File::open(&file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_flag_attribute(&bytes, value_type, offset).unwrap()
+ }
+
+ #[test]
+ fn test_set_flag_is_sticky() {
+ let flag_info_file = copy_to_temp_file("./tests/flag.info", false).unwrap();
+ let flag_info_path = flag_info_file.path().display().to_string();
+ let text_proto = format!(
+ r#"
+ files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "some_flag.val"
+ flag_info: "{}"
+ timestamp: 12345
+ }}
+ "#,
+ flag_info_path
+ );
+ let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed as only this single threaded test process will
+ // write to this file
+ unsafe {
+ let mut file = crate::mapped_file::get_mapped_file(
+ &record_pb_path,
+ "system",
+ StorageFileType::FlagInfo,
+ )
+ .unwrap();
+ for i in 0..8 {
+ set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, true).unwrap();
+ let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0);
+ set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0);
+ }
+ }
+ }
+
+ #[test]
+ fn test_set_flag_has_override() {
+ let flag_info_file = copy_to_temp_file("./tests/flag.info", false).unwrap();
+ let flag_info_path = flag_info_file.path().display().to_string();
+ let text_proto = format!(
+ r#"
+ files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "some_flag.val"
+ flag_info: "{}"
+ timestamp: 12345
+ }}
+ "#,
+ flag_info_path
+ );
+ let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed as only this single threaded test process will
+ // write to this file
+ unsafe {
+ let mut file = crate::mapped_file::get_mapped_file(
+ &record_pb_path,
+ "system",
+ StorageFileType::FlagInfo,
+ )
+ .unwrap();
+ for i in 0..8 {
+ set_flag_has_override(&mut file, FlagValueType::Boolean, i, true).unwrap();
+ let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0);
+ set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0);
+ }
+ }
+ }
+
+ fn create_empty_temp_file() -> Result<NamedTempFile, AconfigStorageError> {
+ let file = NamedTempFile::new().map_err(|_| {
+ AconfigStorageError::FileCreationFail(anyhow!("Failed to create temp file"))
+ })?;
+ Ok(file)
+ }
+
+ #[test]
+ // this test point locks down the flag info creation
+ fn test_create_flag_info() {
+ let package_table =
+ write_bytes_to_temp_file(&create_test_package_table().into_bytes()).unwrap();
+ let flag_table = write_bytes_to_temp_file(&create_test_flag_table().into_bytes()).unwrap();
+ let flag_info = create_empty_temp_file().unwrap();
+
+ let package_table_path = package_table.path().display().to_string();
+ let flag_table_path = flag_table.path().display().to_string();
+ let flag_info_path = flag_info.path().display().to_string();
+
+ assert!(create_flag_info(&package_table_path, &flag_table_path, &flag_info_path).is_ok());
+
+ let flag_info =
+ FlagInfoList::from_bytes(&read_file_to_bytes(&flag_info_path).unwrap()).unwrap();
+ let expected_flag_info = create_test_flag_info_list();
+ assert_eq!(flag_info, expected_flag_info);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
new file mode 100644
index 0000000..ea9ac19
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs
@@ -0,0 +1,211 @@
+/*
+ * 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 anyhow::anyhow;
+use memmap2::MmapMut;
+use std::fs::{self, OpenOptions};
+use std::io::Read;
+
+use aconfig_storage_file::AconfigStorageError::{self, FileReadFail, MapFileFail};
+use aconfig_storage_file::StorageFileType;
+use aconfig_storage_read_api::mapped_file::find_container_storage_location;
+
+/// Get the mutable memory mapping of a storage file
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+unsafe fn map_file(file_path: &str) -> Result<MmapMut, AconfigStorageError> {
+ // make sure file has read write permission
+ let perms = fs::metadata(file_path).unwrap().permissions();
+ if perms.readonly() {
+ return Err(MapFileFail(anyhow!("fail to map non read write storage file {}", file_path)));
+ }
+
+ let file =
+ OpenOptions::new().read(true).write(true).open(file_path).map_err(|errmsg| {
+ FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg))
+ })?;
+
+ unsafe {
+ let mapped_file = MmapMut::map_mut(&file).map_err(|errmsg| {
+ MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg))
+ })?;
+ Ok(mapped_file)
+ }
+}
+
+/// Get a mapped storage file given the container and file type
+///
+/// # Safety
+///
+/// The memory mapped file may have undefined behavior if there are writes to this
+/// file not thru this memory mapped file or there are concurrent writes to this
+/// memory mapped file. Ensure all writes to the underlying file are thru this memory
+/// mapped file and there are no concurrent writes.
+pub unsafe fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+ file_type: StorageFileType,
+) -> Result<MmapMut, AconfigStorageError> {
+ let files_location = find_container_storage_location(location_pb_file, container)?;
+ match file_type {
+ StorageFileType::FlagVal => unsafe { map_file(files_location.flag_val()) },
+ StorageFileType::FlagInfo => unsafe { map_file(files_location.flag_info()) },
+ _ => Err(MapFileFail(anyhow!(
+ "Cannot map file type {:?} as writeable memory mapped files.",
+ file_type
+ ))),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::copy_to_temp_file;
+ use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file;
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let mut rw_val_file = copy_to_temp_file("./tests/flag.val", false).unwrap();
+ let mut rw_info_file = copy_to_temp_file("./tests/flag.info", false).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ flag_info: "{}"
+ timestamp: 12345
+}}
+"#,
+ rw_val_file.path().display().to_string(),
+ rw_info_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ let mut content = Vec::new();
+ rw_val_file.read_to_end(&mut content).unwrap();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let mmaped_file =
+ get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagVal)
+ .unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+
+ let mut content = Vec::new();
+ rw_info_file.read_to_end(&mut content).unwrap();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let mmaped_file =
+ get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagInfo)
+ .unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+ }
+
+ #[test]
+ fn test_mapped_read_only_file() {
+ let ro_file = copy_to_temp_file("./tests/flag.val", true).unwrap();
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "{}"
+ flag_info: "some_flag.info"
+ timestamp: 12345
+}}
+"#,
+ ro_file.path().display().to_string()
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let error =
+ get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagVal)
+ .unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(fail to map non read write storage file {})",
+ ro_file.path().display().to_string()
+ )
+ );
+ }
+ }
+
+ #[test]
+ fn test_mapped_not_supported_file() {
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "system"
+ package_map: "some_package.map"
+ flag_map: "some_flag.map"
+ flag_val: "some_flag.val"
+ flag_info: "some_flag.info"
+ timestamp: 12345
+}}
+"#,
+ );
+ let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
+ let storage_record_file_path = storage_record_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is guaranteed here as no writes happens to this temp file
+ unsafe {
+ let error =
+ get_mapped_file(&storage_record_file_path, "system", StorageFileType::PackageMap)
+ .unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)",
+ StorageFileType::PackageMap
+ )
+ );
+
+ let error =
+ get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagMap)
+ .unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ format!(
+ "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)",
+ StorageFileType::FlagMap
+ )
+ );
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
new file mode 100644
index 0000000..06e2e22
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/src/test_utils.rs
@@ -0,0 +1,32 @@
+/*
+ * 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 anyhow::Result;
+use std::fs;
+use tempfile::NamedTempFile;
+
+/// Create temp file copy
+pub(crate) fn copy_to_temp_file(source_file: &str, read_only: bool) -> Result<NamedTempFile> {
+ let file = NamedTempFile::new()?;
+ fs::copy(source_file, file.path())?;
+ if read_only {
+ let file_name = file.path().display().to_string();
+ let mut perms = fs::metadata(file_name).unwrap().permissions();
+ perms.set_readonly(true);
+ fs::set_permissions(file.path(), perms.clone()).unwrap();
+ }
+ Ok(file)
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
new file mode 100644
index 0000000..85568e0
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp
@@ -0,0 +1,44 @@
+rust_test {
+ name: "aconfig_storage_write_api.test.rust",
+ srcs: [
+ "storage_write_api_test.rs",
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libaconfig_storage_file",
+ "libaconfig_storage_read_api",
+ "libaconfig_storage_write_api",
+ "libprotobuf",
+ "libtempfile",
+ ],
+ data: [
+ "flag.val",
+ "flag.info",
+ ],
+ test_suites: ["general-tests"],
+}
+
+cc_test {
+ name: "aconfig_storage_write_api.test.cpp",
+ srcs: [
+ "storage_write_api_test.cpp",
+ ],
+ static_libs: [
+ "libgmock",
+ "libaconfig_storage_protos_cc",
+ "libprotobuf-cpp-lite",
+ "libaconfig_storage_read_api_cc",
+ "libaconfig_storage_write_api_cc",
+ "libbase",
+ "liblog",
+ ],
+ data: [
+ "flag.val",
+ "flag.info",
+ ],
+ test_suites: [
+ "device-tests",
+ "general-tests",
+ ],
+ ldflags: ["-Wl,--allow-multiple-definition"],
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.info b/tools/aconfig/aconfig_storage_write_api/tests/flag.info
new file mode 100644
index 0000000..820d839
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.info
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.val b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
new file mode 100644
index 0000000..ed203d4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp
new file mode 100644
index 0000000..6de3327
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+#include <string>
+#include <vector>
+#include <cstdio>
+
+#include <sys/stat.h>
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+#include "aconfig_storage/aconfig_storage_write_api.hpp"
+#include <gtest/gtest.h>
+#include <protos/aconfig_storage_metadata.pb.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+
+using android::aconfig_storage_metadata::storage_files;
+using namespace android::base;
+
+namespace api = aconfig_storage;
+namespace private_api = aconfig_storage::private_internal_api;
+
+class AconfigStorageTest : public ::testing::Test {
+ protected:
+ Result<std::string> copy_to_rw_temp_file(std::string const& source_file) {
+ auto temp_file = std::string(std::tmpnam(nullptr));
+ auto content = std::string();
+ if (!ReadFileToString(source_file, &content)) {
+ return Error() << "failed to read file: " << source_file;
+ }
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to copy file: " << source_file;
+ }
+ if (chmod(temp_file.c_str(),
+ S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH) == -1) {
+ return Error() << "failed to chmod";
+ }
+ return temp_file;
+ }
+
+ Result<std::string> write_storage_location_pb_file(std::string const& flag_val,
+ std::string const& flag_info) {
+ auto temp_file = std::tmpnam(nullptr);
+ auto proto = storage_files();
+ auto* info = proto.add_files();
+ info->set_version(0);
+ info->set_container("mockup");
+ info->set_package_map("some_package.map");
+ info->set_flag_map("some_flag.map");
+ info->set_flag_val(flag_val);
+ info->set_flag_info(flag_info);
+ info->set_timestamp(12345);
+
+ auto content = std::string();
+ proto.SerializeToString(&content);
+ if (!WriteStringToFile(content, temp_file)) {
+ return Error() << "failed to write storage records pb file";
+ }
+ return temp_file;
+ }
+
+ void SetUp() override {
+ auto const test_dir = android::base::GetExecutableDirectory();
+ flag_val = *copy_to_rw_temp_file(test_dir + "/flag.val");
+ flag_info = *copy_to_rw_temp_file(test_dir + "/flag.info");
+ storage_record_pb = *write_storage_location_pb_file(flag_val, flag_info);
+ }
+
+ void TearDown() override {
+ std::remove(flag_val.c_str());
+ std::remove(flag_info.c_str());
+ std::remove(storage_record_pb.c_str());
+ }
+
+ std::string flag_val;
+ std::string flag_info;
+ std::string storage_record_pb;
+};
+
+/// Negative test to lock down the error when mapping none exist storage files
+TEST_F(AconfigStorageTest, test_none_exist_storage_file_mapping) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "vendor", api::StorageFileType::flag_val);
+ ASSERT_FALSE(mapped_file_result.ok());
+ ASSERT_EQ(mapped_file_result.error().message(),
+ "Unable to find storage files for container vendor");
+}
+
+/// Negative test to lock down the error when mapping a non writeable storage file
+TEST_F(AconfigStorageTest, test_non_writable_storage_file_mapping) {
+ ASSERT_TRUE(chmod(flag_val.c_str(), S_IRUSR | S_IRGRP | S_IROTH) != -1);
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_val);
+ ASSERT_FALSE(mapped_file_result.ok());
+ auto it = mapped_file_result.error().message().find("cannot map nonwriteable file");
+ ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message();
+}
+
+/// Negative test to lock down the error when mapping a file type that cannot be modified
+TEST_F(AconfigStorageTest, test_invalid_storage_file_type_mapping) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::package_map);
+ ASSERT_FALSE(mapped_file_result.ok());
+ auto it = mapped_file_result.error().message().find(
+ "Cannot create mutable mapped file for this file type");
+ ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message();
+
+ mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_map);
+ ASSERT_FALSE(mapped_file_result.ok());
+ it = mapped_file_result.error().message().find(
+ "Cannot create mutable mapped file for this file type");
+ ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message();
+}
+
+/// Test to lock down storage flag value update api
+TEST_F(AconfigStorageTest, test_boolean_flag_value_update) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file_result.ok());
+ auto mapped_file = *mapped_file_result;
+
+ for (int offset = 0; offset < 8; ++offset) {
+ auto update_result = api::set_boolean_flag_value(mapped_file, offset, true);
+ ASSERT_TRUE(update_result.ok());
+ auto ro_mapped_file = api::MappedStorageFile();
+ ro_mapped_file.file_ptr = mapped_file.file_ptr;
+ ro_mapped_file.file_size = mapped_file.file_size;
+ auto value = api::get_boolean_flag_value(ro_mapped_file, offset);
+ ASSERT_TRUE(value.ok());
+ ASSERT_TRUE(*value);
+ }
+}
+
+/// Negative test to lock down the error when querying flag value out of range
+TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_update) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_val);
+ ASSERT_TRUE(mapped_file_result.ok());
+ auto mapped_file = *mapped_file_result;
+ auto update_result = api::set_boolean_flag_value(mapped_file, 8, true);
+ ASSERT_FALSE(update_result.ok());
+ ASSERT_EQ(update_result.error().message(),
+ std::string("InvalidStorageFileOffset(Flag value offset goes beyond the end of the file.)"));
+}
+
+/// Test to lock down storage flag stickiness update api
+TEST_F(AconfigStorageTest, test_flag_is_sticky_update) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_info);
+ ASSERT_TRUE(mapped_file_result.ok());
+ auto mapped_file = *mapped_file_result;
+
+ for (int offset = 0; offset < 8; ++offset) {
+ auto update_result = api::set_flag_is_sticky(
+ mapped_file, api::FlagValueType::Boolean, offset, true);
+ ASSERT_TRUE(update_result.ok());
+ auto ro_mapped_file = api::MappedStorageFile();
+ ro_mapped_file.file_ptr = mapped_file.file_ptr;
+ ro_mapped_file.file_size = mapped_file.file_size;
+ auto attribute = api::get_flag_attribute(
+ ro_mapped_file, api::FlagValueType::Boolean, offset);
+ ASSERT_TRUE(attribute.ok());
+ ASSERT_TRUE(*attribute & api::FlagInfoBit::IsSticky);
+
+ update_result = api::set_flag_is_sticky(
+ mapped_file, api::FlagValueType::Boolean, offset, false);
+ ASSERT_TRUE(update_result.ok());
+ ro_mapped_file.file_ptr = mapped_file.file_ptr;
+ ro_mapped_file.file_size = mapped_file.file_size;
+ attribute = api::get_flag_attribute(
+ ro_mapped_file, api::FlagValueType::Boolean, offset);
+ ASSERT_TRUE(attribute.ok());
+ ASSERT_FALSE(*attribute & api::FlagInfoBit::IsSticky);
+ }
+}
+
+/// Test to lock down storage flag has override update api
+TEST_F(AconfigStorageTest, test_flag_has_override_update) {
+ auto mapped_file_result = private_api::get_mutable_mapped_file_impl(
+ storage_record_pb, "mockup", api::StorageFileType::flag_info);
+ ASSERT_TRUE(mapped_file_result.ok());
+ auto mapped_file = *mapped_file_result;
+
+ for (int offset = 0; offset < 8; ++offset) {
+ auto update_result = api::set_flag_has_override(
+ mapped_file, api::FlagValueType::Boolean, offset, true);
+ ASSERT_TRUE(update_result.ok());
+ auto ro_mapped_file = api::MappedStorageFile();
+ ro_mapped_file.file_ptr = mapped_file.file_ptr;
+ ro_mapped_file.file_size = mapped_file.file_size;
+ auto attribute = api::get_flag_attribute(
+ ro_mapped_file, api::FlagValueType::Boolean, offset);
+ ASSERT_TRUE(attribute.ok());
+ ASSERT_TRUE(*attribute & api::FlagInfoBit::HasOverride);
+
+ update_result = api::set_flag_has_override(
+ mapped_file, api::FlagValueType::Boolean, offset, false);
+ ASSERT_TRUE(update_result.ok());
+ ro_mapped_file.file_ptr = mapped_file.file_ptr;
+ ro_mapped_file.file_size = mapped_file.file_size;
+ attribute = api::get_flag_attribute(
+ ro_mapped_file, api::FlagValueType::Boolean, offset);
+ ASSERT_TRUE(attribute.ok());
+ ASSERT_FALSE(*attribute & api::FlagInfoBit::HasOverride);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
new file mode 100644
index 0000000..5dd36c4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.rs
@@ -0,0 +1,147 @@
+#[cfg(not(feature = "cargo"))]
+mod aconfig_storage_write_api_test {
+ use aconfig_storage_file::protos::ProtoStorageFiles;
+ use aconfig_storage_file::{FlagInfoBit, FlagValueType, StorageFileType};
+ use aconfig_storage_read_api::flag_info_query::find_flag_attribute;
+ use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+ use aconfig_storage_write_api::{
+ mapped_file::get_mapped_file, set_boolean_flag_value, set_flag_has_override,
+ set_flag_is_sticky,
+ };
+
+ use protobuf::Message;
+ use std::fs::{self, File};
+ use std::io::{Read, Write};
+ use tempfile::NamedTempFile;
+
+ /// Write storage location record pb to a temp file
+ fn write_storage_record_file(flag_val: &str, flag_info: &str) -> NamedTempFile {
+ let text_proto = format!(
+ r#"
+files {{
+ version: 0
+ container: "mockup"
+ package_map: "some_package_map"
+ flag_map: "some_flag_map"
+ flag_val: "{}"
+ flag_info: "{}"
+ timestamp: 12345
+}}
+"#,
+ flag_val, flag_info
+ );
+ let storage_files: ProtoStorageFiles =
+ protobuf::text_format::parse_from_str(&text_proto).unwrap();
+ let mut binary_proto_bytes = Vec::new();
+ storage_files.write_to_vec(&mut binary_proto_bytes).unwrap();
+ let mut file = NamedTempFile::new().unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+ file
+ }
+
+ /// Create temp file copy
+ fn copy_to_temp_rw_file(source_file: &str) -> NamedTempFile {
+ let file = NamedTempFile::new().unwrap();
+ fs::copy(source_file, file.path()).unwrap();
+ file
+ }
+
+ /// Get boolean flag value from offset
+ fn get_boolean_flag_value_at_offset(file: &str, offset: u32) -> bool {
+ let mut f = File::open(file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_boolean_flag_value(&bytes, offset).unwrap()
+ }
+
+ /// Get flag attribute at offset
+ fn get_flag_attribute_at_offset(file: &str, value_type: FlagValueType, offset: u32) -> u8 {
+ let mut f = File::open(file).unwrap();
+ let mut bytes = Vec::new();
+ f.read_to_end(&mut bytes).unwrap();
+ find_flag_attribute(&bytes, value_type, offset).unwrap()
+ }
+
+ #[test]
+ /// Test to lock down flag value update api
+ fn test_boolean_flag_value_update() {
+ let flag_value_file = copy_to_temp_rw_file("./flag.val");
+ let flag_info_file = copy_to_temp_rw_file("./flag.info");
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let flag_info_path = flag_info_file.path().display().to_string();
+ let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path);
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is ensured as only this single threaded test process will
+ // write to this file
+ let mut file = unsafe {
+ get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagVal).unwrap()
+ };
+ for i in 0..8 {
+ set_boolean_flag_value(&mut file, i, true).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(value);
+
+ set_boolean_flag_value(&mut file, i, false).unwrap();
+ let value = get_boolean_flag_value_at_offset(&flag_value_path, i);
+ assert!(!value);
+ }
+ }
+
+ #[test]
+ /// Test to lock down flag is sticky update api
+ fn test_set_flag_is_sticky() {
+ let flag_value_file = copy_to_temp_rw_file("./flag.val");
+ let flag_info_file = copy_to_temp_rw_file("./flag.info");
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let flag_info_path = flag_info_file.path().display().to_string();
+ let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path);
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is ensured as only this single threaded test process will
+ // write to this file
+ let mut file = unsafe {
+ get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagInfo).unwrap()
+ };
+ for i in 0..8 {
+ set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, true).unwrap();
+ let attribute =
+ get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0);
+ set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ let attribute =
+ get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0);
+ }
+ }
+
+ #[test]
+ /// Test to lock down flag is sticky update api
+ fn test_set_flag_has_override() {
+ let flag_value_file = copy_to_temp_rw_file("./flag.val");
+ let flag_info_file = copy_to_temp_rw_file("./flag.info");
+ let flag_value_path = flag_value_file.path().display().to_string();
+ let flag_info_path = flag_info_file.path().display().to_string();
+ let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path);
+ let record_pb_path = record_pb_file.path().display().to_string();
+
+ // SAFETY:
+ // The safety here is ensured as only this single threaded test process will
+ // write to this file
+ let mut file = unsafe {
+ get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagInfo).unwrap()
+ };
+ for i in 0..8 {
+ set_flag_has_override(&mut file, FlagValueType::Boolean, i, true).unwrap();
+ let attribute =
+ get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0);
+ set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ let attribute =
+ get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
+ assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0);
+ }
+ }
+}
diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp
index c65da97..4920a6f 100644
--- a/tools/aconfig/aflags/Android.bp
+++ b/tools/aconfig/aflags/Android.bp
@@ -10,8 +10,11 @@
srcs: ["src/main.rs"],
rustlibs: [
"libaconfig_protos",
+ "libaconfig_storage_read_api",
+ "libaconfig_storage_file",
"libanyhow",
"libclap",
+ "libnix",
"libprotobuf",
"libregex",
],
diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml
index 3350a6cd..cce7f9d 100644
--- a/tools/aconfig/aflags/Cargo.toml
+++ b/tools/aconfig/aflags/Cargo.toml
@@ -6,7 +6,10 @@
[dependencies]
anyhow = "1.0.69"
paste = "1.0.11"
-clap = { version = "4", features = ["derive"] }
protobuf = "3.2.0"
regex = "1.10.3"
aconfig_protos = { path = "../aconfig_protos" }
+nix = { version = "0.28.0", features = ["user"] }
+aconfig_storage_file = { version = "0.1.0", path = "../aconfig_storage_file" }
+aconfig_storage_read_api = { version = "0.1.0", path = "../aconfig_storage_read_api" }
+clap = {version = "4.5.2" }
diff --git a/tools/aconfig/aflags/src/aconfig_storage_source.rs b/tools/aconfig/aflags/src/aconfig_storage_source.rs
new file mode 100644
index 0000000..a3ca221
--- /dev/null
+++ b/tools/aconfig/aflags/src/aconfig_storage_source.rs
@@ -0,0 +1,56 @@
+use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom};
+use anyhow::{anyhow, Result};
+
+use std::fs::File;
+use std::io::Read;
+
+pub struct AconfigStorageSource {}
+
+use aconfig_storage_file::protos::ProtoStorageFiles;
+
+static STORAGE_INFO_FILE_PATH: &str = "/metadata/aconfig/persistent_storage_file_records.pb";
+
+impl FlagSource for AconfigStorageSource {
+ fn list_flags() -> Result<Vec<Flag>> {
+ let mut result = Vec::new();
+
+ let mut file = File::open(STORAGE_INFO_FILE_PATH)?;
+ let mut bytes = Vec::new();
+ file.read_to_end(&mut bytes)?;
+ let storage_file_info: ProtoStorageFiles = protobuf::Message::parse_from_bytes(&bytes)?;
+
+ for file_info in storage_file_info.files {
+ let package_map =
+ file_info.package_map.ok_or(anyhow!("storage file is missing package map"))?;
+ let flag_map = file_info.flag_map.ok_or(anyhow!("storage file is missing flag map"))?;
+ let flag_val = file_info.flag_val.ok_or(anyhow!("storage file is missing flag val"))?;
+ let container =
+ file_info.container.ok_or(anyhow!("storage file is missing container"))?;
+
+ for (package, name, _flag_type, val) in
+ aconfig_storage_file::list_flags(&package_map, &flag_map, &flag_val)?
+ {
+ result.push(Flag {
+ name: name.to_string(),
+ package: package.to_string(),
+ value: FlagValue::try_from(val.to_string().as_str())?,
+ container: container.to_string(),
+
+ // TODO(b/324436145): delete namespace field once DeviceConfig isn't in CLI.
+ namespace: "-".to_string(),
+
+ // TODO(b/324436145): Populate with real values once API is available.
+ staged_value: None,
+ permission: FlagPermission::ReadOnly,
+ value_picked_from: ValuePickedFrom::Default,
+ });
+ }
+ }
+
+ Ok(result)
+ }
+
+ fn override_flag(_namespace: &str, _qualified_name: &str, _value: &str) -> Result<()> {
+ todo!()
+ }
+}
diff --git a/tools/aconfig/aflags/src/device_config_source.rs b/tools/aconfig/aflags/src/device_config_source.rs
index 12a62cf..089f33d 100644
--- a/tools/aconfig/aflags/src/device_config_source.rs
+++ b/tools/aconfig/aflags/src/device_config_source.rs
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-use crate::{Flag, FlagPermission, FlagSource, ValuePickedFrom};
+use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom};
use aconfig_protos::ProtoFlagPermission as ProtoPermission;
use aconfig_protos::ProtoFlagState as ProtoState;
use aconfig_protos::ProtoParsedFlag;
@@ -40,10 +40,9 @@
};
let value = match flag.state() {
- ProtoState::ENABLED => "true",
- ProtoState::DISABLED => "false",
- }
- .to_string();
+ ProtoState::ENABLED => FlagValue::Enabled,
+ ProtoState::DISABLED => FlagValue::Disabled,
+ };
let permission = match flag.permission() {
ProtoPermission::READ_ONLY => FlagPermission::ReadOnly,
@@ -56,6 +55,7 @@
name,
container,
value,
+ staged_value: None,
permission,
value_picked_from: ValuePickedFrom::Default,
}
@@ -64,7 +64,7 @@
fn read_pb_files() -> Result<Vec<Flag>> {
let mut flags: BTreeMap<String, Flag> = BTreeMap::new();
for partition in ["system", "system_ext", "product", "vendor"] {
- let path = format!("/{}/etc/aconfig_flags.pb", partition);
+ let path = format!("/{partition}/etc/aconfig_flags.pb");
let Ok(bytes) = fs::read(&path) else {
eprintln!("warning: failed to read {}", path);
continue;
@@ -86,53 +86,94 @@
Ok(flags.values().cloned().collect())
}
-fn parse_device_config(raw: &str) -> Result<HashMap<String, String>> {
+fn parse_device_config(raw: &str) -> Result<HashMap<String, FlagValue>> {
let mut flags = HashMap::new();
let regex = Regex::new(r"(?m)^([[[:alnum:]]_]+/[[[:alnum:]]_\.]+)=(true|false)$")?;
for capture in regex.captures_iter(raw) {
let key =
capture.get(1).ok_or(anyhow!("invalid device_config output"))?.as_str().to_string();
- let value = capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str();
- flags.insert(key, value.to_string());
+ let value = FlagValue::try_from(
+ capture.get(2).ok_or(anyhow!("invalid device_config output"))?.as_str(),
+ )?;
+ flags.insert(key, value);
}
Ok(flags)
}
-fn read_device_config_output(command: &str) -> Result<String> {
- let output = Command::new("/system/bin/device_config").arg(command).output()?;
+fn read_device_config_output(command: &[&str]) -> Result<String> {
+ let output = Command::new("/system/bin/device_config").args(command).output()?;
if !output.status.success() {
let reason = match output.status.code() {
- Some(code) => format!("exit code {}", code),
+ Some(code) => {
+ let output = str::from_utf8(&output.stdout)?;
+ if !output.is_empty() {
+ format!("exit code {code}, output was {output}")
+ } else {
+ format!("exit code {code}")
+ }
+ }
None => "terminated by signal".to_string(),
};
- bail!("failed to execute device_config: {}", reason);
+ bail!("failed to access flag storage: {}", reason);
}
Ok(str::from_utf8(&output.stdout)?.to_string())
}
-fn read_device_config_flags() -> Result<HashMap<String, String>> {
- let list_output = read_device_config_output("list")?;
+fn read_device_config_flags() -> Result<HashMap<String, FlagValue>> {
+ let list_output = read_device_config_output(&["list"])?;
parse_device_config(&list_output)
}
-fn reconcile(pb_flags: &[Flag], dc_flags: HashMap<String, String>) -> Vec<Flag> {
+/// Parse the list of newline-separated staged flags.
+///
+/// The output is a newline-sepaarated list of entries which follow this format:
+/// `namespace*flagname=value`
+///
+/// The resulting map maps from `namespace/flagname` to `value`, if a staged flag exists for
+/// `namespace/flagname`.
+fn parse_staged_flags(raw: &str) -> Result<HashMap<String, FlagValue>> {
+ let mut flags = HashMap::new();
+ for line in raw.split('\n') {
+ match (line.find('*'), line.find('=')) {
+ (Some(star_index), Some(equal_index)) => {
+ let namespace = &line[..star_index];
+ let flag = &line[star_index + 1..equal_index];
+ if let Ok(value) = FlagValue::try_from(&line[equal_index + 1..]) {
+ flags.insert(namespace.to_owned() + "/" + flag, value);
+ }
+ }
+ _ => continue,
+ };
+ }
+ Ok(flags)
+}
+
+fn read_staged_flags() -> Result<HashMap<String, FlagValue>> {
+ let staged_flags_output = read_device_config_output(&["list", "staged"])?;
+ parse_staged_flags(&staged_flags_output)
+}
+
+fn reconcile(
+ pb_flags: &[Flag],
+ dc_flags: HashMap<String, FlagValue>,
+ staged_flags: HashMap<String, FlagValue>,
+) -> Vec<Flag> {
pb_flags
.iter()
.map(|f| {
- dc_flags
- .get(&format!("{}/{}.{}", f.namespace, f.package, f.name))
- .map(|value| {
- if value.eq(&f.value) {
- Flag { value_picked_from: ValuePickedFrom::Default, ..f.clone() }
- } else {
- Flag {
- value_picked_from: ValuePickedFrom::Server,
- value: value.to_string(),
- ..f.clone()
- }
- }
- })
- .unwrap_or(f.clone())
+ let server_override = dc_flags.get(&format!("{}/{}", f.namespace, f.qualified_name()));
+ let (value_picked_from, selected_value) = match server_override {
+ Some(value) if *value != f.value => (ValuePickedFrom::Server, *value),
+ _ => (ValuePickedFrom::Default, f.value),
+ };
+ Flag { value_picked_from, value: selected_value, ..f.clone() }
+ })
+ .map(|f| {
+ let staged_value = staged_flags
+ .get(&format!("{}/{}", f.namespace, f.qualified_name()))
+ .map(|value| if *value != f.value { Some(*value) } else { None })
+ .unwrap_or(None);
+ Flag { staged_value, ..f }
})
.collect()
}
@@ -141,10 +182,15 @@
fn list_flags() -> Result<Vec<Flag>> {
let pb_flags = read_pb_files()?;
let dc_flags = read_device_config_flags()?;
+ let staged_flags = read_staged_flags()?;
- let flags = reconcile(&pb_flags, dc_flags);
+ let flags = reconcile(&pb_flags, dc_flags, staged_flags);
Ok(flags)
}
+
+ fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()> {
+ read_device_config_output(&["put", namespace, qualified_name, value]).map(|_| ())
+ }
}
#[cfg(test)]
@@ -161,9 +207,9 @@
namespace_two/android.flag_two=nonsense
"#;
let expected = HashMap::from([
- ("namespace_one/com.foo.bar.flag_one".to_string(), "true".to_string()),
- ("namespace_one/com.foo.bar.flag_two".to_string(), "false".to_string()),
- ("namespace_two/android.flag_one".to_string(), "true".to_string()),
+ ("namespace_one/com.foo.bar.flag_one".to_string(), FlagValue::Enabled),
+ ("namespace_one/com.foo.bar.flag_two".to_string(), FlagValue::Disabled),
+ ("namespace_two/android.flag_one".to_string(), FlagValue::Enabled),
]);
let actual = parse_device_config(input).unwrap();
assert_eq!(expected, actual);
diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs
index 1e2a7a0..1c453c5 100644
--- a/tools/aconfig/aflags/src/main.rs
+++ b/tools/aconfig/aflags/src/main.rs
@@ -16,13 +16,16 @@
//! `aflags` is a device binary to read and write aconfig flags.
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
use clap::Parser;
mod device_config_source;
use device_config_source::DeviceConfigSource;
-#[derive(Clone)]
+mod aconfig_storage_source;
+use aconfig_storage_source::AconfigStorageSource;
+
+#[derive(Clone, PartialEq, Debug)]
enum FlagPermission {
ReadOnly,
ReadWrite,
@@ -37,7 +40,7 @@
}
}
-#[derive(Clone)]
+#[derive(Clone, Debug)]
enum ValuePickedFrom {
Default,
Server,
@@ -52,19 +55,66 @@
}
}
-#[derive(Clone)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum FlagValue {
+ Enabled,
+ Disabled,
+}
+
+impl TryFrom<&str> for FlagValue {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+ match value {
+ "true" | "enabled" => Ok(Self::Enabled),
+ "false" | "disabled" => Ok(Self::Disabled),
+ _ => Err(anyhow!("cannot convert string '{}' to FlagValue", value)),
+ }
+ }
+}
+
+impl ToString for FlagValue {
+ fn to_string(&self) -> String {
+ match &self {
+ Self::Enabled => "enabled".into(),
+ Self::Disabled => "disabled".into(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
struct Flag {
namespace: String,
name: String,
package: String,
container: String,
- value: String,
+ value: FlagValue,
+ staged_value: Option<FlagValue>,
permission: FlagPermission,
value_picked_from: ValuePickedFrom,
}
+impl Flag {
+ fn qualified_name(&self) -> String {
+ format!("{}.{}", self.package, self.name)
+ }
+
+ fn display_staged_value(&self) -> String {
+ match self.staged_value {
+ Some(v) => format!("(->{})", v.to_string()),
+ None => "-".to_string(),
+ }
+ }
+}
+
trait FlagSource {
fn list_flags() -> Result<Vec<Flag>>;
+ fn override_flag(namespace: &str, qualified_name: &str, value: &str) -> Result<()>;
+}
+
+enum FlagSourceType {
+ DeviceConfig,
+ AconfigStorage,
}
const ABOUT_TEXT: &str = "Tool for reading and writing flags.
@@ -76,6 +126,10 @@
* `package`: package set for this flag in its .aconfig definition.
* `flag_name`: flag name, also set in definition.
* `value`: the value read from the flag.
+ * `staged_value`: the value on next boot:
+ + `-`: same as current value
+ + `(->enabled) flipped to enabled on boot.
+ + `(->disabled) flipped to disabled on boot.
* `provenance`: one of:
+ `default`: the flag value comes from its build-time default.
+ `server`: the flag value comes from a server override.
@@ -93,26 +147,42 @@
#[derive(Parser, Debug)]
enum Command {
/// List all aconfig flags on this device.
- List,
+ List {
+ /// Read from the new flag storage.
+ #[clap(long)]
+ use_new_storage: bool,
+ },
+
+ /// Enable an aconfig flag on this device, on the next boot.
+ Enable {
+ /// <package>.<flag_name>
+ qualified_name: String,
+ },
+
+ /// Disable an aconfig flag on this device, on the next boot.
+ Disable {
+ /// <package>.<flag_name>
+ qualified_name: String,
+ },
}
struct PaddingInfo {
- longest_package_col: usize,
- longest_name_col: usize,
+ longest_flag_col: usize,
longest_val_col: usize,
+ longest_staged_val_col: usize,
longest_value_picked_from_col: usize,
longest_permission_col: usize,
}
fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String {
- let pkg = &flag.package;
- let p0 = info.longest_package_col + 1;
-
- let name = &flag.name;
- let p1 = info.longest_name_col + 1;
+ let full_name = flag.qualified_name();
+ let p0 = info.longest_flag_col + 1;
let val = flag.value.to_string();
- let p2 = info.longest_val_col + 1;
+ let p1 = info.longest_val_col + 1;
+
+ let staged_val = flag.display_staged_value();
+ let p2 = info.longest_staged_val_col + 1;
let value_picked_from = flag.value_picked_from.to_string();
let p3 = info.longest_value_picked_from_col + 1;
@@ -122,15 +192,40 @@
let container = &flag.container;
- format!("{pkg:p0$}{name:p1$}{val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n")
+ format!(
+ "{full_name:p0$}{val:p1$}{staged_val:p2$}{value_picked_from:p3$}{perm:p4$}{container}\n"
+ )
}
-fn list() -> Result<String> {
- let flags = DeviceConfigSource::list_flags()?;
+fn set_flag(qualified_name: &str, value: &str) -> Result<()> {
+ ensure!(nix::unistd::Uid::current().is_root(), "must be root to mutate flags");
+
+ let flags_binding = DeviceConfigSource::list_flags()?;
+ let flag = flags_binding.iter().find(|f| f.qualified_name() == qualified_name).ok_or(
+ anyhow!("no aconfig flag '{qualified_name}'. Does the flag have an .aconfig definition?"),
+ )?;
+
+ ensure!(flag.permission == FlagPermission::ReadWrite,
+ format!("could not write flag '{qualified_name}', it is read-only for the current release configuration."));
+
+ DeviceConfigSource::override_flag(&flag.namespace, qualified_name, value)?;
+
+ Ok(())
+}
+
+fn list(source_type: FlagSourceType) -> Result<String> {
+ let flags = match source_type {
+ FlagSourceType::DeviceConfig => DeviceConfigSource::list_flags()?,
+ FlagSourceType::AconfigStorage => AconfigStorageSource::list_flags()?,
+ };
let padding_info = PaddingInfo {
- longest_package_col: flags.iter().map(|f| f.package.len()).max().unwrap_or(0),
- longest_name_col: flags.iter().map(|f| f.name.len()).max().unwrap_or(0),
+ longest_flag_col: flags.iter().map(|f| f.qualified_name().len()).max().unwrap_or(0),
longest_val_col: flags.iter().map(|f| f.value.to_string().len()).max().unwrap_or(0),
+ longest_staged_val_col: flags
+ .iter()
+ .map(|f| f.display_staged_value().len())
+ .max()
+ .unwrap_or(0),
longest_value_picked_from_col: flags
.iter()
.map(|f| f.value_picked_from.to_string().len())
@@ -154,10 +249,14 @@
fn main() {
let cli = Cli::parse();
let output = match cli.command {
- Command::List => list(),
+ Command::List { use_new_storage: true } => list(FlagSourceType::AconfigStorage).map(Some),
+ Command::List { use_new_storage: false } => list(FlagSourceType::DeviceConfig).map(Some),
+ Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None),
+ Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None),
};
match output {
- Ok(text) => println!("{text}"),
- Err(msg) => println!("Error: {}", msg),
+ Ok(Some(text)) => println!("{text}"),
+ Ok(None) => (),
+ Err(message) => println!("Error: {message}"),
}
}
diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs
index a0c9ee8..7838b51 100644
--- a/tools/aconfig/printflags/src/main.rs
+++ b/tools/aconfig/printflags/src/main.rs
@@ -67,9 +67,23 @@
let device_config_flags = parse_device_config(dc_stdout);
// read aconfig_flags.pb files
+ let apex_pattern = Regex::new(r"^/apex/[^@]+\.[^@]+$").unwrap();
+ let mut mount_points = vec![
+ "system".to_string(),
+ "system_ext".to_string(),
+ "product".to_string(),
+ "vendor".to_string(),
+ ];
+ for apex in fs::read_dir("/apex")? {
+ let path_name = apex?.path().display().to_string();
+ if let Some(canonical_path) = apex_pattern.captures(&path_name) {
+ mount_points.push(canonical_path.get(0).unwrap().as_str().to_owned());
+ }
+ }
+
let mut flags: BTreeMap<String, Vec<String>> = BTreeMap::new();
- for partition in ["system", "system_ext", "product", "vendor"] {
- let path = format!("/{}/etc/aconfig_flags.pb", partition);
+ for mount_point in mount_points {
+ let path = format!("/{}/etc/aconfig_flags.pb", mount_point);
let Ok(bytes) = fs::read(&path) else {
eprintln!("warning: failed to read {}", path);
continue;
@@ -80,7 +94,7 @@
})?;
for flag in parsed_flags.parsed_flag {
let key = format!("{}/{}.{}", flag.namespace(), flag.package(), flag.name());
- let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), partition);
+ let value = format!("{:?} + {:?} ({})", flag.permission(), flag.state(), mount_point);
flags.entry(key).or_default().push(value);
}
}
diff --git a/tools/buildinfo.sh b/tools/buildinfo.sh
deleted file mode 100755
index 0ed9453..0000000
--- a/tools/buildinfo.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/bash
-
-echo "# begin build properties"
-echo "# autogenerated by buildinfo.sh"
-
-# The ro.build.id will be set dynamically by init, by appending the unique vbmeta digest.
-if [ "$BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT" = "true" ] ; then
- echo "ro.build.legacy.id=$BUILD_ID"
-else
- echo "ro.build.id?=$BUILD_ID"
-fi
-echo "ro.build.display.id?=$BUILD_DISPLAY_ID"
-echo "ro.build.version.incremental=$BUILD_NUMBER"
-echo "ro.build.version.sdk=$PLATFORM_SDK_VERSION"
-echo "ro.build.version.preview_sdk=$PLATFORM_PREVIEW_SDK_VERSION"
-echo "ro.build.version.preview_sdk_fingerprint=$PLATFORM_PREVIEW_SDK_FINGERPRINT"
-echo "ro.build.version.codename=$PLATFORM_VERSION_CODENAME"
-echo "ro.build.version.all_codenames=$PLATFORM_VERSION_ALL_CODENAMES"
-echo "ro.build.version.known_codenames=$PLATFORM_VERSION_KNOWN_CODENAMES"
-echo "ro.build.version.release=$PLATFORM_VERSION_LAST_STABLE"
-echo "ro.build.version.release_or_codename=$PLATFORM_VERSION"
-echo "ro.build.version.release_or_preview_display=$PLATFORM_DISPLAY_VERSION"
-echo "ro.build.version.security_patch=$PLATFORM_SECURITY_PATCH"
-echo "ro.build.version.base_os=$PLATFORM_BASE_OS"
-echo "ro.build.version.min_supported_target_sdk=$PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION"
-echo "ro.build.date=`$DATE`"
-echo "ro.build.date.utc=`$DATE +%s`"
-echo "ro.build.type=$TARGET_BUILD_TYPE"
-echo "ro.build.user=$BUILD_USERNAME"
-echo "ro.build.host=$BUILD_HOSTNAME"
-# TODO: Remove any tag-related optional property declarations once the goals
-# from go/arc-android-sigprop-changes have been achieved.
-echo "ro.build.tags?=$BUILD_VERSION_TAGS"
-echo "ro.build.flavor=$TARGET_BUILD_FLAVOR"
-
-# These values are deprecated, use "ro.product.cpu.abilist"
-# instead (see below).
-echo "# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,"
-echo "# use ro.product.cpu.abilist instead."
-echo "ro.product.cpu.abi=$TARGET_CPU_ABI"
-if [ -n "$TARGET_CPU_ABI2" ] ; then
- echo "ro.product.cpu.abi2=$TARGET_CPU_ABI2"
-fi
-
-if [ -n "$PRODUCT_DEFAULT_LOCALE" ] ; then
- echo "ro.product.locale=$PRODUCT_DEFAULT_LOCALE"
-fi
-echo "ro.wifi.channels=$PRODUCT_DEFAULT_WIFI_CHANNELS"
-
-echo "# ro.build.product is obsolete; use ro.product.device"
-echo "ro.build.product=$TARGET_DEVICE"
-
-echo "# Do not try to parse description or thumbprint"
-echo "ro.build.description?=$PRIVATE_BUILD_DESC"
-if [ -n "$BUILD_THUMBPRINT" ] ; then
- echo "ro.build.thumbprint=$BUILD_THUMBPRINT"
-fi
-
-echo "# end build properties"
diff --git a/tools/finalization/OWNERS b/tools/finalization/OWNERS
index 518b60d..b00b774 100644
--- a/tools/finalization/OWNERS
+++ b/tools/finalization/OWNERS
@@ -1,5 +1,7 @@
include platform/build/soong:/OWNERS
-smoreland@google.com
-alexbuy@google.com
+amhk@google.com
+gurpreetgs@google.com
+michaelwr@google.com
patb@google.com
+smoreland@google.com
zyy@google.com
diff --git a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
index 37c0011..671b036 100755
--- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
@@ -69,6 +69,33 @@
rm "$tmpfile"
}
+function bumpSdkExtensionsVersion() {
+ local SDKEXT="packages/modules/SdkExtensions/"
+
+ # This used to call bump_sdk.sh utility.
+ # However due to TS, we have to build the gen_sdk with a correct set of settings.
+
+ # "$top/packages/modules/SdkExtensions/gen_sdk/bump_sdk.sh" ${FINAL_MAINLINE_EXTENSION}
+ # Leave the last commit as a set of modified files.
+ # The code to create a finalization topic will pick it up later.
+ # git -C ${SDKEXT} reset HEAD~1
+
+ local sdk="${FINAL_MAINLINE_EXTENSION}"
+ local modules_arg=
+
+ TARGET_PRODUCT=aosp_arm64 \
+ TARGET_RELEASE=fina_1 \
+ TARGET_BUILD_VARIANT=userdebug \
+ DIST_DIR=out/dist \
+ $top/build/soong/soong_ui.bash --make-mode --soong-only gen_sdk
+
+ ANDROID_BUILD_TOP="$top" out/soong/host/linux-x86/bin/gen_sdk \
+ --database ${SDKEXT}/gen_sdk/extensions_db.textpb \
+ --action new_sdk \
+ --sdk "$sdk" \
+ $modules_arg
+}
+
function finalize_aidl_vndk_sdk_resources() {
local top="$(dirname "$0")"/../../../..
source $top/build/make/tools/finalization/environment.sh
@@ -76,9 +103,6 @@
local SDK_CODENAME="public static final int $FINAL_PLATFORM_CODENAME_JAVA = CUR_DEVELOPMENT;"
local SDK_VERSION="public static final int $FINAL_PLATFORM_CODENAME_JAVA = $FINAL_PLATFORM_SDK_VERSION;"
- # default target to modify tree and build SDK
- local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
-
# The full process can be found at (INTERNAL) go/android-sdk-finalization.
# apply droidstubs hack to prevent tools from incrementing an API version
@@ -87,20 +111,12 @@
# bionic/NDK
finalize_bionic_ndk
- # VNDK definitions for new SDK version
- cp "$top/development/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-current.txt" \
- "$top/development/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-$FINAL_PLATFORM_SDK_VERSION.txt"
+ # pre-finalization build target (trunk)
+ local aidl_m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=trunk TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
+ AIDL_TRANSITIVE_FREEZE=true $aidl_m aidl-freeze-api
- AIDL_TRANSITIVE_FREEZE=true $m aidl-freeze-api create_reference_dumps
-
- # Generate ABI dumps
- ANDROID_BUILD_TOP="$top" out/host/linux-x86/bin/create_reference_dumps
-
- echo "NOTE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF (until 'DONE')"
- # Update new versions of files. See update-vndk-list.sh (which requires envsetup.sh)
- $m check-vndk-list || \
- { cp $top/out/soong/vndk/vndk.libraries.txt $top/build/make/target/product/gsi/current.txt; }
- echo "DONE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF"
+ # TODO(b/309880485)
+ # Add back create_reference_dumps and $top/build/make/target/product/gsi/current.txt
# Finalize SDK
@@ -114,9 +130,6 @@
sed -i -e 's/Pkg\.Revision.*/Pkg\.Revision=${PLATFORM_SDK_VERSION}.0.0/g' $build_tools_source
# build/make
- local version_defaults="$top/build/make/core/version_defaults.mk"
- sed -i -e "s/PLATFORM_SDK_VERSION := .*/PLATFORM_SDK_VERSION := ${FINAL_PLATFORM_SDK_VERSION}/g" $version_defaults
- sed -i -e "s/PLATFORM_VERSION_LAST_STABLE := .*/PLATFORM_VERSION_LAST_STABLE := ${FINAL_PLATFORM_VERSION}/g" $version_defaults
sed -i -e "s/sepolicy_major_vers := .*/sepolicy_major_vers := ${FINAL_PLATFORM_SDK_VERSION}/g" "$top/build/make/core/config.mk"
cp "$top/build/make/target/product/gsi/current.txt" "$top/build/make/target/product/gsi/$FINAL_PLATFORM_SDK_VERSION.txt"
@@ -156,18 +169,14 @@
sed -i -e "/=.*$((${FINAL_PLATFORM_SDK_VERSION}-1)),/a \\ SDK_${FINAL_PLATFORM_CODENAME_JAVA} = ${FINAL_PLATFORM_SDK_VERSION}," "$top/frameworks/base/tools/aapt2/SdkConstants.h"
# Bump Mainline SDK extension version.
- local SDKEXT="packages/modules/SdkExtensions/"
- "$top/packages/modules/SdkExtensions/gen_sdk/bump_sdk.sh" ${FINAL_MAINLINE_EXTENSION}
- # Leave the last commit as a set of modified files.
- # The code to create a finalization topic will pick it up later.
- git -C ${SDKEXT} reset HEAD~1
+ bumpSdkExtensionsVersion
- local version_defaults="$top/build/make/core/version_defaults.mk"
- sed -i -e "s/PLATFORM_SDK_EXTENSION_VERSION := .*/PLATFORM_SDK_EXTENSION_VERSION := ${FINAL_MAINLINE_EXTENSION}/g" $version_defaults
+ # target to build SDK
+ local sdk_m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
# Force update current.txt
- $m clobber
- $m update-api
+ $sdk_m clobber
+ $sdk_m update-api
}
finalize_aidl_vndk_sdk_resources
diff --git a/tools/finalization/finalize-sdk-rel.sh b/tools/finalization/finalize-sdk-rel.sh
index d4ed380..245305b 100755
--- a/tools/finalization/finalize-sdk-rel.sh
+++ b/tools/finalization/finalize-sdk-rel.sh
@@ -33,10 +33,6 @@
# in REL mode, resources would correctly set the resources_sdk_int, no fix required
revert_resources_sdk_int_fix
- # build/make/core/version_defaults.mk
- # Mark all versions "released".
- sed -i 's/\(PLATFORM_VERSION_CODENAME\.[^[:space:]]*\) := [^[:space:]]*/\1 := REL/g' "$top/build/make/core/version_defaults.mk"
-
# cts
echo "$FINAL_PLATFORM_VERSION" > "$top/cts/tests/tests/os/assets/platform_versions.txt"
if [ "$FINAL_PLATFORM_CODENAME" != "$CURRENT_PLATFORM_CODENAME" ]; then
@@ -47,18 +43,19 @@
# system/sepolicy
system/sepolicy/tools/finalize-sdk-rel.sh "$top" "$FINAL_PLATFORM_SDK_VERSION"
- # prebuilts/abi-dumps/ndk
- mkdir -p "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
- cp -r "$top/prebuilts/abi-dumps/ndk/current/64/" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION/"
-
# prebuilts/abi-dumps/platform
mkdir -p "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION"
cp -r "$top/prebuilts/abi-dumps/platform/current/64/" "$top/prebuilts/abi-dumps/platform/$FINAL_PLATFORM_SDK_VERSION/"
- if [ "$FINAL_STATE" != "sdk" ] || [ "$FINAL_PLATFORM_CODENAME" == "$CURRENT_PLATFORM_CODENAME" ] ; then
+ # TODO(b/309880485)
+ # uncomment and update
+ # prebuilts/abi-dumps/ndk
+ #mkdir -p "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION"
+ #cp -r "$top/prebuilts/abi-dumps/ndk/current/64/" "$top/prebuilts/abi-dumps/ndk/$FINAL_PLATFORM_SDK_VERSION/"
+ #if [ "$FINAL_STATE" != "sdk" ] || [ "$FINAL_PLATFORM_CODENAME" == "$CURRENT_PLATFORM_CODENAME" ] ; then
# prebuilts/abi-dumps/vndk
- mv "$top/prebuilts/abi-dumps/vndk/$CURRENT_PLATFORM_CODENAME" "$top/prebuilts/abi-dumps/vndk/$FINAL_PLATFORM_SDK_VERSION"
- fi;
+ #mv "$top/prebuilts/abi-dumps/vndk/$CURRENT_PLATFORM_CODENAME" "$top/prebuilts/abi-dumps/vndk/$FINAL_PLATFORM_SDK_VERSION"
+ #fi;
}
finalize_sdk_rel
diff --git a/tools/finalization/localonly-steps.sh b/tools/finalization/localonly-steps.sh
index 7318ca1..bebd563 100755
--- a/tools/finalization/localonly-steps.sh
+++ b/tools/finalization/localonly-steps.sh
@@ -7,17 +7,17 @@
source $top/build/make/tools/finalization/environment.sh
# default target to modify tree and build SDK
- local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
+ local m="$top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=aosp_arm64 TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug DIST_DIR=out/dist"
# adb keys
$m adb
LOGNAME=android-eng HOSTNAME=google.com "$top/out/host/linux-x86/bin/adb" keygen "$top/vendor/google/security/adb/${FINAL_PLATFORM_VERSION}.adb_key"
# Build Platform SDKs.
- $top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=sdk TARGET_BUILD_VARIANT=userdebug sdk dist sdk_repo DIST_DIR=out/dist
+ $top/build/soong/soong_ui.bash --make-mode TARGET_PRODUCT=sdk TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug sdk dist sdk_repo DIST_DIR=out/dist
# Build Modules SDKs.
- TARGET_BUILD_VARIANT=userdebug UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true DIST_DIR=out/dist "$top/vendor/google/build/mainline_modules_sdks.sh" --build-release=latest
+ TARGET_RELEASE=fina_1 TARGET_BUILD_VARIANT=userdebug UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true DIST_DIR=out/dist "$top/vendor/google/build/mainline_modules_sdks.sh" --build-release=latest
# Update prebuilts.
"$top/prebuilts/build-tools/path/linux-x86/python3" -W ignore::DeprecationWarning "$top/prebuilts/sdk/update_prebuilts.py" --local_mode -f ${FINAL_PLATFORM_SDK_VERSION} -e ${FINAL_MAINLINE_EXTENSION} --bug 1 1
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index 55fdca4..bd9543a 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -35,7 +35,6 @@
srcs: ["fs_config.c"],
shared_libs: [
"libcutils",
- "libselinux",
],
cflags: ["-Werror"],
}
diff --git a/tools/fs_config/fs_config.c b/tools/fs_config/fs_config.c
index 2a75add..80bd3c1 100644
--- a/tools/fs_config/fs_config.c
+++ b/tools/fs_config/fs_config.c
@@ -22,9 +22,6 @@
#include <string.h>
#include <inttypes.h>
-#include <selinux/selinux.h>
-#include <selinux/label.h>
-
#include "private/android_filesystem_config.h"
#include "private/fs_config.h"
@@ -35,8 +32,8 @@
//
// After the first 4 columns, optional key=value pairs are emitted
// for each file. Currently, the following keys are supported:
-// * -S: selabel=[selinux_label]
-// * -C: capabilities=[hex capabilities value]
+//
+// -C: capabilities=[hex capabilities value]
//
// Example input:
//
@@ -48,45 +45,24 @@
// system/etc/dbus.conf 1002 1002 440
// data/app 1000 1000 771
//
-// or if, for example, -S is used:
-//
-// system/etc/dbus.conf 1002 1002 440 selabel=u:object_r:system_file:s0
-// data/app 1000 1000 771 selabel=u:object_r:apk_data_file:s0
-//
// Note that the output will omit the trailing slash from
// directories.
-static struct selabel_handle* get_sehnd(const char* context_file) {
- struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, context_file } };
- struct selabel_handle* sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1);
-
- if (!sehnd) {
- perror("error running selabel_open");
- exit(EXIT_FAILURE);
- }
- return sehnd;
-}
-
static void usage() {
- fprintf(stderr, "Usage: fs_config [-D product_out_path] [-S context_file] [-R root] [-C]\n");
+ fprintf(stderr, "Usage: fs_config [-D product_out_path] [-R root] [-C]\n");
}
int main(int argc, char** argv) {
char buffer[1024];
- const char* context_file = NULL;
const char* product_out_path = NULL;
char* root_path = NULL;
- struct selabel_handle* sehnd = NULL;
int print_capabilities = 0;
int opt;
- while((opt = getopt(argc, argv, "CS:R:D:")) != -1) {
+ while((opt = getopt(argc, argv, "CR:D:")) != -1) {
switch(opt) {
case 'C':
print_capabilities = 1;
break;
- case 'S':
- context_file = optarg;
- break;
case 'R':
root_path = optarg;
break;
@@ -99,10 +75,6 @@
}
}
- if (context_file != NULL) {
- sehnd = get_sehnd(context_file);
- }
-
if (root_path != NULL) {
size_t root_len = strlen(root_path);
/* Trim any trailing slashes from the root path. */
@@ -141,33 +113,6 @@
}
printf("%s %d %d %o", buffer, uid, gid, mode);
- if (sehnd != NULL) {
- size_t buffer_strlen = strnlen(buffer, sizeof(buffer));
- if (buffer_strlen >= sizeof(buffer)) {
- fprintf(stderr, "non null terminated buffer, aborting\n");
- exit(EXIT_FAILURE);
- }
- size_t full_name_size = buffer_strlen + 2;
- char* full_name = (char*) malloc(full_name_size);
- if (full_name == NULL) {
- perror("malloc");
- exit(EXIT_FAILURE);
- }
-
- full_name[0] = '/';
- strncpy(full_name + 1, buffer, full_name_size - 1);
- full_name[full_name_size - 1] = '\0';
-
- char* secontext;
- if (selabel_lookup(sehnd, &secontext, full_name, ( mode | (is_dir ? S_IFDIR : S_IFREG)))) {
- secontext = strdup("u:object_r:unlabeled:s0");
- }
-
- printf(" selabel=%s", secontext);
- free(full_name);
- freecon(secontext);
- }
-
if (print_capabilities) {
printf(" capabilities=0x%" PRIx64, capabilities);
}
diff --git a/tools/fs_get_stats/Android.bp b/tools/fs_get_stats/Android.bp
deleted file mode 100644
index 0697999..0000000
--- a/tools/fs_get_stats/Android.bp
+++ /dev/null
@@ -1,14 +0,0 @@
-package {
- // See: http://go/android-license-faq
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary_host {
- name: "fs_get_stats",
- srcs: ["fs_get_stats.c"],
- cflags: ["-Wall", "-Werror"],
- shared_libs: [
- "libcutils",
- "liblog",
- ],
-}
diff --git a/tools/fs_get_stats/fs_get_stats.c b/tools/fs_get_stats/fs_get_stats.c
deleted file mode 100644
index 64ef0e2..0000000
--- a/tools/fs_get_stats/fs_get_stats.c
+++ /dev/null
@@ -1,67 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <private/android_filesystem_config.h>
-#include <private/fs_config.h>
-
-#define DO_DEBUG 1
-
-#define ERROR(fmt,args...) \
- do { \
- fprintf(stderr, "%s:%d: ERROR: " fmt, \
- __FILE__, __LINE__, ##args); \
- } while (0)
-
-#if DO_DEBUG
-#define DEBUG(fmt,args...) \
- do { fprintf(stderr, "DEBUG: " fmt, ##args); } while(0)
-#else
-#define DEBUG(x...) do {} while(0)
-#endif
-
-void
-print_help(void)
-{
- fprintf(stderr, "fs_get_stats: retrieve the target file stats "
- "for the specified file\n");
- fprintf(stderr, "usage: fs_get_stats cur_perms is_dir filename targetout\n");
- fprintf(stderr, "\tcur_perms - The current permissions of "
- "the file\n");
- fprintf(stderr, "\tis_dir - Is filename is a dir, 1. Otherwise, 0.\n");
- fprintf(stderr, "\tfilename - The filename to lookup\n");
- fprintf(stderr, "\ttargetout - The target out path to query device specific FS configs\n");
- fprintf(stderr, "\n");
-}
-
-int
-main(int argc, const char *argv[])
-{
- char *endptr;
- char is_dir = 0;
- unsigned perms = 0;
- unsigned uid = (unsigned)-1;
- unsigned gid = (unsigned)-1;
-
- if (argc < 5) {
- ERROR("Invalid arguments\n");
- print_help();
- exit(-1);
- }
-
- perms = (unsigned)strtoul(argv[1], &endptr, 0);
- if (!endptr || (endptr == argv[1]) || (*endptr != '\0')) {
- ERROR("current permissions must be a number. Got '%s'.\n", argv[1]);
- exit(-1);
- }
-
- if (!strcmp(argv[2], "1"))
- is_dir = 1;
-
- uint64_t capabilities;
- fs_config(argv[3], is_dir, argv[4], &uid, &gid, &perms, &capabilities);
- fprintf(stdout, "%d %d 0%o\n", uid, gid, perms);
-
- return 0;
-}
diff --git a/tools/ide_query/cc_analyzer/Android.bp b/tools/ide_query/cc_analyzer/Android.bp
new file mode 100644
index 0000000..3cbbb05
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/Android.bp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "ide_query_cc_analyzer_defaults",
+ compile_multilib: "64",
+ defaults: [
+ "llvm-build-host-tools-defaults",
+ ],
+ cflags: [
+ // LLVM Sources do have unused parameters :(
+ "-Wno-unused-parameter",
+ ],
+ target: {
+ host: {
+ cppflags: [
+ "-fno-rtti",
+ ],
+ },
+ },
+}
+
+cc_library_host_static {
+ name: "builtin_headers",
+ srcs: ["builtin_headers.cc"],
+ generated_headers: ["clang_builtin_headers_resources"],
+ defaults: ["ide_query_cc_analyzer_defaults"],
+}
+
+cc_library_host_static {
+ name: "include_scanner",
+ srcs: ["include_scanner.cc"],
+ shared_libs: ["libclang-cpp_host"],
+ static_libs: ["builtin_headers"],
+ defaults: ["ide_query_cc_analyzer_defaults"],
+}
+
+cc_library_host_static {
+ name: "analyzer",
+ srcs: ["analyzer.cc"],
+ shared_libs: ["libclang-cpp_host"],
+ static_libs: [
+ "include_scanner",
+ "ide_query_proto",
+ ],
+ defaults: ["ide_query_cc_analyzer_defaults"],
+}
+
+cc_binary_host {
+ name: "ide_query_cc_analyzer",
+ defaults: ["ide_query_cc_analyzer_defaults"],
+ srcs: ["main.cc"],
+ shared_libs: [
+ "libclang-cpp_host",
+ "libprotobuf-cpp-full",
+ ],
+ static_libs: [
+ "ide_query_proto",
+ "builtin_headers",
+ "include_scanner",
+ "analyzer",
+ ],
+}
diff --git a/tools/ide_query/cc_analyzer/analyzer.cc b/tools/ide_query/cc_analyzer/analyzer.cc
new file mode 100644
index 0000000..bb7ca0b
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/analyzer.cc
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+#include "analyzer.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/JSONCompilationDatabase.h"
+#include "ide_query.pb.h"
+#include "include_scanner.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+namespace tools::ide_query::cc_analyzer {
+namespace {
+llvm::Expected<std::unique_ptr<clang::tooling::CompilationDatabase>> LoadCompDB(
+ llvm::StringRef comp_db_path) {
+ std::string err;
+ std::unique_ptr<clang::tooling::CompilationDatabase> db =
+ clang::tooling::JSONCompilationDatabase::loadFromFile(
+ comp_db_path, err, clang::tooling::JSONCommandLineSyntax::AutoDetect);
+ if (!db) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Failed to load CDB: " + err);
+ }
+ // Provide some heuristic support for missing files.
+ return inferMissingCompileCommands(std::move(db));
+}
+} // namespace
+
+::ide_query::DepsResponse GetDeps(::ide_query::RepoState state) {
+ ::ide_query::DepsResponse results;
+ auto db = LoadCompDB(state.comp_db_path());
+ if (!db) {
+ results.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ results.mutable_status()->set_message(llvm::toString(db.takeError()));
+ return results;
+ }
+ for (llvm::StringRef active_file : state.active_file_path()) {
+ auto& result = *results.add_deps();
+
+ llvm::SmallString<256> abs_file(state.repo_dir());
+ llvm::sys::path::append(abs_file, active_file);
+ auto cmds = db->get()->getCompileCommands(active_file);
+ if (cmds.empty()) {
+ result.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ result.mutable_status()->set_message(
+ llvm::Twine("Can't find compile flags for file: ", abs_file).str());
+ continue;
+ }
+ result.set_source_file(active_file.str());
+ llvm::StringRef file = cmds[0].Filename;
+ if (llvm::StringRef actual_file(cmds[0].Heuristic);
+ actual_file.consume_front("inferred from ")) {
+ file = actual_file;
+ }
+ // TODO: Query ninja graph to figure out a minimal set of targets to build.
+ result.add_build_target(file.str() + "^");
+ }
+ return results;
+}
+
+::ide_query::IdeAnalysis GetBuildInputs(::ide_query::RepoState state) {
+ auto db = LoadCompDB(state.comp_db_path());
+ ::ide_query::IdeAnalysis results;
+ if (!db) {
+ results.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ results.mutable_status()->set_message(llvm::toString(db.takeError()));
+ return results;
+ }
+ std::string repo_dir = state.repo_dir();
+ if (!repo_dir.empty() && repo_dir.back() == '/') repo_dir.pop_back();
+
+ llvm::SmallString<256> genfile_root_abs(repo_dir);
+ llvm::sys::path::append(genfile_root_abs, state.out_dir());
+ if (genfile_root_abs.empty() || genfile_root_abs.back() != '/') {
+ genfile_root_abs.push_back('/');
+ }
+
+ results.set_build_artifact_root(state.out_dir());
+ for (llvm::StringRef active_file : state.active_file_path()) {
+ auto& result = *results.add_sources();
+ result.set_path(active_file.str());
+
+ llvm::SmallString<256> abs_file(repo_dir);
+ llvm::sys::path::append(abs_file, active_file);
+ auto cmds = db->get()->getCompileCommands(abs_file);
+ if (cmds.empty()) {
+ result.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ result.mutable_status()->set_message(
+ llvm::Twine("Can't find compile flags for file: ", abs_file).str());
+ continue;
+ }
+ const auto& cmd = cmds.front();
+ llvm::StringRef working_dir = cmd.Directory;
+ if (!working_dir.consume_front(repo_dir)) {
+ result.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ result.mutable_status()->set_message("Command working dir " +
+ working_dir.str() +
+ " outside repository " + repo_dir);
+ continue;
+ }
+ working_dir = working_dir.ltrim('/');
+ result.set_working_dir(working_dir.str());
+ for (auto& arg : cmd.CommandLine) result.add_compiler_arguments(arg);
+
+ auto includes =
+ ScanIncludes(cmds.front(), llvm::vfs::createPhysicalFileSystem());
+ if (!includes) {
+ result.mutable_status()->set_code(::ide_query::Status::FAILURE);
+ result.mutable_status()->set_message(
+ llvm::toString(includes.takeError()));
+ continue;
+ }
+
+ for (auto& [req_input, contents] : *includes) {
+ llvm::StringRef req_input_ref(req_input);
+ // We're only interested in generated files.
+ if (!req_input_ref.consume_front(genfile_root_abs)) continue;
+ auto& genfile = *result.add_generated();
+ genfile.set_path(req_input_ref.str());
+ genfile.set_contents(std::move(contents));
+ }
+ }
+ return results;
+}
+} // namespace tools::ide_query::cc_analyzer
diff --git a/tools/ide_query/cc_analyzer/analyzer.h b/tools/ide_query/cc_analyzer/analyzer.h
new file mode 100644
index 0000000..3133795
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/analyzer.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef _TOOLS_IDE_QUERY_CC_ANALYZER_ANALYZER_H_
+#define _TOOLS_IDE_QUERY_CC_ANALYZER_ANALYZER_H_
+
+#include "ide_query.pb.h"
+
+namespace tools::ide_query::cc_analyzer {
+
+// Scans the build graph and returns target names from the build graph to
+// generate all the dependencies for the active files.
+::ide_query::DepsResponse GetDeps(::ide_query::RepoState state);
+
+// Scans the sources and returns all the source files required for analyzing the
+// active files.
+::ide_query::IdeAnalysis GetBuildInputs(::ide_query::RepoState state);
+
+} // namespace tools::ide_query::cc_analyzer
+
+#endif
diff --git a/tools/ide_query/cc_analyzer/builtin_headers.cc b/tools/ide_query/cc_analyzer/builtin_headers.cc
new file mode 100644
index 0000000..6d44ce7
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/builtin_headers.cc
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+#include "builtin_headers.h"
+
+#include <cstddef>
+
+#include "clang_builtin_headers_resources.inc"
+
+const struct FileToc *builtin_headers_create() { return kPackedFiles; }
+size_t builtin_headers_size() {
+ return sizeof(kPackedFiles) / sizeof(FileToc) - 1;
+}
diff --git a/tools/ide_query/cc_analyzer/builtin_headers.h b/tools/ide_query/cc_analyzer/builtin_headers.h
new file mode 100644
index 0000000..eda722f
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/builtin_headers.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+#ifndef _TOOLS_IDE_QUERY_CC_ANALYZER_BUILTIN_HEADERS_H_
+#define _TOOLS_IDE_QUERY_CC_ANALYZER_BUILTIN_HEADERS_H_
+
+#include <cstddef>
+
+struct FileToc {
+ const char *name;
+ const char *data;
+};
+
+const struct FileToc *builtin_headers_create();
+size_t builtin_headers_size();
+
+#endif
diff --git a/tools/ide_query/cc_analyzer/include_scanner.cc b/tools/ide_query/cc_analyzer/include_scanner.cc
new file mode 100644
index 0000000..8916a3e
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/include_scanner.cc
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+#include "include_scanner.h"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "builtin_headers.h"
+#include "clang/Basic/FileEntry.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/Module.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Tooling/ArgumentsAdjusters.h"
+#include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+namespace tools::ide_query::cc_analyzer {
+namespace {
+std::string CleanPath(llvm::StringRef path) {
+ // both ./ and ../ has `./` in them.
+ if (!path.contains("./")) return path.str();
+ llvm::SmallString<256> clean_path(path);
+ llvm::sys::path::remove_dots(clean_path, /*remove_dot_dot=*/true);
+ return clean_path.str().str();
+}
+
+// Returns the absolute path to file_name, treating it as relative to cwd if it
+// isn't already absolute.
+std::string GetAbsolutePath(llvm::StringRef cwd, llvm::StringRef file_name) {
+ if (llvm::sys::path::is_absolute(file_name)) return CleanPath(file_name);
+ llvm::SmallString<256> abs_path(cwd);
+ llvm::sys::path::append(abs_path, file_name);
+ llvm::sys::path::remove_dots(abs_path, /*remove_dot_dot=*/true);
+ return abs_path.str().str();
+}
+
+class IncludeRecordingPP : public clang::PPCallbacks {
+ public:
+ explicit IncludeRecordingPP(
+ std::unordered_map<std::string, std::string> &abs_paths, std::string cwd,
+ const clang::SourceManager &sm)
+ : abs_paths_(abs_paths), cwd_(std::move(cwd)), sm_(sm) {}
+
+ void LexedFileChanged(clang::FileID FID, LexedFileChangeReason Reason,
+ clang::SrcMgr::CharacteristicKind FileType,
+ clang::FileID PrevFID,
+ clang::SourceLocation Loc) override {
+ auto file_entry = sm_.getFileEntryRefForID(FID);
+ if (!file_entry) return;
+ auto abs_path = GetAbsolutePath(cwd_, file_entry->getName());
+ auto [it, inserted] = abs_paths_.try_emplace(abs_path);
+ if (inserted) it->second = sm_.getBufferData(FID);
+ }
+
+ std::unordered_map<std::string, std::string> &abs_paths_;
+ const std::string cwd_;
+ const clang::SourceManager &sm_;
+};
+
+class IncludeScanningAction final : public clang::PreprocessOnlyAction {
+ public:
+ explicit IncludeScanningAction(
+ std::unordered_map<std::string, std::string> &abs_paths)
+ : abs_paths_(abs_paths) {}
+ bool BeginSourceFileAction(clang::CompilerInstance &ci) override {
+ std::string cwd;
+ auto cwd_or_err = ci.getVirtualFileSystem().getCurrentWorkingDirectory();
+ if (!cwd_or_err || cwd_or_err.get().empty()) return false;
+ cwd = cwd_or_err.get();
+ ci.getPreprocessor().addPPCallbacks(std::make_unique<IncludeRecordingPP>(
+ abs_paths_, std::move(cwd), ci.getSourceManager()));
+ return true;
+ }
+
+ private:
+ std::unordered_map<std::string, std::string> &abs_paths_;
+};
+
+llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayBuiltinHeaders(
+ std::vector<std::string> &argv,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> base) {
+ static constexpr llvm::StringLiteral kResourceDir = "/resources";
+ llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay(
+ new llvm::vfs::OverlayFileSystem(std::move(base)));
+ llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> builtin_headers(
+ new llvm::vfs::InMemoryFileSystem);
+
+ llvm::SmallString<256> file_path;
+ for (const auto &builtin_header :
+ llvm::ArrayRef(builtin_headers_create(), builtin_headers_size())) {
+ file_path.clear();
+ llvm::sys::path::append(file_path, kResourceDir, "include",
+ builtin_header.name);
+ builtin_headers->addFile(
+ file_path,
+ /*ModificationTime=*/0,
+ llvm::MemoryBuffer::getMemBuffer(builtin_header.data));
+ }
+ overlay->pushOverlay(std::move(builtin_headers));
+ argv.insert(llvm::find(argv, "--"),
+ llvm::Twine("-resource-dir=", kResourceDir).str());
+ return overlay;
+}
+
+} // namespace
+
+llvm::Expected<std::vector<std::pair<std::string, std::string>>> ScanIncludes(
+ const clang::tooling::CompileCommand &cmd,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+ if (fs->setCurrentWorkingDirectory(cmd.Directory)) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "Failed to set working directory to: " + cmd.Directory);
+ }
+
+ auto main_file = fs->getBufferForFile(cmd.Filename);
+ if (!main_file) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Main file doesn't exist: " + cmd.Filename);
+ }
+ std::unordered_map<std::string, std::string> abs_paths;
+ abs_paths.try_emplace(GetAbsolutePath(cmd.Directory, cmd.Filename),
+ main_file.get()->getBuffer().str());
+
+ std::vector<std::string> argv = cmd.CommandLine;
+ fs = OverlayBuiltinHeaders(argv, std::move(fs));
+
+ llvm::IntrusiveRefCntPtr<clang::FileManager> files(
+ new clang::FileManager(/*FileSystemOpts=*/{}, std::move(fs)));
+ clang::tooling::ToolInvocation tool(
+ argv, std::make_unique<IncludeScanningAction>(abs_paths), files.get());
+ if (!tool.run()) {
+ return llvm::createStringError(
+ llvm::inconvertibleErrorCode(),
+ "Failed to scan includes for: " + cmd.Filename);
+ }
+
+ std::vector<std::pair<std::string, std::string>> result;
+ result.reserve(abs_paths.size());
+ for (auto &entry : abs_paths) {
+ result.emplace_back(entry.first, std::move(entry.second));
+ }
+ return result;
+}
+} // namespace tools::ide_query::cc_analyzer
diff --git a/tools/ide_query/cc_analyzer/include_scanner.h b/tools/ide_query/cc_analyzer/include_scanner.h
new file mode 100644
index 0000000..e814e72
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/include_scanner.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#ifndef _TOOLS_IDE_QUERY_CC_ANALYZER_INCLUDE_SCANNER_H_
+#define _TOOLS_IDE_QUERY_CC_ANALYZER_INCLUDE_SCANNER_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "clang/Tooling/CompilationDatabase.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+namespace tools::ide_query::cc_analyzer {
+
+// Returns absolute paths and contents for all the includes necessary for
+// compiling source file in command.
+llvm::Expected<std::vector<std::pair<std::string, std::string>>> ScanIncludes(
+ const clang::tooling::CompileCommand &cmd,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
+
+} // namespace tools::ide_query::cc_analyzer
+
+#endif
diff --git a/tools/ide_query/cc_analyzer/main.cc b/tools/ide_query/cc_analyzer/main.cc
new file mode 100644
index 0000000..8e00c63
--- /dev/null
+++ b/tools/ide_query/cc_analyzer/main.cc
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+// Driver for c++ extractor. Operates in two modes:
+// - DEPS, scans build graph for active files and reports targets that need to
+// be build for analyzing that file.
+// - INPUTS, scans the source code for active files and returns all the sources
+// required for analyzing that file.
+//
+// Uses stdin/stdout to take in requests and provide responses.
+#include <unistd.h>
+
+#include <memory>
+#include <utility>
+
+#include "analyzer.h"
+#include "google/protobuf/message.h"
+#include "ide_query.pb.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace {
+enum class OpMode {
+ DEPS = 0,
+ INPUTS = 1,
+};
+llvm::cl::opt<OpMode> mode{
+ "mode",
+ llvm::cl::values(clEnumValN(OpMode::DEPS, "deps",
+ "Figure out targets that need to be build"),
+ clEnumValN(OpMode::INPUTS, "inputs",
+ "Figure out generated files used")),
+ llvm::cl::desc("Print the list of headers to insert and remove"),
+};
+
+ide_query::IdeAnalysis ReturnError(llvm::StringRef message) {
+ ide_query::IdeAnalysis result;
+ result.mutable_status()->set_code(ide_query::Status::FAILURE);
+ result.mutable_status()->set_message(message.str());
+ return result;
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ llvm::InitializeAllTargetInfos();
+ llvm::cl::ParseCommandLineOptions(argc, argv);
+
+ ide_query::RepoState state;
+ if (!state.ParseFromFileDescriptor(STDIN_FILENO)) {
+ llvm::errs() << "Failed to parse input!\n";
+ return 1;
+ }
+
+ std::unique_ptr<google::protobuf::Message> result;
+ switch (mode) {
+ case OpMode::DEPS: {
+ result = std::make_unique<ide_query::DepsResponse>(
+ tools::ide_query::cc_analyzer::GetDeps(std::move(state)));
+ break;
+ }
+ case OpMode::INPUTS: {
+ result = std::make_unique<ide_query::IdeAnalysis>(
+ tools::ide_query::cc_analyzer::GetBuildInputs(std::move(state)));
+ break;
+ }
+ default:
+ llvm::errs() << "Unknown operation mode!\n";
+ return 1;
+ }
+ if (!result->SerializeToFileDescriptor(STDOUT_FILENO)) {
+ llvm::errs() << "Failed to serialize result!\n";
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/tools/ide_query/ide_query.go b/tools/ide_query/ide_query.go
index c1c4da0..50264fd 100644
--- a/tools/ide_query/ide_query.go
+++ b/tools/ide_query/ide_query.go
@@ -1,8 +1,25 @@
+/*
+ * 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.
+ */
+
// Binary ide_query generates and analyzes build artifacts.
// The produced result can be consumed by IDEs to provide language features.
package main
import (
+ "bytes"
"container/list"
"context"
"encoding/json"
@@ -21,9 +38,13 @@
// Env contains information about the current environment.
type Env struct {
- LunchTarget LunchTarget
- RepoDir string
- OutDir string
+ LunchTarget LunchTarget
+ RepoDir string
+ OutDir string
+ ClangToolsRoot string
+
+ CcFiles []string
+ JavaFiles []string
}
// LunchTarget is a parsed Android lunch target.
@@ -64,6 +85,7 @@
var env Env
env.OutDir = os.Getenv("OUT_DIR")
env.RepoDir = os.Getenv("ANDROID_BUILD_TOP")
+ env.ClangToolsRoot = os.Getenv("PREBUILTS_CLANG_TOOLS_ROOT")
flag.Var(&env.LunchTarget, "lunch_target", "The lunch target to query")
flag.Parse()
files := flag.Args()
@@ -73,64 +95,169 @@
return
}
- var javaFiles []string
for _, f := range files {
switch {
case strings.HasSuffix(f, ".java") || strings.HasSuffix(f, ".kt"):
- javaFiles = append(javaFiles, f)
+ env.JavaFiles = append(env.JavaFiles, f)
+ case strings.HasSuffix(f, ".cc") || strings.HasSuffix(f, ".cpp") || strings.HasSuffix(f, ".h"):
+ env.CcFiles = append(env.CcFiles, f)
default:
log.Printf("File %q is supported - will be skipped.", f)
}
}
ctx := context.Background()
- javaDepsPath := path.Join(env.RepoDir, env.OutDir, "soong/module_bp_java_deps.json")
- // TODO(michaelmerg): Figure out if module_bp_java_deps.json is outdated.
+ // TODO(michaelmerg): Figure out if module_bp_java_deps.json and compile_commands.json is outdated.
runMake(ctx, env, "nothing")
- javaModules, err := loadJavaModules(javaDepsPath)
+ javaModules, javaFileToModuleMap, err := loadJavaModules(&env)
if err != nil {
- log.Fatalf("Failed to load java modules: %v", err)
+ log.Printf("Failed to load java modules: %v", err)
+ }
+ toMake := getJavaTargets(javaFileToModuleMap)
+
+ ccTargets, status := getCCTargets(ctx, &env)
+ if status != nil && status.Code != pb.Status_OK {
+ log.Fatalf("Failed to query cc targets: %v", *status.Message)
+ }
+ toMake = append(toMake, ccTargets...)
+ fmt.Fprintf(os.Stderr, "Running make for modules: %v\n", strings.Join(toMake, ", "))
+ if err := runMake(ctx, env, toMake...); err != nil {
+ log.Printf("Building deps failed: %v", err)
}
- fileToModule := make(map[string]*javaModule) // file path -> module
- for _, f := range javaFiles {
- for _, m := range javaModules {
- if !slices.Contains(m.Srcs, f) {
- continue
- }
- if fileToModule[f] != nil {
- // TODO(michaelmerg): Handle the case where a file is covered by multiple modules.
- log.Printf("File %q found in module %q but is already covered by module %q", f, m.Name, fileToModule[f].Name)
- continue
- }
- fileToModule[f] = m
+ res := getJavaInputs(&env, javaModules, javaFileToModuleMap)
+ ccAnalysis := getCCInputs(ctx, &env)
+ proto.Merge(res, ccAnalysis)
+
+ res.BuildArtifactRoot = env.OutDir
+ data, err := proto.Marshal(res)
+ if err != nil {
+ log.Fatalf("Failed to marshal result proto: %v", err)
+ }
+
+ _, err = os.Stdout.Write(data)
+ if err != nil {
+ log.Fatalf("Failed to write result proto: %v", err)
+ }
+
+ for _, s := range res.Sources {
+ fmt.Fprintf(os.Stderr, "%s: %v (Deps: %d, Generated: %d)\n", s.GetPath(), s.GetStatus(), len(s.GetDeps()), len(s.GetGenerated()))
+ }
+}
+
+func repoState(env *Env) *pb.RepoState {
+ const compDbPath = "soong/development/ide/compdb/compile_commands.json"
+ return &pb.RepoState{
+ RepoDir: env.RepoDir,
+ ActiveFilePath: env.CcFiles,
+ OutDir: env.OutDir,
+ CompDbPath: path.Join(env.OutDir, compDbPath),
+ }
+}
+
+func runCCanalyzer(ctx context.Context, env *Env, mode string, in []byte) ([]byte, error) {
+ ccAnalyzerPath := path.Join(env.ClangToolsRoot, "bin/ide_query_cc_analyzer")
+ outBuffer := new(bytes.Buffer)
+
+ inBuffer := new(bytes.Buffer)
+ inBuffer.Write(in)
+
+ cmd := exec.CommandContext(ctx, ccAnalyzerPath, "--mode="+mode)
+ cmd.Dir = env.RepoDir
+
+ cmd.Stdin = inBuffer
+ cmd.Stdout = outBuffer
+ cmd.Stderr = os.Stderr
+
+ err := cmd.Run()
+
+ return outBuffer.Bytes(), err
+}
+
+// Execute cc_analyzer and get all the targets that needs to be build for analyzing files.
+func getCCTargets(ctx context.Context, env *Env) ([]string, *pb.Status) {
+ state := repoState(env)
+ bytes, err := proto.Marshal(state)
+ if err != nil {
+ log.Fatalln("Failed to serialize state:", err)
+ }
+
+ resp := new(pb.DepsResponse)
+ result, err := runCCanalyzer(ctx, env, "deps", bytes)
+ if marshal_err := proto.Unmarshal(result, resp); marshal_err != nil {
+ return nil, &pb.Status{
+ Code: pb.Status_FAILURE,
+ Message: proto.String("Malformed response from cc_analyzer: " + marshal_err.Error()),
}
}
- var toMake []string
- for _, m := range fileToModule {
- toMake = append(toMake, m.Name)
+ var targets []string
+ if resp.Status != nil && resp.Status.Code != pb.Status_OK {
+ return targets, resp.Status
}
- fmt.Printf("Running make for modules: %v\n", strings.Join(toMake, ", "))
- if err := runMake(ctx, env, toMake...); err != nil {
- log.Fatalf("Failed to run make: %v", err)
+ for _, deps := range resp.Deps {
+ targets = append(targets, deps.BuildTarget...)
}
+ status := &pb.Status{Code: pb.Status_OK}
+ if err != nil {
+ status = &pb.Status{
+ Code: pb.Status_FAILURE,
+ Message: proto.String(err.Error()),
+ }
+ }
+ return targets, status
+}
+
+func getCCInputs(ctx context.Context, env *Env) *pb.IdeAnalysis {
+ state := repoState(env)
+ bytes, err := proto.Marshal(state)
+ if err != nil {
+ log.Fatalln("Failed to serialize state:", err)
+ }
+
+ resp := new(pb.IdeAnalysis)
+ result, err := runCCanalyzer(ctx, env, "inputs", bytes)
+ if marshal_err := proto.Unmarshal(result, resp); marshal_err != nil {
+ resp.Status = &pb.Status{
+ Code: pb.Status_FAILURE,
+ Message: proto.String("Malformed response from cc_analyzer: " + marshal_err.Error()),
+ }
+ return resp
+ }
+
+ if err != nil && (resp.Status == nil || resp.Status.Code == pb.Status_OK) {
+ resp.Status = &pb.Status{
+ Code: pb.Status_FAILURE,
+ Message: proto.String(err.Error()),
+ }
+ }
+ return resp
+}
+
+func getJavaTargets(javaFileToModuleMap map[string]*javaModule) []string {
+ var targets []string
+ for _, m := range javaFileToModuleMap {
+ targets = append(targets, m.Name)
+ }
+ return targets
+}
+
+func getJavaInputs(env *Env, javaModules map[string]*javaModule, javaFileToModuleMap map[string]*javaModule) *pb.IdeAnalysis {
var sources []*pb.SourceFile
type depsAndGenerated struct {
Deps []string
Generated []*pb.GeneratedFile
}
moduleToDeps := make(map[string]*depsAndGenerated)
- for _, f := range files {
+ for _, f := range env.JavaFiles {
file := &pb.SourceFile{
- Path: f,
- WorkingDir: env.RepoDir,
+ Path: f,
}
sources = append(sources, file)
- m := fileToModule[f]
+ m := javaFileToModuleMap[f]
if m == nil {
file.Status = &pb.Status{
Code: pb.Status_FAILURE,
@@ -167,24 +294,8 @@
file.Generated = generated
file.Deps = deps
}
-
- res := &pb.IdeAnalysis{
- BuildArtifactRoot: env.OutDir,
- Sources: sources,
- Status: &pb.Status{Code: pb.Status_OK},
- }
- data, err := proto.Marshal(res)
- if err != nil {
- log.Fatalf("Failed to marshal result proto: %v", err)
- }
-
- err = os.WriteFile(path.Join(env.OutDir, "ide_query.pb"), data, 0644)
- if err != nil {
- log.Fatalf("Failed to write result proto: %v", err)
- }
-
- for _, s := range sources {
- fmt.Printf("%s: %v (Deps: %d, Generated: %d)\n", s.GetPath(), s.GetStatus(), len(s.GetDeps()), len(s.GetGenerated()))
+ return &pb.IdeAnalysis{
+ Sources: sources,
}
}
@@ -196,11 +307,12 @@
"TARGET_PRODUCT=" + env.LunchTarget.Product,
"TARGET_RELEASE=" + env.LunchTarget.Release,
"TARGET_BUILD_VARIANT=" + env.LunchTarget.Variant,
+ "-k",
}
args = append(args, modules...)
cmd := exec.CommandContext(ctx, "build/soong/soong_ui.bash", args...)
cmd.Dir = env.RepoDir
- cmd.Stdout = os.Stdout
+ cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
return cmd.Run()
}
@@ -214,26 +326,40 @@
SrcJars []string `json:"srcjars,omitempty"`
}
-func loadJavaModules(path string) (map[string]*javaModule, error) {
- data, err := os.ReadFile(path)
+func loadJavaModules(env *Env) (map[string]*javaModule, map[string]*javaModule, error) {
+ javaDepsPath := path.Join(env.RepoDir, env.OutDir, "soong/module_bp_java_deps.json")
+ data, err := os.ReadFile(javaDepsPath)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- var ret map[string]*javaModule // module name -> module
- if err = json.Unmarshal(data, &ret); err != nil {
- return nil, err
+ var moduleMapping map[string]*javaModule // module name -> module
+ if err = json.Unmarshal(data, &moduleMapping); err != nil {
+ return nil, nil, err
}
- for name, module := range ret {
+ javaModules := make(map[string]*javaModule)
+ javaFileToModuleMap := make(map[string]*javaModule)
+ for name, module := range moduleMapping {
if strings.HasSuffix(name, "-jarjar") || strings.HasSuffix(name, ".impl") {
- delete(ret, name)
continue
}
-
module.Name = name
+ javaModules[name] = module
+ for _, src := range module.Srcs {
+ if !slices.Contains(env.JavaFiles, src) {
+ // We are only interested in active files.
+ continue
+ }
+ if javaFileToModuleMap[src] != nil {
+ // TODO(michaelmerg): Handle the case where a file is covered by multiple modules.
+ log.Printf("File %q found in module %q but is already covered by module %q", src, module.Name, javaFileToModuleMap[src].Name)
+ continue
+ }
+ javaFileToModuleMap[src] = module
+ }
}
- return ret, nil
+ return javaModules, javaFileToModuleMap, nil
}
func transitiveDeps(m *javaModule, modules map[string]*javaModule) []string {
diff --git a/tools/ide_query/ide_query.sh b/tools/ide_query/ide_query.sh
index 663c4dc..2df48d0 100755
--- a/tools/ide_query/ide_query.sh
+++ b/tools/ide_query/ide_query.sh
@@ -1,5 +1,19 @@
#!/bin/bash -e
+# 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.
+
cd $(dirname $BASH_SOURCE)
source $(pwd)/../../shell_utils.sh
require_top
@@ -7,6 +21,17 @@
# Ensure cogsetup (out/ will be symlink outside the repo)
. ${TOP}/build/make/cogsetup.sh
+case $(uname -s) in
+ Linux)
+ export PREBUILTS_CLANG_TOOLS_ROOT="${TOP}/prebuilts/clang-tools/linux-x86/"
+ PREBUILTS_GO_ROOT="${TOP}/prebuilts/go/linux-x86/"
+ ;;
+ *)
+ echo "Only supported for linux hosts" >&2
+ exit 1
+ ;;
+esac
+
export ANDROID_BUILD_TOP=$TOP
export OUT_DIR=${OUT_DIR}
-exec "${TOP}/prebuilts/go/linux-x86/bin/go" "run" "ide_query" "$@"
+exec "${PREBUILTS_GO_ROOT}/bin/go" "run" "ide_query" "$@"
diff --git a/tools/ide_query/ide_query_proto/Android.bp b/tools/ide_query/ide_query_proto/Android.bp
new file mode 100644
index 0000000..70f15cd
--- /dev/null
+++ b/tools/ide_query/ide_query_proto/Android.bp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_host_static {
+ name: "ide_query_proto",
+ srcs: [
+ "ide_query.proto",
+ ],
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ canonical_path_from_root: false,
+ },
+ compile_multilib: "64",
+ shared_libs: ["libprotobuf-cpp-full"],
+}
diff --git a/tools/ide_query/ide_query_proto/ide_query.pb.go b/tools/ide_query/ide_query_proto/ide_query.pb.go
index 30571cc..f3a016d 100644
--- a/tools/ide_query/ide_query_proto/ide_query.pb.go
+++ b/tools/ide_query/ide_query_proto/ide_query.pb.go
@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
-// protoc-gen-go v1.30.0
+// protoc-gen-go v1.25.0-devel
// protoc v3.21.12
// source: ide_query.proto
@@ -72,7 +72,7 @@
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Code Status_Code `protobuf:"varint,1,opt,name=code,proto3,enum=cider.build.companion.Status_Code" json:"code,omitempty"`
+ Code Status_Code `protobuf:"varint,1,opt,name=code,proto3,enum=ide_query.Status_Code" json:"code,omitempty"`
// Details about the status, might be displayed to user.
Message *string `protobuf:"bytes,2,opt,name=message,proto3,oneof" json:"message,omitempty"`
}
@@ -123,6 +123,142 @@
return ""
}
+// Represents an Android checkout on user's workstation.
+type RepoState struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Absolute path for the checkout in the workstation.
+ // e.g. /home/user/work/android/
+ RepoDir string `protobuf:"bytes,1,opt,name=repo_dir,json=repoDir,proto3" json:"repo_dir,omitempty"`
+ // Relative to repo_dir.
+ ActiveFilePath []string `protobuf:"bytes,2,rep,name=active_file_path,json=activeFilePath,proto3" json:"active_file_path,omitempty"`
+ // Repository relative path to output directory in workstation.
+ OutDir string `protobuf:"bytes,3,opt,name=out_dir,json=outDir,proto3" json:"out_dir,omitempty"`
+ // Repository relative path to compile_commands.json in workstation.
+ CompDbPath string `protobuf:"bytes,4,opt,name=comp_db_path,json=compDbPath,proto3" json:"comp_db_path,omitempty"`
+}
+
+func (x *RepoState) Reset() {
+ *x = RepoState{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_ide_query_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RepoState) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RepoState) ProtoMessage() {}
+
+func (x *RepoState) ProtoReflect() protoreflect.Message {
+ mi := &file_ide_query_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RepoState.ProtoReflect.Descriptor instead.
+func (*RepoState) Descriptor() ([]byte, []int) {
+ return file_ide_query_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *RepoState) GetRepoDir() string {
+ if x != nil {
+ return x.RepoDir
+ }
+ return ""
+}
+
+func (x *RepoState) GetActiveFilePath() []string {
+ if x != nil {
+ return x.ActiveFilePath
+ }
+ return nil
+}
+
+func (x *RepoState) GetOutDir() string {
+ if x != nil {
+ return x.OutDir
+ }
+ return ""
+}
+
+func (x *RepoState) GetCompDbPath() string {
+ if x != nil {
+ return x.CompDbPath
+ }
+ return ""
+}
+
+// Provides all the targets that are pre-requisities for running language
+// services on active_file_paths.
+type DepsResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Deps []*DepsResponse_Deps `protobuf:"bytes,1,rep,name=deps,proto3" json:"deps,omitempty"`
+ Status *Status `protobuf:"bytes,2,opt,name=status,proto3,oneof" json:"status,omitempty"`
+}
+
+func (x *DepsResponse) Reset() {
+ *x = DepsResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_ide_query_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DepsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DepsResponse) ProtoMessage() {}
+
+func (x *DepsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_ide_query_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DepsResponse.ProtoReflect.Descriptor instead.
+func (*DepsResponse) Descriptor() ([]byte, []int) {
+ return file_ide_query_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *DepsResponse) GetDeps() []*DepsResponse_Deps {
+ if x != nil {
+ return x.Deps
+ }
+ return nil
+}
+
+func (x *DepsResponse) GetStatus() *Status {
+ if x != nil {
+ return x.Status
+ }
+ return nil
+}
+
+// Returns all the information necessary for providing language services for the
+// active files.
type GeneratedFile struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -130,8 +266,7 @@
// Path to the file relative to IdeAnalysis.build_artifact_root.
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
- // The text of the generated file, if not provided contents will be read
- //
+ // The text of the generated file, if not provided contents will be read
// from the path above in user's workstation.
Contents []byte `protobuf:"bytes,2,opt,name=contents,proto3,oneof" json:"contents,omitempty"`
}
@@ -139,7 +274,7 @@
func (x *GeneratedFile) Reset() {
*x = GeneratedFile{}
if protoimpl.UnsafeEnabled {
- mi := &file_ide_query_proto_msgTypes[1]
+ mi := &file_ide_query_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -152,7 +287,7 @@
func (*GeneratedFile) ProtoMessage() {}
func (x *GeneratedFile) ProtoReflect() protoreflect.Message {
- mi := &file_ide_query_proto_msgTypes[1]
+ mi := &file_ide_query_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -165,7 +300,7 @@
// Deprecated: Use GeneratedFile.ProtoReflect.Descriptor instead.
func (*GeneratedFile) Descriptor() ([]byte, []int) {
- return file_ide_query_proto_rawDescGZIP(), []int{1}
+ return file_ide_query_proto_rawDescGZIP(), []int{3}
}
func (x *GeneratedFile) GetPath() string {
@@ -187,11 +322,11 @@
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- // Repo root relative path to the source file in the tree.
+ // Path to the source file relative to repository root.
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
// Working directory used by the build system. All the relative
// paths in compiler_arguments should be relative to this path.
- // Relative to workspace root.
+ // Relative to repository root.
WorkingDir string `protobuf:"bytes,2,opt,name=working_dir,json=workingDir,proto3" json:"working_dir,omitempty"`
// Compiler arguments to compile the source file. If multiple variants
// of the module being compiled are possible, the query script will choose
@@ -200,12 +335,12 @@
// Any generated files that are used in compiling the file.
Generated []*GeneratedFile `protobuf:"bytes,4,rep,name=generated,proto3" json:"generated,omitempty"`
// Paths to all of the sources, like build files, code generators,
- // proto files etc. that were used during analysis. Used to figure
+ // proto files etc. that were used during analysis. Used to figure
// out when a set of build artifacts are stale and the query tool
// must be re-run.
- // Relative to workspace root.
+ // Relative to repository root.
Deps []string `protobuf:"bytes,5,rep,name=deps,proto3" json:"deps,omitempty"`
- // Represensts analysis status for this particular file. e.g. not part
+ // Represents analysis status for this particular file. e.g. not part
// of the build graph.
Status *Status `protobuf:"bytes,6,opt,name=status,proto3,oneof" json:"status,omitempty"`
}
@@ -213,7 +348,7 @@
func (x *SourceFile) Reset() {
*x = SourceFile{}
if protoimpl.UnsafeEnabled {
- mi := &file_ide_query_proto_msgTypes[2]
+ mi := &file_ide_query_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -226,7 +361,7 @@
func (*SourceFile) ProtoMessage() {}
func (x *SourceFile) ProtoReflect() protoreflect.Message {
- mi := &file_ide_query_proto_msgTypes[2]
+ mi := &file_ide_query_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -239,7 +374,7 @@
// Deprecated: Use SourceFile.ProtoReflect.Descriptor instead.
func (*SourceFile) Descriptor() ([]byte, []int) {
- return file_ide_query_proto_rawDescGZIP(), []int{2}
+ return file_ide_query_proto_rawDescGZIP(), []int{4}
}
func (x *SourceFile) GetPath() string {
@@ -289,21 +424,20 @@
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- // Path relative to workspace root, containing all the artifacts
+ // Path relative to repository root, containing all the artifacts
// generated by the build system. GeneratedFile.path are always
// relative to this directory.
BuildArtifactRoot string `protobuf:"bytes,1,opt,name=build_artifact_root,json=buildArtifactRoot,proto3" json:"build_artifact_root,omitempty"`
Sources []*SourceFile `protobuf:"bytes,2,rep,name=sources,proto3" json:"sources,omitempty"`
// Status representing overall analysis.
- // Should fail only when no analysis can be performed, e.g. workspace
- // isn't setup.
+ // Should fail only when no analysis can be performed.
Status *Status `protobuf:"bytes,3,opt,name=status,proto3,oneof" json:"status,omitempty"`
}
func (x *IdeAnalysis) Reset() {
*x = IdeAnalysis{}
if protoimpl.UnsafeEnabled {
- mi := &file_ide_query_proto_msgTypes[3]
+ mi := &file_ide_query_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -316,7 +450,7 @@
func (*IdeAnalysis) ProtoMessage() {}
func (x *IdeAnalysis) ProtoReflect() protoreflect.Message {
- mi := &file_ide_query_proto_msgTypes[3]
+ mi := &file_ide_query_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -329,7 +463,7 @@
// Deprecated: Use IdeAnalysis.ProtoReflect.Descriptor instead.
func (*IdeAnalysis) Descriptor() ([]byte, []int) {
- return file_ide_query_proto_rawDescGZIP(), []int{3}
+ return file_ide_query_proto_rawDescGZIP(), []int{5}
}
func (x *IdeAnalysis) GetBuildArtifactRoot() string {
@@ -353,58 +487,144 @@
return nil
}
+// Build dependencies of a source file for providing language services.
+type DepsResponse_Deps struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Relative to repo_dir.
+ SourceFile string `protobuf:"bytes,1,opt,name=source_file,json=sourceFile,proto3" json:"source_file,omitempty"`
+ // Build target to execute for generating dep.
+ BuildTarget []string `protobuf:"bytes,2,rep,name=build_target,json=buildTarget,proto3" json:"build_target,omitempty"`
+ Status *Status `protobuf:"bytes,3,opt,name=status,proto3,oneof" json:"status,omitempty"`
+}
+
+func (x *DepsResponse_Deps) Reset() {
+ *x = DepsResponse_Deps{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_ide_query_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DepsResponse_Deps) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DepsResponse_Deps) ProtoMessage() {}
+
+func (x *DepsResponse_Deps) ProtoReflect() protoreflect.Message {
+ mi := &file_ide_query_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DepsResponse_Deps.ProtoReflect.Descriptor instead.
+func (*DepsResponse_Deps) Descriptor() ([]byte, []int) {
+ return file_ide_query_proto_rawDescGZIP(), []int{2, 0}
+}
+
+func (x *DepsResponse_Deps) GetSourceFile() string {
+ if x != nil {
+ return x.SourceFile
+ }
+ return ""
+}
+
+func (x *DepsResponse_Deps) GetBuildTarget() []string {
+ if x != nil {
+ return x.BuildTarget
+ }
+ return nil
+}
+
+func (x *DepsResponse_Deps) GetStatus() *Status {
+ if x != nil {
+ return x.Status
+ }
+ return nil
+}
+
var File_ide_query_proto protoreflect.FileDescriptor
var file_ide_query_proto_rawDesc = []byte{
0x0a, 0x0f, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
- 0x6f, 0x12, 0x15, 0x63, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x63,
- 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f, 0x6e, 0x22, 0x88, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61,
- 0x74, 0x75, 0x73, 0x12, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
- 0x0e, 0x32, 0x22, 0x2e, 0x63, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e,
- 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
- 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x6d,
- 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07,
- 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x22, 0x1b, 0x0a, 0x04, 0x43, 0x6f,
- 0x64, 0x65, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41,
- 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6d, 0x65, 0x73, 0x73,
- 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64,
- 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74,
- 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f,
- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f,
- 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x8f, 0x02, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63,
- 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72,
- 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
- 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f,
- 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x5f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73,
- 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72,
- 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x67, 0x65, 0x6e,
- 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63,
- 0x69, 0x64, 0x65, 0x72, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x61,
- 0x6e, 0x69, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69,
- 0x6c, 0x65, 0x52, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a,
- 0x04, 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x70,
- 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28,
- 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e,
- 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
- 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a,
- 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x0b, 0x49, 0x64, 0x65,
- 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x75, 0x69, 0x6c,
- 0x64, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18,
- 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x41, 0x72, 0x74, 0x69,
- 0x66, 0x61, 0x63, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x3b, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72,
- 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x69, 0x64, 0x65,
- 0x72, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f,
- 0x6e, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x07, 0x73, 0x6f,
- 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
- 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x62, 0x75,
- 0x69, 0x6c, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74,
- 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01,
- 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x1b, 0x5a, 0x19,
- 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75,
- 0x65, 0x72, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x33,
+ 0x6f, 0x12, 0x09, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x7c, 0x0a, 0x06,
+ 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79,
+ 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f,
+ 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01,
+ 0x01, 0x22, 0x1b, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10,
+ 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x42, 0x0a,
+ 0x0a, 0x08, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x52,
+ 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6f,
+ 0x5f, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f,
+ 0x44, 0x69, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x69,
+ 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x61,
+ 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x17, 0x0a,
+ 0x07, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+ 0x6f, 0x75, 0x74, 0x44, 0x69, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x5f, 0x64,
+ 0x62, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
+ 0x6d, 0x70, 0x44, 0x62, 0x50, 0x61, 0x74, 0x68, 0x22, 0x83, 0x02, 0x0a, 0x0c, 0x44, 0x65, 0x70,
+ 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x64, 0x65, 0x70,
+ 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75,
+ 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x2e, 0x44, 0x65, 0x70, 0x73, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x69, 0x64,
+ 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00,
+ 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x1a, 0x85, 0x01, 0x0a, 0x04,
+ 0x44, 0x65, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66,
+ 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63,
+ 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74,
+ 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69,
+ 0x6c, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
+ 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x69, 0x64, 0x65, 0x5f, 0x71,
+ 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61,
+ 0x74, 0x75, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x51,
+ 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12,
+ 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70,
+ 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+ 0x73, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+ 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65,
+ 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+ 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x5f,
+ 0x64, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x69,
+ 0x6e, 0x67, 0x44, 0x69, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
+ 0x72, 0x5f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+ 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x41, 0x72, 0x67, 0x75, 0x6d,
+ 0x65, 0x6e, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65,
+ 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75,
+ 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c,
+ 0x65, 0x52, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04,
+ 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73,
+ 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x11, 0x2e, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61,
+ 0x74, 0x75, 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01,
+ 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa9, 0x01, 0x0a, 0x0b,
+ 0x49, 0x64, 0x65, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x62,
+ 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x72, 0x6f,
+ 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x41,
+ 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x73,
+ 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x69,
+ 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46,
+ 0x69, 0x6c, 0x65, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x06,
+ 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x69,
+ 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48,
+ 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07,
+ 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x1b, 0x5a, 0x19, 0x69, 0x64, 0x65, 0x5f, 0x71,
+ 0x75, 0x65, 0x72, 0x79, 0x2f, 0x69, 0x64, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -420,25 +640,31 @@
}
var file_ide_query_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_ide_query_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_ide_query_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_ide_query_proto_goTypes = []interface{}{
- (Status_Code)(0), // 0: cider.build.companion.Status.Code
- (*Status)(nil), // 1: cider.build.companion.Status
- (*GeneratedFile)(nil), // 2: cider.build.companion.GeneratedFile
- (*SourceFile)(nil), // 3: cider.build.companion.SourceFile
- (*IdeAnalysis)(nil), // 4: cider.build.companion.IdeAnalysis
+ (Status_Code)(0), // 0: ide_query.Status.Code
+ (*Status)(nil), // 1: ide_query.Status
+ (*RepoState)(nil), // 2: ide_query.RepoState
+ (*DepsResponse)(nil), // 3: ide_query.DepsResponse
+ (*GeneratedFile)(nil), // 4: ide_query.GeneratedFile
+ (*SourceFile)(nil), // 5: ide_query.SourceFile
+ (*IdeAnalysis)(nil), // 6: ide_query.IdeAnalysis
+ (*DepsResponse_Deps)(nil), // 7: ide_query.DepsResponse.Deps
}
var file_ide_query_proto_depIdxs = []int32{
- 0, // 0: cider.build.companion.Status.code:type_name -> cider.build.companion.Status.Code
- 2, // 1: cider.build.companion.SourceFile.generated:type_name -> cider.build.companion.GeneratedFile
- 1, // 2: cider.build.companion.SourceFile.status:type_name -> cider.build.companion.Status
- 3, // 3: cider.build.companion.IdeAnalysis.sources:type_name -> cider.build.companion.SourceFile
- 1, // 4: cider.build.companion.IdeAnalysis.status:type_name -> cider.build.companion.Status
- 5, // [5:5] is the sub-list for method output_type
- 5, // [5:5] is the sub-list for method input_type
- 5, // [5:5] is the sub-list for extension type_name
- 5, // [5:5] is the sub-list for extension extendee
- 0, // [0:5] is the sub-list for field type_name
+ 0, // 0: ide_query.Status.code:type_name -> ide_query.Status.Code
+ 7, // 1: ide_query.DepsResponse.deps:type_name -> ide_query.DepsResponse.Deps
+ 1, // 2: ide_query.DepsResponse.status:type_name -> ide_query.Status
+ 4, // 3: ide_query.SourceFile.generated:type_name -> ide_query.GeneratedFile
+ 1, // 4: ide_query.SourceFile.status:type_name -> ide_query.Status
+ 5, // 5: ide_query.IdeAnalysis.sources:type_name -> ide_query.SourceFile
+ 1, // 6: ide_query.IdeAnalysis.status:type_name -> ide_query.Status
+ 1, // 7: ide_query.DepsResponse.Deps.status:type_name -> ide_query.Status
+ 8, // [8:8] is the sub-list for method output_type
+ 8, // [8:8] is the sub-list for method input_type
+ 8, // [8:8] is the sub-list for extension type_name
+ 8, // [8:8] is the sub-list for extension extendee
+ 0, // [0:8] is the sub-list for field type_name
}
func init() { file_ide_query_proto_init() }
@@ -460,7 +686,7 @@
}
}
file_ide_query_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*GeneratedFile); i {
+ switch v := v.(*RepoState); i {
case 0:
return &v.state
case 1:
@@ -472,7 +698,7 @@
}
}
file_ide_query_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*SourceFile); i {
+ switch v := v.(*DepsResponse); i {
case 0:
return &v.state
case 1:
@@ -484,6 +710,30 @@
}
}
file_ide_query_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GeneratedFile); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_ide_query_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SourceFile); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_ide_query_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IdeAnalysis); i {
case 0:
return &v.state
@@ -495,18 +745,32 @@
return nil
}
}
+ file_ide_query_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DepsResponse_Deps); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
file_ide_query_proto_msgTypes[0].OneofWrappers = []interface{}{}
- file_ide_query_proto_msgTypes[1].OneofWrappers = []interface{}{}
file_ide_query_proto_msgTypes[2].OneofWrappers = []interface{}{}
file_ide_query_proto_msgTypes[3].OneofWrappers = []interface{}{}
+ file_ide_query_proto_msgTypes[4].OneofWrappers = []interface{}{}
+ file_ide_query_proto_msgTypes[5].OneofWrappers = []interface{}{}
+ file_ide_query_proto_msgTypes[6].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ide_query_proto_rawDesc,
NumEnums: 1,
- NumMessages: 4,
+ NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/tools/ide_query/ide_query_proto/ide_query.proto b/tools/ide_query/ide_query_proto/ide_query.proto
index 63eea39..3d7a8e7 100644
--- a/tools/ide_query/ide_query_proto/ide_query.proto
+++ b/tools/ide_query/ide_query_proto/ide_query.proto
@@ -1,3 +1,18 @@
+/*
+ * 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.
+ */
syntax = "proto3";
package ide_query;
@@ -14,6 +29,36 @@
optional string message = 2;
}
+// Represents an Android checkout on user's workstation.
+message RepoState {
+ // Absolute path for the checkout in the workstation.
+ // e.g. /home/user/work/android/
+ string repo_dir = 1;
+ // Relative to repo_dir.
+ repeated string active_file_path = 2;
+ // Repository relative path to output directory in workstation.
+ string out_dir = 3;
+ // Repository relative path to compile_commands.json in workstation.
+ string comp_db_path = 4;
+}
+
+// Provides all the targets that are pre-requisities for running language
+// services on active_file_paths.
+message DepsResponse {
+ // Build dependencies of a source file for providing language services.
+ message Deps {
+ // Relative to repo_dir.
+ string source_file = 1;
+ // Build target to execute for generating dep.
+ repeated string build_target = 2;
+ optional Status status = 3;
+ }
+ repeated Deps deps = 1;
+ optional Status status = 2;
+}
+
+// Returns all the information necessary for providing language services for the
+// active files.
message GeneratedFile {
// Path to the file relative to IdeAnalysis.build_artifact_root.
string path = 1;
diff --git a/tools/protos/metadata_file.proto b/tools/protos/metadata_file.proto
index 47562c5..5c89618 100644
--- a/tools/protos/metadata_file.proto
+++ b/tools/protos/metadata_file.proto
@@ -282,7 +282,7 @@
optional string element_id = 3;
}
-// Identifier for a third-package package.
+// Identifier for a third-party package.
// See go/tp-metadata-id.
message Identifier {
// The type of the identifier. Either an "ecosystem" value from
@@ -338,7 +338,19 @@
// - "PrebuiltByAlphabet": This type should be used for archives of primarily
// Google-owned source code (may contain non-Google-owned dependencies),
// which has been built using production Google infrastructure, and copied
- // into third_party.
+ // into Android. The "value" field is the URL of the prebuilt artifact or
+ // the relative path of the artifact to the root of a package.
+ // Example:
+ // identifier {
+ // type: "PrebuiltByAlphabet",
+ // version: "1",
+ // value: "v1/arm84_hdpi.apk",
+ // }
+ // identifier {
+ // type: "PrebuiltByAlphabet",
+ // version: "2",
+ // value: "v2/x86_64_xhdpi.apk",
+ // }
//
// - "LocalSource": The "value" field is the URL identifying where the local
// copy of the package source code can be found.
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 4941c71..9385f0c 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -244,7 +244,6 @@
"boot_signer",
"brotli",
"bsdiff",
- "imgdiff",
"lz4",
"mkbootfs",
"signapk",
@@ -308,7 +307,6 @@
"brotli",
"bsdiff",
"deapexer",
- "imgdiff",
"lz4",
"mkbootfs",
"signapk",
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index b39a82c..a2c0949 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -1055,6 +1055,20 @@
for call in add_partition_calls:
add_partition(*call)
else:
+ # When calling avbtool make_vbmeta_image, it uses the `partitions`
+ # dictionary to include the options for --include_descriptors_from_image.
+ # The vbmeta image is different if the order of the
+ # --include_descriptors_from_image changes. As the images are generated
+ # parallelly and entries are added on completion of image creation,
+ # this `partitions` dict might be indeterministic as the order of
+ # completion of image creation cannot be predicted.
+ # To address this issue, add keys to the dict `partitions` with null values
+ # in the order they are listed in the variable `add_partition_calls`, and
+ # then the values are updated by `add_partition` keeping the order of the
+ # items. This ensures generated vbmeta.img is the same for the same input.
+ for call in add_partition_calls:
+ if call[1]:
+ partitions[call[0]] = None
with ThreadPoolExecutor(max_workers=len(add_partition_calls)) as executor:
for future in [executor.submit(add_partition, *call) for call in add_partition_calls]:
future.result()
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index a7b1d8c..464ad9b 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -294,7 +294,7 @@
build_command = [prop_dict["ext_mkuserimg"]]
if "extfs_sparse_flag" in prop_dict and not disable_sparse:
build_command.append(prop_dict["extfs_sparse_flag"])
- run_e2fsck = RunE2fsck
+ run_fsck = RunE2fsck
build_command.extend([in_dir, out_file, fs_type,
prop_dict["mount_point"]])
build_command.append(prop_dict["image_size"])
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index d31f87e..b8dcd84 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -215,7 +215,7 @@
This simulates how apexd activates APEXes.
1. create {inp}/APEX which is treated as a "/apex" on device.
- 2. invoke apexd_host with vendor APEXes.
+ 2. invoke apexd_host with APEXes.
"""
apex_dir = common.MakeTempDir('APEX')
@@ -225,12 +225,13 @@
# Always create /apex directory for dirmap
os.makedirs(apex_dir, exist_ok=True)
- # Invoke apexd_host to activate vendor APEXes for checkvintf
+ # Invoke apexd_host to activate APEXes for checkvintf
apex_host = os.path.join(OPTIONS.search_path, 'bin', 'apexd_host')
cmd = [apex_host, '--tool_path', OPTIONS.search_path]
cmd += ['--apex_path', dirmap['/apex']]
- if '/vendor' in dirmap:
- cmd += ['--vendor_path', dirmap['/vendor']]
+ for p in ['system', 'system_ext', 'product', 'vendor']:
+ if '/' + p in dirmap:
+ cmd += ['--' + p + '_path', dirmap['/' + p]]
common.RunAndCheckOutput(cmd)
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 8a8a613..264c2d8 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -955,6 +955,13 @@
d["build.prop"] = d["system.build.prop"]
if d.get("avb_enable") == "true":
+ build_info = BuildInfo(d, use_legacy_id=True)
+ # Set up the salt for partitions without build.prop
+ if build_info.fingerprint:
+ if "fingerprint" not in d:
+ d["fingerprint"] = build_info.fingerprint
+ if "avb_salt" not in d:
+ d["avb_salt"] = sha256(build_info.fingerprint.encode()).hexdigest()
# Set the vbmeta digest if exists
try:
d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
@@ -1313,7 +1320,11 @@
key = "super_%s_partition_list" % partition_group
merged_dict[key] = uniq_concat(
framework_dict.get(key, ""), vendor_dict.get(key, ""))
-
+ # in the case that vendor is on s build, but is taking a v3 -> v3 vabc ota, we want to fallback to v2
+ if "vabc_cow_version" not in vendor_dict or "vabc_cow_version" not in framework_dict:
+ merged_dict["vabc_cow_version"] = '2'
+ else:
+ merged_dict["vabc_cow_version"] = min(vendor_dict["vabc_cow_version"], framework_dict["vabc_cow_version"])
# Various other flags should be copied from the vendor dict, if defined.
for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
"super_metadata_device", "super_partition_error_limit",
@@ -2456,7 +2467,7 @@
m = re.match(r'(?:minSdkVersion|sdkVersion):\'([^\']*)\'', line)
if m:
return m.group(1)
- raise ExternalError("No minSdkVersion returned by aapt2")
+ raise ExternalError("No minSdkVersion returned by aapt2 for apk: {}".format(apk_name))
def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
diff --git a/tools/releasetools/create_brick_ota.py b/tools/releasetools/create_brick_ota.py
index f290323..9e040a5 100644
--- a/tools/releasetools/create_brick_ota.py
+++ b/tools/releasetools/create_brick_ota.py
@@ -45,13 +45,17 @@
partitions_to_wipe = PARTITIONS_TO_WIPE
if extra_wipe_partitions is not None:
partitions_to_wipe = PARTITIONS_TO_WIPE + extra_wipe_partitions.split(",")
+ ota_metadata = ["ota-type=BRICK", "post-timestamp=9999999999",
+ "pre-device=" + product_name]
+ if serialno is not None:
+ ota_metadata.append("serialno=" + serialno)
# recovery requiers product name to be a | separated list
product_name = product_name.replace(",", "|")
with zipfile.ZipFile(output_path, "w") as zfp:
zfp.writestr("recovery.wipe", "\n".join(partitions_to_wipe))
zfp.writestr("payload.bin", "")
zfp.writestr("META-INF/com/android/metadata", "\n".join(
- ["ota-type=BRICK", "post-timestamp=9999999999", "pre-device=" + product_name, "serialno=" + serialno]))
+ ota_metadata))
def main(argv):
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index c0ff5d2..2c5fe0d 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -872,6 +872,7 @@
target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
if OPTIONS.disable_vabc and target_info.is_release_key:
raise ValueError("Disabling VABC on release-key builds is not supported.")
+
ValidateCompressionParam(target_info)
vabc_compression_param = target_info.vabc_compression_param
@@ -907,7 +908,6 @@
logger.info("Source build and target build use different compression methods {} vs {}, default to source builds parameter {}".format(
source_info.vabc_compression_param, target_info.vabc_compression_param, source_info.vabc_compression_param))
vabc_compression_param = source_info.vabc_compression_param
-
# Virtual AB Cow version 3 is introduced in Android U with improved memory
# and install time performance. All OTA's with
# both the source build and target build with VIRTUAL_AB_COW_VERSION = 3
@@ -918,6 +918,7 @@
elif source_info.vabc_cow_version != target_info.vabc_cow_version:
logger.info("Source and Target have different cow VABC_COW_VERSION specified, default to minimum version")
OPTIONS.vabc_cow_version = min(source_info.vabc_cow_version, target_info.vabc_cow_version)
+
# Virtual AB Compression was introduced in Androd S.
# Later, we backported VABC to Android R. But verity support was not
# backported, so if VABC is used and we are on Android R, disable
@@ -930,6 +931,19 @@
assert "ab_partitions" in OPTIONS.info_dict, \
"META/ab_partitions.txt is required for ab_update."
source_info = None
+ if not target_info.vabc_cow_version:
+ OPTIONS.vabc_cow_version = 2
+ elif target_info.vabc_cow_version >= "3" and target_info.vendor_api_level < 35:
+ logger.warning(
+ "This full OTA is configured to use VABC cow version"
+ " 3 which is supported since"
+ " Android API level 35, but device is "
+ "launched with {} . If this full OTA is"
+ " served to a device running old build, OTA might fail due to "
+ "unsupported vabc cow version. For safety, version 2 is used because "
+ "it's supported since day 1.".format(
+ target_info.vendor_api_level))
+ OPTIONS.vabc_cow_version = 2
if OPTIONS.vabc_compression_param is None and vabc_compression_param:
minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[
vabc_compression_param]
@@ -1274,7 +1288,7 @@
assert len(words) >= 1 and len(words) <= 2
OPTIONS.vabc_compression_param = a.lower()
if len(words) == 2:
- if not words[1].isdigit():
+ if not words[1].lstrip("-").isdigit():
raise ValueError("Cannot parse value %r for option $COMPRESSION_LEVEL - only "
"integers are allowed." % words[1])
elif o == "--security_patch_level":
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index bf69dec..b8f848f 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -271,6 +271,10 @@
def IsEntryOtaPackage(input_zip, filename):
with input_zip.open(filename, "r") as fp:
+ external_attr = input_zip.getinfo(filename).external_attr
+ if stat.S_ISLNK(external_attr >> 16):
+ return IsEntryOtaPackage(input_zip,
+ os.path.join(os.path.dirname(filename), fp.read().decode()))
return IsOtaPackage(fp)
@@ -795,6 +799,16 @@
# Copy it verbatim if we allow the file to exist.
common.ZipWriteStr(output_tf_zip, out_info, data)
+ # Sign microdroid_vendor.img.
+ elif filename == "VENDOR/etc/avf/microdroid/microdroid_vendor.img":
+ vendor_key = OPTIONS.avb_keys.get("vendor")
+ vendor_algorithm = OPTIONS.avb_algorithms.get("vendor")
+ with tempfile.NamedTemporaryFile() as image:
+ image.write(data)
+ image.flush()
+ ReplaceKeyInAvbHashtreeFooter(image, vendor_key, vendor_algorithm,
+ misc_info)
+ common.ZipWrite(output_tf_zip, image.name, filename)
# A non-APK file; copy it verbatim.
else:
common.ZipWriteStr(output_tf_zip, out_info, data)
@@ -812,6 +826,108 @@
# Write back misc_info with the latest values.
ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
+# Parse string output of `avbtool info_image`.
+def ParseAvbInfo(info_raw):
+ # line_matcher is for parsing each output line of `avbtool info_image`.
+ # example string input: " Hash Algorithm: sha1"
+ # example matched input: (" ", "Hash Algorithm", "sha1")
+ line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
+ # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
+ # example string input: "example_prop_key -> 'example_prop_value'"
+ # example matched output: ("example_prop_key", "example_prop_value")
+ prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
+ info = {}
+ indent_stack = [[-1, info]]
+ for line_info_raw in info_raw.split('\n'):
+ # Parse the line
+ line_info_parsed = line_matcher.match(line_info_raw)
+ if not line_info_parsed:
+ continue
+ indent = len(line_info_parsed.group(1))
+ key = line_info_parsed.group(2).strip()
+ value = line_info_parsed.group(3).strip()
+
+ # Pop indentation stack
+ while indent <= indent_stack[-1][0]:
+ del indent_stack[-1]
+
+ # Insert information into 'info'.
+ cur_info = indent_stack[-1][1]
+ if value == "":
+ if key == "Descriptors":
+ empty_list = []
+ cur_info[key] = empty_list
+ indent_stack.append([indent, empty_list])
+ else:
+ empty_dict = {}
+ cur_info.append({key:empty_dict})
+ indent_stack.append([indent, empty_dict])
+ elif key == "Prop":
+ prop_parsed = prop_matcher.match(value)
+ if not prop_parsed:
+ raise ValueError(
+ "Failed to parse prop while getting avb information.")
+ cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
+ else:
+ cur_info[key] = value
+ return info
+
+def ReplaceKeyInAvbHashtreeFooter(image, new_key, new_algorithm, misc_info):
+ # Get avb information about the image by parsing avbtool info_image.
+ def GetAvbInfo(avbtool, image_name):
+ # Get information with raw string by `avbtool info_image`.
+ info_raw = common.RunAndCheckOutput([
+ avbtool, 'info_image',
+ '--image', image_name
+ ])
+ return ParseAvbInfo(info_raw)
+
+ # Get hashtree descriptor from info
+ def GetAvbHashtreeDescriptor(avb_info):
+ hashtree_descriptors = tuple(filter(lambda x: "Hashtree descriptor" in x,
+ info.get('Descriptors')))
+ if len(hashtree_descriptors) != 1:
+ raise ValueError("The number of hashtree descriptor is not 1.")
+ return hashtree_descriptors[0]["Hashtree descriptor"]
+
+ # Get avb info
+ avbtool = misc_info['avb_avbtool']
+ info = GetAvbInfo(avbtool, image.name)
+ hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+
+ # Generate command
+ cmd = [avbtool, 'add_hashtree_footer',
+ '--key', new_key,
+ '--algorithm', new_algorithm,
+ '--partition_name', hashtree_descriptor.get("Partition Name"),
+ '--partition_size', info.get("Image size").removesuffix(" bytes"),
+ '--hash_algorithm', hashtree_descriptor.get("Hash Algorithm"),
+ '--salt', hashtree_descriptor.get("Salt"),
+ '--do_not_generate_fec',
+ '--image', image.name
+ ]
+
+ # Append properties into command
+ props = map(lambda x: x.get("Prop"), filter(lambda x: "Prop" in x,
+ info.get('Descriptors')))
+ for prop_wrapped in props:
+ prop = tuple(prop_wrapped.items())
+ if len(prop) != 1:
+ raise ValueError("The number of property is not 1.")
+ cmd.append('--prop')
+ cmd.append(prop[0][0] + ':' + prop[0][1])
+
+ # Replace Hashtree Footer with new key
+ common.RunAndCheckOutput(cmd)
+
+ # Check root digest is not changed
+ new_info = GetAvbInfo(avbtool, image.name)
+ new_hashtree_descriptor = GetAvbHashtreeDescriptor(info)
+ root_digest = hashtree_descriptor.get("Root Digest")
+ new_root_digest = new_hashtree_descriptor.get("Root Digest")
+ assert root_digest == new_root_digest, \
+ ("Root digest in hashtree descriptor shouldn't be changed. Old: {}, New: "
+ "{}").format(root_digest, new_root_digest)
def ReplaceCerts(data):
"""Replaces all the occurences of X.509 certs with the new ones.
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 2989338..89933a0 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1515,6 +1515,7 @@
'super_group_a_group_size': '1000',
'super_group_b_partition_list': 'product',
'super_group_b_group_size': '2000',
+ 'vabc_cow_version': '2',
}
self.assertEqual(merged_dict, expected_merged_dict)
@@ -1525,6 +1526,7 @@
'dynamic_partition_list': 'system',
'super_group_a_partition_list': 'system',
'super_group_a_group_size': '5000',
+ 'vabc_cow_version': '3',
}
vendor_dict = {
'use_dynamic_partitions': 'true',
@@ -1546,6 +1548,7 @@
'super_group_a_group_size': '1000',
'super_group_b_partition_list': 'product',
'super_group_b_group_size': '2000',
+ 'vabc_cow_version': '2',
}
self.assertEqual(merged_dict, expected_merged_dict)
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 9cc6df4..7ac1cff 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -22,8 +22,9 @@
import common
import test_utils
from sign_target_files_apks import (
- CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ReadApexKeysInfo,
- ReplaceCerts, RewriteAvbProps, RewriteProps, WriteOtacerts)
+ CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ParseAvbInfo,
+ ReadApexKeysInfo, ReplaceCerts, RewriteAvbProps, RewriteProps,
+ WriteOtacerts)
class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
@@ -535,3 +536,86 @@
'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
'build/make/target/product/security/testkey', None),
}, keys_info)
+
+ def test_ParseAvbInfo(self):
+ avb_info_string = """
+ Footer version: 1.0
+ Image size: 9999999 bytes
+ Original image size: 8888888 bytes
+ VBMeta offset: 7777777
+ VBMeta size: 1111 bytes
+ --
+ Minimum libavb version: 1.0
+ Header Block: 222 bytes
+ Authentication Block: 333 bytes
+ Auxiliary Block: 888 bytes
+ Public key (sha1): abababababababababababababababababababab
+ Algorithm: SHA256_RSA2048
+ Rollback Index: 0
+ Flags: 0
+ Rollback Index Location: 0
+ Release String: 'avbtool 1.3.0'
+ Descriptors:
+ Hashtree descriptor:
+ Version of dm-verity: 1
+ Image Size: 8888888 bytes
+ Tree Offset: 8888888
+ Tree Size: 44444 bytes
+ Data Block Size: 4444 bytes
+ Hash Block Size: 4444 bytes
+ FEC num roots: 0
+ FEC offset: 0
+ FEC size: 0 bytes
+ Hash Algorithm: sha1
+ Partition Name: partition-name
+ Salt: cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+ Root Digest: efefefefefefefefefefefefefefefefefef
+ Flags: 0
+ Prop: prop.key -> 'prop.value'
+ """
+
+ self.assertEqual(
+ {
+ 'Footer version': '1.0',
+ 'Image size': '9999999 bytes',
+ 'Original image size': '8888888 bytes',
+ 'VBMeta offset': '7777777',
+ 'VBMeta size': '1111 bytes',
+ 'Minimum libavb version': '1.0',
+ 'Header Block': '222 bytes',
+ 'Authentication Block': '333 bytes',
+ 'Auxiliary Block': '888 bytes',
+ 'Public key (sha1)': 'abababababababababababababababababababab',
+ 'Algorithm': 'SHA256_RSA2048',
+ 'Rollback Index': '0',
+ 'Flags': '0',
+ 'Rollback Index Location': '0',
+ 'Release String': "'avbtool 1.3.0'",
+ 'Descriptors': [
+ {
+ 'Hashtree descriptor': {
+ 'Version of dm-verity': '1',
+ 'Image Size': '8888888 bytes',
+ 'Tree Offset': '8888888',
+ 'Tree Size': '44444 bytes',
+ 'Data Block Size': '4444 bytes',
+ 'Hash Block Size': '4444 bytes',
+ 'FEC num roots': '0',
+ 'FEC offset': '0',
+ 'FEC size': '0 bytes',
+ 'Hash Algorithm': 'sha1',
+ 'Partition Name': 'partition-name',
+ 'Salt': 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',
+ 'Root Digest': 'efefefefefefefefefefefefefefefefefef',
+ 'Flags': '0',
+ }
+ },
+ {
+ 'Prop': {
+ 'prop.key': 'prop.value',
+ }
+ },
+ ],
+ },
+ ParseAvbInfo(avb_info_string),
+ )
\ No newline at end of file
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 2f2b833..6b2341b 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -986,15 +986,17 @@
}
private static class ZipSections {
- ByteBuffer beforeCentralDir;
+ DataSource beforeCentralDir;
+
+ // The following fields are still valid after closing the backing DataSource.
+ long beforeCentralDirSize;
ByteBuffer centralDir;
ByteBuffer eocd;
}
- private static ZipSections findMainZipSections(ByteBuffer apk)
+ private static ZipSections findMainZipSections(DataSource apk)
throws IOException, ZipFormatException {
- apk.slice();
- ApkUtils.ZipSections sections = ApkUtils.findZipSections(DataSources.asDataSource(apk));
+ ApkUtils.ZipSections sections = ApkUtils.findZipSections(apk);
long centralDirStartOffset = sections.getZipCentralDirectoryOffset();
long centralDirSizeBytes = sections.getZipCentralDirectorySizeBytes();
long centralDirEndOffset = centralDirStartOffset + centralDirSizeBytes;
@@ -1005,25 +1007,20 @@
+ ". CD end: " + centralDirEndOffset
+ ", EoCD start: " + eocdStartOffset);
}
- apk.position(0);
- apk.limit((int) centralDirStartOffset);
- ByteBuffer beforeCentralDir = apk.slice();
-
- apk.position((int) centralDirStartOffset);
- apk.limit((int) centralDirEndOffset);
- ByteBuffer centralDir = apk.slice();
-
- apk.position((int) eocdStartOffset);
- apk.limit(apk.capacity());
- ByteBuffer eocd = apk.slice();
-
- apk.position(0);
- apk.limit(apk.capacity());
ZipSections result = new ZipSections();
- result.beforeCentralDir = beforeCentralDir;
- result.centralDir = centralDir;
- result.eocd = eocd;
+
+ result.beforeCentralDir = apk.slice(0, centralDirStartOffset);
+ result.beforeCentralDirSize = result.beforeCentralDir.size();
+
+ long centralDirSize = centralDirEndOffset - centralDirStartOffset;
+ if (centralDirSize >= Integer.MAX_VALUE) throw new IndexOutOfBoundsException();
+ result.centralDir = apk.getByteBuffer(centralDirStartOffset, (int)centralDirSize);
+
+ long eocdSize = apk.size() - eocdStartOffset;
+ if (eocdSize >= Integer.MAX_VALUE) throw new IndexOutOfBoundsException();
+ result.eocd = apk.getByteBuffer(eocdStartOffset, (int)eocdSize);
+
return result;
}
@@ -1278,11 +1275,8 @@
// signatures)
apkSigner.inputApkSigningBlock(null);
- // Build the output APK in memory, by copying input APK's ZIP entries across
- // and then signing the output APK.
- ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
CountingOutputStream outputJarCounter =
- new CountingOutputStream(v1SignedApkBuf);
+ new CountingOutputStream(outputFile);
JarOutputStream outputJar = new JarOutputStream(outputJarCounter);
// Use maximum compression for compressed entries because the APK lives forever
// on the system partition.
@@ -1295,24 +1289,31 @@
addV1Signature(apkSigner, addV1SignatureRequest, outputJar, timestamp);
addV1SignatureRequest.done();
}
- outputJar.close();
- ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
- v1SignedApkBuf.reset();
- ByteBuffer[] outputChunks = new ByteBuffer[] {v1SignedApk};
- ZipSections zipSections = findMainZipSections(v1SignedApk);
+ // close output and switch to input mode
+ outputJar.close();
+ outputJar = null;
+ outputJarCounter = null;
+ outputFile = null;
+ RandomAccessFile v1SignedApk = new RandomAccessFile(outputFilename, "r");
+
+ ZipSections zipSections = findMainZipSections(DataSources.asDataSource(
+ v1SignedApk));
ByteBuffer eocd = ByteBuffer.allocate(zipSections.eocd.remaining());
eocd.put(zipSections.eocd);
eocd.flip();
eocd.order(ByteOrder.LITTLE_ENDIAN);
+
+ ByteBuffer[] outputChunks = new ByteBuffer[] {};
+
// This loop is supposed to be iterated twice at most.
// The second pass is to align the file size after amending EOCD comments
// with assumption that re-generated signing block would be the same size.
while (true) {
ApkSignerEngine.OutputApkSigningBlockRequest2 addV2SignatureRequest =
apkSigner.outputZipSections2(
- DataSources.asDataSource(zipSections.beforeCentralDir),
+ zipSections.beforeCentralDir,
DataSources.asDataSource(zipSections.centralDir),
DataSources.asDataSource(eocd));
if (addV2SignatureRequest == null) break;
@@ -1330,11 +1331,10 @@
modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
ApkUtils.setZipEocdCentralDirectoryOffset(
modifiedEocd,
- zipSections.beforeCentralDir.remaining() + padding +
+ zipSections.beforeCentralDir.size() + padding +
apkSigningBlock.length);
outputChunks =
new ByteBuffer[] {
- zipSections.beforeCentralDir,
ByteBuffer.allocate(padding),
ByteBuffer.wrap(apkSigningBlock),
zipSections.centralDir,
@@ -1348,7 +1348,7 @@
// Calculate the file size
eocd = modifiedEocd;
- int fileSize = 0;
+ long fileSize = zipSections.beforeCentralDirSize;
for (ByteBuffer buf : outputChunks) {
fileSize += buf.remaining();
}
@@ -1357,7 +1357,7 @@
break;
}
// Pad EOCD comment to align the file size.
- int commentLen = alignment - fileSize % alignment;
+ int commentLen = alignment - (int)(fileSize % alignment);
modifiedEocd = ByteBuffer.allocate(eocd.remaining() + commentLen);
modifiedEocd.put(eocd);
modifiedEocd.rewind();
@@ -1368,6 +1368,12 @@
eocd = modifiedEocd;
}
+ // close input and switch back to output mode
+ v1SignedApk.close();
+ v1SignedApk = null;
+ outputFile = new FileOutputStream(outputFilename, true);
+ outputFile.getChannel().truncate(zipSections.beforeCentralDirSize);
+
// This assumes outputChunks are array-backed. To avoid this assumption, the
// code could be rewritten to use FileChannel.
for (ByteBuffer outputChunk : outputChunks) {