Merge "Fix dependency of system img on boot_otas" into main
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..cd5c426
--- /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/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ce75150..97ecd33 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 0000000..066b83f
--- /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 0000000..81a3435
--- /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 03f6731..5aaf2f4 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 1d5b377..29ed50e 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']
-)
+class Error(Exception):
+
+ def __init__(self, message):
+ super().__init__(message)
-def build_test_suites(argv):
+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
+
+
+REQUIRED_ENV_VARS = frozenset(['TARGET_PRODUCT', 'TARGET_RELEASE', 'TOP'])
+SOONG_UI_EXE_REL_PATH = 'build/soong/soong_ui.bash'
+
+
+def get_top() -> pathlib.Path:
+ return pathlib.Path(os.environ['TOP'])
+
+
+def build_test_suites(argv: list[str]) -> int:
+ """Builds the general-tests and any other test suites passed in.
+
+ Args:
+ argv: The command line arguments passed in.
+
+ Returns:
+ The exit code of the build.
+ """
args = parse_args(argv)
+ check_required_env()
- 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:
+ try:
build_everything(args)
+ except BuildFailureError as e:
+ logging.error('Build command failed! Check build_log for details.')
+ return e.return_code
+
+ return 0
+
+
+def check_required_env():
+ """Check for required env vars.
+
+ 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 not missing_env_vars:
+ return
+
+ t = ','.join(missing_env_vars)
+ raise Error(f'Missing required environment variables: {t}')
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='?')
- return argparser.parse_args()
-
-
-def is_optimization_enabled() -> bool:
- # TODO(lucafarsi): switch back to building only affected general-tests modules
- # in presubmit once ready.
- # if os.environ.get('BUILD_NUMBER')[0] == 'P':
- # return True
- return False
+ return argparser.parse_args(argv)
def build_everything(args: argparse.Namespace):
+ """Builds all tests (regardless of whether they are needed).
+
+ Args:
+ args: The parsed arguments.
+
+ Raises:
+ BuildFailure: If the build command fails.
+ """
build_command = base_build_command(args, args.extra_targets)
- build_command.append('general-tests')
- run_command(build_command, print_output=True)
-
-
-def build_affected_modules(args: argparse.Namespace):
- modules_to_build = find_modules_to_build(
- pathlib.Path(args.change_info), args.extra_required_modules
- )
-
- # Call the build command with everything.
- build_command = base_build_command(args, 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)
-
- zip_build_outputs(modules_to_build, args.target_release)
+ try:
+ run_command(build_command)
+ except subprocess.CalledProcessError as e:
+ raise BuildFailureError(e.returncode) from e
def base_build_command(
args: argparse.Namespace, extra_targets: set[str]
-) -> list:
+) -> list[str]:
+
build_command = []
- build_command.append('time')
- build_command.append('./build/soong/soong_ui.bash')
+ build_command.append(get_top().joinpath(SOONG_UI_EXE_REL_PATH))
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)
return build_command
-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)
-
- result.check_returncode()
-
- if print_output:
- print(result.stdout)
-
- return result.stdout
-
-
-def find_modules_to_build(
- change_info: pathlib.Path, extra_required_modules: list[str]
-) -> set[str]:
- changed_files = find_changed_files(change_info)
-
- test_mappings = test_mapping_module_retriever.GetTestMappings(
- changed_files, set()
- )
-
- # Soong_zip is required to generate the output zip so always build it.
- modules_to_build = set(REQUIRED_MODULES)
- if extra_required_modules:
- modules_to_build.update(extra_required_modules)
-
- modules_to_build.update(find_affected_modules(test_mappings, changed_files))
-
- return modules_to_build
-
-
-def find_changed_files(change_info: pathlib.Path) -> set[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'))
-
- run_command(zip_command, print_output=True)
-
-
-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))
-
-
-def base_zip_command(
- host_out: pathlib.Path, dist_dir: pathlib.Path, name: 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)
-
-
-def get_soong_var(var: str, target_release: str) -> str:
- new_env = os.environ.copy()
- new_env['TARGET_RELEASE'] = target_release
-
- value = run_command(
- ['./build/soong/soong_ui.bash', '--dumpvar-mode', '--abs', var],
- env=new_env,
- ).strip()
- if not value:
- raise RuntimeError('Necessary soong variable ' + var + ' not found.')
-
- return value
+def 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 0000000..78e52d3
--- /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 0000000..08a79a3
--- /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 0000000..2d70d3f
--- /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 44538f2..ef1485d 100644
--- a/cogsetup.sh
+++ b/cogsetup.sh
@@ -52,7 +52,9 @@
# 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 41a97d9..00577ed 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1128,10 +1128,15 @@
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
@@ -6212,6 +6217,8 @@
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,
@@ -6913,6 +6920,7 @@
--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 758d404..56da574 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -26,13 +26,17 @@
# Add variables to the namespace below:
-$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER)
-$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_DRMSERVER)
-$(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64)
$(call add_soong_config_var,ANDROID,BOARD_USES_ODMIMAGE)
$(call add_soong_config_var,ANDROID,BOARD_USES_RECOVERY_AS_BOOT)
$(call add_soong_config_var,ANDROID,CHECK_DEV_TYPE_VIOLATIONS)
+$(call add_soong_config_var,ANDROID,PLATFORM_SEPOLICY_COMPAT_VERSIONS)
$(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
+$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_DRMSERVER)
+$(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64)
+$(call add_soong_config_var,ANDROID,TARGET_DYNAMIC_64_32_MEDIASERVER)
+
+# 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
@@ -74,34 +78,6 @@
$(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)
@@ -132,6 +108,18 @@
$(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))
@@ -149,6 +137,8 @@
$(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/base_rules.mk b/core/base_rules.mk
index b8aa5fe..4c92814 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -694,6 +694,16 @@
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
diff --git a/core/config.mk b/core/config.mk
index daefa70..5842594 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -420,9 +420,12 @@
.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
@@ -600,8 +603,6 @@
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
diff --git a/core/dex_preopt.mk b/core/dex_preopt.mk
index 08311ca..26b8b17 100644
--- a/core/dex_preopt.mk
+++ b/core/dex_preopt.mk
@@ -123,22 +123,28 @@
$(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 @@
@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/main.mk b/core/main.mk
index b798b49..8182740 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -343,6 +343,10 @@
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
+
# -----------------------------------------------------------------
###
### In this section we set up the things that are different
@@ -1908,7 +1912,7 @@
$(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 9752f32..f8634ef 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -428,6 +428,12 @@
# 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
@@ -447,6 +453,9 @@
# 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 +478,10 @@
_product_single_value_vars += PRODUCT_EXPORT_RUNTIME_APIS
+# If set, determines which version of the GKI is used as guest kernel for Microdroid VMs.
+# TODO(b/325991735): link to documentation once it is done.
+_product_single_value_vars += PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION
+
.KATI_READONLY := _product_single_value_vars _product_list_vars
_product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
diff --git a/core/product_config.mk b/core/product_config.mk
index d16c38d..4eeac95 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -630,6 +630,15 @@
endif
endef
+ifndef PRODUCT_VIRTUAL_AB_COW_VERSION
+ PRODUCT_VIRTUAL_AB_COW_VERSION := 2
+ ifdef PRODUCT_SHIPPING_API_LEVEL
+ ifeq (true,$(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),34))
+ PRODUCT_VIRTUAL_AB_COW_VERSION := 3
+ endif
+ endif
+endif
+
# Copy and check the value of each PRODUCT_BUILD_*_IMAGE variable
$(foreach image, \
PVMFW \
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 3cffef2..534270e 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -52,8 +52,6 @@
$(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))
diff --git a/core/tasks/meta-lic.mk b/core/tasks/meta-lic.mk
index 0348844..c630bcc 100644
--- a/core/tasks/meta-lic.mk
+++ b/core/tasks/meta-lic.mk
@@ -14,6 +14,76 @@
# 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/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/sample/Android.mk
$(eval $(call declare-1p-copy-files,device/sample,))
diff --git a/core/version_util.mk b/core/version_util.mk
index 610cdaf..eb568be 100644
--- a/core/version_util.mk
+++ b/core/version_util.mk
@@ -221,10 +221,8 @@
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 fbe522d..ab43ada 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -1100,7 +1100,49 @@
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 logging is not enabled or the logger is not configured, run the original command and return.
+ if [[ "${ANDROID_ENABLE_TOOL_LOGGING}" != "true" ]] || [[ -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}" \
+ > /dev/null 2>&1 &
+ exit ${exit_code}
+ ' SIGINT SIGTERM SIGQUIT EXIT
+
+ # Run the original command.
+ "${tool_binary}" "${@}"
+ )
}
# simplified version of ps; output in the form
diff --git a/target/product/aosp_arm64.mk b/target/product/aosp_arm64.mk
index d3514a5..d944615 100644
--- a/target/product/aosp_arm64.mk
+++ b/target/product/aosp_arm64.mk
@@ -72,3 +72,5 @@
PRODUCT_DEVICE := generic_arm64
PRODUCT_BRAND := Android
PRODUCT_MODEL := AOSP on ARM64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/aosp_product.mk b/target/product/aosp_product.mk
index f72f2df..3a5b622 100644
--- a/target/product/aosp_product.mk
+++ b/target/product/aosp_product.mk
@@ -34,7 +34,6 @@
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 3040dd3..4344f50 100644
--- a/target/product/aosp_x86_64.mk
+++ b/target/product/aosp_x86_64.mk
@@ -74,3 +74,5 @@
PRODUCT_DEVICE := generic_x86_64
PRODUCT_BRAND := Android
PRODUCT_MODEL := AOSP on x86_64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 884af4f..57e8275 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -423,8 +423,7 @@
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,8 @@
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
$(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 fa31e04..9748c7c 100644
--- a/target/product/generic_system.mk
+++ b/target/product/generic_system.mk
@@ -36,6 +36,11 @@
Stk \
Tag \
+ifeq ($(RELEASE_AVATAR_PICKER_APP),true)
+ PRODUCT_PACKAGES += \
+ AvatarPicker
+endif
+
# OTA support
PRODUCT_PACKAGES += \
recovery-refresh \
@@ -68,7 +73,6 @@
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/gsi/Android.mk b/target/product/gsi/Android.mk
index fc6cc68..36897fe 100644
--- a/target/product/gsi/Android.mk
+++ b/target/product/gsi/Android.mk
@@ -8,11 +8,14 @@
# 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 @@
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 @@
#####################################################################
# 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 @@
# $(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 @@
# 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 2e37366..884b419 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -44,9 +44,6 @@
# 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 bf9aa41..3c401f3 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -82,8 +82,9 @@
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 38ba219..503c9b3 100644
--- a/target/product/media_system.mk
+++ b/target/product/media_system.mk
@@ -59,10 +59,6 @@
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 2e8c8a7..634a03d 100644
--- a/target/product/module_arm64.mk
+++ b/target/product/module_arm64.mk
@@ -19,3 +19,5 @@
PRODUCT_NAME := module_arm64
PRODUCT_DEVICE := module_arm64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/module_arm64only.mk b/target/product/module_arm64only.mk
index c0769bf..822ac24 100644
--- a/target/product/module_arm64only.mk
+++ b/target/product/module_arm64only.mk
@@ -19,3 +19,5 @@
PRODUCT_NAME := module_arm64only
PRODUCT_DEVICE := module_arm64only
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/module_x86_64.mk b/target/product/module_x86_64.mk
index 20f443a..9bd0264 100644
--- a/target/product/module_x86_64.mk
+++ b/target/product/module_x86_64.mk
@@ -19,3 +19,5 @@
PRODUCT_NAME := module_x86_64
PRODUCT_DEVICE := module_x86_64
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/module_x86_64only.mk b/target/product/module_x86_64only.mk
index b0d72bf..056fb90 100644
--- a/target/product/module_x86_64only.mk
+++ b/target/product/module_x86_64only.mk
@@ -19,3 +19,5 @@
PRODUCT_NAME := module_x86_64only
PRODUCT_DEVICE := module_x86_64only
+
+PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index a9d478d..d9c3c9a 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -175,15 +175,5 @@
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/android_t_baseline.mk b/target/product/virtual_ab_ota/android_t_baseline.mk
index af0f7a9..418aaa4 100644
--- a/target/product/virtual_ab_ota/android_t_baseline.mk
+++ b/target/product/virtual_ab_ota/android_t_baseline.mk
@@ -20,5 +20,3 @@
#
# All U+ launching devices should instead use vabc_features.mk.
$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/vabc_features.mk)
-
-PRODUCT_VIRTUAL_AB_COW_VERSION ?= 2
diff --git a/target/product/virtual_ab_ota/compression.mk b/target/product/virtual_ab_ota/compression.mk
index dc1ee3e..c964860 100644
--- a/target/product/virtual_ab_ota/compression.mk
+++ b/target/product/virtual_ab_ota/compression.mk
@@ -28,5 +28,4 @@
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 6c29cba..118d3f2 100644
--- a/target/product/virtual_ab_ota/compression_retrofit.mk
+++ b/target/product/virtual_ab_ota/compression_retrofit.mk
@@ -24,5 +24,4 @@
# 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 874eb9c..3f484e4 100644
--- a/target/product/virtual_ab_ota/vabc_features.mk
+++ b/target/product/virtual_ab_ota/vabc_features.mk
@@ -38,6 +38,9 @@
# 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 a02a573..78efa61 100644
--- a/teams/Android.bp
+++ b/teams/Android.bp
@@ -4371,3 +4371,10 @@
// go/trendy/manage/engineers/5810097836621824
trendy_team_id: "5810097836621824",
}
+
+team {
+ name: "trendy_team_adte",
+
+ // go/trendy/manage/engineers/5551098528825344
+ trendy_team_id: "5551098528825344",
+}
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..d3964e5
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,41 @@
+// 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",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+}
diff --git a/tests/run_tool_with_logging_test.py b/tests/run_tool_with_logging_test.py
new file mode 100644
index 0000000..1eb78f1
--- /dev/null
+++ b/tests/run_tool_with_logging_test.py
@@ -0,0 +1,336 @@
+# 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
+from importlib import resources
+import logging
+import os
+from pathlib import Path
+import re
+import signal
+import stat
+import subprocess
+import tempfile
+import textwrap
+import time
+import unittest
+import zipfile
+import sys
+
+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_logging_disabled(self):
+ test_tool = TestScript.create(self.working_dir)
+ test_logger = TestScript.create(self.working_dir)
+
+ self._run_script_and_wait(f"""
+ ANDROID_ENABLE_TOOL_LOGGING=false
+ 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")
+ test_logger.assert_not_called()
+
+ def test_does_not_log_when_logger_var_unset(self):
+ test_tool = TestScript.create(self.working_dir)
+ test_logger = TestScript.create(self.working_dir)
+
+ self._run_script_and_wait(f"""
+ unset ANDROID_ENABLE_TOOL_LOGGING
+ 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")
+ test_logger.assert_not_called()
+
+ def test_does_not_log_when_logger_var_empty(self):
+ test_tool = TestScript.create(self.working_dir)
+
+ self._run_script_and_wait(f"""
+ ANDROID_ENABLE_TOOL_LOGGING=true
+ 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"""
+ ANDROID_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=false
+ ANDROID_TOOL_LOGGER="{test_logger.executable}"
+ ANDROID_ENABLE_TOOL_LOGGING=true
+ 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_ENABLE_TOOL_LOGGING=true
+ ANDROID_TOOL_LOGGER="{test_logger.executable}"
+ ANDROID_ENABLE_TOOL_LOGGING=false
+ run_tool_with_logging "FAKE_TOOL" {test_tool.executable} arg1 arg2
+ """)
+
+ test_logger.assert_not_called()
+
+ 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"))
+
+ os.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/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index 6bd0d06..bf5e1a9 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/aconfig/src/codegen/cpp.rs b/tools/aconfig/aconfig/src/codegen/cpp.rs
index cd71b10..e743b2f 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 @@
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 @@
readwrite_count,
is_test_mode: codegen_mode == CodegenMode::Test,
class_elements,
+ allow_instrumentation,
};
let files = [
@@ -96,6 +100,7 @@
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 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 @@
},
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 @@
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 @@
CodegenMode::Production,
EXPORTED_PROD_HEADER_EXPECTED,
PROD_SOURCE_FILE_EXPECTED,
+ false,
);
}
@@ -1222,6 +1246,7 @@
CodegenMode::Test,
EXPORTED_TEST_HEADER_EXPECTED,
TEST_SOURCE_FILE_EXPECTED,
+ false,
);
}
@@ -1233,6 +1258,7 @@
CodegenMode::Exported,
EXPORTED_EXPORTED_HEADER_EXPECTED,
EXPORTED_SOURCE_FILE_EXPECTED,
+ false,
);
}
@@ -1244,6 +1270,7 @@
CodegenMode::ForceReadOnly,
EXPORTED_FORCE_READ_ONLY_HEADER_EXPECTED,
FORCE_READ_ONLY_SOURCE_FILE_EXPECTED,
+ false,
);
}
@@ -1255,6 +1282,7 @@
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 18a4be5..3360ddd 100644
--- a/tools/aconfig/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/aconfig/src/codegen/java.rs
@@ -64,20 +64,27 @@
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 @@
}
"#;
- 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);
+ return getValue(Flags.FLAG_ENABLED_RW,
+ FeatureFlags::enabledRw);
}
- 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);
- }
- }
+
public boolean isFlagReadOnlyOptimized(String flagName) {
if (mReadOnlyFlagsSet.contains(flagName) &&
isOptimizationEnabled()) {
@@ -369,30 +382,30 @@
}
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 @@
}
"#;
+ 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 @@
("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 @@
}
}"#;
- 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);
+
+ protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
+ return mGetValueImpl.test(flagName, getter);
}
- public void resetAll() {
- for (Map.Entry entry : mFlagMap.entrySet()) {
- entry.setValue(null);
- }
+
+ public List<String> getFlagNames() {
+ return Arrays.asList(
+ Flags.FLAG_DISABLED_RW_EXPORTED,
+ Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
+ Flags.FLAG_ENABLED_RO_EXPORTED
+ );
}
- private boolean getValue(String flagName) {
- Boolean value = this.mFlagMap.get(flagName);
- if (value == null) {
- throw new IllegalArgumentException(flagName + " is not set");
- }
- return value;
- }
- private Map<String, Boolean> mFlagMap = new HashMap<>(
- Map.ofEntries(
- Map.entry(Flags.FLAG_DISABLED_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 @@
("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 @@
("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 @@
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);
+ return getValue(Flags.FLAG_ENABLED_RW,
+ FeatureFlags::enabledRw);
}
- 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);
- }
- }
+
public boolean isFlagReadOnlyOptimized(String flagName) {
if (mReadOnlyFlagsSet.contains(flagName) &&
isOptimizationEnabled()) {
@@ -1082,27 +1160,27 @@
}
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 @@
);
}
"#;
+
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 7736ce7..6945fd4 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -202,7 +202,11 @@
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 @@
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 @@
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 @@
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 69f5458..72be1c9 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -83,6 +83,12 @@
.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 @@
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 855ed02..73339f2 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -189,14 +189,14 @@
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 0000000..b82b9cb
--- /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 177e711..290d2c4 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);
}
-{{ 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});
+ public FakeFeatureFlagsImpl(FeatureFlags defaults) \{
+ super(null);
+ mDefaults = defaults;
+ // Initialize the map with null values
+ for (String flagName : getFlagNames()) \{
+ mFlagMap.put(flagName, null);
+ }
}
-{{ endfor}}
+
+ @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);
@@ -34,46 +46,4 @@
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 4bcd1b7..7646015 100644
--- a/tools/aconfig/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/aconfig/templates/cpp_source_file.template
@@ -1,5 +1,16 @@
#include "{header}.h"
+{{ if allow_instrumentation }}
+#include <sys/stat.h>
+#include "aconfig_storage/aconfig_storage_read_api.hpp"
+#include <protos/aconfig_storage_metadata.pb.h>
+#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 +108,58 @@
{{ -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;
+ }
+
+ 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->package_id + {item.flag_offset});
+ if (!value.ok()) \{
+ ALOGI("error: failed to get flag val: %s", package_map_file.error().message().c_str());
+ return result;
+ }
+
+ 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 +167,7 @@
{{ -endif }}
{{ -endif }}
{{ -endif }}
+ {{ -endif }}
}
{{ -if is_test_mode }}
@@ -119,3 +183,4 @@
}
{{ -endif }}
+
diff --git a/tools/aconfig/aconfig/tests/storage_test_2.aconfig b/tools/aconfig/aconfig/tests/storage_test_2.aconfig
index bb14fd1..db77f7a 100644
--- a/tools/aconfig/aconfig/tests/storage_test_2.aconfig
+++ b/tools/aconfig/aconfig/tests/storage_test_2.aconfig
@@ -9,7 +9,7 @@
}
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 a7bb0b1..b650721 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 {
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 333fe09..5802a73 100644
--- a/tools/aconfig/aconfig/tests/storage_test_4.aconfig
+++ b/tools/aconfig/aconfig/tests/storage_test_4.aconfig
@@ -2,7 +2,7 @@
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 fa21317..784b744 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 0000000..21aa9a9
--- /dev/null
+++ b/tools/aconfig/aconfig_device_paths/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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"],
+}
diff --git a/tools/aconfig/aconfig_device_paths/Cargo.toml b/tools/aconfig/aconfig_device_paths/Cargo.toml
new file mode 100644
index 0000000..dbe9b3a
--- /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 0000000..3d2deb2
--- /dev/null
+++ b/tools/aconfig/aconfig_device_paths/partition_aconfig_flags_paths.txt
@@ -0,0 +1,6 @@
+[
+ "/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/lib.rs b/tools/aconfig/aconfig_device_paths/src/lib.rs
new file mode 100644
index 0000000..7bb62f4
--- /dev/null
+++ b/tools/aconfig/aconfig_device_paths/src/lib.rs
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Library for finding all aconfig on-device protobuf file paths.
+
+use anyhow::Result;
+use std::path::PathBuf;
+
+use std::fs;
+
+/// Determine all paths that contain an aconfig protobuf file.
+pub fn parsed_flags_proto_paths() -> Result<Vec<PathBuf>> {
+ let mut result: Vec<PathBuf> = include!("../partition_aconfig_flags_paths.txt")
+ .map(|s| PathBuf::from(s.to_string()))
+ .to_vec();
+ for dir in fs::read_dir("/apex")? {
+ let dir = dir?;
+
+ // Only scan the currently active version of each mainline module; skip the @version dirs.
+ if dir.file_name().as_encoded_bytes().iter().any(|&b| b == b'@') {
+ continue;
+ }
+
+ let mut path = PathBuf::from("/apex");
+ path.push(dir.path());
+ path.push("etc");
+ path.push("aconfig_flags.pb");
+ if path.exists() {
+ result.push(path);
+ }
+ }
+
+ Ok(result)
+}
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index b590312..08c00b0 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -27,6 +27,8 @@
"//apex_available:anyapex",
],
min_sdk_version: "29",
+ vendor_available: true,
+ product_available: true,
}
rust_binary_host {
@@ -54,9 +56,11 @@
"//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 +72,8 @@
"//apex_available:anyapex",
],
host_supported: true,
+ min_sdk_version: "29",
+ vendor_available: true,
+ product_available: true,
+ double_loadable: true,
}
diff --git a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
index e1c1c7f..7de43ca 100644
--- a/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
+++ b/tools/aconfig/aconfig_storage_file/protos/aconfig_storage_metadata.proto
@@ -27,7 +27,8 @@
optional string flag_map = 4;
optional string flag_val = 5;
optional string flag_info = 6;
- optional int64 timestamp = 7;
+ optional string local_overrides = 7;
+ optional int64 timestamp = 8;
}
message storage_files {
diff --git a/tools/aconfig/aconfig_storage_file/src/flag_info.rs b/tools/aconfig/aconfig_storage_file/src/flag_info.rs
index dc2a8d6..beac38d 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 @@
/// 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 @@
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 070a3cf..2acfc7d 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -357,8 +357,8 @@
),
(
String::from("com.android.aconfig.storage.test_2"),
- String::from("disabled_ro"),
- StoredFlagType::ReadOnlyBoolean,
+ String::from("disabled_rw"),
+ StoredFlagType::ReadWriteBoolean,
false,
),
(
@@ -381,8 +381,8 @@
),
(
String::from("com.android.aconfig.storage.test_4"),
- String::from("enabled_ro"),
- StoredFlagType::ReadOnlyBoolean,
+ String::from("enabled_rw"),
+ StoredFlagType::ReadWriteBoolean,
true,
),
];
diff --git a/tools/aconfig/aconfig_storage_file/src/test_utils.rs b/tools/aconfig/aconfig_storage_file/src/test_utils.rs
index 608563c..106666c 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 @@
None,
None,
None,
- Some(178),
None,
+ Some(177),
Some(204),
None,
Some(262),
@@ -108,8 +108,8 @@
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 @@
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_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index a0980b6..c89107f 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -65,7 +65,14 @@
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
@@ -79,14 +86,26 @@
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",
"libbase",
+ ],
+ shared_libs: [
"liblog",
],
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,
}
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 ff2f38e..8fe42ce 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
@@ -65,8 +65,24 @@
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;
@@ -90,22 +106,6 @@
return mapped_file;
}
-namespace private_internal_api {
-
-/// Get mapped file implementation.
-Result<MappedStorageFile> get_mapped_file_impl(
- std::string const& pb_file,
- std::string const& container,
- StorageFileType file_type) {
- auto file_result = find_storage_file(pb_file, container, file_type);
- if (!file_result.ok()) {
- return Error() << file_result.error();
- }
- return map_storage_file(*file_result);
-}
-
-} // namespace private internal api
-
/// Map from StoredFlagType to FlagValueType
android::base::Result<FlagValueType> map_to_flag_value_type(
StoredFlagType stored_type) {
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 7c63ef2..2bd84fc 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,9 +32,9 @@
/// 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
@@ -67,6 +67,11 @@
} // 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
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 0000000..7d47e0b
--- /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 e593418..6d03377 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
@@ -56,13 +56,13 @@
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_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
- assert_eq!((attribute & FlagInfoBit::IsSticky as u8) != 0u8, false);
+ assert_eq!((attribute & FlagInfoBit::HasServerOverride as u8) != 0u8, false);
}
}
@@ -70,7 +70,7 @@
// 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_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
@@ -82,13 +82,13 @@
}
#[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_flag_attribute(&flag_info_list[..], FlagValueType::Boolean, offset).unwrap();
- assert_eq!((attribute & FlagInfoBit::HasOverride as u8) != 0u8, false);
+ assert_eq!((attribute & FlagInfoBit::HasLocalOverride as u8) != 0u8, false);
}
}
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 55fdcb7..a1a4793 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 @@
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 bc09112..e65dcfb 100644
--- a/tools/aconfig/aconfig_storage_read_api/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_read_api/src/lib.rs
@@ -468,8 +468,8 @@
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),
@@ -508,9 +508,9 @@
for (offset, expected_value) in is_rw.into_iter().enumerate() {
let attribute =
get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
- assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8);
assert_eq!((attribute & FlagInfoBit::IsReadWrite as u8) != 0u8, expected_value);
- assert!((attribute & FlagInfoBit::HasOverride as u8) == 0u8);
+ 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.map b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
index d26e00f..e868f53 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/flag.map
+++ b/tools/aconfig/aconfig_storage_read_api/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp b/tools/aconfig/aconfig_storage_read_api/tests/storage_read_api_test.cpp
index 10f71a5..b499c1c 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
@@ -167,8 +167,8 @@
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},
@@ -236,10 +236,10 @@
for (int index = 0; index < 8; ++index) {
auto attribute = api::get_flag_attribute(*mapped_file, api::FlagValueType::Boolean, index);
ASSERT_TRUE(attribute.ok());
- ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::IsSticky), 0);
+ ASSERT_EQ(*attribute & static_cast<uint8_t>(api::FlagInfoBit::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);
}
}
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 212f734..ce9c018 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
@@ -118,8 +118,8 @@
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),
@@ -192,9 +192,9 @@
for (offset, expected_value) in is_rw.into_iter().enumerate() {
let attribute =
get_flag_attribute(&flag_info_file, FlagValueType::Boolean, offset as u32).unwrap();
- assert!((attribute & FlagInfoBit::IsSticky as u8) == 0u8);
+ assert!((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);
}
}
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 01785e1..cd57f4f 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 @@
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";
@@ -97,36 +48,6 @@
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 @@
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 @@
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 8699b88..7148396 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
@@ -16,20 +16,9 @@
size_t file_size;
};
-/// DO NOT USE APIS IN THE FOLLOWING NAMESPACE DIRECTLY
-namespace private_internal_api {
-
-Result<MutableMappedStorageFile> get_mutable_mapped_file_impl(
- std::string const& pb_file,
- std::string const& container,
- StorageFileType file_type);
-
-} // namespace private_internal_api
-
-/// Get mapped writeable storage file
-Result<MutableMappedStorageFile> get_mutable_mapped_file(
- std::string const& container,
- StorageFileType file_type);
+/// 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 +26,15 @@
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 3f38705..6f03f12 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 @@
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(())
}
@@ -98,32 +98,32 @@
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();
+ 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::IsSticky as u8)) != 0);
- update_flag_is_sticky(&mut buf, FlagValueType::Boolean, i, false).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::IsSticky as u8)) == 0);
+ 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();
+ 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::HasOverride as u8)) != 0);
- update_flag_has_override(&mut buf, FlagValueType::Boolean, i, false).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::HasOverride as u8)) == 0);
+ 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 8b7e459..aec28de 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 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 @@
/// 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 @@
})
}
-/// 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 @@
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 @@
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,53 +261,56 @@
}
}
-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(""),
},
- 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(""),
},
- 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),
},
@@ -341,7 +335,6 @@
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,
@@ -364,33 +357,12 @@
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);
@@ -411,85 +383,47 @@
}
#[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, FlagValueType::Boolean, i);
- assert!((attribute & (FlagInfoBit::IsSticky as u8)) != 0);
- set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap();
- let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
- assert!((attribute & (FlagInfoBit::IsSticky as u8)) == 0);
+ 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, FlagValueType::Boolean, i);
- assert!((attribute & (FlagInfoBit::HasOverride as u8)) != 0);
- set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap();
- let attribute = get_flag_attribute_at_offset(&flag_info_path, FlagValueType::Boolean, i);
- assert!((attribute & (FlagInfoBit::HasOverride as u8)) == 0);
+ 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 ea9ac19..401d6b7 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 @@
/// 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 @@
}
}
-/// 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 @@
// 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 @@
// 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();
+ let error = map_file(&flag_val).unwrap_err();
assert_eq!(
format!("{:?}", error),
- format!(
- "MapFileFail(fail to map non read write storage file {})",
- ro_file.path().display().to_string()
- )
- );
- }
- }
-
- #[test]
- fn test_mapped_not_supported_file() {
- let text_proto = format!(
- r#"
-files {{
- version: 0
- container: "system"
- package_map: "some_package.map"
- flag_map: "some_flag.map"
- flag_val: "some_flag.val"
- flag_info: "some_flag.info"
- timestamp: 12345
-}}
-"#,
- );
- let storage_record_file = write_proto_to_temp_file(&text_proto).unwrap();
- let storage_record_file_path = storage_record_file.path().display().to_string();
-
- // SAFETY:
- // The safety here is guaranteed here as no writes happens to this temp file
- unsafe {
- let error =
- get_mapped_file(&storage_record_file_path, "system", StorageFileType::PackageMap)
- .unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)",
- StorageFileType::PackageMap
- )
- );
-
- let error =
- get_mapped_file(&storage_record_file_path, "system", StorageFileType::FlagMap)
- .unwrap_err();
- assert_eq!(
- format!("{:?}", error),
- format!(
- "MapFileFail(Cannot map file type {:?} as writeable memory mapped files.)",
- StorageFileType::FlagMap
- )
+ 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
index 820d839..6223edf 100644
--- a/tools/aconfig/aconfig_storage_write_api/tests/flag.info
+++ b/tools/aconfig/aconfig_storage_write_api/tests/flag.info
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp b/tools/aconfig/aconfig_storage_write_api/tests/storage_write_api_test.cpp
index 6de3327..bd39e9e 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,88 +50,36 @@
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 = *mapped_file_result;
for (int offset = 0; offset < 8; ++offset) {
auto update_result = api::set_boolean_flag_value(mapped_file, offset, true);
ASSERT_TRUE(update_result.ok());
@@ -146,36 +94,35 @@
/// 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);
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;
for (int offset = 0; offset < 8; ++offset) {
- auto update_result = api::set_flag_is_sticky(
+ auto update_result = api::set_flag_has_server_override(
mapped_file, api::FlagValueType::Boolean, offset, true);
- ASSERT_TRUE(update_result.ok());
+ ASSERT_TRUE(update_result.ok()) << update_result.error();
auto ro_mapped_file = api::MappedStorageFile();
ro_mapped_file.file_ptr = mapped_file.file_ptr;
ro_mapped_file.file_size = mapped_file.file_size;
auto attribute = api::get_flag_attribute(
ro_mapped_file, api::FlagValueType::Boolean, offset);
ASSERT_TRUE(attribute.ok());
- ASSERT_TRUE(*attribute & api::FlagInfoBit::IsSticky);
+ ASSERT_TRUE(*attribute & api::FlagInfoBit::HasServerOverride);
- update_result = api::set_flag_is_sticky(
+ 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;
@@ -183,19 +130,18 @@
attribute = api::get_flag_attribute(
ro_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;
for (int offset = 0; offset < 8; ++offset) {
- auto update_result = api::set_flag_has_override(
+ 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();
@@ -204,9 +150,9 @@
auto attribute = api::get_flag_attribute(
ro_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(
+ 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;
@@ -214,6 +160,6 @@
attribute = api::get_flag_attribute(
ro_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 5dd36c4..367569d 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_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();
@@ -66,18 +39,12 @@
/// 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,58 +57,46 @@
}
#[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();
+ 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::IsSticky as u8)) != 0);
- set_flag_is_sticky(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ 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::IsSticky as u8)) == 0);
+ 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();
+ 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::HasOverride as u8)) != 0);
- set_flag_has_override(&mut file, FlagValueType::Boolean, i, false).unwrap();
+ 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::HasOverride as u8)) == 0);
+ assert!((attribute & (FlagInfoBit::HasLocalOverride as u8)) == 0);
}
}
}
diff --git a/tools/aconfig/aflags/Android.bp b/tools/aconfig/aflags/Android.bp
index 4920a6f..2a02379 100644
--- a/tools/aconfig/aflags/Android.bp
+++ b/tools/aconfig/aflags/Android.bp
@@ -9,6 +9,7 @@
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 cce7f9d..eeae295 100644
--- a/tools/aconfig/aflags/Cargo.toml
+++ b/tools/aconfig/aflags/Cargo.toml
@@ -13,3 +13,4 @@
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/device_config_source.rs b/tools/aconfig/aflags/src/device_config_source.rs
index 089f33d..cf6ab28 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 @@
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 0000000..90d8599
--- /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 1c453c5..4ce0d35 100644
--- a/tools/aconfig/aflags/src/main.rs
+++ b/tools/aconfig/aflags/src/main.rs
@@ -25,6 +25,8 @@
mod aconfig_storage_source;
use aconfig_storage_source::AconfigStorageSource;
+mod load_protos;
+
#[derive(Clone, PartialEq, Debug)]
enum FlagPermission {
ReadOnly,
diff --git a/tools/check-flagged-apis/Android.bp b/tools/check-flagged-apis/Android.bp
new file mode 100644
index 0000000..43c9c8e
--- /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 0000000..289e21e
--- /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 0000000..cd37a2d
--- /dev/null
+++ b/tools/check-flagged-apis/check-flagged-apis.sh
@@ -0,0 +1,78 @@
+#!/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
+
+function m() {
+ $(gettop)/build/soong/soong_ui.bash --build-mode --all-modules --dir="$(pwd)" "$@"
+}
+
+function build() {
+ m sdk dist && 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
+}
+
+function run() {
+ local errors=0
+
+ echo "# current"
+ check-flagged-apis \
+ --api-signature $(gettop)/out/target/product/mainline_x86/obj/ETC/frameworks-base-api-current.txt_intermediates/frameworks-base-api-current.txt \
+ --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \
+ --api-versions $(gettop)/out/dist/data/api-versions.xml
+ (( errors += $? ))
+
+ echo
+ echo "# system-current"
+ check-flagged-apis \
+ --api-signature $(gettop)/out/target/product/mainline_x86/obj/ETC/frameworks-base-api-system-current.txt_intermediates/frameworks-base-api-system-current.txt \
+ --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \
+ --api-versions $(gettop)/out/dist/system-data/api-versions.xml
+ (( errors += $? ))
+
+ echo
+ echo "# system-server-current"
+ check-flagged-apis \
+ --api-signature $(gettop)/out/target/product/mainline_x86/obj/ETC/frameworks-base-api-system-server-current.txt_intermediates/frameworks-base-api-system-server-current.txt \
+ --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \
+ --api-versions $(gettop)/out/dist/system-server-data/api-versions.xml
+ (( errors += $? ))
+
+ echo
+ echo "# module-lib"
+ check-flagged-apis \
+ --api-signature $(gettop)/out/target/product/mainline_x86/obj/ETC/frameworks-base-api-module-lib-current.txt_intermediates/frameworks-base-api-module-lib-current.txt \
+ --flag-values $(gettop)/out/soong/.intermediates/all_aconfig_declarations.pb \
+ --api-versions $(gettop)/out/dist/module-lib-data/api-versions.xml
+ (( 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 0000000..0569bfd
--- /dev/null
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/CheckFlaggedApisTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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();
+ }
+ @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">
+ <method name="<init>()V"/>
+ <field name="FOO"/>
+ <method name="getErrorCode()I"/>
+ </class>
+ <class name="android/Clazz${"$"}Builder" since="2">
+ </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("android.Clazz"), Flag("android.flag.foo")),
+ Pair(Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
+ Pair(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
+ Pair(Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
+ Pair(Symbol("android.Clazz.Builder"), 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("android.Clazz"),
+ Symbol("android.Clazz.Clazz()"),
+ Symbol("android.Clazz.FOO"),
+ Symbol("android.Clazz.getErrorCode()"),
+ Symbol("android.Clazz.Builder"),
+ )
+ val actual = parseApiVersions(API_VERSIONS.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 testFindErrorsDisabledFlaggedApiIsPresent() {
+ val expected =
+ setOf<ApiError>(
+ DisabledFlaggedApiIsPresentError(Symbol("android.Clazz"), Flag("android.flag.foo")),
+ DisabledFlaggedApiIsPresentError(
+ Symbol("android.Clazz.Clazz()"), Flag("android.flag.foo")),
+ DisabledFlaggedApiIsPresentError(Symbol("android.Clazz.FOO"), Flag("android.flag.foo")),
+ DisabledFlaggedApiIsPresentError(
+ Symbol("android.Clazz.getErrorCode()"), Flag("android.flag.foo")),
+ DisabledFlaggedApiIsPresentError(
+ Symbol("android.Clazz.Builder"), 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 0000000..0c078a0
--- /dev/null
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ *
+ * All parts of the fully qualified name of the Symbol are separated by a dot, e.g.:
+ * <pre>
+ * package.class.inner-class.field
+ * </pre>
+ */
+@JvmInline
+internal value class Symbol(val name: String) {
+ companion object {
+ private val FORBIDDEN_CHARS = listOf('#', '$')
+
+ /** Create a new Symbol from a String that may include delimiters other than dot. */
+ fun create(name: String): Symbol {
+ var sanitizedName = name
+ for (ch in FORBIDDEN_CHARS) {
+ sanitizedName = sanitizedName.replace(ch, '.')
+ }
+ return Symbol(sanitizedName)
+ }
+ }
+
+ init {
+ require(!name.isEmpty()) { "empty string" }
+ for (ch in FORBIDDEN_CHARS) {
+ require(!name.contains(ch)) { "$name: contains $ch" }
+ }
+ }
+
+ override fun toString(): String = name.toString()
+}
+
+/**
+ * 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 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 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 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>> {
+ // TODO(334870672): add support for metods
+ val output = mutableSetOf<Pair<Symbol, Flag>>()
+ val visitor =
+ object : BaseItemVisitor() {
+ override fun visitClass(cls: ClassItem) {
+ getFlagOrNull(cls)?.let { flag ->
+ val symbol = Symbol.create(cls.baselineElementId())
+ output.add(Pair(symbol, flag))
+ }
+ }
+
+ override fun visitField(field: FieldItem) {
+ getFlagOrNull(field)?.let { flag ->
+ val symbol = Symbol.create(field.baselineElementId())
+ output.add(Pair(symbol, flag))
+ }
+ }
+
+ override fun visitMethod(method: MethodItem) {
+ getFlagOrNull(method)?.let { flag ->
+ val name = buildString {
+ append(method.containingClass().qualifiedName())
+ append(".")
+ append(method.name())
+ append("(")
+ // TODO(334870672): replace this early return with proper parsing of the command line
+ // arguments, followed by translation to Lname/of/class; + III format
+ if (!method.parameters().isEmpty()) {
+ return
+ }
+ append(")")
+ }
+ val symbol = Symbol.create(name)
+ 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"
+ }
+ output.add(Symbol.create(className.replace("/", ".")))
+ }
+
+ 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.create("${className.replace("/", ".")}.$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, methodReturnValue) = methodSignatureParts
+ val packageAndClassName =
+ requireNotNull(method.getParentNode()?.getAttribute("name")) {
+ "Bad XML: top level <method> element, or <class> element missing name attribute"
+ }
+ if (methodName == "<init>") {
+ methodName = packageAndClassName.split("/").last()
+ }
+ output.add(Symbol.create("${packageAndClassName.replace("/", ".")}.$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> {
+ val errors = mutableSetOf<ApiError>()
+ for ((symbol, flag) in flaggedSymbolsInSource) {
+ try {
+ if (flags.getValue(flag)) {
+ if (!symbolsInOutput.contains(symbol)) {
+ errors.add(EnabledFlaggedApiNotPresentError(symbol, flag))
+ }
+ } else {
+ if (symbolsInOutput.contains(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 671b036..75379ff 100755
--- a/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
+++ b/tools/finalization/finalize-aidl-vndk-sdk-resources.sh
@@ -133,10 +133,10 @@
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/ide_query/ide_query.go b/tools/ide_query/ide_query.go
index 50264fd..de84fbe 100644
--- a/tools/ide_query/ide_query.go
+++ b/tools/ide_query/ide_query.go
@@ -304,6 +304,7 @@
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/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index a2c0949..b39a82c 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -1055,20 +1055,6 @@
for call in add_partition_calls:
add_partition(*call)
else:
- # When calling avbtool make_vbmeta_image, it uses the `partitions`
- # dictionary to include the options for --include_descriptors_from_image.
- # The vbmeta image is different if the order of the
- # --include_descriptors_from_image changes. As the images are generated
- # parallelly and entries are added on completion of image creation,
- # this `partitions` dict might be indeterministic as the order of
- # completion of image creation cannot be predicted.
- # To address this issue, add keys to the dict `partitions` with null values
- # in the order they are listed in the variable `add_partition_calls`, and
- # then the values are updated by `add_partition` keeping the order of the
- # items. This ensures generated vbmeta.img is the same for the same input.
- for call in add_partition_calls:
- if call[1]:
- partitions[call[0]] = None
with ThreadPoolExecutor(max_workers=len(add_partition_calls)) as executor:
for future in [executor.submit(add_partition, *call) for call in add_partition_calls]:
future.result()
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 264c2d8..2367691 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -490,7 +490,6 @@
return -1
props = [
- "ro.board.api_level",
"ro.board.first_api_level",
"ro.product.first_api_level",
]
@@ -1524,7 +1523,7 @@
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 @@
"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/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 2c5fe0d..432ea19 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -195,6 +195,8 @@
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 @@
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
@@ -1048,6 +1050,10 @@
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/tool_event_logger/Android.bp b/tools/tool_event_logger/Android.bp
new file mode 100644
index 0000000..7a1d2aa
--- /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 0000000..b692c9e
--- /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 0000000..61e28a2
--- /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 0000000..65a9696
--- /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 0000000..34b6c35
--- /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 b0bf2e4..8cf84f5 100755
--- a/tools/whichgit
+++ b/tools/whichgit
@@ -50,7 +50,7 @@
referenced_dirs.add(d)
prev_dir = d
break
- return [d[0:-1] for d in referenced_dirs]
+ return referenced_dirs
def main(argv):
@@ -63,9 +63,11 @@
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 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"]
@@ -92,15 +94,21 @@
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__":