diff options
151 files changed, 5953 insertions, 2783 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/METADATA b/METADATA deleted file mode 100644 index 44781a7088..0000000000 --- a/METADATA +++ /dev/null @@ -1,8 +0,0 @@ -third_party { - license_note: "would be NOTICE save for GPL in:\n" - " core/LINUX_KERNEL_COPYING\n" - " tools/droiddoc/templates-pdk/assets/jquery-1.6.2.min.js\n" - " tools/droiddoc/templates-pdk/assets/jquery-history.js\n" - " tools/droiddoc/templates-pdk/assets/jquery-resizable.min.js" - license_type: RESTRICTED -} 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 1d5b3774d9..29ed50e095 100644 --- a/ci/build_test_suites.py +++ b/ci/build_test_suites.py @@ -12,404 +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 - - -# List of modules that are always required to be in general-tests.zip -REQUIRED_MODULES = frozenset( - ['cts-tradefed', 'vts-tradefed', 'compatibility-host-util', 'soong_zip'] -) - - -def build_test_suites(argv): - args = parse_args(argv) - - if 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 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('--change_info', nargs='?') +class Error(Exception): - return argparser.parse_args() + def __init__(self, message): + super().__init__(message) -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 +class BuildFailureError(Error): + def __init__(self, return_code): + super().__init__(f'Build command failed with return code: f{return_code}') + self.return_code = return_code -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.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('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], 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)) - dist_dir = pathlib.Path(get_soong_var('DIST_DIR', target_release)) - - # Call the class to package the outputs. - # TODO(lucafarsi): Move this code into a replaceable class. - 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 b245d3264e..02deaddd82 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 @@ -1472,13 +1477,14 @@ boototapackage_4k: $(BUILT_BOOT_OTA_PACKAGE_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)) -endif # BOARD_16K_OTA_MOVE_VENDOR == true - ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_4k.zip ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT)/boot_otas/boot_ota_16k.zip +endif # BOARD_16K_OTA_MOVE_VENDOR == true endif @@ -3400,12 +3406,29 @@ 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): $$(HOST_OUT_EXECUTABLES)/extra_install_zips_file_list $(foreach p,$(call relevant-extra-install-zips,$(filter $(2)/%,$(3))),$(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) $(call relevant-extra-install-zips,$(filter $(2)/%,$(3))) >> $$@ endef # ----------------------------------------------------------------- @@ -3471,7 +3494,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 $@ @@ -3588,7 +3611,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 $@ @@ -3644,7 +3667,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 $@ @@ -3731,7 +3754,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 $@ @@ -3837,7 +3860,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 $@ @@ -3910,7 +3933,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 $@ @@ -3980,7 +4003,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 $@ @@ -4069,7 +4092,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 $@ @@ -4138,7 +4161,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 $@ @@ -4207,7 +4230,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 $@ @@ -4278,7 +4301,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 $@ @@ -6211,6 +6234,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, @@ -6912,6 +6937,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/android_soong_config_vars.mk b/core/android_soong_config_vars.mk index 6f8610b0fd..c43081e4a9 100644 --- a/core/android_soong_config_vars.mk +++ b/core/android_soong_config_vars.mk @@ -38,38 +38,6 @@ $(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER) # 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)) -# 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 (,$(MODULE_BUILD_FROM_SOURCE)) - # Keep an explicit setting. -else ifeq (,$(filter docs sdk win_sdk sdk_addon,$(MAKECMDGOALS))) - MODULE_BUILD_FROM_SOURCE := true -else ifneq (,$(PRODUCT_MODULE_BUILD_FROM_SOURCE)) - # Let products override the branch default. - 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 - -$(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 @@ -78,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) @@ -115,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)) @@ -136,11 +77,24 @@ 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)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_llpvm_changes,$(RELEASE_AVF_ENABLE_LLPVM_CHANGES)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_multi_tenant_microdroid_vm,$(RELEASE_AVF_ENABLE_MULTI_TENANT_MICRODROID_VM)) +$(call add_soong_config_var_value,ANDROID,release_avf_enable_network,$(RELEASE_AVF_ENABLE_NETWORK)) $(call add_soong_config_var_value,ANDROID,release_avf_enable_remote_attestation,$(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)) @@ -153,6 +107,8 @@ $(call add_soong_config_var_value,ANDROID,release_package_libandroid_runtime_pun $(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..9e87a7bbe9 100644 --- a/core/art_config.mk +++ b/core/art_config.mk @@ -19,22 +19,19 @@ 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. -# soong variables indicate whether the prebuilt is enabled: -# - $(m)_module/source_build for art and TOGGLEABLE_PREBUILT_MODULES -# - ANDROID/module_build_from_source for other mainline modules # Note that RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST is the list of module names # and library names of jars that need to be removed. We have to keep separated list per # release config due to possibility of different prebuilt content. -APEX_BOOT_JARS_EXCLUDED := -$(foreach pair, $(RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST),\ - $(eval m := $(subst com.android.,,$(call word-colon,1,$(pair)))) \ - $(if $(call soong_config_get,$(m)_module,source_build), \ - $(if $(filter true,$(call soong_config_get,$(m)_module,source_build)),, \ - $(eval APEX_BOOT_JARS_EXCLUDED += $(pair))), \ - $(if $(filter true,$(call soong_config_get,ANDROID,module_build_from_source)),, \ - $(eval APEX_BOOT_JARS_EXCLUDED += $(pair))))) +# +# If a device has opted to not use google prebuilts (determined using +# PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS), then no jars need to be removed. +# Example of products where PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS is true are +# 1. aosp devices (they do not use google apexes) +# 2. hwasan devices (apex prebuilts are not compatible with these devices) +# 3. coverage builds +ifneq (true, $(PRODUCT_BUILD_IGNORE_APEX_CONTRIBUTION_CONTENTS)) + APEX_BOOT_JARS_EXCLUDED += $(RELEASE_APEX_BOOT_JARS_PREBUILT_EXCLUDED_LIST) +endif 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 633303f01a..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 := 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 1c3a1b3b6b..3271079abc 100644 --- a/core/envsetup.mk +++ b/core/envsetup.mk @@ -255,6 +255,7 @@ endif HOST_PREBUILT_ARCH := x86 # This is the standard way to name a directory containing prebuilt host # objects. E.g., prebuilt/$(HOST_PREBUILT_TAG)/cc +# This must match the logic in get_host_prebuilt_prefix in envsetup.sh HOST_PREBUILT_TAG := $(BUILD_OS)-$(HOST_PREBUILT_ARCH) # TARGET_COPY_OUT_* are all relative to the staging directory, ie PRODUCT_OUT. 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 b798b4981b..62fa53d08e 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,174 +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 - -# 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) - # ----------------------------------------------------------------- ### ### In this section we set up the things that are different @@ -355,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 ## @@ -450,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. @@ -502,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 := @@ -1908,7 +1651,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 9752f326db..15faf7d88f 100644 --- a/core/product.mk +++ b/core/product.mk @@ -418,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. @@ -428,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,13 +447,17 @@ _product_list_vars += PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS # device may have to re-compile everything on the first boot if the kernel doesn't support # userfaultfd # - "false": disallows the build system and the runtime to use userfaultfd GC even if the device -# supports it +# supports it. This option is temporary - the plan is to remove it by Aug 2025, at which time +# Mainline updates of the ART module will ignore it as well. _product_single_value_vars += PRODUCT_ENABLE_UFFD_GC # Specifies COW version to be used by update_engine and libsnapshot. If this value is not # 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 @@ -469,6 +480,13 @@ _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 + +# Enables 16KB developer option for device if set. +_product_single_value_vars += PRODUCT_16K_DEVELOPER_OPTION + .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 4eeac9546c..f21c1c4188 100644 --- a/core/product_config.mk +++ b/core/product_config.mk @@ -314,6 +314,14 @@ 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 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 3e51af5a44..5fe3d3905a 100644 --- a/core/release_config.mk +++ b/core/release_config.mk @@ -14,6 +14,16 @@ # ----------------------------------------------------------------- +# Determine which pass this is. +# ----------------------------------------------------------------- +# On the first pass, we are asked for only PRODUCT_RELEASE_CONFIG_MAPS, +# on the second pass, we are asked for whatever else is wanted. +_final_product_config_pass:= +ifneq (PRODUCT_RELEASE_CONFIG_MAPS,$(DUMP_MANY_VARS)) + _final_product_config_pass:=true +endif + +# ----------------------------------------------------------------- # Choose the flag files # ----------------------------------------------------------------- # Release configs are defined in reflease_config_map files, which map @@ -41,6 +51,7 @@ # 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. +_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), \ @@ -53,13 +64,96 @@ config_map_files := $(wildcard build/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).vars + # release-config generates $(_flags_varmk) + _flags_varmk:=$(_flags_file:.vars=.varmk) + $(shell $(OUT_DIR)/release-config $(_args) >$(OUT_DIR)/release-config.out 2>&1 && touch -t 200001010000 $(_flags_varmk)) + $(if $(filter-out 0,$(.SHELLSTATUS)),$(error release-config failed to run)) + ifneq (,$(_final_product_config_pass)) + # Save the final version of the config. + $(shell if ! cmp --quiet $(_flags_varmk) $(_flags_file); then cp $(_flags_varmk) $(_flags_file); fi) + # This will also set _all_release_configs and _used_files for us. + $(eval include $(_flags_file)) + $(KATI_extra_file_deps $(OUT_DIR)/release-config $(protobuf_map_files) $(_flags_file)) + else + # This is the first pass of product config. + $(eval include $(_flags_varmk)) + endif + _used_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 # # This should be used to declare a release as an alias of another, meaning no @@ -144,6 +238,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 @@ -161,12 +258,13 @@ endif # During pass 1 of product config, using a non-existent release config is not an error. # We can safely assume that we are doing pass 1 if DUMP_MANY_VARS=="PRODUCT_RELEASE_CONFIG_MAPS". -ifneq (PRODUCT_RELEASE_CONFIG_MAPS,$(DUMP_MANY_VARS)) +ifneq (,$(_final_product_config_pass)) ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),) $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE). Available releases are: $(_all_release_configs)) endif endif +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. @@ -196,6 +294,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) @@ -207,15 +308,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 # ----------------------------------------------------------------- @@ -252,3 +358,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/soong_config.mk b/core/soong_config.mk index 3cffef2538..acd213c776 100644 --- a/core/soong_config.mk +++ b/core/soong_config.mk @@ -52,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)) @@ -156,7 +154,7 @@ $(call add_json_list, ExtraVndkVersions, $(PRODUCT_EXTRA_VNDK_VE $(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)) diff --git a/core/sysprop_config.mk b/core/sysprop_config.mk new file mode 100644 index 0000000000..e8428c8eaf --- /dev/null +++ b/core/sysprop_config.mk @@ -0,0 +1,281 @@ +# 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) + +# This is a temporary system property that controls the ART module. The plan is +# to remove it by Aug 2025, at which time Mainline updates of the ART module +# will ignore it as well. +# 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/meta-lic.mk b/core/tasks/meta-lic.mk index 0348844035..c41de63b0a 100644 --- a/core/tasks/meta-lic.mk +++ b/core/tasks/meta-lic.mk @@ -14,9 +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)) @@ -33,3 +140,10 @@ $(eval $(call declare-1p-copy-files,frameworks/base,.idc)) $(eval $(call declare-1p-copy-files,frameworks/base,dirty-image-objects)) $(eval $(call declare-1p-copy-files,frameworks/base/config,)) $(eval $(call declare-1p-copy-files,frameworks/native/data,)) + +# Moved here from hardware/google/camera/Android.mk +$(eval $(call declare-1p-copy-files,hardware/google/camera,)) + +# Moved here from hardware/interfaces/tv/Android.mk +$(eval $(call declare-1p-copy-files,hardware/interfaces/tv,tuner_vts_config_1_0.xml)) +$(eval $(call declare-1p-copy-files,hardware/interfaces/tv,tuner_vts_config_1_1.xml)) 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/version_util.mk b/core/version_util.mk index 610cdaf107..eb568becc4 100644 --- a/core/version_util.mk +++ b/core/version_util.mk @@ -221,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..af3317439b 100644 --- a/envsetup.sh +++ b/envsetup.sh @@ -196,40 +196,19 @@ function get_build_var() (\cd $T; build/soong/soong_ui.bash --dumpvar-mode $1) } -# check to see if the supplied product is one we can build -function check_product() -{ - local T=$(gettop) - if [ ! "$T" ]; then - echo "Couldn't locate the top of the tree. Try setting TOP." >&2 - return - fi - TARGET_PRODUCT=$1 \ - TARGET_RELEASE= \ - TARGET_BUILD_VARIANT= \ - TARGET_BUILD_TYPE= \ - TARGET_BUILD_APPS= \ - get_build_var TARGET_DEVICE > /dev/null - # hide successful answers, but allow the errors to show -} - -VARIANT_CHOICES=(user userdebug eng) - -# check to see if the supplied variant is valid -function check_variant() -{ - local v - for v in ${VARIANT_CHOICES[@]} - do - if [ "$v" = "$1" ] - then - return 0 - fi - done - return 1 +# This logic matches envsetup.mk +function get_host_prebuilt_prefix +{ + local un=$(uname) + if [[ $un == "Linux" ]] ; then + echo linux-x86 + elif [[ $un == "Darwin" ]] ; then + echo darwin-x86 + else + echo "Error: Invalid host operating system: $un" 1>&2 + fi } - # Add directories to PATH that are dependent on the lunch target. # For directories that are not lunch-specific, add them in set_global_paths function set_lunch_paths() @@ -467,9 +446,6 @@ function addcompletions() fi done - if should_add_completion bit ; then - complete -C "bit --tab" bit - fi if [ -z "$ZSH_VERSION" ]; then # Doesn't work in zsh. complete -o nospace -F _croot croot @@ -486,236 +462,6 @@ function addcompletions() complete -F _complete_android_module_names m } -function multitree_lunch_help() -{ - echo "usage: lunch PRODUCT-RELEASE-VARIANT" 1>&2 - echo " Set up android build environment based on a product short name and variant" 1>&2 - echo 1>&2 - echo "lunch COMBO_FILE VARIANT" 1>&2 - echo " Set up android build environment based on a specific lunch combo file" 1>&2 - echo " and variant." 1>&2 - echo 1>&2 - echo "lunch --print [CONFIG]" 1>&2 - echo " Print the contents of a configuration. If CONFIG is supplied, that config" 1>&2 - echo " will be flattened and printed. If CONFIG is not supplied, the currently" 1>&2 - echo " selected config will be printed. Returns 0 on success or nonzero on error." 1>&2 - echo 1>&2 - echo "lunch --list" 1>&2 - echo " List all possible combo files available in the current tree" 1>&2 - echo 1>&2 - echo "lunch --help" 1>&2 - echo "lunch -h" 1>&2 - echo " Prints this message." 1>&2 -} - -function multitree_lunch() -{ - local code - local results - # Lunch must be run in the topdir, but this way we get a clear error - # message, instead of FileNotFound. - local T=$(multitree_gettop) - if [ -z "$T" ]; then - _multitree_lunch_error - return 1 - fi - if $(echo "$1" | grep -q '^-') ; then - # Calls starting with a -- argument are passed directly and the function - # returns with the lunch.py exit code. - "${T}/orchestrator/build/orchestrator/core/lunch.py" "$@" - code=$? - if [[ $code -eq 2 ]] ; then - echo 1>&2 - multitree_lunch_help - return $code - elif [[ $code -ne 0 ]] ; then - return $code - fi - else - # All other calls go through the --lunch variant of lunch.py - results=($(${T}/orchestrator/build/orchestrator/core/lunch.py --lunch "$@")) - code=$? - if [[ $code -eq 2 ]] ; then - echo 1>&2 - multitree_lunch_help - return $code - elif [[ $code -ne 0 ]] ; then - return $code - fi - - export TARGET_BUILD_COMBO=${results[0]} - export TARGET_BUILD_VARIANT=${results[1]} - fi -} - -function choosetype() -{ - echo "Build type choices are:" - echo " 1. release" - echo " 2. debug" - echo - - local DEFAULT_NUM DEFAULT_VALUE - DEFAULT_NUM=1 - DEFAULT_VALUE=release - - export TARGET_BUILD_TYPE= - local ANSWER - while [ -z $TARGET_BUILD_TYPE ] - do - echo -n "Which would you like? ["$DEFAULT_NUM"] " - if [ -z "$1" ] ; then - read ANSWER - else - echo $1 - ANSWER=$1 - fi - case $ANSWER in - "") - export TARGET_BUILD_TYPE=$DEFAULT_VALUE - ;; - 1) - export TARGET_BUILD_TYPE=release - ;; - release) - export TARGET_BUILD_TYPE=release - ;; - 2) - export TARGET_BUILD_TYPE=debug - ;; - debug) - export TARGET_BUILD_TYPE=debug - ;; - *) - echo - echo "I didn't understand your response. Please try again." - echo - ;; - esac - if [ -n "$1" ] ; then - break - fi - done - - build_build_var_cache - set_stuff_for_environment - destroy_build_var_cache -} - -# -# This function isn't really right: It chooses a TARGET_PRODUCT -# based on the list of boards. Usually, that gets you something -# that kinda works with a generic product, but really, you should -# pick a product by name. -# -function chooseproduct() -{ - local default_value - if [ "x$TARGET_PRODUCT" != x ] ; then - default_value=$TARGET_PRODUCT - else - default_value=aosp_arm - fi - - export TARGET_BUILD_APPS= - export TARGET_PRODUCT= - local ANSWER - while [ -z "$TARGET_PRODUCT" ] - do - echo -n "Which product would you like? [$default_value] " - if [ -z "$1" ] ; then - read ANSWER - else - echo $1 - ANSWER=$1 - fi - - if [ -z "$ANSWER" ] ; then - export TARGET_PRODUCT=$default_value - else - if check_product $ANSWER - then - export TARGET_PRODUCT=$ANSWER - else - echo "** Not a valid product: $ANSWER" - fi - fi - if [ -n "$1" ] ; then - break - fi - done - - build_build_var_cache - set_stuff_for_environment - destroy_build_var_cache -} - -function choosevariant() -{ - echo "Variant choices are:" - local index=1 - local v - for v in ${VARIANT_CHOICES[@]} - do - # The product name is the name of the directory containing - # the makefile we found, above. - echo " $index. $v" - index=$(($index+1)) - done - - local default_value=eng - local ANSWER - - export TARGET_BUILD_VARIANT= - while [ -z "$TARGET_BUILD_VARIANT" ] - do - echo -n "Which would you like? [$default_value] " - if [ -z "$1" ] ; then - read ANSWER - else - echo $1 - ANSWER=$1 - fi - - if [ -z "$ANSWER" ] ; then - export TARGET_BUILD_VARIANT=$default_value - elif (echo -n $ANSWER | grep -q -e "^[0-9][0-9]*$") ; then - if [ "$ANSWER" -le "${#VARIANT_CHOICES[@]}" ] ; then - export TARGET_BUILD_VARIANT=${VARIANT_CHOICES[@]:$(($ANSWER-1)):1} - fi - else - if check_variant $ANSWER - then - export TARGET_BUILD_VARIANT=$ANSWER - else - echo "** Not a valid variant: $ANSWER" - fi - fi - if [ -n "$1" ] ; then - break - fi - done -} - -function choosecombo() -{ - choosetype $1 - - echo - echo - chooseproduct $2 - - echo - echo - choosevariant $3 - - echo - build_build_var_cache - set_stuff_for_environment - printconfig - destroy_build_var_cache -} - function add_lunch_combo() { if [ -n "$ZSH_VERSION" ]; then @@ -1013,34 +759,6 @@ function banchan() destroy_build_var_cache } -# TODO: Merge into gettop as part of launching multitree -function multitree_gettop -{ - local TOPFILE=orchestrator/build/make/core/envsetup.mk - if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then - # The following circumlocution ensures we remove symlinks from TOP. - (cd "$TOP"; PWD= /bin/pwd) - else - if [ -f $TOPFILE ] ; then - # The following circumlocution (repeated below as well) ensures - # that we record the true directory name and not one that is - # faked up with symlink names. - PWD= /bin/pwd - else - local HERE=$PWD - local T= - while [ \( ! \( -f $TOPFILE \) \) -a \( "$PWD" != "/" \) ]; do - \cd .. - T=`PWD= /bin/pwd -P` - done - \cd "$HERE" - if [ -f "$T/$TOPFILE" ]; then - echo "$T" - fi - fi - fi -} - function croot() { local T=$(gettop) @@ -1100,7 +818,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 @@ -1532,17 +1293,6 @@ function smoketest() adb shell am instrument -w com.android.smoketest.tests/android.test.InstrumentationTestRunner } -# simple shortcut to the runtest command -function runtest() -{ - local T=$(gettop) - if [ ! "$T" ]; then - echo "Couldn't locate the top of the tree. Try setting TOP." >&2 - return - fi - ("$T"/development/testrunner/runtest.py $@) -} - function godir () { if [[ -z "$1" ]]; then echo "Usage: godir <regex>" @@ -1939,22 +1689,6 @@ function make() _wrap_build $(get_make_command "$@") "$@" } -function _multitree_lunch_error() -{ - >&2 echo "Couldn't locate the top of the tree. Please run \'source build/envsetup.sh\' and multitree_lunch from the root of your workspace." -} - -function multitree_build() -{ - local T=$(multitree_gettop) - if [ -n "$T" ]; then - "$T/orchestrator/build/orchestrator/core/orchestrator.py" "$@" - else - _multitree_lunch_error - return 1 - fi -} - function provision() { if [ ! "$ANDROID_PRODUCT_OUT" ]; then @@ -2076,25 +1810,17 @@ function showcommands() { fi } -function avbtool() { - if [[ ! -f "$ANDROID_SOONG_HOST_OUT"/bin/avbtool ]]; then - m avbtool - fi - "$ANDROID_SOONG_HOST_OUT"/bin/avbtool $@ -} - -function overrideflags() { - local T="$(gettop)" - (\cd "${T}" && build/make/tools/overrideflags.sh "$@") -} +# These functions used to be here but are now standalone scripts. +# Unset these for the time being so the real script is picked up. +# TODO: Remove this some time after a suitable delay (maybe 2025?) +unset aninja +unset overrideflags -function aninja() { - local T="$(gettop)" - (\cd "${T}" && prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-${TARGET_PRODUCT}.ninja "$@") -} validate_current_shell set_global_paths source_vendorsetup addcompletions + + diff --git a/shell_utils.sh b/shell_utils.sh index 9de5a504e5..15051e9585 100644 --- a/shell_utils.sh +++ b/shell_utils.sh @@ -40,9 +40,7 @@ function gettop fi } -# Sets TOP, or if the root of the tree can't be found, prints a message and -# exits. Since this function exits, it should not be called from functions -# defined in envsetup.sh. +# Asserts that the root of the tree can be found. if [ -z "${IMPORTING_ENVSETUP:-}" ] ; then function require_top { @@ -54,6 +52,17 @@ function require_top } fi +# Asserts that the lunch variables have been set +if [ -z "${IMPORTING_ENVSETUP:-}" ] ; then +function require_lunch +{ + if [[ ! $TARGET_PRODUCT || ! $TARGET_RELEASE || ! $TARGET_BUILD_VARIANT ]] ; then + echo "Please run lunch and try again." >&2 + exit 1 + fi +} +fi + function getoutdir { local top=$(gettop) 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..634bf668ce 100644 --- a/target/product/base_system.mk +++ b/target/product/base_system.mk @@ -172,7 +172,6 @@ PRODUCT_PACKAGES += \ libjpeg \ liblog \ libm.bootstrap \ - libmdnssd \ libmedia \ libmedia_jni \ libmediandk \ @@ -289,6 +288,7 @@ PRODUCT_PACKAGES += \ uncrypt \ usbd \ vdc \ + vintf \ voip-common \ vold \ watchdogd \ @@ -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 fc6cc68439..36897fef8e 100644 --- a/target/product/gsi/Android.mk +++ b/target/product/gsi/Android.mk @@ -8,11 +8,14 @@ LOCAL_PATH:= $(call my-dir) # Check if vndk list is changed droidcore: check-abi-dump-list -check-vndk-abi-dump-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-abi-dump-list-timestamp +check-abi-dump-list-timestamp := $(call intermediates-dir-for,PACKAGING,vndk)/check-abi-dump-list-timestamp -ifneq ($(SKIP_ABI_CHECKS),true) +# 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 +endif ##################################################################### # ABI reference dumps. @@ -34,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)) @@ -48,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. @@ -60,9 +66,12 @@ 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 VNDK_ABI_DUMP_DIR := prebuilts/abi-dumps/vndk/$(RELEASE_BOARD_API_LEVEL) @@ -77,21 +86,22 @@ 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-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,$(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.") # 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.") 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/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/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..1219763b0d 100644 --- a/target/product/virtual_ab_ota/vabc_features.mk +++ b/target/product/virtual_ab_ota/vabc_features.mk @@ -34,10 +34,28 @@ PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.io_uring.enabled=true PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.batch_writes=true +# Low memory device configurations. If memory usage and cpu utilization is +# a bottleneck during OTA, the below configurations can be added to a +# device's .mk file improve performance for low mem devices. Disabling +# ro.virtual_ab.compression.xor.enabled and ro.virtual_ab.io_uring.enabled +# is also recommended +# +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.o_direct.enabled=true +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.merge_thread_priority=19 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.worker_thread_priority=0 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.num_worker_threads=3 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.num_merge_threads=1 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.num_verify_threads=1 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.cow_op_merge_size=16 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.verify_threshold_size=1073741824 +# PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.verify_block_size=1048576 # 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 a02a57392c..b3a5752749 100644 --- a/teams/Android.bp +++ b/teams/Android.bp @@ -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 638b92a578..421e94a8a6 100644 --- a/tools/aconfig/TEST_MAPPING +++ b/tools/aconfig/TEST_MAPPING @@ -84,6 +84,10 @@ "name": "aconfig_storage_write_api.test.rust" }, { + // aconfig_storage write api cpp integration tests + "name": "aconfig_storage_write_api.test.cpp" + }, + { // aconfig_storage read api rust integration tests "name": "aconfig_storage_read_api.test.rust" }, @@ -94,8 +98,8 @@ ], "postsubmit": [ { - // aconfig_storage write api cpp integration tests - "name": "aconfig_storage_write_api.test.cpp" + // 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 18a4be5094..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> { @@ -292,76 +299,82 @@ mod tests { } "#; - 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()) { @@ -369,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, @@ -406,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(); @@ -549,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, ), @@ -671,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); + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, + FeatureFlags::enabledRoExported); } - 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); - } + + 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( "" @@ -733,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, ), ]); @@ -854,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, ), @@ -1020,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()) { @@ -1082,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, @@ -1116,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/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs index 855ed0235a..73339f24b3 100644 --- a/tools/aconfig/aconfig/src/storage/mod.rs +++ b/tools/aconfig/aconfig/src/storage/mod.rs @@ -189,14 +189,14 @@ mod tests { 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_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_start_index, 6); } 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/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_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 index a7bb0b1a3a..b6507210da 100644 --- a/tools/aconfig/aconfig/tests/storage_test_2.values +++ b/tools/aconfig/aconfig/tests/storage_test_2.values @@ -6,9 +6,9 @@ flag_value { } flag_value { package: "com.android.aconfig.storage.test_2" - name: "disabled_ro" + name: "disabled_rw" state: DISABLED - permission: READ_ONLY + permission: READ_WRITE } flag_value { package: "com.android.aconfig.storage.test_2" 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 index fa21317c43..784b744f62 100644 --- a/tools/aconfig/aconfig/tests/storage_test_4.values +++ b/tools/aconfig/aconfig/tests/storage_test_4.values @@ -1,8 +1,8 @@ flag_value { package: "com.android.aconfig.storage.test_4" - name: "enabled_ro" + name: "enabled_rw" state: ENABLED - permission: READ_ONLY + permission: READ_WRITE } flag_value { package: "com.android.aconfig.storage.test_4" 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..7480b3002c --- /dev/null +++ b/tools/aconfig/aconfig_device_paths/src/lib.rs @@ -0,0 +1,75 @@ +/* + * 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; + +fn read_partition_paths() -> Vec<PathBuf> { + include_str!("../partition_aconfig_flags_paths.txt") + .split(',') + .map(|s| s.trim().trim_matches('"')) + .filter(|s| !s.is_empty()) + .map(|s| PathBuf::from(s.to_string())) + .collect() +} + +/// Determine all paths that contain an aconfig protobuf file. +pub fn parsed_flags_proto_paths() -> Result<Vec<PathBuf>> { + let mut result: Vec<PathBuf> = read_partition_paths(); + + 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) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_partition_paths() { + assert_eq!(read_partition_paths().len(), 4); + + assert_eq!( + read_partition_paths(), + vec![ + PathBuf::from("/system/etc/aconfig_flags.pb"), + PathBuf::from("/system_ext/etc/aconfig_flags.pb"), + PathBuf::from("/product/etc/aconfig_flags.pb"), + PathBuf::from("/vendor/etc/aconfig_flags.pb") + ] + ); + } +} diff --git a/tools/aconfig/aconfig_protos/Android.bp b/tools/aconfig/aconfig_protos/Android.bp index 18c545ae96..d24199443c 100644 --- a/tools/aconfig/aconfig_protos/Android.bp +++ b/tools/aconfig/aconfig_protos/Android.bp @@ -17,7 +17,22 @@ java_library { apex_available: [ "com.android.configinfrastructure", "//apex_available:platform", - ] + ], +} + +java_library { + name: "libaconfig_java_proto_nano", + srcs: ["protos/aconfig.proto"], + static_libs: ["libprotobuf-java-nano"], + proto: { + type: "nano", + }, + sdk_version: "current", + min_sdk_version: "UpsideDownCake", + apex_available: [ + "//apex_available:platform", + ], + jarjar_rules: "jarjar-nano-rules.txt", } java_library_host { @@ -58,7 +73,7 @@ rust_defaults { ], proc_macros: [ "libpaste", - ] + ], } rust_library { diff --git a/tools/aconfig/aconfig_protos/jarjar-nano-rules.txt b/tools/aconfig/aconfig_protos/jarjar-nano-rules.txt new file mode 100644 index 0000000000..b58fa64838 --- /dev/null +++ b/tools/aconfig/aconfig_protos/jarjar-nano-rules.txt @@ -0,0 +1 @@ +rule com.google.protobuf.** android.internal.framework.protobuf.@1
\ No newline at end of file diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp index b590312961..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", ], } @@ -27,6 +28,8 @@ rust_library { "//apex_available:anyapex", ], min_sdk_version: "29", + vendor_available: true, + product_available: true, } rust_binary_host { @@ -54,9 +57,11 @@ rust_protobuf { "//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, @@ -68,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/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs index dc2a8d6d0d..beac38d156 100644 --- a/tools/aconfig/aconfig_storage_file/src/flag_info.rs +++ b/tools/aconfig/aconfig_storage_file/src/flag_info.rs @@ -91,9 +91,9 @@ impl FlagInfoHeader { /// bit field for flag info #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlagInfoBit { - IsSticky = 1 << 0, + HasServerOverride = 1 << 0, IsReadWrite = 1 << 1, - HasOverride = 1 << 2, + HasLocalOverride = 1 << 2, } /// Flag info node struct @@ -107,10 +107,10 @@ impl fmt::Debug for FlagInfoNode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, - "sticky: {}, readwrite: {}, override: {}", - self.attributes & (FlagInfoBit::IsSticky as u8) != 0, + "readwrite: {}, server override: {}, local override: {}", self.attributes & (FlagInfoBit::IsReadWrite as u8) != 0, - self.attributes & (FlagInfoBit::HasOverride as u8) != 0, + self.attributes & (FlagInfoBit::HasServerOverride as u8) != 0, + self.attributes & (FlagInfoBit::HasLocalOverride as u8) != 0, )?; Ok(()) } diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs index 070a3cfc17..26e9c1a3be 100644 --- a/tools/aconfig/aconfig_storage_file/src/lib.rs +++ b/tools/aconfig/aconfig_storage_file/src/lib.rs @@ -278,15 +278,73 @@ 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, String, StoredFlagType, 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() { @@ -295,30 +353,159 @@ pub fn list_flags( let mut flags = Vec::new(); for node in flag_table.nodes.iter() { - let (package_name, package_offset) = package_info[node.package_id as usize]; - let flag_offset = package_offset + node.flag_index as u32; - let flag_value = flag_value_list.booleans[flag_offset as usize]; - flags.push(( - String::from(package_name), - node.flag_name.clone(), - node.flag_type, - flag_value, - )); + 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| match v1.0.cmp(&v2.0) { - Ordering::Equal => v1.1.cmp(&v2.1), + 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] @@ -337,54 +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"), - String::from("disabled_rw"), - StoredFlagType::ReadWriteBoolean, - false, - ), - ( - String::from("com.android.aconfig.storage.test_1"), - String::from("enabled_ro"), - StoredFlagType::ReadOnlyBoolean, - true, - ), - ( - String::from("com.android.aconfig.storage.test_1"), - String::from("enabled_rw"), - StoredFlagType::ReadWriteBoolean, - true, - ), - ( - String::from("com.android.aconfig.storage.test_2"), - String::from("disabled_ro"), - StoredFlagType::ReadOnlyBoolean, - false, - ), - ( - String::from("com.android.aconfig.storage.test_2"), - String::from("enabled_fixed_ro"), - StoredFlagType::FixedReadOnlyBoolean, - true, - ), - ( - String::from("com.android.aconfig.storage.test_2"), - String::from("enabled_ro"), - StoredFlagType::ReadOnlyBoolean, - true, - ), - ( - String::from("com.android.aconfig.storage.test_4"), - String::from("enabled_fixed_ro"), - StoredFlagType::FixedReadOnlyBoolean, - true, - ), - ( - String::from("com.android.aconfig.storage.test_4"), - String::from("enabled_ro"), - StoredFlagType::ReadOnlyBoolean, - true, - ), + 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 b686274a2d..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, FlagInfoList, 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), + ), ) } @@ -87,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 (package_name, flag_name, flag_type, flag_value) in flags.iter() { - println!("{} {} {:?} {}", package_name, flag_name, flag_type, flag_value); + 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/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs index 608563c498..106666c47f 100644 --- a/tools/aconfig/aconfig_storage_file/src/test_utils.rs +++ b/tools/aconfig/aconfig_storage_file/src/test_utils.rs @@ -92,8 +92,8 @@ pub fn create_test_flag_table() -> FlagTable { None, None, None, - Some(178), None, + Some(177), Some(204), None, Some(262), @@ -108,8 +108,8 @@ pub fn create_test_flag_table() -> FlagTable { let nodes = vec![ FlagTableNode::new_expected(0, "enabled_ro", 1, 1, None), FlagTableNode::new_expected(0, "enabled_rw", 0, 2, Some(151)), - FlagTableNode::new_expected(1, "disabled_ro", 1, 0, None), - FlagTableNode::new_expected(2, "enabled_ro", 1, 1, None), + FlagTableNode::new_expected(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", 2, 0, None), @@ -140,7 +140,7 @@ pub fn create_test_flag_info_list() -> FlagInfoList { num_flags: 8, boolean_flag_offset: 27, }; - let is_flag_rw = [true, false, true, false, false, false, false, false]; + 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 } } 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 3746d1731a..db362944c5 100644 --- a/tools/aconfig/aconfig_storage_read_api/Android.bp +++ b/tools/aconfig/aconfig_storage_read_api/Android.bp @@ -65,11 +65,18 @@ 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: [ @@ -79,9 +86,34 @@ cc_library_static { 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 39f1840856..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(); @@ -65,8 +70,24 @@ 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 ErrnoError() << "failed to open " << file; @@ -83,31 +104,28 @@ static Result<MappedStorageFile> map_storage_file(std::string const& file) { 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( @@ -178,12 +196,14 @@ Result<bool> get_boolean_flag_value( } /// Get boolean flag attribute -Result<uint8_t> 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_boolean_flag_attribute_cxx(content, index); + 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 { 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 45552074fc..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 @@ -32,16 +32,16 @@ enum FlagValueType { /// Flag info enum, to be consistent with the one defined in /// aconfig_storage_file/src/flag_info.rs enum FlagInfoBit { - IsSticky = 1<<0, + HasServerOverride = 1<<0, IsReadWrite = 1<<1, - HasOverride = 1<<2, + HasLocalOverride = 1<<2, }; - /// Mapped storage file struct MappedStorageFile { void* file_ptr; size_t file_size; + virtual ~MappedStorageFile(); }; /// Package read context query result @@ -61,18 +61,29 @@ struct FlagReadContext { /// 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); @@ -110,9 +121,11 @@ android::base::Result<bool> get_boolean_flag_value( /// 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_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 index 807da971ed..6d03377683 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/flag_info_query.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/flag_info_query.rs @@ -17,11 +17,15 @@ //! 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}; +use aconfig_storage_file::{flag_info::FlagInfoHeader, read_u8_from_bytes, FlagValueType}; use anyhow::anyhow; /// Get flag attribute bitfield -pub fn find_boolean_flag_attribute(buf: &[u8], flag_index: u32) -> Result<u8, AconfigStorageError> { +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!( @@ -31,8 +35,11 @@ pub fn find_boolean_flag_attribute(buf: &[u8], flag_index: u32) -> Result<u8, Ac ))); } - // Find the byte offset to the flag info, each flag info now takes one byte - let mut head = (interpreted_header.boolean_flag_offset + flag_index) as usize; + // 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." @@ -49,12 +56,13 @@ mod tests { use aconfig_storage_file::{test_utils::create_test_flag_info_list, FlagInfoBit}; #[test] - // this test point locks down query if flag is sticky + // 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_boolean_flag_attribute(&flag_info_list[..], offset).unwrap(); - assert_eq!((attribute & FlagInfoBit::IsSticky as u8) != 0u8, false); + let attribute = + find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap(); + assert_eq!((attribute & FlagInfoBit::HasServerOverride as u8) != 0u8, false); } } @@ -62,9 +70,10 @@ mod tests { // this test point locks down query if flag is readwrite fn test_is_flag_readwrite() { let flag_info_list = create_test_flag_info_list().into_bytes(); - let baseline: Vec<bool> = vec![true, false, true, false, false, false, false, false]; + let baseline: Vec<bool> = vec![true, false, true, true, false, false, false, true]; for offset in 0..8 { - let attribute = find_boolean_flag_attribute(&flag_info_list[..], offset).unwrap(); + let attribute = + find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap(); assert_eq!( (attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, baseline[offset as usize] @@ -73,12 +82,13 @@ mod tests { } #[test] - // this test point locks down query if flag has override + // 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_boolean_flag_attribute(&flag_info_list[..], offset).unwrap(); - assert_eq!((attribute & FlagInfoBit::HasOverride as u8) != 0u8, false); + let attribute = + find_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap(); + assert_eq!((attribute & FlagInfoBit::HasLocalOverride as u8) != 0u8, false); } } @@ -86,7 +96,8 @@ mod tests { // 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_boolean_flag_attribute(&flag_info_list[..], 8).unwrap_err(); + 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.)" @@ -99,7 +110,7 @@ mod tests { 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_boolean_flag_attribute(&flag_info[..], 4).unwrap_err(); + let error = find_flag_attribute(&flag_info[..], FlagValueType::Boolean, 4).unwrap_err(); assert_eq!( format!("{:?}", error), format!( 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 55fdcb7606..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 @@ -82,8 +82,8 @@ mod tests { let baseline = vec![ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), - (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16), - (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (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), diff --git a/tools/aconfig/aconfig_storage_read_api/src/lib.rs b/tools/aconfig/aconfig_storage_read_api/src/lib.rs index aa0145f71d..e4192066d5 100644 --- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs +++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs @@ -45,12 +45,12 @@ pub mod package_table_query; #[cfg(test)] mod test_utils; -pub use aconfig_storage_file::{AconfigStorageError, StorageFileType}; +pub use aconfig_storage_file::{AconfigStorageError, FlagValueType, StorageFileType}; pub use flag_table_query::FlagReadContext; pub use package_table_query::PackageReadContext; use aconfig_storage_file::{read_u32_from_bytes, FILE_VERSION}; -use flag_info_query::find_boolean_flag_attribute; +use flag_info_query::find_flag_attribute; use flag_table_query::find_flag_read_context; use flag_value_query::find_boolean_flag_value; use package_table_query::find_package_read_context; @@ -149,16 +149,21 @@ pub fn get_storage_file_version(file_path: &str) -> Result<u32, AconfigStorageEr read_u32_from_bytes(&buffer, &mut head) } -/// Get the boolean flag attribute. +/// Get the flag attribute. /// /// \input file: mapped flag info file -/// \input index: boolean flag index +/// \input flag_type: flag value type +/// \input flag_index: flag index /// /// \return -/// If the provide offset is valid, it returns the boolean flag attribute bitfiled, otherwise it +/// If the provide offset is valid, it returns the flag attribute bitfiled, otherwise it /// returns the error message. -pub fn get_boolean_flag_attribute(file: &Mmap, index: u32) -> Result<u8, AconfigStorageError> { - find_boolean_flag_attribute(file, index) +pub fn get_flag_attribute( + file: &Mmap, + flag_type: FlagValueType, + flag_index: u32, +) -> Result<u8, AconfigStorageError> { + find_flag_attribute(file, flag_type, flag_index) } // *************************************** // @@ -201,7 +206,7 @@ mod ffi { } // Flag info query return for cc interlop - pub struct BooleanFlagAttributeQueryCXX { + pub struct FlagAttributeQueryCXX { pub query_success: bool, pub error_message: String, pub flag_attribute: u8, @@ -224,10 +229,11 @@ mod ffi { pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> BooleanFlagValueQueryCXX; - pub fn get_boolean_flag_attribute_cxx( + pub fn get_flag_attribute_cxx( file: &[u8], - offset: u32, - ) -> BooleanFlagAttributeQueryCXX; + flag_type: u16, + flag_index: u32, + ) -> FlagAttributeQueryCXX; } } @@ -312,7 +318,7 @@ impl ffi::BooleanFlagValueQueryCXX { } /// Implement the flag info interlop return type, create from actual flag info api return type -impl ffi::BooleanFlagAttributeQueryCXX { +impl ffi::FlagAttributeQueryCXX { pub(crate) fn new(info_result: Result<u8, AconfigStorageError>) -> Self { match info_result { Ok(info) => { @@ -365,12 +371,18 @@ pub fn get_boolean_flag_value_cxx(file: &[u8], offset: u32) -> ffi::BooleanFlagV ffi::BooleanFlagValueQueryCXX::new(find_boolean_flag_value(file, offset)) } -/// Get boolean flag attribute cc interlop -pub fn get_boolean_flag_attribute_cxx( +/// Get flag attribute cc interlop +pub fn get_flag_attribute_cxx( file: &[u8], - offset: u32, -) -> ffi::BooleanFlagAttributeQueryCXX { - ffi::BooleanFlagAttributeQueryCXX::new(find_boolean_flag_attribute(file, offset)) + 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 @@ -456,8 +468,8 @@ files {{ let baseline = vec![ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), - (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16), - (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (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), @@ -492,12 +504,13 @@ files {{ let pb_file_path = pb_file.path().display().to_string(); let flag_info_file = unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagInfo).unwrap() }; - let is_rw: Vec<bool> = vec![true, false, true, false, false, false, false, false]; + 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_boolean_flag_attribute(&flag_info_file, offset as u32).unwrap(); - assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8); + 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::HasOverride as u8) == 0u8); + assert!((attribute & FlagInfoBit::HasServerOverride as u8) == 0u8); + assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8); } } diff --git a/tools/aconfig/aconfig_storage_read_api/tests/flag.info b/tools/aconfig/aconfig_storage_read_api/tests/flag.info Binary files differindex 820d839363..6223edf369 100644 --- a/tools/aconfig/aconfig_storage_read_api/tests/flag.info +++ 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 d26e00f5dc..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/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp index 8ed3756e33..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> @@ -111,18 +112,19 @@ TEST_F(AconfigStorageTest, test_storage_version_query) { /// 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 context query api TEST_F(AconfigStorageTest, test_package_context_query) { - auto mapped_file = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::package_map); - ASSERT_TRUE(mapped_file.ok()); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); auto context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_1"); @@ -148,9 +150,10 @@ TEST_F(AconfigStorageTest, test_package_context_query) { /// Test to lock down when querying none exist package TEST_F(AconfigStorageTest, test_none_existent_package_context_query) { - auto mapped_file = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::package_map); - ASSERT_TRUE(mapped_file.ok()); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); auto context = api::get_package_read_context( *mapped_file, "com.android.aconfig.storage.test_3"); @@ -160,15 +163,16 @@ TEST_F(AconfigStorageTest, test_none_existent_package_context_query) { /// Test to lock down storage flag context query api TEST_F(AconfigStorageTest, test_flag_context_query) { - auto mapped_file = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_map); - ASSERT_TRUE(mapped_file.ok()); + 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}, - {1, "disabled_ro", api::StoredFlagType::ReadOnlyBoolean, 0}, - {2, "enabled_ro", api::StoredFlagType::ReadOnlyBoolean, 1}, + {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}, @@ -185,9 +189,10 @@ TEST_F(AconfigStorageTest, test_flag_context_query) { /// Test to lock down when querying none exist flag TEST_F(AconfigStorageTest, test_none_existent_flag_context_query) { - auto mapped_file = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_map); - ASSERT_TRUE(mapped_file.ok()); + 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()); @@ -200,9 +205,10 @@ TEST_F(AconfigStorageTest, test_none_existent_flag_context_query) { /// 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( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_val); - ASSERT_TRUE(mapped_file.ok()); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); auto expected_value = std::vector<bool>{ false, true, true, false, true, true, true, true}; @@ -215,9 +221,10 @@ TEST_F(AconfigStorageTest, test_boolean_flag_value_query) { /// 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( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_val); - ASSERT_TRUE(mapped_file.ok()); + 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()); @@ -227,29 +234,31 @@ TEST_F(AconfigStorageTest, test_invalid_boolean_flag_value_query) { /// Test to lock down storage flag info query api TEST_F(AconfigStorageTest, test_boolean_flag_info_query) { - auto mapped_file = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_info); - ASSERT_TRUE(mapped_file.ok()); + 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, false, false, false, false, false}; + true, false, true, true, false, false, false, true}; for (int index = 0; index < 8; ++index) { - auto attribute = api::get_boolean_flag_attribute(*mapped_file, index); + auto attribute = api::get_flag_attribute(*mapped_file, api::FlagValueType::Boolean, index); ASSERT_TRUE(attribute.ok()); - ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::IsSticky), 0); + ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::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::HasOverride), 0); + 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 = private_api::get_mapped_file_impl( + auto mapped_file_result = private_api::get_mapped_file_impl( storage_record_pb, "mockup", api::StorageFileType::flag_info); - ASSERT_TRUE(mapped_file.ok()); + ASSERT_TRUE(mapped_file_result.ok()); + auto mapped_file = std::unique_ptr<api::MappedStorageFile>(*mapped_file_result); - auto attribute = api::get_boolean_flag_attribute(*mapped_file, 8); + 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 bddb396a95..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,9 +1,9 @@ #[cfg(not(feature = "cargo"))] mod aconfig_storage_rust_test { use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file; - use aconfig_storage_file::{FlagInfoBit, StorageFileType, StoredFlagType}; + use aconfig_storage_file::{FlagInfoBit, FlagValueType, StorageFileType, StoredFlagType}; use aconfig_storage_read_api::{ - get_boolean_flag_attribute, get_boolean_flag_value, get_flag_read_context, + 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, }; @@ -118,8 +118,8 @@ files {{ let baseline = vec![ (0, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), (0, "enabled_rw", StoredFlagType::ReadWriteBoolean, 2u16), - (1, "disabled_ro", StoredFlagType::ReadOnlyBoolean, 0u16), - (2, "enabled_ro", StoredFlagType::ReadOnlyBoolean, 1u16), + (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), @@ -188,12 +188,13 @@ files {{ // The safety here is ensured as the test process will not write to temp storage file let flag_info_file = unsafe { get_mapped_file(&pb_file_path, "mockup", StorageFileType::FlagInfo).unwrap() }; - let is_rw: Vec<bool> = vec![true, false, true, false, false, false, false, false]; + 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_boolean_flag_attribute(&flag_info_file, offset as u32).unwrap(); - assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8); + 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::HasOverride as u8) == 0u8); + assert!((attribute & FlagInfoBit::HasLocalOverride as u8) == 0u8); } } @@ -205,7 +206,7 @@ files {{ // 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_boolean_flag_attribute(&flag_info_file, 8u32).unwrap_err(); + 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.)" 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 01785e12c3..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,57 +17,8 @@ using namespace android::base; namespace aconfig_storage { -/// Storage location pb file -static constexpr char kPersistStorageRecordsPb[] = - "/metadata/aconfig/persistent_storage_file_records.pb"; - -/// Read aconfig storage records pb file -static Result<storage_records_pb> read_storage_records_pb(std::string const& pb_file) { - auto records = storage_records_pb(); - auto content = std::string(); - if (!ReadFileToString(pb_file, &content)) { - return ErrnoError() << "ReadFileToString failed"; - } - - if (!records.ParseFromString(content)) { - return ErrnoError() << "Unable to parse persistent storage records protobuf"; - } - return records; -} - -/// Get storage file path -static Result<std::string> find_storage_file( - std::string const& pb_file, - std::string const& container, - StorageFileType file_type) { - auto records_pb = read_storage_records_pb(pb_file); - if (!records_pb.ok()) { - return Error() << "Unable to read storage records from " << pb_file - << " : " << records_pb.error(); - } - - for (auto& entry : records_pb->files()) { - if (entry.container() == container) { - switch(file_type) { - case StorageFileType::package_map: - return entry.package_map(); - case StorageFileType::flag_map: - return entry.flag_map(); - case StorageFileType::flag_val: - return entry.flag_val(); - case StorageFileType::flag_info: - return entry.flag_info(); - default: - return Error() << "Invalid file type " << file_type; - } - } - } - - return Error() << "Unable to find storage files for container " << container; -} - /// Map a storage file -static Result<MutableMappedStorageFile> map_storage_file(std::string const& file) { +Result<MutableMappedStorageFile*> map_mutable_storage_file(std::string const& file) { struct stat file_stat; if (stat(file.c_str(), &file_stat) < 0) { return ErrnoError() << "stat failed"; @@ -90,43 +41,13 @@ static Result<MutableMappedStorageFile> map_storage_file(std::string const& file return ErrnoError() << "mmap failed"; } - auto mapped_file = MutableMappedStorageFile(); - 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 mutable mapped file implementation. -Result<MutableMappedStorageFile> get_mutable_mapped_file_impl( - std::string const& pb_file, - std::string const& container, - StorageFileType file_type) { - if (file_type != StorageFileType::flag_val && - file_type != StorageFileType::flag_info) { - return Error() << "Cannot create mutable mapped file for this file type"; - } - - auto file_result = find_storage_file(pb_file, container, file_type); - if (!file_result.ok()) { - return Error() << file_result.error(); - } - - return map_storage_file(*file_result); -} - -} // namespace private internal api - -/// Get mutable mapped file -Result<MutableMappedStorageFile> get_mutable_mapped_file( - std::string const& container, - StorageFileType file_type) { - return private_internal_api::get_mutable_mapped_file_impl( - kPersistStorageRecordsPb, container, file_type); -} - /// Set boolean flag value Result<void> set_boolean_flag_value( const MutableMappedStorageFile& file, @@ -141,15 +62,15 @@ Result<void> set_boolean_flag_value( return {}; } -/// Set if flag is sticky -Result<void> set_flag_is_sticky( +/// 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_is_sticky_cxx( + 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()); @@ -157,15 +78,15 @@ Result<void> set_flag_is_sticky( return {}; } -/// Set if flag has override -Result<void> set_flag_has_override( +/// 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_flag_has_override_cxx( + 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()); 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 8699b88614..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 @@ -11,25 +11,11 @@ using namespace android::base; namespace aconfig_storage { /// Mapped flag value file -struct MutableMappedStorageFile{ - void* file_ptr; - size_t file_size; -}; +struct MutableMappedStorageFile : MappedStorageFile {}; -/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY -namespace private_internal_api { - -Result<MutableMappedStorageFile> get_mutable_mapped_file_impl( - std::string const& pb_file, - std::string const& container, - StorageFileType file_type); - -} // namespace private_internal_api - -/// Get mapped writeable storage file -Result<MutableMappedStorageFile> get_mutable_mapped_file( - std::string const& container, - StorageFileType file_type); +/// Map a storage file +Result<MutableMappedStorageFile*> map_mutable_storage_file( + std::string const& file); /// Set boolean flag value Result<void> set_boolean_flag_value( @@ -37,15 +23,15 @@ Result<void> set_boolean_flag_value( uint32_t offset, bool value); -/// Set if flag is sticky -Result<void> set_flag_is_sticky( +/// 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 override -Result<void> set_flag_has_override( +/// Set if flag has local override +Result<void> set_flag_has_local_override( const MutableMappedStorageFile& file, FlagValueType value_type, uint32_t offset, 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 index 56e1253c7e..6f03f128a5 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/flag_info_update.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/flag_info_update.rs @@ -61,32 +61,32 @@ fn get_flag_attribute_and_offset( Ok((attribute, head)) } -/// Set if flag is sticky -pub fn update_flag_is_sticky( +/// 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 is_sticky = (attribute & (FlagInfoBit::IsSticky as u8)) != 0; - if is_sticky != value { - buf[head] = (attribute ^ FlagInfoBit::IsSticky as u8).to_le_bytes()[0]; + 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 override -pub fn update_flag_has_override( +/// 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::HasOverride as u8)) != 0; + let has_override = (attribute & (FlagInfoBit::HasLocalOverride as u8)) != 0; if has_override != value { - buf[head] = (attribute ^ FlagInfoBit::HasOverride as u8).to_le_bytes()[0]; + buf[head] = (attribute ^ FlagInfoBit::HasLocalOverride as u8).to_le_bytes()[0]; } Ok(()) } @@ -95,35 +95,35 @@ pub fn update_flag_has_override( mod tests { use super::*; use aconfig_storage_file::test_utils::create_test_flag_info_list; - use aconfig_storage_read_api::flag_info_query::find_boolean_flag_attribute; + use aconfig_storage_read_api::flag_info_query::find_flag_attribute; #[test] - // this test point locks down is sticky update - fn test_update_flag_is_sticky() { + // 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_is_sticky(&mut buf, FlagValueType::Boolean, i, true).unwrap(); - let attribute = find_boolean_flag_attribute(&buf, i).unwrap(); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0); - update_flag_is_sticky(&mut buf, FlagValueType::Boolean, i, false).unwrap(); - let attribute = find_boolean_flag_attribute(&buf, i).unwrap(); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0); + 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 override update - fn test_update_flag_has_override() { + // 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_override(&mut buf, FlagValueType::Boolean, i, true).unwrap(); - let attribute = find_boolean_flag_attribute(&buf, i).unwrap(); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0); - update_flag_has_override(&mut buf, FlagValueType::Boolean, i, false).unwrap(); - let attribute = find_boolean_flag_attribute(&buf, i).unwrap(); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0); + 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/lib.rs b/tools/aconfig/aconfig_storage_write_api/src/lib.rs index ee08d1651b..aec28def68 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/lib.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/lib.rs @@ -34,15 +34,9 @@ use memmap2::MmapMut; use std::fs::File; use std::io::{Read, Write}; -/// Storage file location pb file -pub const STORAGE_LOCATION_FILE: &str = "/metadata/aconfig/persistent_storage_file_records.pb"; - /// Get read write mapped storage files. /// -/// \input container: the flag package container -/// \input file_type: storage file type enum -/// \return a result of read write mapped file -/// +/// \input file_path: path to the storage file /// /// # Safety /// @@ -50,11 +44,8 @@ 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_storage_file( - container: &str, - file_type: StorageFileType, -) -> Result<MmapMut, AconfigStorageError> { - unsafe { crate::mapped_file::get_mapped_file(STORAGE_LOCATION_FILE, container, file_type) } +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 @@ -75,39 +66,39 @@ pub fn set_boolean_flag_value( }) } -/// Set if flag is sticky thru mapped file and flush the change to file +/// 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 sticky value +/// \input value: updated flag has server override value /// \return a result of () /// -pub fn set_flag_is_sticky( +pub fn set_flag_has_server_override( file: &mut MmapMut, flag_type: FlagValueType, index: u32, value: bool, ) -> Result<(), AconfigStorageError> { - crate::flag_info_update::update_flag_is_sticky(file, flag_type, index, value)?; + 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 override thru mapped file and flush the change to file +/// 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 override value +/// \input value: updated flag has local override value /// \return a result of () /// -pub fn set_flag_has_override( +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_override(file, flag_type, index, value)?; + 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)) }) @@ -206,14 +197,14 @@ mod ffi { pub error_message: String, } - // Flag is sticky update return for cc interlop - pub struct FlagIsStickyUpdateCXX { + // Flag has server override update return for cc interlop + pub struct FlagHasServerOverrideUpdateCXX { pub update_success: bool, pub error_message: String, } - // Flag has override update return for cc interlop - pub struct FlagHasOverrideUpdateCXX { + // Flag has local override update return for cc interlop + pub struct FlagHasLocalOverrideUpdateCXX { pub update_success: bool, pub error_message: String, } @@ -232,19 +223,19 @@ mod ffi { value: bool, ) -> BooleanFlagValueUpdateCXX; - pub fn update_flag_is_sticky_cxx( + pub fn update_flag_has_server_override_cxx( file: &mut [u8], flag_type: u16, offset: u32, value: bool, - ) -> FlagIsStickyUpdateCXX; + ) -> FlagHasServerOverrideUpdateCXX; - pub fn update_flag_has_override_cxx( + pub fn update_flag_has_local_override_cxx( file: &mut [u8], flag_type: u16, offset: u32, value: bool, - ) -> FlagHasOverrideUpdateCXX; + ) -> FlagHasLocalOverrideUpdateCXX; pub fn create_flag_info_cxx( package_map: &str, @@ -270,55 +261,59 @@ pub(crate) fn update_boolean_flag_value_cxx( } } -pub(crate) fn update_flag_is_sticky_cxx( +pub(crate) fn update_flag_has_server_override_cxx( file: &mut [u8], flag_type: u16, offset: u32, value: bool, -) -> ffi::FlagIsStickyUpdateCXX { +) -> ffi::FlagHasServerOverrideUpdateCXX { match FlagValueType::try_from(flag_type) { Ok(value_type) => { - match crate::flag_info_update::update_flag_is_sticky(file, value_type, offset, value) { - Ok(()) => ffi::FlagIsStickyUpdateCXX { + 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("") + error_message: String::from(""), }, - Err(errmsg) => ffi::FlagIsStickyUpdateCXX { + Err(errmsg) => ffi::FlagHasServerOverrideUpdateCXX { update_success: false, error_message: format!("{:?}", errmsg), }, } } - Err(errmsg) => ffi::FlagIsStickyUpdateCXX { + Err(errmsg) => ffi::FlagHasServerOverrideUpdateCXX { update_success: false, error_message: format!("{:?}", errmsg), - } + }, } } -pub(crate) fn update_flag_has_override_cxx( +pub(crate) fn update_flag_has_local_override_cxx( file: &mut [u8], flag_type: u16, offset: u32, value: bool, -) -> ffi::FlagHasOverrideUpdateCXX { +) -> ffi::FlagHasLocalOverrideUpdateCXX { match FlagValueType::try_from(flag_type) { Ok(value_type) => { - match crate::flag_info_update::update_flag_has_override(file, value_type, offset, value) { - Ok(()) => ffi::FlagHasOverrideUpdateCXX { + 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("") + error_message: String::from(""), }, - Err(errmsg) => ffi::FlagHasOverrideUpdateCXX { + Err(errmsg) => ffi::FlagHasLocalOverrideUpdateCXX { update_success: false, error_message: format!("{:?}", errmsg), }, } } - Err(errmsg) => ffi::FlagHasOverrideUpdateCXX { + Err(errmsg) => ffi::FlagHasLocalOverrideUpdateCXX { update_success: false, error_message: format!("{:?}", errmsg), - } + }, } } @@ -340,13 +335,12 @@ pub(crate) fn create_flag_info_cxx( 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_boolean_flag_attribute; + 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; @@ -363,33 +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: "{}" - flag_info: "some_flag.info" - timestamp: 12345 -}} -"#, - flag_value_path - ); - let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap(); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is guaranteed as only this single threaded test process will // write to this file unsafe { - let mut file = crate::mapped_file::get_mapped_file( - &record_pb_path, - "system", - StorageFileType::FlagVal, - ) - .unwrap(); + 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); @@ -402,93 +375,55 @@ files {{ } } - fn get_flag_attribute_at_offset(file: &str, offset: u32) -> u8 { + 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_boolean_flag_attribute(&bytes, offset).unwrap() + find_flag_attribute(&bytes, value_type, offset).unwrap() } #[test] - fn test_set_flag_is_sticky() { + 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(); - let text_proto = format!( - r#" - files {{ - version: 0 - container: "system" - package_map: "some_package.map" - flag_map: "some_flag.map" - flag_val: "some_flag.val" - flag_info: "{}" - timestamp: 12345 - }} - "#, - flag_info_path - ); - let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap(); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is guaranteed as only this single threaded test process will // write to this file unsafe { - let mut file = crate::mapped_file::get_mapped_file( - &record_pb_path, - "system", - StorageFileType::FlagInfo, - ) - .unwrap(); + let mut file = map_mutable_storage_file(&flag_info_path).unwrap(); for i in 0..8 { - set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, true).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0); - set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0); + 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_override() { + 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(); - let text_proto = format!( - r#" - files {{ - version: 0 - container: "system" - package_map: "some_package.map" - flag_map: "some_flag.map" - flag_val: "some_flag.val" - flag_info: "{}" - timestamp: 12345 - }} - "#, - flag_info_path - ); - let record_pb_file = write_proto_to_temp_file(&text_proto).unwrap(); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is guaranteed as only this single threaded test process will // write to this file unsafe { - let mut file = crate::mapped_file::get_mapped_file( - &record_pb_path, - "system", - StorageFileType::FlagInfo, - ) - .unwrap(); + let mut file = map_mutable_storage_file(&flag_info_path).unwrap(); for i in 0..8 { - set_flag_has_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0); - set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0); + 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/aconfig_storage_write_api/src/mapped_file.rs b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs index ea9ac199af..401d6b798a 100644 --- a/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs +++ b/tools/aconfig/aconfig_storage_write_api/src/mapped_file.rs @@ -17,11 +17,8 @@ use anyhow::anyhow; use memmap2::MmapMut; use std::fs::{self, OpenOptions}; -use std::io::Read; use aconfig_storage_file::AconfigStorageError::{self, FileReadFail, MapFileFail}; -use aconfig_storage_file::StorageFileType; -use aconfig_storage_read_api::mapped_file::find_container_storage_location; /// Get the mutable memory mapping of a storage file /// @@ -31,7 +28,7 @@ use aconfig_storage_read_api::mapped_file::find_container_storage_location; /// file not thru this memory mapped file or there are concurrent writes to this /// memory mapped file. Ensure all writes to the underlying file are thru this memory /// mapped file and there are no concurrent writes. -unsafe fn map_file(file_path: &str) -> Result<MmapMut, AconfigStorageError> { +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(); if perms.readonly() { @@ -51,57 +48,18 @@ unsafe fn map_file(file_path: &str) -> Result<MmapMut, AconfigStorageError> { } } -/// Get a mapped storage file given the container and file type -/// -/// # Safety -/// -/// The memory mapped file may have undefined behavior if there are writes to this -/// file not thru this memory mapped file or there are concurrent writes to this -/// memory mapped file. Ensure all writes to the underlying file are thru this memory -/// mapped file and there are no concurrent writes. -pub unsafe fn get_mapped_file( - location_pb_file: &str, - container: &str, - file_type: StorageFileType, -) -> Result<MmapMut, AconfigStorageError> { - let files_location = find_container_storage_location(location_pb_file, container)?; - match file_type { - StorageFileType::FlagVal => unsafe { map_file(files_location.flag_val()) }, - StorageFileType::FlagInfo => unsafe { map_file(files_location.flag_info()) }, - _ => Err(MapFileFail(anyhow!( - "Cannot map file type {:?} as writeable memory mapped files.", - file_type - ))), - } -} - #[cfg(test)] mod tests { use super::*; use crate::test_utils::copy_to_temp_file; - use aconfig_storage_file::protos::storage_record_pb::write_proto_to_temp_file; + use std::io::Read; #[test] fn test_mapped_file_contents() { let mut rw_val_file = copy_to_temp_file("./tests/flag.val", false).unwrap(); let mut rw_info_file = copy_to_temp_file("./tests/flag.info", false).unwrap(); - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "some_package.map" - flag_map: "some_flag.map" - flag_val: "{}" - flag_info: "{}" - timestamp: 12345 -}} -"#, - rw_val_file.path().display().to_string(), - rw_info_file.path().display().to_string() - ); - let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap(); - let storage_record_file_path = storage_record_file.path().display().to_string(); + let 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(); @@ -109,9 +67,7 @@ files {{ // SAFETY: // The safety here is guaranteed here as no writes happens to this temp file unsafe { - let mmaped_file = - get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagVal) - .unwrap(); + let mmaped_file = map_file(&flag_val).unwrap(); assert_eq!(mmaped_file[..], content[..]); } @@ -121,90 +77,23 @@ files {{ // SAFETY: // The safety here is guaranteed here as no writes happens to this temp file unsafe { - let mmaped_file = - get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagInfo) - .unwrap(); + 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: "{}" - flag_info: "some_flag.info" - timestamp: 12345 -}} -"#, - ro_file.path().display().to_string() - ); - let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap(); - let storage_record_file_path = storage_record_file.path().display().to_string(); + 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", StorageFileType::FlagVal) - .unwrap_err(); - assert_eq!( - format!("{:?}", error), - format!( - "MapFileFail(fail to map non read write storage file {})", - ro_file.path().display().to_string() - ) - ); - } - } - - #[test] - fn test_mapped_not_supported_file() { - let text_proto = format!( - r#" -files {{ - version: 0 - container: "system" - package_map: "some_package.map" - flag_map: "some_flag.map" - flag_val: "some_flag.val" - flag_info: "some_flag.info" - timestamp: 12345 -}} -"#, - ); - let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap(); - let storage_record_file_path = storage_record_file.path().display().to_string(); - - // SAFETY: - // The safety here is guaranteed here as no writes happens to this temp file - unsafe { - let error = - get_mapped_file(&storage_record_file_path, "system", StorageFileType::PackageMap) - .unwrap_err(); - assert_eq!( - format!("{:?}", error), - format!( - "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)", - StorageFileType::PackageMap - ) - ); - - let error = - get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagMap) - .unwrap_err(); + let error = map_file(&flag_val).unwrap_err(); assert_eq!( format!("{:?}", error), - format!( - "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)", - StorageFileType::FlagMap - ) + format!("MapFileFail(fail to map non read write storage file {})", flag_val) ); } } diff --git a/tools/aconfig/aconfig_storage_write_api/tests/flag.info b/tools/aconfig/aconfig_storage_write_api/tests/flag.info Binary files differindex 820d839363..6223edf369 100644 --- a/tools/aconfig/aconfig_storage_write_api/tests/flag.info +++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.info 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 77664e40a8..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,95 +50,40 @@ class AconfigStorageTest : public ::testing::Test { return temp_file; } - Result<std::string> write_storage_location_pb_file(std::string const& flag_val, - std::string const& flag_info) { - auto temp_file = std::tmpnam(nullptr); - auto proto = storage_files(); - auto* info = proto.add_files(); - info->set_version(0); - info->set_container("mockup"); - info->set_package_map("some_package.map"); - info->set_flag_map("some_flag.map"); - info->set_flag_val(flag_val); - info->set_flag_info(flag_info); - info->set_timestamp(12345); - - auto content = std::string(); - proto.SerializeToString(&content); - if (!WriteStringToFile(content, temp_file)) { - return Error() << "failed to write storage records pb file"; - } - return temp_file; - } - void SetUp() override { auto const test_dir = android::base::GetExecutableDirectory(); flag_val = *copy_to_rw_temp_file(test_dir + "/flag.val"); flag_info = *copy_to_rw_temp_file(test_dir + "/flag.info"); - storage_record_pb = *write_storage_location_pb_file(flag_val, flag_info); } void TearDown() override { std::remove(flag_val.c_str()); std::remove(flag_info.c_str()); - std::remove(storage_record_pb.c_str()); } std::string flag_val; std::string flag_info; - std::string storage_record_pb; }; -/// Negative test to lock down the error when mapping none exist storage files -TEST_F(AconfigStorageTest, test_none_exist_storage_file_mapping) { - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "vendor", api::StorageFileType::flag_val); - ASSERT_FALSE(mapped_file_result.ok()); - ASSERT_EQ(mapped_file_result.error().message(), - "Unable to find storage files for container vendor"); -} - /// Negative test to lock down the error when mapping a non writeable storage file TEST_F(AconfigStorageTest, test_non_writable_storage_file_mapping) { ASSERT_TRUE(chmod(flag_val.c_str(), S_IRUSR | S_IRGRP | S_IROTH) != -1); - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_val); + auto mapped_file_result = api::map_mutable_storage_file(flag_val); ASSERT_FALSE(mapped_file_result.ok()); auto it = mapped_file_result.error().message().find("cannot map nonwriteable file"); ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message(); } -/// Negative test to lock down the error when mapping a file type that cannot be modified -TEST_F(AconfigStorageTest, test_invalid_storage_file_type_mapping) { - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::package_map); - ASSERT_FALSE(mapped_file_result.ok()); - auto it = mapped_file_result.error().message().find( - "Cannot create mutable mapped file for this file type"); - ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message(); - - mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_map); - ASSERT_FALSE(mapped_file_result.ok()); - it = mapped_file_result.error().message().find( - "Cannot create mutable mapped file for this file type"); - ASSERT_TRUE(it != std::string::npos) << mapped_file_result.error().message(); -} - /// Test to lock down storage flag value update api TEST_F(AconfigStorageTest, test_boolean_flag_value_update) { - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_val); + 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); } @@ -146,70 +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_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_val); + 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 stickiness update api -TEST_F(AconfigStorageTest, test_flag_is_sticky_update) { - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_info); +/// 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 = *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_flag_is_sticky( - mapped_file, api::FlagValueType::Boolean, offset, true); - ASSERT_TRUE(update_result.ok()); - auto ro_mapped_file = api::MappedStorageFile(); - ro_mapped_file.file_ptr = mapped_file.file_ptr; - ro_mapped_file.file_size = mapped_file.file_size; - auto attribute = api::get_boolean_flag_attribute(ro_mapped_file, 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::IsSticky); + ASSERT_TRUE(*attribute & api::FlagInfoBit::HasServerOverride); - update_result = api::set_flag_is_sticky( - mapped_file, api::FlagValueType::Boolean, offset, false); + update_result = api::set_flag_has_server_override( + *mapped_file, api::FlagValueType::Boolean, offset, false); ASSERT_TRUE(update_result.ok()); - ro_mapped_file.file_ptr = mapped_file.file_ptr; - ro_mapped_file.file_size = mapped_file.file_size; - attribute = api::get_boolean_flag_attribute(ro_mapped_file, offset); + attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); ASSERT_TRUE(attribute.ok()); - ASSERT_FALSE(*attribute & api::FlagInfoBit::IsSticky); + ASSERT_FALSE(*attribute & api::FlagInfoBit::HasServerOverride); } } -/// Test to lock down storage flag has override update api -TEST_F(AconfigStorageTest, test_flag_has_override_update) { - auto mapped_file_result = private_api::get_mutable_mapped_file_impl( - storage_record_pb, "mockup", api::StorageFileType::flag_info); +/// 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 = *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_flag_has_override( - mapped_file, api::FlagValueType::Boolean, offset, true); + auto update_result = api::set_flag_has_local_override( + *mapped_file, api::FlagValueType::Boolean, offset, true); ASSERT_TRUE(update_result.ok()); - auto ro_mapped_file = api::MappedStorageFile(); - ro_mapped_file.file_ptr = mapped_file.file_ptr; - ro_mapped_file.file_size = mapped_file.file_size; - auto attribute = api::get_boolean_flag_attribute(ro_mapped_file, offset); + auto attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); ASSERT_TRUE(attribute.ok()); - ASSERT_TRUE(*attribute & api::FlagInfoBit::HasOverride); + ASSERT_TRUE(*attribute & api::FlagInfoBit::HasLocalOverride); - update_result = api::set_flag_has_override( - mapped_file, api::FlagValueType::Boolean, offset, false); + update_result = api::set_flag_has_local_override( + *mapped_file, api::FlagValueType::Boolean, offset, false); ASSERT_TRUE(update_result.ok()); - ro_mapped_file.file_ptr = mapped_file.file_ptr; - ro_mapped_file.file_size = mapped_file.file_size; - attribute = api::get_boolean_flag_attribute(ro_mapped_file, offset); + attribute = api::get_flag_attribute( + *mapped_file, api::FlagValueType::Boolean, offset); ASSERT_TRUE(attribute.ok()); - ASSERT_FALSE(*attribute & api::FlagInfoBit::HasOverride); + 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 f6a9b2808e..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,44 +1,17 @@ #[cfg(not(feature = "cargo"))] mod aconfig_storage_write_api_test { - use aconfig_storage_file::protos::ProtoStorageFiles; - use aconfig_storage_file::{FlagInfoBit, FlagValueType, StorageFileType}; - use aconfig_storage_read_api::flag_info_query::find_boolean_flag_attribute; + 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, set_flag_has_override, - set_flag_is_sticky, + 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, flag_info: &str) -> NamedTempFile { - let text_proto = format!( - r#" -files {{ - version: 0 - container: "mockup" - package_map: "some_package_map" - flag_map: "some_flag_map" - flag_val: "{}" - flag_info: "{}" - timestamp: 12345 -}} -"#, - flag_val, flag_info - ); - let storage_files: ProtoStorageFiles = - protobuf::text_format::parse_from_str(&text_proto).unwrap(); - let mut binary_proto_bytes = Vec::new(); - storage_files.write_to_vec(&mut binary_proto_bytes).unwrap(); - let mut file = NamedTempFile::new().unwrap(); - file.write_all(&binary_proto_bytes).unwrap(); - file - } - /// Create temp file copy fn copy_to_temp_rw_file(source_file: &str) -> NamedTempFile { let file = NamedTempFile::new().unwrap(); @@ -55,29 +28,23 @@ files {{ } /// Get flag attribute at offset - fn get_flag_attribute_at_offset(file: &str, offset: u32) -> u8 { + 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_boolean_flag_attribute(&bytes, offset).unwrap() + find_flag_attribute(&bytes, value_type, offset).unwrap() } #[test] /// Test to lock down flag value update api fn test_boolean_flag_value_update() { let flag_value_file = copy_to_temp_rw_file("./flag.val"); - let flag_info_file = copy_to_temp_rw_file("./flag.info"); let flag_value_path = flag_value_file.path().display().to_string(); - let flag_info_path = flag_info_file.path().display().to_string(); - let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is ensured as only this single threaded test process will // write to this file - let mut file = unsafe { - get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagVal).unwrap() - }; + 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); @@ -90,54 +57,46 @@ files {{ } #[test] - /// Test to lock down flag is sticky update api - fn test_set_flag_is_sticky() { - let flag_value_file = copy_to_temp_rw_file("./flag.val"); + /// 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_value_path = flag_value_file.path().display().to_string(); let flag_info_path = flag_info_file.path().display().to_string(); - let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is ensured as only this single threaded test process will // write to this file - let mut file = unsafe { - get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagInfo).unwrap() - }; + let mut file = unsafe { map_mutable_storage_file(&flag_info_path).unwrap() }; for i in 0..8 { - set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, true).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0); - set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0); + 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 is sticky update api - fn test_set_flag_has_override() { - let flag_value_file = copy_to_temp_rw_file("./flag.val"); + /// 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_value_path = flag_value_file.path().display().to_string(); let flag_info_path = flag_info_file.path().display().to_string(); - let record_pb_file = write_storage_record_file(&flag_value_path, &flag_info_path); - let record_pb_path = record_pb_file.path().display().to_string(); // SAFETY: // The safety here is ensured as only this single threaded test process will // write to this file - let mut file = unsafe { - get_mapped_file(&record_pb_path, "mockup", StorageFileType::FlagInfo).unwrap() - }; + let mut file = unsafe { map_mutable_storage_file(&flag_info_path).unwrap() }; for i in 0..8 { - set_flag_has_override(&mut file, FlagValueType::Boolean, i, true).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0); - set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap(); - let attribute = get_flag_attribute_at_offset(&flag_info_path, i); - assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0); + 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 4920a6fd81..2a023792b6 100644 --- a/tools/aconfig/aflags/Android.bp +++ b/tools/aconfig/aflags/Android.bp @@ -9,6 +9,7 @@ rust_defaults { lints: "android", srcs: ["src/main.rs"], rustlibs: [ + "libaconfig_device_paths", "libaconfig_protos", "libaconfig_storage_read_api", "libaconfig_storage_file", diff --git a/tools/aconfig/aflags/Cargo.toml b/tools/aconfig/aflags/Cargo.toml index cce7f9d6cc..eeae295316 100644 --- a/tools/aconfig/aflags/Cargo.toml +++ b/tools/aconfig/aflags/Cargo.toml @@ -13,3 +13,4 @@ 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 index a3ca221d9e..04140c7fa3 100644 --- a/tools/aconfig/aflags/src/aconfig_storage_source.rs +++ b/tools/aconfig/aflags/src/aconfig_storage_source.rs @@ -27,13 +27,12 @@ impl FlagSource for AconfigStorageSource { let container = file_info.container.ok_or(anyhow!("storage file is missing container"))?; - for (package, name, _flag_type, val) in - aconfig_storage_file::list_flags(&package_map, &flag_map, &flag_val)? + for listed_flag in aconfig_storage_file::list_flags(&package_map, &flag_map, &flag_val)? { result.push(Flag { - name: name.to_string(), - package: package.to_string(), - value: FlagValue::try_from(val.to_string().as_str())?, + 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. 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 1c453c52c3..05c15bb304 100644 --- a/tools/aconfig/aflags/src/main.rs +++ b/tools/aconfig/aflags/src/main.rs @@ -25,18 +25,24 @@ 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", + } + ) } } @@ -46,12 +52,16 @@ 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", + } + ) } } @@ -73,12 +83,16 @@ 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", + } + ) } } @@ -101,7 +115,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(), } } @@ -151,6 +165,10 @@ enum Command { /// Read from the new flag storage. #[clap(long)] use_new_storage: bool, + + /// Optionally filter by container name. + #[clap(short = 'c', long = "container")] + container: Option<String>, }, /// Enable an aconfig flag on this device, on the next boot. @@ -174,6 +192,23 @@ struct PaddingInfo { longest_permission_col: usize, } +struct Filter { + container: Option<String>, +} + +impl Filter { + fn apply(&self, flags: &[Flag]) -> Vec<Flag> { + flags + .iter() + .filter(|flag| match &self.container { + Some(c) => flag.container == *c, + None => true, + }) + .cloned() + .collect() + } +} + fn format_flag_row(flag: &Flag, info: &PaddingInfo) -> String { let full_name = flag.qualified_name(); let p0 = info.longest_flag_col + 1; @@ -213,11 +248,12 @@ fn set_flag(qualified_name: &str, value: &str) -> Result<()> { Ok(()) } -fn list(source_type: FlagSourceType) -> Result<String> { - let flags = match source_type { +fn list(source_type: FlagSourceType, container: Option<String>) -> Result<String> { + let flags_unfiltered = match source_type { FlagSourceType::DeviceConfig => DeviceConfigSource::list_flags()?, FlagSourceType::AconfigStorage => AconfigStorageSource::list_flags()?, }; + let flags = (Filter { container }).apply(&flags_unfiltered); 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), @@ -249,8 +285,12 @@ fn list(source_type: FlagSourceType) -> Result<String> { fn main() { let cli = Cli::parse(); let output = match cli.command { - Command::List { use_new_storage: true } => list(FlagSourceType::AconfigStorage).map(Some), - Command::List { use_new_storage: false } => list(FlagSourceType::DeviceConfig).map(Some), + Command::List { use_new_storage: true, container } => { + list(FlagSourceType::AconfigStorage, container).map(Some) + } + Command::List { use_new_storage: false, container } => { + list(FlagSourceType::DeviceConfig, container).map(Some) + } Command::Enable { qualified_name } => set_flag(&qualified_name, "true").map(|_| None), Command::Disable { qualified_name } => set_flag(&qualified_name, "false").map(|_| None), }; @@ -260,3 +300,84 @@ fn main() { Err(message) => println!("Error: {message}"), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_filter_container() { + let flags = vec![ + Flag { + namespace: "namespace".to_string(), + name: "test1".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "system".to_string(), + }, + Flag { + namespace: "namespace".to_string(), + name: "test2".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "not_system".to_string(), + }, + Flag { + namespace: "namespace".to_string(), + name: "test3".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "system".to_string(), + }, + ]; + + assert_eq!((Filter { container: Some("system".to_string()) }).apply(&flags).len(), 2); + } + + #[test] + fn test_filter_no_container() { + let flags = vec![ + Flag { + namespace: "namespace".to_string(), + name: "test1".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "system".to_string(), + }, + Flag { + namespace: "namespace".to_string(), + name: "test2".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "not_system".to_string(), + }, + Flag { + namespace: "namespace".to_string(), + name: "test3".to_string(), + package: "package".to_string(), + value: FlagValue::Disabled, + staged_value: None, + permission: FlagPermission::ReadWrite, + value_picked_from: ValuePickedFrom::Default, + container: "system".to_string(), + }, + ]; + + assert_eq!((Filter { container: None }).apply(&flags).len(), 3); + } +} 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/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/overrideflags.sh b/tools/overrideflags.sh deleted file mode 100755 index b8605dc034..0000000000 --- a/tools/overrideflags.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -e -# 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. - - -source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../shell_utils.sh -require_top - -function print_help() { - echo -e "overrideflags is used to set default value for local build." - echo -e "\nOptions:" - echo -e "\t--release-config \tPath to release configuration directory. Required" - echo -e "\t--no-edit \tIf present, skip editing flag value file." - echo -e "\t-h/--help \tShow this help." -} - -function main() { - while (($# > 0)); do - case $1 in - --release-config) - if [[ $# -le 1 ]]; then - echo "--release-config requires a path" - return 1 - fi - local release_config_dir="$2" - shift 2 - ;; - --no-edit) - local no_edit="true" - shift 1 - ;; - -h|--help) - print_help - return - ;; - *) - echo "$1 is unrecognized" - print_help - return 1 - ;; - esac - done - - - - case $(uname -s) in - Darwin) - local host_arch=darwin-x86 - ;; - Linux) - local host_arch=linux-x86 - ;; - *) - >&2 echo Unknown host $(uname -s) - return - ;; - esac - - if [[ -z "${release_config_dir}" ]]; then - echo "Please provide release configuration path by --release-config" - exit 1 - elif [ ! -d "${release_config_dir}" ]; then - echo "${release_config_dir} is an invalid directory" - exit 1 - fi - local T="$(gettop)" - local aconfig_dir="${T}"/build/make/tools/aconfig/ - local overrideflag_py="${aconfig_dir}"/overrideflags/overrideflags.py - local overridefile="${release_config_dir}/aconfig/override_values.textproto" - - # Edit override file - if [[ -z "${no_edit}" ]]; then - editor="${EDITOR:-$(which vim)}" - - eval "${editor} ${overridefile}" - if [ $? -ne 0 ]; then - echo "Fail to set override values" - return 1 - fi - fi - - ${T}/prebuilts/build-tools/${host_arch}/bin/py3-cmd -u "${overrideflag_py}" \ - --overrides "${overridefile}" \ - --out "${release_config_dir}/aconfig" -} - - -main "$@" diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp index 9385f0ccab..9b134f22d4 100644 --- a/tools/releasetools/Android.bp +++ b/tools/releasetools/Android.bp @@ -632,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 264c2d8081..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", ] @@ -1524,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 @@ -1612,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 2c5fe0d016..5a024cec54 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 @@ -912,12 +914,13 @@ def GenerateAbOtaPackage(target_file, output_file, source_file=None): # and install time performance. All OTA's with # both the source build and target build with VIRTUAL_AB_COW_VERSION = 3 # can support the new format. Otherwise, fallback on older versions - if not source_info.vabc_cow_version or not target_info.vabc_cow_version: - logger.info("Source or Target doesn't have VABC_COW_VERSION specified, default to version 2") - OPTIONS.vabc_cow_version = 2 - 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) + if not OPTIONS.vabc_cow_version: + if not source_info.vabc_cow_version or not target_info.vabc_cow_version: + logger.info("Source or Target doesn't have VABC_COW_VERSION specified, default to version 2") + OPTIONS.vabc_cow_version = 2 + 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 @@ -931,19 +934,20 @@ 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: + if not OPTIONS.vabc_cow_version: + 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 - 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] @@ -1048,6 +1052,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. 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__": |