diff options
181 files changed, 7749 insertions, 3185 deletions
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000000..cd5c426a04 --- /dev/null +++ b/Android.bp @@ -0,0 +1,36 @@ +// Copyright 2024 Google Inc. All rights reserved. +// +// 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"], +} + +// Package the minimal files required to run envsetup.sh in the test +// environment. +genrule { + name: "envsetup_minimum.zip", + visibility: [ + "//build/make/tests:__subpackages__", + ], + tools: [ + "soong_zip", + ], + srcs: [ + "envsetup.sh", + "shell_utils.sh", + "core/envsetup.mk", + ], + out: ["envsetup.zip"], + cmd: "$(location soong_zip) -o $(out) -D build/make", +} diff --git a/CleanSpec.mk b/CleanSpec.mk index dfc0cd0fff..f8c96ffffe 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -779,6 +779,14 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/bazel/output/execroot/__main__/bazel-ou # Clear out rustc compiler intermediates after reverting rust compiler/linker split. $(call add-clean-step, find $(OUT_DIR) -name "*.rsp.whole.a" -print0 | xargs -0 /bin/bash -c 'rm -f $$$${@}; rm -f $$$${@/.rsp.whole.a/.rsp.a}; rm -f $$$${@/.rsp.whole.a/.rsp}') +# Remove obsolete java compilation artifacts +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/) +$(call add-clean-step, find $(OUT_DIR) -type f -name "*.jar" -print0 | xargs -0 rm -f) + +# Remove obsolete java compilation artifacts +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/) +$(call add-clean-step, find $(OUT_DIR) -type f -name "*.jar" -print0 | xargs -0 rm -f) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index ce7515044e..97ecd33212 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,2 +1,5 @@ [Hook Scripts] do_not_use_DO_NOT_MERGE = ${REPO_ROOT}/build/soong/scripts/check_do_not_merge.sh ${PREUPLOAD_COMMIT} + +[Builtin Hooks] +ktfmt = true diff --git a/ci/Android.bp b/ci/Android.bp new file mode 100644 index 0000000000..066b83fb2d --- /dev/null +++ b/ci/Android.bp @@ -0,0 +1,85 @@ +// Copyright 2024 Google Inc. All rights reserved. +// +// 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"], +} + +python_test_host { + name: "build_test_suites_test", + main: "build_test_suites_test.py", + pkg_path: "testdata", + srcs: [ + "build_test_suites_test.py", + ], + libs: [ + "build_test_suites", + "pyfakefs", + "ci_test_lib", + ], + test_options: { + unit_test: true, + }, + data: [ + ":py3-cmd", + ], + version: { + py3: { + embedded_launcher: true, + }, + }, +} + +// This test is only intended to be run locally since it's slow, not hermetic, +// and requires a lot of system state. It is therefore not marked as `unit_test` +// and is not part of any test suite. Note that we also don't want to run this +// test with Bazel since that would require disabling sandboxing and explicitly +// passing in all the env vars we depend on via the command-line. The test +// target could be configured to do so but it's not worth doing seeing that +// we're moving away from Bazel. +python_test_host { + name: "build_test_suites_local_test", + main: "build_test_suites_local_test.py", + srcs: [ + "build_test_suites_local_test.py", + ], + libs: [ + "build_test_suites", + "pyfakefs", + "ci_test_lib", + ], + test_config_template: "AndroidTest.xml.template", + test_options: { + unit_test: false, + }, + version: { + py3: { + embedded_launcher: true, + }, + }, +} + +python_library_host { + name: "build_test_suites", + srcs: [ + "build_test_suites.py", + ], +} + +python_library_host { + name: "ci_test_lib", + srcs: [ + "ci_test_lib.py", + ], +} diff --git a/ci/AndroidTest.xml.template b/ci/AndroidTest.xml.template new file mode 100644 index 0000000000..81a3435b68 --- /dev/null +++ b/ci/AndroidTest.xml.template @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> +<configuration> + <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest"> + <option name="par-file-name" value="{MODULE}"/> + <option name="use-test-output-file" value="false"/> + <option name="test-timeout" value="5m"/> + </test> +</configuration> diff --git a/ci/build_test_suites b/ci/build_test_suites index 03f6731dcd..5aaf2f49b7 100755 --- a/ci/build_test_suites +++ b/ci/build_test_suites @@ -1,4 +1,5 @@ #!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"); @@ -13,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys import build_test_suites +import sys -build_test_suites.main(sys.argv) +build_test_suites.main(sys.argv[1:]) diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py index 23e896d70a..29ed50e095 100644 --- a/ci/build_test_suites.py +++ b/ci/build_test_suites.py @@ -12,403 +12,115 @@ # 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. -""" +"""Build script for the CI `test_suites` target.""" import argparse -from collections.abc import Sequence -import json +import logging import os import pathlib -import re import subprocess import sys -from typing import Any -import test_mapping_module_retriever +class Error(Exception): -# 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 __init__(self, message): + super().__init__(message) -def build_test_suites(argv): - args = parse_args(argv) +class BuildFailureError(Error): - 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) + def __init__(self, return_code): + super().__init__(f'Build command failed with return code: f{return_code}') + self.return_code = return_code -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='?') - - 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, args.extra_targets) - build_command.append('general-tests') - - run_command(build_command, print_output=True) +REQUIRED_ENV_VARS = frozenset(['TARGET_PRODUCT', 'TARGET_RELEASE', 'TOP']) +SOONG_UI_EXE_REL_PATH = 'build/soong/soong_ui.bash' -def build_affected_modules(args: argparse.Namespace): - modules_to_build = find_modules_to_build( - pathlib.Path(args.change_info), args.extra_required_modules - ) +def get_top() -> pathlib.Path: + return pathlib.Path(os.environ['TOP']) - # Call the build command with everything. - 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. - build_command.append('general-tests-shared-libs') - run_command(build_command, print_output=True) +def build_test_suites(argv: list[str]) -> int: + """Builds the general-tests and any other test suites passed in. - zip_build_outputs(modules_to_build, args.dist_dir, args.target_release) + Args: + argv: The command line arguments passed in. + Returns: + The exit code of the build. + """ + args = parse_args(argv) + check_required_env() -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(extra_targets) + try: + build_everything(args) + except BuildFailureError as e: + logging.error('Build command failed! Check build_log for details.') + return e.return_code - return build_command + return 0 -def run_command( - args: list[str], - env: dict[str, str] = 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) +def check_required_env(): + """Check for required env vars. - result.check_returncode() + Raises: + RuntimeError: If any required env vars are not found. + """ + missing_env_vars = sorted(v for v in REQUIRED_ENV_VARS if v not in os.environ) - if print_output: - print(result.stdout) + if not missing_env_vars: + return - return result.stdout + t = ','.join(missing_env_vars) + raise Error(f'Missing required environment variables: {t}') -def find_modules_to_build( - change_info: pathlib.Path, extra_required_modules: list[str] -) -> set[str]: - changed_files = find_changed_files(change_info) +def parse_args(argv): + argparser = argparse.ArgumentParser() - test_mappings = test_mapping_module_retriever.GetTestMappings( - changed_files, set() + argparser.add_argument( + 'extra_targets', nargs='*', help='Extra test suites to build.' ) - # 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 - + return argparser.parse_args(argv) -def find_changed_files(change_info: pathlib.Path) -> set[str]: - 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[str] -) -> set[str]: - 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[set], changed_files: set[str] -) -> 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[str], dist_dir: str, target_release: str -): - 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 = pathlib.Path( - get_soong_var('HOST_OUT_TESTCASES', target_release) - ) - target_out_testcases = pathlib.Path( - get_soong_var('TARGET_OUT_TESTCASES', target_release) - ) - 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)) - - # Call the class to package the outputs. - # TODO(lucafarsi): Move this code into a replaceable class. - host_paths = [] - target_paths = [] - host_config_files = [] - target_config_files = [] - 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) - collect_config_files(src_top, host_path, host_config_files) - - target_path = os.path.join(target_out_testcases, module) - if os.path.exists(target_path): - target_paths.append(target_path) - collect_config_files(src_top, target_path, target_config_files) - - zip_test_configs_zips( - dist_dir, host_out, product_out, host_config_files, target_config_files - ) - - zip_command = base_zip_command(host_out, dist_dir, 'general-tests.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')) +def build_everything(args: argparse.Namespace): + """Builds all tests (regardless of whether they are needed). - run_command(zip_command, print_output=True) + Args: + args: The parsed arguments. + Raises: + BuildFailure: If the build command fails. + """ + build_command = base_build_command(args, args.extra_targets) -def collect_config_files( - src_top: pathlib.Path, root_dir: pathlib.Path, config_files: list[str] -): - for root, dirs, files in os.walk(os.path.join(src_top, root_dir)): - for file in files: - if file.endswith('.config'): - config_files.append(os.path.join(root_dir, file)) + try: + run_command(build_command) + except subprocess.CalledProcessError as e: + raise BuildFailureError(e.returncode) from e -def base_zip_command( - host_out: pathlib.Path, dist_dir: pathlib.Path, name: str +def base_build_command( + args: argparse.Namespace, extra_targets: set[str] ) -> list[str]: - return [ - 'time', - os.path.join(host_out, 'bin', 'soong_zip'), - '-d', - '-o', - os.path.join(dist_dir, name), - ] - - -# generate general-tests_configs.zip which contains all of the .config files -# that were built and general-tests_list.zip which contains a text file which -# lists all of the .config files that are in general-tests_configs.zip. -# -# general-tests_comfigs.zip is organized as follows: -# / -# host/ -# testcases/ -# test_1.config -# test_2.config -# ... -# target/ -# testcases/ -# test_1.config -# test_2.config -# ... -# -# So the process is we write out the paths to all the host config files into one -# file and all the paths to the target config files in another. We also write -# the paths to all the config files into a third file to use for -# general-tests_list.zip. -def zip_test_configs_zips( - dist_dir: pathlib.Path, - host_out: pathlib.Path, - product_out: pathlib.Path, - host_config_files: list[str], - target_config_files: list[str], -): - with open( - os.path.join(host_out, 'host_general-tests_list'), 'w' - ) as host_list_file, open( - os.path.join(product_out, 'target_general-tests_list'), 'w' - ) as target_list_file, open( - os.path.join(host_out, 'general-tests_list'), 'w' - ) as list_file: - - for config_file in host_config_files: - host_list_file.write(config_file + '\n') - list_file.write('host/' + os.path.relpath(config_file, host_out) + '\n') - - for config_file in target_config_files: - target_list_file.write(config_file + '\n') - list_file.write( - 'target/' + os.path.relpath(config_file, product_out) + '\n' - ) - - tests_config_zip_command = base_zip_command( - host_out, dist_dir, 'general-tests_configs.zip' - ) - tests_config_zip_command.append('-P') - tests_config_zip_command.append('host') - tests_config_zip_command.append('-C') - tests_config_zip_command.append(host_out) - tests_config_zip_command.append('-l') - tests_config_zip_command.append( - os.path.join(host_out, 'host_general-tests_list') - ) - tests_config_zip_command.append('-P') - tests_config_zip_command.append('target') - tests_config_zip_command.append('-C') - tests_config_zip_command.append(product_out) - tests_config_zip_command.append('-l') - tests_config_zip_command.append( - os.path.join(product_out, 'target_general-tests_list') - ) - run_command(tests_config_zip_command, print_output=True) - - tests_list_zip_command = base_zip_command( - host_out, dist_dir, 'general-tests_list.zip' - ) - tests_list_zip_command.append('-C') - tests_list_zip_command.append(host_out) - tests_list_zip_command.append('-f') - tests_list_zip_command.append(os.path.join(host_out, 'general-tests_list')) - run_command(tests_list_zip_command, print_output=True) + build_command = [] + build_command.append(get_top().joinpath(SOONG_UI_EXE_REL_PATH)) + build_command.append('--make-mode') + build_command.extend(extra_targets) -def get_soong_var(var: str, target_release: str) -> str: - new_env = os.environ.copy() - new_env['TARGET_RELEASE'] = target_release + return build_command - 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 run_command(args: list[str], stdout=None): + subprocess.run(args=args, check=True, stdout=stdout) def main(argv): - build_test_suites(argv) + sys.exit(build_test_suites(argv)) diff --git a/ci/build_test_suites_local_test.py b/ci/build_test_suites_local_test.py new file mode 100644 index 0000000000..78e52d327c --- /dev/null +++ b/ci/build_test_suites_local_test.py @@ -0,0 +1,123 @@ +# 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. + +"""Integration tests for build_test_suites that require a local build env.""" + +import os +import pathlib +import shutil +import signal +import subprocess +import tempfile +import time +import ci_test_lib + + +class BuildTestSuitesLocalTest(ci_test_lib.TestCase): + + def setUp(self): + self.top_dir = pathlib.Path(os.environ['ANDROID_BUILD_TOP']).resolve() + self.executable = self.top_dir.joinpath('build/make/ci/build_test_suites') + self.process_session = ci_test_lib.TemporaryProcessSession(self) + self.temp_dir = ci_test_lib.TestTemporaryDirectory.create(self) + + def build_subprocess_args(self, build_args: list[str]): + env = os.environ.copy() + env['TOP'] = str(self.top_dir) + env['OUT_DIR'] = self.temp_dir + + args = ([self.executable] + build_args,) + kwargs = { + 'cwd': self.top_dir, + 'env': env, + 'text': True, + } + + return (args, kwargs) + + def run_build(self, build_args: list[str]) -> subprocess.CompletedProcess: + args, kwargs = self.build_subprocess_args(build_args) + + return subprocess.run( + *args, + **kwargs, + check=True, + capture_output=True, + timeout=5 * 60, + ) + + def assert_children_alive(self, children: list[int]): + for c in children: + self.assertTrue(ci_test_lib.process_alive(c)) + + def assert_children_dead(self, children: list[int]): + for c in children: + self.assertFalse(ci_test_lib.process_alive(c)) + + def test_fails_for_invalid_arg(self): + invalid_arg = '--invalid-arg' + + with self.assertRaises(subprocess.CalledProcessError) as cm: + self.run_build([invalid_arg]) + + self.assertIn(invalid_arg, cm.exception.stderr) + + def test_builds_successfully(self): + self.run_build(['nothing']) + + def test_can_interrupt_build(self): + args, kwargs = self.build_subprocess_args(['general-tests']) + p = self.process_session.create(args, kwargs) + + # TODO(lucafarsi): Replace this (and other instances) with a condition. + time.sleep(5) # Wait for the build to get going. + self.assertIsNone(p.poll()) # Check that the process is still alive. + children = query_child_pids(p.pid) + self.assert_children_alive(children) + + p.send_signal(signal.SIGINT) + p.wait() + + time.sleep(5) # Wait for things to die out. + self.assert_children_dead(children) + + def test_can_kill_build_process_group(self): + args, kwargs = self.build_subprocess_args(['general-tests']) + p = self.process_session.create(args, kwargs) + + time.sleep(5) # Wait for the build to get going. + self.assertIsNone(p.poll()) # Check that the process is still alive. + children = query_child_pids(p.pid) + self.assert_children_alive(children) + + os.killpg(os.getpgid(p.pid), signal.SIGKILL) + p.wait() + + time.sleep(5) # Wait for things to die out. + self.assert_children_dead(children) + + +# TODO(hzalek): Replace this with `psutils` once available in the tree. +def query_child_pids(parent_pid: int) -> set[int]: + p = subprocess.run( + ['pgrep', '-P', str(parent_pid)], + check=True, + capture_output=True, + text=True, + ) + return {int(pid) for pid in p.stdout.splitlines()} + + +if __name__ == '__main__': + ci_test_lib.main() diff --git a/ci/build_test_suites_test.py b/ci/build_test_suites_test.py new file mode 100644 index 0000000000..08a79a3294 --- /dev/null +++ b/ci/build_test_suites_test.py @@ -0,0 +1,254 @@ +# 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. + +"""Tests for build_test_suites.py""" + +from importlib import resources +import multiprocessing +import os +import pathlib +import shutil +import signal +import stat +import subprocess +import sys +import tempfile +import textwrap +import time +from typing import Callable +from unittest import mock +import build_test_suites +import ci_test_lib +from pyfakefs import fake_filesystem_unittest + + +class BuildTestSuitesTest(fake_filesystem_unittest.TestCase): + + def setUp(self): + self.setUpPyfakefs() + + os_environ_patcher = mock.patch.dict('os.environ', {}) + self.addCleanup(os_environ_patcher.stop) + self.mock_os_environ = os_environ_patcher.start() + + subprocess_run_patcher = mock.patch('subprocess.run') + self.addCleanup(subprocess_run_patcher.stop) + self.mock_subprocess_run = subprocess_run_patcher.start() + + self._setup_working_build_env() + + def test_missing_target_release_env_var_raises(self): + del os.environ['TARGET_RELEASE'] + + with self.assert_raises_word(build_test_suites.Error, 'TARGET_RELEASE'): + build_test_suites.main([]) + + def test_missing_target_product_env_var_raises(self): + del os.environ['TARGET_PRODUCT'] + + with self.assert_raises_word(build_test_suites.Error, 'TARGET_PRODUCT'): + build_test_suites.main([]) + + def test_missing_top_env_var_raises(self): + del os.environ['TOP'] + + with self.assert_raises_word(build_test_suites.Error, 'TOP'): + build_test_suites.main([]) + + def test_invalid_arg_raises(self): + invalid_args = ['--invalid_arg'] + + with self.assertRaisesRegex(SystemExit, '2'): + build_test_suites.main(invalid_args) + + def test_build_failure_returns(self): + self.mock_subprocess_run.side_effect = subprocess.CalledProcessError( + 42, None + ) + + with self.assertRaisesRegex(SystemExit, '42'): + build_test_suites.main([]) + + def test_build_success_returns(self): + with self.assertRaisesRegex(SystemExit, '0'): + build_test_suites.main([]) + + def assert_raises_word(self, cls, word): + return self.assertRaisesRegex(build_test_suites.Error, rf'\b{word}\b') + + def _setup_working_build_env(self): + self.fake_top = pathlib.Path('/fake/top') + self.fake_top.mkdir(parents=True) + + self.soong_ui_dir = self.fake_top.joinpath('build/soong') + self.soong_ui_dir.mkdir(parents=True, exist_ok=True) + + self.soong_ui = self.soong_ui_dir.joinpath('soong_ui.bash') + self.soong_ui.touch() + + self.mock_os_environ.update({ + 'TARGET_RELEASE': 'release', + 'TARGET_PRODUCT': 'product', + 'TOP': str(self.fake_top), + }) + + self.mock_subprocess_run.return_value = 0 + + +class RunCommandIntegrationTest(ci_test_lib.TestCase): + + def setUp(self): + self.temp_dir = ci_test_lib.TestTemporaryDirectory.create(self) + + # Copy the Python executable from 'non-code' resources and make it + # executable for use by tests that launch a subprocess. Note that we don't + # use Python's native `sys.executable` property since that is not set when + # running via the embedded launcher. + base_name = 'py3-cmd' + dest_file = self.temp_dir.joinpath(base_name) + with resources.as_file( + resources.files('testdata').joinpath(base_name) + ) as p: + shutil.copy(p, dest_file) + dest_file.chmod(dest_file.stat().st_mode | stat.S_IEXEC) + self.python_executable = dest_file + + self._managed_processes = [] + + def tearDown(self): + self._terminate_managed_processes() + + def test_raises_on_nonzero_exit(self): + with self.assertRaises(Exception): + build_test_suites.run_command([ + self.python_executable, + '-c', + textwrap.dedent(f"""\ + import sys + sys.exit(1) + """), + ]) + + def test_streams_stdout(self): + + def run_slow_command(stdout_file, marker): + with open(stdout_file, 'w') as f: + build_test_suites.run_command( + [ + self.python_executable, + '-c', + textwrap.dedent(f"""\ + import time + + print('{marker}', end='', flush=True) + + # Keep process alive until we check stdout. + time.sleep(10) + """), + ], + stdout=f, + ) + + marker = 'Spinach' + stdout_file = self.temp_dir.joinpath('stdout.txt') + + p = self.start_process(target=run_slow_command, args=[stdout_file, marker]) + + self.assert_file_eventually_contains(stdout_file, marker) + + def test_propagates_interruptions(self): + + def run(pid_file): + build_test_suites.run_command([ + self.python_executable, + '-c', + textwrap.dedent(f"""\ + import os + import pathlib + import time + + pathlib.Path('{pid_file}').write_text(str(os.getpid())) + + # Keep the process alive for us to explicitly interrupt it. + time.sleep(10) + """), + ]) + + pid_file = self.temp_dir.joinpath('pid.txt') + p = self.start_process(target=run, args=[pid_file]) + subprocess_pid = int(read_eventual_file_contents(pid_file)) + + os.kill(p.pid, signal.SIGINT) + p.join() + + self.assert_process_eventually_dies(p.pid) + self.assert_process_eventually_dies(subprocess_pid) + + def start_process(self, *args, **kwargs) -> multiprocessing.Process: + p = multiprocessing.Process(*args, **kwargs) + self._managed_processes.append(p) + p.start() + return p + + def assert_process_eventually_dies(self, pid: int): + try: + wait_until(lambda: not ci_test_lib.process_alive(pid)) + except TimeoutError as e: + self.fail(f'Process {pid} did not die after a while: {e}') + + def assert_file_eventually_contains(self, file: pathlib.Path, substring: str): + wait_until(lambda: file.is_file() and file.stat().st_size > 0) + self.assertIn(substring, read_file_contents(file)) + + def _terminate_managed_processes(self): + for p in self._managed_processes: + if not p.is_alive(): + continue + + # We terminate the process with `SIGINT` since using `terminate` or + # `SIGKILL` doesn't kill any grandchild processes and we don't have + # `psutil` available to easily query all children. + os.kill(p.pid, signal.SIGINT) + + +def wait_until( + condition_function: Callable[[], bool], + timeout_secs: float = 3.0, + polling_interval_secs: float = 0.1, +): + """Waits until a condition function returns True.""" + + start_time_secs = time.time() + + while not condition_function(): + if time.time() - start_time_secs > timeout_secs: + raise TimeoutError( + f'Condition not met within timeout: {timeout_secs} seconds' + ) + + time.sleep(polling_interval_secs) + + +def read_file_contents(file: pathlib.Path) -> str: + with open(file, 'r') as f: + return f.read() + + +def read_eventual_file_contents(file: pathlib.Path) -> str: + wait_until(lambda: file.is_file() and file.stat().st_size > 0) + return read_file_contents(file) + + +if __name__ == '__main__': + ci_test_lib.main() diff --git a/ci/ci_test_lib.py b/ci/ci_test_lib.py new file mode 100644 index 0000000000..2d70d3f01e --- /dev/null +++ b/ci/ci_test_lib.py @@ -0,0 +1,86 @@ +# 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. + +"""Testing utilities for tests in the CI package.""" + +import logging +import os +import unittest +import subprocess +import pathlib +import shutil +import tempfile + + +# Export the TestCase class to reduce the number of imports tests have to list. +TestCase = unittest.TestCase + + +def process_alive(pid): + """Check For the existence of a pid.""" + + try: + os.kill(pid, 0) + except OSError: + return False + + return True + + +class TemporaryProcessSession: + + def __init__(self, test_case: TestCase): + self._created_processes = [] + test_case.addCleanup(self.cleanup) + + def create(self, args, kwargs): + p = subprocess.Popen(*args, **kwargs, start_new_session=True) + self._created_processes.append(p) + return p + + def cleanup(self): + for p in self._created_processes: + if not process_alive(p.pid): + return + os.killpg(os.getpgid(p.pid), signal.SIGKILL) + + +class TestTemporaryDirectory: + + def __init__(self, delete: bool, ): + self._delete = delete + + @classmethod + def create(cls, test_case: TestCase, delete: bool = True): + temp_dir = TestTemporaryDirectory(delete) + temp_dir._dir = pathlib.Path(tempfile.mkdtemp()) + test_case.addCleanup(temp_dir.cleanup) + return temp_dir._dir + + def get_dir(self): + return self._dir + + def cleanup(self): + if not self._delete: + return + shutil.rmtree(self._dir, ignore_errors=True) + + +def main(): + + # Disable logging since it breaks the TF Python test output parser. + # TODO(hzalek): Use TF's `test-output-file` option to re-enable logging. + logging.getLogger().disabled = True + + unittest.main() diff --git a/cogsetup.sh b/cogsetup.sh index 44538f2a65..ef1485d5f2 100644 --- a/cogsetup.sh +++ b/cogsetup.sh @@ -52,7 +52,9 @@ function _setup_cog_env() { # it with this function. If the user is running repo within a Cog workspace, # we'll fail with an error, otherwise, we run the original repo command with # the given args. - ORIG_REPO_PATH=`which repo` + if ! ORIG_REPO_PATH=`which repo`; then + return 0 + fi function repo { if [[ "${PWD}" == /google/cog/* ]]; then echo "\e[01;31mERROR:\e[0mrepo command is disallowed within Cog workspaces." diff --git a/core/Makefile b/core/Makefile index 9d77ec10e0..4900ac2fdc 100644 --- a/core/Makefile +++ b/core/Makefile @@ -1128,10 +1128,15 @@ $(foreach \ BOARD_VENDOR_RAMDISK_FRAGMENT.16K.PREBUILT := $(BUILT_RAMDISK_16K_TARGET) +ifndef BOARD_KERNEL_MODULES_LOAD_16K + BOARD_KERNEL_MODULES_LOAD_16K := $(BOARD_KERNEL_MODULES_16K) +endif + $(BUILT_RAMDISK_16K_TARGET): $(DEPMOD) $(MKBOOTFS) $(EXTRACT_KERNEL) $(COMPRESSION_COMMAND_DEPS) $(BUILT_RAMDISK_16K_TARGET): $(foreach file,$(BOARD_KERNEL_MODULES_16K),$(RAMDISK_16K_STAGING_DIR)/lib/modules/0.0/$(notdir $(file))) $(DEPMOD) -b $(RAMDISK_16K_STAGING_DIR) 0.0 - for MODULE in $(BOARD_KERNEL_MODULES_16K); do \ + rm -f $(RAMDISK_16K_STAGING_DIR)/lib/modules/0.0/modules.load + for MODULE in $(BOARD_KERNEL_MODULES_LOAD_16K); do \ basename $$MODULE >> $(RAMDISK_16K_STAGING_DIR)/lib/modules/0.0/modules.load ; \ done; rm -rf $(TARGET_OUT_RAMDISK_16K)/lib/modules @@ -1469,11 +1474,17 @@ $(BUILT_BOOT_OTA_PACKAGE_4K): $(OTA_FROM_RAW_IMG) $(INSTALLED_BOOTIMAGE_TARGET) 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)) +ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_VENDOR)/boot_otas/boot_ota_4k.zip +ALL_DEFAULT_INSTALLED_MODULES += $(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)) - ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_4k.zip ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_16k.zip +endif # BOARD_16K_OTA_MOVE_VENDOR == true endif @@ -3395,12 +3406,30 @@ endif FULL_SYSTEMIMAGE_DEPS += $(INTERNAL_ROOT_FILES) $(INSTALLED_FILES_FILE_ROOT) -define write-file-lines -$(1): +# Returns a list of EXTRA_INSTALL_ZIPS trios whose primary file is contained within $(1) +# The trios will contain the primary installed file : the directory to unzip the zip to : the zip +define relevant-extra-install-zips +$(strip $(foreach p,$(EXTRA_INSTALL_ZIPS), \ + $(if $(filter $(call word-colon,1,$(p)),$(1)), \ + $(p)))) +endef + +# Writes a text file that contains all of the files that will be inside a partition. +# All the file paths will be relative to the partition's staging directory. +# It will also take into account files inside zips listed in EXTRA_INSTALL_ZIPS. +# +# Arguments: +# $(1): Output file +# $(2): The partition's staging directory +# $(3): Files to include in the partition +define write-partition-file-list +$(1): PRIVATE_RELEVANT_EXTRA_INSTALL_ZIPS := $(call relevant-extra-install-zips,$(filter $(2)/%,$(3))) +$(1): $$(HOST_OUT_EXECUTABLES)/extra_install_zips_file_list $$(foreach p,$$(PRIVATE_RELEVANT_EXTRA_INSTALL_ZIPS),$$(call word-colon,3,$$(p))) @echo Writing $$@ rm -f $$@ echo -n > $$@ - $$(foreach f,$(2),echo "$$(f)" >> $$@$$(newline)) + $$(foreach f,$(subst $(2)/,,$(filter $(2)/%,$(3))),echo "$$(f)" >> $$@$$(newline)) + $$(HOST_OUT_EXECUTABLES)/extra_install_zips_file_list $(2) $$(PRIVATE_RELEVANT_EXTRA_INSTALL_ZIPS) >> $$@ endef # ----------------------------------------------------------------- @@ -3466,7 +3495,7 @@ define build-systemimage-target exit 1 ) endef -$(eval $(call write-file-lines,$(systemimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT)/,,$(filter $(TARGET_OUT)/%,$(FULL_SYSTEMIMAGE_DEPS))))) +$(eval $(call write-partition-file-list,$(systemimage_intermediates)/file_list.txt,$(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 $@ @@ -3583,7 +3612,7 @@ INSTALLED_USERDATAIMAGE_TARGET_DEPS := \ $(INTERNAL_USERIMAGES_DEPS) \ $(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))))) +$(eval $(call write-partition-file-list,$(userdataimage_intermediates)/file_list.txt,$(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 $@ @@ -3639,7 +3668,7 @@ define build-cacheimage-target $(call assert-max-image-size,$(INSTALLED_CACHEIMAGE_TARGET),$(BOARD_CACHEIMAGE_PARTITION_SIZE)) endef -$(eval $(call write-file-lines,$(cacheimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_CACHE)/,,$(filter $(TARGET_OUT_CACHE)/%,$(INTERNAL_CACHEIMAGE_FILES))))) +$(eval $(call write-partition-file-list,$(cacheimage_intermediates)/file_list.txt,$(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 $@ @@ -3726,7 +3755,7 @@ define build-systemotherimage-target $(call assert-max-image-size,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)) endef -$(eval $(call write-file-lines,$(systemotherimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_SYSTEM_OTHER)/,,$(filter $(TARGET_OUT_SYSTEM_OTHER)/%,$(INTERNAL_SYSTEMOTHERIMAGE_FILES))))) +$(eval $(call write-partition-file-list,$(systemotherimage_intermediates)/file_list.txt,$(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 $@ @@ -3832,7 +3861,7 @@ define build-vendorimage-target $(call assert-max-image-size,$(INSTALLED_VENDORIMAGE_TARGET) $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_VENDORIMAGE_PARTITION_SIZE)) endef -$(eval $(call write-file-lines,$(vendorimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_VENDOR)/,,$(filter $(TARGET_OUT_VENDOR)/%,$(INTERNAL_VENDORIMAGE_FILES))))) +$(eval $(call write-partition-file-list,$(vendorimage_intermediates)/file_list.txt,$(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 $@ @@ -3905,7 +3934,7 @@ define build-productimage-target $(call assert-max-image-size,$(INSTALLED_PRODUCTIMAGE_TARGET),$(BOARD_PRODUCTIMAGE_PARTITION_SIZE)) endef -$(eval $(call write-file-lines,$(productimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_PRODUCT)/,,$(filter $(TARGET_OUT_PRODUCT)/%,$(INTERNAL_PRODUCTIMAGE_FILES))))) +$(eval $(call write-partition-file-list,$(productimage_intermediates)/file_list.txt,$(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 $@ @@ -3975,7 +4004,7 @@ define build-system_extimage-target $(call assert-max-image-size,$(INSTALLED_PRODUCT_SERVICESIMAGE_TARGET),$(BOARD_PRODUCT_SERVICESIMAGE_PARTITION_SIZE)) 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))))) +$(eval $(call write-partition-file-list,$(system_extimage_intermediates)/file_list.txt,$(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 $@ @@ -4064,7 +4093,7 @@ define build-odmimage-target $(call assert-max-image-size,$(INSTALLED_ODMIMAGE_TARGET),$(BOARD_ODMIMAGE_PARTITION_SIZE)) endef -$(eval $(call write-file-lines,$(odmimage_intermediates)/file_list.txt,$(subst $(TARGET_OUT_ODM)/,,$(filter $(TARGET_OUT_ODM)/%,$(INTERNAL_ODMIMAGE_FILES))))) +$(eval $(call write-partition-file-list,$(odmimage_intermediates)/file_list.txt,$(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 $@ @@ -4133,7 +4162,7 @@ define build-vendor_dlkmimage-target $(call assert-max-image-size,$(INSTALLED_VENDOR_DLKMIMAGE_TARGET),$(BOARD_VENDOR_DLKMIMAGE_PARTITION_SIZE)) 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))))) +$(eval $(call write-partition-file-list,$(vendor_dlkmimage_intermediates)/file_list.txt,$(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 $@ @@ -4202,7 +4231,7 @@ define build-odm_dlkmimage-target $(call assert-max-image-size,$(INSTALLED_ODM_DLKMIMAGE_TARGET),$(BOARD_ODM_DLKMIMAGE_PARTITION_SIZE)) 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))))) +$(eval $(call write-partition-file-list,$(odm_dlkmimage_intermediates)/file_list.txt,$(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 $@ @@ -4273,7 +4302,7 @@ define build-system_dlkmimage-target $(call assert-max-image-size,$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE)) 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))))) +$(eval $(call write-partition-file-list,$(system_dlkmimage_intermediates)/file_list.txt,$(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 $@ @@ -6206,6 +6235,8 @@ define dump-dynamic-partitions-info echo "virtual_ab_retrofit=true" >> $(1)) $(if $(PRODUCT_VIRTUAL_AB_COW_VERSION), \ echo "virtual_ab_cow_version=$(PRODUCT_VIRTUAL_AB_COW_VERSION)" >> $(1)) + $(if $(PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR), \ + echo "virtual_ab_compression_factor=$(PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR)" >> $(1)) endef # Copy an image file to a directory and generate a block list map file from the image, @@ -6907,6 +6938,7 @@ PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$(dir $(ZIP2ZIP)):$$PATH \ --verbose \ --path $(HOST_OUT) \ $(if $(OEM_OTA_CONFIG), --oem_settings $(OEM_OTA_CONFIG)) \ + $(if $(BOOT_VAR_OTA_CONFIG), --boot_variable_file $(BOOT_VAR_OTA_CONFIG)) \ $(2) \ $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE)) $(1) endef diff --git a/core/OWNERS b/core/OWNERS index 36951a9589..35ea83d2fe 100644 --- a/core/OWNERS +++ b/core/OWNERS @@ -10,3 +10,4 @@ per-file version_defaults.mk = amhk@google.com,gurpreetgs@google.com,mkhokhlova@ # 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 ca87417eaa..ed72fc3a0d 100644 --- a/core/android_soong_config_vars.mk +++ b/core/android_soong_config_vars.mk @@ -26,96 +26,18 @@ $(call add_soong_config_namespace,ANDROID) # Add variables to the namespace below: -$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER) -$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_DRMSERVER) -$(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64) $(call add_soong_config_var,ANDROID,BOARD_USES_ODMIMAGE) $(call add_soong_config_var,ANDROID,BOARD_USES_RECOVERY_AS_BOOT) $(call add_soong_config_var,ANDROID,CHECK_DEV_TYPE_VIOLATIONS) +$(call add_soong_config_var,ANDROID,PLATFORM_SEPOLICY_COMPAT_VERSIONS) $(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT) +$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_DRMSERVER) +$(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64) +$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER) -# 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 := $(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 ($(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 - -ifneq ($(CLANG_COVERAGE)$(NATIVE_COVERAGE_PATHS),) - # Always use sources when building with clang coverage and native coverage. - # It is possible that there are certain situations when building with coverage - # would work with prebuilts, e.g. when the coverage is not being applied to - # modules for which we provide prebuilts. Unfortunately, determining that - # would require embedding knowledge of which coverage paths affect which - # modules here. That would duplicate a lot of information, add yet another - # location module authors have to update and complicate the logic here. - # For nowe we will just always build from sources when doing coverage builds. - 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 -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. - MODULE_BUILD_FROM_SOURCE := true -else ifneq (,$(PRODUCT_MODULE_BUILD_FROM_SOURCE)) - # Let products override the branch default. - MODULE_BUILD_FROM_SOURCE := $(PRODUCT_MODULE_BUILD_FROM_SOURCE) -else - MODULE_BUILD_FROM_SOURCE := $(BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE) -endif - -ifneq (,$(ART_MODULE_BUILD_FROM_SOURCE)) - # Keep an explicit setting. -else ifneq (,$(findstring .android.art,$(TARGET_BUILD_APPS))) - # Build ART modules from source if they are listed in TARGET_BUILD_APPS. - ART_MODULE_BUILD_FROM_SOURCE := true -else - # Do the same as other modules by default. - ART_MODULE_BUILD_FROM_SOURCE := $(MODULE_BUILD_FROM_SOURCE) -endif +# PRODUCT_PRECOMPILED_SEPOLICY defaults to true. Explicitly check if it's "false" or not. +$(call add_soong_config_var_value,ANDROID,PRODUCT_PRECOMPILED_SEPOLICY,$(if $(filter false,$(PRODUCT_PRECOMPILED_SEPOLICY)),false,true)) -$(call soong_config_set,art_module,source_build,$(ART_MODULE_BUILD_FROM_SOURCE)) ifdef ART_DEBUG_OPT_FLAG $(call soong_config_set,art_module,art_debug_opt_flag,$(ART_DEBUG_OPT_FLAG)) endif @@ -124,34 +46,6 @@ ifdef TARGET_BOARD_AUTO $(call add_soong_config_var_value, ANDROID, target_board_auto, $(TARGET_BOARD_AUTO)) endif -# Ensure that those mainline modules who have individually toggleable prebuilts -# 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 \ - -$(foreach m, $(INDIVIDUALLY_TOGGLEABLE_PREBUILT_MODULES),\ - $(if $(call soong_config_get,$(m)_module,source_build),,\ - $(call soong_config_set,$(m)_module,source_build,$(MODULE_BUILD_FROM_SOURCE)))) - # Apex build mode variables ifdef APEX_BUILD_FOR_PRE_S_DEVICES $(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static) @@ -161,9 +55,10 @@ $(call add_soong_config_var_value,ANDROID,library_linking_strategy,prefer_static endif endif -ifeq (true,$(MODULE_BUILD_FROM_SOURCE)) +# TODO(b/308187800): some internal modules set `prefer` to true on the prebuilt apex module, +# and set that to false when `ANDROID.module_build_from_source` is true. +# Set this soong config variable to true for now, and cleanup `prefer` as part of b/308187800 $(call add_soong_config_var_value,ANDROID,module_build_from_source,true) -endif # Messaging app vars ifeq (eng,$(TARGET_BUILD_VARIANT)) @@ -182,6 +77,18 @@ ifdef PRODUCT_AVF_ENABLED $(call add_soong_config_var_value,ANDROID,avf_enabled,$(PRODUCT_AVF_ENABLED)) endif +ifdef PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION +$(call add_soong_config_var_value,ANDROID,avf_microdroid_guest_gki_version,$(PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION)) +endif + +ifdef PRODUCT_MEMCG_V2_FORCE_ENABLED +$(call add_soong_config_var_value,ANDROID,memcg_v2_force_enabled,$(PRODUCT_MEMCG_V2_FORCE_ENABLED)) +endif + +ifdef PRODUCT_CGROUP_V2_SYS_APP_ISOLATION_ENABLED +$(call add_soong_config_var_value,ANDROID,cgroup_v2_sys_app_isolation,$(PRODUCT_CGROUP_V2_SYS_APP_ISOLATION_ENABLED)) +endif + $(call add_soong_config_var_value,ANDROID,release_avf_allow_preinstalled_apps,$(RELEASE_AVF_ALLOW_PREINSTALLED_APPS)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_device_assignment,$(RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_dice_changes,$(RELEASE_AVF_ENABLE_DICE_CHANGES)) @@ -191,11 +98,16 @@ $(call add_soong_config_var_value,ANDROID,release_avf_enable_remote_attestation, $(call add_soong_config_var_value,ANDROID,release_avf_enable_vendor_modules,$(RELEASE_AVF_ENABLE_VENDOR_MODULES)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_virt_cpufreq,$(RELEASE_AVF_ENABLE_VIRT_CPUFREQ)) $(call add_soong_config_var_value,ANDROID,release_avf_microdroid_kernel_version,$(RELEASE_AVF_MICRODROID_KERNEL_VERSION)) +$(call add_soong_config_var_value,ANDROID,release_avf_support_custom_vm_with_paravirtualized_devices,$(RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES)) $(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)) +$(call add_soong_config_var_value,ANDROID,release_write_appcompat_override_system_properties,$(RELEASE_WRITE_APPCOMPAT_OVERRIDE_SYSTEM_PROPERTIES)) + # 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 47b4bcfce6..196db4f30b 100644 --- a/core/art_config.mk +++ b/core/art_config.mk @@ -19,8 +19,6 @@ ifeq (,$(filter default true false,$(config_enable_uffd_gc))) endif ENABLE_UFFD_GC := $(config_enable_uffd_gc) -# If the value is "default", it will be mangled by post_process_props.py. -ADDITIONAL_PRODUCT_PROPERTIES += ro.dalvik.vm.enable_uffd_gc=$(config_enable_uffd_gc) # Create APEX_BOOT_JARS_EXCLUDED which is a list of jars to be removed from # ApexBoorJars when built from mainline prebuilts. diff --git a/core/base_rules.mk b/core/base_rules.mk index b8aa5fed03..9c7e906671 100644 --- a/core/base_rules.mk +++ b/core/base_rules.mk @@ -393,8 +393,8 @@ endif logtags_sources := $(filter %.logtags,$(LOCAL_SRC_FILES)) $(LOCAL_LOGTAGS_FILES) -ifneq ($(strip $(logtags_sources)),) -event_log_tags := $(foreach f,$(addprefix $(LOCAL_PATH)/,$(logtags_sources)),$(call clean-path,$(f))) +ifneq ($(strip $(logtags_sources) $(LOCAL_SOONG_LOGTAGS_FILES)),) +event_log_tags := $(foreach f,$(LOCAL_SOONG_LOGTAGS_FILES) $(addprefix $(LOCAL_PATH)/,$(logtags_sources)),$(call clean-path,$(f))) else event_log_tags := endif @@ -694,6 +694,16 @@ endif endif ########################################################### +## SOONG INSTALL PAIRS +########################################################### +# Declare dependencies for LOCAL_SOONG_INSTALL_PAIRS in soong to the module it relies on. +ifneq (,$(LOCAL_SOONG_INSTALLED_MODULE)) +$(my_all_targets): \ + $(foreach f, $(LOCAL_SOONG_INSTALL_PAIRS),\ + $(word 2,$(subst :,$(space),$(f)))) +endif + +########################################################### ## Compatibility suite files. ########################################################### ifdef LOCAL_COMPATIBILITY_SUITE @@ -706,6 +716,14 @@ else test_config := $(wildcard $(LOCAL_PATH)/AndroidTest.xml) endif +ifeq ($(EXCLUDE_MCTS),true) + ifneq (,$(test_config)) + ifneq (,$(filter mcts-%,$(LOCAL_COMPATIBILITY_SUITE))) + LOCAL_COMPATIBILITY_SUITE := $(filter-out cts,$(LOCAL_COMPATIBILITY_SUITE)) + endif + endif +endif + ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE)) # If we are building a native test or benchmark and its stem variants are not defined, diff --git a/core/binary.mk b/core/binary.mk index b17ab00aad..f86b5a464e 100644 --- a/core/binary.mk +++ b/core/binary.mk @@ -1196,6 +1196,17 @@ ifneq ($(filter hwaddress,$(my_sanitize)),) endif ################################################################### +## When compiling a memtag_stack enabled target, use the .memtag_stack variant +## of any static dependencies (where they exist). +################################################################## +ifneq ($(filter memtag_stack,$(my_sanitize)),) + my_whole_static_libraries := $(call use_soong_sanitized_static_libraries,\ + $(my_whole_static_libraries),memtag_stack) + my_static_libraries := $(call use_soong_sanitized_static_libraries,\ + $(my_static_libraries),memtag_stack) +endif + +################################################################### ## When compiling against API imported module, use API import stub ## libraries. ################################################################## diff --git a/core/board_config.mk b/core/board_config.mk index 25e0643fca..e184601008 100644 --- a/core/board_config.mk +++ b/core/board_config.mk @@ -274,7 +274,7 @@ endif ifneq ($(MALLOC_IMPL),) $(warning *** Unsupported option MALLOC_IMPL defined by board config: $(board_config_mk).) - $(error Use `MALLOC_SVELTE := true` to configure jemalloc for low-memory) + $(error Use `MALLOC_LOW_MEMORY := true` to use low-memory allocator config) endif board_config_mk := @@ -973,15 +973,6 @@ define check_vndk_version $(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)) @@ -1001,7 +992,7 @@ endif # 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/clear_vars.mk b/core/clear_vars.mk index 5481d50713..fb42878584 100644 --- a/core/clear_vars.mk +++ b/core/clear_vars.mk @@ -264,6 +264,7 @@ LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR := LOCAL_SOONG_LICENSE_METADATA := LOCAL_SOONG_LINK_TYPE := LOCAL_SOONG_LINT_REPORTS := +LOCAL_SOONG_LOGTAGS_FILES := LOCAL_SOONG_MODULE_INFO_JSON := LOCAL_SOONG_MODULE_TYPE := LOCAL_SOONG_PROGUARD_DICT := diff --git a/core/config.mk b/core/config.mk index daefa70f57..ce11b1d558 100644 --- a/core/config.mk +++ b/core/config.mk @@ -420,9 +420,12 @@ endif .KATI_READONLY := TARGET_MAX_PAGE_SIZE_SUPPORTED # Boolean variable determining if AOSP relies on bionic's PAGE_SIZE macro. -TARGET_NO_BIONIC_PAGE_SIZE_MACRO := false ifdef PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO TARGET_NO_BIONIC_PAGE_SIZE_MACRO := $(PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO) +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 @@ -506,7 +509,6 @@ include $(BUILD_SYSTEM)/combo/javac.mk ifeq ($(CALLED_FROM_SETUP),true) include $(BUILD_SYSTEM)/ccache.mk -include $(BUILD_SYSTEM)/goma.mk include $(BUILD_SYSTEM)/rbe.mk endif @@ -600,8 +602,6 @@ else prebuilt_build_tools_bin := $(prebuilt_build_tools)/$(HOST_PREBUILT_TAG)/asan/bin endif -USE_PREBUILT_SDK_TOOLS_IN_PLACE := true - # Work around for b/68406220 # This should match the soong version. USE_D8 := true @@ -1231,6 +1231,8 @@ BUILD_WARNING_BAD_OPTIONAL_USES_LIBS_ALLOWLIST := LegacyCamera Gallery2 # in the source tree. dont_bother_goals := out product-graph +include $(BUILD_SYSTEM)/sysprop_config.mk + # Make ANDROID Soong config variables visible to Android.mk files, for # consistency with those defined in BoardConfig.mk files. include $(BUILD_SYSTEM)/android_soong_config_vars.mk diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk index 08311ca481..26b8b17a49 100644 --- a/core/dex_preopt.mk +++ b/core/dex_preopt.mk @@ -123,22 +123,28 @@ $(boot_zip): $(bootclasspath_jars) $(system_server_jars) $(SOONG_ZIP) $(MERGE_ZI $(call dist-for-goals, droidcore, $(boot_zip)) -ifneq (,$(filter true,$(ART_MODULE_BUILD_FROM_SOURCE) $(MODULE_BUILD_FROM_SOURCE))) # Build the system_server.zip which contains the Apex system server jars and standalone system server jars +system_server_dex2oat_dir := $(SOONG_OUT_DIR)/system_server_dexjars system_server_zip := $(PRODUCT_OUT)/system_server.zip +# non_updatable_system_server_jars contains jars in /system and /system_ext that are not part of an apex. +non_updatable_system_server_jars := \ + $(foreach m,$(PRODUCT_SYSTEM_SERVER_JARS),\ + $(system_server_dex2oat_dir)/$(call word-colon,2,$(m)).jar) + apex_system_server_jars := \ $(foreach m,$(PRODUCT_APEX_SYSTEM_SERVER_JARS),\ - $(PRODUCT_OUT)/apex/$(call word-colon,1,$(m))/javalib/$(call word-colon,2,$(m)).jar) + $(system_server_dex2oat_dir)/$(call word-colon,2,$(m)).jar) apex_standalone_system_server_jars := \ $(foreach m,$(PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS),\ - $(PRODUCT_OUT)/apex/$(call word-colon,1,$(m))/javalib/$(call word-colon,2,$(m)).jar) + $(system_server_dex2oat_dir)/$(call word-colon,2,$(m)).jar) standalone_system_server_jars := \ $(foreach m,$(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS),\ - $(PRODUCT_OUT)/apex/$(call word-colon,1,$(m))/javalib/$(call word-colon,2,$(m)).jar) + $(system_server_dex2oat_dir)/$(call word-colon,2,$(m)).jar) -$(system_server_zip): PRIVATE_SYSTEM_SERVER_JARS := $(system_server_jars) +$(system_server_zip): PRIVATE_SYSTEM_SERVER_DEX2OAT_DIR := $(system_server_dex2oat_dir) +$(system_server_zip): PRIVATE_SYSTEM_SERVER_JARS := $(non_updatable_system_server_jars) $(system_server_zip): PRIVATE_APEX_SYSTEM_SERVER_JARS := $(apex_system_server_jars) $(system_server_zip): PRIVATE_APEX_STANDALONE_SYSTEM_SERVER_JARS := $(apex_standalone_system_server_jars) $(system_server_zip): PRIVATE_STANDALONE_SYSTEM_SERVER_JARS := $(standalone_system_server_jars) @@ -146,14 +152,13 @@ $(system_server_zip): $(system_server_jars) $(apex_system_server_jars) $(apex_st @echo "Create system server package: $@" rm -f $@ $(SOONG_ZIP) -o $@ \ - -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_SYSTEM_SERVER_JARS)) \ - -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_APEX_SYSTEM_SERVER_JARS)) \ - -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_APEX_STANDALONE_SYSTEM_SERVER_JARS)) \ - -C $(PRODUCT_OUT) $(addprefix -f ,$(PRIVATE_STANDALONE_SYSTEM_SERVER_JARS)) + -C $(PRIVATE_SYSTEM_SERVER_DEX2OAT_DIR) $(addprefix -f ,$(PRIVATE_SYSTEM_SERVER_JARS)) \ + -C $(PRIVATE_SYSTEM_SERVER_DEX2OAT_DIR) $(addprefix -f ,$(PRIVATE_APEX_SYSTEM_SERVER_JARS)) \ + -C $(PRIVATE_SYSTEM_SERVER_DEX2OAT_DIR) $(addprefix -f ,$(PRIVATE_APEX_STANDALONE_SYSTEM_SERVER_JARS)) \ + -C $(PRIVATE_SYSTEM_SERVER_DEX2OAT_DIR) $(addprefix -f ,$(PRIVATE_STANDALONE_SYSTEM_SERVER_JARS)) $(call dist-for-goals, droidcore, $(system_server_zip)) -endif #ART_MODULE_BUILD_FROM_SOURCE || MODULE_BUILD_FROM_SOURCE endif #PRODUCT_USES_DEFAULT_ART_CONFIG endif #WITH_DEXPREOPT_ART_BOOT_IMG_ONLY endif #WITH_DEXPREOPT diff --git a/core/envsetup.mk b/core/envsetup.mk index 7f9cbad0d8..1c3a1b3b6b 100644 --- a/core/envsetup.mk +++ b/core/envsetup.mk @@ -57,18 +57,6 @@ else 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 diff --git a/core/goma.mk b/core/goma.mk deleted file mode 100644 index 2b51d8be44..0000000000 --- a/core/goma.mk +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright (C) 2015 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. -# - -# Notice: this works only with Google's Goma build infrastructure. -ifneq ($(filter-out false,$(USE_GOMA)),) - ifdef GOMA_DIR - goma_dir := $(GOMA_DIR) - else - goma_dir := $(HOME)/goma - endif - GOMA_CC := $(goma_dir)/gomacc - - # Append gomacc to existing *_WRAPPER variables so it's possible to - # use both ccache and gomacc. - CC_WRAPPER := $(strip $(CC_WRAPPER) $(GOMA_CC)) - CXX_WRAPPER := $(strip $(CXX_WRAPPER) $(GOMA_CC)) - # b/143658984: goma can't handle the --system argument to javac - #JAVAC_WRAPPER := $(strip $(JAVAC_WRAPPER) $(GOMA_CC)) - - goma_dir := -endif diff --git a/core/main.mk b/core/main.mk index 051ebdd812..d700fcb231 100644 --- a/core/main.mk +++ b/core/main.mk @@ -113,37 +113,8 @@ ifdef TARGET_ARCH_SUITE # $(error TARGET_ARCH_SUITE is not supported in kati/make builds) endif -# ADDITIONAL_<partition>_PROPERTIES are properties that are determined by the -# build system itself. Don't let it be defined from outside of the core build -# system like Android.mk or <product>.mk files. -_additional_prop_var_names := \ - ADDITIONAL_SYSTEM_PROPERTIES \ - ADDITIONAL_VENDOR_PROPERTIES \ - ADDITIONAL_ODM_PROPERTIES \ - ADDITIONAL_PRODUCT_PROPERTIES - -$(foreach name, $(_additional_prop_var_names),\ - $(if $($(name)),\ - $(error $(name) must not set before here. $($(name)))\ - ,)\ - $(eval $(name) :=)\ -) -_additional_prop_var_names := - $(KATI_obsolete_var ADDITIONAL_BUILD_PROPERTIES, Please use ADDITIONAL_SYSTEM_PROPERTIES) -# -# ----------------------------------------------------------------- -# Add the product-defined properties to the build properties. -ifneq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true) - ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) -else - ifndef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE - ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) - endif -endif - - # Bring in standard build system definitions. include $(BUILD_SYSTEM)/definitions.mk @@ -175,200 +146,8 @@ endif # PDK builds are no longer supported, this is always platform TARGET_BUILD_JAVA_SUPPORT_LEVEL :=$= platform -# ----------------------------------------------------------------- - -ADDITIONAL_SYSTEM_PROPERTIES += ro.treble.enabled=${PRODUCT_FULL_TREBLE} - $(KATI_obsolete_var PRODUCT_FULL_TREBLE,\ Code should be written to work regardless of a device being Treble) - -# Set ro.llndk.api_level to show the maximum vendor API level that the LLNDK in -# the system partition supports. -ifdef RELEASE_BOARD_API_LEVEL -ADDITIONAL_SYSTEM_PROPERTIES += ro.llndk.api_level=$(RELEASE_BOARD_API_LEVEL) -endif - -# Sets ro.actionable_compatible_property.enabled to know on runtime whether the -# allowed list of actionable compatible properties is enabled or not. -ADDITIONAL_SYSTEM_PROPERTIES += ro.actionable_compatible_property.enabled=true - -# Add the system server compiler filter if they are specified for the product. -ifneq (,$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER)) -ADDITIONAL_PRODUCT_PROPERTIES += dalvik.vm.systemservercompilerfilter=$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER) -endif - -# Add the 16K developer option if it is defined for the product. -ifeq ($(PRODUCT_16K_DEVELOPER_OPTION),true) -ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=true -else -ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=false -endif - -# Enable core platform API violation warnings on userdebug and eng builds. -ifneq ($(TARGET_BUILD_VARIANT),user) -ADDITIONAL_SYSTEM_PROPERTIES += persist.debug.dalvik.vm.core_platform_api_policy=just-warn -endif - -# Define ro.sanitize.<name> properties for all global sanitizers. -ADDITIONAL_SYSTEM_PROPERTIES += $(foreach s,$(SANITIZE_TARGET),ro.sanitize.$(s)=true) - -# Sets the default value of ro.postinstall.fstab.prefix to /system. -# Device board config should override the value to /product when needed by: -# -# PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product -# -# It then uses ${ro.postinstall.fstab.prefix}/etc/fstab.postinstall to -# 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) -ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_arch=$(TARGET_2ND_ARCH) -ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_cpu_variant=$(TARGET_2ND_CPU_VARIANT_RUNTIME) - -ADDITIONAL_VENDOR_PROPERTIES += persist.sys.dalvik.vm.lib.2=libart.so -ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).variant=$(DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) -ifneq ($(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) - ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).features=$(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) -endif - -ifdef TARGET_2ND_ARCH - ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).variant=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) - ifneq ($($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) - ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).features=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) - endif -endif - -# Although these variables are prefixed with TARGET_RECOVERY_, they are also needed under charger -# mode (via libminui). -ifdef TARGET_RECOVERY_DEFAULT_ROTATION -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.minui.default_rotation=$(TARGET_RECOVERY_DEFAULT_ROTATION) -endif -ifdef TARGET_RECOVERY_OVERSCAN_PERCENT -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.minui.overscan_percent=$(TARGET_RECOVERY_OVERSCAN_PERCENT) -endif -ifdef TARGET_RECOVERY_PIXEL_FORMAT -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.minui.pixel_format=$(TARGET_RECOVERY_PIXEL_FORMAT) -endif - -ifdef PRODUCT_USE_DYNAMIC_PARTITIONS -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.boot.dynamic_partitions=$(PRODUCT_USE_DYNAMIC_PARTITIONS) -endif - -ifdef PRODUCT_RETROFIT_DYNAMIC_PARTITIONS -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.boot.dynamic_partitions_retrofit=$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS) -endif - -ifdef PRODUCT_SHIPPING_API_LEVEL -ADDITIONAL_VENDOR_PROPERTIES += \ - 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 += \ - ro.product.debugfs_restrictions.enabled=$(PRODUCT_SET_DEBUGFS_RESTRICTIONS) - endif -endif - -# Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level. -# This must not be defined for the non-GRF devices. -# The values of the GRF properties will be verified by post_process_props.py -ifdef BOARD_SHIPPING_API_LEVEL -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.board.first_api_level=$(BOARD_SHIPPING_API_LEVEL) -endif - -# Build system set BOARD_API_LEVEL to show the api level of the vendor API surface. -# This must not be altered outside of build system. -ifdef BOARD_API_LEVEL -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.board.api_level=$(BOARD_API_LEVEL) -endif -# RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen. -ifdef RELEASE_BOARD_API_LEVEL_FROZEN -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.board.api_frozen=$(RELEASE_BOARD_API_LEVEL_FROZEN) -endif - -# Set build prop. This prop is read by ota_from_target_files when generating OTA, -# to decide if VABC should be disabled. -ifeq ($(BOARD_DONT_USE_VABC_OTA),true) -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.vendor.build.dont_use_vabc=true -endif - -# Set the flag in vendor. So VTS would know if the new fingerprint format is in use when -# the system images are replaced by GSI. -ifeq ($(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT),true) -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.vendor.build.fingerprint_has_digest=1 -endif - -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.vendor.build.security_patch=$(VENDOR_SECURITY_PATCH) \ - ro.product.board=$(TARGET_BOOTLOADER_BOARD_NAME) \ - ro.board.platform=$(TARGET_BOARD_PLATFORM) \ - ro.hwui.use_vulkan=$(TARGET_USES_VULKAN) - -ifdef TARGET_SCREEN_DENSITY -ADDITIONAL_VENDOR_PROPERTIES += \ - ro.sf.lcd_density=$(TARGET_SCREEN_DENSITY) -endif - -ifdef AB_OTA_UPDATER -ADDITIONAL_VENDOR_PROPERTIES += \ - 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) -ADDITIONAL_PRODUCT_PROPERTIES += ro.product.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) -ADDITIONAL_VENDOR_PROPERTIES += ro.vendor.build.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) -endif - -# Set this property for VTS to skip large page size tests on unsupported devices. -ADDITIONAL_PRODUCT_PROPERTIES += \ - ro.product.cpu.pagesize.max=$(TARGET_MAX_PAGE_SIZE_SUPPORTED) - # ----------------------------------------------------------------- ### ### In this section we set up the things that are different @@ -381,66 +160,15 @@ ifneq ($(filter sdk sdk_addon,$(MAKECMDGOALS)),) is_sdk_build := true endif -## user/userdebug ## - -user_variant := $(filter user userdebug,$(TARGET_BUILD_VARIANT)) -enable_target_debugging := true tags_to_install := -ifneq (,$(user_variant)) - # Target is secure in user builds. - ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=1 - ADDITIONAL_SYSTEM_PROPERTIES += security.perf_harden=1 - ifeq ($(user_variant),user) - ADDITIONAL_SYSTEM_PROPERTIES += ro.adb.secure=1 - endif - - ifeq ($(user_variant),userdebug) - # Pick up some extra useful tools - tags_to_install += debug - else - # Disable debugging in plain user builds. - enable_target_debugging := - endif - - # Disallow mock locations by default for user builds - ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=0 - -else # !user_variant - # Turn on checkjni for non-user builds. - ADDITIONAL_SYSTEM_PROPERTIES += ro.kernel.android.checkjni=1 - # Set device insecure for non-user builds. - ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=0 - # Allow mock locations by default for non user builds - ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=1 -endif # !user_variant - -ifeq (true,$(strip $(enable_target_debugging))) - # Target is more debuggable and adbd is on by default - ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=1 - # Enable Dalvik lock contention logging. - ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.lockprof.threshold=500 -else # !enable_target_debugging - # Target is less debuggable and adbd is off by default - ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=0 -endif # !enable_target_debugging - -## eng ## +ifeq ($(TARGET_BUILD_VARIANT),userdebug) +# Pick up some extra useful tools +tags_to_install := debug +endif ifeq ($(TARGET_BUILD_VARIANT),eng) tags_to_install := debug eng -ifneq ($(filter ro.setupwizard.mode=ENABLED, $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))),) - # Don't require the setup wizard on eng builds - ADDITIONAL_SYSTEM_PROPERTIES := $(filter-out ro.setupwizard.mode=%,\ - $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))) \ - ro.setupwizard.mode=OPTIONAL -endif -ifndef is_sdk_build - # To speedup startup of non-preopted builds, don't verify or compile the boot image. - ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.image-dex2oat-filter=extract -endif -# b/323566535 -ADDITIONAL_SYSTEM_PROPERTIES += init.svc_debug.no_fatal.zygote=true endif ## asan ## @@ -476,18 +204,11 @@ endif # TODO: this should be eng I think. Since the sdk is built from the eng # variant. tags_to_install := debug eng -ADDITIONAL_SYSTEM_PROPERTIES += xmpp.auto-presence=true -ADDITIONAL_SYSTEM_PROPERTIES += ro.config.nocheckin=yes else # !sdk endif BUILD_WITHOUT_PV := true -ADDITIONAL_SYSTEM_PROPERTIES += net.bt.name=Android - -# This property is set by flashing debug boot image, so default to false. -ADDITIONAL_SYSTEM_PROPERTIES += ro.force.debuggable=0 - # ------------------------------------------------------------ # Define a function that, given a list of module tags, returns # non-empty if that module should be installed in /system. @@ -528,10 +249,6 @@ include $(BUILD_SYSTEM)/dex_preopt.mk # Strip and readonly a few more variables so they won't be modified. $(readonly-final-product-vars) -ADDITIONAL_SYSTEM_PROPERTIES := $(strip $(ADDITIONAL_SYSTEM_PROPERTIES)) -.KATI_READONLY := ADDITIONAL_SYSTEM_PROPERTIES -ADDITIONAL_PRODUCT_PROPERTIES := $(strip $(ADDITIONAL_PRODUCT_PROPERTIES)) -.KATI_READONLY := ADDITIONAL_PRODUCT_PROPERTIES ifneq ($(PRODUCT_ENFORCE_RRO_TARGETS),) ENFORCE_RRO_SOURCES := @@ -559,7 +276,7 @@ FULL_BUILD := true # Include all of the makefiles in the system # -subdir_makefiles := $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk $(SOONG_ANDROID_MK) +subdir_makefiles := $(SOONG_ANDROID_MK) # Android.mk files are only used on Linux builds, Mac only supports Android.bp ifeq ($(HOST_OS),linux) subdir_makefiles += $(file <$(OUT_DIR)/.module_paths/Android.mk.list) @@ -570,6 +287,8 @@ subdir_makefiles_total := $(words int $(subdir_makefiles) post finish) $(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk))) +include $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk + # For an unbundled image, we can skip blueprint_tools because unbundled image # aims to remove a large number framework projects from the manifest, the # sources or dependencies for these tools may be missing from the tree. @@ -1248,8 +967,7 @@ endef # 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) \ @@ -1935,7 +1653,7 @@ else ifeq ($(TARGET_BUILD_UNBUNDLED),$(TARGET_BUILD_UNBUNDLED_IMAGE)) $(api_xmls): $(hide) echo "Converting API file to XML: $@" $(hide) mkdir -p $(dir $@) - $(hide) $(APICHECK_COMMAND) --input-api-jar $< --api-xml $@ + $(hide) $(APICHECK_COMMAND) jar-to-jdiff $< $@ $(foreach xml,$(sort $(api_xmls)),$(call declare-1p-target,$(xml),)) diff --git a/core/product.mk b/core/product.mk index 01b5ead7db..0a761fb44e 100644 --- a/core/product.mk +++ b/core/product.mk @@ -325,6 +325,13 @@ _product_single_value_vars += \ # 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. @@ -411,8 +418,9 @@ _product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT # /system/etc/security/fsverity/BuildManifest.apk _product_single_value_vars += PRODUCT_FSVERITY_GENERATE_METADATA -# If true, sets the default for MODULE_BUILD_FROM_SOURCE. This overrides -# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE but not an explicitly set value. +# If true, this builds the mainline modules from source. This overrides any +# prebuilts selected via RELEASE_APEX_CONTRIBUTIONS_* build flags for the +# current release config. _product_single_value_vars += PRODUCT_MODULE_BUILD_FROM_SOURCE # If true, installs a full version of com.android.virt APEX. @@ -421,6 +429,12 @@ _product_single_value_vars += PRODUCT_AVF_ENABLED # If true, kernel with modules will be used for Microdroid VMs. _product_single_value_vars += PRODUCT_AVF_KERNEL_MODULES_ENABLED +# If true, the memory controller will be force-enabled in the cgroup v2 hierarchy +_product_single_value_vars += PRODUCT_MEMCG_V2_FORCE_ENABLED + +# If true, the cgroup v2 hierarchy will be split into apps/system subtrees +_product_single_value_vars += PRODUCT_CGROUP_V2_SYS_APP_ISOLATION_ENABLED + # List of .json files to be merged/compiled into vendor/etc/linker.config.pb _product_list_vars += PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS @@ -440,6 +454,9 @@ _product_single_value_vars += PRODUCT_ENABLE_UFFD_GC # specified we default to COW version 2 in update_engine for backwards compatibility _product_single_value_vars += PRODUCT_VIRTUAL_AB_COW_VERSION +# Specifies maximum bytes to be compressed at once during ota. Options: 4096, 8192, 16384, 32768, 65536, 131072, 262144. +_product_single_value_vars += PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR + # If set, determines whether the build system checks vendor seapp contexts violations. _product_single_value_vars += PRODUCT_CHECK_VENDOR_SEAPP_VIOLATIONS @@ -456,12 +473,16 @@ _product_list_vars += PRODUCT_VALIDATION_CHECKS _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 +# If set, determines which version of the GKI is used as guest kernel for Microdroid VMs. +# TODO(b/325991735): link to documentation once it is done. +_product_single_value_vars += PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION + .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 4525423b4f..f21c1c4188 100644 --- a/core/product_config.mk +++ b/core/product_config.mk @@ -301,6 +301,31 @@ ifeq (, $(PRODUCT_INCLUDE_TAGS)) 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 +ifneq ($(EMMA_INSTRUMENT)$(EMMA_INSTRUMENT_STATIC)$(EMMA_INSTRUMENT_FRAMEWORK)$(CLANG_COVERAGE)$(NATIVE_COVERAGE_PATHS),) +# Coverage builds for TARGET_RELEASE=foo should always build from source, +# even if TARGET_RELEASE=foo uses prebuilt mainline modules. +# This is necessary because the checked-in prebuilts were generated with +# instrumentation turned off. + 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 +577,27 @@ ifdef PRODUCT_ENFORCE_RRO_EXEMPTED_TARGETS $(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 - -# Calculate the VSR vendor API level. -VSR_VENDOR_API_LEVEL := $(board_api_level) +# 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 -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 +612,7 @@ endif # 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/product_config.rbc b/core/product_config.rbc index 921f06805c..59e2c95903 100644 --- a/core/product_config.rbc +++ b/core/product_config.rbc @@ -351,6 +351,7 @@ def _percolate_inherited(configs, cfg_name, cfg, children_names): if cfg.get(attr, "") == "": cfg[attr] = value percolated_attrs[attr] = True + child_cfg.pop(attr) for attr in _options.trace_variables: if attr in percolated_attrs: @@ -360,7 +361,7 @@ def __move_items(to_list, from_cfg, attr): value = from_cfg.get(attr, []) if value: to_list.extend(value) - from_cfg[attr] = [] + from_cfg.pop(attr) def _indirect(pcm_name): """Returns configuration item for the inherited module.""" diff --git a/core/proguard/kotlin.flags b/core/proguard/kotlin.flags index 70dbaa7e81..ef6bf0e9e3 100644 --- a/core/proguard/kotlin.flags +++ b/core/proguard/kotlin.flags @@ -10,7 +10,9 @@ # Kotlin DebugMetadata has no value in release builds, these two rules, will # allow AppReduce to strip out DebutMetadata. --checkdiscard interface kotlin.coroutines.jvm.internal.DebugMetadata +# TODO(b/302383328): Restore the below checkdiscard after resolving transitive +# inclusion of kotlin-stdlib from androidx.annotation library deps. +# -checkdiscard interface kotlin.coroutines.jvm.internal.DebugMetadata -assumenosideeffects class kotlin.coroutines.jvm.internal.DebugMetadataKt { *** getDebugMetadataAnnotation(...); } diff --git a/core/release_config.mk b/core/release_config.mk index a7b5b3ff38..97c8dd3571 100644 --- a/core/release_config.mk +++ b/core/release_config.mk @@ -41,8 +41,8 @@ # which has OWNERS control. If it isn't let others define their own. # TODO: Remove wildcard for build/release one when all branch manifests # have updated. -config_map_files := $(wildcard build/trunk_release/release_config_map.mk) \ - $(wildcard build/release/release_config_map.mk) \ +_must_protobuf := +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, \ @@ -54,12 +54,85 @@ config_map_files := $(wildcard build/trunk_release/release_config_map.mk) \ ) \ ) +protobuf_map_files := $(wildcard build/release/release_config_map.textproto) \ + $(wildcard vendor/google_shared/build/release/release_config_map.textproto) \ + $(if $(wildcard vendor/google/release/release_config_map.textproto), \ + vendor/google/release/release_config_map.textproto, \ + $(sort \ + $(wildcard device/*/release/release_config_map.textproto) \ + $(wildcard device/*/*/release/release_config_map.textproto) \ + $(wildcard vendor/*/release/release_config_map.textproto) \ + $(wildcard vendor/*/*/release/release_config_map.textproto) \ + ) \ + ) + # PRODUCT_RELEASE_CONFIG_MAPS is set by Soong using an initial run of product # config to capture only the list of config maps needed by the build. # Keep them in the order provided, but remove duplicates. +# Treat .mk and .textproto as equal for duplicate elimination, but force +# protobuf if any PRODUCT_RELEASE_CONFIG_MAPS specify .textproto. $(foreach map,$(PRODUCT_RELEASE_CONFIG_MAPS), \ - $(if $(filter $(map),$(config_map_files)),,$(eval config_map_files += $(map))) \ + $(if $(filter $(basename $(map)),$(basename $(config_map_files))),, \ + $(eval config_map_files += $(map))) \ + $(if $(filter $(basename $(map)).textproto,$(map)),$(eval _must_protobuf := true)) \ +) + + +# If we are missing the textproto version of any of $(config_map_files), we cannot use protobuf. +_can_protobuf := true +$(foreach map,$(config_map_files), \ + $(if $(wildcard $(basename $(map)).textproto),,$(eval _can_protobuf :=)) \ ) +# If we are missing the mk version of any of $(protobuf_map_files), we must use protobuf. +$(foreach map,$(protobuf_map_files), \ + $(if $(wildcard $(basename $(map)).mk),,$(eval _must_protobuf := true)) \ +) + +ifneq (,$(_must_protobuf)) + ifeq (,$(_can_protobuf)) + # We must use protobuf, but we cannot use protobuf. + $(error release config is a mixture of .scl and .textproto) + endif +endif + +_use_protobuf := +ifneq (,$(_must_protobuf)) + _use_protobuf := true +else + ifneq ($(_can_protobuf),) + # Determine the default + $(foreach map,$(config_map_files), \ + $(if $(wildcard $(dir $(map))/build_config/DEFAULT=proto),$(eval _use_protobuf := true)) \ + $(if $(wildcard $(dir $(map))/build_config/DEFAULT=make),$(eval _use_protobuf := )) \ + ) + # Update for this specific release config only (no inheritance). + $(foreach map,$(config_map_files), \ + $(if $(wildcard $(dir $(map))/build_config/$(TARGET_RELEASE)=proto),$(eval _use_protobuf := true)) \ + $(if $(wildcard $(dir $(map))/build_config/$(TARGET_RELEASE)=make),$(eval _use_protobuf := )) \ + ) + endif +endif + +ifneq (,$(_use_protobuf)) + # The .textproto files are the canonical source of truth. + _args := $(foreach map,$(config_map_files), --map $(map) ) + ifneq (,$(_must_protobuf)) + # Disable the build flag in release-config. + _args += --guard=false + endif + _flags_file:=$(OUT_DIR)/soong/release-config/release_config-$(TARGET_PRODUCT)-$(TARGET_RELEASE).mk + $(KATI_shell_no_rerun $(OUT_DIR)/release-config $(_args) >$(OUT_DIR)/release-config.out 2>&1 && touch -t 200001010000 $(OUT_DIR)/release-config.out $(_flags_file)) + $(if $(filter-out 0,$(.SHELLSTATUS)),$(error release-config failed to run)) + # This will also set _all_release_configs for us. + $(eval include $(OUT_DIR)/soong/release-config/release_config-$(TARGET_PRODUCT)-$(TARGET_RELEASE).mk) + $(KATI_extra_file_deps $(OUT_DIR)/release-config $(config_map_files)) + ifeq (,$(_must_protobuf)$(RELEASE_BUILD_FLAGS_IN_PROTOBUF)) + _use_protobuf := + endif +endif +ifeq (,$(_use_protobuf)) + # The .mk files are the canonical source of truth. + # Declare an alias release-config # @@ -145,6 +218,9 @@ $(foreach r,$(_all_release_configs),\ $(error Alias release config "$(r)" may not specify release config files $(_all_release_configs.$(r).FILES))\ ))) +# Use makefiles +endif + 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 @@ -168,6 +244,7 @@ ifneq (PRODUCT_RELEASE_CONFIG_MAPS,$(DUMP_MANY_VARS)) endif endif +ifeq (,$(_use_protobuf)) # Choose flag files # Don't sort this, use it in the order they gave us. # Do allow duplicate entries, retaining only the first usage. @@ -197,6 +274,9 @@ define _apply-release-config-overrides $(error invalid use of apply-release-config-overrides) endef +# use makefiles +endif + # TODO: Remove this check after enough people have sourced lunch that we don't # need to worry about it trying to do get_build_vars TARGET_RELEASE. Maybe after ~9/2023 ifneq ($(CALLED_FROM_SETUP),true) @@ -208,15 +288,20 @@ TARGET_RELEASE:= endif .KATI_READONLY := TARGET_RELEASE +ifeq (,$(_use_protobuf)) $(foreach config, $(_all_release_configs), \ $(eval _all_release_configs.$(config).DECLARED_IN:= ) \ $(eval _all_release_configs.$(config).FILES:= ) \ ) +applied_releases:= +# use makefiles +endif _all_release_configs:= config_map_files:= -applied_releases:= +protobuf_map_files:= +ifeq (,$(_use_protobuf)) # ----------------------------------------------------------------- # Flag declarations and values # ----------------------------------------------------------------- @@ -253,3 +338,8 @@ filename_to_starlark:= # outside of the source tree. $(call run-starlark,$(OUT_DIR)/release_config_entrypoint.scl,$(OUT_DIR)/release_config_entrypoint.scl,--allow_external_entrypoint) +# use makefiles +endif +_can_protobuf := +_must_protobuf := +_use_protobuf := diff --git a/core/release_config.scl b/core/release_config.scl index 728fc1b399..c5815dfe30 100644 --- a/core/release_config.scl +++ b/core/release_config.scl @@ -179,18 +179,23 @@ def release_config(all_flags, all_values): 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: - if equal_flag_declaration(flag, flags_dict[flag["name"]]): + 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 " + flag["name"] + - " (declared first in " + flags_dict[flag["name"]]["declared_in"] + ")") - flag_names.append(flag["name"]) - flags_dict[flag["name"]] = flag + 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 = {} @@ -206,7 +211,6 @@ def release_config(all_flags, all_values): # 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: @@ -227,19 +231,13 @@ def release_config(all_flags, all_values): 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 943ed30b24..a1c64786ee 100644 --- a/core/soong_cc_rust_prebuilt.mk +++ b/core/soong_cc_rust_prebuilt.mk @@ -38,14 +38,6 @@ ifndef LOCAL_UNINSTALLABLE_MODULE 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 d4c56e5707..acd213c776 100644 --- a/core/soong_config.mk +++ b/core/soong_config.mk @@ -31,7 +31,11 @@ $(call add_json_str, Make_suffix, -$(TARGET_PRODUCT)) $(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)) @@ -48,8 +52,6 @@ $(call add_json_str, Platform_version_known_codenames, $(PLATFORM_VERSION_KNOW $(call add_json_bool, Release_aidl_use_unfrozen, $(RELEASE_AIDL_USE_UNFROZEN)) -$(call add_json_str, Platform_min_supported_target_sdk_version, $(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)) - $(call add_json_bool, Allow_missing_dependencies, $(filter true,$(ALLOW_MISSING_DEPENDENCIES))) $(call add_json_bool, Unbundled_build, $(TARGET_BUILD_UNBUNDLED)) $(call add_json_list, Unbundled_build_apps, $(TARGET_BUILD_APPS)) @@ -58,6 +60,7 @@ $(call add_json_bool, Always_use_prebuilt_sdks, $(TARGET_BUILD_USE_PREB $(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,15 +150,11 @@ $(call add_json_bool, ArtUseReadBarrier, $(call invert_bool,$(fi $(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)) $(call add_json_list, Platform_systemsdk_versions, $(PLATFORM_SYSTEMSDK_VERSIONS)) -$(call add_json_bool, Malloc_not_svelte, $(call invert_bool,$(filter true,$(MALLOC_SVELTE)))) +$(call add_json_bool, Malloc_low_memory, $(findstring true,$(MALLOC_SVELTE) $(MALLOC_LOW_MEMORY))) $(call add_json_bool, Malloc_zero_contents, $(call invert_bool,$(filter false,$(MALLOC_ZERO_CONTENTS)))) $(call add_json_bool, Malloc_pattern_fill_contents, $(MALLOC_PATTERN_FILL_CONTENTS)) $(call add_json_str, Override_rs_driver, $(OVERRIDE_RS_DRIVER)) @@ -323,7 +322,6 @@ $(call add_json_list, AfdoProfiles, $(ALL_AFDO_PROFILES)) $(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 +333,7 @@ $(call add_json_bool, KeepVndk, $(filter true,$(KEEP_VNDK))) $(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))) @@ -406,6 +404,14 @@ $(call add_json_bool, ExportRuntimeApis, $(filter true,$(PRODUCT_EXPORT_RUNTIME_ $(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 652ca9757e..47d8a41a38 100644 --- a/core/sysprop.mk +++ b/core/sysprop.mk @@ -23,7 +23,6 @@ ifeq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true) 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 @@ endif 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 @@ KNOWN_OEM_THUMBPRINT_PROPERTIES:= # 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 @@ system_prop_file := $(wildcard $(TARGET_DEVICE_DIR)/system.prop) 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/sysprop_config.mk b/core/sysprop_config.mk new file mode 100644 index 0000000000..a019a7d2c3 --- /dev/null +++ b/core/sysprop_config.mk @@ -0,0 +1,278 @@ +# ADDITIONAL_<partition>_PROPERTIES are properties that are determined by the +# build system itself. Don't let it be defined from outside of the core build +# system like Android.mk or <product>.mk files. +_additional_prop_var_names := \ + ADDITIONAL_SYSTEM_PROPERTIES \ + ADDITIONAL_VENDOR_PROPERTIES \ + ADDITIONAL_ODM_PROPERTIES \ + ADDITIONAL_PRODUCT_PROPERTIES + +$(foreach name, $(_additional_prop_var_names),\ + $(if $($(name)),\ + $(error $(name) must not set before here. $($(name)))\ + ,)\ + $(eval $(name) :=)\ +) +_additional_prop_var_names := + +# +# ----------------------------------------------------------------- +# Add the product-defined properties to the build properties. +ifneq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true) + ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) +else + ifndef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE + ADDITIONAL_SYSTEM_PROPERTIES += $(PRODUCT_PROPERTY_OVERRIDES) + endif +endif + +ADDITIONAL_SYSTEM_PROPERTIES += ro.treble.enabled=${PRODUCT_FULL_TREBLE} + +# Set ro.llndk.api_level to show the maximum vendor API level that the LLNDK in +# the system partition supports. +ifdef RELEASE_BOARD_API_LEVEL +ADDITIONAL_SYSTEM_PROPERTIES += ro.llndk.api_level=$(RELEASE_BOARD_API_LEVEL) +endif + +# Sets ro.actionable_compatible_property.enabled to know on runtime whether the +# allowed list of actionable compatible properties is enabled or not. +ADDITIONAL_SYSTEM_PROPERTIES += ro.actionable_compatible_property.enabled=true + +# Add the system server compiler filter if they are specified for the product. +ifneq (,$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER)) +ADDITIONAL_PRODUCT_PROPERTIES += dalvik.vm.systemservercompilerfilter=$(PRODUCT_SYSTEM_SERVER_COMPILER_FILTER) +endif + +# Add the 16K developer option if it is defined for the product. +ifeq ($(PRODUCT_16K_DEVELOPER_OPTION),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=true +else +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.16k_page.enabled=false +endif + +# Enable core platform API violation warnings on userdebug and eng builds. +ifneq ($(TARGET_BUILD_VARIANT),user) +ADDITIONAL_SYSTEM_PROPERTIES += persist.debug.dalvik.vm.core_platform_api_policy=just-warn +endif + +# Define ro.sanitize.<name> properties for all global sanitizers. +ADDITIONAL_SYSTEM_PROPERTIES += $(foreach s,$(SANITIZE_TARGET),ro.sanitize.$(s)=true) + +# Sets the default value of ro.postinstall.fstab.prefix to /system. +# Device board config should override the value to /product when needed by: +# +# PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product +# +# It then uses ${ro.postinstall.fstab.prefix}/etc/fstab.postinstall to +# mount system_other partition. +ADDITIONAL_SYSTEM_PROPERTIES += ro.postinstall.fstab.prefix=/system + +# 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) +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_arch=$(TARGET_2ND_ARCH) +ADDITIONAL_VENDOR_PROPERTIES += ro.bionic.2nd_cpu_variant=$(TARGET_2ND_CPU_VARIANT_RUNTIME) + +ADDITIONAL_VENDOR_PROPERTIES += persist.sys.dalvik.vm.lib.2=libart.so +ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).variant=$(DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) +ifneq ($(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_ARCH).features=$(DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) +endif + +ifdef TARGET_2ND_ARCH + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).variant=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_CPU_VARIANT_RUNTIME) + ifneq ($($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES),) + ADDITIONAL_VENDOR_PROPERTIES += dalvik.vm.isa.$(TARGET_2ND_ARCH).features=$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) + endif +endif + +# Although these variables are prefixed with TARGET_RECOVERY_, they are also needed under charger +# mode (via libminui). +ifdef TARGET_RECOVERY_DEFAULT_ROTATION +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.default_rotation=$(TARGET_RECOVERY_DEFAULT_ROTATION) +endif +ifdef TARGET_RECOVERY_OVERSCAN_PERCENT +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.overscan_percent=$(TARGET_RECOVERY_OVERSCAN_PERCENT) +endif +ifdef TARGET_RECOVERY_PIXEL_FORMAT +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.minui.pixel_format=$(TARGET_RECOVERY_PIXEL_FORMAT) +endif + +ifdef PRODUCT_USE_DYNAMIC_PARTITIONS +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.boot.dynamic_partitions=$(PRODUCT_USE_DYNAMIC_PARTITIONS) +endif + +ifdef PRODUCT_RETROFIT_DYNAMIC_PARTITIONS +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.boot.dynamic_partitions_retrofit=$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS) +endif + +ifdef PRODUCT_SHIPPING_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + 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 += \ + ro.product.debugfs_restrictions.enabled=$(PRODUCT_SET_DEBUGFS_RESTRICTIONS) + endif +endif + +# Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level. +# This must not be defined for the non-GRF devices. +# The values of the GRF properties will be verified by post_process_props.py +ifdef BOARD_SHIPPING_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.first_api_level=$(BOARD_SHIPPING_API_LEVEL) +endif + +# Build system set BOARD_API_LEVEL to show the api level of the vendor API surface. +# This must not be altered outside of build system. +ifdef BOARD_API_LEVEL +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.api_level=$(BOARD_API_LEVEL) +endif +# RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen. +ifdef RELEASE_BOARD_API_LEVEL_FROZEN +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.board.api_frozen=$(RELEASE_BOARD_API_LEVEL_FROZEN) +endif + +# Set build prop. This prop is read by ota_from_target_files when generating OTA, +# to decide if VABC should be disabled. +ifeq ($(BOARD_DONT_USE_VABC_OTA),true) +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.dont_use_vabc=true +endif + +# Set the flag in vendor. So VTS would know if the new fingerprint format is in use when +# the system images are replaced by GSI. +ifeq ($(BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT),true) +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.fingerprint_has_digest=1 +endif + +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.vendor.build.security_patch=$(VENDOR_SECURITY_PATCH) \ + ro.product.board=$(TARGET_BOOTLOADER_BOARD_NAME) \ + ro.board.platform=$(TARGET_BOARD_PLATFORM) \ + ro.hwui.use_vulkan=$(TARGET_USES_VULKAN) + +ifdef TARGET_SCREEN_DENSITY +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.sf.lcd_density=$(TARGET_SCREEN_DENSITY) +endif + +ifdef AB_OTA_UPDATER +ADDITIONAL_VENDOR_PROPERTIES += \ + ro.build.ab_update=$(AB_OTA_UPDATER) +endif + +ADDITIONAL_PRODUCT_PROPERTIES += ro.build.characteristics=$(TARGET_AAPT_CHARACTERISTICS) + +ifeq ($(AB_OTA_UPDATER),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) +ADDITIONAL_VENDOR_PROPERTIES += ro.vendor.build.ab_ota_partitions=$(subst $(space),$(comma),$(sort $(AB_OTA_PARTITIONS))) +endif + +# Set this property for VTS to skip large page size tests on unsupported devices. +ADDITIONAL_PRODUCT_PROPERTIES += \ + ro.product.cpu.pagesize.max=$(TARGET_MAX_PAGE_SIZE_SUPPORTED) + +ifeq ($(PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO),true) +ADDITIONAL_PRODUCT_PROPERTIES += ro.product.build.no_bionic_page_size_macro=true +endif + +user_variant := $(filter user userdebug,$(TARGET_BUILD_VARIANT)) +enable_target_debugging := true +ifneq (,$(user_variant)) + # Target is secure in user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=1 + ADDITIONAL_SYSTEM_PROPERTIES += security.perf_harden=1 + + ifeq ($(user_variant),user) + ADDITIONAL_SYSTEM_PROPERTIES += ro.adb.secure=1 + endif + + ifneq ($(user_variant),userdebug) + # Disable debugging in plain user builds. + enable_target_debugging := + endif + + # Disallow mock locations by default for user builds + ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=0 + +else # !user_variant + # Turn on checkjni for non-user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.kernel.android.checkjni=1 + # Set device insecure for non-user builds. + ADDITIONAL_SYSTEM_PROPERTIES += ro.secure=0 + # Allow mock locations by default for non user builds + ADDITIONAL_SYSTEM_PROPERTIES += ro.allow.mock.location=1 +endif # !user_variant + +ifeq (true,$(strip $(enable_target_debugging))) + # Target is more debuggable and adbd is on by default + ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=1 + # Enable Dalvik lock contention logging. + ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.lockprof.threshold=500 +else # !enable_target_debugging + # Target is less debuggable and adbd is off by default + ADDITIONAL_SYSTEM_PROPERTIES += ro.debuggable=0 +endif # !enable_target_debugging + +ifneq ($(filter sdk sdk_addon,$(MAKECMDGOALS)),) +_is_sdk_build := true +endif + +ifeq ($(TARGET_BUILD_VARIANT),eng) +ifneq ($(filter ro.setupwizard.mode=ENABLED, $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))),) + # Don't require the setup wizard on eng builds + ADDITIONAL_SYSTEM_PROPERTIES := $(filter-out ro.setupwizard.mode=%,\ + $(call collapse-pairs, $(ADDITIONAL_SYSTEM_PROPERTIES))) \ + ro.setupwizard.mode=OPTIONAL +endif +ifndef _is_sdk_build + # To speedup startup of non-preopted builds, don't verify or compile the boot image. + ADDITIONAL_SYSTEM_PROPERTIES += dalvik.vm.image-dex2oat-filter=extract +endif +# b/323566535 +ADDITIONAL_SYSTEM_PROPERTIES += init.svc_debug.no_fatal.zygote=true +endif + +ifdef _is_sdk_build +ADDITIONAL_SYSTEM_PROPERTIES += xmpp.auto-presence=true +ADDITIONAL_SYSTEM_PROPERTIES += ro.config.nocheckin=yes +endif + +_is_sdk_build := + +ADDITIONAL_SYSTEM_PROPERTIES += net.bt.name=Android + +# This property is set by flashing debug boot image, so default to false. +ADDITIONAL_SYSTEM_PROPERTIES += ro.force.debuggable=0 + +config_enable_uffd_gc := \ + $(firstword $(OVERRIDE_ENABLE_UFFD_GC) $(PRODUCT_ENABLE_UFFD_GC) default) + +# If the value is "default", it will be mangled by post_process_props.py. +ADDITIONAL_PRODUCT_PROPERTIES += ro.dalvik.vm.enable_uffd_gc=$(config_enable_uffd_gc) + +ADDITIONAL_SYSTEM_PROPERTIES := $(strip $(ADDITIONAL_SYSTEM_PROPERTIES)) +ADDITIONAL_PRODUCT_PROPERTIES := $(strip $(ADDITIONAL_PRODUCT_PROPERTIES)) +ADDITIONAL_VENDOR_PROPERTIES := $(strip $(ADDITIONAL_VENDOR_PROPERTIES)) + +.KATI_READONLY += \ + ADDITIONAL_SYSTEM_PROPERTIES \ + ADDITIONAL_PRODUCT_PROPERTIES \ + ADDITIONAL_VENDOR_PROPERTIES diff --git a/core/tasks/automotive-sdv-tests.mk b/core/tasks/automotive-sdv-tests.mk new file mode 100644 index 0000000000..12706ce33d --- /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 0000000000..860470980d --- /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/meta-lic.mk b/core/tasks/meta-lic.mk index dea4892211..2126bd02ba 100644 --- a/core/tasks/meta-lic.mk +++ b/core/tasks/meta-lic.mk @@ -14,6 +14,116 @@ # Declare license metadata for non-module files released with products. +# Moved here from device/generic/car/Android.mk +$(eval $(call declare-1p-copy-files,device/generic/car,)) + +# Moved here from device/generic/trusty/Android.mk +$(eval $(call declare-1p-copy-files,device/generic/trusty,)) + +# Moved here from device/generic/uml/Android.mk +$(eval $(call declare-1p-copy-files,device/generic/uml,)) + +# Moved here from device/google_car/common/Android.mk +$(eval $(call declare-1p-copy-files,device/google_car/common,)) + +# Moved here from device/google/atv/Android.mk +$(eval $(call declare-1p-copy-files,device/google/atv,atv-component-overrides.xml)) +$(eval $(call declare-1p-copy-files,device/google/atv,tv_core_hardware.xml)) + +# Moved here from device/google/barbet/Android.mk +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,media_profiles_V1_0.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,media_codecs_performance.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/barbet,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) + +$(eval $(call declare-1p-copy-files,device/google/barbet,audio_policy_configuration.xml)) + +# Moved here from device/google/coral/Android.mk +$(eval $(call declare-copy-files-license-metadata,device/google/coral,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,media_profiles_V1_0.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,media_codecs_performance.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/coral,display_19261132550654593.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) + +$(eval $(call declare-1p-copy-files,device/google/coral,audio_policy_configuration.xml)) +$(eval $(call declare-1p-copy-files,device/google/coral,display_19260504575090817.xml)) + +# Moved here from device/google/gs101/Android.mk +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,media_profiles_V1_0.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,media_codecs_performance.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,p2p_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/gs101,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) + +$(eval $(call declare-1p-copy-files,device/google/gs101,audio_policy_configuration.xml)) + +# Move here from device/google/raviole/Android.mk +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,libnfc-nci-raven.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,media_profiles_V1_0.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,media_codecs_performance.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/raviole,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) + +$(eval $(call declare-1p-copy-files,device/google/raviole,audio_policy_configuration.xml)) + +# Moved here from device/google/redfin/Android.mk +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,default-permissions.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,libnfc-nci.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,fstab.postinstall,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,ueventd.rc,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,hals.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,media_profiles_V1_0.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,media_codecs_performance.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,device_state_configuration.xml,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,task_profiles.json,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,p2p_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,wpa_supplicant.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) +$(eval $(call declare-copy-files-license-metadata,device/google/redfin,wpa_supplicant_overlay.conf,SPDX-license-identifier-Apache-2.0,notice,build/soong/licenses/LICENSE,)) + +$(eval $(call declare-1p-copy-files,device/google/redfin,audio_policy_configuration.xml)) + +# Moved here from device/sample/Android.mk +$(eval $(call declare-1p-copy-files,device/sample,)) + +# Moved here from device/google/trout/Android.mk +$(eval $(call declare-1p-copy-files,device/google/trout,)) + # 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)) diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk index 8546828c2c..aa695eb31c 100644 --- a/core/tasks/module-info.mk +++ b/core/tasks/module-info.mk @@ -39,7 +39,7 @@ $(MODULE_INFO_JSON): $(SOONG_MODULE_INFO) $(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/sdk-addon.mk b/core/tasks/sdk-addon.mk index 7acac72f1d..2fd4ce9ec7 100644 --- a/core/tasks/sdk-addon.mk +++ b/core/tasks/sdk-addon.mk @@ -126,7 +126,7 @@ $(full_target_img): PRIVATE_STAGING_DIR := $(call append-path,$(staging),$(addon $(full_target_img): $(full_target) $(addon_img_source_prop) | $(SOONG_ZIP) @echo Packaging SDK Addon System-Image: $@ $(hide) mkdir -p $(dir $@) - cp -R $(PRODUCT_OUT)/data $(PRIVATE_STAGING_DIR)/data + cp -R $(PRODUCT_OUT)/data $(PRIVATE_STAGING_DIR) $(hide) $(SOONG_ZIP) -o $@ -C $(dir $(PRIVATE_STAGING_DIR)) -D $(PRIVATE_STAGING_DIR) diff --git a/core/tasks/vndk.mk b/core/tasks/vndk.mk deleted file mode 100644 index ebe9bd4736..0000000000 --- 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 6cda0fc6be..eb568becc4 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 @@ ifndef DEFAULT_APP_TARGET_SDK 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, @@ -240,10 +221,8 @@ ifndef HAS_BUILD_NUMBER endif .KATI_READONLY := HAS_BUILD_NUMBER -ifndef PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION - # Used to set minimum supported target sdk version. Apps targeting sdk - # version lower than the set value will result in a warning being shown - # when any activity from the app is started. - PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 28 +ifdef PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION + $(error Do not set PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION directly. Use RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION. value: $(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)) endif +PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := $(RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION) .KATI_READONLY := PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION diff --git a/envsetup.sh b/envsetup.sh index fbe522d866..50fec5146a 100644 --- a/envsetup.sh +++ b/envsetup.sh @@ -1100,7 +1100,50 @@ function adb() { echo "Command adb not found; try lunch (and building) first?" return 1 fi - $ADB "${@}" + run_tool_with_logging "ADB" $ADB "${@}" +} + +function run_tool_with_logging() { + # Run commands in a subshell for us to handle forced terminations with a trap + # handler. + ( + local tool_tag="$1" + shift + local tool_binary="$1" + shift + + # If the logger is not configured, run the original command and return. + if [[ -z "${ANDROID_TOOL_LOGGER}" ]]; then + "${tool_binary}" "${@}" + return $? + fi + + # Otherwise, run the original command and call the logger when done. + local start_time + start_time=$(date +%s.%N) + local logger=${ANDROID_TOOL_LOGGER} + + # Install a trap to call the logger even when the process terminates abnormally. + # The logger is run in the background and its output suppressed to avoid + # interference with the user flow. + trap ' + exit_code=$?; + # Remove the trap to prevent duplicate log. + trap - EXIT; + "${logger}" \ + --tool_tag "${tool_tag}" \ + --start_timestamp "${start_time}" \ + --end_timestamp "$(date +%s.%N)" \ + --tool_args "$*" \ + --exit_code "${exit_code}" \ + ${ANDROID_TOOL_LOGGER_EXTRA_ARGS} \ + > /dev/null 2>&1 & + exit ${exit_code} + ' SIGINT SIGTERM SIGQUIT EXIT + + # Run the original command. + "${tool_binary}" "${@}" + ) } # simplified version of ps; output in the form diff --git a/target/board/BoardConfigMainlineCommon.mk b/target/board/BoardConfigMainlineCommon.mk index 2b17349a2f..b5e3dc2b1e 100644 --- a/target/board/BoardConfigMainlineCommon.mk +++ b/target/board/BoardConfigMainlineCommon.mk @@ -24,11 +24,6 @@ BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE := ext4 # 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/ndk/BoardConfig.mk b/target/board/ndk/BoardConfig.mk index b485f8b79e..d5399b226b 100644 --- a/target/board/ndk/BoardConfig.mk +++ b/target/board/ndk/BoardConfig.mk @@ -15,6 +15,6 @@ TARGET_ARCH_SUITE := ndk -MALLOC_SVELTE := true +MALLOC_LOW_MEMORY := true USE_SAFESTACK := false diff --git a/target/product/aosp_arm64.mk b/target/product/aosp_arm64.mk index d3514a50de..364fed4efc 100644 --- a/target/product/aosp_arm64.mk +++ b/target/product/aosp_arm64.mk @@ -55,7 +55,8 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk) # All components inherited here go to vendor or vendor_boot image # $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_arm64/device.mk) -$(call inherit-product, $(SRC_TARGET_DIR)/product/non_ab_device.mk) +AB_OTA_UPDATER := true +AB_OTA_PARTITIONS ?= system # # Special settings for GSI releasing @@ -72,3 +73,5 @@ PRODUCT_NAME := aosp_arm64 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_product.mk b/target/product/aosp_product.mk index f72f2dfec4..3a5b622f99 100644 --- a/target/product/aosp_product.mk +++ b/target/product/aosp_product.mk @@ -34,7 +34,6 @@ PRODUCT_PACKAGES += \ PhotoTable \ preinstalled-packages-platform-aosp-product.xml \ ThemePicker \ - WallpaperPicker \ # Telephony: # Provide a APN configuration to GSI product diff --git a/target/product/aosp_x86_64.mk b/target/product/aosp_x86_64.mk index 3040dd3473..595940d9d1 100644 --- a/target/product/aosp_x86_64.mk +++ b/target/product/aosp_x86_64.mk @@ -57,7 +57,8 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk) # All components inherited here go to vendor image # $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_64/device.mk) -$(call inherit-product, $(SRC_TARGET_DIR)/product/non_ab_device.mk) +AB_OTA_UPDATER := true +AB_OTA_PARTITIONS ?= system # # Special settings for GSI releasing @@ -74,3 +75,5 @@ PRODUCT_NAME := aosp_x86_64 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 884af4f5c8..22284b1c18 100644 --- a/target/product/base_system.mk +++ b/target/product/base_system.mk @@ -423,8 +423,7 @@ PRODUCT_HOST_PACKAGES += \ PRODUCT_PACKAGES += init.usb.rc init.usb.configfs.rc -PRODUCT_COPY_FILES += \ - system/core/rootdir/etc/hosts:system/etc/hosts +PRODUCT_PACKAGES += etc_hosts PRODUCT_PACKAGES += init.zygote32.rc PRODUCT_VENDOR_PROPERTIES += ro.zygote?=zygote32 @@ -484,10 +483,13 @@ PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE := \ PRODUCT_COPY_FILES += $(call add-to-product-copy-files-if-exists,\ frameworks/base/config/preloaded-classes:system/etc/preloaded-classes) -# Note: it is acceptable to not have a dirty-image-objects file. In that case, the special bin -# for known dirty objects in the image will be empty. -PRODUCT_COPY_FILES += $(call add-to-product-copy-files-if-exists,\ - frameworks/base/config/dirty-image-objects:system/etc/dirty-image-objects) +# Enable dirty image object binning to reduce dirty pages in the image. +PRODUCT_PACKAGES += dirty-image-objects + +# Enable go/perfetto-persistent-tracing for eng builds +ifneq (,$(filter eng, $(TARGET_BUILD_VARIANT))) + PRODUCT_PRODUCT_PROPERTIES += persist.debug.perfetto.persistent_sysui_tracing_for_bugreport=1 +endif $(call inherit-product, $(SRC_TARGET_DIR)/product/runtime_libart.mk) diff --git a/target/product/generic_system.mk b/target/product/generic_system.mk index fa31e04e46..9748c7cd46 100644 --- a/target/product/generic_system.mk +++ b/target/product/generic_system.mk @@ -36,6 +36,11 @@ PRODUCT_PACKAGES += \ Stk \ Tag \ +ifeq ($(RELEASE_AVATAR_PICKER_APP),true) + PRODUCT_PACKAGES += \ + AvatarPicker +endif + # OTA support PRODUCT_PACKAGES += \ recovery-refresh \ @@ -68,7 +73,6 @@ PRODUCT_PACKAGES += \ android.hardware.radio.config@1.0 \ android.hardware.radio.deprecated@1.0 \ android.hardware.secure_element@1.0 \ - android.hardware.wifi \ libaudio-resampler \ libaudiohal \ libdrm \ diff --git a/target/product/go_defaults.mk b/target/product/go_defaults.mk index b7174868ee..a10cfa858f 100644 --- a/target/product/go_defaults.mk +++ b/target/product/go_defaults.mk @@ -17,6 +17,8 @@ # Inherit common Android Go defaults. $(call inherit-product, build/make/target/product/go_defaults_common.mk) +PRODUCT_RELEASE_CONFIG_MAPS += $(wildcard vendor/google/release/go_devices/release_config_map.mk) + # Add the system properties. TARGET_SYSTEM_PROP += \ build/make/target/board/go_defaults.prop diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk index c8ace36fbd..36897fef8e 100644 --- a/target/product/gsi/Android.mk +++ b/target/product/gsi/Android.mk @@ -1,112 +1,21 @@ 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 - -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) -endif -endif +droidcore: check-abi-dump-list -_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-abi-dump-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-abi-dump-list-timestamp -$(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)" >> $@ +# The ABI tool does not support sanitizer and coverage builds. +ifeq (,$(filter true,$(SKIP_ABI_CHECKS) $(CLANG_COVERAGE))) +ifeq (,$(SANITIZE_TARGET)) +check-abi-dump-list: $(check-abi-dump-list-timestamp) endif - $(hide) echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@ endif - @chmod a+x $@ ##################################################################### # ABI reference dumps. @@ -128,6 +37,9 @@ $(patsubst $(tag_patterns),%,$(filter $(tag_patterns),$(2))) endef # Subsets of LSDUMP_PATHS. +.PHONY: findlsdumps_APEX +findlsdumps_APEX: $(LSDUMP_PATHS_FILE) $(call filter-abi-dump-paths,APEX,$(LSDUMP_PATHS)) + .PHONY: findlsdumps_LLNDK findlsdumps_LLNDK: $(LSDUMP_PATHS_FILE) $(call filter-abi-dump-paths,LLNDK,$(LSDUMP_PATHS)) @@ -142,7 +54,7 @@ findlsdumps: $(LSDUMP_PATHS_FILE) $(foreach p,$(LSDUMP_PATHS),$(call word-colon, ##################################################################### # Check that all ABI reference dumps have corresponding -# NDK/VNDK/PLATFORM libraries. +# APEX/LLNDK/PLATFORM libraries. # $(1): The directory containing ABI dumps. # Return a list of ABI dump paths ending with .so.lsdump. @@ -154,57 +66,47 @@ endef # $(1): A list of tags. # $(2): A list of tag:path. -# Return the file names of the ABI dumps that match the tags. +# Return the file names of the ABI dumps that match the tags, and replace the +# file name extensions with .so.lsdump. define filter-abi-dump-names -$(notdir $(call filter-abi-dump-paths,$(1),$(2))) +$(patsubst %.so.llndk.lsdump,%.so.lsdump, \ + $(patsubst %.so.apex.lsdump,%.so.lsdump, \ + $(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 # libraries that can be built from source in the current build, and prebuilts of # Mainline modules may be in use, we also allow the libs in STUB_LIBRARIES for -# NDK and platform ABIs. +# platform ABIs. +# In addition, libRS is allowed because it's disabled for RISC-V. -$(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)) libRS.so.lsdump, \ $(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)) \ - $(addsuffix .lsdump,$(PRIVATE_STUB_LIBRARIES)), \ + $(call filter-abi-dump-names,APEX LLNDK PLATFORM,$(PRIVATE_LSDUMP_PATHS)) \ + $(addsuffix .lsdump,$(PRIVATE_STUB_LIBRARIES)) libRS.so.lsdump, \ $(notdir $(PLATFORM_ABI_DUMPS)))))) $(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,27 +114,6 @@ $(check-vndk-abi-dump-list-timestamp): # 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) - -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_release.mk b/target/product/gsi_release.mk index 2e373663a4..884b419868 100644 --- a/target/product/gsi_release.mk +++ b/target/product/gsi_release.mk @@ -44,9 +44,6 @@ PRODUCT_USE_DYNAMIC_PARTITIONS := true # Enable dynamic partition size PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true -# Disable the build-time debugfs restrictions on GSI builds -PRODUCT_SET_DEBUGFS_RESTRICTIONS := false - # GSI specific tasks on boot PRODUCT_PACKAGES += \ gsi_skip_mount.cfg \ diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk index bf9aa418f9..3c401f399b 100644 --- a/target/product/handheld_system.mk +++ b/target/product/handheld_system.mk @@ -82,8 +82,9 @@ PRODUCT_SYSTEM_SERVER_APPS += \ KeyChain \ Telecom \ +PRODUCT_PACKAGES += framework-audio_effects.xml + PRODUCT_COPY_FILES += \ - frameworks/av/media/libeffects/data/audio_effects.xml:system/etc/audio_effects.xml \ frameworks/native/data/etc/android.software.window_magnification.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/android.software.window_magnification.xml \ PRODUCT_VENDOR_PROPERTIES += \ diff --git a/target/product/handheld_system_ext.mk b/target/product/handheld_system_ext.mk index 1218f7aea8..187b6275bb 100644 --- a/target/product/handheld_system_ext.mk +++ b/target/product/handheld_system_ext.mk @@ -29,8 +29,3 @@ PRODUCT_PACKAGES += \ 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/media_system.mk b/target/product/media_system.mk index 38ba21989d..503c9b3531 100644 --- a/target/product/media_system.mk +++ b/target/product/media_system.mk @@ -59,10 +59,6 @@ PRODUCT_COPY_FILES += \ PRODUCT_COPY_FILES += $(call add-to-product-copy-files-if-exists,\ frameworks/base/config/compiled-classes-phone:system/etc/compiled-classes) -# Enable dirty image object binning to reduce dirty pages in the image. -PRODUCT_COPY_FILES += $(call add-to-product-copy-files-if-exists,\ - frameworks/base/dirty-image-objects-phone:system/etc/dirty-image-objects) - # On userdebug builds, collect more tombstones by default. ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) PRODUCT_VENDOR_PROPERTIES += \ diff --git a/target/product/module_arm64.mk b/target/product/module_arm64.mk index 2e8c8a7f8b..d6487ca56b 100644 --- a/target/product/module_arm64.mk +++ b/target/product/module_arm64.mk @@ -19,3 +19,6 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk) PRODUCT_NAME := module_arm64 PRODUCT_DEVICE := module_arm64 + +PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true +PRODUCT_MAX_PAGE_SIZE_SUPPORTED := 16384 diff --git a/target/product/module_arm64only.mk b/target/product/module_arm64only.mk index c0769bfa15..137701a441 100644 --- a/target/product/module_arm64only.mk +++ b/target/product/module_arm64only.mk @@ -19,3 +19,6 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk) PRODUCT_NAME := module_arm64only PRODUCT_DEVICE := module_arm64only + +PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true +PRODUCT_MAX_PAGE_SIZE_SUPPORTED := 16384 diff --git a/target/product/module_common.mk b/target/product/module_common.mk index bf146a0d2f..da4ea23ad9 100644 --- a/target/product/module_common.mk +++ b/target/product/module_common.mk @@ -24,8 +24,9 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/memtag-common.mk) # uses -DENFORCE_VINTF_MANIFEST. See b/185759877 PRODUCT_SHIPPING_API_LEVEL := 29 -# Builds using a module product should build modules from source, even if -# BRANCH_DEFAULT_MODULE_BUILD_FROM_SOURCE says otherwise. +# If true, this builds the mainline modules from source. This overrides any +# prebuilts selected via RELEASE_APEX_CONTRIBUTIONS_* build flags for the +# current release config. PRODUCT_MODULE_BUILD_FROM_SOURCE := true # Build sdk from source if the branch is not using slim manifests. diff --git a/target/product/module_x86_64.mk b/target/product/module_x86_64.mk index 20f443a1e8..e182bf6578 100644 --- a/target/product/module_x86_64.mk +++ b/target/product/module_x86_64.mk @@ -19,3 +19,6 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk) PRODUCT_NAME := module_x86_64 PRODUCT_DEVICE := module_x86_64 + +PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true +PRODUCT_MAX_PAGE_SIZE_SUPPORTED := 16384 diff --git a/target/product/module_x86_64only.mk b/target/product/module_x86_64only.mk index b0d72bfe2b..fa4a04d7e8 100644 --- a/target/product/module_x86_64only.mk +++ b/target/product/module_x86_64only.mk @@ -19,3 +19,6 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk) PRODUCT_NAME := module_x86_64only PRODUCT_DEVICE := module_x86_64only + +PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true +PRODUCT_MAX_PAGE_SIZE_SUPPORTED := 16384 diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk index a9d478d452..d9c3c9a0a8 100644 --- a/target/product/runtime_libart.mk +++ b/target/product/runtime_libart.mk @@ -175,15 +175,5 @@ PRODUCT_SYSTEM_PROPERTIES += \ dalvik.vm.usap_pool_size_min?=1 \ dalvik.vm.usap_pool_refill_delay_ms?=3000 -# Allow dexopt files that are side-effects of already allowlisted files. -# This is only necessary when ART is prebuilt. -ifeq (false,$(ART_MODULE_BUILD_FROM_SOURCE)) - PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \ - system/framework/%.art \ - system/framework/%.oat \ - system/framework/%.odex \ - system/framework/%.vdex -endif - PRODUCT_SYSTEM_PROPERTIES += \ dalvik.vm.useartservice=true diff --git a/target/product/sdk.mk b/target/product/sdk.mk index 04649a2d48..1a073636ef 100644 --- a/target/product/sdk.mk +++ b/target/product/sdk.mk @@ -34,6 +34,9 @@ PRODUCT_DEVICE := mainline_x86 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/virtual_ab_ota/compression.mk b/target/product/virtual_ab_ota/compression.mk index dc1ee3e028..c964860740 100644 --- a/target/product/virtual_ab_ota/compression.mk +++ b/target/product/virtual_ab_ota/compression.mk @@ -28,5 +28,4 @@ PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.batch_writes=true PRODUCT_VIRTUAL_AB_COMPRESSION := true PRODUCT_PACKAGES += \ snapuserd.vendor_ramdisk \ - snapuserd \ - snapuserd.recovery + snapuserd diff --git a/target/product/virtual_ab_ota/compression_retrofit.mk b/target/product/virtual_ab_ota/compression_retrofit.mk index 6c29cba6e1..118d3f2b7b 100644 --- a/target/product/virtual_ab_ota/compression_retrofit.mk +++ b/target/product/virtual_ab_ota/compression_retrofit.mk @@ -24,5 +24,4 @@ PRODUCT_VIRTUAL_AB_COMPRESSION := true # as well. PRODUCT_PACKAGES += \ snapuserd.ramdisk \ - snapuserd \ - snapuserd.recovery + snapuserd diff --git a/target/product/virtual_ab_ota/vabc_features.mk b/target/product/virtual_ab_ota/vabc_features.mk index 874eb9cd83..3f484e4f3e 100644 --- a/target/product/virtual_ab_ota/vabc_features.mk +++ b/target/product/virtual_ab_ota/vabc_features.mk @@ -38,6 +38,9 @@ PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.batch_writes=true # Enabling this property, will improve OTA install time # but will use an additional CPU core # PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.threads=true +ifndef PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR + PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 65536 +endif PRODUCT_VIRTUAL_AB_COMPRESSION := true PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD ?= none diff --git a/teams/Android.bp b/teams/Android.bp index 8f83e71df1..b3a5752749 100644 --- a/teams/Android.bp +++ b/teams/Android.bp @@ -732,7 +732,7 @@ team { } team { - name: "trendy_team_deprecated_systemui_gfx", + name: "trendy_team_ailabs", // go/trendy/manage/engineers/6673470538285056 trendy_team_id: "6673470538285056", @@ -4337,37 +4337,57 @@ team { } team { - name: "trendy_team_media_framework_drm", + name: "trendy_team_media_framework_drm", - // go/trendy/manage/engineers/5311752690335744 - trendy_team_id: "5311752690335744", + // go/trendy/manage/engineers/5311752690335744 + trendy_team_id: "5311752690335744", } team { - name: "trendy_team_media_framework_audio", + name: "trendy_team_media_framework_audio", - // go/trendy/manage/engineers/5823575353065472 - trendy_team_id: "5823575353065472", + // go/trendy/manage/engineers/5823575353065472 + trendy_team_id: "5823575353065472", } team { - name: "trendy_team_ar_sensors_context_hub", + name: "trendy_team_pixel_pearl", - // go/trendy/manage/engineers/4776371090259968 - trendy_team_id: "4776371090259968", + // go/trendy/manage/engineers/6326219602231296 + trendy_team_id: "6326219602231296", } +team { + name: "trendy_team_ar_sensors_context_hub", + + // go/trendy/manage/engineers/4776371090259968 + trendy_team_id: "4776371090259968", +} team { - name: "trendy_team_media_codec_framework", + name: "trendy_team_media_codec_framework", - // go/trendy/manage/engineers/4943966050844672 - trendy_team_id: "4943966050844672", + // 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", +} + +team { + name: "trendy_team_adte", + + // go/trendy/manage/engineers/5551098528825344 + trendy_team_id: "5551098528825344", } team { - name: "trendy_team_android_platform_performance_testing", + name: "trendy_team_incremental", - // go/trendy/manage/engineers/5810097836621824 - trendy_team_id: "5810097836621824", + // go/trendy/manage/engineers/5955405559201792 + trendy_team_id: "5955405559201792", } diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000000..39debf5c6d --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,42 @@ +// Copyright 2024 Google Inc. All rights reserved. +// +// 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"], + default_team: "trendy_team_build", +} + +python_test_host { + name: "run_tool_with_logging_test", + main: "run_tool_with_logging_test.py", + pkg_path: "testdata", + srcs: [ + "run_tool_with_logging_test.py", + ], + test_options: { + unit_test: true, + }, + data: [ + ":envsetup_minimum.zip", + ":tool_event_logger", + ], + test_suites: [ + "general-tests", + ], + version: { + py3: { + embedded_launcher: true, + }, + }, +} diff --git a/tests/run.rbc b/tests/run.rbc index 85d6c09bc5..221b40f04d 100644 --- a/tests/run.rbc +++ b/tests/run.rbc @@ -26,6 +26,7 @@ load(":product.rbc", "init") load(":board.rbc", board_init = "init") load(":board_input_vars.rbc", board_input_vars_init = "init") load("//build/make/tests/single_value_inheritance:test.rbc", test_single_value_inheritance = "test") +load("//build/make/tests/single_value_inheritance_2:test.rbc", test_single_value_inheritance_2 = "test") load("//build/make/tests/artifact_path_requirements:test.rbc", test_artifact_path_requirements = "test") load("//build/make/tests/prefixed_sort_order:test.rbc", test_prefixed_sort_order = "test") load("//build/make/tests/inherits_in_regular_variables:test.rbc", test_inherits_in_regular_variables = "test") @@ -181,6 +182,7 @@ assert_eq("f", cfg["BAZ"]) assert_eq("", g.get("NEWVAR")) test_single_value_inheritance() +test_single_value_inheritance_2() test_artifact_path_requirements() test_prefixed_sort_order() test_inherits_in_regular_variables() diff --git a/tests/run_tool_with_logging_test.py b/tests/run_tool_with_logging_test.py new file mode 100644 index 0000000000..18abd8e54f --- /dev/null +++ b/tests/run_tool_with_logging_test.py @@ -0,0 +1,332 @@ +# 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 dataclasses +import glob +from importlib import resources +import logging +import os +from pathlib import Path +import re +import shutil +import signal +import stat +import subprocess +import sys +import tempfile +import textwrap +import time +import unittest +import zipfile + +EXII_RETURN_CODE = 0 +INTERRUPTED_RETURN_CODE = 130 + + +class RunToolWithLoggingTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + # Configure to print logging to stdout. + logging.basicConfig(filename=None, level=logging.DEBUG) + console = logging.StreamHandler(sys.stdout) + logging.getLogger("").addHandler(console) + + def setUp(self): + super().setUp() + self.working_dir = tempfile.TemporaryDirectory() + # Run all the tests from working_dir which is our temp Android build top. + os.chdir(self.working_dir.name) + # Extract envsetup.zip which contains the envsetup.sh and other dependent + # scripts required to set up the build environments. + with resources.files("testdata").joinpath("envsetup.zip").open("rb") as p: + with zipfile.ZipFile(p, "r") as zip_f: + zip_f.extractall() + + def tearDown(self): + self.working_dir.cleanup() + super().tearDown() + + def test_does_not_log_when_logger_var_empty(self): + test_tool = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + + def test_does_not_log_with_logger_unset(self): + test_tool = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + unset ANDROID_TOOL_LOGGER + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + + def test_log_success_with_logger_enabled(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_tool.assert_called_once_with_args("arg1 arg2") + expected_logger_args = ( + "--tool_tag FAKE_TOOL --start_timestamp \d+\.\d+ --end_timestamp" + " \d+\.\d+ --tool_args arg1 arg2 --exit_code 0" + ) + test_logger.assert_called_once_with_args(expected_logger_args) + + def test_run_tool_output_is_same_with_and_without_logging(self): + test_tool = TestScript.create(self.working_dir, "echo 'tool called'") + test_logger = TestScript.create(self.working_dir) + + run_tool_with_logging_stdout, run_tool_with_logging_stderr = ( + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + ) + + run_tool_without_logging_stdout, run_tool_without_logging_stderr = ( + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + {test_tool.executable} arg1 arg2 + """) + ) + + self.assertEqual( + run_tool_with_logging_stdout, run_tool_without_logging_stdout + ) + self.assertEqual( + run_tool_with_logging_stderr, run_tool_without_logging_stderr + ) + + def test_logger_output_is_suppressed(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir, "echo 'logger called'") + + run_tool_with_logging_output, _ = self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self.assertNotIn("logger called", run_tool_with_logging_output) + + def test_logger_error_is_suppressed(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create( + self.working_dir, "echo 'logger failed' > /dev/stderr; exit 1" + ) + + _, err = self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self.assertNotIn("logger failed", err) + + def test_log_success_when_tool_interrupted(self): + test_tool = TestScript.create(self.working_dir, script_body="sleep 100") + test_logger = TestScript.create(self.working_dir) + + process = self._run_script_in_build_env(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + pgid = os.getpgid(process.pid) + # Give sometime for the subprocess to start. + time.sleep(1) + # Kill the subprocess and any processes created in the same group. + os.killpg(pgid, signal.SIGINT) + + returncode, _, _ = self._wait_for_process(process) + self.assertEqual(returncode, INTERRUPTED_RETURN_CODE) + + expected_logger_args = ( + "--tool_tag FAKE_TOOL --start_timestamp \d+\.\d+ --end_timestamp" + " \d+\.\d+ --tool_args arg1 arg2 --exit_code 130" + ) + test_logger.assert_called_once_with_args(expected_logger_args) + + def test_logger_can_be_toggled_on(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_logger.assert_called_with_times(1) + + def test_logger_can_be_toggled_off(self): + test_tool = TestScript.create(self.working_dir) + test_logger = TestScript.create(self.working_dir) + + self._run_script_and_wait(f""" + ANDROID_TOOL_LOGGER="{test_logger.executable}" + ANDROID_TOOL_LOGGER="" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + test_logger.assert_not_called() + + def test_integration_tool_event_logger_dry_run(self): + test_tool = TestScript.create(self.working_dir) + logger_path = self._import_logger() + + self._run_script_and_wait(f""" + TMPDIR="{self.working_dir.name}" + ANDROID_TOOL_LOGGER="{logger_path}" + ANDROID_TOOL_LOGGER_EXTRA_ARGS="--dry_run" + run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2 + """) + + self._assert_logger_dry_run() + + def _import_logger(self) -> Path: + logger = "tool_event_logger" + logger_path = Path(self.working_dir.name).joinpath(logger) + with resources.as_file(resources.files("testdata").joinpath(logger)) as p: + shutil.copy(p, logger_path) + Path.chmod(logger_path, 0o755) + return logger_path + + def _assert_logger_dry_run(self): + log_files = glob.glob(self.working_dir.name + "/tool_event_logger_*/*.log") + self.assertEqual(len(log_files), 1) + + with open(log_files[0], "r") as f: + lines = f.readlines() + self.assertEqual(len(lines), 1) + self.assertIn("dry run", lines[0]) + + def _create_build_env_script(self) -> str: + return f""" + source {Path(self.working_dir.name).joinpath("build/make/envsetup.sh")} + """ + + def _run_script_and_wait(self, test_script: str) -> tuple[str, str]: + process = self._run_script_in_build_env(test_script) + returncode, out, err = self._wait_for_process(process) + logging.debug("script stdout: %s", out) + logging.debug("script stderr: %s", err) + self.assertEqual(returncode, EXII_RETURN_CODE) + return out, err + + def _run_script_in_build_env(self, test_script: str) -> subprocess.Popen: + setup_build_env_script = self._create_build_env_script() + return subprocess.Popen( + setup_build_env_script + test_script, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + start_new_session=True, + executable="/bin/bash", + ) + + def _wait_for_process( + self, process: subprocess.Popen + ) -> tuple[int, str, str]: + pgid = os.getpgid(process.pid) + out, err = process.communicate() + # Wait for all process in the same group to complete since the logger runs + # as a separate detached process. + self._wait_for_process_group(pgid) + return (process.returncode, out, err) + + def _wait_for_process_group(self, pgid: int, timeout: int = 5): + """Waits for all subprocesses within the process group to complete.""" + start_time = time.time() + while True: + if time.time() - start_time > timeout: + raise TimeoutError( + f"Process group did not complete after {timeout} seconds" + ) + for pid in os.listdir("/proc"): + if pid.isdigit(): + try: + if os.getpgid(int(pid)) == pgid: + time.sleep(0.1) + break + except (FileNotFoundError, PermissionError, ProcessLookupError): + pass + else: + # All processes have completed. + break + + +@dataclasses.dataclass +class TestScript: + executable: Path + output_file: Path + + def create(temp_dir: Path, script_body: str = ""): + with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f: + output_file = f.name + + with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f: + executable = f.name + executable_contents = textwrap.dedent(f""" + #!/bin/bash + + echo "${{@}}" >> {output_file} + {script_body} + """) + f.write(executable_contents.encode("utf-8")) + + Path.chmod(f.name, os.stat(f.name).st_mode | stat.S_IEXEC) + + return TestScript(executable, output_file) + + def assert_called_with_times(self, expected_call_times: int): + lines = self._read_contents_from_output_file() + assert len(lines) == expected_call_times, ( + f"Expect to call {expected_call_times} times, but actually called" + f" {len(lines)} times." + ) + + def assert_called_with_args(self, expected_args: str): + lines = self._read_contents_from_output_file() + assert len(lines) > 0 + assert re.search(expected_args, lines[0]), ( + f"Expect to call with args {expected_args}, but actually called with" + f" args {lines[0]}." + ) + + def assert_not_called(self): + self.assert_called_with_times(0) + + def assert_called_once_with_args(self, expected_args: str): + self.assert_called_with_times(1) + self.assert_called_with_args(expected_args) + + def _read_contents_from_output_file(self) -> list[str]: + with open(self.output_file, "r") as f: + return f.readlines() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/single_value_inheritance_2/a.rbc b/tests/single_value_inheritance_2/a.rbc new file mode 100644 index 0000000000..fe186c7e84 --- /dev/null +++ b/tests/single_value_inheritance_2/a.rbc @@ -0,0 +1,20 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") + +def init(g, handle): + cfg = rblf.cfg(handle) + + cfg["PRODUCT_ENABLE_UFFD_GC"] = "true" diff --git a/tests/single_value_inheritance_2/b.rbc b/tests/single_value_inheritance_2/b.rbc new file mode 100644 index 0000000000..7d95749aa4 --- /dev/null +++ b/tests/single_value_inheritance_2/b.rbc @@ -0,0 +1,20 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") + +def init(g, handle): + cfg = rblf.cfg(handle) + + cfg["PRODUCT_ENABLE_UFFD_GC"] = "default" diff --git a/tests/single_value_inheritance_2/c.rbc b/tests/single_value_inheritance_2/c.rbc new file mode 100644 index 0000000000..e90e37d318 --- /dev/null +++ b/tests/single_value_inheritance_2/c.rbc @@ -0,0 +1,21 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") +load(":b.rbc", _b_init = "init") + +def init(g, handle): + cfg = rblf.cfg(handle) + + rblf.inherit(handle, "test/b", _b_init) diff --git a/tests/single_value_inheritance_2/d.rbc b/tests/single_value_inheritance_2/d.rbc new file mode 100644 index 0000000000..3a88c2c17f --- /dev/null +++ b/tests/single_value_inheritance_2/d.rbc @@ -0,0 +1,23 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") +load(":c.rbc", _c_init = "init") +load(":a.rbc", _a_init = "init") + +def init(g, handle): + cfg = rblf.cfg(handle) + + rblf.inherit(handle, "test/a", _a_init) + rblf.inherit(handle, "test/c", _c_init) diff --git a/tests/single_value_inheritance_2/product.rbc b/tests/single_value_inheritance_2/product.rbc new file mode 100644 index 0000000000..c47664db16 --- /dev/null +++ b/tests/single_value_inheritance_2/product.rbc @@ -0,0 +1,23 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") +load(":b.rbc", _b_init = "init") +load(":d.rbc", _d_init = "init") + +def init(g, handle): + cfg = rblf.cfg(handle) + + rblf.inherit(handle, "test/b", _b_init) + rblf.inherit(handle, "test/d", _d_init) diff --git a/tests/single_value_inheritance_2/test.rbc b/tests/single_value_inheritance_2/test.rbc new file mode 100644 index 0000000000..fa93aaa51e --- /dev/null +++ b/tests/single_value_inheritance_2/test.rbc @@ -0,0 +1,40 @@ +# Copyright 2024 Google LLC +# +# 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 +# +# https://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. + +load("//build/make/core:product_config.rbc", "rblf") +load("//build/make/tests/input_variables.rbc", input_variables_init = "init") +load(":product.rbc", "init") + + +def assert_eq(expected, actual): + if expected != actual: + fail("Expected '%s', got '%s'" % (expected, actual)) + +# This test is testing that single value variables are "stolen" when processing the inheritance +# graph. i.e. if you have a graph like this: +# +# B A +# |\ | +# | C | +# \ \| +# \ D +# \| +# E +# +# The same variable is defined in both A and B. In D, the value from A is chosen because it comes +# alphabetically before C. But then in E, the value from D is chosen instead of the value from B, +# because the value of B was "stolen" and sucked into C, leaving B with no value set. +def test(): + (globals, globals_base) = rblf.product_configuration("test/device", init, input_variables_init) + assert_eq("true", globals["PRODUCTS.test/device.mk.PRODUCT_ENABLE_UFFD_GC"]) diff --git a/tools/Android.bp b/tools/Android.bp index 0a55ed4b85..59831a61ec 100644 --- a/tools/Android.bp +++ b/tools/Android.bp @@ -115,3 +115,11 @@ python_binary_host { }, }, } + +python_binary_host { + name: "merge-event-log-tags", + srcs: [ + "event_log_tags.py", + "merge-event-log-tags.py", + ], +} diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml index 6bd0d06681..bf5e1a9bc4 100644 --- a/tools/aconfig/Cargo.toml +++ b/tools/aconfig/Cargo.toml @@ -2,6 +2,7 @@ members = [ "aconfig", + "aconfig_device_paths", "aconfig_protos", "aconfig_storage_file", "aconfig_storage_read_api", diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING index 0ea8feab7c..421e94a8a6 100644 --- a/tools/aconfig/TEST_MAPPING +++ b/tools/aconfig/TEST_MAPPING @@ -20,7 +20,7 @@ // aconfig C++ integration tests (test mode auto-generated code) "name": "aconfig.test.cpp.test_mode" }, - // TODO(327420679): Enable export mode for native flag library + // 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" @@ -33,12 +33,16 @@ // aconfig Rust integration tests (test mode auto-generated code) "name": "aconfig.test_mode.test.rust" }, - // TODO(327420679): Enable export mode for native flag library + // 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" // }, { + // aflags CLI unit tests + "name": "aflags.test" + }, + { // printflags unit tests "name": "printflags.test" }, @@ -66,9 +70,7 @@ // 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" @@ -92,11 +94,12 @@ { // aconfig_storage read api cpp integration tests "name": "aconfig_storage_read_api.test.cpp" - }, + } + ], + "postsubmit": [ { - // aflags CLI unit tests - // TODO(b/326062088): add to presubmit once proven in postsubmit. - "name": "aflags.test" + // aconfig_storage file cpp integration tests + "name": "aconfig_storage_file.test.cpp" } ] } diff --git a/tools/aconfig/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp index 00a6feedc0..68521af91f 100644 --- a/tools/aconfig/aconfig/Android.bp +++ b/tools/aconfig/aconfig/Android.bp @@ -161,6 +161,9 @@ cc_test { shared_libs: [ "server_configurable_flags", ], + defaults: [ + "aconfig_lib_cc_static_link.defaults", + ], test_suites: ["general-tests"], } @@ -176,6 +179,9 @@ cc_test { shared_libs: [ "server_configurable_flags", ], + defaults: [ + "aconfig_lib_cc_static_link.defaults", + ], test_suites: ["general-tests"], } @@ -199,6 +205,9 @@ cc_test { shared_libs: [ "server_configurable_flags", ], + defaults: [ + "aconfig_lib_cc_static_link.defaults", + ], test_suites: ["general-tests"], } */ @@ -215,6 +224,9 @@ cc_test { shared_libs: [ "server_configurable_flags", ], + defaults: [ + "aconfig_lib_cc_static_link.defaults", + ], test_suites: ["general-tests"], } diff --git a/tools/aconfig/aconfig/src/codegen/cpp.rs b/tools/aconfig/aconfig/src/codegen/cpp.rs index cd71b10d52..e743b2fc59 100644 --- a/tools/aconfig/aconfig/src/codegen/cpp.rs +++ b/tools/aconfig/aconfig/src/codegen/cpp.rs @@ -16,6 +16,7 @@ use anyhow::{ensure, Result}; use serde::Serialize; +use std::collections::HashMap; use std::path::PathBuf; use tinytemplate::TinyTemplate; @@ -29,13 +30,15 @@ pub fn generate_cpp_code<I>( package: &str, parsed_flags_iter: I, codegen_mode: CodegenMode, + flag_ids: HashMap<String, u16>, + allow_instrumentation: bool, ) -> Result<Vec<OutputFile>> where I: Iterator<Item = ProtoParsedFlag>, { let mut readwrite_count = 0; let class_elements: Vec<ClassElement> = parsed_flags_iter - .map(|pf| create_class_element(package, &pf, &mut readwrite_count)) + .map(|pf| create_class_element(package, &pf, flag_ids.clone(), &mut readwrite_count)) .collect(); let readwrite = readwrite_count > 0; let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only); @@ -53,6 +56,7 @@ where readwrite_count, is_test_mode: codegen_mode == CodegenMode::Test, class_elements, + allow_instrumentation, }; let files = [ @@ -96,6 +100,7 @@ pub struct Context<'a> { pub readwrite_count: i32, pub is_test_mode: bool, pub class_elements: Vec<ClassElement>, + pub allow_instrumentation: bool, } #[derive(Serialize)] @@ -106,11 +111,18 @@ pub struct ClassElement { pub default_value: String, pub flag_name: String, pub flag_macro: String, + pub flag_offset: u16, pub device_config_namespace: String, pub device_config_flag: String, + pub container: String, } -fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) -> ClassElement { +fn create_class_element( + package: &str, + pf: &ProtoParsedFlag, + flag_ids: HashMap<String, u16>, + rw_count: &mut i32, +) -> ClassElement { ClassElement { readwrite_idx: if pf.permission() == ProtoFlagPermission::READ_WRITE { let index = *rw_count; @@ -128,9 +140,11 @@ fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) }, flag_name: pf.name().to_string(), flag_macro: pf.name().to_uppercase(), + flag_offset: *flag_ids.get(pf.name()).expect("values checked at flag parse time"), device_config_namespace: pf.namespace().to_string(), device_config_flag: codegen::create_device_config_ident(package, pf.name()) .expect("values checked at flag parse time"), + container: pf.container().to_string(), } } @@ -1162,18 +1176,27 @@ bool com_android_aconfig_test_enabled_ro() { return true; } "#; + use crate::commands::assign_flag_ids; fn test_generate_cpp_code( parsed_flags: ProtoParsedFlags, mode: CodegenMode, expected_header: &str, expected_src: &str, + allow_instrumentation: bool, ) { let modified_parsed_flags = crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap(); - let generated = - generate_cpp_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode) - .unwrap(); + let flag_ids = + assign_flag_ids(crate::test::TEST_PACKAGE, modified_parsed_flags.iter()).unwrap(); + let generated = generate_cpp_code( + crate::test::TEST_PACKAGE, + modified_parsed_flags.into_iter(), + mode, + flag_ids, + allow_instrumentation, + ) + .unwrap(); let mut generated_files_map = HashMap::new(); for file in generated { generated_files_map.insert( @@ -1211,6 +1234,7 @@ bool com_android_aconfig_test_enabled_ro() { CodegenMode::Production, EXPORTED_PROD_HEADER_EXPECTED, PROD_SOURCE_FILE_EXPECTED, + false, ); } @@ -1222,6 +1246,7 @@ bool com_android_aconfig_test_enabled_ro() { CodegenMode::Test, EXPORTED_TEST_HEADER_EXPECTED, TEST_SOURCE_FILE_EXPECTED, + false, ); } @@ -1233,6 +1258,7 @@ bool com_android_aconfig_test_enabled_ro() { CodegenMode::Exported, EXPORTED_EXPORTED_HEADER_EXPECTED, EXPORTED_SOURCE_FILE_EXPECTED, + false, ); } @@ -1244,6 +1270,7 @@ bool com_android_aconfig_test_enabled_ro() { CodegenMode::ForceReadOnly, EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED, FORCE_READ_ONLY_SOURCE_FILE_EXPECTED, + false, ); } @@ -1255,6 +1282,7 @@ bool com_android_aconfig_test_enabled_ro() { CodegenMode::Production, READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED, READ_ONLY_PROD_SOURCE_FILE_EXPECTED, + false, ); } } diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs index fab2fa37ba..3360ddd68b 100644 --- a/tools/aconfig/aconfig/src/codegen/java.rs +++ b/tools/aconfig/aconfig/src/codegen/java.rs @@ -64,20 +64,27 @@ where include_str!("../../templates/FeatureFlags.java.template"), )?; template.add_template( + "CustomFeatureFlags.java", + include_str!("../../templates/CustomFeatureFlags.java.template"), + )?; + template.add_template( "FakeFeatureFlagsImpl.java", include_str!("../../templates/FakeFeatureFlagsImpl.java.template"), )?; let path: PathBuf = package.split('.').collect(); - ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] - .iter() - .map(|file| { - Ok(OutputFile { - contents: template.render(file, &context)?.into(), - path: path.join(file), - }) - }) - .collect::<Result<Vec<OutputFile>>>() + [ + "Flags.java", + "FeatureFlags.java", + "FeatureFlagsImpl.java", + "CustomFeatureFlags.java", + "FakeFeatureFlagsImpl.java", + ] + .iter() + .map(|file| { + Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file) }) + }) + .collect::<Result<Vec<OutputFile>>>() } fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec<NamespaceFlags> { @@ -181,26 +188,35 @@ mod tests { /** @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,118 +248,133 @@ mod tests { 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(); } "#; - const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" + const EXPECTED_CUSTOMFEATUREFLAGS_CONTENT: &str = 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; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override @UnsupportedAppUsage public boolean disabledRo() { - return getValue(Flags.FLAG_DISABLED_RO); + return getValue(Flags.FLAG_DISABLED_RO, + FeatureFlags::disabledRo); } @Override @UnsupportedAppUsage public boolean disabledRw() { - return getValue(Flags.FLAG_DISABLED_RW); + return getValue(Flags.FLAG_DISABLED_RW, + FeatureFlags::disabledRw); } @Override @UnsupportedAppUsage public boolean disabledRwExported() { - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, + FeatureFlags::disabledRwExported); } @Override @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + FeatureFlags::disabledRwInOtherNamespace); } @Override @UnsupportedAppUsage public boolean enabledFixedRo() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO); + return getValue(Flags.FLAG_ENABLED_FIXED_RO, + FeatureFlags::enabledFixedRo); } @Override @UnsupportedAppUsage public boolean enabledFixedRoExported() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + FeatureFlags::enabledFixedRoExported); } @Override @UnsupportedAppUsage public boolean enabledRo() { - return getValue(Flags.FLAG_ENABLED_RO); + return getValue(Flags.FLAG_ENABLED_RO, + FeatureFlags::enabledRo); } @Override @UnsupportedAppUsage public boolean enabledRoExported() { - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, + FeatureFlags::enabledRoExported); } @Override @UnsupportedAppUsage public boolean enabledRw() { - return getValue(Flags.FLAG_ENABLED_RW); - } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); - } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + return getValue(Flags.FLAG_ENABLED_RW, + FeatureFlags::enabledRw); } + public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && isOptimizationEnabled()) { @@ -351,30 +382,30 @@ mod tests { } 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) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; + + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) { + return mGetValueImpl.test(flagName, getter); } - private Map<String, Boolean> mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RO, false), - Map.entry(Flags.FLAG_DISABLED_RW, false), - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RW, false) - ) - ); + + public List<String> getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RO, + Flags.FLAG_DISABLED_RW, + Flags.FLAG_DISABLED_RW_EXPORTED, + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + Flags.FLAG_ENABLED_FIXED_RO, + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + Flags.FLAG_ENABLED_RO, + Flags.FLAG_ENABLED_RO_EXPORTED, + Flags.FLAG_ENABLED_RW + ); + } + private Set<String> mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( Flags.FLAG_DISABLED_RO, @@ -388,6 +419,58 @@ mod tests { } "#; + const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" + package com.android.aconfig.test; + + import java.util.HashMap; + import java.util.Map; + import java.util.function.Predicate; + + /** @hide */ + public class FakeFeatureFlagsImpl extends CustomFeatureFlags { + private final Map<String, Boolean> mFlagMap = new HashMap<>(); + private final FeatureFlags mDefaults; + + public FakeFeatureFlagsImpl() { + this(null); + } + + public FakeFeatureFlagsImpl(FeatureFlags defaults) { + super(null); + mDefaults = defaults; + // Initialize the map with null values + for (String flagName : getFlagNames()) { + mFlagMap.put(flagName, null); + } + } + + @Override + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) { + Boolean value = this.mFlagMap.get(flagName); + if (value != null) { + return value; + } + if (mDefaults != null) { + return getter.test(mDefaults); + } + throw new IllegalArgumentException(flagName + " is not set"); + } + + public void setFlag(String flagName, boolean value) { + if (!this.mFlagMap.containsKey(flagName)) { + throw new IllegalArgumentException("no such flag " + flagName); + } + this.mFlagMap.put(flagName, value); + } + + public void resetAll() { + for (Map.Entry entry : mFlagMap.entrySet()) { + entry.setValue(null); + } + } + } + "#; + #[test] fn test_generate_java_code_production() { let parsed_flags = crate::test::parse_test_flags(); @@ -458,13 +541,14 @@ mod tests { 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 +557,7 @@ mod tests { return disabledRw; } @Override + @com.android.aconfig.annotations.AconfigFlagAccessor @UnsupportedAppUsage public boolean disabledRwExported() { if (!aconfig_test_is_cached) { @@ -481,6 +566,7 @@ mod tests { return disabledRwExported; } @Override + @com.android.aconfig.annotations.AconfigFlagAccessor @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { if (!other_namespace_is_cached) { @@ -489,26 +575,31 @@ mod tests { 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) { @@ -523,6 +614,10 @@ mod tests { ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT), ( + "com/android/aconfig/test/CustomFeatureFlags.java", + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, + ), + ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, ), @@ -566,7 +661,6 @@ mod tests { 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"; - public static boolean disabledRwExported() { return FEATURE_FLAGS.disabledRwExported(); } @@ -623,7 +717,6 @@ mod tests { } aconfig_test_is_cached = true; } - @Override public boolean disabledRwExported() { if (!aconfig_test_is_cached) { @@ -631,7 +724,6 @@ mod tests { } return disabledRwExported; } - @Override public boolean enabledFixedRoExported() { if (!aconfig_test_is_cached) { @@ -639,7 +731,6 @@ mod tests { } return enabledFixedRoExported; } - @Override public boolean enabledRoExported() { if (!aconfig_test_is_cached) { @@ -649,55 +740,53 @@ mod tests { } }"#; - let expect_fake_feature_flags_impl_content = r#" + let expect_custom_feature_flags_content = r#" package com.android.aconfig.test; + import java.util.Arrays; - import java.util.HashMap; import java.util.HashSet; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override public boolean disabledRwExported() { - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, + FeatureFlags::disabledRwExported); } @Override public boolean enabledFixedRoExported() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + FeatureFlags::enabledFixedRoExported); } @Override public boolean enabledRoExported() { - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); - } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, + FeatureFlags::enabledRoExported); } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) { + return mGetValueImpl.test(flagName, getter); } - private boolean getValue(String flagName) { - Boolean value = this.mFlagMap.get(flagName); - if (value == null) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; + + public List<String> getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RW_EXPORTED, + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + Flags.FLAG_ENABLED_RO_EXPORTED + ); } - private Map<String, Boolean> mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false) - ) - ); + private Set<String> mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( "" @@ -711,8 +800,12 @@ mod tests { ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content), ( + "com/android/aconfig/test/CustomFeatureFlags.java", + expect_custom_feature_flags_content, + ), + ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", - expect_fake_feature_flags_impl_content, + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, ), ]); @@ -762,54 +855,63 @@ mod tests { /** @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( @@ -823,6 +925,10 @@ mod tests { ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), ( + "com/android/aconfig/test/CustomFeatureFlags.java", + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, + ), + ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, ), @@ -862,21 +968,27 @@ mod tests { /** @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(); }"#; @@ -888,31 +1000,37 @@ mod tests { /** @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; @@ -938,33 +1056,38 @@ mod tests { 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(); @@ -972,61 +1095,64 @@ mod tests { private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); }"#; - let expect_fakefeatureflags_content = r#" + let expect_customfeatureflags_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; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override @UnsupportedAppUsage public boolean disabledRo() { - return getValue(Flags.FLAG_DISABLED_RO); + return getValue(Flags.FLAG_DISABLED_RO, + FeatureFlags::disabledRo); } @Override @UnsupportedAppUsage public boolean disabledRw() { - return getValue(Flags.FLAG_DISABLED_RW); + return getValue(Flags.FLAG_DISABLED_RW, + FeatureFlags::disabledRw); } @Override @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + FeatureFlags::disabledRwInOtherNamespace); } @Override @UnsupportedAppUsage public boolean enabledFixedRo() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO); + return getValue(Flags.FLAG_ENABLED_FIXED_RO, + FeatureFlags::enabledFixedRo); } @Override @UnsupportedAppUsage public boolean enabledRo() { - return getValue(Flags.FLAG_ENABLED_RO); + return getValue(Flags.FLAG_ENABLED_RO, + FeatureFlags::enabledRo); } @Override @UnsupportedAppUsage public boolean enabledRw() { - return getValue(Flags.FLAG_ENABLED_RW); - } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); - } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + return getValue(Flags.FLAG_ENABLED_RW, + FeatureFlags::enabledRw); } + public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && isOptimizationEnabled()) { @@ -1034,27 +1160,27 @@ mod tests { } 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) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; + + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) { + return mGetValueImpl.test(flagName, getter); } - private Map<String, Boolean> mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RO, false), - Map.entry(Flags.FLAG_DISABLED_RW, false), - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RW, false) - ) - ); + + public List<String> getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RO, + Flags.FLAG_DISABLED_RW, + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + Flags.FLAG_ENABLED_FIXED_RO, + Flags.FLAG_ENABLED_RO, + Flags.FLAG_ENABLED_RW + ); + } + private Set<String> mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( Flags.FLAG_DISABLED_RO, @@ -1068,11 +1194,16 @@ mod tests { ); } "#; + let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content), - ("com/android/aconfig/test/FakeFeatureFlagsImpl.java", expect_fakefeatureflags_content), + ("com/android/aconfig/test/CustomFeatureFlags.java", expect_customfeatureflags_content), + ( + "com/android/aconfig/test/FakeFeatureFlagsImpl.java", + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, + ), ]); for file in generated_files { diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs index 7736ce75ee..6945fd4649 100644 --- a/tools/aconfig/aconfig/src/commands.rs +++ b/tools/aconfig/aconfig/src/commands.rs @@ -202,7 +202,11 @@ pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Ve generate_java_code(&package, modified_parsed_flags.into_iter(), codegen_mode) } -pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> { +pub fn create_cpp_lib( + mut input: Input, + codegen_mode: CodegenMode, + allow_instrumentation: bool, +) -> Result<Vec<OutputFile>> { // TODO(327420679): Enable export mode for native flag library ensure!( codegen_mode != CodegenMode::Exported, @@ -214,8 +218,14 @@ pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec bail!("no parsed flags, or the parsed flags use different packages"); }; let package = package.to_string(); - let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; - generate_cpp_code(&package, modified_parsed_flags.into_iter(), codegen_mode) + let flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?; + generate_cpp_code( + &package, + modified_parsed_flags.into_iter(), + codegen_mode, + flag_ids, + allow_instrumentation, + ) } pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> { @@ -239,13 +249,8 @@ pub fn create_storage( container: &str, file: &StorageFileType, ) -> Result<Vec<u8>> { - let parsed_flags_vec: Vec<ProtoParsedFlags> = caches - .into_iter() - .map(|mut input| input.try_parse_flags()) - .collect::<Result<Vec<_>>>()? - .into_iter() - .filter(|pfs| find_unique_container(pfs) == Some(container)) - .collect(); + let parsed_flags_vec: Vec<ProtoParsedFlags> = + caches.into_iter().map(|mut input| input.try_parse_flags()).collect::<Result<Vec<_>>>()?; generate_storage_file(container, parsed_flags_vec.iter(), file) } @@ -324,14 +329,6 @@ fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> { Some(package) } -fn find_unique_container(parsed_flags: &ProtoParsedFlags) -> Option<&str> { - let container = parsed_flags.parsed_flag.first().map(|pf| pf.container())?; - if parsed_flags.parsed_flag.iter().any(|pf| pf.container() != container) { - return None; - } - Some(container) -} - pub fn modify_parsed_flags_based_on_mode( parsed_flags: ProtoParsedFlags, codegen_mode: CodegenMode, diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs index 69f54581cd..72be1c9896 100644 --- a/tools/aconfig/aconfig/src/main.rs +++ b/tools/aconfig/aconfig/src/main.rs @@ -83,6 +83,12 @@ fn cli() -> Command { .long("mode") .value_parser(EnumValueParser::<CodegenMode>::new()) .default_value("production"), + ) + .arg( + Arg::new("allow-instrumentation") + .long("allow-instrumentation") + .value_parser(clap::value_parser!(bool)) + .default_value("false"), ), ) .subcommand( @@ -241,8 +247,10 @@ fn main() -> Result<()> { Some(("create-cpp-lib", sub_matches)) => { let cache = open_single_file(sub_matches, "cache")?; let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?; - let generated_files = - commands::create_cpp_lib(cache, *mode).context("failed to create cpp lib")?; + let allow_instrumentation = + get_required_arg::<bool>(sub_matches, "allow-instrumentation")?; + let generated_files = commands::create_cpp_lib(cache, *mode, *allow_instrumentation) + .context("failed to create cpp lib")?; let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?); generated_files .iter() diff --git a/tools/aconfig/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs index b861c1f7ee..a9712119bf 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, StorageFileType + get_table_size, FlagTable, FlagTableHeader, FlagTableNode, StorageFileType, StoredFlagType, + FILE_VERSION, }; use anyhow::{anyhow, Result}; @@ -45,8 +47,8 @@ impl FlagTableNodeWrapper { 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); @@ -54,7 +56,7 @@ impl FlagTableNodeWrapper { package_id, flag_name: flag_name.to_string(), flag_type, - flag_id, + flag_index, next_offset: None, }; Self { node, bucket_index } @@ -70,11 +72,14 @@ impl FlagTableNodeWrapper { 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<_>>>() @@ -95,10 +100,10 @@ pub fn create_flag_table(container: &str, packages: &[FlagPackage]) -> Result<Fl .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)); @@ -116,7 +121,7 @@ pub fn create_flag_table(container: &str, packages: &[FlagPackage]) -> Result<Fl 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 { @@ -136,79 +141,18 @@ mod tests { 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_type: StorageFileType::FlagMap as u8, - file_size: 321, - num_flags: 8, - bucket_offset: 31, - node_offset: 99, - }; - assert_eq!(header, &expected_header); - - let buckets: &Vec<Option<u32>> = &flag_table.as_ref().unwrap().buckets; - let expected_bucket: 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, - ]; - 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(151))); - 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(236))); - 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 e40bbc19d5..c15ba54112 100644 --- a/tools/aconfig/aconfig/src/storage/flag_value.rs +++ b/tools/aconfig/aconfig/src/storage/flag_value.rs @@ -17,7 +17,7 @@ use crate::commands::assign_flag_ids; use crate::storage::FlagPackage; use aconfig_protos::ProtoFlagState; -use aconfig_storage_file::{FlagValueHeader, FlagValueList, FILE_VERSION, StorageFileType}; +use aconfig_storage_file::{FlagValueHeader, FlagValueList, StorageFileType, FILE_VERSION}; use anyhow::{anyhow, Result}; fn new_header(container: &str, num_flags: u32) -> FlagValueHeader { @@ -41,19 +41,19 @@ pub fn create_flag_value(container: &str, packages: &[FlagPackage]) -> Result<Fl }; 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) @@ -64,31 +64,19 @@ mod tests { 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_type: StorageFileType::FlagVal as u8, - file_size: 35, - num_flags: 8, - boolean_value_offset: 27, - }; - 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 c818d79c14..73339f24b3 100644 --- a/tools/aconfig/aconfig/src/storage/mod.rs +++ b/tools/aconfig/aconfig/src/storage/mod.rs @@ -18,7 +18,7 @@ pub mod flag_table; pub mod flag_value; pub mod package_table; -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::collections::{HashMap, HashSet}; use crate::storage::{ @@ -33,9 +33,9 @@ pub struct FlagPackage<'a> { 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 @@ impl<'a> FlagPackage<'a> { package_id, flag_names: HashSet::new(), boolean_flags: vec![], - boolean_offset: 0, + boolean_start_index: 0, } } @@ -73,12 +73,11 @@ where } } - // 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 @@ -97,16 +96,17 @@ where match file { StorageFileType::PackageMap => { let package_table = create_package_table(container, &packages)?; - Ok(package_table.as_bytes()) + Ok(package_table.into_bytes()) } StorageFileType::FlagMap => { let flag_table = create_flag_table(container, &packages)?; - Ok(flag_table.as_bytes()) + Ok(flag_table.into_bytes()) } 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 @@ mod tests { "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![Input { + source: format!("tests/{}", value_file).to_string(), + reader: Box::new(value_content), }], - vec![], crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); @@ -175,21 +183,21 @@ mod tests { 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); assert_eq!(packages[1].flag_names.len(), 3); assert!(packages[1].flag_names.contains("enabled_ro")); - assert!(packages[1].flag_names.contains("disabled_ro")); + assert!(packages[1].flag_names.contains("disabled_rw")); 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_rw")); 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 bc2da4d909..c53602f9cb 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, StorageFileType + get_table_size, PackageTable, PackageTableHeader, PackageTableNode, StorageFileType, + FILE_VERSION, }; use crate::storage::FlagPackage; @@ -47,7 +48,7 @@ impl PackageTableNodeWrapper { 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); @@ -65,10 +66,10 @@ pub fn create_package_table(container: &str, packages: &[FlagPackage]) -> Result 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)); @@ -86,7 +87,7 @@ pub fn create_package_table(container: &str, packages: &[FlagPackage]) -> Result 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 { @@ -108,56 +109,18 @@ mod tests { 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_type: StorageFileType::PackageMap as u8, - file_size: 209, - num_packages: 3, - bucket_offset: 31, - node_offset: 59, - }; - assert_eq!(header, &expected_header); - - let buckets: &Vec<Option<u32>> = &package_table.as_ref().unwrap().buckets; - let expected: Vec<Option<u32>> = vec![Some(59), None, None, Some(109), 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(159), - }; - 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/CustomFeatureFlags.java.template b/tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template new file mode 100644 index 0000000000..b82b9cb827 --- /dev/null +++ b/tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template @@ -0,0 +1,70 @@ +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.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** @hide */ +public class CustomFeatureFlags implements FeatureFlags \{ + + private BiPredicate<String, Predicate<FeatureFlags>> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> getValueImpl) \{ + mGetValueImpl = getValueImpl; + } + +{{ -for item in flag_elements}} + @Override +{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} + public boolean {item.method_name}() \{ + return getValue(Flags.FLAG_{item.flag_name_constant_suffix}, + FeatureFlags::{item.method_name}); + } +{{ endfor }} + +{{ -if not library_exported }} + public boolean isFlagReadOnlyOptimized(String flagName) \{ + if (mReadOnlyFlagsSet.contains(flagName) && + isOptimizationEnabled()) \{ + return true; + } + return false; + } + + @com.android.aconfig.annotations.AssumeTrueForR8 + private boolean isOptimizationEnabled() \{ + return false; + } +{{ -endif }} + + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) \{ + return mGetValueImpl.test(flagName, getter); + } + + public List<String> getFlagNames() \{ + return Arrays.asList( + {{ -for item in flag_elements }} + Flags.FLAG_{item.flag_name_constant_suffix} + {{ -if not @last }},{{ endif }} + {{ -endfor }} + ); + } + + private Set<String> mReadOnlyFlagsSet = new HashSet<>( + Arrays.asList( + {{ -for item in flag_elements }} + {{ -if not item.is_read_write }} + Flags.FLAG_{item.flag_name_constant_suffix}, + {{ -endif }} + {{ -endfor }} + ""{# The empty string here is to resolve the ending comma #} + ) + ); +} diff --git a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template index 177e711e77..290d2c4b24 100644 --- a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template +++ b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template @@ -1,27 +1,39 @@ 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; import java.util.Map; -import java.util.Set; +import java.util.function.Predicate; /** @hide */ -public class FakeFeatureFlagsImpl implements FeatureFlags \{ +public class FakeFeatureFlagsImpl extends CustomFeatureFlags \{ + private final Map<String, Boolean> mFlagMap = new HashMap<>(); + private final FeatureFlags mDefaults; + public FakeFeatureFlagsImpl() \{ - resetAll(); + this(null); + } + + public FakeFeatureFlagsImpl(FeatureFlags defaults) \{ + super(null); + mDefaults = defaults; + // Initialize the map with null values + for (String flagName : getFlagNames()) \{ + mFlagMap.put(flagName, null); + } } -{{ for item in flag_elements}} @Override -{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} - public boolean {item.method_name}() \{ - return getValue(Flags.FLAG_{item.flag_name_constant_suffix}); + protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) \{ + Boolean value = this.mFlagMap.get(flagName); + if (value != null) \{ + return value; + } + if (mDefaults != null) \{ + return getter.test(mDefaults); + } + throw new IllegalArgumentException(flagName + " is not set"); } -{{ endfor}} + public void setFlag(String flagName, boolean value) \{ if (!this.mFlagMap.containsKey(flagName)) \{ throw new IllegalArgumentException("no such flag " + flagName); @@ -34,46 +46,4 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{ entry.setValue(null); } } -{{ if not library_exported }} - public boolean isFlagReadOnlyOptimized(String flagName) \{ - if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) \{ - return true; - } - 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) \{ - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; - } - - - private Map<String, Boolean> mFlagMap = new HashMap<>( - Map.ofEntries( - {{ -for item in flag_elements }} - Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false) - {{ -if not @last }},{{ endif }} - {{ -endfor }} - ) - ); - - private Set<String> mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - {{ -for item in flag_elements }} - {{ -if not item.is_read_write }} - Flags.FLAG_{item.flag_name_constant_suffix}, - {{ -endif }} - {{ -endfor }} - ""{# The empty string here is to resolve the ending comma #} - ) - ); } diff --git a/tools/aconfig/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/aconfig/templates/FeatureFlags.java.template index 13edcb4e52..38c8f13aaf 100644 --- a/tools/aconfig/aconfig/templates/FeatureFlags.java.template +++ b/tools/aconfig/aconfig/templates/FeatureFlags.java.template @@ -14,6 +14,7 @@ public interface FeatureFlags \{ {{ -endif- }} {{ -endif }} {{ -if not library_exported }} + @com.android.aconfig.annotations.AconfigFlagAccessor @UnsupportedAppUsage {{ -endif }} boolean {item.method_name}(); diff --git a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template index 12b2fc1ddd..6235e6946c 100644 --- a/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template +++ b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template @@ -47,6 +47,7 @@ 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}() \{ @@ -68,6 +69,7 @@ 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}() \{ diff --git a/tools/aconfig/aconfig/templates/Flags.java.template b/tools/aconfig/aconfig/templates/Flags.java.template index e105991369..e2f70b95fa 100644 --- a/tools/aconfig/aconfig/templates/Flags.java.template +++ b/tools/aconfig/aconfig/templates/Flags.java.template @@ -18,6 +18,7 @@ public final class Flags \{ {{ -endif }} {{ -endif }} {{ -if not library_exported }} + @com.android.aconfig.annotations.AconfigFlagAccessor @UnsupportedAppUsage {{ -endif }} public static boolean {item.method_name}() \{ diff --git a/tools/aconfig/aconfig/templates/cpp_source_file.template b/tools/aconfig/aconfig/templates/cpp_source_file.template index 4bcd1b7581..4c098c5b41 100644 --- a/tools/aconfig/aconfig/templates/cpp_source_file.template +++ b/tools/aconfig/aconfig/templates/cpp_source_file.template @@ -1,5 +1,15 @@ #include "{header}.h" +{{ if allow_instrumentation }} +#include <sys/stat.h> +#include "aconfig_storage/aconfig_storage_read_api.hpp" +#include <android/log.h> + +#define ALOGI(msg, ...) \ + __android_log_print(ANDROID_LOG_INFO, "AconfigTestMission1", (msg), __VA_ARGS__) + +{{ endif }} + {{ if readwrite- }} #include <server_configurable_flags/get_flags.h> {{ endif }} @@ -97,6 +107,62 @@ bool {header}_{item.flag_name}() \{ {{ -if item.readwrite }} return {cpp_namespace}::{item.flag_name}(); {{ -else }} + {{ if allow_instrumentation }} + auto result = + {{ if item.is_fixed_read_only }} + {package_macro}_{item.flag_macro} + {{ else }} + {item.default_value} + {{ endif }}; + + struct stat buffer; + if (stat("/metadata/aconfig_test_missions/mission_1", &buffer) != 0) \{ + return result; + } + + auto package_map_file = aconfig_storage::get_mapped_file( + "{item.container}", + aconfig_storage::StorageFileType::package_map); + if (!package_map_file.ok()) \{ + ALOGI("error: failed to get package map file: %s", package_map_file.error().message().c_str()); + return result; + } + + auto package_read_context = aconfig_storage::get_package_read_context( + **package_map_file, "{package}"); + if (!package_read_context.ok()) \{ + ALOGI("error: failed to get package read context: %s", package_map_file.error().message().c_str()); + return result; + } + + delete *package_map_file; + + auto flag_val_map = aconfig_storage::get_mapped_file( + "{item.container}", + aconfig_storage::StorageFileType::flag_val); + if (!flag_val_map.ok()) \{ + ALOGI("error: failed to get flag val map: %s", package_map_file.error().message().c_str()); + return result; + } + + auto value = aconfig_storage::get_boolean_flag_value( + **flag_val_map, + package_read_context->boolean_start_index + {item.flag_offset}); + if (!value.ok()) \{ + ALOGI("error: failed to get flag val: %s", package_map_file.error().message().c_str()); + return result; + } + + delete *flag_val_map; + + if (*value != result) \{ + ALOGI("error: new storage value '%d' does not match current value '%d'", *value, result); + } else \{ + ALOGI("success: new storage value was '%d, legacy storage was '%d'", *value, result); + } + + return result; + {{ else }} {{ -if item.is_fixed_read_only }} return {package_macro}_{item.flag_macro}; {{ -else }} @@ -104,6 +170,7 @@ bool {header}_{item.flag_name}() \{ {{ -endif }} {{ -endif }} {{ -endif }} + {{ -endif }} } {{ -if is_test_mode }} @@ -119,3 +186,4 @@ void {header}_reset_flags() \{ } {{ -endif }} + 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 0000000000..35548ae5ba --- /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.aconfig b/tools/aconfig/aconfig/tests/storage_test_2.aconfig index bb14fd1324..db77f7a35c 100644 --- a/tools/aconfig/aconfig/tests/storage_test_2.aconfig +++ b/tools/aconfig/aconfig/tests/storage_test_2.aconfig @@ -9,7 +9,7 @@ flag { } flag { - name: "disabled_ro" + name: "disabled_rw" namespace: "aconfig_test" description: "This flag is DISABLED + READ_ONLY" bug: "123" 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 0000000000..b6507210da --- /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_rw" + state: DISABLED + permission: READ_WRITE +} +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.aconfig b/tools/aconfig/aconfig/tests/storage_test_4.aconfig index 333fe09e8e..5802a73b6d 100644 --- a/tools/aconfig/aconfig/tests/storage_test_4.aconfig +++ b/tools/aconfig/aconfig/tests/storage_test_4.aconfig @@ -2,7 +2,7 @@ package: "com.android.aconfig.storage.test_4" container: "system" flag { - name: "enabled_ro" + name: "enabled_rw" namespace: "aconfig_test" description: "This flag is ENABLED + READ_ONLY" bug: "abc" 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 0000000000..784b744f62 --- /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_rw" + state: ENABLED + permission: READ_WRITE +} +flag_value { + package: "com.android.aconfig.storage.test_4" + name: "enabled_fixed_ro" + state: ENABLED + permission: READ_ONLY +} diff --git a/tools/aconfig/aconfig_device_paths/Android.bp b/tools/aconfig/aconfig_device_paths/Android.bp new file mode 100644 index 0000000000..2c771e09f5 --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/Android.bp @@ -0,0 +1,51 @@ +// 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"], +} + +rust_defaults { + name: "libaconfig_device_paths.defaults", + edition: "2021", + clippy_lints: "android", + lints: "android", + srcs: ["src/lib.rs"], + rustlibs: [ + "libaconfig_protos", + "libanyhow", + "libprotobuf", + "libregex", + ], +} + +rust_library { + name: "libaconfig_device_paths", + crate_name: "aconfig_device_paths", + host_supported: true, + defaults: ["libaconfig_device_paths.defaults"], +} + +genrule { + name: "libaconfig_java_device_paths_src", + srcs: ["src/DevicePathsTemplate.java"], + out: ["DevicePaths.java"], + tool_files: ["partition_aconfig_flags_paths.txt"], + cmd: "sed -e '/TEMPLATE/{r$(location partition_aconfig_flags_paths.txt)' -e 'd}' $(in) > $(out)" +} + +java_library { + name: "aconfig_device_paths_java", + srcs: [":libaconfig_java_device_paths_src"], +} diff --git a/tools/aconfig/aconfig_device_paths/Cargo.toml b/tools/aconfig/aconfig_device_paths/Cargo.toml new file mode 100644 index 0000000000..dbe9b3a111 --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "aconfig_device_paths" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.82" diff --git a/tools/aconfig/aconfig_device_paths/partition_aconfig_flags_paths.txt b/tools/aconfig/aconfig_device_paths/partition_aconfig_flags_paths.txt new file mode 100644 index 0000000000..140cd21ac8 --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/partition_aconfig_flags_paths.txt @@ -0,0 +1,4 @@ +"/system/etc/aconfig_flags.pb", +"/system_ext/etc/aconfig_flags.pb", +"/product/etc/aconfig_flags.pb", +"/vendor/etc/aconfig_flags.pb", diff --git a/tools/aconfig/aconfig_device_paths/src/DevicePathsTemplate.java b/tools/aconfig/aconfig_device_paths/src/DevicePathsTemplate.java new file mode 100644 index 0000000000..f27b9bd360 --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/src/DevicePathsTemplate.java @@ -0,0 +1,68 @@ +/* + * 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 android.aconfig; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @hide + */ +public class DevicePaths { + static final String[] PATHS = { + TEMPLATE + }; + + private static final String APEX_DIR = "/apex"; + private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb"; + + + /** + * Returns the list of all on-device aconfig protos paths. + * @hide + */ + public List<String> parsedFlagsProtoPaths() { + ArrayList<String> paths = new ArrayList(Arrays.asList(PATHS)); + + File apexDirectory = new File(APEX_DIR); + if (!apexDirectory.isDirectory()) { + return paths; + } + + File[] subdirs = apexDirectory.listFiles(); + if (subdirs == null) { + return paths; + } + + for (File prefix : subdirs) { + // For each mainline modules, there are two directories, one <modulepackage>/, + // and one <modulepackage>@<versioncode>/. Just read the former. + if (prefix.getAbsolutePath().contains("@")) { + continue; + } + + File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX); + if (!protoPath.exists()) { + continue; + } + + paths.add(protoPath.getAbsolutePath()); + } + return paths; + } +} diff --git a/tools/aconfig/aconfig_device_paths/src/lib.rs b/tools/aconfig/aconfig_device_paths/src/lib.rs new file mode 100644 index 0000000000..c5a6bff1f7 --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/src/lib.rs @@ -0,0 +1,47 @@ +/* + * 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. + */ + +//! Library for finding all aconfig on-device protobuf file paths. + +use anyhow::Result; +use std::path::PathBuf; + +use std::fs; + +/// Determine all paths that contain an aconfig protobuf file. +pub fn parsed_flags_proto_paths() -> Result<Vec<PathBuf>> { + let mut result: Vec<PathBuf> = [include_str!("../partition_aconfig_flags_paths.txt")] + .map(|s| PathBuf::from(s.to_string())) + .to_vec(); + for dir in fs::read_dir("/apex")? { + let dir = dir?; + + // Only scan the currently active version of each mainline module; skip the @version dirs. + if dir.file_name().as_encoded_bytes().iter().any(|&b| b == b'@') { + continue; + } + + let mut path = PathBuf::from("/apex"); + path.push(dir.path()); + path.push("etc"); + path.push("aconfig_flags.pb"); + if path.exists() { + result.push(path); + } + } + + Ok(result) +} diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp index 2a606bf3de..e066e31ac1 100644 --- a/tools/aconfig/aconfig_storage_file/Android.bp +++ b/tools/aconfig/aconfig_storage_file/Android.bp @@ -12,6 +12,7 @@ rust_defaults { "libtempfile", "libprotobuf", "libclap", + "libcxx", "libaconfig_storage_protos", ], } @@ -22,6 +23,13 @@ rust_library { host_supported: true, defaults: ["aconfig_storage_file.defaults"], srcs: ["src/lib.rs"], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "29", + vendor_available: true, + product_available: true, } rust_binary_host { @@ -44,9 +52,16 @@ rust_protobuf { 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", + vendor_available: true, + product_available: true, } -cc_library_static { +cc_library { name: "libaconfig_storage_protos_cc", proto: { export_proto_headers: true, @@ -58,4 +73,67 @@ cc_library_static { "//apex_available:anyapex", ], host_supported: true, + min_sdk_version: "29", + vendor_available: true, + product_available: true, + double_loadable: true, +} + +// cxx source codegen from rust api +genrule { + name: "libcxx_aconfig_storage_file_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_file_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_file_cxx_bridge", + crate_name: "aconfig_storage_file_cxx_bridge", + host_supported: true, + vendor_available: true, + product_available: true, + srcs: ["src/lib.rs"], + defaults: ["aconfig_storage_file.defaults"], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "29", +} + +// storage file parse api cc interface +cc_library { + name: "libaconfig_storage_file_cc", + srcs: ["aconfig_storage_file.cpp"], + generated_headers: [ + "cxx-bridge-header", + "libcxx_aconfig_storage_file_bridge_header", + ], + generated_sources: ["libcxx_aconfig_storage_file_bridge_code"], + whole_static_libs: ["libaconfig_storage_file_cxx_bridge"], + export_include_dirs: ["include"], + host_supported: true, + vendor_available: true, + product_available: true, + shared_libs: [ + "libbase", + ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "29", + double_loadable: true, } diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml index 641f481ed3..192dfad40a 100644 --- a/tools/aconfig/aconfig_storage_file/Cargo.toml +++ b/tools/aconfig/aconfig_storage_file/Cargo.toml @@ -13,6 +13,7 @@ protobuf = "3.2.0" tempfile = "3.9.0" thiserror = "1.0.56" clap = { version = "4.1.8", features = ["derive"] } +cxx = "1.0" [[bin]] name = "aconfig-storage" diff --git a/tools/aconfig/aconfig_storage_file/aconfig_storage_file.cpp b/tools/aconfig/aconfig_storage_file/aconfig_storage_file.cpp new file mode 100644 index 0000000000..7af024b39d --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/aconfig_storage_file.cpp @@ -0,0 +1,61 @@ +#include "rust/cxx.h" +#include "aconfig_storage/lib.rs.h" + +#include "aconfig_storage/aconfig_storage_file.hpp" + +using namespace android::base; + +namespace aconfig_storage { + +Result<std::vector<FlagValueSummary>> list_flags( + const std::string& package_map, + const std::string& flag_map, + const std::string& flag_val) { + auto flag_list_cxx = list_flags_cxx(rust::Str(package_map.c_str()), + rust::Str(flag_map.c_str()), + rust::Str(flag_val.c_str())); + if (flag_list_cxx.query_success) { + auto flag_list = std::vector<FlagValueSummary>(); + for (const auto& flag_cxx : flag_list_cxx.flags) { + auto flag = FlagValueSummary(); + flag.package_name = std::string(flag_cxx.package_name); + flag.flag_name = std::string(flag_cxx.flag_name); + flag.flag_value = std::string(flag_cxx.flag_value); + flag.value_type = std::string(flag_cxx.value_type); + flag_list.push_back(flag); + } + return flag_list; + } else { + return Error() << flag_list_cxx.error_message; + } +} + +Result<std::vector<FlagValueAndInfoSummary>> list_flags_with_info( + const std::string& package_map, + const std::string& flag_map, + const std::string& flag_val, + const std::string& flag_info) { + auto flag_list_cxx = list_flags_with_info_cxx(rust::Str(package_map.c_str()), + rust::Str(flag_map.c_str()), + rust::Str(flag_val.c_str()), + rust::Str(flag_info.c_str())); + if (flag_list_cxx.query_success) { + auto flag_list = std::vector<FlagValueAndInfoSummary>(); + for (const auto& flag_cxx : flag_list_cxx.flags) { + auto flag = FlagValueAndInfoSummary(); + flag.package_name = std::string(flag_cxx.package_name); + flag.flag_name = std::string(flag_cxx.flag_name); + flag.flag_value = std::string(flag_cxx.flag_value); + flag.value_type = std::string(flag_cxx.value_type); + flag.is_readwrite = flag_cxx.is_readwrite; + flag.has_server_override = flag_cxx.has_server_override; + flag.has_local_override = flag_cxx.has_local_override; + flag_list.push_back(flag); + } + return flag_list; + } else { + return Error() << flag_list_cxx.error_message; + } +} + +} // namespace aconfig_storage diff --git a/tools/aconfig/aconfig_storage_file/build.rs b/tools/aconfig/aconfig_storage_file/build.rs index 1feeb60677..e0ade2aba0 100644 --- a/tools/aconfig/aconfig_storage_file/build.rs +++ b/tools/aconfig/aconfig_storage_file/build.rs @@ -14,4 +14,6 @@ fn main() { .inputs(proto_files) .cargo_out_dir("aconfig_storage_protos") .run_from_script(); + + let _ = cxx_build::bridge("src/lib.rs"); } diff --git a/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage_file.hpp b/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage_file.hpp new file mode 100644 index 0000000000..9f3cdb046b --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/include/aconfig_storage/aconfig_storage_file.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <vector> +#include <string> +#include <android-base/result.h> + +namespace aconfig_storage { + +/// Flag value summary for a flag +struct FlagValueSummary { + std::string package_name; + std::string flag_name; + std::string flag_value; + std::string value_type; +}; + +/// List all flag values +/// \input package_map: package map file +/// \input flag_map: flag map file +/// \input flag_val: flag value file +android::base::Result<std::vector<FlagValueSummary>> list_flags( + const std::string& package_map, + const std::string& flag_map, + const std::string& flag_val); + +/// Flag value and info summary for a flag +struct FlagValueAndInfoSummary { + std::string package_name; + std::string flag_name; + std::string flag_value; + std::string value_type; + bool is_readwrite; + bool has_server_override; + bool has_local_override; +}; + +/// List all flag values with their flag info +/// \input package_map: package map file +/// \input flag_map: flag map file +/// \input flag_val: flag value file +/// \input flag_info: flag info file +android::base::Result<std::vector<FlagValueAndInfoSummary>> list_flags_with_info( + const std::string& package_map, + const std::string& flag_map, + const std::string& flag_val, + const std::string& flag_info); + +}// 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 c6728bdfee..e1c1c7ffca 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 @@ message storage_file_info { 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 0000000000..beac38d156 --- /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 { + HasServerOverride = 1 << 0, + IsReadWrite = 1 << 1, + HasLocalOverride = 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, + "readwrite: {}, server override: {}, local override: {}", + self.attributes & (FlagInfoBit::IsReadWrite as u8) != 0, + self.attributes & (FlagInfoBit::HasServerOverride as u8) != 0, + self.attributes & (FlagInfoBit::HasLocalOverride 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 f9b3158eef..64b90eabfa 100644 --- a/tools/aconfig/aconfig_storage_file/src/flag_table.rs +++ b/tools/aconfig/aconfig_storage_file/src/flag_table.rs @@ -21,7 +21,7 @@ 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}; +use crate::{AconfigStorageError, StorageFileType, StoredFlagType}; use anyhow::anyhow; use std::fmt; @@ -59,7 +59,7 @@ impl fmt::Debug for FlagTableHeader { 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(); @@ -99,8 +99,9 @@ impl FlagTableHeader { 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>, } @@ -109,8 +110,8 @@ impl fmt::Debug for FlagTableNode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, - "Package Id: {}, Flag: {}, Type: {}, Offset: {}, Next: {:?}", - self.package_id, self.flag_name, self.flag_type, self.flag_id, self.next_offset + "Package Id: {}, Flag: {}, Type: {:?}, Index: {}, Next: {:?}", + self.package_id, self.flag_name, self.flag_type, self.flag_index, self.next_offset )?; Ok(()) } @@ -118,14 +119,14 @@ impl fmt::Debug for FlagTableNode { 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 } @@ -136,8 +137,8 @@ impl FlagTableNode { 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), @@ -178,11 +179,11 @@ impl fmt::Debug for FlagTable { /// 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() } @@ -192,7 +193,7 @@ impl FlagTable { 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, @@ -202,7 +203,7 @@ impl FlagTable { 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>>() @@ -226,17 +227,17 @@ mod tests { 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 flag_table_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()); @@ -248,10 +249,10 @@ mod tests { // bytes fn test_version_number() { let flag_table = create_test_flag_table(); - let bytes = &flag_table.as_bytes(); + let bytes = &flag_table.into_bytes(); let mut head = 0; let version = read_u32_from_bytes(bytes, &mut head).unwrap(); - assert_eq!(version, 1234) + assert_eq!(version, 1); } #[test] @@ -259,7 +260,7 @@ mod tests { 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.as_bytes()).unwrap_err(); + let error = FlagTable::from_bytes(&flag_table.into_bytes()).unwrap_err(); assert_eq!( format!("{:?}", error), 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 c9d09a1c17..506924b339 100644 --- a/tools/aconfig/aconfig_storage_file/src/flag_value.rs +++ b/tools/aconfig/aconfig_storage_file/src/flag_value.rs @@ -55,7 +55,7 @@ impl fmt::Debug for FlagValueHeader { 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(); @@ -108,9 +108,9 @@ impl fmt::Debug for FlagValueList { 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() @@ -120,7 +120,7 @@ impl FlagValueList { 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 }; @@ -139,11 +139,11 @@ mod tests { 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 flag_value_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()); @@ -155,10 +155,10 @@ mod tests { // bytes fn test_version_number() { let flag_value_list = create_test_flag_value_list(); - let bytes = &flag_value_list.as_bytes(); + let bytes = &flag_value_list.into_bytes(); let mut head = 0; let version = read_u32_from_bytes(bytes, &mut head).unwrap(); - assert_eq!(version, 1234) + assert_eq!(version, 1); } #[test] @@ -166,7 +166,7 @@ mod tests { 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.as_bytes()).unwrap_err(); + let error = FlagValueList::from_bytes(&flag_value_list.into_bytes()).unwrap_err(); assert_eq!( format!("{:?}", error), 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 24b16a1a47..26e9c1a3be 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 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_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::{PackageTable, PackageTableHeader, PackageTableNode}; -use crate::AconfigStorageError::{BytesParseFail, HashTableSizeLimit}; +use crate::AconfigStorageError::{ + BytesParseFail, HashTableSizeLimit, InvalidFlagValueType, InvalidStoredFlagType, +}; /// Storage file version pub const FILE_VERSION: u32 = 1; @@ -68,6 +71,7 @@ pub enum StorageFileType { PackageMap = 0, FlagMap = 1, FlagVal = 2, + FlagInfo = 3, } impl TryFrom<&str> for StorageFileType { @@ -78,8 +82,9 @@ impl TryFrom<&str> for StorageFileType { "package_map" => Ok(Self::PackageMap), "flag_map" => Ok(Self::FlagMap), "flag_val" => Ok(Self::FlagVal), + "flag_info" => Ok(Self::FlagInfo), _ => Err(anyhow!( - "Invalid storage file type, valid types are package_map|flag_map|flag_val" + "Invalid storage file type, valid types are package_map|flag_map|flag_val|flag_info" )), } } @@ -93,11 +98,108 @@ impl TryFrom<u8> for StorageFileType { 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> { @@ -160,44 +262,6 @@ pub(crate) fn read_str_from_bytes( 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("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), -} - /// 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| { @@ -206,7 +270,7 @@ pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageErro let mut buffer = Vec::new(); file.read_to_end(&mut buffer).map_err(|errmsg| { AconfigStorageError::FileReadFail(anyhow!( - "Failed to read 4 bytes from file {}: {}", + "Failed to read bytes from file {}: {}", file_path, errmsg )) @@ -214,50 +278,244 @@ pub fn read_file_to_bytes(file_path: &str) -> Result<Vec<u8>, AconfigStorageErro Ok(buffer) } +/// Flag value summary +#[derive(Debug, PartialEq)] +pub struct FlagValueSummary { + pub package_name: String, + pub flag_name: String, + pub flag_value: String, + pub value_type: StoredFlagType, +} + /// List flag values from storage files pub fn list_flags( package_map: &str, flag_map: &str, flag_val: &str, -) -> Result<Vec<(String, bool)>, AconfigStorageError> { +) -> Result<Vec<FlagValueSummary>, 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)?)?; + + 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); + } + + let mut flags = Vec::new(); + for node in flag_table.nodes.iter() { + let (package_name, boolean_start_index) = package_info[node.package_id as usize]; + let flag_index = boolean_start_index + node.flag_index as u32; + let flag_value = flag_value_list.booleans[flag_index as usize]; + flags.push(FlagValueSummary { + package_name: String::from(package_name), + flag_name: node.flag_name.clone(), + flag_value: flag_value.to_string(), + value_type: node.flag_type, + }); + } + + flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) { + Ordering::Equal => v1.flag_name.cmp(&v2.flag_name), + other => other, + }); + Ok(flags) +} + +/// Flag value and info summary +#[derive(Debug, PartialEq)] +pub struct FlagValueAndInfoSummary { + pub package_name: String, + pub flag_name: String, + pub flag_value: String, + pub value_type: StoredFlagType, + pub is_readwrite: bool, + pub has_server_override: bool, + pub has_local_override: bool, +} + +/// List flag values and info from storage files +pub fn list_flags_with_info( + package_map: &str, + flag_map: &str, + flag_val: &str, + flag_info: &str, +) -> Result<Vec<FlagValueAndInfoSummary>, 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)?)?; + let flag_info = FlagInfoList::from_bytes(&read_file_to_bytes(flag_info)?)?; 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_offset); + package_info[node.package_id as usize] = (&node.package_name, node.boolean_start_index); } 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 full_flag_name = String::from(package_name) + "/" + &node.flag_name; - let flag_offset = package_offset + node.flag_id as u32; - let flag_value = flag_value_list.booleans[flag_offset as usize]; - flags.push((full_flag_name, flag_value)); + let (package_name, boolean_start_index) = package_info[node.package_id as usize]; + let flag_index = boolean_start_index + node.flag_index as u32; + let flag_value = flag_value_list.booleans[flag_index as usize]; + let flag_attribute = flag_info.nodes[flag_index as usize].attributes; + flags.push(FlagValueAndInfoSummary { + package_name: String::from(package_name), + flag_name: node.flag_name.clone(), + flag_value: flag_value.to_string(), + value_type: node.flag_type, + is_readwrite: flag_attribute & (FlagInfoBit::IsReadWrite as u8) != 0, + has_server_override: flag_attribute & (FlagInfoBit::HasServerOverride as u8) != 0, + has_local_override: flag_attribute & (FlagInfoBit::HasLocalOverride as u8) != 0, + }); } - flags.sort_by(|v1, v2| v1.0.cmp(&v2.0)); + flags.sort_by(|v1, v2| match v1.package_name.cmp(&v2.package_name) { + Ordering::Equal => v1.flag_name.cmp(&v2.flag_name), + other => other, + }); Ok(flags) } +// *************************************** // +// CC INTERLOP +// *************************************** // + +// Exported rust data structure and methods, c++ code will be generated +#[cxx::bridge] +mod ffi { + /// flag value summary cxx return + pub struct FlagValueSummaryCXX { + pub package_name: String, + pub flag_name: String, + pub flag_value: String, + pub value_type: String, + } + + /// flag value and info summary cxx return + pub struct FlagValueAndInfoSummaryCXX { + pub package_name: String, + pub flag_name: String, + pub flag_value: String, + pub value_type: String, + pub is_readwrite: bool, + pub has_server_override: bool, + pub has_local_override: bool, + } + + /// list flag result cxx return + pub struct ListFlagValueResultCXX { + pub query_success: bool, + pub error_message: String, + pub flags: Vec<FlagValueSummaryCXX>, + } + + /// list flag with info result cxx return + pub struct ListFlagValueAndInfoResultCXX { + pub query_success: bool, + pub error_message: String, + pub flags: Vec<FlagValueAndInfoSummaryCXX>, + } + + // Rust export to c++ + extern "Rust" { + pub fn list_flags_cxx( + package_map: &str, + flag_map: &str, + flag_val: &str, + ) -> ListFlagValueResultCXX; + + pub fn list_flags_with_info_cxx( + package_map: &str, + flag_map: &str, + flag_val: &str, + flag_info: &str, + ) -> ListFlagValueAndInfoResultCXX; + } +} + +/// implement flag value summary cxx return type +impl ffi::FlagValueSummaryCXX { + pub(crate) fn new(summary: FlagValueSummary) -> Self { + Self { + package_name: summary.package_name, + flag_name: summary.flag_name, + flag_value: summary.flag_value, + value_type: format!("{:?}", summary.value_type), + } + } +} + +/// implement flag value and info summary cxx return type +impl ffi::FlagValueAndInfoSummaryCXX { + pub(crate) fn new(summary: FlagValueAndInfoSummary) -> Self { + Self { + package_name: summary.package_name, + flag_name: summary.flag_name, + flag_value: summary.flag_value, + value_type: format!("{:?}", summary.value_type), + is_readwrite: summary.is_readwrite, + has_server_override: summary.has_server_override, + has_local_override: summary.has_local_override, + } + } +} + +/// implement list flag cxx interlop +pub fn list_flags_cxx( + package_map: &str, + flag_map: &str, + flag_val: &str, +) -> ffi::ListFlagValueResultCXX { + match list_flags(package_map, flag_map, flag_val) { + Ok(summary) => ffi::ListFlagValueResultCXX { + query_success: true, + error_message: String::new(), + flags: summary.into_iter().map(ffi::FlagValueSummaryCXX::new).collect(), + }, + Err(errmsg) => ffi::ListFlagValueResultCXX { + query_success: false, + error_message: format!("{:?}", errmsg), + flags: Vec::new(), + }, + } +} + +/// implement list flag with info cxx interlop +pub fn list_flags_with_info_cxx( + package_map: &str, + flag_map: &str, + flag_val: &str, + flag_info: &str, +) -> ffi::ListFlagValueAndInfoResultCXX { + match list_flags_with_info(package_map, flag_map, flag_val, flag_info) { + Ok(summary) => ffi::ListFlagValueAndInfoResultCXX { + query_success: true, + error_message: String::new(), + flags: summary.into_iter().map(ffi::FlagValueAndInfoSummaryCXX::new).collect(), + }, + Err(errmsg) => ffi::ListFlagValueAndInfoResultCXX { + query_success: false, + error_message: format!("{:?}", errmsg), + flags: Vec::new(), + }, + } +} + #[cfg(test)] mod tests { use super::*; use crate::test_utils::{ - create_test_flag_table, create_test_flag_value_list, create_test_package_table, - write_bytes_to_temp_file, + create_test_flag_info_list, create_test_flag_table, create_test_flag_value_list, + create_test_package_table, write_bytes_to_temp_file, }; #[test] // 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().as_bytes()).unwrap(); - let flag_table = write_bytes_to_temp_file(&create_test_flag_table().as_bytes()).unwrap(); + 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().as_bytes()).unwrap(); + write_bytes_to_temp_file(&create_test_flag_value_list().into_bytes()).unwrap(); let package_table_path = package_table.path().display().to_string(); let flag_table_path = flag_table.path().display().to_string(); @@ -266,14 +524,154 @@ mod tests { 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/disabled_rw"), false), - (String::from("com.android.aconfig.storage.test_1/enabled_ro"), true), - (String::from("com.android.aconfig.storage.test_1/enabled_rw"), false), - (String::from("com.android.aconfig.storage.test_2/disabled_ro"), false), - (String::from("com.android.aconfig.storage.test_2/enabled_fixed_ro"), true), - (String::from("com.android.aconfig.storage.test_2/enabled_ro"), true), - (String::from("com.android.aconfig.storage.test_4/enabled_fixed_ro"), false), - (String::from("com.android.aconfig.storage.test_4/enabled_ro"), true), + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("disabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("false"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("enabled_ro"), + value_type: StoredFlagType::ReadOnlyBoolean, + flag_value: String::from("true"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("enabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("true"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("disabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("false"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("enabled_fixed_ro"), + value_type: StoredFlagType::FixedReadOnlyBoolean, + flag_value: String::from("true"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("enabled_ro"), + value_type: StoredFlagType::ReadOnlyBoolean, + flag_value: String::from("true"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_4"), + flag_name: String::from("enabled_fixed_ro"), + value_type: StoredFlagType::FixedReadOnlyBoolean, + flag_value: String::from("true"), + }, + FlagValueSummary { + package_name: String::from("com.android.aconfig.storage.test_4"), + flag_name: String::from("enabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("true"), + }, + ]; + assert_eq!(flags, expected); + } + + #[test] + // this test point locks down the flag list with info api + fn test_list_flag_with_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_value_list = + write_bytes_to_temp_file(&create_test_flag_value_list().into_bytes()).unwrap(); + let flag_info_list = + write_bytes_to_temp_file(&create_test_flag_info_list().into_bytes()).unwrap(); + + 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 flag_info_list_path = flag_info_list.path().display().to_string(); + + let flags = list_flags_with_info( + &package_table_path, + &flag_table_path, + &flag_value_list_path, + &flag_info_list_path, + ) + .unwrap(); + let expected = [ + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("disabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("false"), + is_readwrite: true, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("enabled_ro"), + value_type: StoredFlagType::ReadOnlyBoolean, + flag_value: String::from("true"), + is_readwrite: false, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_1"), + flag_name: String::from("enabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("true"), + is_readwrite: true, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("disabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("false"), + is_readwrite: true, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("enabled_fixed_ro"), + value_type: StoredFlagType::FixedReadOnlyBoolean, + flag_value: String::from("true"), + is_readwrite: false, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_2"), + flag_name: String::from("enabled_ro"), + value_type: StoredFlagType::ReadOnlyBoolean, + flag_value: String::from("true"), + is_readwrite: false, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_4"), + flag_name: String::from("enabled_fixed_ro"), + value_type: StoredFlagType::FixedReadOnlyBoolean, + flag_value: String::from("true"), + is_readwrite: false, + has_server_override: false, + has_local_override: false, + }, + FlagValueAndInfoSummary { + package_name: String::from("com.android.aconfig.storage.test_4"), + flag_name: String::from("enabled_rw"), + value_type: StoredFlagType::ReadWriteBoolean, + flag_value: String::from("true"), + is_readwrite: true, + has_server_override: false, + has_local_override: false, + }, ]; assert_eq!(flags, expected); } diff --git a/tools/aconfig/aconfig_storage_file/src/main.rs b/tools/aconfig/aconfig_storage_file/src/main.rs index 293d018c2e..8b9e38da02 100644 --- a/tools/aconfig/aconfig_storage_file/src/main.rs +++ b/tools/aconfig/aconfig_storage_file/src/main.rs @@ -17,8 +17,8 @@ //! `aconfig-storage` is a debugging tool to parse storage files use aconfig_storage_file::{ - list_flags, read_file_to_bytes, AconfigStorageError, FlagTable, FlagValueList, PackageTable, - StorageFileType, + list_flags, list_flags_with_info, read_file_to_bytes, AconfigStorageError, FlagInfoList, + FlagTable, FlagValueList, PackageTable, StorageFileType, }; use clap::{builder::ArgAction, Arg, Command}; @@ -45,7 +45,10 @@ fn cli() -> Command { .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)), + .arg(Arg::new("flag-val").long("flag-val").required(true).action(ArgAction::Set)) + .arg( + Arg::new("flag-info").long("flag-info").required(false).action(ArgAction::Set), + ), ) } @@ -67,6 +70,10 @@ fn print_storage_file( let flag_value = FlagValueList::from_bytes(&bytes)?; println!("{:?}", flag_value); } + StorageFileType::FlagInfo => { + let flag_info = FlagInfoList::from_bytes(&bytes)?; + println!("{:?}", flag_info); + } } Ok(()) } @@ -83,9 +90,27 @@ fn main() -> Result<(), AconfigStorageError> { 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 flag in flags.iter() { - println!("{}: {}", flag.0, flag.1); + let flag_info = sub_matches.get_one::<String>("flag-info"); + match flag_info { + Some(info_file) => { + let flags = list_flags_with_info(package_map, flag_map, flag_val, info_file)?; + for flag in flags.iter() { + println!( + "{} {} {} {:?} IsReadWrite: {}, HasServerOverride: {}, HasLocalOverride: {}", + flag.package_name, flag.flag_name, flag.flag_value, flag.value_type, + flag.is_readwrite, flag.has_server_override, flag.has_local_override, + ); + } + } + None => { + let flags = list_flags(package_map, flag_map, flag_val)?; + for flag in flags.iter() { + println!( + "{} {} {} {:?}", + flag.package_name, flag.flag_name, flag.flag_value, flag.value_type, + ); + } + } } } _ => unreachable!(), diff --git a/tools/aconfig/aconfig_storage_file/src/package_table.rs b/tools/aconfig/aconfig_storage_file/src/package_table.rs index 7cb60eb6c1..b734972f33 100644 --- a/tools/aconfig/aconfig_storage_file/src/package_table.rs +++ b/tools/aconfig/aconfig_storage_file/src/package_table.rs @@ -56,7 +56,7 @@ impl fmt::Debug for PackageTableHeader { 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(); @@ -96,9 +96,9 @@ impl PackageTableHeader { 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>, } @@ -107,8 +107,8 @@ impl fmt::Debug for PackageTableNode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, - "Package: {}, Id: {}, Offset: {}, Next: {:?}", - self.package_name, self.package_id, self.boolean_offset, self.next_offset + "Package: {}, Id: {}, Boolean flag start index: {}, Next: {:?}", + self.package_name, self.package_id, self.boolean_start_index, self.next_offset )?; Ok(()) } @@ -116,13 +116,13 @@ impl fmt::Debug for PackageTableNode { 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 } @@ -133,7 +133,7 @@ impl PackageTableNode { 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), @@ -175,11 +175,11 @@ impl fmt::Debug for PackageTable { 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() } @@ -189,7 +189,7 @@ impl PackageTable { 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, @@ -199,7 +199,7 @@ impl PackageTable { 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>>() @@ -225,17 +225,17 @@ mod tests { 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 package_table_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()); @@ -247,10 +247,10 @@ mod tests { // bytes fn test_version_number() { let package_table = create_test_package_table(); - let bytes = &package_table.as_bytes(); + let bytes = &package_table.into_bytes(); let mut head = 0; let version = read_u32_from_bytes(bytes, &mut head).unwrap(); - assert_eq!(version, 1234) + assert_eq!(version, 1); } #[test] @@ -258,7 +258,7 @@ mod tests { 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.as_bytes()).unwrap_err(); + let error = PackageTable::from_bytes(&package_table.into_bytes()).unwrap_err(); assert_eq!( format!("{:?}", error), format!("BytesParseFail(binary file is not a package map)") diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs index 586bb4c682..106666c47f 100644 --- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs +++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs @@ -14,19 +14,20 @@ * limitations under the License. */ +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}; +use crate::{AconfigStorageError, StorageFileType, StoredFlagType}; use anyhow::anyhow; use std::io::Write; use tempfile::NamedTempFile; -pub(crate) fn create_test_package_table() -> PackageTable { +pub fn create_test_package_table() -> PackageTable { let header = PackageTableHeader { - version: 1234, - container: String::from("system"), + version: 1, + container: String::from("mockup"), file_type: StorageFileType::PackageMap as u8, file_size: 209, num_packages: 3, @@ -37,19 +38,19 @@ pub(crate) fn create_test_package_table() -> PackageTable { let first_node = PackageTableNode { package_name: String::from("com.android.aconfig.storage.test_2"), package_id: 1, - boolean_offset: 3, + 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_offset: 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_offset: 6, + boolean_start_index: 6, next_offset: None, }; let nodes = vec![first_node, second_node, third_node]; @@ -62,17 +63,23 @@ impl FlagTableNode { package_id: u32, flag_name: &str, flag_type: u16, - flag_id: u16, + flag_index: u16, next_offset: Option<u32>, ) -> Self { - Self { package_id, flag_name: flag_name.to_string(), flag_type, flag_id, next_offset } + Self { + package_id, + flag_name: flag_name.to_string(), + flag_type: StoredFlagType::try_from(flag_type).unwrap(), + flag_index, + next_offset, + } } } -pub(crate) fn create_test_flag_table() -> FlagTable { +pub fn create_test_flag_table() -> FlagTable { let header = FlagTableHeader { - version: 1234, - container: String::from("system"), + version: 1, + container: String::from("mockup"), file_type: StorageFileType::FlagMap as u8, file_size: 321, num_flags: 8, @@ -85,8 +92,8 @@ pub(crate) fn create_test_flag_table() -> FlagTable { None, None, None, - Some(178), None, + Some(177), Some(204), None, Some(262), @@ -100,31 +107,45 @@ pub(crate) fn create_test_flag_table() -> FlagTable { ]; let nodes = vec![ FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None), - FlagTableNode::new_expected(0, "enabled_rw", 1, 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", 1, 1, Some(236)), + FlagTableNode::new_expected(0, "enabled_rw", 0, 2, Some(151)), + FlagTableNode::new_expected(2, "enabled_rw", 0, 1, None), + FlagTableNode::new_expected(1, "disabled_rw", 0, 0, 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", 1, 0, None), - FlagTableNode::new_expected(0, "disabled_rw", 1, 0, 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(crate) fn create_test_flag_value_list() -> FlagValueList { +pub fn create_test_flag_value_list() -> FlagValueList { let header = FlagValueHeader { - version: 1234, - container: String::from("system"), + 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, false, false, true, true, false, true]; + let booleans: Vec<bool> = vec![false, true, true, false, true, true, true, true]; FlagValueList { header, booleans } } -pub(crate) fn write_bytes_to_temp_file(bytes: &[u8]) -> Result<NamedTempFile, AconfigStorageError> { +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, true, false, false, false, true]; + 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")) })?; diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_file/tests/Android.bp new file mode 100644 index 0000000000..26b7800877 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/Android.bp @@ -0,0 +1,23 @@ + +cc_test { + name: "aconfig_storage_file.test.cpp", + team: "trendy_team_android_core_experiments", + srcs: [ + "storage_file_test.cpp", + ], + static_libs: [ + "libgmock", + "libaconfig_storage_file_cc", + "libbase", + ], + data: [ + "package.map", + "flag.map", + "flag.val", + "flag.info", + ], + test_suites: [ + "device-tests", + "general-tests", + ], +} diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.info b/tools/aconfig/aconfig_storage_file/tests/flag.info Binary files differnew file mode 100644 index 0000000000..6223edf369 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/flag.info diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_file/tests/flag.map Binary files differnew file mode 100644 index 0000000000..e868f53d7e --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/flag.map diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_file/tests/flag.val Binary files differnew file mode 100644 index 0000000000..ed203d4d13 --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/flag.val diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_file/tests/package.map Binary files differnew file mode 100644 index 0000000000..6c46a0339c --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/package.map diff --git a/tools/aconfig/aconfig_storage_file/tests/storage_file_test.cpp b/tools/aconfig/aconfig_storage_file/tests/storage_file_test.cpp new file mode 100644 index 0000000000..ebd1dd89bd --- /dev/null +++ b/tools/aconfig/aconfig_storage_file/tests/storage_file_test.cpp @@ -0,0 +1,112 @@ +/* + * 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 <android-base/file.h> +#include <android-base/result.h> +#include <gtest/gtest.h> +#include "aconfig_storage/aconfig_storage_file.hpp" + +using namespace android::base; +using namespace aconfig_storage; + +void verify_value(const FlagValueSummary& flag, + const std::string& package_name, + const std::string& flag_name, + const std::string& flag_val, + const std::string& value_type) { + ASSERT_EQ(flag.package_name, package_name); + ASSERT_EQ(flag.flag_name, flag_name); + ASSERT_EQ(flag.flag_value, flag_val); + ASSERT_EQ(flag.value_type, value_type); +} + +void verify_value_info(const FlagValueAndInfoSummary& flag, + const std::string& package_name, + const std::string& flag_name, + const std::string& flag_val, + const std::string& value_type, + bool is_readwrite, + bool has_server_override, + bool has_local_override) { + ASSERT_EQ(flag.package_name, package_name); + ASSERT_EQ(flag.flag_name, flag_name); + ASSERT_EQ(flag.flag_value, flag_val); + ASSERT_EQ(flag.value_type, value_type); + ASSERT_EQ(flag.is_readwrite, is_readwrite); + ASSERT_EQ(flag.has_server_override, has_server_override); + ASSERT_EQ(flag.has_local_override, has_local_override); +} + +TEST(AconfigStorageFileTest, test_list_flag) { + auto const test_dir = GetExecutableDirectory(); + auto const package_map = test_dir + "/package.map"; + auto const flag_map = test_dir + "/flag.map"; + auto const flag_val = test_dir + "/flag.val"; + auto flag_list_result = aconfig_storage::list_flags( + package_map, flag_map, flag_val); + ASSERT_TRUE(flag_list_result.ok()); + + auto const& flag_list = *flag_list_result; + ASSERT_EQ(flag_list.size(), 8); + verify_value(flag_list[0], "com.android.aconfig.storage.test_1", "disabled_rw", + "false", "ReadWriteBoolean"); + verify_value(flag_list[1], "com.android.aconfig.storage.test_1", "enabled_ro", + "true", "ReadOnlyBoolean"); + verify_value(flag_list[2], "com.android.aconfig.storage.test_1", "enabled_rw", + "true", "ReadWriteBoolean"); + verify_value(flag_list[3], "com.android.aconfig.storage.test_2", "disabled_rw", + "false", "ReadWriteBoolean"); + verify_value(flag_list[4], "com.android.aconfig.storage.test_2", "enabled_fixed_ro", + "true", "FixedReadOnlyBoolean"); + verify_value(flag_list[5], "com.android.aconfig.storage.test_2", "enabled_ro", + "true", "ReadOnlyBoolean"); + verify_value(flag_list[6], "com.android.aconfig.storage.test_4", "enabled_fixed_ro", + "true", "FixedReadOnlyBoolean"); + verify_value(flag_list[7], "com.android.aconfig.storage.test_4", "enabled_rw", + "true", "ReadWriteBoolean"); +} + +TEST(AconfigStorageFileTest, test_list_flag_with_info) { + auto const test_dir = GetExecutableDirectory(); + auto const package_map = test_dir + "/package.map"; + auto const flag_map = test_dir + "/flag.map"; + auto const flag_val = test_dir + "/flag.val"; + auto const flag_info = test_dir + "/flag.info"; + auto flag_list_result = aconfig_storage::list_flags_with_info( + package_map, flag_map, flag_val, flag_info); + ASSERT_TRUE(flag_list_result.ok()); + + auto const& flag_list = *flag_list_result; + ASSERT_EQ(flag_list.size(), 8); + verify_value_info(flag_list[0], "com.android.aconfig.storage.test_1", "disabled_rw", + "false", "ReadWriteBoolean", true, false, false); + verify_value_info(flag_list[1], "com.android.aconfig.storage.test_1", "enabled_ro", + "true", "ReadOnlyBoolean", false, false, false); + verify_value_info(flag_list[2], "com.android.aconfig.storage.test_1", "enabled_rw", + "true", "ReadWriteBoolean", true, false, false); + verify_value_info(flag_list[3], "com.android.aconfig.storage.test_2", "disabled_rw", + "false", "ReadWriteBoolean", true, false, false); + verify_value_info(flag_list[4], "com.android.aconfig.storage.test_2", "enabled_fixed_ro", + "true", "FixedReadOnlyBoolean", false, false, false); + verify_value_info(flag_list[5], "com.android.aconfig.storage.test_2", "enabled_ro", + "true", "ReadOnlyBoolean", false, false, false); + verify_value_info(flag_list[6], "com.android.aconfig.storage.test_4", "enabled_fixed_ro", + "true", "FixedReadOnlyBoolean", false, false, false); + verify_value_info(flag_list[7], "com.android.aconfig.storage.test_4", "enabled_rw", + "true", "ReadWriteBoolean", true, false, false); +} diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp index 5006161cbb..db362944c5 100644 --- a/tools/aconfig/aconfig_storage_read_api/Android.bp +++ b/tools/aconfig/aconfig_storage_read_api/Android.bp @@ -23,6 +23,11 @@ rust_library { 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 { @@ -33,6 +38,7 @@ rust_test_host { "tests/package.map", "tests/flag.map", "tests/flag.val", + "tests/flag.info", ], } @@ -59,23 +65,55 @@ rust_ffi_static { name: "libaconfig_storage_read_api_cxx_bridge", crate_name: "aconfig_storage_read_api_cxx_bridge", host_supported: true, + vendor_available: true, + product_available: true, defaults: ["aconfig_storage_read_api.defaults"], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "29", } // flag read api cc interface -cc_library_static { +cc_library { name: "libaconfig_storage_read_api_cc", srcs: ["aconfig_storage_read_api.cpp"], generated_headers: [ "cxx-bridge-header", - "libcxx_aconfig_storage_read_api_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"], + host_supported: true, + vendor_available: true, + product_available: true, static_libs: [ "libaconfig_storage_protos_cc", "libprotobuf-cpp-lite", + ], + shared_libs: [ + "liblog", "libbase", ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "29", + target: { + linux: { + version_script: "libaconfig_storage_read_api_cc.map", + }, + }, + double_loadable: true, +} + +cc_defaults { + name: "aconfig_lib_cc_static_link.defaults", + shared_libs: [ + "libaconfig_storage_read_api_cc", + "liblog", + ], } 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 index ea756b3962..0aa936a9eb 100644 --- a/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp +++ b/tools/aconfig/aconfig_storage_read_api/aconfig_storage_read_api.cpp @@ -20,6 +20,11 @@ namespace aconfig_storage { static constexpr char kAvailableStorageRecordsPb[] = "/metadata/aconfig/boot/available_storage_file_records.pb"; +/// destructor +MappedStorageFile::~MappedStorageFile() { + munmap(file_ptr, file_size); +} + /// 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(); @@ -54,6 +59,8 @@ static Result<std::string> find_storage_file( 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; } @@ -63,49 +70,62 @@ static Result<std::string> find_storage_file( return Error() << "Unable to find storage files for container " << container;; } +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 a storage file -static Result<MappedStorageFile> map_storage_file(std::string const& file) { +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 Error() << "failed to open " << file; + return ErrnoError() << "failed to open " << file; }; struct stat fd_stat; if (fstat(fd, &fd_stat) < 0) { - return Error() << "fstat failed"; + 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 Error() << "mmap failed"; + return ErrnoError() << "mmap failed"; } - auto mapped_file = MappedStorageFile(); - mapped_file.file_ptr = map_result; - mapped_file.file_size = file_size; + auto mapped_file = new 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(); +/// 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"; } - return map_storage_file(*file_result); } -} // namespace private internal api - /// Get mapped storage file -Result<MappedStorageFile> get_mapped_file( +Result<MappedStorageFile*> get_mapped_file( std::string const& container, StorageFileType file_type) { return private_internal_api::get_mapped_file_impl( @@ -124,49 +144,50 @@ Result<uint32_t> get_storage_file_version( } } -/// Get package offset -Result<PackageOffset> get_package_offset( +/// 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 offset_cxx = get_package_offset_cxx(content, rust::Str(package.c_str())); - if (offset_cxx.query_success) { - auto offset = PackageOffset(); - offset.package_exists = offset_cxx.package_exists; - offset.package_id = offset_cxx.package_id; - offset.boolean_offset = offset_cxx.boolean_offset; - return offset; + 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() << offset_cxx.error_message; + return Error() << context_cxx.error_message; } } -/// Get flag offset -Result<FlagOffset> get_flag_offset( +/// 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 offset_cxx = get_flag_offset_cxx(content, package_id, rust::Str(flag_name.c_str())); - if (offset_cxx.query_success) { - auto offset = FlagOffset(); - offset.flag_exists = offset_cxx.flag_exists; - offset.flag_offset = offset_cxx.flag_offset; - return offset; + 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() << offset_cxx.error_message; + return Error() << context_cxx.error_message; } } /// Get boolean flag value Result<bool> get_boolean_flag_value( MappedStorageFile const& file, - uint32_t offset) { + 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, offset); + auto value_cxx = get_boolean_flag_value_cxx(content, index); if (value_cxx.query_success) { return value_cxx.flag_value; } else { @@ -174,4 +195,19 @@ Result<bool> get_boolean_flag_value( } } +/// 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/include/aconfig_storage/aconfig_storage_read_api.hpp b/tools/aconfig/aconfig_storage_read_api/include/aconfig_storage/aconfig_storage_read_api.hpp index aa90f47212..e6d75373a9 100644 --- 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 @@ -6,47 +6,84 @@ namespace aconfig_storage { -/// Storage file type enum +/// 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_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 { + HasServerOverride = 1<<0, + IsReadWrite = 1<<1, + HasLocalOverride = 1<<2, }; /// Mapped storage file struct MappedStorageFile { void* file_ptr; size_t file_size; + virtual ~MappedStorageFile(); }; -/// Package offset query result -struct PackageOffset { +/// Package read context query result +struct PackageReadContext { bool package_exists; uint32_t package_id; - uint32_t boolean_offset; + uint32_t boolean_start_index; }; -/// Flag offset query result -struct FlagOffset { +/// Flag read context query result +struct FlagReadContext { bool flag_exists; - uint16_t flag_offset; + 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( +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 a storage file +android::base::Result<MappedStorageFile*> map_storage_file( + std::string const& file); + + +/// 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( +android::base::Result<MappedStorageFile*> get_mapped_file( std::string const& container, StorageFileType file_type); @@ -56,30 +93,39 @@ android::base::Result<MappedStorageFile> get_mapped_file( android::base::Result<uint32_t> get_storage_file_version( std::string const& file_path); -/// Get package offset +/// Get package read context /// \input file: mapped storage file /// \input package: the flag package name -/// \returns a package offset -android::base::Result<PackageOffset> get_package_offset( +/// \returns a package read context +android::base::Result<PackageReadContext> get_package_read_context( MappedStorageFile const& file, std::string const& package); -/// Get flag offset +/// 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 offset -android::base::Result<FlagOffset> get_flag_offset( +/// \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 offset: the boolean flag value byte offset in the 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 offset); + 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/libaconfig_storage_read_api_cc.map b/tools/aconfig/aconfig_storage_read_api/libaconfig_storage_read_api_cc.map new file mode 100644 index 0000000000..7d47e0ba0e --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/libaconfig_storage_read_api_cc.map @@ -0,0 +1,11 @@ +LIBACONFIG_STORAGE_READ_API_CC { + # Export everything in the aconfig_storage namespace. This includes both the + # public API and library internals. + global: + extern "C++" { + aconfig_storage::*; + }; + # Hide everything else. + local: + *; +}; 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 0000000000..6d03377683 --- /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 has server override + 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::HasServerOverride 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, true, false, false, false, true]; + 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 local 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::HasLocalOverride 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 index 43977ee267..a1a4793bc2 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/flag_table_query.rs @@ -18,18 +18,23 @@ use crate::{AconfigStorageError, FILE_VERSION}; use aconfig_storage_file::{ - flag_table::FlagTableHeader, flag_table::FlagTableNode, read_u32_from_bytes, + flag_table::FlagTableHeader, flag_table::FlagTableNode, read_u32_from_bytes, StoredFlagType, }; use anyhow::anyhow; -pub type FlagOffset = u16; +/// Flag table query return +#[derive(PartialEq, Debug)] +pub struct FlagReadContext { + pub flag_type: StoredFlagType, + pub flag_index: u16, +} -/// Query flag within package offset -pub fn find_flag_offset( +/// 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<FlagOffset>, AconfigStorageError> { +) -> Result<Option<FlagReadContext>, AconfigStorageError> { let interpreted_header = FlagTableHeader::from_bytes(buf)?; if interpreted_header.version > crate::FILE_VERSION { return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!( @@ -53,7 +58,10 @@ pub fn find_flag_offset( 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)); + 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, @@ -65,96 +73,38 @@ pub fn find_flag_offset( #[cfg(test)] mod tests { use super::*; - use aconfig_storage_file::{FlagTable, StorageFileType}; - - // 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, - } - } - - pub fn create_test_flag_table() -> FlagTable { - let header = FlagTableHeader { - version: crate::FILE_VERSION, - container: String::from("system"), - 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![ - new_expected_node(0, "enabled_ro", 1, 1, None), - new_expected_node(0, "enabled_rw", 1, 2, Some(151)), - new_expected_node(1, "disabled_ro", 1, 0, None), - new_expected_node(2, "enabled_ro", 1, 1, None), - new_expected_node(1, "enabled_fixed_ro", 1, 1, Some(236)), - new_expected_node(1, "enabled_ro", 1, 2, None), - new_expected_node(2, "enabled_fixed_ro", 1, 0, None), - new_expected_node(0, "disabled_rw", 1, 0, None), - ]; - FlagTable { header, buckets, nodes } - } + 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().as_bytes(); + let flag_table = create_test_flag_table().into_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), + (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), + (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16), + (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16), + (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, 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); + 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().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); + 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] @@ -162,8 +112,8 @@ mod tests { 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(); + let flag_table = table.into_bytes(); + let error = find_flag_read_context(&flag_table[..], 0, "enabled_ro").unwrap_err(); assert_eq!( format!("{:?}", error), format!( 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 index 88d2397545..9d32a16ac8 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/flag_value_query.rs @@ -21,7 +21,7 @@ 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_offset: u32) -> Result<bool, AconfigStorageError> { +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!( @@ -31,10 +31,8 @@ pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, Aco ))); } - 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). + // 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." @@ -48,26 +46,13 @@ pub fn find_boolean_flag_value(buf: &[u8], flag_offset: u32) -> Result<bool, Aco #[cfg(test)] mod tests { use super::*; - use aconfig_storage_file::{FlagValueList, StorageFileType}; - - pub fn create_test_flag_value_list() -> FlagValueList { - let header = FlagValueHeader { - version: crate::FILE_VERSION, - container: String::from("system"), - file_type: StorageFileType::FlagVal as u8, - file_size: 35, - num_flags: 8, - boolean_value_offset: 27, - }; - let booleans: Vec<bool> = vec![false, true, false, false, true, true, false, true]; - FlagValueList { header, booleans } - } + 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().as_bytes(); - let baseline: Vec<bool> = vec![false, true, false, false, true, true, false, true]; + 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); @@ -77,7 +62,7 @@ mod tests { #[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 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), @@ -90,7 +75,7 @@ mod tests { 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 flag_value = value_list.into_bytes(); let error = find_boolean_flag_value(&flag_value[..], 4).unwrap_err(); assert_eq!( format!("{:?}", error), diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs index 8a71480134..e4192066d5 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs @@ -17,14 +17,16 @@ //! `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 flag value start offset -//! pub fn get_package_offset(container: &str, package: &str) -> `Result<Option<PackageOffset>>>` +//! 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 offset within a specific package -//! pub fn get_flag_offset(container: &str, package_id: u32, flag: &str) -> `Result<Option<u16>>>` +//! 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 offset (combined package and -//! flag offset). +//! 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. @@ -34,6 +36,7 @@ //! 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; @@ -42,14 +45,15 @@ pub mod package_table_query; #[cfg(test)] mod test_utils; -pub use aconfig_storage_file::{AconfigStorageError, StorageFileType}; -pub use flag_table_query::FlagOffset; -pub use package_table_query::PackageOffset; +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_table_query::find_flag_offset; +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_offset; +use package_table_query::find_package_read_context; use anyhow::anyhow; use memmap2::Mmap; @@ -77,50 +81,50 @@ pub unsafe fn get_mapped_storage_file( unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container, file_type) } } -/// Get package start offset for flags. +/// 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(PackageOffset)) +/// 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_offset( +pub fn get_package_read_context( file: &Mmap, package: &str, -) -> Result<Option<PackageOffset>, AconfigStorageError> { - find_package_offset(file, package) +) -> Result<Option<PackageReadContext>, AconfigStorageError> { + find_package_read_context(file, package) } -/// Get flag offset within a package given. +/// 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(u16)) +/// 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_offset( +pub fn get_flag_read_context( file: &Mmap, package_id: u32, flag: &str, -) -> Result<Option<FlagOffset>, AconfigStorageError> { - find_flag_offset(file, package_id, flag) +) -> Result<Option<FlagReadContext>, AconfigStorageError> { + find_flag_read_context(file, package_id, flag) } /// Get the boolean flag value. /// /// \input file: mapped flag file -/// \input offset: flag value offset +/// \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, offset: u32) -> Result<bool, AconfigStorageError> { - find_boolean_flag_value(file, offset) +pub fn get_boolean_flag_value(file: &Mmap, index: u32) -> Result<bool, AconfigStorageError> { + find_boolean_flag_value(file, index) } /// Get storage file version number @@ -145,6 +149,23 @@ pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageEr 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 // *************************************** // @@ -160,20 +181,21 @@ mod ffi { } // Package table query return for cc interlop - pub struct PackageOffsetQueryCXX { + pub struct PackageReadContextQueryCXX { pub query_success: bool, pub error_message: String, pub package_exists: bool, pub package_id: u32, - pub boolean_offset: u32, + pub boolean_start_index: u32, } // Flag table query return for cc interlop - pub struct FlagOffsetQueryCXX { + pub struct FlagReadContextQueryCXX { pub query_success: bool, pub error_message: String, pub flag_exists: bool, - pub flag_offset: u16, + pub flag_type: u16, + pub flag_index: u16, } // Flag value query return for cc interlop @@ -183,21 +205,43 @@ mod ffi { 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_offset_cxx(file: &[u8], package: &str) -> PackageOffsetQueryCXX; + pub fn get_package_read_context_cxx( + file: &[u8], + package: &str, + ) -> PackageReadContextQueryCXX; - pub fn get_flag_offset_cxx(file: &[u8], package_id: u32, flag: &str) -> FlagOffsetQueryCXX; + 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::PackageOffsetQueryCXX { - pub(crate) fn new(offset_result: Result<Option<PackageOffset>, AconfigStorageError>) -> Self { +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 { @@ -205,14 +249,14 @@ impl ffi::PackageOffsetQueryCXX { error_message: String::from(""), package_exists: true, package_id: offset.package_id, - boolean_offset: offset.boolean_offset, + boolean_start_index: offset.boolean_start_index, }, None => Self { query_success: true, error_message: String::from(""), package_exists: false, package_id: 0, - boolean_offset: 0, + boolean_start_index: 0, }, }, Err(errmsg) => Self { @@ -220,35 +264,38 @@ impl ffi::PackageOffsetQueryCXX { error_message: format!("{:?}", errmsg), package_exists: false, package_id: 0, - boolean_offset: 0, + boolean_start_index: 0, }, } } } /// Implement the flag offset interlop return type, create from actual flag offset api return type -impl ffi::FlagOffsetQueryCXX { - pub(crate) fn new(offset_result: Result<Option<FlagOffset>, AconfigStorageError>) -> Self { +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_offset: offset, + 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_offset: 0, + flag_type: 0u16, + flag_index: 0u16, }, }, Err(errmsg) => Self { query_success: false, error_message: format!("{:?}", errmsg), flag_exists: false, - flag_offset: 0, + flag_type: 0u16, + flag_index: 0u16, }, } } @@ -270,6 +317,22 @@ impl ffi::BooleanFlagValueQueryCXX { } } +/// 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 { @@ -289,14 +352,18 @@ impl ffi::VersionNumberQueryCXX { } } -/// Get package start offset cc interlop -pub fn get_package_offset_cxx(file: &[u8], package: &str) -> ffi::PackageOffsetQueryCXX { - ffi::PackageOffsetQueryCXX::new(find_package_offset(file, package)) +/// 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 start offset cc interlop -pub fn get_flag_offset_cxx(file: &[u8], package_id: u32, flag: &str) -> ffi::FlagOffsetQueryCXX { - ffi::FlagOffsetQueryCXX::new(find_flag_offset(file, package_id, flag)) +/// 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 @@ -304,6 +371,20 @@ pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagV 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)) @@ -315,96 +396,101 @@ mod tests { 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; 4] { + 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: "system" + 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_val.path().display(), + flag_info.path().display() ); let pb_file = write_proto_to_temp_file(&text_proto).unwrap(); - [package_map, flag_map, flag_val, pb_file] + [package_map, flag_map, flag_val, flag_info, pb_file] } #[test] - // this test point locks down flag package offset query - fn test_package_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + // 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, "system", StorageFileType::PackageMap).unwrap() + get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap() }; - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_1") + let package_context = + get_package_read_context(&package_mapped_file, "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 expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 }; + assert_eq!(package_context, expected_package_context); - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_2") + let package_context = + get_package_read_context(&package_mapped_file, "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 expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 }; + assert_eq!(package_context, expected_package_context); - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_4") + let package_context = + get_package_read_context(&package_mapped_file, "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 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 offset query - fn test_flag_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + // 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, "system", StorageFileType::FlagMap).unwrap() }; + unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagMap).unwrap() }; 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), + (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), + (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16), + (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16), + (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, expected_offset) in baseline.into_iter() { - let flag_offset = - get_flag_offset(&flag_mapped_file, package_id, flag_name).unwrap().unwrap(); - assert_eq!(flag_offset, expected_offset); + 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 offset query + // this test point locks down flag value query fn test_flag_value_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::FlagVal).unwrap() }; - let baseline: Vec<bool> = vec![false; 8]; + 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); @@ -412,10 +498,28 @@ files {{ } #[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, true, false, false, false, true]; + 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_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value); + assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8); + assert!((attribute & FlagInfoBit::HasLocalOverride 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 index 86c6a1b053..378644317c 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/mapped_file.rs @@ -29,7 +29,7 @@ use aconfig_storage_file::protos::{ }; /// Find where storage files are stored for a particular container -fn find_container_storage_location( +pub fn find_container_storage_location( location_pb_file: &str, container: &str, ) -> Result<ProtoStorageFileInfo, AconfigStorageError> { @@ -91,6 +91,7 @@ pub unsafe fn get_mapped_file( 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()) }, } } 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 index 3587e10dd6..2cb854b1b1 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/package_table_query.rs @@ -24,16 +24,16 @@ use anyhow::anyhow; /// Package table query return #[derive(PartialEq, Debug)] -pub struct PackageOffset { +pub struct PackageReadContext { pub package_id: u32, - pub boolean_offset: u32, + pub boolean_start_index: u32, } -/// Query package id and start offset -pub fn find_package_offset( +/// Query package read context: package id and start index +pub fn find_package_read_context( buf: &[u8], package: &str, -) -> Result<Option<PackageOffset>, AconfigStorageError> { +) -> Result<Option<PackageReadContext>, AconfigStorageError> { let interpreted_header = PackageTableHeader::from_bytes(buf)?; if interpreted_header.version > FILE_VERSION { return Err(AconfigStorageError::HigherStorageFileVersion(anyhow!( @@ -57,9 +57,9 @@ pub fn find_package_offset( loop { let interpreted_node = PackageTableNode::from_bytes(&buf[package_node_offset..])?; if interpreted_node.package_name == package { - return Ok(Some(PackageOffset { + return Ok(Some(PackageReadContext { package_id: interpreted_node.package_id, - boolean_offset: interpreted_node.boolean_offset, + boolean_start_index: interpreted_node.boolean_start_index, })); } match interpreted_node.next_offset { @@ -72,77 +72,46 @@ pub fn find_package_offset( #[cfg(test)] mod tests { use super::*; - use aconfig_storage_file::{PackageTable, StorageFileType}; - - pub fn create_test_package_table() -> PackageTable { - let header = PackageTableHeader { - version: crate::FILE_VERSION, - container: String::from("system"), - 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_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(159), - }; - 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 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().as_bytes(); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_1") + 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_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") + 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_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") + 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_offset = PackageOffset { package_id: 2, boolean_offset: 6 }; - assert_eq!(package_offset, expected_package_offset); + 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().as_bytes(); - let package_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_3").unwrap(); - assert_eq!(package_offset, None); + 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_offset = - find_package_offset(&package_table[..], "com.android.aconfig.storage.test_5").unwrap(); - assert_eq!(package_offset, None); + let package_context = + find_package_read_context(&package_table[..], "com.android.aconfig.storage.test_5") + .unwrap(); + assert_eq!(package_context, None); } #[test] @@ -150,9 +119,10 @@ mod tests { 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(); + 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!( diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp index d9cf238ff7..6b05ca6fb1 100644 --- a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp +++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp @@ -14,6 +14,7 @@ rust_test { "package.map", "flag.map", "flag.val", + "flag.info", ], test_suites: ["general-tests"], } @@ -35,6 +36,7 @@ cc_test { "package.map", "flag.map", "flag.val", + "flag.info", ], test_suites: [ "device-tests", diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.info b/tools/aconfig/aconfig_storage_read_api/tests/flag.info Binary files differnew file mode 100644 index 0000000000..6223edf369 --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.info diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map Binary files differindex 5507894d70..e868f53d7e 100644 --- a/tools/aconfig/aconfig_storage_read_api/tests/flag.map +++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.val b/tools/aconfig/aconfig_storage_read_api/tests/flag.val Binary files differindex 75b8564de3..ed203d4d13 100644 --- a/tools/aconfig/aconfig_storage_read_api/tests/flag.val +++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.val diff --git a/tools/aconfig/aconfig_storage_read_api/tests/package.map b/tools/aconfig/aconfig_storage_read_api/tests/package.map Binary files differindex 02267e550d..6c46a0339c 100644 --- a/tools/aconfig/aconfig_storage_read_api/tests/package.map +++ b/tools/aconfig/aconfig_storage_read_api/tests/package.map 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 index 1d36aae8cb..5393c49a7d 100644 --- 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 @@ -16,6 +16,7 @@ #include <string> #include <vector> +#include <memory> #include <cstdio> #include <sys/stat.h> @@ -47,15 +48,17 @@ class AconfigStorageTest : public ::testing::Test { 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_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("system"); + 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(); @@ -71,20 +74,23 @@ class AconfigStorageTest : public ::testing::Test { 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); + 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; }; @@ -99,117 +105,161 @@ TEST_F(AconfigStorageTest, test_storage_version_query) { 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( + auto mapped_file_result = 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(), + ASSERT_FALSE(mapped_file_result.ok()); + ASSERT_EQ(mapped_file_result.error().message(), "Unable to find storage files for container vendor"); } -/// Test to lock down storage package offset query api -TEST_F(AconfigStorageTest, test_package_offset_query) { - auto mapped_file = private_api::get_mapped_file_impl( - storage_record_pb, "system", api::StorageFileType::package_map); - ASSERT_TRUE(mapped_file.ok()); +/// Test to lock down storage package context query api +TEST_F(AconfigStorageTest, test_package_context_query) { + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::package_map); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); - auto offset = api::get_package_offset( + auto context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_1"); - ASSERT_TRUE(offset.ok()); - ASSERT_TRUE(offset->package_exists); - ASSERT_EQ(offset->package_id, 0); - ASSERT_EQ(offset->boolean_offset, 0); + ASSERT_TRUE(context.ok()); + ASSERT_TRUE(context->package_exists); + ASSERT_EQ(context->package_id, 0); + ASSERT_EQ(context->boolean_start_index, 0); - offset = api::get_package_offset( + context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_2"); - ASSERT_TRUE(offset.ok()); - ASSERT_TRUE(offset->package_exists); - ASSERT_EQ(offset->package_id, 1); - ASSERT_EQ(offset->boolean_offset, 3); + ASSERT_TRUE(context.ok()); + ASSERT_TRUE(context->package_exists); + ASSERT_EQ(context->package_id, 1); + ASSERT_EQ(context->boolean_start_index, 3); - offset = api::get_package_offset( + context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_4"); - ASSERT_TRUE(offset.ok()); - ASSERT_TRUE(offset->package_exists); - ASSERT_EQ(offset->package_id, 2); - ASSERT_EQ(offset->boolean_offset, 6); + 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_offset_query) { - auto mapped_file = private_api::get_mapped_file_impl( - storage_record_pb, "system", api::StorageFileType::package_map); - ASSERT_TRUE(mapped_file.ok()); +TEST_F(AconfigStorageTest, test_none_existent_package_context_query) { + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::package_map); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); - auto offset = api::get_package_offset( + auto context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_3"); - ASSERT_TRUE(offset.ok()); - ASSERT_FALSE(offset->package_exists); + ASSERT_TRUE(context.ok()); + ASSERT_FALSE(context->package_exists); } -/// Test to lock down storage flag offset query api -TEST_F(AconfigStorageTest, test_flag_offset_query) { - auto mapped_file = private_api::get_mapped_file_impl( - storage_record_pb, "system", api::StorageFileType::flag_map); - ASSERT_TRUE(mapped_file.ok()); - - 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}, +/// Test to lock down storage flag context query api +TEST_F(AconfigStorageTest, test_flag_context_query) { + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_map); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); + + 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}, + {2, "enabled_rw", api::StoredFlagType::ReadWriteBoolean, 1}, + {1, "disabled_rw", api::StoredFlagType::ReadWriteBoolean, 0}, + {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, expected_offset] : baseline) { - auto offset = api::get_flag_offset(*mapped_file, package_id, flag_name); - ASSERT_TRUE(offset.ok()); - ASSERT_TRUE(offset->flag_exists); - ASSERT_EQ(offset->flag_offset, expected_offset); + 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_offset_query) { - auto mapped_file = private_api::get_mapped_file_impl( - storage_record_pb, "system", api::StorageFileType::flag_map); - ASSERT_TRUE(mapped_file.ok()); - - auto offset = api::get_flag_offset(*mapped_file, 0, "none_exist"); - ASSERT_TRUE(offset.ok()); - ASSERT_FALSE(offset->flag_exists); - - offset = api::get_flag_offset(*mapped_file, 3, "enabled_ro"); - ASSERT_TRUE(offset.ok()); - ASSERT_FALSE(offset->flag_exists); +TEST_F(AconfigStorageTest, test_none_existent_flag_context_query) { + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_map); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); + + 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, "system", api::StorageFileType::flag_val); - ASSERT_TRUE(mapped_file.ok()); + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_val); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); - for (int offset = 0; offset < 8; ++offset) { - auto value = api::get_boolean_flag_value(*mapped_file, offset); + 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_FALSE(*value); + 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, "system", api::StorageFileType::flag_val); - ASSERT_TRUE(mapped_file.ok()); + auto mapped_file_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_val); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); 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_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_info); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); + + auto expected_value = std::vector<bool>{ + true, false, true, true, false, false, false, true}; + 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::HasServerOverride), 0); + ASSERT_EQ((*attribute & static_cast<uint8_t>(api::FlagInfoBit::IsReadWrite)) != 0, + expected_value[index]); + ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::HasLocalOverride), 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_result = private_api::get_mapped_file_impl( + storage_record_pb, "mockup", api::StorageFileType::flag_info); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); + + 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 index afcd5a7f3c..ecba573d82 100644 --- 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 @@ -1,10 +1,11 @@ #[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::StorageFileType; + use aconfig_storage_file::{FlagInfoBit, FlagValueType, StorageFileType, StoredFlagType}; use aconfig_storage_read_api::{ - get_boolean_flag_value, get_flag_offset, get_package_offset, get_storage_file_version, - mapped_file::get_mapped_file, PackageOffset, + 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; @@ -15,33 +16,36 @@ mod aconfig_storage_rust_test { file } - fn create_test_storage_files() -> [NamedTempFile; 4] { + 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: "system" + 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_val.path().display(), + flag_info.path().display() ); let pb_file = write_proto_to_temp_file(&text_proto).unwrap(); - [package_map, flag_map, flag_val, pb_file] + [package_map, flag_map, flag_val, flag_info, pb_file] } #[test] fn test_unavailable_stoarge() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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 @@ -55,102 +59,106 @@ files {{ } #[test] - fn test_package_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::PackageMap).unwrap() + get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap() }; - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_1") + let package_context = + get_package_read_context(&package_mapped_file, "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 expected_package_context = PackageReadContext { package_id: 0, boolean_start_index: 0 }; + assert_eq!(package_context, expected_package_context); - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_2") + let package_context = + get_package_read_context(&package_mapped_file, "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 expected_package_context = PackageReadContext { package_id: 1, boolean_start_index: 3 }; + assert_eq!(package_context, expected_package_context); - let package_offset = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_4") + let package_context = + get_package_read_context(&package_mapped_file, "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 expected_package_context = PackageReadContext { package_id: 2, boolean_start_index: 6 }; + assert_eq!(package_context, expected_package_context); } #[test] - fn test_none_exist_package_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::PackageMap).unwrap() + get_mapped_file(&pb_file_path, "mockup", StorageFileType::PackageMap).unwrap() }; - let package_offset_option = - get_package_offset(&package_mapped_file, "com.android.aconfig.storage.test_3").unwrap(); - assert_eq!(package_offset_option, None); + 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_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::FlagMap).unwrap() }; + unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagMap).unwrap() }; 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), + (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), + (2, "enabled_rw", StoredFlagType::ReadWriteBoolean, 1u16), + (1, "disabled_rw", StoredFlagType::ReadWriteBoolean, 0u16), + (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, expected_offset) in baseline.into_iter() { - let flag_offset = - get_flag_offset(&flag_mapped_file, package_id, flag_name).unwrap().unwrap(); - assert_eq!(flag_offset, expected_offset); + 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_offset_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::FlagMap).unwrap() }; - let flag_offset_option = get_flag_offset(&flag_mapped_file, 0, "none_exist").unwrap(); - assert_eq!(flag_offset_option, None); - - let flag_offset_option = get_flag_offset(&flag_mapped_file, 3, "enabled_ro").unwrap(); - assert_eq!(flag_offset_option, None); + 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, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::FlagVal).unwrap() }; - let baseline: Vec<bool> = vec![false; 8]; + 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); @@ -159,12 +167,12 @@ files {{ #[test] fn test_invalid_boolean_flag_value_query() { - let [_package_map, _flag_map, _flag_val, pb_file] = create_test_storage_files(); + 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, "system", StorageFileType::FlagVal).unwrap() }; + 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), @@ -173,9 +181,43 @@ files {{ } #[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, true, false, false, false, true]; + 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::HasServerOverride as u8) == 0u8); + assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value); + assert!((attribute & FlagInfoBit::HasLocalOverride 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 index 0f15b9c762..4dbdbbfb2f 100644 --- a/tools/aconfig/aconfig_storage_write_api/Android.bp +++ b/tools/aconfig/aconfig_storage_write_api/Android.bp @@ -14,6 +14,7 @@ rust_defaults { "libcxx", "libthiserror", "libaconfig_storage_file", + "libaconfig_storage_read_api", ], } @@ -30,6 +31,7 @@ rust_test_host { defaults: ["aconfig_storage_write_api.defaults"], data: [ "tests/flag.val", + "tests/flag.info", ], rustlibs: [ "libaconfig_storage_read_api", @@ -68,12 +70,13 @@ cc_library_static { srcs: ["aconfig_storage_write_api.cpp"], generated_headers: [ "cxx-bridge-header", - "libcxx_aconfig_storage_write_api_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/aconfig_storage_write_api.cpp b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp index 391b3050ca..f529f7954c 100644 --- a/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp +++ b/tools/aconfig/aconfig_storage_write_api/aconfig_storage_write_api.cpp @@ -17,48 +17,11 @@ 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) { - 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) { - return entry.flag_val(); - } - } - - return Error() << "Unable to find storage files for container " << container;; -} - /// Map a storage file -static Result<MappedFlagValueFile> map_storage_file(std::string const& file) { +Result<MutableMappedStorageFile*> map_mutable_storage_file(std::string const& file) { struct stat file_stat; if (stat(file.c_str(), &file_stat) < 0) { - return Error() << "fstat failed"; + return ErrnoError() << "stat failed"; } if ((file_stat.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) { @@ -69,56 +32,80 @@ static Result<MappedFlagValueFile> map_storage_file(std::string const& file) { const int fd = open(file.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC); if (fd == -1) { - return Error() << "failed to open " << file; + 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 Error() << "mmap failed"; + return ErrnoError() << "mmap failed"; } - auto mapped_file = MappedFlagValueFile(); - mapped_file.file_ptr = map_result; - mapped_file.file_size = file_size; + auto mapped_file = new MutableMappedStorageFile(); + mapped_file->file_ptr = map_result; + mapped_file->file_size = file_size; return mapped_file; } -namespace private_internal_api { - -/// Get mapped file implementation. -Result<MappedFlagValueFile> get_mapped_flag_value_file_impl( - std::string const& pb_file, - std::string const& container) { - auto file_result = find_storage_file(pb_file, container); - if (!file_result.ok()) { - return Error() << file_result.error(); +/// 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 map_storage_file(*file_result); + return {}; } -} // namespace private internal api - -/// Get mapped writeable flag value file -Result<MappedFlagValueFile> get_mapped_flag_value_file( - std::string const& container) { - return private_internal_api::get_mapped_flag_value_file_impl( - kPersistStorageRecordsPb, container); +/// Set if flag has server override +Result<void> set_flag_has_server_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_server_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 {}; } -/// Set boolean flag value -Result<void> set_boolean_flag_value( - const MappedFlagValueFile& file, +/// Set if flag has local override +Result<void> set_flag_has_local_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_boolean_flag_value_cxx(content, offset, value); + auto update_cxx = update_flag_has_local_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/include/aconfig_storage/aconfig_storage_write_api.hpp b/tools/aconfig/aconfig_storage_write_api/include/aconfig_storage/aconfig_storage_write_api.hpp index 9e6332ac27..ff06cbc6de 100644 --- 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 @@ -4,34 +4,46 @@ #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 MappedFlagValueFile{ - void* file_ptr; - size_t file_size; -}; +struct MutableMappedStorageFile : MappedStorageFile {}; -/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY -namespace private_internal_api { - -Result<MappedFlagValueFile> get_mapped_flag_value_file_impl( - std::string const& pb_file, - std::string const& container); - -} // namespace private_internal_api - -/// Get mapped writeable flag value file -Result<MappedFlagValueFile> get_mapped_flag_value_file( - std::string const& container); +/// Map a storage file +Result<MutableMappedStorageFile*> map_mutable_storage_file( + std::string const& file); /// Set boolean flag value Result<void> set_boolean_flag_value( - const MappedFlagValueFile& file, + const MutableMappedStorageFile& file, + uint32_t offset, + bool value); + +/// Set if flag has server override +Result<void> set_flag_has_server_override( + const MutableMappedStorageFile& file, + FlagValueType value_type, uint32_t offset, bool value); +/// Set if flag has local override +Result<void> set_flag_has_local_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 0000000000..6f03f128a5 --- /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 has server override +pub fn update_flag_has_server_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::HasServerOverride as u8)) != 0; + if has_override != value { + buf[head] = (attribute ^ FlagInfoBit::HasServerOverride as u8).to_le_bytes()[0]; + } + Ok(()) +} + +/// Set if flag has local override +pub fn update_flag_has_local_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::HasLocalOverride as u8)) != 0; + if has_override != value { + buf[head] = (attribute ^ FlagInfoBit::HasLocalOverride 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 has server override update + fn test_update_flag_has_server_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_server_override(&mut buf, FlagValueType::Boolean, i, true).unwrap(); + let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap(); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) != 0); + update_flag_has_server_override(&mut buf, FlagValueType::Boolean, i, false).unwrap(); + let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap(); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) == 0); + } + } + + #[test] + // this test point locks down has local override update + fn test_update_flag_has_local_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_local_override(&mut buf, FlagValueType::Boolean, i, true).unwrap(); + let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap(); + assert!((attribute & (FlagInfoBit::HasLocalOverride as u8)) != 0); + update_flag_has_local_override(&mut buf, FlagValueType::Boolean, i, false).unwrap(); + let attribute = find_flag_attribute(&buf, FlagValueType::Boolean, i).unwrap(); + assert!((attribute & (FlagInfoBit::HasLocalOverride 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 index c2375dd4e4..0938715714 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/flag_value_update.rs @@ -22,7 +22,7 @@ use anyhow::anyhow; /// Set flag value pub fn update_boolean_flag_value( buf: &mut [u8], - flag_offset: u32, + flag_index: u32, flag_value: bool, ) -> Result<(), AconfigStorageError> { let interpreted_header = FlagValueHeader::from_bytes(buf)?; @@ -34,10 +34,8 @@ pub fn update_boolean_flag_value( ))); } - let 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). + // 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." @@ -51,27 +49,14 @@ pub fn update_boolean_flag_value( #[cfg(test)] mod tests { use super::*; - use aconfig_storage_file::{FlagValueList, StorageFileType}; - - pub fn create_test_flag_value_list() -> FlagValueList { - let header = FlagValueHeader { - version: FILE_VERSION, - container: String::from("system"), - file_type: StorageFileType::FlagVal as u8, - file_size: 35, - num_flags: 8, - boolean_value_offset: 27, - }; - let booleans: Vec<bool> = vec![false; 8]; - FlagValueList { header, booleans } - } + 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.as_bytes(); + 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]; @@ -87,7 +72,7 @@ mod tests { #[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().as_bytes(); + 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), @@ -100,7 +85,7 @@ mod tests { 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.as_bytes(); + 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), diff --git a/tools/aconfig/aconfig_storage_write_api/src/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs index 5562d6a126..aec28def68 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/lib.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs @@ -17,25 +17,26 @@ //! `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; +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 mmaped flag value file given the container name -/// -/// \input container: the flag package container -/// \return a result of mapped file +/// Get read write mapped storage files. /// +/// \input file_path: path to the storage file /// /// # Safety /// @@ -43,28 +44,146 @@ pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/persistent_storage_fi /// 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_flag_value_file(container: &str) -> Result<MmapMut, AconfigStorageError> { - unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container) } +pub unsafe fn map_mutable_storage_file(file_path: &str) -> Result<MmapMut, AconfigStorageError> { + crate::mapped_file::map_file(file_path) } /// Set boolean flag value thru mapped file and flush the change to file /// /// \input mapped_file: the mapped flag value file -/// \input offset: flag value offset +/// \input index: flag index /// \input value: updated flag value /// \return a result of () /// pub fn set_boolean_flag_value( file: &mut MmapMut, - offset: u32, + 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 has server 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 server override value +/// \return a result of () +/// +pub fn set_flag_has_server_override( + file: &mut MmapMut, + flag_type: FlagValueType, + index: u32, value: bool, ) -> Result<(), AconfigStorageError> { - crate::flag_value_update::update_boolean_flag_value(file, offset, value)?; + crate::flag_info_update::update_flag_has_server_override(file, flag_type, index, value)?; file.flush().map_err(|errmsg| { AconfigStorageError::MapFlushFail(anyhow!("fail to flush storage file: {}", errmsg)) }) } +/// Set if flag has local 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 local override value +/// \return a result of () +/// +pub fn set_flag_has_local_override( + file: &mut MmapMut, + flag_type: FlagValueType, + index: u32, + value: bool, +) -> Result<(), AconfigStorageError> { + crate::flag_info_update::update_flag_has_local_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 // *************************************** // @@ -78,6 +197,24 @@ mod ffi { pub error_message: String, } + // Flag has server override update return for cc interlop + pub struct FlagHasServerOverrideUpdateCXX { + pub update_success: bool, + pub error_message: String, + } + + // Flag has local override update return for cc interlop + pub struct FlagHasLocalOverrideUpdateCXX { + 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( @@ -85,6 +222,26 @@ mod ffi { offset: u32, value: bool, ) -> BooleanFlagValueUpdateCXX; + + pub fn update_flag_has_server_override_cxx( + file: &mut [u8], + flag_type: u16, + offset: u32, + value: bool, + ) -> FlagHasServerOverrideUpdateCXX; + + pub fn update_flag_has_local_override_cxx( + file: &mut [u8], + flag_type: u16, + offset: u32, + value: bool, + ) -> FlagHasLocalOverrideUpdateCXX; + + pub fn create_flag_info_cxx( + package_map: &str, + flag_map: &str, + flag_info_out: &str, + ) -> FlagInfoCreationCXX; } } @@ -104,14 +261,90 @@ pub(crate) fn update_boolean_flag_value_cxx( } } +pub(crate) fn update_flag_has_server_override_cxx( + file: &mut [u8], + flag_type: u16, + offset: u32, + value: bool, +) -> ffi::FlagHasServerOverrideUpdateCXX { + match FlagValueType::try_from(flag_type) { + Ok(value_type) => { + match crate::flag_info_update::update_flag_has_server_override( + file, value_type, offset, value, + ) { + Ok(()) => ffi::FlagHasServerOverrideUpdateCXX { + update_success: true, + error_message: String::from(""), + }, + Err(errmsg) => ffi::FlagHasServerOverrideUpdateCXX { + update_success: false, + error_message: format!("{:?}", errmsg), + }, + } + } + Err(errmsg) => ffi::FlagHasServerOverrideUpdateCXX { + update_success: false, + error_message: format!("{:?}", errmsg), + }, + } +} + +pub(crate) fn update_flag_has_local_override_cxx( + file: &mut [u8], + flag_type: u16, + offset: u32, + value: bool, +) -> ffi::FlagHasLocalOverrideUpdateCXX { + match FlagValueType::try_from(flag_type) { + Ok(value_type) => { + match crate::flag_info_update::update_flag_has_local_override( + file, value_type, offset, value, + ) { + Ok(()) => ffi::FlagHasLocalOverrideUpdateCXX { + update_success: true, + error_message: String::from(""), + }, + Err(errmsg) => ffi::FlagHasLocalOverrideUpdateCXX { + update_success: false, + error_message: format!("{:?}", errmsg), + }, + } + } + Err(errmsg) => ffi::FlagHasLocalOverrideUpdateCXX { + 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(); @@ -124,27 +357,12 @@ mod tests { 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: "{}" - 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").unwrap(); + let mut file = map_mutable_storage_file(&flag_value_path).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); @@ -156,4 +374,84 @@ files {{ } } } + + 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_has_server_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(); + + // SAFETY: + // The safety here is guaranteed as only this single threaded test process will + // write to this file + unsafe { + let mut file = map_mutable_storage_file(&flag_info_path).unwrap(); + for i in 0..8 { + set_flag_has_server_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) != 0); + set_flag_has_server_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) == 0); + } + } + } + + #[test] + fn test_set_flag_has_local_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(); + + // SAFETY: + // The safety here is guaranteed as only this single threaded test process will + // write to this file + unsafe { + let mut file = map_mutable_storage_file(&flag_info_path).unwrap(); + for i in 0..8 { + set_flag_has_local_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasLocalOverride as u8)) != 0); + set_flag_has_local_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasLocalOverride 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 index 4c98be4692..401d6b798a 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs @@ -14,46 +14,13 @@ * limitations under the License. */ -use std::fs::{self, File, OpenOptions}; -use std::io::{BufReader, Read}; - use anyhow::anyhow; use memmap2::MmapMut; +use std::fs::{self, OpenOptions}; -use aconfig_storage_file::protos::{storage_record_pb::try_from_binary_proto, ProtoStorageFiles}; -use aconfig_storage_file::AconfigStorageError::{ - self, FileReadFail, MapFileFail, ProtobufParseFail, StorageFileNotFound, -}; - -/// Find where persistent storage value file is for a particular container -fn find_persist_flag_value_file( - location_pb_file: &str, - container: &str, -) -> Result<String, 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.flag_val().to_string()); - } - } - Err(StorageFileNotFound(anyhow!("Persistent flag value file does not exist for {}", container))) -} +use aconfig_storage_file::AconfigStorageError::{self, FileReadFail, MapFileFail}; -/// Get a mapped storage file given the container and file type +/// Get the mutable memory mapping of a storage file /// /// # Safety /// @@ -61,27 +28,23 @@ fn find_persist_flag_value_file( /// 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, -) -> Result<MmapMut, AconfigStorageError> { - let file_path = find_persist_flag_value_file(location_pb_file, container)?; - +pub(crate) 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(); + 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| { + OpenOptions::new().read(true).write(true).open(file_path).map_err(|errmsg| { FileReadFail(anyhow!("Failed to open file {}: {}", file_path, errmsg)) })?; unsafe { - MmapMut::map_mut(&file).map_err(|errmsg| { + let mapped_file = MmapMut::map_mut(&file).map_err(|errmsg| { MapFileFail(anyhow!("fail to map storage file {}: {}", file_path, errmsg)) - }) + })?; + Ok(mapped_file) } } @@ -89,100 +52,48 @@ pub unsafe fn get_mapped_file( 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_find_persist_flag_value_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 flag_value_file = find_persist_flag_value_file(&file_full_path, "system").unwrap(); - assert_eq!(flag_value_file, "/metadata/aconfig/system.val"); - let flag_value_file = find_persist_flag_value_file(&file_full_path, "product").unwrap(); - assert_eq!(flag_value_file, "/metadata/aconfig/product.val"); - let err = find_persist_flag_value_file(&file_full_path, "vendor").unwrap_err(); - assert_eq!( - format!("{:?}", err), - "StorageFileNotFound(Persistent flag value file does not exist for vendor)" - ); - } + use std::io::Read; #[test] fn test_mapped_file_contents() { - let mut rw_file = copy_to_temp_file("./tests/flag.val", false).unwrap(); - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "some_package.map" - flag_map: "some_flag.map" - flag_val: "{}" - timestamp: 12345 -}} -"#, - rw_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 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 flag_val = rw_val_file.path().display().to_string(); + let flag_info = rw_info_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 = map_file(&flag_val).unwrap(); + assert_eq!(mmaped_file[..], content[..]); + } let mut content = Vec::new(); - rw_file.read_to_end(&mut content).unwrap(); + 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").unwrap(); + let mmaped_file = map_file(&flag_info).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: "{}" - 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(); + let ro_val_file = copy_to_temp_file("./tests/flag.val", true).unwrap(); + let flag_val = ro_val_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").unwrap_err(); + let error = map_file(&flag_val).unwrap_err(); assert_eq!( format!("{:?}", error), - format!( - "MapFileFail(fail to map non read write storage file {})", - ro_file.path().display().to_string() - ) + format!("MapFileFail(fail to map non read write storage file {})", flag_val) ); } } diff --git a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp index d2a52fe09e..85568e063b 100644 --- a/tools/aconfig/aconfig_storage_write_api/tests/Android.bp +++ b/tools/aconfig/aconfig_storage_write_api/tests/Android.bp @@ -1,8 +1,7 @@ - rust_test { name: "aconfig_storage_write_api.test.rust", srcs: [ - "storage_write_api_test.rs" + "storage_write_api_test.rs", ], rustlibs: [ "libanyhow", @@ -14,6 +13,7 @@ rust_test { ], data: [ "flag.val", + "flag.info", ], test_suites: ["general-tests"], } @@ -34,9 +34,11 @@ cc_test { ], 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 Binary files differnew file mode 100644 index 0000000000..6223edf369 --- /dev/null +++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.info diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.val b/tools/aconfig/aconfig_storage_write_api/tests/flag.val Binary files differindex 75b8564de3..ed203d4d13 100644 --- a/tools/aconfig/aconfig_storage_write_api/tests/flag.val +++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.val 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 index 3a1c5de590..54373798c9 100644 --- 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 @@ -50,72 +50,40 @@ class AconfigStorageTest : public ::testing::Test { return temp_file; } - Result<std::string> write_storage_location_pb_file(std::string const& flag_val) { - auto temp_file = std::tmpnam(nullptr); - auto proto = storage_files(); - auto* info = proto.add_files(); - info->set_version(0); - info->set_container("system"); - info->set_package_map("some_package.map"); - info->set_flag_map("some_flag.map"); - info->set_flag_val(flag_val); - 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"); - storage_record_pb = *write_storage_location_pb_file(flag_val); + flag_info = *copy_to_rw_temp_file(test_dir + "/flag.info"); } void TearDown() override { std::remove(flag_val.c_str()); - std::remove(storage_record_pb.c_str()); + std::remove(flag_info.c_str()); } std::string flag_val; - std::string storage_record_pb; + std::string flag_info; }; -/// 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_mapped_flag_value_file_impl( - storage_record_pb, "vendor"); - 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_mapped_flag_value_file_impl( - storage_record_pb, "system"); + auto mapped_file_result = api::map_mutable_storage_file(flag_val); ASSERT_FALSE(mapped_file_result.ok()); - ASSERT_EQ(mapped_file_result.error().message(), "cannot map nonwriteable file"); + auto it = mapped_file_result.error().message().find("cannot map nonwriteable file"); + 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_mapped_flag_value_file_impl( - storage_record_pb, "system"); + auto mapped_file_result = api::map_mutable_storage_file(flag_val); ASSERT_TRUE(mapped_file_result.ok()); - auto mapped_file = *mapped_file_result; + auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result); for (int offset = 0; offset < 8; ++offset) { - auto update_result = api::set_boolean_flag_value(mapped_file, offset, true); + 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); + auto value = api::get_boolean_flag_value(*mapped_file, offset); ASSERT_TRUE(value.ok()); ASSERT_TRUE(*value); } @@ -123,12 +91,61 @@ TEST_F(AconfigStorageTest, test_boolean_flag_value_update) { /// 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_mapped_flag_value_file_impl( - storage_record_pb, "system"); + auto mapped_file_result = api::map_mutable_storage_file(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); + auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*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 has server override update api +TEST_F(AconfigStorageTest, test_flag_has_server_override_update) { + auto mapped_file_result = api::map_mutable_storage_file(flag_info); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result); + + for (int offset = 0; offset < 8; ++offset) { + auto update_result = api::set_flag_has_server_override( + *mapped_file, api::FlagValueType::Boolean, offset, true); + ASSERT_TRUE(update_result.ok()) << update_result.error(); + auto attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); + ASSERT_TRUE(attribute.ok()); + ASSERT_TRUE(*attribute & api::FlagInfoBit::HasServerOverride); + + update_result = api::set_flag_has_server_override( + *mapped_file, api::FlagValueType::Boolean, offset, false); + ASSERT_TRUE(update_result.ok()); + attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); + ASSERT_TRUE(attribute.ok()); + ASSERT_FALSE(*attribute & api::FlagInfoBit::HasServerOverride); + } +} + +/// Test to lock down storage flag has local override update api +TEST_F(AconfigStorageTest, test_flag_has_local_override_update) { + auto mapped_file_result = api::map_mutable_storage_file(flag_info); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MutableMappedStorageFile>(*mapped_file_result); + + for (int offset = 0; offset < 8; ++offset) { + auto update_result = api::set_flag_has_local_override( + *mapped_file, api::FlagValueType::Boolean, offset, true); + ASSERT_TRUE(update_result.ok()); + auto attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); + ASSERT_TRUE(attribute.ok()); + ASSERT_TRUE(*attribute & api::FlagInfoBit::HasLocalOverride); + + update_result = api::set_flag_has_local_override( + *mapped_file, api::FlagValueType::Boolean, offset, false); + ASSERT_TRUE(update_result.ok()); + attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); + ASSERT_TRUE(attribute.ok()); + ASSERT_FALSE(*attribute & api::FlagInfoBit::HasLocalOverride); + } +} 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 index f6c1bbcd7b..367569def4 100644 --- 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 @@ -1,38 +1,17 @@ #[cfg(not(feature = "cargo"))] mod aconfig_storage_write_api_test { - use aconfig_storage_file::protos::ProtoStorageFiles; + use aconfig_storage_file::{FlagInfoBit, FlagValueType}; + 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}; + use aconfig_storage_write_api::{ + map_mutable_storage_file, set_boolean_flag_value, set_flag_has_local_override, + set_flag_has_server_override, + }; - use protobuf::Message; use std::fs::{self, File}; - use std::io::{Read, Write}; + use std::io::Read; use tempfile::NamedTempFile; - /// Write storage location record pb to a temp file - fn write_storage_record_file(flag_val: &str) -> NamedTempFile { - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "some_package_map" - flag_map: "some_flag_map" - flag_val: "{}" - timestamp: 12345 -}} -"#, - flag_val - ); - 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(); @@ -48,18 +27,24 @@ files {{ 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_value_path = flag_value_file.path().display().to_string(); - let record_pb_file = write_storage_record_file(&flag_value_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, "system").unwrap() }; + let mut file = unsafe { map_mutable_storage_file(&flag_value_path).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); @@ -70,4 +55,48 @@ files {{ assert!(!value); } } + + #[test] + /// Test to lock down flag has server override update api + fn test_set_flag_has_server_override() { + let flag_info_file = copy_to_temp_rw_file("./flag.info"); + let flag_info_path = flag_info_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 { map_mutable_storage_file(&flag_info_path).unwrap() }; + for i in 0..8 { + set_flag_has_server_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) != 0); + set_flag_has_server_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasServerOverride as u8)) == 0); + } + } + + #[test] + /// Test to lock down flag has local override update api + fn test_set_flag_has_local_override() { + let flag_info_file = copy_to_temp_rw_file("./flag.info"); + let flag_info_path = flag_info_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 { map_mutable_storage_file(&flag_info_path).unwrap() }; + for i in 0..8 { + set_flag_has_local_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasLocalOverride as u8)) != 0); + set_flag_has_local_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); + let attribute = + get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i); + assert!((attribute & (FlagInfoBit::HasLocalOverride as u8)) == 0); + } + } } diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp index b36aa34c64..2a023792b6 100644 --- a/tools/aconfig/aflags/Android.bp +++ b/tools/aconfig/aflags/Android.bp @@ -9,7 +9,10 @@ rust_defaults { lints: "android", srcs: ["src/main.rs"], rustlibs: [ + "libaconfig_device_paths", "libaconfig_protos", + "libaconfig_storage_read_api", + "libaconfig_storage_file", "libanyhow", "libclap", "libnix", diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml index 6a08da6745..eeae295316 100644 --- a/tools/aconfig/aflags/Cargo.toml +++ b/tools/aconfig/aflags/Cargo.toml @@ -6,8 +6,11 @@ edition = "2021" [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" } +aconfig_device_paths = { version = "0.1.0", path = "../aconfig_device_paths" } 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 0000000000..c21c5424bb --- /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 listed_flag in + aconfig_storage_file::list_flags(&package_map, &flag_map, &flag_val)? + { + result.push(Flag { + name: listed_flag.flag_name, + package: listed_flag.package_name, + value: FlagValue::try_from(listed_flag.flag_value.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 089f33dc16..cf6ab28e8b 100644 --- a/tools/aconfig/aflags/src/device_config_source.rs +++ b/tools/aconfig/aflags/src/device_config_source.rs @@ -14,78 +14,17 @@ * limitations under the License. */ -use crate::{Flag, FlagPermission, FlagSource, FlagValue, ValuePickedFrom}; -use aconfig_protos::ProtoFlagPermission as ProtoPermission; -use aconfig_protos::ProtoFlagState as ProtoState; -use aconfig_protos::ProtoParsedFlag; -use aconfig_protos::ProtoParsedFlags; +use crate::load_protos; +use crate::{Flag, FlagSource, FlagValue, ValuePickedFrom}; + use anyhow::{anyhow, bail, Result}; use regex::Regex; -use std::collections::BTreeMap; use std::collections::HashMap; use std::process::Command; -use std::{fs, str}; +use std::str; pub struct DeviceConfigSource {} -fn convert_parsed_flag(flag: &ProtoParsedFlag) -> Flag { - let namespace = flag.namespace().to_string(); - let package = flag.package().to_string(); - let name = flag.name().to_string(); - - let container = if flag.container().is_empty() { - "system".to_string() - } else { - flag.container().to_string() - }; - - let value = match flag.state() { - ProtoState::ENABLED => FlagValue::Enabled, - ProtoState::DISABLED => FlagValue::Disabled, - }; - - let permission = match flag.permission() { - ProtoPermission::READ_ONLY => FlagPermission::ReadOnly, - ProtoPermission::READ_WRITE => FlagPermission::ReadWrite, - }; - - Flag { - namespace, - package, - name, - container, - value, - staged_value: None, - permission, - value_picked_from: ValuePickedFrom::Default, - } -} - -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!("/{partition}/etc/aconfig_flags.pb"); - let Ok(bytes) = fs::read(&path) else { - eprintln!("warning: failed to read {}", path); - continue; - }; - let parsed_flags: ProtoParsedFlags = protobuf::Message::parse_from_bytes(&bytes)?; - for flag in parsed_flags.parsed_flag { - let key = format!("{}.{}", flag.package(), flag.name()); - let container = if flag.container().is_empty() { - "system".to_string() - } else { - flag.container().to_string() - }; - - if container.eq(partition) { - flags.insert(key, convert_parsed_flag(&flag)); - } - } - } - Ok(flags.values().cloned().collect()) -} - 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)$")?; @@ -180,7 +119,7 @@ fn reconcile( impl FlagSource for DeviceConfigSource { fn list_flags() -> Result<Vec<Flag>> { - let pb_flags = read_pb_files()?; + let pb_flags = load_protos::load()?; let dc_flags = read_device_config_flags()?; let staged_flags = read_staged_flags()?; diff --git a/tools/aconfig/aflags/src/load_protos.rs b/tools/aconfig/aflags/src/load_protos.rs new file mode 100644 index 0000000000..90d8599145 --- /dev/null +++ b/tools/aconfig/aflags/src/load_protos.rs @@ -0,0 +1,62 @@ +use crate::{Flag, FlagPermission, FlagValue, ValuePickedFrom}; +use aconfig_protos::ProtoFlagPermission as ProtoPermission; +use aconfig_protos::ProtoFlagState as ProtoState; +use aconfig_protos::ProtoParsedFlag; +use aconfig_protos::ProtoParsedFlags; +use anyhow::Result; +use std::fs; +use std::path::Path; + +// TODO(b/329875578): use container field directly instead of inferring. +fn infer_container(path: &Path) -> String { + let path_str = path.to_string_lossy(); + path_str + .strip_prefix("/apex/") + .or_else(|| path_str.strip_prefix('/')) + .unwrap_or(&path_str) + .strip_suffix("/etc/aconfig_flags.pb") + .unwrap_or(&path_str) + .to_string() +} + +fn convert_parsed_flag(path: &Path, flag: &ProtoParsedFlag) -> Flag { + let namespace = flag.namespace().to_string(); + let package = flag.package().to_string(); + let name = flag.name().to_string(); + + let value = match flag.state() { + ProtoState::ENABLED => FlagValue::Enabled, + ProtoState::DISABLED => FlagValue::Disabled, + }; + + let permission = match flag.permission() { + ProtoPermission::READ_ONLY => FlagPermission::ReadOnly, + ProtoPermission::READ_WRITE => FlagPermission::ReadWrite, + }; + + Flag { + namespace, + package, + name, + container: infer_container(path), + value, + staged_value: None, + permission, + value_picked_from: ValuePickedFrom::Default, + } +} + +pub(crate) fn load() -> Result<Vec<Flag>> { + let mut result = Vec::new(); + + let paths = aconfig_device_paths::parsed_flags_proto_paths()?; + for path in paths { + let bytes = fs::read(path.clone())?; + let parsed_flags: ProtoParsedFlags = protobuf::Message::parse_from_bytes(&bytes)?; + for flag in parsed_flags.parsed_flag { + // TODO(b/334954748): enforce one-container-per-flag invariant. + result.push(convert_parsed_flag(&path, &flag)); + } + } + Ok(result) +} diff --git a/tools/aconfig/aflags/src/main.rs b/tools/aconfig/aflags/src/main.rs index 808ffa0da8..516b773eaa 100644 --- a/tools/aconfig/aflags/src/main.rs +++ b/tools/aconfig/aflags/src/main.rs @@ -22,18 +22,23 @@ use clap::Parser; mod device_config_source; use device_config_source::DeviceConfigSource; +mod aconfig_storage_source; +use aconfig_storage_source::AconfigStorageSource; + +mod load_protos; + #[derive(Clone, PartialEq, Debug)] enum FlagPermission { ReadOnly, ReadWrite, } -impl ToString for FlagPermission { - fn to_string(&self) -> String { - match &self { - Self::ReadOnly => "read-only".into(), - Self::ReadWrite => "read-write".into(), - } +impl std::fmt::Display for FlagPermission { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match &self { + Self::ReadOnly => "read-only", + Self::ReadWrite => "read-write", + }) } } @@ -43,12 +48,12 @@ enum ValuePickedFrom { Server, } -impl ToString for ValuePickedFrom { - fn to_string(&self) -> String { - match &self { - Self::Default => "default".into(), - Self::Server => "server".into(), - } +impl std::fmt::Display for ValuePickedFrom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match &self { + Self::Default => "default", + Self::Server => "server", + }) } } @@ -70,12 +75,12 @@ impl TryFrom<&str> for FlagValue { } } -impl ToString for FlagValue { - fn to_string(&self) -> String { - match &self { - Self::Enabled => "enabled".into(), - Self::Disabled => "disabled".into(), - } +impl std::fmt::Display for FlagValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match &self { + Self::Enabled => "enabled", + Self::Disabled => "disabled", + }) } } @@ -98,7 +103,7 @@ impl Flag { fn display_staged_value(&self) -> String { match self.staged_value { - Some(v) => format!("(->{})", v.to_string()), + Some(v) => format!("(->{})", v), None => "-".to_string(), } } @@ -109,6 +114,11 @@ trait FlagSource { 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. Rows in the table from the `list` command follow this format: @@ -139,7 +149,11 @@ struct Cli { #[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 { @@ -201,8 +215,11 @@ fn set_flag(qualified_name: &str, value: &str) -> Result<()> { Ok(()) } -fn list() -> Result<String> { - let flags = DeviceConfigSource::list_flags()?; +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_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), @@ -234,7 +251,8 @@ fn list() -> Result<String> { fn main() { let cli = Cli::parse(); let output = match cli.command { - Command::List => list().map(Some), + 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), }; diff --git a/tools/buildinfo.sh b/tools/buildinfo.sh deleted file mode 100755 index 0ed9453e8d..0000000000 --- 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/check-flagged-apis/Android.bp b/tools/check-flagged-apis/Android.bp new file mode 100644 index 0000000000..43c9c8e975 --- /dev/null +++ b/tools/check-flagged-apis/Android.bp @@ -0,0 +1,51 @@ +// 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_team: "trendy_team_updatable_sdk_apis", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_defaults { + name: "check-flagged-apis-defaults", + srcs: [ + "src/com/android/checkflaggedapis/Main.kt", + ], + static_libs: [ + "libaconfig_java_proto_lite", + "metalava-signature-reader", + "metalava-tools-common-m2-deps", + ], +} + +java_binary_host { + name: "check-flagged-apis", + defaults: [ + "check-flagged-apis-defaults", + ], + main_class: "com.android.checkflaggedapis.Main", +} + +java_test_host { + name: "check-flagged-apis-test", + defaults: [ + "check-flagged-apis-defaults", + ], + srcs: [ + "src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt", + ], + static_libs: [ + "junit", + ], +} diff --git a/tools/check-flagged-apis/OWNERS b/tools/check-flagged-apis/OWNERS new file mode 100644 index 0000000000..289e21e4b6 --- /dev/null +++ b/tools/check-flagged-apis/OWNERS @@ -0,0 +1,4 @@ +amhk@google.com +gurpreetgs@google.com +michaelwr@google.com +paulduffin@google.com diff --git a/tools/check-flagged-apis/check-flagged-apis.sh b/tools/check-flagged-apis/check-flagged-apis.sh new file mode 100755 index 0000000000..d9934a11fe --- /dev/null +++ b/tools/check-flagged-apis/check-flagged-apis.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# 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. + +# Run check-flagged-apis for public APIs and the three @SystemApi flavours +# Usage: lunch <your-target> && source <this script> + +source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../shell_utils.sh +require_top + +PUBLIC_XML_VERSIONS=out/target/common/obj/PACKAGING/api_versions_public_generated-api-versions.xml +SYSTEM_XML_VERSIONS=out/target/common/obj/PACKAGING/api_versions_system_generated-api-versions.xml +SYSTEM_SERVER_XML_VERSONS=out/target/common/obj/PACKAGING/api_versions_system_server_complete_generated-api-versions.xml +MODULE_LIB_XML_VERSIONS=out/target/common/obj/PACKAGING/api_versions_module_lib_complete_generated-api-versions.xml + +function m() { + $(gettop)/build/soong/soong_ui.bash --build-mode --all-modules --dir="$(pwd)" "$@" +} + +function build() { + m \ + check-flagged-apis \ + all_aconfig_declarations \ + frameworks-base-api-current.txt \ + frameworks-base-api-system-current.txt \ + frameworks-base-api-system-server-current.txt \ + frameworks-base-api-module-lib-current.txt \ + $PUBLIC_XML_VERSIONS \ + $SYSTEM_XML_VERSIONS \ + $SYSTEM_SERVER_XML_VERSONS \ + $MODULE_LIB_XML_VERSIONS +} + +function aninja() { + local T="$(gettop)" + (\cd "${T}" && prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-${TARGET_PRODUCT}.ninja "$@") +} + +function path_to_api_signature_file { + aninja -t query device_"$1"_all_targets | grep -A1 -e input: | tail -n1 +} + +function run() { + local errors=0 + + echo "# current" + check-flagged-apis \ + --api-signature $(path_to_api_signature_file "frameworks-base-api-current.txt") \ + --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \ + --api-versions $PUBLIC_XML_VERSIONS + (( errors += $? )) + + echo + echo "# system-current" + check-flagged-apis \ + --api-signature $(path_to_api_signature_file "frameworks-base-api-system-current.txt") \ + --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \ + --api-versions $SYSTEM_XML_VERSIONS + (( errors += $? )) + + echo + echo "# system-server-current" + check-flagged-apis \ + --api-signature $(path_to_api_signature_file "frameworks-base-api-system-server-current.txt") \ + --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \ + --api-versions $SYSTEM_SERVER_XML_VERSONS + (( errors += $? )) + + echo + echo "# module-lib" + check-flagged-apis \ + --api-signature $(path_to_api_signature_file "frameworks-base-api-module-lib-current.txt") \ + --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \ + --api-versions $MODULE_LIB_XML_VERSIONS + (( errors += $? )) + + return $errors +} + +if [[ "$1" != "--skip-build" ]]; then + build && run +else + run +fi diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt new file mode 100644 index 0000000000..111ea91f85 --- /dev/null +++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt @@ -0,0 +1,341 @@ +/* + * 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 com.android.checkflaggedapis + +import android.aconfig.Aconfig +import android.aconfig.Aconfig.flag_state.DISABLED +import android.aconfig.Aconfig.flag_state.ENABLED +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +private val API_SIGNATURE = + """ + // Signature format: 2.0 + package android { + @FlaggedApi("android.flag.foo") public final class Clazz { + ctor @FlaggedApi("android.flag.foo") public Clazz(); + field @FlaggedApi("android.flag.foo") public static final int FOO = 1; // 0x1 + method @FlaggedApi("android.flag.foo") public int getErrorCode(); + method @FlaggedApi("android.flag.foo") public boolean setData(int, int[][], @NonNull android.util.Utility<T, U>); + method @FlaggedApi("android.flag.foo") public boolean setVariableData(int, android.util.Atom...); + method @FlaggedApi("android.flag.foo") public boolean innerClassArg(android.Clazz.Builder); + } + @FlaggedApi("android.flag.bar") public static class Clazz.Builder { + } + } +""" + .trim() + +private val API_VERSIONS = + """ + <?xml version="1.0" encoding="utf-8"?> + <api version="3"> + <class name="android/Clazz" since="1"> + <extends name="java/lang/Object"/> + <method name="<init>()V"/> + <field name="FOO"/> + <method name="getErrorCode()I"/> + <method name="setData(I[[ILandroid/util/Utility;)Z"/> + <method name="setVariableData(I[Landroid/util/Atom;)Z"/> + <method name="innerClassArg(Landroid/Clazz${"$"}Builder;)"/> + </class> + <class name="android/Clazz${"$"}Builder" since="2"> + <extends name="java/lang/Object"/> + </class> + </api> +""" + .trim() + +private fun generateFlagsProto( + fooState: Aconfig.flag_state, + barState: Aconfig.flag_state +): InputStream { + val fooFlag = + Aconfig.parsed_flag + .newBuilder() + .setPackage("android.flag") + .setName("foo") + .setState(fooState) + .setPermission(Aconfig.flag_permission.READ_ONLY) + .build() + val barFlag = + Aconfig.parsed_flag + .newBuilder() + .setPackage("android.flag") + .setName("bar") + .setState(barState) + .setPermission(Aconfig.flag_permission.READ_ONLY) + .build() + val flags = + Aconfig.parsed_flags.newBuilder().addParsedFlag(fooFlag).addParsedFlag(barFlag).build() + val binaryProto = ByteArrayOutputStream() + flags.writeTo(binaryProto) + return ByteArrayInputStream(binaryProto.toByteArray()) +} + +@RunWith(JUnit4::class) +class CheckFlaggedApisTest { + @Test + fun testParseApiSignature() { + val expected = + setOf( + Pair( + Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), + Flag("android.flag.foo")), + Pair(Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")), + Pair(Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")), + Pair(Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")), + Pair( + Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), + Flag("android.flag.foo")), + Pair( + Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), + Flag("android.flag.foo")), + Pair( + Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), + Flag("android.flag.foo")), + Pair( + Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), + Flag("android.flag.bar")), + ) + val actual = parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()) + assertEquals(expected, actual) + } + + @Test + fun testParseFlagValues() { + val expected: Map<Flag, Boolean> = + mapOf(Flag("android.flag.foo") to true, Flag("android.flag.bar") to true) + val actual = parseFlagValues(generateFlagsProto(ENABLED, ENABLED)) + assertEquals(expected, actual) + } + + @Test + fun testParseApiVersions() { + val expected: Set<Symbol> = + setOf( + Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), + Symbol.createMethod("android/Clazz", "Clazz()"), + Symbol.createField("android/Clazz", "FOO"), + Symbol.createMethod("android/Clazz", "getErrorCode()"), + Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), + Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), + Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), + Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), + ) + val actual = parseApiVersions(API_VERSIONS.byteInputStream()) + assertEquals(expected, actual) + } + + @Test + fun testParseApiVersionsNestedClasses() { + val apiVersions = + """ + <?xml version="1.0" encoding="utf-8"?> + <api version="3"> + <class name="android/Clazz${'$'}Foo${'$'}Bar" since="1"> + <extends name="java/lang/Object"/> + <method name="<init>()V"/> + </class> + </api> + """ + .trim() + val expected: Set<Symbol> = + setOf( + Symbol.createClass("android/Clazz/Foo/Bar", "java/lang/Object", setOf()), + Symbol.createMethod("android/Clazz/Foo/Bar", "Bar()"), + ) + val actual = parseApiVersions(apiVersions.byteInputStream()) + assertEquals(expected, actual) + } + + @Test + fun testFindErrorsNoErrors() { + val expected = setOf<ApiError>() + val actual = + findErrors( + parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()), + parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), + parseApiVersions(API_VERSIONS.byteInputStream())) + assertEquals(expected, actual) + } + + @Test + fun testFindErrorsVerifyImplements() { + val apiSignature = + """ + // Signature format: 2.0 + package android { + @FlaggedApi("android.flag.foo") public final class Clazz implements android.Interface { + method @FlaggedApi("android.flag.foo") public boolean foo(); + method @FlaggedApi("android.flag.foo") public boolean bar(); + } + public interface Interface { + method public boolean bar(); + } + } + """ + .trim() + + val apiVersions = + """ + <?xml version="1.0" encoding="utf-8"?> + <api version="3"> + <class name="android/Clazz" since="1"> + <extends name="java/lang/Object"/> + <implements name="android/Interface"/> + <method name="foo()Z"/> + </class> + <class name="android/Interface" since="1"> + <method name="bar()Z"/> + </class> + </api> + """ + .trim() + + val expected = setOf<ApiError>() + val actual = + findErrors( + parseApiSignature("in-memory", apiSignature.byteInputStream()), + parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), + parseApiVersions(apiVersions.byteInputStream())) + assertEquals(expected, actual) + } + + @Test + fun testFindErrorsVerifySuperclass() { + val apiSignature = + """ + // Signature format: 2.0 + package android { + @FlaggedApi("android.flag.foo") public final class C extends android.B { + method @FlaggedApi("android.flag.foo") public boolean c(); + method @FlaggedApi("android.flag.foo") public boolean b(); + method @FlaggedApi("android.flag.foo") public boolean a(); + } + public final class B extends android.A { + method public boolean b(); + } + public final class A { + method public boolean a(); + } + } + """ + .trim() + + val apiVersions = + """ + <?xml version="1.0" encoding="utf-8"?> + <api version="3"> + <class name="android/C" since="1"> + <extends name="android/B"/> + <method name="c()Z"/> + </class> + <class name="android/B" since="1"> + <extends name="android/A"/> + <method name="b()Z"/> + </class> + <class name="android/A" since="1"> + <method name="a()Z"/> + </class> + </api> + """ + .trim() + + val expected = setOf<ApiError>() + val actual = + findErrors( + parseApiSignature("in-memory", apiSignature.byteInputStream()), + parseFlagValues(generateFlagsProto(ENABLED, ENABLED)), + parseApiVersions(apiVersions.byteInputStream())) + assertEquals(expected, actual) + } + + @Test + fun testNestedFlagsOuterFlagWins() { + val apiSignature = + """ + // Signature format: 2.0 + package android { + @FlaggedApi("android.flag.foo") public final class A { + method @FlaggedApi("android.flag.bar") public boolean method(); + } + @FlaggedApi("android.flag.bar") public final class B { + method @FlaggedApi("android.flag.foo") public boolean method(); + } + } + """ + .trim() + + val apiVersions = + """ + <?xml version="1.0" encoding="utf-8"?> + <api version="3"> + <class name="android/B" since="1"> + <extends name="java/lang/Object"/> + </class> + </api> + """ + .trim() + + val expected = setOf<ApiError>() + val actual = + findErrors( + parseApiSignature("in-memory", apiSignature.byteInputStream()), + parseFlagValues(generateFlagsProto(DISABLED, ENABLED)), + parseApiVersions(apiVersions.byteInputStream())) + assertEquals(expected, actual) + } + + @Test + fun testFindErrorsDisabledFlaggedApiIsPresent() { + val expected = + setOf<ApiError>( + DisabledFlaggedApiIsPresentError( + Symbol.createClass("android/Clazz", "java/lang/Object", setOf()), + Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createMethod("android/Clazz", "Clazz()"), Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createField("android/Clazz", "FOO"), Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createMethod("android/Clazz", "getErrorCode()"), Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createMethod("android/Clazz", "setData(I[[ILandroid/util/Utility;)"), + Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createMethod("android/Clazz", "setVariableData(I[Landroid/util/Atom;)"), + Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createMethod("android/Clazz", "innerClassArg(Landroid/Clazz/Builder;)"), + Flag("android.flag.foo")), + DisabledFlaggedApiIsPresentError( + Symbol.createClass("android/Clazz/Builder", "java/lang/Object", setOf()), + Flag("android.flag.bar")), + ) + val actual = + findErrors( + parseApiSignature("in-memory", API_SIGNATURE.byteInputStream()), + parseFlagValues(generateFlagsProto(DISABLED, DISABLED)), + parseApiVersions(API_VERSIONS.byteInputStream())) + assertEquals(expected, actual) + } +} diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt new file mode 100644 index 0000000000..a277ce815f --- /dev/null +++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt @@ -0,0 +1,445 @@ +/* + * 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. + */ +@file:JvmName("Main") + +package com.android.checkflaggedapis + +import android.aconfig.Aconfig +import com.android.tools.metalava.model.BaseItemVisitor +import com.android.tools.metalava.model.ClassItem +import com.android.tools.metalava.model.FieldItem +import com.android.tools.metalava.model.Item +import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.text.ApiFile +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.ProgramResult +import com.github.ajalt.clikt.parameters.options.help +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.path +import java.io.InputStream +import javax.xml.parsers.DocumentBuilderFactory +import org.w3c.dom.Node + +/** + * Class representing the fully qualified name of a class, method or field. + * + * This tool reads a multitude of input formats all of which represents the fully qualified path to + * a Java symbol slightly differently. To keep things consistent, all parsed APIs are converted to + * Symbols. + * + * Symbols are encoded using the format similar to the one described in section 4.3.2 of the JVM + * spec [1], that is, "package.class.inner-class.method(int, int[], android.util.Clazz)" is + * represented as + * <pre> + * package.class.inner-class.method(II[Landroid/util/Clazz;) + * <pre> + * + * Where possible, the format has been simplified (to make translation of the + * various input formats easier): for instance, only / is used as delimiter (# + * and $ are never used). + * + * 1. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2 + */ +internal sealed class Symbol { + companion object { + private val FORBIDDEN_CHARS = listOf('#', '$', '.') + + fun createClass(clazz: String, superclass: String?, interfaces: Set<String>): Symbol { + return ClassSymbol( + toInternalFormat(clazz), + superclass?.let { toInternalFormat(it) }, + interfaces.map { toInternalFormat(it) }.toSet()) + } + + fun createField(clazz: String, field: String): Symbol { + require(!field.contains("(") && !field.contains(")")) + return MemberSymbol(toInternalFormat(clazz), toInternalFormat(field)) + } + + fun createMethod(clazz: String, method: String): Symbol { + return MemberSymbol(toInternalFormat(clazz), toInternalFormat(method)) + } + + protected fun toInternalFormat(name: String): String { + var internalName = name + for (ch in FORBIDDEN_CHARS) { + internalName = internalName.replace(ch, '/') + } + return internalName + } + } + + abstract fun toPrettyString(): String +} + +internal data class ClassSymbol( + val clazz: String, + val superclass: String?, + val interfaces: Set<String> +) : Symbol() { + override fun toPrettyString(): String = "$clazz" +} + +internal data class MemberSymbol(val clazz: String, val member: String) : Symbol() { + override fun toPrettyString(): String = "$clazz/$member" +} + +/** + * Class representing the fully qualified name of an aconfig flag. + * + * This includes both the flag's package and name, separated by a dot, e.g.: + * <pre> + * com.android.aconfig.test.disabled_ro + * <pre> + */ +@JvmInline +internal value class Flag(val name: String) { + override fun toString(): String = name.toString() +} + +internal sealed class ApiError { + abstract val symbol: Symbol + abstract val flag: Flag +} + +internal data class EnabledFlaggedApiNotPresentError( + override val symbol: Symbol, + override val flag: Flag +) : ApiError() { + override fun toString(): String { + return "error: enabled @FlaggedApi not present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag" + } +} + +internal data class DisabledFlaggedApiIsPresentError( + override val symbol: Symbol, + override val flag: Flag +) : ApiError() { + override fun toString(): String { + return "error: disabled @FlaggedApi is present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag" + } +} + +internal data class UnknownFlagError(override val symbol: Symbol, override val flag: Flag) : + ApiError() { + override fun toString(): String { + return "error: unknown flag: symbol=${symbol.toPrettyString()} flag=$flag" + } +} + +class CheckCommand : + CliktCommand( + help = + """ +Check that all flagged APIs are used in the correct way. + +This tool reads the API signature file and checks that all flagged APIs are used in the correct way. + +The tool will exit with a non-zero exit code if any flagged APIs are found to be used in the incorrect way. +""") { + private val apiSignaturePath by + option("--api-signature") + .help( + """ + Path to API signature file. + Usually named *current.txt. + Tip: `m frameworks-base-api-current.txt` will generate a file that includes all platform and mainline APIs. + """) + .path(mustExist = true, canBeDir = false, mustBeReadable = true) + .required() + private val flagValuesPath by + option("--flag-values") + .help( + """ + Path to aconfig parsed_flags binary proto file. + Tip: `m all_aconfig_declarations` will generate a file that includes all information about all flags. + """) + .path(mustExist = true, canBeDir = false, mustBeReadable = true) + .required() + private val apiVersionsPath by + option("--api-versions") + .help( + """ + Path to API versions XML file. + Usually named xml-versions.xml. + Tip: `m sdk dist` will generate a file that includes all platform and mainline APIs. + """) + .path(mustExist = true, canBeDir = false, mustBeReadable = true) + .required() + + override fun run() { + val flaggedSymbols = + apiSignaturePath.toFile().inputStream().use { + parseApiSignature(apiSignaturePath.toString(), it) + } + val flags = flagValuesPath.toFile().inputStream().use { parseFlagValues(it) } + val exportedSymbols = apiVersionsPath.toFile().inputStream().use { parseApiVersions(it) } + val errors = findErrors(flaggedSymbols, flags, exportedSymbols) + for (e in errors) { + println(e) + } + throw ProgramResult(errors.size) + } +} + +internal fun parseApiSignature(path: String, input: InputStream): Set<Pair<Symbol, Flag>> { + val output = mutableSetOf<Pair<Symbol, Flag>>() + val visitor = + object : BaseItemVisitor() { + override fun visitClass(cls: ClassItem) { + getFlagOrNull(cls)?.let { flag -> + val symbol = + Symbol.createClass( + cls.baselineElementId(), + cls.superClass()?.baselineElementId(), + cls.allInterfaces() + .map { it.baselineElementId() } + .filter { it != cls.baselineElementId() } + .toSet()) + output.add(Pair(symbol, flag)) + } + } + + override fun visitField(field: FieldItem) { + getFlagOrNull(field)?.let { flag -> + val symbol = + Symbol.createField(field.containingClass().baselineElementId(), field.name()) + output.add(Pair(symbol, flag)) + } + } + + override fun visitMethod(method: MethodItem) { + getFlagOrNull(method)?.let { flag -> + val methodName = buildString { + append(method.name()) + append("(") + method.parameters().joinTo(this, separator = "") { it.type().internalName() } + append(")") + } + val symbol = Symbol.createMethod(method.containingClass().qualifiedName(), methodName) + output.add(Pair(symbol, flag)) + } + } + + private fun getFlagOrNull(item: Item): Flag? { + return item.modifiers + .findAnnotation("android.annotation.FlaggedApi") + ?.findAttribute("value") + ?.value + ?.let { Flag(it.value() as String) } + } + } + val codebase = ApiFile.parseApi(path, input) + codebase.accept(visitor) + return output +} + +internal fun parseFlagValues(input: InputStream): Map<Flag, Boolean> { + val parsedFlags = Aconfig.parsed_flags.parseFrom(input).getParsedFlagList() + return parsedFlags.associateBy( + { Flag("${it.getPackage()}.${it.getName()}") }, + { it.getState() == Aconfig.flag_state.ENABLED }) +} + +internal fun parseApiVersions(input: InputStream): Set<Symbol> { + fun Node.getAttribute(name: String): String? = getAttributes()?.getNamedItem(name)?.getNodeValue() + + val output = mutableSetOf<Symbol>() + val factory = DocumentBuilderFactory.newInstance() + val parser = factory.newDocumentBuilder() + val document = parser.parse(input) + + val classes = document.getElementsByTagName("class") + // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead + for (i in 0.rangeUntil(classes.getLength())) { + val cls = classes.item(i) + val className = + requireNotNull(cls.getAttribute("name")) { + "Bad XML: <class> element without name attribute" + } + var superclass: String? = null + val interfaces = mutableSetOf<String>() + val children = cls.getChildNodes() + for (j in 0.rangeUntil(children.getLength())) { + val child = children.item(j) + when (child.getNodeName()) { + "extends" -> { + superclass = + requireNotNull(child.getAttribute("name")) { + "Bad XML: <extends> element without name attribute" + } + } + "implements" -> { + val interfaceName = + requireNotNull(child.getAttribute("name")) { + "Bad XML: <implements> element without name attribute" + } + interfaces.add(interfaceName) + } + } + } + output.add(Symbol.createClass(className, superclass, interfaces)) + } + + val fields = document.getElementsByTagName("field") + // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead + for (i in 0.rangeUntil(fields.getLength())) { + val field = fields.item(i) + val fieldName = + requireNotNull(field.getAttribute("name")) { + "Bad XML: <field> element without name attribute" + } + val className = + requireNotNull(field.getParentNode()?.getAttribute("name")) { + "Bad XML: top level <field> element" + } + output.add(Symbol.createField(className, fieldName)) + } + + val methods = document.getElementsByTagName("method") + // ktfmt doesn't understand the `..<` range syntax; explicitly call .rangeUntil instead + for (i in 0.rangeUntil(methods.getLength())) { + val method = methods.item(i) + val methodSignature = + requireNotNull(method.getAttribute("name")) { + "Bad XML: <method> element without name attribute" + } + val methodSignatureParts = methodSignature.split(Regex("\\(|\\)")) + if (methodSignatureParts.size != 3) { + throw Exception("Bad XML: method signature '$methodSignature'") + } + var (methodName, methodArgs, _) = methodSignatureParts + val packageAndClassName = + requireNotNull(method.getParentNode()?.getAttribute("name")) { + "Bad XML: top level <method> element, or <class> element missing name attribute" + } + .replace("$", "/") + if (methodName == "<init>") { + methodName = packageAndClassName.split("/").last() + } + output.add(Symbol.createMethod(packageAndClassName, "$methodName($methodArgs)")) + } + + return output +} + +/** + * Find errors in the given data. + * + * @param flaggedSymbolsInSource the set of symbols that are flagged in the source code + * @param flags the set of flags and their values + * @param symbolsInOutput the set of symbols that are present in the output + * @return the set of errors found + */ +internal fun findErrors( + flaggedSymbolsInSource: Set<Pair<Symbol, Flag>>, + flags: Map<Flag, Boolean>, + symbolsInOutput: Set<Symbol> +): Set<ApiError> { + fun Set<Symbol>.containsSymbol(symbol: Symbol): Boolean { + // trivial case: the symbol is explicitly listed in api-versions.xml + if (contains(symbol)) { + return true + } + + // non-trivial case: the symbol could be part of the surrounding class' + // super class or interfaces + val (className, memberName) = + when (symbol) { + is ClassSymbol -> return false + is MemberSymbol -> { + Pair(symbol.clazz, symbol.member) + } + } + val clazz = find { it is ClassSymbol && it.clazz == className } as? ClassSymbol? + if (clazz == null) { + return false + } + + for (interfaceName in clazz.interfaces) { + // createMethod is the same as createField, except it allows parenthesis + val interfaceSymbol = Symbol.createMethod(interfaceName, memberName) + if (contains(interfaceSymbol)) { + return true + } + } + + if (clazz.superclass != null) { + val superclassSymbol = Symbol.createMethod(clazz.superclass, memberName) + return containsSymbol(superclassSymbol) + } + + return false + } + + /** + * Returns whether the given flag is enabled for the given symbol. + * + * A flagged member inside a flagged class is ignored (and the flag value considered disabled) if + * the class' flag is disabled. + * + * @param symbol the symbol to check + * @param flag the flag to check + * @return whether the flag is enabled for the given symbol + */ + fun isFlagEnabledForSymbol(symbol: Symbol, flag: Flag): Boolean { + when (symbol) { + is ClassSymbol -> return flags.getValue(flag) + is MemberSymbol -> { + val memberFlagValue = flags.getValue(flag) + if (!memberFlagValue) { + return false + } + // Special case: if the MemberSymbol's flag is enabled, but the outer + // ClassSymbol's flag (if the class is flagged) is disabled, consider + // the MemberSymbol's flag as disabled: + // + // @FlaggedApi(this-flag-is-disabled) Clazz { + // @FlaggedApi(this-flag-is-enabled) method(); // The Clazz' flag "wins" + // } + // + // Note: the current implementation does not handle nested classes. + val classFlagValue = + flaggedSymbolsInSource + .find { it.first.toPrettyString() == symbol.clazz } + ?.let { flags.getValue(it.second) } + ?: true + return classFlagValue + } + } + } + + val errors = mutableSetOf<ApiError>() + for ((symbol, flag) in flaggedSymbolsInSource) { + try { + if (isFlagEnabledForSymbol(symbol, flag)) { + if (!symbolsInOutput.containsSymbol(symbol)) { + errors.add(EnabledFlaggedApiNotPresentError(symbol, flag)) + } + } else { + if (symbolsInOutput.containsSymbol(symbol)) { + errors.add(DisabledFlaggedApiIsPresentError(symbol, flag)) + } + } + } catch (e: NoSuchElementException) { + errors.add(UnknownFlagError(symbol, flag)) + } + } + return errors +} + +fun main(args: Array<String>) = CheckCommand().main(args) diff --git a/tools/finalization/OWNERS b/tools/finalization/OWNERS index 518b60db7e..b00b774b72 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 671b036e60..75379ff661 100755 --- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh +++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh @@ -133,10 +133,10 @@ function finalize_aidl_vndk_sdk_resources() { 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" - # build/bazel + # build/soong local codename_version="\"${FINAL_PLATFORM_CODENAME}\": ${FINAL_PLATFORM_SDK_VERSION}" - if ! grep -q "$codename_version" "$top/build/bazel/rules/common/api_constants.bzl" ; then - sed -i -e "/:.*$((${FINAL_PLATFORM_SDK_VERSION}-1)),/a \\ $codename_version," "$top/build/bazel/rules/common/api_constants.bzl" + if ! grep -q "$codename_version" "$top/build/soong/android/api_levels.go" ; then + sed -i -e "/:.*$((${FINAL_PLATFORM_SDK_VERSION}-1)),/a \\\t\t$codename_version," "$top/build/soong/android/api_levels.go" fi # cts diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp index bd9543a2e2..6aa528963d 100644 --- a/tools/fs_config/Android.bp +++ b/tools/fs_config/Android.bp @@ -258,3 +258,173 @@ prebuilt_etc { system_ext_specific: true, src: ":group_gen_system_ext", } + +fs_config_cmd = "$(location fs_config_generator) fsconfig " + + "--aid-header $(location :android_filesystem_config_header) " + + "--capability-header $(location :linux_capability_header) " + + "--out_file $(out) " +fs_config_cmd_dirs = fs_config_cmd + "--dirs " +fs_config_cmd_files = fs_config_cmd + "--files " + +genrule_defaults { + name: "fs_config_defaults", + tools: ["fs_config_generator"], + srcs: [ + ":android_filesystem_config_header", + ":linux_capability_header", + ":target_fs_config_gen", + ], + out: ["out"], +} + +genrule { + name: "fs_config_dirs_system_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_dirs + + "--partition system " + + "--all-partitions vendor,oem,odm,vendor_dlkm,odm_dlkm,system_dlkm " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_dirs_system", + filename: "fs_config_dirs", + src: ":fs_config_dirs_system_gen", +} + +genrule { + name: "fs_config_files_system_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_files + + "--partition system " + + "--all-partitions vendor,oem,odm,vendor_dlkm,odm_dlkm,system_dlkm " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_files_system", + filename: "fs_config_files", + src: ":fs_config_files_system_gen", +} + +genrule { + name: "fs_config_dirs_system_ext_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_dirs + + "--partition system_ext " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_dirs_system_ext", + filename: "fs_config_dirs", + src: ":fs_config_dirs_system_ext_gen", + system_ext_specific: true, +} + +genrule { + name: "fs_config_files_system_ext_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_files + + "--partition system_ext " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_files_system_ext", + filename: "fs_config_files", + src: ":fs_config_files_system_ext_gen", + system_ext_specific: true, +} + +genrule { + name: "fs_config_dirs_product_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_dirs + + "--partition product " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_dirs_product", + filename: "fs_config_dirs", + src: ":fs_config_dirs_product_gen", + product_specific: true, +} + +genrule { + name: "fs_config_files_product_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_files + + "--partition product " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_files_product", + filename: "fs_config_files", + src: ":fs_config_files_product_gen", + product_specific: true, +} + +genrule { + name: "fs_config_dirs_vendor_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_dirs + + "--partition vendor " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_dirs_vendor", + filename: "fs_config_dirs", + src: ":fs_config_dirs_vendor_gen", + vendor: true, +} + +genrule { + name: "fs_config_files_vendor_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_files + + "--partition vendor " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_files_vendor", + filename: "fs_config_files", + src: ":fs_config_files_vendor_gen", + vendor: true, +} + +genrule { + name: "fs_config_dirs_odm_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_dirs + + "--partition odm " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_dirs_odm", + filename: "fs_config_dirs", + src: ":fs_config_dirs_odm_gen", + device_specific: true, +} + +genrule { + name: "fs_config_files_odm_gen", + defaults: ["fs_config_defaults"], + cmd: fs_config_cmd_files + + "--partition odm " + + "$(locations :target_fs_config_gen)", +} + +prebuilt_etc { + name: "fs_config_files_odm", + filename: "fs_config_files", + src: ":fs_config_files_odm_gen", + device_specific: true, +} + +// TODO(jiyong): add fs_config for oem, system_dlkm, vendor_dlkm, odm_dlkm partitions diff --git a/tools/fs_config/Android.mk b/tools/fs_config/Android.mk index c36c3aa6a2..e4c362630f 100644 --- a/tools/fs_config/Android.mk +++ b/tools/fs_config/Android.mk @@ -24,23 +24,8 @@ ifneq ($(wildcard $(TARGET_DEVICE_DIR)/android_filesystem_config.h),) $(error Using $(TARGET_DEVICE_DIR)/android_filesystem_config.h is deprecated, please use TARGET_FS_CONFIG_GEN instead) endif -system_android_filesystem_config := system/core/libcutils/include/private/android_filesystem_config.h -system_capability_header := bionic/libc/kernel/uapi/linux/capability.h - -# Use snapshots if exist -vendor_android_filesystem_config := $(strip \ - $(if $(filter-out current,$(BOARD_VNDK_VERSION)), \ - $(SOONG_VENDOR_$(BOARD_VNDK_VERSION)_SNAPSHOT_DIR)/include/$(system_android_filesystem_config))) -ifeq (,$(wildcard $(vendor_android_filesystem_config))) -vendor_android_filesystem_config := $(system_android_filesystem_config) -endif - -vendor_capability_header := $(strip \ - $(if $(filter-out current,$(BOARD_VNDK_VERSION)), \ - $(SOONG_VENDOR_$(BOARD_VNDK_VERSION)_SNAPSHOT_DIR)/include/$(system_capability_header))) -ifeq (,$(wildcard $(vendor_capability_header))) -vendor_capability_header := $(system_capability_header) -endif +android_filesystem_config := system/core/libcutils/include/private/android_filesystem_config.h +capability_header := bionic/libc/kernel/uapi/linux/capability.h # List of supported vendor, oem, odm, vendor_dlkm, odm_dlkm, and system_dlkm Partitions fs_config_generate_extra_partition_list := $(strip \ @@ -85,58 +70,6 @@ LOCAL_REQUIRED_MODULES := \ include $(BUILD_PHONY_PACKAGE) ################################## -# Generate the system_ext/etc/fs_config_dirs binary file for the target if the -# system_ext partition is generated. Add fs_config_dirs or fs_config_dirs_system_ext -# to PRODUCT_PACKAGES in the device make file to enable. -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_dirs_system_ext -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_system_ext) -include $(BUILD_PHONY_PACKAGE) - -################################## -# Generate the system_ext/etc/fs_config_files binary file for the target if the -# system_ext partition is generated. Add fs_config_files or fs_config_files_system_ext -# to PRODUCT_PACKAGES in the device make file to enable. -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_files_system_ext -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_system_ext) -include $(BUILD_PHONY_PACKAGE) - -################################## -# Generate the product/etc/fs_config_dirs binary file for the target if the -# product partition is generated. Add fs_config_dirs or fs_config_dirs_product -# to PRODUCT_PACKAGES in the device make file to enable. -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_dirs_product -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_product) -include $(BUILD_PHONY_PACKAGE) - -################################## -# Generate the product/etc/fs_config_files binary file for the target if the -# product partition is generated. Add fs_config_files or fs_config_files_product -# to PRODUCT_PACKAGES in the device make file to enable. -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_files_product -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_product) -include $(BUILD_PHONY_PACKAGE) - -################################## # Generate the <p>/etc/fs_config_dirs binary files for all enabled partitions # excluding /system, /system_ext and /product. Add fs_config_dirs_nonsystem to # PRODUCT_PACKAGES in the device make file to enable. @@ -146,7 +79,7 @@ LOCAL_MODULE := fs_config_dirs_nonsystem LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_dirs_$(t)) +LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),fs_config_dirs_$(t)) include $(BUILD_PHONY_PACKAGE) ################################## @@ -159,122 +92,9 @@ LOCAL_MODULE := fs_config_files_nonsystem LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_files_$(t)) +LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),fs_config_files_$(t)) include $(BUILD_PHONY_PACKAGE) -################################## -# Generate the system/etc/fs_config_dirs binary file for the target -# Add fs_config_dirs or fs_config_dirs_system to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_dirs_system -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_PARTITION_LIST := $(fs_config_generate_extra_partition_list) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition system \ - --all-partitions "$(subst $(space),$(comma),$(PRIVATE_PARTITION_LIST))" \ - --dirs \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -################################## -# Generate the system/etc/fs_config_files binary file for the target -# Add fs_config_files or fs_config_files_system to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := fs_config_files_system -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_files -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_PARTITION_LIST := $(fs_config_generate_extra_partition_list) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition system \ - --all-partitions "$(subst $(space),$(comma),$(PRIVATE_PARTITION_LIST))" \ - --files \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -ifneq ($(filter vendor,$(fs_config_generate_extra_partition_list)),) -################################## -# Generate the vendor/etc/fs_config_dirs binary file for the target -# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES -# in the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_dirs_vendor -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs -LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition vendor \ - --dirs \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -################################## -# Generate the vendor/etc/fs_config_files binary file for the target -# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES -# in the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_files_vendor -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_files -LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition vendor \ - --files \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -endif - ifneq ($(filter oem,$(fs_config_generate_extra_partition_list)),) ################################## # Generate the oem/etc/fs_config_dirs binary file for the target @@ -282,7 +102,7 @@ ifneq ($(filter oem,$(fs_config_generate_extra_partition_list)),) # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_dirs_oem +LOCAL_MODULE := fs_config_dirs_oem LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -290,10 +110,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -309,7 +129,7 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_files_oem +LOCAL_MODULE := fs_config_files_oem LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -317,10 +137,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_files LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -332,63 +152,6 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G endif -ifneq ($(filter odm,$(fs_config_generate_extra_partition_list)),) -################################## -# Generate the odm/etc/fs_config_dirs binary file for the target -# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES -# in the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_dirs_odm -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs -LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition odm \ - --dirs \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -################################## -# Generate the odm/etc/fs_config_files binary file for the target -# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES -# in the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_files_odm -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_files -LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition odm \ - --files \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -endif - ifneq ($(filter vendor_dlkm,$(fs_config_generate_extra_partition_list)),) ################################## # Generate the vendor_dlkm/etc/fs_config_dirs binary file for the target @@ -396,7 +159,7 @@ ifneq ($(filter vendor_dlkm,$(fs_config_generate_extra_partition_list)),) # the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_dirs_vendor_dlkm +LOCAL_MODULE := fs_config_dirs_vendor_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -404,10 +167,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -423,7 +186,7 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G # the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_files_vendor_dlkm +LOCAL_MODULE := fs_config_files_vendor_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -431,10 +194,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_files LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -453,7 +216,7 @@ ifneq ($(filter odm_dlkm,$(fs_config_generate_extra_partition_list)),) # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_dirs_odm_dlkm +LOCAL_MODULE := fs_config_dirs_odm_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -461,10 +224,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -480,7 +243,7 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_files_odm_dlkm +LOCAL_MODULE := fs_config_files_odm_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -488,10 +251,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_files LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -510,7 +273,7 @@ ifneq ($(filter system_dlkm,$(fs_config_generate_extra_partition_list)),) # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_dirs_system_dlkm +LOCAL_MODULE := fs_config_dirs_system_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -518,10 +281,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -537,7 +300,7 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G # in the device make file to enable include $(CLEAR_VARS) -LOCAL_MODULE := _fs_config_files_system_dlkm +LOCAL_MODULE := fs_config_files_system_dlkm LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE @@ -545,10 +308,10 @@ LOCAL_MODULE_CLASS := ETC LOCAL_INSTALLED_MODULE_STEM := fs_config_files LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(android_filesystem_config) +$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(capability_header) $(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(android_filesystem_config) $(capability_header) @mkdir -p $(dir $@) $< fsconfig \ --aid-header $(PRIVATE_ANDROID_FS_HDR) \ @@ -560,118 +323,6 @@ $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_G endif -ifneq ($(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),) -################################## -# Generate the product/etc/fs_config_dirs binary file for the target -# Add fs_config_dirs or fs_config_dirs_product to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_dirs_product -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs -LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition product \ - --dirs \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -################################## -# Generate the product/etc/fs_config_files binary file for the target -# Add fs_config_files or fs_config_files_product to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_files_product -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_files -LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition product \ - --files \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) -endif - -ifneq ($(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),) -################################## -# Generate the system_ext/etc/fs_config_dirs binary file for the target -# Add fs_config_dirs or fs_config_dirs_system_ext to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_dirs_system_ext -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs -LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition system_ext \ - --dirs \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) - -################################## -# Generate the system_ext/etc/fs_config_files binary file for the target -# Add fs_config_files or fs_config_files_system_ext to PRODUCT_PACKAGES in -# the device make file to enable -include $(CLEAR_VARS) - -LOCAL_MODULE := _fs_config_files_system_ext -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE -LOCAL_MODULE_CLASS := ETC -LOCAL_INSTALLED_MODULE_STEM := fs_config_files -LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc -include $(BUILD_SYSTEM)/base_rules.mk -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(system_android_filesystem_config) -$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(system_capability_header) -$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN) -$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(system_android_filesystem_config) $(system_capability_header) - @mkdir -p $(dir $@) - $< fsconfig \ - --aid-header $(PRIVATE_ANDROID_FS_HDR) \ - --capability-header $(PRIVATE_ANDROID_CAP_HDR) \ - --partition system_ext \ - --files \ - --out_file $@ \ - $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null) -endif - -system_android_filesystem_config := -system_capability_header := +android_filesystem_config := +capability_header := fs_config_generate_extra_partition_list := diff --git a/tools/ide_query/ide_query.go b/tools/ide_query/ide_query.go index 50264fd180..de84fbe3e4 100644 --- a/tools/ide_query/ide_query.go +++ b/tools/ide_query/ide_query.go @@ -304,6 +304,7 @@ func runMake(ctx context.Context, env Env, modules ...string) error { args := []string{ "--make-mode", "ANDROID_BUILD_ENVIRONMENT_CONFIG=googler-cog", + "SOONG_GEN_COMPDB=1", "TARGET_PRODUCT=" + env.LunchTarget.Product, "TARGET_RELEASE=" + env.LunchTarget.Release, "TARGET_BUILD_VARIANT=" + env.LunchTarget.Variant, diff --git a/tools/lunchable b/tools/lunchable new file mode 100755 index 0000000000..fce2c2719d --- /dev/null +++ b/tools/lunchable @@ -0,0 +1,72 @@ +#!/bin/bash + +# TODO: Currently only checks trunk_staging. Should check trunk_staging first, +# then use the product-specfic releases. Only applies to -c though. + +function Help() { +cat <<@EOF@ +Usage: lunchable [options] + +Lists products that have no functioning lunch combo. + +options: +-c prints all failing lunch combos for all targets; +-w why? Prints the error message after each failed lunch combo. Only + works with -c + +@EOF@ +} + +complete=0 +why=0 +while getopts "cwh" option; do + case $option in + c) + complete=1;; + w) + why=1;; + h) + Help + exit;; + esac +done + +# Getting all named products can fail if we haven't lunched anything +source $(pwd)/build/envsetup.sh &> /dev/null +all_named_products=( $(get_build_var all_named_products 2> /dev/null) ) +if [[ $? -ne 0 ]]; then + echo "get_build_var all_named_products failed. Lunch something first?" >&2 + exit 1 +fi +total_products=${#all_named_products[@]} +current_product=0 + +for product in "${all_named_products[@]}"; do + (( current_product += 1 )) + single_pass=0 + printf " Checking ${current_product}/${total_products} \r" >&2 + for release in trunk_staging; do + for variant in eng user userdebug; do + lunchcombo="${product}-${release}-${variant}" + lunch_error="$(lunch $lunchcombo 2>&1 > /dev/null)" + if [[ $? -ne 0 ]]; then + # Lunch failed + if [[ $complete -eq 1 ]]; then + echo -e "${product} : ${lunchcombo}" + if [[ $why -eq 1 ]]; then + echo -e "$(sed 's/^/ /g' <<<$lunch_error)" + fi + fi + elif [[ $complete -ne 1 ]]; then + single_pass=1 + break # skip variant + fi + done + if [[ $single_pass -eq 1 ]]; then + break # skip release + fi + done + if [[ $complete -eq 0 ]] && [[ $single_pass -eq 0 ]]; then + echo "${product}" + fi +done diff --git a/tools/protos/metadata_file.proto b/tools/protos/metadata_file.proto index 47562c580d..5c8961812c 100644 --- a/tools/protos/metadata_file.proto +++ b/tools/protos/metadata_file.proto @@ -282,7 +282,7 @@ message SBOMRef { 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 @@ message Identifier { // - "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 4941c710ff..9b134f22d4 100644 --- a/tools/releasetools/Android.bp +++ b/tools/releasetools/Android.bp @@ -244,7 +244,6 @@ python_library_host { "boot_signer", "brotli", "bsdiff", - "imgdiff", "lz4", "mkbootfs", "signapk", @@ -308,7 +307,6 @@ python_defaults { "brotli", "bsdiff", "deapexer", - "imgdiff", "lz4", "mkbootfs", "signapk", @@ -634,7 +632,6 @@ python_defaults { data: [ "testdata/**/*", ":com.android.apex.compressed.v1", - ":com.android.apex.compressed.v1_original", ":com.android.apex.vendor.foo.with_vintf" ], target: { diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 88362489fd..2367691e43 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -490,7 +490,6 @@ class BuildInfo(object): return -1 props = [ - "ro.board.api_level", "ro.board.first_api_level", "ro.product.first_api_level", ] @@ -955,6 +954,13 @@ def LoadInfoDict(input_file, repacking=False): 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() @@ -1517,7 +1523,7 @@ def GetAvbPartitionsArg(partitions, AVB_ARG_NAME_CHAIN_PARTITION: [] } - for partition, path in partitions.items(): + for partition, path in sorted(partitions.items()): avb_partition_arg = GetAvbPartitionArg(partition, path, info_dict) if not avb_partition_arg: continue @@ -1605,7 +1611,7 @@ def BuildVBMeta(image_path, partitions, name, needed_partitions, "avb_custom_vbmeta_images_partition_list", "").strip().split()] avb_partitions = {} - for partition, path in partitions.items(): + for partition, path in sorted(partitions.items()): if partition not in needed_partitions: continue assert (partition in AVB_PARTITIONS or diff --git a/tools/releasetools/create_brick_ota.py b/tools/releasetools/create_brick_ota.py index 9e040a53d4..bf50f71049 100644 --- a/tools/releasetools/create_brick_ota.py +++ b/tools/releasetools/create_brick_ota.py @@ -45,10 +45,10 @@ def CreateBrickOta(product_name: str, output_path: Path, extra_wipe_partitions: 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) + 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: diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index c0ff5d2741..432ea199bb 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -195,6 +195,8 @@ A/B OTA specific options ro.product.* properties are overridden by the 'import' statement. The file expects one property per line, and each line has the following format: 'prop_name=value1,value2'. e.g. 'ro.boot.product.sku=std,pro' + The path specified can either be relative to the current working directory + or the path to a file inside of input_target_files. --skip_postinstall Skip the postinstall hooks when generating an A/B OTA package (default: @@ -745,7 +747,7 @@ def GetTargetFilesZipForRetrofitDynamicPartitions(input_file, os.rename(source_path, target_path) # Write new ab_partitions.txt file - new_ab_partitions = os.paht.join(input_file, AB_PARTITIONS) + new_ab_partitions = os.path.join(input_file, AB_PARTITIONS) with open(new_ab_partitions, 'w') as f: for partition in ab_partitions: if (partition in dynamic_partition_list and @@ -872,6 +874,7 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): 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 +910,6 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): 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 +920,7 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): 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 +933,19 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): 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] @@ -1034,6 +1050,10 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): from check_target_files_vintf import CheckVintfIfTrebleEnabled CheckVintfIfTrebleEnabled(target_file, target_info) + # Allow boot_variable_file to also exist in target-files + if OPTIONS.boot_variable_file: + if not os.path.isfile(OPTIONS.boot_variable_file): + OPTIONS.boot_variable_file = os.path.join(target_file, OPTIONS.boot_variable_file) # Metadata to comply with Android OTA package format. metadata = GetPackageMetadata(target_info, source_info) # Generate payload. @@ -1274,7 +1294,7 @@ def main(argv): 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/ota_utils.py b/tools/releasetools/ota_utils.py index 048a497585..81b53dce36 100644 --- a/tools/releasetools/ota_utils.py +++ b/tools/releasetools/ota_utils.py @@ -1111,9 +1111,10 @@ def CopyTargetFilesDir(input_dir): relative_path = path.removeprefix(input_dir).removeprefix("/") if not Fnmatch(relative_path, UNZIP_PATTERN): continue - if filename.endswith(".prop") or filename == "prop.default" or "/etc/vintf/" in relative_path: - target_path = os.path.join( - output_dir, relative_path) - os.makedirs(os.path.dirname(target_path), exist_ok=True) - shutil.copy(path, target_path) + target_path = os.path.join( + output_dir, relative_path) + if os.path.exists(target_path): + continue + os.makedirs(os.path.dirname(target_path), exist_ok=True) + shutil.copy(path, target_path) return output_dir diff --git a/tools/tool_event_logger/Android.bp b/tools/tool_event_logger/Android.bp new file mode 100644 index 0000000000..7a1d2aaa71 --- /dev/null +++ b/tools/tool_event_logger/Android.bp @@ -0,0 +1,67 @@ +// 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. + +// Set of error prone rules to ensure code quality +// PackageLocation check requires the androidCompatible=false otherwise it does not do anything. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], + default_team: "trendy_team_adte", +} + +python_library_host { + name: "tool_event_proto", + srcs: [ + "proto/tool_event.proto", + ], + proto: { + canonical_path_from_root: false, + }, +} + +python_binary_host { + name: "tool_event_logger", + pkg_path: "tool_event_logger", + srcs: [ + "tool_event_logger.py", + ], + libs: [ + "asuite_cc_client", + "tool_event_proto", + ], + main: "tool_event_logger.py", +} + +python_test_host { + name: "tool_event_logger_test", + main: "tool_event_logger_test.py", + pkg_path: "tool_event_logger", + srcs: [ + "tool_event_logger.py", + "tool_event_logger_test.py", + ], + test_options: { + unit_test: true, + }, + libs: [ + "asuite_cc_client", + "tool_event_proto", + ], + version: { + py3: { + embedded_launcher: true, + enabled: true, + }, + }, +} diff --git a/tools/tool_event_logger/OWNERS b/tools/tool_event_logger/OWNERS new file mode 100644 index 0000000000..b692c9edf3 --- /dev/null +++ b/tools/tool_event_logger/OWNERS @@ -0,0 +1,4 @@ +include platform/tools/asuite:/OWNERS + +zhuoyao@google.com +hzalek@google.com
\ No newline at end of file diff --git a/tools/tool_event_logger/proto/tool_event.proto b/tools/tool_event_logger/proto/tool_event.proto new file mode 100644 index 0000000000..61e28a25e7 --- /dev/null +++ b/tools/tool_event_logger/proto/tool_event.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package tools.asuite.tool_event_logger; + +message ToolEvent { + // Occurs immediately upon execution of the tool. + message InvocationStarted { + string command_args = 1; + string cwd = 2; + string os = 3; + } + + // Occurs when tool exits for any reason. + message InvocationStopped { + int32 exit_code = 2; + string exit_log = 3; + } + + // ------------------------ + // FIELDS FOR ToolEvent + // ------------------------ + // Random string generated to identify the invocation. + string invocation_id = 1; + // Internal user name. + string user_name = 2; + // The root of Android source. + string source_root = 3; + // Name of the tool used. + string tool_tag = 6; + + oneof event { + InvocationStarted invocation_started = 4; + InvocationStopped invocation_stopped = 5; + } +} diff --git a/tools/tool_event_logger/tool_event_logger.py b/tools/tool_event_logger/tool_event_logger.py new file mode 100644 index 0000000000..65a9696011 --- /dev/null +++ b/tools/tool_event_logger/tool_event_logger.py @@ -0,0 +1,229 @@ +# 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 argparse +import datetime +import getpass +import logging +import os +import platform +import sys +import tempfile +import uuid + +from atest.metrics import clearcut_client +from atest.proto import clientanalytics_pb2 +from proto import tool_event_pb2 + +LOG_SOURCE = 2395 + + +class ToolEventLogger: + """Logs tool events to Sawmill through Clearcut.""" + + def __init__( + self, + tool_tag: str, + invocation_id: str, + user_name: str, + source_root: str, + platform_version: str, + python_version: str, + client: clearcut_client.Clearcut, + ): + self.tool_tag = tool_tag + self.invocation_id = invocation_id + self.user_name = user_name + self.source_root = source_root + self.platform_version = platform_version + self.python_version = python_version + self._clearcut_client = client + + @classmethod + def create(cls, tool_tag: str): + return ToolEventLogger( + tool_tag=tool_tag, + invocation_id=str(uuid.uuid4()), + user_name=getpass.getuser(), + source_root=os.environ.get('ANDROID_BUILD_TOP', ''), + platform_version=platform.platform(), + python_version=platform.python_version(), + client=clearcut_client.Clearcut(LOG_SOURCE), + ) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.flush() + + def log_invocation_started(self, event_time: datetime, command_args: str): + """Creates an event log with invocation started info.""" + event = self._create_tool_event() + event.invocation_started.CopyFrom( + tool_event_pb2.ToolEvent.InvocationStarted( + command_args=command_args, + os=f'{self.platform_version}:{self.python_version}', + ) + ) + + logging.debug('Log invocation_started: %s', event) + self._log_clearcut_event(event, event_time) + + def log_invocation_stopped( + self, + event_time: datetime, + exit_code: int, + exit_log: str, + ): + """Creates an event log with invocation stopped info.""" + event = self._create_tool_event() + event.invocation_stopped.CopyFrom( + tool_event_pb2.ToolEvent.InvocationStopped( + exit_code=exit_code, + exit_log=exit_log, + ) + ) + + logging.debug('Log invocation_stopped: %s', event) + self._log_clearcut_event(event, event_time) + + def flush(self): + """Sends all batched events to Clearcut.""" + logging.debug('Sending events to Clearcut.') + self._clearcut_client.flush_events() + + def _create_tool_event(self): + return tool_event_pb2.ToolEvent( + tool_tag=self.tool_tag, + invocation_id=self.invocation_id, + user_name=self.user_name, + source_root=self.source_root, + ) + + def _log_clearcut_event( + self, tool_event: tool_event_pb2.ToolEvent, event_time: datetime + ): + log_event = clientanalytics_pb2.LogEvent( + event_time_ms=int(event_time.timestamp() * 1000), + source_extension=tool_event.SerializeToString(), + ) + self._clearcut_client.log(log_event) + + +class ArgumentParserWithLogging(argparse.ArgumentParser): + + def error(self, message): + logging.error('Failed to parse args with error: %s', message) + super().error(message) + + +def create_arg_parser(): + """Creates an instance of the default ToolEventLogger arg parser.""" + + parser = ArgumentParserWithLogging( + description='Build and upload logs for Android dev tools', + add_help=True, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + '--tool_tag', + type=str, + required=True, + help='Name of the tool.', + ) + + parser.add_argument( + '--start_timestamp', + type=lambda ts: datetime.datetime.fromtimestamp(float(ts)), + required=True, + help=( + 'Timestamp when the tool starts. The timestamp should have the format' + '%s.%N which represents the seconds elapses since epoch.' + ), + ) + + parser.add_argument( + '--end_timestamp', + type=lambda ts: datetime.datetime.fromtimestamp(float(ts)), + required=True, + help=( + 'Timestamp when the tool exits. The timestamp should have the format' + '%s.%N which represents the seconds elapses since epoch.' + ), + ) + + parser.add_argument( + '--tool_args', + type=str, + help='Parameters that are passed to the tool.', + ) + + parser.add_argument( + '--exit_code', + type=int, + required=True, + help='Tool exit code.', + ) + + parser.add_argument( + '--exit_log', + type=str, + help='Logs when tool exits.', + ) + + parser.add_argument( + '--dry_run', + action='store_true', + help='Dry run the tool event logger if set.', + ) + + return parser + + +def configure_logging(): + root_logging_dir = tempfile.mkdtemp(prefix='tool_event_logger_') + + log_fmt = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s' + date_fmt = '%Y-%m-%d %H:%M:%S' + _, log_path = tempfile.mkstemp(dir=root_logging_dir, suffix='.log') + + logging.basicConfig( + filename=log_path, level=logging.DEBUG, format=log_fmt, datefmt=date_fmt + ) + + +def main(argv: list[str]): + args = create_arg_parser().parse_args(argv[1:]) + + if args.dry_run: + logging.debug('This is a dry run.') + return + + try: + with ToolEventLogger.create(args.tool_tag) as logger: + logger.log_invocation_started(args.start_timestamp, args.tool_args) + logger.log_invocation_stopped( + args.end_timestamp, args.exit_code, args.exit_log + ) + except Exception as e: + logging.error('Log failed with unexpected error: %s', e) + raise + + +if __name__ == '__main__': + configure_logging() + main(sys.argv) diff --git a/tools/tool_event_logger/tool_event_logger_test.py b/tools/tool_event_logger/tool_event_logger_test.py new file mode 100644 index 0000000000..34b6c357cc --- /dev/null +++ b/tools/tool_event_logger/tool_event_logger_test.py @@ -0,0 +1,209 @@ +# 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. + +"""Unittests for ToolEventLogger.""" + +import datetime +import logging +import unittest +from unittest import mock + +from atest.metrics import clearcut_client +from proto import tool_event_pb2 +from tool_event_logger import tool_event_logger + +TEST_INVOCATION_ID = 'test_invocation_id' +TEST_USER_NAME = 'test_user' +TEST_TOOL_TAG = 'test_tool' +TEST_SOURCE_ROOT = 'test_source_root' +TEST_PLATFORM_VERSION = 'test_platform_version' +TEST_PYTHON_VERSION = 'test_python_version' +TEST_EVENT_TIMESTAMP = datetime.datetime.now() + + +class ToolEventLoggerTest(unittest.TestCase): + + def setUp(self): + super().setUp() + self.clearcut_client = FakeClearcutClient() + self.logger = tool_event_logger.ToolEventLogger( + TEST_TOOL_TAG, + TEST_INVOCATION_ID, + TEST_USER_NAME, + TEST_SOURCE_ROOT, + TEST_PLATFORM_VERSION, + TEST_PYTHON_VERSION, + client=self.clearcut_client, + ) + + def test_log_event_timestamp(self): + with self.logger: + self.logger.log_invocation_started( + datetime.datetime.fromtimestamp(100.101), 'test_command' + ) + + self.assertEqual( + self.clearcut_client.get_last_sent_event().event_time_ms, 100101 + ) + + def test_log_event_basic_information(self): + with self.logger: + self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') + + sent_event = self.clearcut_client.get_last_sent_event() + log_event = tool_event_pb2.ToolEvent.FromString(sent_event.source_extension) + self.assertEqual(log_event.invocation_id, TEST_INVOCATION_ID) + self.assertEqual(log_event.user_name, TEST_USER_NAME) + self.assertEqual(log_event.tool_tag, TEST_TOOL_TAG) + self.assertEqual(log_event.source_root, TEST_SOURCE_ROOT) + + def test_log_invocation_started(self): + expected_invocation_started = tool_event_pb2.ToolEvent.InvocationStarted( + command_args='test_command', + os=TEST_PLATFORM_VERSION + ':' + TEST_PYTHON_VERSION, + ) + + with self.logger: + self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') + + self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 1) + sent_event = self.clearcut_client.get_last_sent_event() + self.assertEqual( + expected_invocation_started, + tool_event_pb2.ToolEvent.FromString( + sent_event.source_extension + ).invocation_started, + ) + + def test_log_invocation_stopped(self): + expected_invocation_stopped = tool_event_pb2.ToolEvent.InvocationStopped( + exit_code=0, + exit_log='exit_log', + ) + + with self.logger: + self.logger.log_invocation_stopped(TEST_EVENT_TIMESTAMP, 0, 'exit_log') + + self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 1) + sent_event = self.clearcut_client.get_last_sent_event() + self.assertEqual( + expected_invocation_stopped, + tool_event_pb2.ToolEvent.FromString( + sent_event.source_extension + ).invocation_stopped, + ) + + def test_log_multiple_events(self): + with self.logger: + self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') + self.logger.log_invocation_stopped(TEST_EVENT_TIMESTAMP, 0, 'exit_log') + + self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 2) + + +class MainTest(unittest.TestCase): + + REQUIRED_ARGS = [ + '', + '--tool_tag', + 'test_tool', + '--start_timestamp', + '1', + '--end_timestamp', + '2', + '--exit_code', + '0', + ] + + def test_log_and_exit_with_missing_required_args(self): + with self.assertLogs() as logs: + with self.assertRaises(SystemExit) as ex: + tool_event_logger.main(['', '--tool_tag', 'test_tool']) + + with self.subTest('Verify exception code'): + self.assertEqual(ex.exception.code, 2) + + with self.subTest('Verify log messages'): + self.assertIn( + 'the following arguments are required', + '\n'.join(logs.output), + ) + + def test_log_and_exit_with_invalid_args(self): + with self.assertLogs() as logs: + with self.assertRaises(SystemExit) as ex: + tool_event_logger.main(['', '--start_timestamp', 'test']) + + with self.subTest('Verify exception code'): + self.assertEqual(ex.exception.code, 2) + + with self.subTest('Verify log messages'): + self.assertIn( + '--start_timestamp: invalid', + '\n'.join(logs.output), + ) + + def test_log_and_exit_with_dry_run(self): + with self.assertLogs(level=logging.DEBUG) as logs: + tool_event_logger.main(self.REQUIRED_ARGS + ['--dry_run']) + + with self.subTest('Verify log messages'): + self.assertIn('dry run', '\n'.join(logs.output)) + + @mock.patch.object(clearcut_client, 'Clearcut') + def test_log_and_exit_with_unexpected_exception(self, mock_cc): + mock_cc.return_value = FakeClearcutClient(raise_log_exception=True) + + with self.assertLogs() as logs: + with self.assertRaises(Exception) as ex: + tool_event_logger.main(self.REQUIRED_ARGS) + + with self.subTest('Verify log messages'): + self.assertIn('unexpected error', '\n'.join(logs.output)) + + @mock.patch.object(clearcut_client, 'Clearcut') + def test_success(self, mock_cc): + mock_clear_cut_client = FakeClearcutClient() + mock_cc.return_value = mock_clear_cut_client + + tool_event_logger.main(self.REQUIRED_ARGS) + + self.assertEqual(mock_clear_cut_client.get_number_of_sent_events(), 2) + + +class FakeClearcutClient: + + def __init__(self, raise_log_exception=False): + self.pending_log_events = [] + self.sent_log_events = [] + self.raise_log_exception = raise_log_exception + + def log(self, log_event): + if self.raise_log_exception: + raise Exception('unknown exception') + self.pending_log_events.append(log_event) + + def flush_events(self): + self.sent_log_events.extend(self.pending_log_events) + self.pending_log_events.clear() + + def get_number_of_sent_events(self): + return len(self.sent_log_events) + + def get_last_sent_event(self): + return self.sent_log_events[-1] + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/whichgit b/tools/whichgit index b0bf2e42f8..55c8c6fb5a 100755 --- a/tools/whichgit +++ b/tools/whichgit @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import itertools import os import subprocess import sys @@ -10,15 +11,34 @@ def get_build_var(var): check=True, capture_output=True, text=True).stdout.strip() +def get_all_modules(): + product_out = subprocess.run(["build/soong/soong_ui.bash", "--dumpvar-mode", "--abs", "PRODUCT_OUT"], + check=True, capture_output=True, text=True).stdout.strip() + result = subprocess.run(["cat", product_out + "/all_modules.txt"], check=True, capture_output=True, text=True) + return result.stdout.strip().split("\n") + + +def batched(iterable, n): + # introduced in itertools 3.12, could delete once that's universally available + if n < 1: + raise ValueError('n must be at least one') + it = iter(iterable) + while batch := tuple(itertools.islice(it, n)): + yield batch + + def get_sources(modules): - result = subprocess.run(["./prebuilts/build-tools/linux-x86/bin/ninja", "-f", - "out/combined-" + os.environ["TARGET_PRODUCT"] + ".ninja", - "-t", "inputs", "-d", ] + modules, - stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=False, text=True) - if result.returncode != 0: - sys.stderr.write(result.stdout) - sys.exit(1) - return set([f for f in result.stdout.split("\n") if not f.startswith("out/")]) + sources = set() + for module_group in batched(modules, 40_000): + result = subprocess.run(["./prebuilts/build-tools/linux-x86/bin/ninja", "-f", + "out/combined-" + os.environ["TARGET_PRODUCT"] + ".ninja", + "-t", "inputs", "-d", ] + list(module_group), + stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=False, text=True) + if result.returncode != 0: + sys.stderr.write(result.stdout) + sys.exit(1) + sources.update(set([f for f in result.stdout.split("\n") if not f.startswith("out/")])) + return sources def m_nothing(): @@ -50,57 +70,76 @@ def get_referenced_projects(git_dirs, files): referenced_dirs.add(d) prev_dir = d break - return [d[0:-1] for d in referenced_dirs] + return referenced_dirs def main(argv): # Argument parsing ap = argparse.ArgumentParser(description="List the required git projects for the given modules") ap.add_argument("--products", nargs="*", - help="The TARGET_PRODUCT to check. If not provided just uses whatever has" - + " already been built") + help="One or more TARGET_PRODUCT to check, or \"*\" for all. If not provided" + + "just uses whatever has already been built") ap.add_argument("--variants", nargs="*", help="The TARGET_BUILD_VARIANTS to check. If not provided just uses whatever has" + " already been built, or eng if --products is supplied") ap.add_argument("--modules", nargs="*", - help="The build modules to check, or droid it not supplied") + help="The build modules to check, or \"*\" for all, or droid if not supplied") ap.add_argument("--why", nargs="*", help="Also print the input files used in these projects, or \"*\" for all") + ap.add_argument("--unused", help="List the unused git projects for the given modules rather than" + + "the used ones. Ignores --why", action="store_true") args = ap.parse_args(argv[1:]) modules = args.modules if args.modules else ["droid"] + match args.products: + case ["*"]: + products = get_build_var("all_named_products").split(" ") + case _: + products = args.products + # Get the list of sources for all of the requested build combos - if not args.products and not args.variants: + if not products and not args.variants: + m_nothing() + if args.modules == ["*"]: + modules = get_all_modules() sources = get_sources(modules) else: - if not args.products: + if not products: sys.stderr.write("Error: --products must be supplied if --variants is supplied") sys.exit(1) sources = set() build_num = 1 - for product in args.products: + for product in products: os.environ["TARGET_PRODUCT"] = product variants = args.variants if args.variants else ["user", "userdebug", "eng"] for variant in variants: - sys.stderr.write(f"Analyzing build {build_num} of {len(args.products)*len(variants)}\r") + sys.stderr.write(f"Analyzing build {build_num} of {len(products)*len(variants)}\r") os.environ["TARGET_BUILD_VARIANT"] = variant m_nothing() + if args.modules == ["*"]: + modules = get_all_modules() sources.update(get_sources(modules)) build_num += 1 sys.stderr.write("\n\n") sources = sorted(sources) - # Print the list of git directories that has one or more of the sources in it - for project in sorted(get_referenced_projects(get_git_dirs(), sources)): - print(project) - if args.why: - if "*" in args.why or project in args.why: - prefix = project + "/" - for f in sources: - if f.startswith(prefix): - print(" " + f) + if args.unused: + # Print the list of git directories that don't contain sources + used_git_dirs = set(get_git_dirs()) + for project in sorted(used_git_dirs.difference(set(get_referenced_projects(used_git_dirs, sources)))): + print(project[0:-1]) + else: + # Print the list of git directories that has one or more of the sources in it + for project in sorted(get_referenced_projects(get_git_dirs(), sources)): + print(project[0:-1]) + if args.why: + if "*" in args.why or project[0:-1] in args.why: + prefix = project + for f in sources: + if f.startswith(prefix): + print(" " + f) if __name__ == "__main__": |