Merge "Add the rebuilt modules to the benchmark formatting" into main
diff --git a/ci/build_test_suites b/ci/build_test_suites
new file mode 100755
index 0000000..89ecefe
--- /dev/null
+++ b/ci/build_test_suites
@@ -0,0 +1,23 @@
+#!prebuilts/build-tools/linux-x86/bin/py3-cmd
+# Copyright 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.
+
+import sys
+
+import build_test_suites
+
+if __name__ == '__main__':
+ sys.dont_write_bytecode = True
+
+ build_test_suites.main(sys.argv)
diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py
new file mode 100644
index 0000000..1064041
--- /dev/null
+++ b/ci/build_test_suites.py
@@ -0,0 +1,299 @@
+# Copyright 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.
+
+"""Script to build only the necessary modules for general-tests along
+
+with whatever other targets are passed in.
+"""
+
+import argparse
+from collections.abc import Sequence
+import json
+import os
+import pathlib
+import re
+import subprocess
+import sys
+from typing import Any, Dict, Set, Text
+
+import test_mapping_module_retriever
+
+
+# List of modules that are always required to be in general-tests.zip
+REQUIRED_MODULES = frozenset(
+ ['cts-tradefed', 'vts-tradefed', 'compatibility-host-util', 'soong_zip']
+)
+
+
+def build_test_suites(argv):
+ args = parse_args(argv)
+
+ if not args.change_info:
+ 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):
+ argparser = argparse.ArgumentParser()
+ argparser.add_argument(
+ 'extra_targets', nargs='*', help='Extra test suites to build.'
+ )
+ argparser.add_argument('--target_product')
+ argparser.add_argument('--target_release')
+ 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 build_everything(args: argparse.Namespace):
+ build_command = base_build_command(args)
+ build_command.append('general-tests')
+
+ run_command(build_command, print_output=True)
+
+
+def build_affected_modules(args: argparse.Namespace):
+ modules_to_build = find_modules_to_build(
+ pathlib.Path(args.change_info), args.extra_required_modules
+ )
+
+ # Call the build command with everything.
+ build_command = base_build_command(args)
+ build_command.extend(modules_to_build)
+
+ run_command(build_command, print_output=True)
+
+ zip_build_outputs(modules_to_build, args.dist_dir, args.target_release)
+
+
+def base_build_command(args: argparse.Namespace) -> 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)
+
+ return build_command
+
+
+def run_command(
+ args: list[str],
+ env: Dict[Text, Text] = os.environ,
+ print_output: bool = False,
+) -> str:
+ result = subprocess.run(
+ args=args,
+ text=True,
+ capture_output=True,
+ check=False,
+ env=env,
+ )
+ # If the process failed, print its stdout and propagate the exception.
+ if not result.returncode == 0:
+ print('Build command failed! output:')
+ print('stdout: ' + result.stdout)
+ print('stderr: ' + result.stderr)
+
+ result.check_returncode()
+
+ if print_output:
+ print(result.stdout)
+
+ return result.stdout
+
+
+def find_modules_to_build(
+ change_info: pathlib.Path, extra_required_modules: list[Text]
+) -> Set[Text]:
+ changed_files = find_changed_files(change_info)
+
+ test_mappings = test_mapping_module_retriever.GetTestMappings(
+ changed_files, set()
+ )
+
+ # Soong_zip is required to generate the output zip so always build it.
+ modules_to_build = set(REQUIRED_MODULES)
+ if extra_required_modules:
+ modules_to_build.update(extra_required_modules)
+
+ modules_to_build.update(find_affected_modules(test_mappings, changed_files))
+
+ return modules_to_build
+
+
+def find_changed_files(change_info: pathlib.Path) -> Set[Text]:
+ with open(change_info) as change_info_file:
+ change_info_contents = json.load(change_info_file)
+
+ changed_files = set()
+
+ for change in change_info_contents['changes']:
+ project_path = change.get('projectPath') + '/'
+
+ for revision in change.get('revisions'):
+ for file_info in revision.get('fileInfos'):
+ changed_files.add(project_path + file_info.get('path'))
+
+ return changed_files
+
+
+def find_affected_modules(
+ test_mappings: Dict[str, Any], changed_files: Set[Text]
+) -> Set[Text]:
+ modules = set()
+
+ # The test_mappings object returned by GetTestMappings is organized as
+ # follows:
+ # {
+ # 'test_mapping_file_path': {
+ # 'group_name' : [
+ # 'name': 'module_name',
+ # ],
+ # }
+ # }
+ for test_mapping in test_mappings.values():
+ for group in test_mapping.values():
+ for entry in group:
+ module_name = entry.get('name', None)
+
+ if not module_name:
+ continue
+
+ file_patterns = entry.get('file_patterns')
+ if not file_patterns:
+ modules.add(module_name)
+ continue
+
+ if matches_file_patterns(file_patterns, changed_files):
+ modules.add(module_name)
+ continue
+
+ return modules
+
+
+# TODO(lucafarsi): Share this logic with the original logic in
+# test_mapping_test_retriever.py
+def matches_file_patterns(
+ file_patterns: list[Text], changed_files: Set[Text]
+) -> bool:
+ for changed_file in changed_files:
+ for pattern in file_patterns:
+ if re.search(pattern, changed_file):
+ return True
+
+ return False
+
+
+def zip_build_outputs(
+ modules_to_build: Set[Text], dist_dir: Text, target_release: Text
+):
+ src_top = os.environ.get('TOP', os.getcwd())
+
+ # Call dumpvars to get the necessary things.
+ # TODO(lucafarsi): Don't call soong_ui 4 times for this, --dumpvars-mode can
+ # do it but it requires parsing.
+ host_out_testcases = get_soong_var('HOST_OUT_TESTCASES', target_release)
+ target_out_testcases = get_soong_var('TARGET_OUT_TESTCASES', target_release)
+ product_out = get_soong_var('PRODUCT_OUT', target_release)
+ soong_host_out = get_soong_var('SOONG_HOST_OUT', target_release)
+ host_out = get_soong_var('HOST_OUT', target_release)
+
+ # Call the class to package the outputs.
+ # TODO(lucafarsi): Move this code into a replaceable class.
+ host_paths = []
+ target_paths = []
+ for module in modules_to_build:
+ host_path = os.path.join(host_out_testcases, module)
+ if os.path.exists(host_path):
+ host_paths.append(host_path)
+
+ target_path = os.path.join(target_out_testcases, module)
+ if os.path.exists(target_path):
+ target_paths.append(target_path)
+
+ zip_command = ['time', os.path.join(host_out, 'bin', 'soong_zip')]
+
+ # Add host testcases.
+ zip_command.append('-C')
+ zip_command.append(os.path.join(src_top, soong_host_out))
+ zip_command.append('-P')
+ zip_command.append('host/')
+ for path in host_paths:
+ zip_command.append('-D')
+ zip_command.append(path)
+
+ # Add target testcases.
+ zip_command.append('-C')
+ zip_command.append(os.path.join(src_top, product_out))
+ zip_command.append('-P')
+ zip_command.append('target')
+ for path in target_paths:
+ zip_command.append('-D')
+ zip_command.append(path)
+
+ # TODO(lucafarsi): Push this logic into a general-tests-minimal build command
+ # Add necessary tools. These are also hardcoded in general-tests.mk.
+ framework_path = os.path.join(soong_host_out, 'framework')
+
+ zip_command.append('-C')
+ zip_command.append(framework_path)
+ zip_command.append('-P')
+ zip_command.append('host/tools')
+ zip_command.append('-f')
+ zip_command.append(os.path.join(framework_path, 'cts-tradefed.jar'))
+ zip_command.append('-f')
+ zip_command.append(
+ os.path.join(framework_path, 'compatibility-host-util.jar')
+ )
+ zip_command.append('-f')
+ zip_command.append(os.path.join(framework_path, 'vts-tradefed.jar'))
+
+ # Zip to the DIST dir.
+ zip_command.append('-o')
+ zip_command.append(os.path.join(dist_dir, 'general-tests.zip'))
+
+ run_command(zip_command, print_output=True)
+
+
+def get_soong_var(var: str, target_release: str) -> str:
+ new_env = os.environ.copy()
+ new_env['TARGET_RELEASE'] = target_release
+
+ value = run_command(
+ ['./build/soong/soong_ui.bash', '--dumpvar-mode', '--abs', var],
+ env=new_env,
+ ).strip()
+ if not value:
+ raise RuntimeError('Necessary soong variable ' + var + ' not found.')
+
+ return value
+
+
+def main(argv):
+ build_test_suites(sys.argv)
diff --git a/ci/test_mapping_module_retriever.py b/ci/test_mapping_module_retriever.py
new file mode 100644
index 0000000..d2c13c0
--- /dev/null
+++ b/ci/test_mapping_module_retriever.py
@@ -0,0 +1,125 @@
+# Copyright 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.
+
+"""
+Simple parsing code to scan test_mapping files and determine which
+modules are needed to build for the given list of changed files.
+TODO(lucafarsi): Deduplicate from artifact_helper.py
+"""
+
+from typing import Any, Dict, Set, Text
+import json
+import os
+import re
+
+# Regex to extra test name from the path of test config file.
+TEST_NAME_REGEX = r'(?:^|.*/)([^/]+)\.config'
+
+# Key name for TEST_MAPPING imports
+KEY_IMPORTS = 'imports'
+KEY_IMPORT_PATH = 'path'
+
+# Name of TEST_MAPPING file.
+TEST_MAPPING = 'TEST_MAPPING'
+
+# Pattern used to identify double-quoted strings and '//'-format comments in
+# TEST_MAPPING file, but only double-quoted strings are included within the
+# matching group.
+_COMMENTS_RE = re.compile(r'(\"(?:[^\"\\]|\\.)*\"|(?=//))(?://.*)?')
+
+
+def FilterComments(test_mapping_file: Text) -> Text:
+ """Remove comments in TEST_MAPPING file to valid format.
+
+ Only '//' is regarded as comments.
+
+ Args:
+ test_mapping_file: Path to a TEST_MAPPING file.
+
+ Returns:
+ Valid json string without comments.
+ """
+ return re.sub(_COMMENTS_RE, r'\1', test_mapping_file)
+
+def GetTestMappings(paths: Set[Text],
+ checked_paths: Set[Text]) -> Dict[Text, Dict[Text, Any]]:
+ """Get the affected TEST_MAPPING files.
+
+ TEST_MAPPING files in source code are packaged into a build artifact
+ `test_mappings.zip`. Inside the zip file, the path of each TEST_MAPPING file
+ is preserved. From all TEST_MAPPING files in the source code, this method
+ locates the affected TEST_MAPPING files based on the given paths list.
+
+ A TEST_MAPPING file may also contain `imports` that import TEST_MAPPING files
+ from a different location, e.g.,
+ "imports": [
+ {
+ "path": "../folder2"
+ }
+ ]
+ In that example, TEST_MAPPING files inside ../folder2 (relative to the
+ TEST_MAPPING file containing that imports section) and its parent directories
+ will also be included.
+
+ Args:
+ paths: A set of paths with related TEST_MAPPING files for given changes.
+ checked_paths: A set of paths that have been checked for TEST_MAPPING file
+ already. The set is updated after processing each TEST_MAPPING file. It's
+ used to prevent infinite loop when the method is called recursively.
+
+ Returns:
+ A dictionary of Test Mapping containing the content of the affected
+ TEST_MAPPING files, indexed by the path containing the TEST_MAPPING file.
+ """
+ test_mappings = {}
+
+ # Search for TEST_MAPPING files in each modified path and its parent
+ # directories.
+ all_paths = set()
+ for path in paths:
+ dir_names = path.split(os.path.sep)
+ all_paths |= set(
+ [os.path.sep.join(dir_names[:i + 1]) for i in range(len(dir_names))])
+ # Add root directory to the paths to search for TEST_MAPPING file.
+ all_paths.add('')
+
+ all_paths.difference_update(checked_paths)
+ checked_paths |= all_paths
+ # Try to load TEST_MAPPING file in each possible path.
+ for path in all_paths:
+ try:
+ test_mapping_file = os.path.join(os.path.join(os.getcwd(), path), 'TEST_MAPPING')
+ # Read content of TEST_MAPPING file.
+ content = FilterComments(open(test_mapping_file, "r").read())
+ test_mapping = json.loads(content)
+ test_mappings[path] = test_mapping
+
+ import_paths = set()
+ for import_detail in test_mapping.get(KEY_IMPORTS, []):
+ import_path = import_detail[KEY_IMPORT_PATH]
+ # Try the import path as absolute path.
+ import_paths.add(import_path)
+ # Try the import path as relative path based on the test mapping file
+ # containing the import.
+ norm_import_path = os.path.normpath(os.path.join(path, import_path))
+ import_paths.add(norm_import_path)
+ import_paths.difference_update(checked_paths)
+ if import_paths:
+ import_test_mappings = GetTestMappings(import_paths, checked_paths)
+ test_mappings.update(import_test_mappings)
+ except (KeyError, FileNotFoundError, NotADirectoryError):
+ # TEST_MAPPING file doesn't exist in path
+ pass
+
+ return test_mappings
diff --git a/core/Makefile b/core/Makefile
index 6edac1a..90312d0 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -2094,11 +2094,6 @@
# Get a colon-separated list of search paths.
INTERNAL_USERIMAGES_BINARY_PATHS := $(subst $(space),:,$(sort $(dir $(INTERNAL_USERIMAGES_DEPS))))
-# Collects file_contexts files from modules to be installed
-$(call merge-fc-files, \
- $(sort $(foreach m,$(product_MODULES),$(ALL_MODULES.$(m).FILE_CONTEXTS))),\
- $(call intermediates-dir-for,ETC,file_contexts.bin)/file_contexts.modules.tmp)
-
SELINUX_FC := $(call intermediates-dir-for,ETC,file_contexts.bin)/file_contexts.bin
INTERNAL_USERIMAGES_DEPS += $(SELINUX_FC)
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 6af6f08..6ae8b0d 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -189,6 +189,9 @@
$(call add_soong_config_var,ANDROID,SYSTEM_OPTIMIZE_JAVA)
$(call add_soong_config_var,ANDROID,FULL_SYSTEM_OPTIMIZE_JAVA)
+# TODO(b/319697968): Remove this build flag support when metalava fully supports flagged api
+$(call soong_config_set,ANDROID,release_hidden_api_exportable_stubs,$(RELEASE_HIDDEN_API_EXPORTABLE_STUBS))
+
# Check for SupplementalApi module.
ifeq ($(wildcard packages/modules/SupplementalApi),)
$(call add_soong_config_var_value,ANDROID,include_nonpublic_framework_api,false)
@@ -196,3 +199,7 @@
$(call add_soong_config_var_value,ANDROID,include_nonpublic_framework_api,true)
endif
+# Add crashrecovery build flag to soong
+$(call soong_config_set,ANDROID,release_crashrecovery_module,$(RELEASE_CRASHRECOVERY_MODULE))
+# Weirdly required because platform_bootclasspath is using AUTO namespace
+$(call soong_config_set,AUTO,release_crashrecovery_module,$(RELEASE_CRASHRECOVERY_MODULE))
diff --git a/core/base_rules.mk b/core/base_rules.mk
index 254bfeb..1793f00 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -1187,7 +1187,6 @@
endif
ALL_MODULES.$(my_register_name).FOR_HOST_CROSS := $(my_host_cross)
ifndef LOCAL_IS_HOST_MODULE
-ALL_MODULES.$(my_register_name).FILE_CONTEXTS := $(LOCAL_FILE_CONTEXTS)
ALL_MODULES.$(my_register_name).APEX_KEYS_FILE := $(LOCAL_APEX_KEY_PATH)
endif
test_config :=
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 2b84fcd..8d99176 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -87,7 +87,6 @@
LOCAL_EXTRA_FULL_TEST_CONFIGS:=
LOCAL_EXTRACT_APK:=
LOCAL_EXTRACT_DPI_APK:=
-LOCAL_FILE_CONTEXTS:=
LOCAL_FINDBUGS_FLAGS:=
LOCAL_FORCE_STATIC_EXECUTABLE:=
LOCAL_FULL_CLASSES_JACOCO_JAR:=
diff --git a/core/main.mk b/core/main.mk
index 348a964..649c75c 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1721,10 +1721,8 @@
# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files
-ifeq ($(SOONG_COLLECT_JAVA_DEPS), true)
- $(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
- $(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
-endif
+$(call dist-for-goals, dist_files, $(SOONG_OUT_DIR)/module_bp_java_deps.json)
+$(call dist-for-goals, dist_files, $(PRODUCT_OUT)/module-info.json)
.PHONY: apps_only
ifeq ($(HOST_OS),darwin)
diff --git a/core/packaging/flags.mk b/core/packaging/flags.mk
index 57df911..12057fb 100644
--- a/core/packaging/flags.mk
+++ b/core/packaging/flags.mk
@@ -97,6 +97,46 @@
)) \
)
+# Create a set of storage file for each partition
+# $(1): built aconfig flags storage dir (out)
+# $(2): installed aconfig flags storage package map file (out)
+# $(3): installed aconfig flags storage flag map file (out)
+# $(4): installed aconfig flags storage flag value file (out)
+# $(5): input aconfig files for the partition (in)
+define generate-partition-aconfig-storage-file
+$(eval $(strip $(1))/target: PRIVATE_OUT_DIR := $(strip $(1)))
+$(eval $(strip $(1))/target: PRIVATE_IN := $(strip $(5)))
+$(strip $(1))/target: $(ACONFIG) $(strip $(5))
+ mkdir -p $$(PRIVATE_OUT_DIR)
+ $$(if $$(PRIVATE_IN), \
+ $$(ACONFIG) create-storage --container "" --out $$(PRIVATE_OUT_DIR) \
+ $$(addprefix --cache ,$$(PRIVATE_IN)), \
+ )
+ echo -n > $$(PRIVATE_OUT_DIR)/target
+$(strip $(1))/package.map: $(strip $(1))/target
+$(strip $(1))/flag.map: $(strip $(1))/target
+$(strip $(1))/flag.val: $(strip $(1))/target
+$(call copy-one-file, $(strip $(1))/package.map, $(2))
+$(call copy-one-file, $(strip $(1))/flag.map, $(3))
+$(call copy-one-file, $(strip $(1))/flag.val, $(4))
+endef
+
+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_falg_value.$(partition) := $(PRODUCT_OUT)/$(partition)/etc/flag.val) \
+ $(eval $(call generate-partition-aconfig-storage-file, \
+ $(TARGET_OUT_FLAGS)/$(partition), \
+ $(aconfig_storage_package_map.$(partition)), \
+ $(aconfig_storage_flag_map.$(partition)), \
+ $(aconfig_storage_flag_val.$(partition)), \
+ $(sort $(foreach m,$(call register-names-for-partition, $(partition)), \
+ $(ALL_MODULES.$(m).ACONFIG_FILES) \
+ )), \
+ )) \
+)
+endif
# -----------------------------------------------------------------
# Install the ones we need for the configured product
@@ -104,6 +144,9 @@
$(sort $(foreach partition, $(filter $(IMAGES_TO_BUILD), $(_FLAG_PARTITIONS)), \
$(build_flag_summaries.$(partition)) \
$(aconfig_flag_summaries_protobuf.$(partition)) \
+ $(aconfig_storage_package_map.$(partition)) \
+ $(aconfig_storage_flag_map.$(partition)) \
+ $(aconfig_storage_flag_val.$(partition)) \
))
ALL_DEFAULT_INSTALLED_MODULES += $(required_flags_files)
@@ -119,5 +162,8 @@
$(foreach partition, $(_FLAG_PARTITIONS), \
$(eval build_flag_summaries.$(partition):=) \
$(eval aconfig_flag_summaries_protobuf.$(partition):=) \
+ $(eval aconfig_storage_package_map.$(partition):=) \
+ $(eval aconfig_storage_flag_map.$(partition):=) \
+ $(eval aconfig_storage_flag_val.$(partition):=) \
)
diff --git a/core/product.mk b/core/product.mk
index 5515a8a..2d22ebf 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -446,7 +446,6 @@
_product_list_vars += PRODUCT_AFDO_PROFILES
-_product_single_value_vars += PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API
_product_single_value_vars += PRODUCT_SCUDO_ALLOCATION_RING_BUFFER_SIZE
_product_list_vars += PRODUCT_RELEASE_CONFIG_MAPS
diff --git a/core/soong_config.mk b/core/soong_config.mk
index b6ce2a7..7d03aa3 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -47,7 +47,6 @@
$(call add_json_str, Platform_version_known_codenames, $(PLATFORM_VERSION_KNOWN_CODENAMES))
$(call add_json_bool, Release_aidl_use_unfrozen, $(RELEASE_AIDL_USE_UNFROZEN))
-$(call add_json_bool, Release_expose_flagged_api, $(RELEASE_EXPOSE_FLAGGED_API))
$(call add_json_str, Platform_min_supported_target_sdk_version, $(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION))
@@ -396,8 +395,6 @@
$(call add_json_list, ProductPackages, $(sort $(PRODUCT_PACKAGES)))
$(call end_json_map)
-$(call add_json_bool, NextReleaseHideFlaggedApi, $(filter true,$(PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API)))
-
$(call add_json_bool, BuildFromSourceStub, $(findstring true,$(PRODUCT_BUILD_FROM_SOURCE_STUB) $(BUILD_FROM_SOURCE_STUB)))
$(call json_end)
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 6a101da9..9b321d9 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -94,7 +94,6 @@
framework-graphics \
framework-minus-apex \
framework-minus-apex-install-dependencies \
- framework-nfc \
framework-res \
framework-sysconfig.xml \
fsck.erofs \
@@ -288,6 +287,13 @@
wifi.rc \
wm \
+# When we release crashrecovery module
+ifeq ($(RELEASE_CRASHRECOVERY_MODULE),true)
+ PRODUCT_PACKAGES += \
+ com.android.crashrecovery \
+
+endif
+
# These packages are not used on Android TV
ifneq ($(PRODUCT_IS_ATV),true)
PRODUCT_PACKAGES += \
@@ -302,6 +308,16 @@
endif
+# Check if the build supports NFC apex or not
+ifeq ($(RELEASE_PACKAGE_NFC_STACK),NfcNci)
+ PRODUCT_PACKAGES += \
+ framework-nfc \
+ NfcNci
+else
+ PRODUCT_PACKAGES += \
+ com.android.nfcservices
+endif
+
# VINTF data for system image
PRODUCT_PACKAGES += \
system_manifest.xml \
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index 55fcf2f..2fd7209 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -50,7 +50,6 @@
PRODUCT_BOOT_JARS += \
framework-minus-apex \
framework-graphics \
- framework-nfc \
ext \
telephony-common \
voip-common \
@@ -88,6 +87,22 @@
com.android.virt:framework-virtualization \
com.android.wifi:framework-wifi \
+# When we release crashrecovery module
+ifeq ($(RELEASE_CRASHRECOVERY_MODULE),true)
+ PRODUCT_APEX_BOOT_JARS += \
+ com.android.crashrecovery:framework-crashrecovery \
+
+endif
+
+# Check if the build supports NFC apex or not
+ifeq ($(RELEASE_PACKAGE_NFC_STACK),NfcNci)
+ PRODUCT_BOOT_JARS += \
+ framework-nfc
+else
+ PRODUCT_APEX_BOOT_JARS := \
+ 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.
@@ -109,6 +124,13 @@
com.android.permission:service-permission \
com.android.rkpd:service-rkp \
+# When we release crashrecovery module
+ifeq ($(RELEASE_CRASHRECOVERY_MODULE),true)
+ PRODUCT_APEX_SYSTEM_SERVER_JARS += \
+ com.android.crashrecovery:service-crashrecovery \
+
+endif
+
# Use $(wildcard) to avoid referencing the profile in thin manifests that don't have the
# art project.
ifneq (,$(wildcard art))
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index 3acf1e6..b5292d2 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -73,7 +73,6 @@
UserDictionaryProvider \
VpnDialogs \
vr \
- $(RELEASE_PACKAGE_NFC_STACK)
PRODUCT_SYSTEM_SERVER_APPS += \
diff --git a/target/product/mainline_sdk.mk b/target/product/mainline_sdk.mk
index cb23bc8..10bb0a0 100644
--- a/target/product/mainline_sdk.mk
+++ b/target/product/mainline_sdk.mk
@@ -17,6 +17,4 @@
PRODUCT_BRAND := Android
PRODUCT_DEVICE := mainline_sdk
-PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
-
PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/target/product/sdk.mk b/target/product/sdk.mk
index b9ccad3..650f8e9 100644
--- a/target/product/sdk.mk
+++ b/target/product/sdk.mk
@@ -29,6 +29,4 @@
PRODUCT_BRAND := Android
PRODUCT_DEVICE := mainline_x86
-PRODUCT_NEXT_RELEASE_HIDE_FLAGGED_API := true
-
PRODUCT_BUILD_FROM_SOURCE_STUB := true
\ No newline at end of file
diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs
index 4110317..ae9b83a 100644
--- a/tools/aconfig/printflags/src/main.rs
+++ b/tools/aconfig/printflags/src/main.rs
@@ -20,6 +20,7 @@
use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags;
use anyhow::{bail, Context, Result};
use regex::Regex;
+use std::collections::BTreeMap;
use std::collections::HashMap;
use std::process::Command;
use std::{fs, str};
@@ -66,7 +67,7 @@
let device_config_flags = parse_device_config(dc_stdout);
// read aconfig_flags.pb files
- let mut flags: HashMap<String, Vec<String>> = HashMap::new();
+ 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);
let Ok(bytes) = fs::read(&path) else {
@@ -86,11 +87,10 @@
// print flags
for (key, mut value) in flags {
- let (_, package_and_name) = key.split_once('/').unwrap();
if let Some(dc_value) = device_config_flags.get(&key) {
value.push(dc_value.to_string());
}
- println!("{}: {}", package_and_name, value.join(", "));
+ println!("{}: {}", key, value.join(", "));
}
Ok(())
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 6c4e241..7d719f0 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -135,7 +135,7 @@
.required(true)
.help("The target container for the generated storage file."),
)
- .arg(Arg::new("cache").long("cache").required(true))
+ .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
.arg(Arg::new("out").long("out").required(true)),
)
}
diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/src/storage/flag_table.rs
index 595217e..3545700 100644
--- a/tools/aconfig/src/storage/flag_table.rs
+++ b/tools/aconfig/src/storage/flag_table.rs
@@ -295,8 +295,6 @@
};
assert_eq!(header, &expected_header);
- println!("{:?}", &flag_table.as_ref().unwrap().nodes);
-
let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets;
let expected_bucket: Vec<Option<u32>> = vec![
Some(98),
@@ -338,9 +336,7 @@
#[test]
// this test point locks down the table serialization
fn test_serialization() {
- let flag_table = create_test_flag_table();
- assert!(flag_table.is_ok());
- let flag_table = flag_table.unwrap();
+ let flag_table = create_test_flag_table().unwrap();
let header: &FlagTableHeader = &flag_table.header;
let reinterpreted_header = FlagTableHeader::from_bytes(&header.as_bytes());
diff --git a/tools/aconfig/src/storage/flag_value.rs b/tools/aconfig/src/storage/flag_value.rs
new file mode 100644
index 0000000..45f5ec0
--- /dev/null
+++ b/tools/aconfig/src/storage/flag_value.rs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::commands::assign_flag_ids;
+use crate::protos::ProtoFlagState;
+use crate::storage::{self, FlagPackage};
+use anyhow::{anyhow, Result};
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueHeader {
+ pub version: u32,
+ pub container: String,
+ pub file_size: u32,
+ pub num_flags: u32,
+ pub boolean_value_offset: u32,
+}
+
+impl FlagValueHeader {
+ fn new(container: &str, num_flags: u32) -> Self {
+ Self {
+ version: storage::FILE_VERSION,
+ container: String::from(container),
+ file_size: 0,
+ num_flags,
+ boolean_value_offset: 0,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.version.to_le_bytes());
+ let container_bytes = self.container.as_bytes();
+ result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_size.to_le_bytes());
+ result.extend_from_slice(&self.num_flags.to_le_bytes());
+ result.extend_from_slice(&self.boolean_value_offset.to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct FlagValueList {
+ pub header: FlagValueHeader,
+ pub booleans: Vec<bool>,
+}
+
+impl FlagValueList {
+ pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+ // create list
+ let num_flags = packages.iter().map(|pkg| pkg.boolean_flags.len() as u32).sum();
+
+ let mut list = Self {
+ header: FlagValueHeader::new(container, num_flags),
+ booleans: vec![false; num_flags as usize],
+ };
+
+ for pkg in packages.iter() {
+ let start_offset = pkg.boolean_offset 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;
+ }
+ }
+
+ // initialize all header fields
+ list.header.boolean_value_offset = list.header.as_bytes().len() as u32;
+ list.header.file_size = list.header.boolean_value_offset + num_flags;
+
+ Ok(list)
+ }
+
+ pub fn as_bytes(&self) -> Vec<u8> {
+ [
+ self.header.as_bytes(),
+ self.booleans
+ .iter()
+ .map(|&v| u8::from(v).to_le_bytes())
+ .collect::<Vec<_>>()
+ .concat(),
+ ]
+ .concat()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::storage::{
+ group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+ tests::read_u32_from_bytes, tests::read_u8_from_bytes,
+ };
+
+ impl FlagValueHeader {
+ // test only method to deserialize back into the header struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let mut head = 0;
+ Ok(Self {
+ version: read_u32_from_bytes(bytes, &mut head)?,
+ container: read_str_from_bytes(bytes, &mut head)?,
+ file_size: read_u32_from_bytes(bytes, &mut head)?,
+ num_flags: read_u32_from_bytes(bytes, &mut head)?,
+ boolean_value_offset: read_u32_from_bytes(bytes, &mut head)?,
+ })
+ }
+ }
+
+ impl FlagValueList {
+ // test only method to deserialize back into the flag value struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let header = FlagValueHeader::from_bytes(bytes)?;
+ let num_flags = header.num_flags;
+ let mut head = header.as_bytes().len();
+ let booleans = (0..num_flags)
+ .map(|_| read_u8_from_bytes(bytes, &mut head).unwrap() == 1)
+ .collect();
+ let list = Self { header, booleans };
+ Ok(list)
+ }
+ }
+
+ pub fn create_test_flag_value_list() -> Result<FlagValueList> {
+ let caches = parse_all_test_flags();
+ let packages = group_flags_by_package(caches.iter());
+ FlagValueList::new("system", &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();
+ assert!(flag_value_list.is_ok());
+
+ let header: &FlagValueHeader = &flag_value_list.as_ref().unwrap().header;
+ let expected_header = FlagValueHeader {
+ version: storage::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);
+ }
+
+ #[test]
+ // this test point locks down the value list serialization
+ fn test_serialization() {
+ let flag_value_list = create_test_flag_value_list().unwrap();
+
+ let header: &FlagValueHeader = &flag_value_list.header;
+ let reinterpreted_header = FlagValueHeader::from_bytes(&header.as_bytes());
+ assert!(reinterpreted_header.is_ok());
+ assert_eq!(header, &reinterpreted_header.unwrap());
+
+ let reinterpreted_value_list = FlagValueList::from_bytes(&flag_value_list.as_bytes());
+ assert!(reinterpreted_value_list.is_ok());
+ assert_eq!(&flag_value_list, &reinterpreted_value_list.unwrap());
+ }
+}
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index a28fccd..36ea309 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -15,6 +15,7 @@
*/
pub mod flag_table;
+pub mod flag_value;
pub mod package_table;
use anyhow::{anyhow, Result};
@@ -24,7 +25,9 @@
use crate::commands::OutputFile;
use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
-use crate::storage::{flag_table::FlagTable, package_table::PackageTable};
+use crate::storage::{
+ flag_table::FlagTable, flag_value::FlagValueList, package_table::PackageTable,
+};
pub const FILE_VERSION: u32 = 1;
@@ -128,7 +131,13 @@
let flag_table_file =
OutputFile { contents: flag_table.as_bytes(), path: flag_table_file_path };
- Ok(vec![package_table_file, flag_table_file])
+ // create and serialize flag value
+ let flag_value = FlagValueList::new(container, &packages)?;
+ let flag_value_file_path = PathBuf::from("flag.val");
+ let flag_value_file =
+ OutputFile { contents: flag_value.as_bytes(), path: flag_value_file_path };
+
+ Ok(vec![package_table_file, flag_table_file, flag_value_file])
}
#[cfg(test)]
@@ -136,6 +145,13 @@
use super::*;
use crate::Input;
+ /// Read and parse bytes as u8
+ pub fn read_u8_from_bytes(buf: &[u8], head: &mut usize) -> Result<u8> {
+ let val = u8::from_le_bytes(buf[*head..*head + 1].try_into()?);
+ *head += 1;
+ Ok(val)
+ }
+
/// Read and parse bytes as u16
pub fn read_u16_from_bytes(buf: &[u8], head: &mut usize) -> Result<u16> {
let val = u16::from_le_bytes(buf[*head..*head + 2].try_into()?);
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
index 0ce1349..4036234 100644
--- a/tools/aconfig/src/storage/package_table.rs
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -277,9 +277,7 @@
#[test]
// this test point locks down the table serialization
fn test_serialization() {
- let package_table = create_test_package_table();
- assert!(package_table.is_ok());
- let package_table = package_table.unwrap();
+ let package_table = create_test_package_table().unwrap();
let header: &PackageTableHeader = &package_table.header;
let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());
diff --git a/tools/perf/benchmarks b/tools/perf/benchmarks
index e188858..acc53bb 100755
--- a/tools/perf/benchmarks
+++ b/tools/perf/benchmarks
@@ -130,8 +130,9 @@
def Clean():
"""Remove the out directory."""
def remove_out():
- if os.path.exists("out"):
- shutil.rmtree("out")
+ out_dir = utils.get_out_dir()
+ if os.path.exists(out_dir):
+ shutil.rmtree(out_dir)
return Change(label="Remove out", change=remove_out, undo=lambda: None)
@@ -270,7 +271,7 @@
def _run_benchmark(self, lunch, benchmark, iteration):
"""Run a single benchmark."""
- benchmark_log_subdir = self._log_dir(lunch, benchmark, iteration)
+ benchmark_log_subdir = self._benchmark_log_dir(lunch, benchmark, iteration)
benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
@@ -298,7 +299,7 @@
dist_one = self._options.DistOne()
if dist_one:
# If we're disting just one benchmark, save the logs and we can stop here.
- self._dist(dist_one)
+ self._dist(utils.get_dist_dir())
else:
# Postroll builds
for i in range(benchmark.preroll):
@@ -315,7 +316,7 @@
self._write_summary()
sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
- def _log_dir(self, lunch, benchmark, iteration):
+ def _benchmark_log_dir(self, lunch, benchmark, iteration):
"""Construct the log directory fir a benchmark run."""
path = f"{lunch.Combine()}/{benchmark.id}"
# Zero pad to the correct length for correct alpha sorting
@@ -355,8 +356,8 @@
return after_ns - before_ns
def _dist(self, dist_dir):
- out_dir = pathlib.Path("out")
- dest_dir = pathlib.Path(dist_dir).joinpath("logs")
+ out_dir = utils.get_out_dir()
+ dest_dir = dist_dir.joinpath("logs")
os.makedirs(dest_dir, exist_ok=True)
basenames = [
"build.trace.gz",
@@ -704,6 +705,7 @@
runner.Run()
except FatalError:
sys.stderr.write(f"FAILED\n")
+ sys.exit(1)
if __name__ == "__main__":
diff --git a/tools/perf/utils.py b/tools/perf/utils.py
index 08e393f..934130d 100644
--- a/tools/perf/utils.py
+++ b/tools/perf/utils.py
@@ -28,3 +28,15 @@
d = d.parent
if d == pathlib.Path("/"):
return None
+
+def get_dist_dir():
+ dist_dir = os.getenv("DIST_DIR")
+ if dist_dir:
+ return pathlib.Path(dist_dir).resolve()
+ return get_out_dir().joinpath("dist")
+
+def get_out_dir():
+ out_dir = os.getenv("OUT_DIR")
+ if not out_dir:
+ out_dir = "out"
+ return pathlib.Path(out_dir).resolve()
diff --git a/tools/releasetools/merge_ota.py b/tools/releasetools/merge_ota.py
index 24d9ea9..fb5957a 100644
--- a/tools/releasetools/merge_ota.py
+++ b/tools/releasetools/merge_ota.py
@@ -243,8 +243,6 @@
# Get signing keys
key_passwords = common.GetKeyPasswords([args.package_key])
- generator = PayloadGenerator()
-
apex_info_bytes = ApexInfo(file_paths)
with tempfile.NamedTemporaryFile() as unsigned_payload:
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 29042a5..e521e1f 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -856,10 +856,10 @@
return ExtractTargetFiles(target_file)
-def ValidateCompressinParam(target_info):
+def ValidateCompressionParam(target_info):
vabc_compression_param = OPTIONS.vabc_compression_param
if vabc_compression_param:
- minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[vabc_compression_param]
+ minimum_api_level_required = VABC_COMPRESSION_PARAM_SUPPORT[vabc_compression_param.split(",")[0]]
if target_info.vendor_api_level < minimum_api_level_required:
raise ValueError("Specified VABC compression param {} is only supported for API level >= {}, device is on API level {}".format(
vabc_compression_param, minimum_api_level_required, target_info.vendor_api_level))
@@ -872,7 +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.")
- ValidateCompressinParam(target_info)
+ ValidateCompressionParam(target_info)
vabc_compression_param = target_info.vabc_compression_param
target_file = ExtractOrCopyTargetFiles(target_file)
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 4356394..7b497c1 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -1137,6 +1137,7 @@
devkeydir + "/shared": d + "/shared",
devkeydir + "/platform": d + "/platform",
devkeydir + "/networkstack": d + "/networkstack",
+ devkeydir + "/sdk_sandbox": d + "/sdk_sandbox",
})
else:
OPTIONS.key_map[s] = d