diff options
66 files changed, 1744 insertions, 537 deletions
diff --git a/BUILD.bazel b/BUILD.bazel index 958984d..9690bae 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -100,7 +100,6 @@ _HERMETIC_TOOLS = [ "//prebuilts/kernel-build-tools:linux-x86/bin/swig", "//prebuilts/kernel-build-tools:linux-x86/bin/tune2fs", "//prebuilts/kernel-build-tools:linux-x86/bin/ufdt_apply_overlay", - "//prebuilts/clang/host/linux-x86/clang-{}:bin/llvm-strings".format(VARS["CLANG_VERSION"]), ] + glob([ # Intentionally glob here to test for existance of alias destination # buildifier: disable=constant-glob @@ -155,6 +154,7 @@ _TOYS = [ "sort", "stat", "tail", + "tar", "tee", "test", "timeout", @@ -172,12 +172,20 @@ _TOYS = [ "whoami", "xargs", "xxd", - # Real tar binary that shouldn't be used directly - "kleaf_internal_do_not_use/tar", ] hermetic_tools( name = "hermetic-tools", + extra_args = { + "rsync": ["--no-group"], + "tar": [ + "--mtime=@0", + "--owner=0", + "--group=0", + "--numeric-owner", + "--sort=name", + ], + }, symlinks = select({ "//build/kernel/kleaf:debug_cache_dir_conflict_needs_flock": { ":toybox_flock": "flock", @@ -204,6 +212,11 @@ hermetic_tools( "//conditions:default": { ":toybox_gzip": "gzip", }, + }) | select({ + "//build/kernel/kleaf:remove_strings_from_hermetic_tools_is_true": {}, + "//conditions:default": { + "//prebuilts/clang/host/linux-x86/clang-{}:bin/llvm-strings".format(VARS["CLANG_VERSION"]): "llvm-strings", + }, }) | { ":toybox": ":".join(_TOYS), @@ -211,14 +224,7 @@ hermetic_tools( "@kleaf_host_tools//:bash": "bash", "@kleaf_host_tools//:sh": "sh", "@kleaf_host_tools//:perl": "perl", - - # Real rsync that shouldn't be used directly - "@kleaf_host_tools//:rsync": "kleaf_internal_do_not_use/rsync", - # Embed additional args that calls kleaf_internal_do_not_use/rsync - "build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync": "rsync", - - # Embeds additional args that calls kleaf_internal_do_not_use/tar - "build-tools/kleaf_internal_do_not_use_path/linux-x86/tar": "tar", + "@kleaf_host_tools//:rsync": "rsync", # Additional symlinks other than in //prebuilts/build-tools "//prebuilts/kernel-build-tools:linux-x86/bin/depmod": ":".join([ @@ -305,7 +311,6 @@ py_binary( name = "abi_process_symbols", srcs = ["abi/process_symbols.py"], data = [ - "abi/symbols.allow", "abi/symbols.deny", ], main = "abi/process_symbols.py", diff --git a/abi/process_symbols.py b/abi/process_symbols.py index 5d7bada..0d43da9 100755 --- a/abi/process_symbols.py +++ b/abi/process_symbols.py @@ -15,7 +15,6 @@ # limitations under the License. import argparse -import enum import os import sys @@ -24,12 +23,6 @@ _TRACE_POINT = '__tracepoint_' _TRACE_ITER = '__traceiter_' -class Status(enum.Enum): - UNKNOWN = 0 - ALLOWED = 1 - FORBIDDEN = 2 - - def _validate_symbols(symbol_list, symbols): """Validates Tracepoints consistenty in a given symbol list.""" missing = [] @@ -55,31 +48,27 @@ def _validate_symbols(symbol_list, symbols): sys.exit(1) -def _read_config(allow_file, deny_file): - """Reads symbol configuration file.""" - config = {} +def _read_denied_symbols_config(deny_file): + """Reads denied symbols configuration file.""" + denied_symbols = {} - def read_file(status, config_file): - with open(config_file) as file: - for line in file: - fields = line.rstrip('\n').split(None, 1) - if not fields: - continue - symbol = fields[0] - if symbol.startswith('#'): - continue - reason = '' - if len(fields) > 1: - reason = fields[1] - if symbol in config: - print(f"symbol '{symbol}' duplicate configuration", file=sys.stderr) - continue - config[symbol] = (status, reason) + with open(deny_file) as file: + for line in file: + fields = line.rstrip('\n').split(None, 1) + if not fields: + continue + symbol = fields[0] + if symbol.startswith('#'): + continue + reason = '' + if len(fields) > 1: + reason = fields[1] + if symbol in denied_symbols: + print(f"symbol '{symbol}' duplicate configuration", file=sys.stderr) + continue + denied_symbols[symbol] = reason - read_file(Status.FORBIDDEN, deny_file) - read_file(Status.ALLOWED, allow_file) - - return config + return denied_symbols def _read_symbol_lists(symbol_lists): @@ -106,21 +95,8 @@ def _get_symbols(lines): return symbols -def _check_symbols(config, symbols): - """Checks symbols against configuration.""" - report = [] - for symbol in sorted(symbols): - if symbol in config: - status, reason = config[symbol] - report.append([symbol, status, reason]) - else: - report.append([symbol, Status.UNKNOWN, '']) - return report - - def main(): dir = os.path.dirname(sys.argv[0]) - allow_file = os.path.join(dir, 'symbols.allow') deny_file = os.path.join(dir, 'symbols.deny') parser = argparse.ArgumentParser() @@ -143,9 +119,6 @@ def main(): '--out-file', required=True, help='combined symbol list file name' ) parser.add_argument( - '--report-file', required=True, help='symbol list report file name' - ) - parser.add_argument( '--verbose', action='store_true', help='increase verbosity of the output' ) @@ -155,12 +128,10 @@ def main(): out_directory = args.out_dir symbol_lists = [os.path.join(in_directory, s) for s in args.symbol_lists] out_file = os.path.join(out_directory, args.out_file) - report_file = os.path.join(out_directory, args.report_file) - config = _read_config(allow_file, deny_file) + denied_symbols = _read_denied_symbols_config(deny_file) lines = _read_symbol_lists(symbol_lists) symbols = _get_symbols(lines) - report = _check_symbols(config, symbols) if args.verbose: print('========================================================') @@ -170,13 +141,12 @@ def main(): exit_status = 0 if args.verbose: - print(f'Generating ABI symbol report {report_file}') - with open(report_file, 'w') as rf: - for symbol, status, reason in report: - rf.write(f'{symbol}\t{status.name}\t{reason}\n') - if status == Status.FORBIDDEN: - print(f"symbol '{symbol}' is not allowed: {reason}", file=sys.stderr) - exit_status = 1 + print('Checking symbols are not forbidden') + for symbol in symbols: + if symbol in denied_symbols: + reason = denied_symbols[symbol] + print(f"symbol '{symbol}' is not allowed: {reason}", file=sys.stderr) + exit_status = 1 return exit_status diff --git a/abi/symbols.allow b/abi/symbols.allow deleted file mode 100644 index 4ecdb38..0000000 --- a/abi/symbols.allow +++ /dev/null @@ -1,2 +0,0 @@ -__put_task_struct -module_layout diff --git a/abi_compliance.sh b/abi_compliance.sh new file mode 100755 index 0000000..71a5160 --- /dev/null +++ b/abi_compliance.sh @@ -0,0 +1,35 @@ +#!/bin/bash -e + +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script is used as a test after +# `tools/bazel //common:kernel_aarch64_abi_dist`` +# to check if the build reported ABI differences. It exits with non-zero error +# code if ABI report file is missing or when it is not empty. + +# Example: +# tools/bazel //common:kernel_aarch64_abi_dist +# build/kernel/abi_compliance.sh out_abi/kernel_aarch64/dist + +dist_dir="$1" +abi_report=$(cat "${dist_dir}/abi_stgdiff/abi.report.short") + +if [ -n "${abi_report}" ]; then + echo 'ERROR: ABI DIFFERENCES HAVE BEEN DETECTED!' >&2 + echo "ERROR: ${abi_report}" >&2 + exit 1 +fi + +echo 'INFO: no ABI differences reported by dependency target build.' diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync b/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync deleted file mode 100755 index 5abcde9..0000000 --- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -${0%/*}/kleaf_internal_do_not_use/rsync "$@" --no-group diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar b/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar deleted file mode 100755 index 46c7d65..0000000 --- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -${0%/*}/kleaf_internal_do_not_use/tar "$@" \ - --mtime=@0 \ - --owner=0 \ - --group=0 \ - --numeric-owner \ - --sort=name diff --git a/build_utils.sh b/build_utils.sh index 50260e3..d204ac7 100644 --- a/build_utils.sh +++ b/build_utils.sh @@ -637,11 +637,18 @@ function build_boot_images() { exit 1 fi + MKBOOTFS_ARGS=() + if [ -n "${VENDOR_RAMDISK_DEV_NODES}" ]; then + for vendor_ramdisk_dev_nodes in ${VENDOR_RAMDISK_DEV_NODES}; do + MKBOOTFS_ARGS+=("-n" "${vendor_ramdisk_dev_nodes}") + done + fi + if [ -n "${SKIP_UNPACKING_RAMDISK}" ] && [ -e "${VENDOR_RAMDISK_BINARY}" ]; then cp "${VENDOR_RAMDISK_BINARY}" "${DIST_DIR}/ramdisk.${RAMDISK_EXT}" - elif [ "${#MKBOOTIMG_RAMDISK_DIRS[@]}" -gt 0 ]; then + elif [ "${#MKBOOTIMG_RAMDISK_DIRS[@]}" -gt 0 ] || [ "${#MKBOOTFS_ARGS[@]}" -gt 0 ]; then MKBOOTIMG_RAMDISK_CPIO="${MKBOOTIMG_STAGING_DIR}/ramdisk.cpio" - mkbootfs "${MKBOOTIMG_RAMDISK_DIRS[@]}" >"${MKBOOTIMG_RAMDISK_CPIO}" + mkbootfs "${MKBOOTIMG_RAMDISK_DIRS[@]}" "${MKBOOTFS_ARGS[@]}" >"${MKBOOTIMG_RAMDISK_CPIO}" ${RAMDISK_COMPRESS} "${MKBOOTIMG_RAMDISK_CPIO}" >"${DIST_DIR}/ramdisk.${RAMDISK_EXT}" fi diff --git a/gki/download_from_ci b/gki/download_from_ci index ca504d4..4cb3a26 100755 --- a/gki/download_from_ci +++ b/gki/download_from_ci @@ -47,8 +47,6 @@ ARM64_TARGETS = ["kernel_aarch64", "u-boot_gem5_aarch64", "u-boot_qemu_aarch64", "u-boot_rockpi4"] -RISCV64_TARGETS = ["kernel_riscv64", - "u-boot_qemu_riscv64"] X86_64_TARGETS = ["kernel_x86_64", "kernel_debug_x86_64", "kernel_microdroid_x86_64", @@ -92,20 +90,21 @@ def parse_args(): nargs="?", type=str, help='the build target to download, e.g. "kernel_aarch64"') - parser.add_argument( + update_group = parser.add_mutually_exclusive_group() + update_group.add_argument( "-u", "--update-gki", action="store_true", help="update GKI kernel prebuilts in Android platform (set $ANDROID_BUILD_TOP)") - parser.add_argument( + update_group.add_argument( "--update-16k", action="store_true", help="update 16k kernel prebuilts in Android platform (set $ANDROID_BUILD_TOP)") - parser.add_argument( + update_group.add_argument( "--update-u-boot", action="store_true", help="update U-Boot prebuilts in Android platform (set $ANDROID_BUILD_TOP)") - parser.add_argument( + update_group.add_argument( "--update-microdroid", action="store_true", help="update Microdroid kernel prebuilts in Android platform (set $ANDROID_BUILD_TOP)") @@ -167,6 +166,8 @@ def get_build_kernel_version(data): branch = json.loads(data)["branch"] if branch == "aosp_kernel-common-android-mainline": return "mainline" + elif branch == "aosp_kernel-common-android-mainline-riscv64": + return "mainline-riscv64" else: pattern = "android(\d\d)?-(?P<version>\d+\.\d+|\w\+)(-stable)?(-\d{4}-\d{2})?$" result = re.search(pattern, branch) @@ -538,12 +539,20 @@ def update_androidx(output_dir, version, android_version): update_androidx_virtual_device_modules(gitlog, output_dir, version) update_androidx_cf(gitlog, output_dir, version) -def update_android_mainline_cf(gitlog, output_dir): +def update_android_mainline_riscv64(output_dir): + # Although we don't update the x86_64 kernel, we use it's path to compute the + # binary kernel version, as the version parser only supports x86 images. + x86_64_kernel = os.path.join(output_dir, "kernel", "prebuilts", "mainline", + "x86_64", "kernel-mainline-allsyms") + (old_version, old_sha) = get_binary_kernel_version(x86_64_kernel) + (new_version, new_sha) = get_binary_kernel_version(x86_64_kernel) + gitlog = get_git_log(old_sha, new_sha) + cf_dir = os.path.join(output_dir, "device", "google", "cuttlefish_prebuilts", "kernel") riscv64_cf_dir = os.path.join(cf_dir, "mainline-riscv64") - subprocess.check_call(["git", "rm", "-rf", riscv64_cf_dir], cwd=cf_dir) - download_kernel("kernel_virt_riscv64", "mainline", riscv64_cf_dir) + + download_kernel("kernel_virt_riscv64", "mainline-riscv64", riscv64_cf_dir) download_kernel_modules("kernel_virt_riscv64", riscv64_cf_dir) riscv64_cf_system_dlkm_dir = os.path.join(riscv64_cf_dir, "system_dlkm") @@ -573,8 +582,6 @@ def update_android_mainline(output_dir): gitlog = get_git_log(old_sha, new_sha) commit_prebuilts(arm64_dir, gitlog) - update_android_mainline_cf(gitlog, output_dir) - def update_16k(): url_base = BASE_URL.format(build_id=_args.build_id, target="kernel_aarch64") url = os.path.join(url_base, "BUILD_INFO") @@ -603,11 +610,25 @@ def update_16k(): download_kernel_modules("kernel_virt_aarch64_16k", arm64_16k_mods_dir, fetch_initramfs=True) commit_prebuilts(arm64_16k_mods_dir, gitlog, "16KB page size kernel") +def get_build_info(targets): + for target in targets: + try: + url_base = BASE_URL.format(build_id=_args.build_id, target=target) + url = os.path.join(url_base, "BUILD_INFO") + response = urllib.request.urlopen(url) + data = response.read().decode("utf-8") + # Test if we can load an expected value in the response data. + json.loads(data)["branch"] + return data + except json.decoder.JSONDecodeError: + pass + raise json.decoder.JSONDecodeError("URL data did not contain BUILD_INFO.") + def update_gki(): - url_base = BASE_URL.format(build_id=_args.build_id, target="kernel_aarch64") - url = os.path.join(url_base, "BUILD_INFO") - response = urllib.request.urlopen(url) - data = response.read().decode("utf-8") + # The 'kernel_riscv64' target is on a separate branch that does not include + # 'kernel_aarch64' as a target. If we fail to download the build information + # from 'kernel_aarch64', try again with 'kernel_riscv64' as a fallback. + data = get_build_info(["kernel_aarch64", "kernel_riscv64"]) branch = json.loads(data)["branch"] output_dir = os.environ["ANDROID_BUILD_TOP"] @@ -622,6 +643,8 @@ def update_gki(): update_android11_5_4(output_dir) elif branch == "aosp_kernel-common-android-mainline": update_android_mainline(output_dir) + elif branch == "aosp_kernel-common-android-mainline-riscv64": + update_android_mainline_riscv64(output_dir) else: update_androidx(output_dir, get_build_kernel_version(data), get_android_version(branch)) @@ -634,7 +657,7 @@ def download_u_boot(target, output_dir): extension = "elf" elif target == "u-boot_rockpi4": extension = "itb" - elif target in ARM_TARGETS + ARM64_TARGETS + RISCV64_TARGETS: + elif target in ARM_TARGETS + ARM64_TARGETS: extension = "bin" files = [f"u-boot.{extension}"] files.append("System.map") @@ -668,7 +691,6 @@ def update_u_boot_mainline(output_dir): gem5_aarch64_dir = os.path.join(bootloader_dir, "gem5_aarch64") qemu_aarch64_dir = os.path.join(bootloader_dir, "qemu_aarch64") qemu_arm_dir = os.path.join(bootloader_dir, "qemu_arm") - qemu_riscv64_dir = os.path.join(bootloader_dir, "qemu_riscv64") qemu_x86_64_dir = os.path.join(bootloader_dir, "qemu_x86_64") rockpi_aarch64_dir = os.path.join(bootloader_dir, "rockpi_aarch64") @@ -697,9 +719,6 @@ def update_u_boot_mainline(output_dir): if os.path.exists(qemu_arm_dir): subprocess.check_call(["git", "rm", "-rf", "*"], cwd=qemu_arm_dir) download_u_boot("u-boot_qemu_arm", qemu_arm_dir) - if os.path.exists(qemu_riscv64_dir): - subprocess.check_call(["git", "rm", "-rf", "*"], cwd=qemu_riscv64_dir) - download_u_boot("u-boot_qemu_riscv64", qemu_riscv64_dir) if os.path.exists(qemu_x86_64_dir): subprocess.check_call(["git", "rm", "-rf", "*"], cwd=qemu_x86_64_dir) download_u_boot("u-boot_qemu_x86_64", qemu_x86_64_dir) diff --git a/init/init_ddk.py b/init/init_ddk.py index 2798924..998dd02 100644 --- a/init/init_ddk.py +++ b/init/init_ddk.py @@ -25,9 +25,9 @@ import pathlib import shutil import subprocess import sys +import tarfile import tempfile -import textwrap -import urllib +import urllib.parse _TOOLS_BAZEL = "tools/bazel" _DEVICE_BAZELRC = "device.bazelrc" @@ -51,7 +51,6 @@ kernel_prebuilt_ext = use_extension( ) kernel_prebuilt_ext.declare_kernel_prebuilts( name = "gki_prebuilts", - download_configs = {download_configs}, local_artifact_path = "{prebuilts_dir_relative}", ) use_repo(kernel_prebuilt_ext, "gki_prebuilts") @@ -87,7 +86,7 @@ class KleafProjectSetter: tools_bazel.symlink_to(kleaf_tools_bazel) @staticmethod - def _update_file(path: pathlib.Path | str, update: str): + def _update_file(path: pathlib.Path, update: str): """Updates the content of a section between markers in a file.""" add_content: bool = False skip_line: bool = False @@ -123,6 +122,10 @@ class KleafProjectSetter: def _try_rel_workspace(self, path: pathlib.Path): """Tries to convert |path| to be relative to ddk_workspace.""" + if not self.ddk_workspace: + raise KleafProjectSetterError( + "ERROR: _try_rel_workspace called without --ddk_workspace set!" + ) try: return path.relative_to(self.ddk_workspace) except ValueError: @@ -134,12 +137,30 @@ class KleafProjectSetter: ) return path - def _read_download_configs(self) -> str: - """Reads the previously downloaded download_configs.json file.""" - download_configs = self.prebuilts_dir / "download_configs.json" - with open(download_configs, "r", encoding="utf-8") as config: - # Compress the representation by removing empty spaces to save some space. - return repr(json.dumps(json.load(config), separators=(",", ":"))) + def _get_local_path_overrides(self): + """Naive algorithm to extract local_path_override()'s from local @kleaf.""" + path_attr_prefix = 'path = "' + section = [] + overrides = [] + module_bazel = self.kleaf_repo / _MODULE_BAZEL_FILE + # Modify path so it is relative to the current DDK workspace. + kleaf_repo = self._try_rel_workspace(self.kleaf_repo) + with open(module_bazel, "r", encoding="utf-8") as src: + for line in src: + if line.startswith("local_path_override("): + section.append(line) + continue + if not section: + continue + elif line.lstrip().startswith(path_attr_prefix): + line = line.strip().removeprefix(path_attr_prefix) + line = line.removesuffix('",') + line = f' path = "{kleaf_repo / line}",\n' + section.append(line) + if line.strip() == ")": + overrides.append("".join(section)) + section.clear() + return "".join(overrides) def _generate_module_bazel(self): """Configures the dependencies for the DDK workspace.""" @@ -151,20 +172,23 @@ class KleafProjectSetter: module_bazel_content += _KLEAF_DEPENDENCY_TEMPLATE.format( kleaf_repo_relative=self._try_rel_workspace(self.kleaf_repo), ) + module_bazel_content += self._get_local_path_overrides() + # b/338440785 Due to an issue in Bazel, rules_cc seems to be + # implicitly added in a fallback WORKSPACE.bzlmod file, hence + # forcing an empty one here. + workspace_bzlmod = self.ddk_workspace / "WORKSPACE.bzlmod" + workspace_bzlmod.touch(exist_ok=True) if self.prebuilts_dir: module_bazel_content += "\n" module_bazel_content += _LOCAL_PREBUILTS_CONTENT_TEMPLATE.format( - # TODO: b/328770706 - Use download_configs_file when available. - download_configs=self._read_download_configs(), # The prebuilts directory must be relative to the DDK workspace. prebuilts_dir_relative=self._try_rel_workspace( self.prebuilts_dir ), ) - if module_bazel_content: - self._update_file(module_bazel, module_bazel_content) - else: + if not module_bazel_content: logging.info("Nothing to update in %s", module_bazel) + self._update_file(module_bazel, module_bazel_content) def _generate_bazelrc(self): """Creates a Bazel configuration file with the minimum setup required.""" @@ -174,18 +198,27 @@ class KleafProjectSetter: kleaf_repo = self._try_rel_workspace(self.kleaf_repo) if not kleaf_repo.is_absolute(): - kleaf_repo = (pathlib.Path("%workspace%") / kleaf_repo) + kleaf_repo = pathlib.Path("%workspace%") / kleaf_repo + + bazelrc_content = [] + bazelrc_content.append(( + "common" + f" --registry=file://{kleaf_repo}/external/bazelbuild-bazel-central-registry" + )) + # Explicitly disable internet usage. + bazelrc_content.append("common --config=no_internet") self._update_file( bazelrc, - textwrap.dedent(f"""\ - common --config=internet - common --registry=file://{kleaf_repo}/external/bazelbuild-bazel-central-registry - """), + "\n".join(bazelrc_content), ) - def _get_url(self, remote_filename: str) -> str: + def _get_url(self, remote_filename: str) -> str | None: """Returns a valid url when it can be formed with target and id.""" + if not self.url_fmt: + raise KleafProjectSetterError( + "ERROR: _get_url called without url_fmt set!" + ) url = self.url_fmt.format( build_id=self.build_id, build_target=self.build_target, @@ -204,6 +237,7 @@ class KleafProjectSetter: """Validates that download are possible within the current context.""" if not self.url_fmt: return False + # Check if build_id is missing and url_fmt has an anchor depending on it. if self._get_url("") is None: return False return True @@ -224,6 +258,10 @@ class KleafProjectSetter: not be downloaded. """ url = self._get_url(remote_filename) + if not url: + raise KleafProjectSetterError( + f"ERROR: Unable to download {remote_filename}: can't infer URL" + ) # Workaround: Rely on host keychain to download files. # This is needed otheriwese downloads fail when running this script # using the hermetic Python toolchain. @@ -240,13 +278,21 @@ class KleafProjectSetter: def _infer_download_list(self) -> dict[str, dict]: """Infers the list of files to be downloaded using download_configs.json.""" + if not self.prebuilts_dir: + raise KleafProjectSetterError( + "ERROR: _infer_download_list called without --prebuilts_dir!" + ) download_configs = self.prebuilts_dir / "download_configs.json" with open(download_configs, "w+", encoding="utf-8") as config: - self._download("download_configs.json", config.name) + self._download("download_configs.json", pathlib.Path(config.name)) return json.load(config) def _download_prebuilts(self) -> None: """Downloads prebuilts from a given build_id when provided.""" + if not self.prebuilts_dir: + raise KleafProjectSetterError( + "ERROR: _download_prebuilts called without --prebuilts_dir!" + ) logging.info("Downloading prebuilts into %s", self.prebuilts_dir) files_dict = self._infer_download_list() with concurrent.futures.ThreadPoolExecutor() as executor: @@ -273,13 +319,75 @@ class KleafProjectSetter: self.kleaf_repo.mkdir(parents=True, exist_ok=True) # TODO: b/328770706 - According to the needs, syncing git repos logic should go here. + self._populate_kleaf_repo_extra_files() + def _handle_prebuilts(self) -> None: - if not self.ddk_workspace or not self.prebuilts_dir: + if not self.prebuilts_dir: return self.prebuilts_dir.mkdir(parents=True, exist_ok=True) if self._can_download_artifacts(): self._download_prebuilts() + def _populate_kleaf_repo_extra_files(self) -> None: + """Populates kleaf_repo by adding extra files""" + if self.local: + logging.info("Skipped populating kleaf_repo with --local.") + # --local assumes the kernel source tree is complete. + return + if not self.kleaf_repo: + logging.info( + "Skipped populating --kleaf_repo because it is unspecified" + ) + return + if not self.prebuilts_dir: + logging.info( + "No prebuilts specified, skip populating %s", self.kleaf_repo + ) + return + self._extract_headers_archive(self.prebuilts_dir, self.kleaf_repo) + + build_config_constants = self.prebuilts_dir / "build.config.constants" + if not build_config_constants.is_file(): + logging.warning( + "%s is not a file, skip copying", build_config_constants + ) + return + shutil.copy( + build_config_constants, + self.kleaf_repo / "common/build.config.constants", + ) + if not (self.kleaf_repo / "common/BUILD.bazel").is_file(): + (self.kleaf_repo / "common/BUILD.bazel").write_text("") + + @staticmethod + def _extract_headers_archive( + prebuilts_dir: pathlib.Path, kleaf_repo: pathlib.Path + ): + """Extracts DDK headers archive from prebuilts_dir into kleaf_repo""" + # TODO: This should be target-specific. The name of the output is + # currently (2024-05-16) defined by common/BUILD.bazel, but it may + # change in the future. + header_archives = list( + prebuilts_dir.glob("*_ddk_headers_archive.tar.gz") + ) + if not header_archives: + logging.warning( + "No _ddk_headers_archive.tar.gz found in %s, " + "skipping header extraction.", + prebuilts_dir, + ) + return + if len(header_archives) > 1: + raise KleafProjectSetterError( + "Multiple _ddk_headers_archive.tar.gz found in " + f"{prebuilts_dir}: {header_archives}" + ) + logging.info( + "Extracting header archive %s to %s", header_archives[0], kleaf_repo + ) + with tarfile.open(header_archives[0]) as tar: + tar.extractall(kleaf_repo) + def _run(self) -> None: self._symlink_tools_bazel() self._generate_module_bazel() @@ -287,15 +395,15 @@ class KleafProjectSetter: def run(self) -> None: self._handle_ddk_workspace() - self._handle_kleaf_repo() self._handle_prebuilts() + self._handle_kleaf_repo() self._run() if __name__ == "__main__": - def abs_path(path: str) -> pathlib.Path | None: - path = pathlib.Path(path) + def abs_path(path_string: str) -> pathlib.Path | None: + path = pathlib.Path(path_string) if not path.is_absolute(): raise ValueError(f"{path} is not an absolute path.") return path @@ -349,7 +457,9 @@ if __name__ == "__main__": args = parser.parse_args() logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") - + # Validate pre-condition. + if args.local and not args.kleaf_repo: + parser.error("--local requires --kleaf_repo.") try: KleafProjectSetter(**vars(args)).run() except KleafProjectSetterError as e: diff --git a/init/init_ddk_test.py b/init/init_ddk_test.py index db6773f..77fa3ba 100644 --- a/init/init_ddk_test.py +++ b/init/init_ddk_test.py @@ -177,7 +177,6 @@ class KleafProjectSetterTest(parameterized.TestCase): """Tests prebuilts setup is correct for relative and non-relative to workspace dirs.""" with tempfile.TemporaryDirectory() as tmp: ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" - # Verify the right local_artifact_path is set for prebuilts # in a relative to workspace directory. prebuilts_dir_rel = ddk_workspace / "prebuilts_dir" @@ -226,13 +225,15 @@ class KleafProjectSetterTest(parameterized.TestCase): prebuilts_dir = ddk_workspace / "prebuilts_dir" download_configs = ddk_workspace / "download_configs.json" download_configs.parent.mkdir(parents=True, exist_ok=True) - download_configs.write_text(json.dumps({ - "non-existent-file": { - "target_suffix": "non-existent-file", - "mandatory": False, - "remote_filename_fmt": "non-existent-file", - } - })) + download_configs.write_text( + json.dumps({ + "non-existent-file": { + "target_suffix": "non-existent-file", + "mandatory": False, + "remote_filename_fmt": "non-existent-file", + } + }) + ) with open(download_configs, "r", encoding="utf-8"): url_fmt = f"file://{str(download_configs.parent)}/{{filename}}" init_ddk.KleafProjectSetter( @@ -245,6 +246,52 @@ class KleafProjectSetterTest(parameterized.TestCase): url_fmt=url_fmt, ).run() + @parameterized.named_parameters( + # (Name, MODULE.bazel in @kleaf, expectation) + ("Empty", "", ""), + ( + "Dependencies", + """ +local_path_override( + module_name = "abseil-py", + path = "external/python/absl-py", +) +local_path_override( + module_name = "apple_support", + path = "external/bazelbuild-apple_support", +) + """, + """local_path_override( + module_name = "abseil-py", + path = "kleaf_repo/external/python/absl-py", +) +local_path_override( + module_name = "apple_support", + path = "kleaf_repo/external/bazelbuild-apple_support", +)\n""", + ), + ) + def test_local_path_overrides_extraction( + self, current_content, wanted_content + ): + """Tests extraction of local path overrides works correctly.""" + with tempfile.TemporaryDirectory() as tmp: + ddk_workspace = pathlib.Path(tmp) / "ddk_workspace" + kleaf_repo = ddk_workspace / "kleaf_repo" + kleaf_repo.mkdir(parents=True, exist_ok=True) + kleaf_repo_module_bazel = kleaf_repo / init_ddk._MODULE_BAZEL_FILE + kleaf_repo_module_bazel.write_text(current_content) + got_content = init_ddk.KleafProjectSetter( + build_id=None, + build_target=None, + ddk_workspace=ddk_workspace, + kleaf_repo=kleaf_repo, + local=True, + prebuilts_dir=None, + url_fmt=None, + )._get_local_path_overrides() + self.assertEqual(got_content, wanted_content) + # This could be run as: tools/bazel test //build/kernel:init_ddk_test --test_output=all if __name__ == "__main__": diff --git a/kleaf/BUILD.bazel b/kleaf/BUILD.bazel index ce58f2e..b45d048 100644 --- a/kleaf/BUILD.bazel +++ b/kleaf/BUILD.bazel @@ -635,6 +635,19 @@ config_setting( visibility = ["//visibility:public"], ) +# If true, drop llvm-strings from hermetic-tools. +bool_flag( + name = "remove_strings_from_hermetic_tools", + build_setting_default = False, + visibility = ["//visibility:private"], +) + +config_setting( + name = "remove_strings_from_hermetic_tools_is_true", + flag_values = {":remove_strings_from_hermetic_tools": "1"}, + visibility = ["//build/kernel:__pkg__"], +) + # The values of --config, as passed by command line and bazelrc files. _config_values = ( "local", diff --git a/kleaf/README.md b/kleaf/README.md index 02900e9..e7e7c3a 100644 --- a/kleaf/README.md +++ b/kleaf/README.md @@ -56,6 +56,8 @@ [Kleaf Development](docs/kleaf_development.md) +[Discover newest Kleaf features in Canary](docs/canary.md) + ### Configurations in command line `--config=fast`: [Make local builds faster](docs/fast.md) @@ -66,6 +68,8 @@ `--config=stamp`: [Handling SCM version](docs/scmversion.md) +`--config=canary`: [Kleaf Canary Features](docs/canary.md) + ### Flags For a full list of flags, run diff --git a/kleaf/artifact_tests/BUILD.bazel b/kleaf/artifact_tests/BUILD.bazel index 2c95478..28d3526 100644 --- a/kleaf/artifact_tests/BUILD.bazel +++ b/kleaf/artifact_tests/BUILD.bazel @@ -24,12 +24,12 @@ bzl_library( srcs = [ "device_modules_test.bzl", "kernel_test.bzl", - "py_test_hack.bzl", ], visibility = ["//build/kernel/kleaf:__subpackages__"], deps = [ "//build/kernel/kleaf/impl", "//build/kernel/kleaf/tests:empty_test", + "//build/kernel/kleaf/tests:hermetic_test", "@bazel_skylib//rules:write_file", ], ) diff --git a/kleaf/artifact_tests/device_modules_test.bzl b/kleaf/artifact_tests/device_modules_test.bzl index bcc1b40..9cfe20e 100644 --- a/kleaf/artifact_tests/device_modules_test.bzl +++ b/kleaf/artifact_tests/device_modules_test.bzl @@ -16,12 +16,11 @@ load("@bazel_skylib//rules:write_file.bzl", "write_file") load("//build/kernel/kleaf/impl:common_providers.bzl", "KernelModuleInfo") -load("//build/kernel/kleaf/impl:hermetic_exec.bzl", "hermetic_exec_test") load("//build/kernel/kleaf/impl:kernel_build.bzl", "kernel_build") load("//build/kernel/kleaf/impl:kernel_modules_install.bzl", "kernel_modules_install") load("//build/kernel/kleaf/impl:utils.bzl", "kernel_utils") load("//build/kernel/kleaf/tests:empty_test.bzl", "empty_test") -load(":py_test_hack.bzl", "run_py_binary_cmd") +load("//build/kernel/kleaf/tests:hermetic_test.bzl", "hermetic_test") visibility("//build/kernel/kleaf/...") @@ -48,13 +47,12 @@ def _check_signature( base_kernel_module, expect_signature, directory): - test_binary = Label("//build/kernel/kleaf/artifact_tests:check_module_signature") args = [ "--module", base_kernel_module, "--expect_signature" if expect_signature else "--noexpect_signature", ] - data = [test_binary] + data = [] if directory: args += [ "--dir", @@ -62,10 +60,10 @@ def _check_signature( ] data.append(directory) - hermetic_exec_test( + hermetic_test( name = name, + actual = Label("//build/kernel/kleaf/artifact_tests:check_module_signature"), data = data, - script = run_py_binary_cmd(test_binary), args = args, timeout = "short", ) diff --git a/kleaf/artifact_tests/kernel_test.bzl b/kleaf/artifact_tests/kernel_test.bzl index 2bd35e7..ef9a551 100644 --- a/kleaf/artifact_tests/kernel_test.bzl +++ b/kleaf/artifact_tests/kernel_test.bzl @@ -15,8 +15,7 @@ Tests for artifacts produced by kernel_module. """ -load("//build/kernel/kleaf/impl:hermetic_exec.bzl", "hermetic_exec_test") -load(":py_test_hack.bzl", "run_py_binary_cmd") +load("//build/kernel/kleaf/tests:hermetic_test.bzl", "hermetic_test") visibility("//build/kernel/kleaf/...") @@ -35,18 +34,17 @@ def kernel_module_test( See complete list [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). """ - test_binary = Label("//build/kernel/kleaf/artifact_tests:kernel_module_test") args = [] - data = [test_binary] + data = [] if modules: args.append("--modules") args += ["$(rootpaths {})".format(module) for module in modules] data += modules - hermetic_exec_test( + hermetic_test( name = name, + actual = Label("//build/kernel/kleaf/artifact_tests:kernel_module_test"), data = data, - script = run_py_binary_cmd(test_binary), args = args, timeout = "short", **kwargs @@ -66,17 +64,17 @@ def kernel_build_test( See complete list [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). """ - test_binary = Label("//build/kernel/kleaf/artifact_tests:kernel_build_test") args = [] - data = [test_binary] + data = [] if target: args += ["--artifacts", "$(rootpaths {})".format(target)] data.append(target) - hermetic_exec_test( + hermetic_test( name = name, + actual = Label("//build/kernel/kleaf/artifact_tests:kernel_build_test"), + use_cc_toolchain = True, data = data, - script = run_py_binary_cmd(test_binary), args = args, timeout = "short", **kwargs @@ -98,21 +96,19 @@ def initramfs_modules_options_test( See complete list [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). """ - test_binary = Label("//build/kernel/kleaf/artifact_tests:initramfs_modules_options_test") args = [ "--expected", "$(rootpath {})".format(expected_modules_options), "$(rootpaths {})".format(kernel_images), ] - hermetic_exec_test( + hermetic_test( name = name, + actual = Label("//build/kernel/kleaf/artifact_tests:initramfs_modules_options_test"), data = [ expected_modules_options, kernel_images, - test_binary, ], - script = run_py_binary_cmd(test_binary), args = args, timeout = "short", **kwargs @@ -142,7 +138,6 @@ def initramfs_modules_lists_test( See complete list [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). """ - test_binary = Label("//build/kernel/kleaf/artifact_tests:initramfs_modules_lists_test") args = [] if expected_modules_list: @@ -170,16 +165,15 @@ def initramfs_modules_lists_test( args.append("$(rootpaths {})".format(kernel_images)) - hermetic_exec_test( + hermetic_test( name = name, + actual = Label("//build/kernel/kleaf/artifact_tests:initramfs_modules_lists_test"), data = [ expected_modules_list, expected_modules_recovery_list, expected_modules_charger_list, kernel_images, - test_binary, ], - script = run_py_binary_cmd(test_binary), args = args, timeout = "short", **kwargs diff --git a/kleaf/artifact_tests/py_test_hack.bzl b/kleaf/artifact_tests/py_test_hack.bzl deleted file mode 100644 index c6cc05b..0000000 --- a/kleaf/artifact_tests/py_test_hack.bzl +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Hacks for running `py_binary` in custom rules.""" - -def run_py_binary_cmd(test_binary): - """Returns a cmd that runs `test_binary` with $@. - - Args: - test_binary: label to a `py_binary`. - """ - - # https://github.com/bazelbuild/bazel/issues/20038 - # HACK: py_binary puts multiple File's in DefaultInfo, including the - # source files. Only filter the one with executable bit. - return """ - $(find $(rootpaths {test_binary}) $(rootpaths {test_binary}) -executable | head -n1) "$@" - """.format( - test_binary = test_binary, - ) diff --git a/kleaf/bazel.py b/kleaf/bazel.py index 2d3c689..7f0e640 100755 --- a/kleaf/bazel.py +++ b/kleaf/bazel.py @@ -104,6 +104,21 @@ def _require_absolute_path(p: str | pathlib.Path) -> pathlib.Path: return p +def _check_repo_manifest(value: str) \ + -> tuple[pathlib.Path | None, pathlib.Path | None]: + tokens = value.split(":") + match len(tokens): + case 0: return (None, None) + case 1: return (None, _require_absolute_path(value)) + case 2: + repo_root, repo_manifest = tokens + return (_require_absolute_path(repo_root), + _require_absolute_path(repo_manifest)) + raise argparse.ArgumentTypeError( + "Must be <REPO_MANIFEST> or <REPO_ROOT>:<REPO_MANIFEST>" + ) + + def _partition(lst: list[str], index: Optional[int]) \ -> Tuple[list[str], Optional[str], list[str]]: """Returns the triple split by index. @@ -294,10 +309,18 @@ class BazelWrapper(KleafHelpPrinter): default=absolute_cache_dir, help="Cache directory for --config=local.") group.add_argument( - "--repo_manifest", metavar="<manifest.xml>", - help="""Absolute path to repo manifest file, generated with """ - """`repo manifest -r`.""", - type=_require_absolute_path, + "--repo_manifest", metavar="<repo_root>:<manifest.xml>", + help=textwrap.dedent("""\ + One of the following: + - <REPO_MANIFEST>, an absolute path to the repo manifest file, + generated with `repo manifest -r`. In this case REPO_ROOT is + assumed to be the workspace root. This usage is deprecated + and may be removed in the future. + - <REPO_ROOT>:<REPO_MANIFEST>, where REPO_ROOT is the absolute + path to the repo root where `repo manifest -r` was executed. + """), + type=_check_repo_manifest, + default=(None, None), ) group.add_argument( "--ignore_missing_projects", @@ -375,8 +398,13 @@ class BazelWrapper(KleafHelpPrinter): self.env["KLEAF_MAKE_KEEP_GOING"] = "true" if self.known_args.make_keep_going else "false" - if self.known_args.repo_manifest is not None: - self.env["KLEAF_REPO_MANIFEST"] = self.known_args.repo_manifest + repo_root, repo_manifest = self.known_args.repo_manifest + if repo_root is None: + repo_root = self.workspace_dir + if repo_manifest is not None: + self.env["KLEAF_REPO_MANIFEST"] = f"{repo_root}:{repo_manifest}" + else: + self.env["KLEAF_REPO_MANIFEST"] = f"{repo_root}:" if self.known_args.ignore_missing_projects: self.env["KLEAF_IGNORE_MISSING_PROJECTS"] = "true" @@ -408,7 +436,6 @@ class BazelWrapper(KleafHelpPrinter): self.transformed_startup_options += self._transform_bazelrc_files([ # Add support for various configs # Do not sort, the order here might matter. - self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/canary.bazelrc", self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/ants.bazelrc", self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/android_ci.bazelrc", self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/local.bazelrc", @@ -461,6 +488,9 @@ class BazelWrapper(KleafHelpPrinter): # Experimental bzlmod support self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/bzlmod.bazelrc", + # Canary goes to the end because it uses flags / configs from elsewhere. + self.kleaf_repo_dir / "build/kernel/kleaf/bazelrc/canary.bazelrc", + self.kleaf_repo_dir / "build/kernel/kleaf/common.bazelrc", ]) diff --git a/kleaf/bazelrc/canary.bazelrc b/kleaf/bazelrc/canary.bazelrc index afa8455..d4603bd 100644 --- a/kleaf/bazelrc/canary.bazelrc +++ b/kleaf/bazelrc/canary.bazelrc @@ -18,5 +18,7 @@ # releases and some of them might get discontinued. # Enable building the toolchain from sources. -build:canary --//build/kernel/kleaf:toolchain_from_sources +build:canary --toolchain_from_sources +# Drop llvm-strings from hermetic tools. +build:canary --remove_strings_from_hermetic_tools diff --git a/kleaf/bazelrc/flags.bazelrc b/kleaf/bazelrc/flags.bazelrc index f8b6764..a487ccb 100644 --- a/kleaf/bazelrc/flags.bazelrc +++ b/kleaf/bazelrc/flags.bazelrc @@ -28,6 +28,8 @@ build --flag_alias=gzip_is_pigz=//build/kernel/kleaf:gzip_is_pigz build --flag_alias=nogzip_is_pigz=no//build/kernel/kleaf:gzip_is_pigz build --flag_alias=toolchain_from_sources=//build/kernel/kleaf:toolchain_from_sources build --flag_alias=notoolchain_from_sources=no//build/kernel/kleaf:toolchain_from_sources +build --flag_alias=remove_strings_from_hermetic_tools=//build/kernel/kleaf:remove_strings_from_hermetic_tools +build --flag_alias=noremove_strings_from_hermetic_tools=no//build/kernel/kleaf:remove_strings_from_hermetic_tools # flags that control kleaf integrity checking build --flag_alias=allow_ddk_unsafe_headers=//build/kernel/kleaf:allow_ddk_unsafe_headers diff --git a/kleaf/bazelrc/silent.bazelrc b/kleaf/bazelrc/silent.bazelrc index 1416c5e..e0ad696 100644 --- a/kleaf/bazelrc/silent.bazelrc +++ b/kleaf/bazelrc/silent.bazelrc @@ -20,7 +20,7 @@ common:silent --noshow_progress # Suppresses warnings like # WARNING: Build option [...] has changed, discarding analysis cache # We only care about error and failures from Bazel. -common:silent --ui_event_filters=error,fail +common:silent --ui_event_filters=,+error,+fail # suppresses "Target X up-to-date" "bazel-bin/..." lines common:silent --show_result=0 diff --git a/kleaf/bzlmod/bazel.MODULE.bazel b/kleaf/bzlmod/bazel.MODULE.bazel index fc6cfe3..dc08b32 100644 --- a/kleaf/bzlmod/bazel.MODULE.bazel +++ b/kleaf/bzlmod/bazel.MODULE.bazel @@ -95,7 +95,7 @@ bazel_dep( ) bazel_dep( name = "platforms", - version = "0.0.8", + version = "0.0.10", ) bazel_dep( name = "rules_cc", @@ -107,31 +107,6 @@ bazel_dep( ) bazel_dep( - name = "rules_rust", - version = "0.40.0", - - # If RUSTC_VERSION is set in kernel_toolchain_ext, kernel_build depends - # on rules_rust, so rules_rust is needed for at least GKI. - # However, if @kleaf is used as a dependent module in a DDKv2 set up and - # drivers are building against prebuilts, kernel_build might not be - # involved, and rules_rust might not be needed. If a DDKv2 driver is - # building against a core kernel built from source, it needs to have - # a direct dependency against rules_rust and call rust.toolchain() as well. - dev_dependency = True, -) -rust = use_extension( - "@rules_rust//rust:extensions.bzl", - "rust", - # See bazel_dep(name = "rules_rust") - dev_dependency = True, -) -rust.toolchain( - edition = "2021", - # If in a future we need to specify a version, keep it in sync with //prebuilts/rust/linux-x86/ - # versions = ["1.70.2"], -) - -bazel_dep( name = "stardoc", version = "0.6.2", dev_dependency = True, @@ -182,8 +157,3 @@ local_path_override( module_name = "rules_python", path = "external/bazelbuild-rules_python", ) - -local_path_override( - module_name = "rules_rust", - path = "external/bazelbuild-rules_rust", -) diff --git a/kleaf/common_kernels.bzl b/kleaf/common_kernels.bzl index 3269365..0a2f5ff 100644 --- a/kleaf/common_kernels.bzl +++ b/kleaf/common_kernels.bzl @@ -476,6 +476,7 @@ def define_common_kernels( # Workaround to set KERNEL_DIR correctly and # avoid using the fallback (directory of the config). + # TODO(b/338438451): Clean this up with kernel_build.kernel_dir attr. set_kernel_dir_cmd = "KERNEL_DIR=\"{kernel_dir}\"".format( kernel_dir = paths.join( native.package_relative_label(":x").workspace_root, diff --git a/kleaf/docs/abi.md b/kleaf/docs/abi.md index 5a9217b..266dcaa 100644 --- a/kleaf/docs/abi.md +++ b/kleaf/docs/abi.md @@ -77,14 +77,29 @@ of `//common:kernel_aarch64`, which is `common/android/abi_gki_aarch64.stg`. The exit code reflects whether an ABI change is detected in the comparison, just like `build_abi.sh --update`. -Running the script with `--commit` creates a git commit with -pre-filled message. For example: +Running the script with `--print_git_commands` prints git commands you +may run to create a commit with pre-filled message. For example: ```shell -# -- is needed before --commit to pass the argument to the script. -$ tools/bazel run //common:kernel_aarch64_abi_update -- --commit +# -- is needed before --print_git_commands to pass the argument to the script. +$ tools/bazel run //common:kernel_aarch64_abi_update -- --print_git_commands ``` +Example output: + +``` +$ tools/bazel run //common:kernel_aarch64_abi_update -- --print_git_commands +[...] +INFO: Running command line: bazel-bin/common/kernel_aarch64_abi_update.sh --print_git_commands + git -C /path/to/common/android commit \ + -F /path/to/bazel-out/k8-fastbuild/bin/common/kernel_aarch64_abi_diff/git_message.txt \ + --signoff --edit -- abi_gki_aarch64.stg +``` + +**NOTE**: The printed command (especially the path to git_message.txt) may +occasionally change. Always run with `--print_git_commands` to get the current +command for your source tree. + The command brings up your pre-configured text editor for git to edit the commit message. You may edit the subject line, add additional message, and add a bug number. diff --git a/kleaf/docs/api_reference/hermetic_tools.md b/kleaf/docs/api_reference/hermetic_tools.md index 6075eaa..510717e 100644 --- a/kleaf/docs/api_reference/hermetic_tools.md +++ b/kleaf/docs/api_reference/hermetic_tools.md @@ -107,7 +107,7 @@ _HermeticToolchainInfo (see hermetic_tools.bzl). ## hermetic_tools <pre> -hermetic_tools(<a href="#hermetic_tools-name">name</a>, <a href="#hermetic_tools-deps">deps</a>, <a href="#hermetic_tools-symlinks">symlinks</a>, <a href="#hermetic_tools-aliases">aliases</a>, <a href="#hermetic_tools-kwargs">kwargs</a>) +hermetic_tools(<a href="#hermetic_tools-name">name</a>, <a href="#hermetic_tools-deps">deps</a>, <a href="#hermetic_tools-symlinks">symlinks</a>, <a href="#hermetic_tools-kwargs">kwargs</a>) </pre> Provide tools for a hermetic build. @@ -120,7 +120,6 @@ Provide tools for a hermetic build. | <a id="hermetic_tools-name"></a>name | Name of the target. | none | | <a id="hermetic_tools-deps"></a>deps | additional dependencies. These aren't added to the `PATH`. | `None` | | <a id="hermetic_tools-symlinks"></a>symlinks | A dictionary, where keys are labels to an executable, and values are names to the tool, separated with `:`. e.g.<br><br><pre><code>{"//label/to:toybox": "cp:realpath"}</code></pre> | `None` | -| <a id="hermetic_tools-aliases"></a>aliases | **Deprecated; do not use.**<br><br>[nonconfigurable](https://bazel.build/reference/be/common-definitions#configurable-attributes).<br><br>List of aliases to create to refer to a `fail_rule`.<br><br>For example, if `aliases = ["cp"],` then usage of `<name>/cp` will fail.<br><br>**Note**: It is not allowed to rely on these targets. Consider using the full hermetic toolchain with [`hermetic_toolchain`](#hermetic_toolchainget) or [`hermetic_genrule`](#hermetic_genrule), etc. | `None` | | <a id="hermetic_tools-kwargs"></a>kwargs | Additional attributes to the internal rule, e.g. [`visibility`](https://docs.bazel.build/versions/main/visibility.html). See complete list [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common | none | diff --git a/kleaf/docs/api_reference/kernel.md b/kleaf/docs/api_reference/kernel.md index d617e70..a97c14b 100644 --- a/kleaf/docs/api_reference/kernel.md +++ b/kleaf/docs/api_reference/kernel.md @@ -258,11 +258,11 @@ Define an executable that creates `compile_commands.json` from a `kernel_build`. ## kernel_filegroup <pre> -kernel_filegroup(<a href="#kernel_filegroup-name">name</a>, <a href="#kernel_filegroup-deps">deps</a>, <a href="#kernel_filegroup-srcs">srcs</a>, <a href="#kernel_filegroup-outs">outs</a>, <a href="#kernel_filegroup-collect_unstripped_modules">collect_unstripped_modules</a>, <a href="#kernel_filegroup-config_out_dir">config_out_dir</a>, - <a href="#kernel_filegroup-config_out_dir_files">config_out_dir_files</a>, <a href="#kernel_filegroup-ddk_module_defconfig_fragments">ddk_module_defconfig_fragments</a>, <a href="#kernel_filegroup-debug">debug</a>, <a href="#kernel_filegroup-env_setup_script">env_setup_script</a>, - <a href="#kernel_filegroup-exec_platform">exec_platform</a>, <a href="#kernel_filegroup-gki_artifacts">gki_artifacts</a>, <a href="#kernel_filegroup-images">images</a>, <a href="#kernel_filegroup-internal_outs">internal_outs</a>, <a href="#kernel_filegroup-kasan">kasan</a>, <a href="#kernel_filegroup-kasan_generic">kasan_generic</a>, - <a href="#kernel_filegroup-kasan_sw_tags">kasan_sw_tags</a>, <a href="#kernel_filegroup-kcsan">kcsan</a>, <a href="#kernel_filegroup-kernel_release">kernel_release</a>, <a href="#kernel_filegroup-kernel_uapi_headers">kernel_uapi_headers</a>, <a href="#kernel_filegroup-lto">lto</a>, <a href="#kernel_filegroup-module_env_archive">module_env_archive</a>, - <a href="#kernel_filegroup-module_outs_file">module_outs_file</a>, <a href="#kernel_filegroup-modules_prepare_archive">modules_prepare_archive</a>, <a href="#kernel_filegroup-protected_modules_list">protected_modules_list</a>, <a href="#kernel_filegroup-strip_modules">strip_modules</a>, +kernel_filegroup(<a href="#kernel_filegroup-name">name</a>, <a href="#kernel_filegroup-deps">deps</a>, <a href="#kernel_filegroup-srcs">srcs</a>, <a href="#kernel_filegroup-outs">outs</a>, <a href="#kernel_filegroup-all_module_names">all_module_names</a>, <a href="#kernel_filegroup-collect_unstripped_modules">collect_unstripped_modules</a>, + <a href="#kernel_filegroup-config_out_dir">config_out_dir</a>, <a href="#kernel_filegroup-config_out_dir_files">config_out_dir_files</a>, <a href="#kernel_filegroup-ddk_module_defconfig_fragments">ddk_module_defconfig_fragments</a>, <a href="#kernel_filegroup-debug">debug</a>, + <a href="#kernel_filegroup-env_setup_script">env_setup_script</a>, <a href="#kernel_filegroup-exec_platform">exec_platform</a>, <a href="#kernel_filegroup-gki_artifacts">gki_artifacts</a>, <a href="#kernel_filegroup-images">images</a>, <a href="#kernel_filegroup-internal_outs">internal_outs</a>, <a href="#kernel_filegroup-kasan">kasan</a>, + <a href="#kernel_filegroup-kasan_generic">kasan_generic</a>, <a href="#kernel_filegroup-kasan_sw_tags">kasan_sw_tags</a>, <a href="#kernel_filegroup-kcsan">kcsan</a>, <a href="#kernel_filegroup-kernel_release">kernel_release</a>, <a href="#kernel_filegroup-kernel_uapi_headers">kernel_uapi_headers</a>, <a href="#kernel_filegroup-lto">lto</a>, + <a href="#kernel_filegroup-module_env_archive">module_env_archive</a>, <a href="#kernel_filegroup-modules_prepare_archive">modules_prepare_archive</a>, <a href="#kernel_filegroup-protected_modules_list">protected_modules_list</a>, <a href="#kernel_filegroup-strip_modules">strip_modules</a>, <a href="#kernel_filegroup-target_platform">target_platform</a>, <a href="#kernel_filegroup-trim_nonlisted_kmi">trim_nonlisted_kmi</a>) </pre> @@ -288,6 +288,7 @@ It can be used in the `base_kernel` attribute of a [`kernel_build`](#kernel_buil | <a id="kernel_filegroup-deps"></a>deps | A list of additional labels that participates in implementing the providers.<br><br>This usually contains a list of prebuilts.<br><br>Unlike srcs, these labels are NOT added to the [`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html) | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` | | <a id="kernel_filegroup-srcs"></a>srcs | The list of labels that are members of this file group.<br><br>This usually contains a list of prebuilts, e.g. `vmlinux`, `Image.lz4`, `kernel-headers.tar.gz`, etc.<br><br>Not to be confused with [`kernel_srcs`](#kernel_filegroup-kernel_srcs). | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` | | <a id="kernel_filegroup-outs"></a>outs | Keys: from `_kernel_build.outs`. Values: path under `$OUT_DIR`. | <a href="https://bazel.build/rules/lib/dict">Dictionary: Label -> String</a> | optional | `{}` | +| <a id="kernel_filegroup-all_module_names"></a>all_module_names | `module_outs` and `module_implicit_outs` of the original [`kernel_build`](#kernel_build) target. | List of strings | optional | `[]` | | <a id="kernel_filegroup-collect_unstripped_modules"></a>collect_unstripped_modules | See [`kernel_build.collect_unstripped_modules`](#kernel_build-collect_unstripped_modules).<br><br>Unlike `kernel_build`, this has default value `True` because [`kernel_abi`](#kernel_abi) sets [`define_abi_targets`](#kernel_abi-define_abi_targets) to `True` by default, which in turn sets `collect_unstripped_modules` to `True` by default. | Boolean | optional | `True` | | <a id="kernel_filegroup-config_out_dir"></a>config_out_dir | Directory to support `kernel_config` | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` | | <a id="kernel_filegroup-config_out_dir_files"></a>config_out_dir_files | Files in `config_out_dir` | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` | @@ -306,7 +307,6 @@ It can be used in the `base_kernel` attribute of a [`kernel_build`](#kernel_buil | <a id="kernel_filegroup-kernel_uapi_headers"></a>kernel_uapi_headers | The label pointing to `kernel-uapi-headers.tar.gz`.<br><br>This attribute should be set to the `kernel-uapi-headers.tar.gz` artifact built by the [`kernel_build`](#kernel_build) macro if the `kernel_filegroup` rule were a `kernel_build`.<br><br>Setting this attribute allows [`merged_kernel_uapi_headers`](#merged_kernel_uapi_headers) to work properly when this `kernel_filegroup` is set to the `base_kernel`.<br><br>For example: <pre><code>kernel_filegroup( name = "kernel_aarch64_prebuilts", srcs = [ "vmlinux", # ... ], kernel_uapi_headers = "kernel-uapi-headers.tar.gz", ) kernel_build( name = "tuna", base_kernel = ":kernel_aarch64_prebuilts", # ... ) merged_kernel_uapi_headers( name = "tuna_merged_kernel_uapi_headers", kernel_build = "tuna", # ... )</code></pre> | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` | | <a id="kernel_filegroup-lto"></a>lto | - | String | optional | `"default"` | | <a id="kernel_filegroup-module_env_archive"></a>module_env_archive | Archive from `kernel_build.pack_module_env` that contains necessary files to build external modules. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` | -| <a id="kernel_filegroup-module_outs_file"></a>module_outs_file | A file containing `module_outs` of the original [`kernel_build`](#kernel_build) target. | <a href="https://bazel.build/concepts/labels">Label</a> | required | | | <a id="kernel_filegroup-modules_prepare_archive"></a>modules_prepare_archive | Archive from `modules_prepare` | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` | | <a id="kernel_filegroup-protected_modules_list"></a>protected_modules_list | - | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` | | <a id="kernel_filegroup-strip_modules"></a>strip_modules | See [`kernel_build.strip_modules`](#kernel_build-strip_modules). | Boolean | optional | `False` | diff --git a/kleaf/docs/api_reference/kernel_prebuilt_ext.md b/kleaf/docs/api_reference/kernel_prebuilt_ext.md index cc7f46f..68aef2a 100644 --- a/kleaf/docs/api_reference/kernel_prebuilt_ext.md +++ b/kleaf/docs/api_reference/kernel_prebuilt_ext.md @@ -8,8 +8,7 @@ Extension that helps building Android kernel and drivers. <pre> kernel_prebuilt_ext = use_extension("@kleaf//build/kernel/kleaf:kernel_prebuilt_ext.bzl", "kernel_prebuilt_ext") -kernel_prebuilt_ext.declare_kernel_prebuilts(<a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-name">name</a>, <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-auto_download_config">auto_download_config</a>, <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-download_configs">download_configs</a>, - <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-local_artifact_path">local_artifact_path</a>, <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-target">target</a>) +kernel_prebuilt_ext.declare_kernel_prebuilts(<a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-name">name</a>, <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-local_artifact_path">local_artifact_path</a>, <a href="#kernel_prebuilt_ext.declare_kernel_prebuilts-target">target</a>) </pre> Extension that manages what prebuilts Kleaf should use. @@ -28,8 +27,6 @@ Declares a repo that contains kernel prebuilts | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | <a id="kernel_prebuilt_ext.declare_kernel_prebuilts-name"></a>name | name of repository | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | | -| <a id="kernel_prebuilt_ext.declare_kernel_prebuilts-auto_download_config"></a>auto_download_config | If `True`, infer download configs from `target`. | Boolean | optional | `False` | -| <a id="kernel_prebuilt_ext.declare_kernel_prebuilts-download_configs"></a>download_configs | A JSON dictionary that configure the list of files to download.<br><br>Key: local file name.<br><br>Value: A dictionary with the following keys: * `mandatory`: Whether the files in `outs_mapping` is mandatory. If mandatory, failure to download the file results in a build failure. * `remote_filename_fmt`: remote file name format string, with the following anchors: * {build_number} * {target} | String | optional | `""` | | <a id="kernel_prebuilt_ext.declare_kernel_prebuilts-local_artifact_path"></a>local_artifact_path | Directory to local artifacts.<br><br>If set, `artifact_url_fmt` is ignored.<br><br>Only the root module may call `declare()` with this attribute set.<br><br>If relative, it is interpreted against workspace root.<br><br>If absolute, this is similar to setting `artifact_url_fmt` to `file://<absolute local_artifact_path>/{filename}`, but avoids using `download()`. Files are symlinked not copied, and `--config=internet` is not necessary. | String | optional | `""` | | <a id="kernel_prebuilt_ext.declare_kernel_prebuilts-target"></a>target | Name of the build target as identified by the remote build server.<br><br>This attribute has two effects:<br><br>* Replaces the `{target}` anchor in `artifact_url_fmt`. If `artifact_url_fmt` does not have the `{target}` anchor, this has no effect.<br><br>* If `auto_download_config` is `True`, `download_config` and `mandatory` is inferred from a list of known configs keyed on `target`. | String | optional | `"kernel_aarch64"` | diff --git a/kleaf/docs/bzlmod.md b/kleaf/docs/bzlmod.md index 0e21198..513de6f 100644 --- a/kleaf/docs/bzlmod.md +++ b/kleaf/docs/bzlmod.md @@ -2,14 +2,23 @@ ## Migrate to bzlmod -### Use @kleaf as root module +### Use @kleaf as dependent module (recommended) + +If you are setting up a new workspace, it is recommended +to use `@kleaf` as a dependent module. See [Setting up DDK workspace](ddk/workspace.md). + +### Use @kleaf as root module (legacy) + +If you are migrating from non-Bzlmod, `WORKSPACE`-style +setup, this may be the easier option because it resembles the directory +structure of `WORKSPACE`-style setup. Set up your repo manifest to conform with the following filesystem layout. ```text <workspace_root>/ |- WORKSPACE -> build/kernel/kleaf/bazel.WORKSPACE # Note 1 - |- WORKSPACE.bzlmod -> build/kernel/kleaf/bzlmod/bazel.WORKSPACE.bzlmod + |- WORKSPACE.bzlmod -> build/kernel/kleaf/bzlmod/bazel.WORKSPACE.bzlmod # Note 1 |- MODULE.bazel -> build/kernel/kleaf/bzlmod/bazel.MODULE.bazel |- build/ | `- kernel/ @@ -20,19 +29,28 @@ Set up your repo manifest to conform with the following filesystem layout. `- <other external repositories> # Note 3 ``` -**Note 1**: The root `WORKSPACE` file is present to support pre-bzlmod builds. -After bzlmod migration, this file may be removed. +**Note 1**: The root `WORKSPACE` and `WORKSPACE.bzlmod` files are present to +support switching between bzlmod and non-bzlmod builds. During migration to +bzlmod, you may have an non-empty `WORKSPACE.bzlmod` file for dependencies +that has not been migrated to bzlmod. After all dependencies and the +root module migrated to Bzlmod, both files may be removed. + +See +[hybrid mode for gradual migration](https://bazel.build/external/migration#hybrid-mode) +for details. **Note 2**: If `build.config.constants` exists elsewhere other than `common/`, create the symlink `common/build.config.constants` to the file. This may be done with `<linkfile>` in your repo manifest. **Note 3**: A list of external repositories are required for bzlmod to work. -For the up-to-date list, refer to the repo manifest of the ACK branch. - -### Use @kleaf as dependency +For the up-to-date list, refer to the repo manifest of the correspoding ACK +branch. -This will be supported in the near future. Stay tuned! +See example manifests for +[Pixel 6 and Pixel 6 Pro](https://android.googlesource.com/kernel/manifest/+/refs/heads/gs-android-gs-raviole-mainline/default.xml) +and for +[Android Common Kernel and Cloud Android Kernel](https://android.googlesource.com/kernel/manifest/+/refs/heads/common-android-mainline/default.xml). ## Versions of dependent modules diff --git a/kleaf/docs/canary.md b/kleaf/docs/canary.md index 624df94..807caa3 100644 --- a/kleaf/docs/canary.md +++ b/kleaf/docs/canary.md @@ -11,3 +11,12 @@ As of now, `--config=canary` enables: - `--toolchain_from_sources`: Build (some) build time dependencies from sources, like `toybox`. + +To opt-in a local workspace without having to pass the command line flag for +every invocation, add to `user.bazelrc`: + +```text +# Opt into future features of Kleaf and the DDK. +build --config=canary +``` + diff --git a/kleaf/docs/ddk/main.md b/kleaf/docs/ddk/main.md index 1f6ad8c..7dc80e3 100644 --- a/kleaf/docs/ddk/main.md +++ b/kleaf/docs/ddk/main.md @@ -28,6 +28,8 @@ The virtual device serves as a reference implementation for DDK modules. See ## Read more +[Setting up workspace](workspace.md) + [Rules and macros](rules.md) [Using headers from the common kernel](common_headers.md) diff --git a/kleaf/docs/ddk/workspace.md b/kleaf/docs/ddk/workspace.md new file mode 100644 index 0000000..3cb1c2a --- /dev/null +++ b/kleaf/docs/ddk/workspace.md @@ -0,0 +1,241 @@ +# Setting up a DDK Workspace + +## Use the bootstrapping script + +The bootstrapping script may be found in the +[kernel/build/bootstrap](https://android.googlesource.com/kernel/build/bootstrap) +project. The following one-line command downloads and executes it: + +```shell +$ curl https://android.googlesource.com/kernel/build/bootstrap/+/refs/heads/main/init.py?format=TEXT | base64 --decode | python3 - [flags] +``` + +This command will be referred to as `python3 init.py` below. + +### Typical usage + +In the examples below, `/path/to/ddk/workspace` is the path to the +DDK workspace the script will populate. Usually, the script generates at +least the following in the directory: +* [`MODULE.bazel`](#root-modulebazel) +* [`device.bazelrc`](#devicebazelrc) +* `tools/bazel` symlink + +#### Local sources + +Populate DDK workspace against a local, full +checkout of ACK source tree at `/path/to/ddk/workspace/external/kleaf`: + +```shell +$ python init.py --local \ + --ddk_workspace /path/to/ddk/workspace \ + --kleaf_repo /path/to/ddk/workspace/external/kleaf +``` + +#### Local sources + prebuilts + +Populate DDK workspace against a local checkout of +Kleaf projects at `/path/to/ddk/workspace/external/kleaf` and prebuilts at +`/path/to/ddk/workspace/prebuilts/kernel`: + +```shell +$ python init.py --local \ + --ddk_workspace /path/to/ddk/workspace \ + --kleaf_repo /path/to/ddk/workspace/external/kleaf \ + --prebuilts_dir /path/to/ddk/workspace/prebuilts/kernel +``` + +Other usages will be added here soon. Stay tuned! + +## Root MODULE.bazel + +**Note**: The content below is automatically generated if you are using +the [bootstrapping script](#use-the-bootstrapping-script). + +### @kleaf dependency + +The `MODULE.bazel` file of the root module should declare a dependency +to `@kleaf`. In this example, the module is checked out at +`external/kleaf` relative to the workspace root: + +```python +bazel_dep(name = "kleaf") +local_path_override( + module_name = "kleaf", + path = "external/kleaf", # or absolute path +) +``` + +You may now use rules in the `@kleaf` repository. For example, in `BUILD.bazel`: + +```python +load("@kleaf//build/kernel/kleaf:kernel.bzl", "ddk_module") +``` + +If the full kernel source tree exists in `external/kleaf/common`, you may also +use the kernel built from source. For example: + +```python +ddk_module( + name = "mymodule", + kernel_build = "@kleaf//common:kernel_aarch64", + deps = [ + "@kleaf//common:all_headers_aarch64", + ], + # other attrs +) +``` + +### Declare prebuilts repository + +The `MODULE.bazel` file of the root module may declare a repository containing +kernel prebuilts, if they exist. For example: + +```python +kernel_prebuilt_ext = use_extension( + "@kleaf//build/kernel/kleaf:kernel_prebuilt_ext.bzl", + "kernel_prebuilt_ext", +) +kernel_prebuilt_ext.declare_kernel_prebuilts( + name = "gki_prebuilts", # name of your choice + local_artifact_path = "prebuilts/kernel", # or absolute path + # other attrs +) +``` + +See +[kernel_prebuilt_ext.declare_kernel_prebuilts](../api_reference/kernel_prebuilt_ext.md) for its API. +**Note**: the API may undergo changes when this feature is in experiemental +stage. + +You may now use `@gki_prebuilts//kernel_aarch64` in the `kernel_build` +attributes of various rules. For example: + +```python +ddk_module( + name = "mymodule", + kernel_build = "@gki_prebuilts//kernel_aarch64", + # other attrs +) +``` + +**Advanced usage**: If you have both `@kleaf//common:kernel_aarch64` and +`@gki_prebuilts//kernel_aarch64`, you may define flags to switch between +them. For example: + +```python +# mypackage/BUILD.bazel +label_flag( + name = "base_kernel", + build_setting_default = "@gki_prebuilts//kernel_aarch64", +) + +ddk_module( + name = "mymodule", + kernel_build = ":base_kernel", + # other attrs +) +``` + +```shell +# Build against prebuilt kernel +$ tools/bazel build //mypackage:mymodule + +# Build against kernel built from sources +$ tools/bazel build --//mypackage:base_kernel=@kleaf//common:kernel_aarch64 \ + //mypackage:mymodule +``` + +You may add flag aliases and configs to `device.bazelrc`. See +[.bazelrc files](../impl.md#bazelrc-files). + +### Transitive dependencies + +This refers to the list of dependencies of `@kleaf` that are not +directly needed by the root module, plus the Bazel Central Registry. + +#### Download from the Internet + +To rely on the Internet for transitive dependencies from the `@kleaf`, +add [`--config=internet` flag](../network.md). You may add the flag to +[`device.bazelrc`](#devicebazelrc). + +#### True local builds + +To build in an air-gapped environment without Internet access, all +dependencies must be vendored locally. + +##### Registry + +You may checkout a registry from one of the following: + +* [Bazel Central Registry (BCR)](https://bcr.bazel.build/) +* [AOSP mirror of BCR](https://android.googlesource.com/platform/external/bazelbuild-bazel-central-registry). + This may be slightly outdated. + +Assuming that you have checked out the registry somewhere on your disk, +you may specify the `--registry` flag. For example, in +[`device.bazelrc`](#devicebazelrc): + +```text +common --registry=file://%workspace%/external/bazelbuild-bazel-central-registry +``` + +##### Dependent modules + +You may collect the list of all dependent modules with the following command: + +```shell +$ tools/bazel mod graph --include_builtin +``` + +For details, see [bazel mod command](https://bazel.build/external/mod-command). + +Among them, the list of modules that `@kleaf` module depends on may be +found by looking at `external/kleaf/MODULE.bazel`, assuming that the module is +located at `external/kleaf`. Its content may also be found in +[this file](../../bzlmod/bazel.MODULE.bazel). + +Then, declare `local_path_override()` for each dependency and transitive +dependency in your **root `MODULE.bazel`**. You may also use other +[non-registry overrides](https://bazel.build/external/module#non-registry_overrides) +if applicable. For example: + +```python +local_path_override( + module_name = "rules_cc", + path = "external/bazelbuild-rules_cc", +) +``` + +**Note**: `local_path_override()` in dependent modules (e.g. the ones +in `@kleaf`) has no effect. `local_path_override()` is only effective +when used at the root MODULE.bazel. See +[documentation for local_path_override()](https://bazel.build/rules/lib/globals/module#local_path_override). + +**Note**: If `path` is relative, it is interpreted against the workspace root. +In the above example, `@rules_cc` is found at +`/path/to/ddk/workspace/external/bazelbuild-rules_cc`. + +## device.bazelrc + +The `device.bazelrc` file in your workspace root may contain the following +lines. See [.bazelrc files](../impl.md#bazelrc-files) for details. + +Bazel may fetch dependencies from the Internet if there are no +`local_path_override` +declarations for the dependency. The following +allows Internet access. For details, see [Internet access](../network.md). + +```text +common --config=internet +``` + +Kleaf sets `--registry` by default; see +[bzlmod.bazelrc](../../bazelrc/bzlmod.bazelrc). If the registry is not checked +out at `external/bazelbuild-bazel-central-registry` under the workspace root, +override its value. For example: + +```text +common:bzlmod --registry=file:///path/to/bcr +``` diff --git a/kleaf/docs/download_prebuilt.md b/kleaf/docs/download_prebuilt.md index 05f201c..2e46e12 100644 --- a/kleaf/docs/download_prebuilt.md +++ b/kleaf/docs/download_prebuilt.md @@ -1,7 +1,9 @@ -# Build against downloaded prebuilt GKI +# Build against downloaded prebuilt GKI (deprecated) -**WARNING**: Building against downloaded prebuilts is currently experimental. If -you encounter any errors, see [common errors](#common-errors). +**WARNING**: Contents in this page are deprecated, including +the `--use_prebuilt_gki` flag and `*_download_or_build` targets. +To build against downloaded prebuilt GKI with bzlmod, see +[Setting up DDK workspace](ddk/workspace.md#declare-prebuilts-repository). ## Step 1: Replace reference to GKI targets with downloaded targets diff --git a/kleaf/docs/impl.md b/kleaf/docs/impl.md index aa57a35..8095366 100644 --- a/kleaf/docs/impl.md +++ b/kleaf/docs/impl.md @@ -5,27 +5,14 @@ You may view the documentation for the following Bazel rules and macros on Android Continuous Integration. See [API Reference and Documentation for all rules](api_reference.md). -## Manifest changes - -Make the following changes to the kernel manifest to support Bazel build. - -* Add `tools/bazel` symlink to `build/kernel/kleaf/bazel.sh` -* Add `WORKSPACE` symlink to `build/kernel/kleaf/bazel.WORKSPACE` - * See [workspace.md](workspace.md) for building with a custom workspace. -* Dependent repositories for Bazel, including: - * [prebuilts/bazel/linux-x86\_64](https://android.googlesource.com/platform/prebuilts/bazel/linux-x86_64/) - * [prebuilts/jdk/jdk11](https://android.googlesource.com/platform/prebuilts/jdk/jdk11/) - * [build/bazel\_common\_rules](https://android.googlesource.com/platform/build/bazel_common_rules/) - * [external/bazel-skylib](https://android.googlesource.com/platform/external/bazel-skylib/) - * [external/stardoc](https://android.googlesource.com/platform/external/stardoc/) - -Example for Pixel 2021: - -[https://android.googlesource.com/kernel/manifest/+/refs/heads/gs-android-gs-raviole-mainline/default.xml](https://android.googlesource.com/kernel/manifest/+/refs/heads/gs-android-gs-raviole-mainline/default.xml) - -Example for Android Common Kernel and Cloud Android kernel: - -[https://android.googlesource.com/kernel/manifest/+/refs/heads/common-android-mainline/default.xml](https://android.googlesource.com/kernel/manifest/+/refs/heads/common-android-mainline/default.xml) +## Setting up the workspace + +* (Recommended) To use Kleaf tooling as a dependent Bazel module, see + [Setting up DDK workspace](ddk/workspace.md). +* To use Kleaf tooling as the root Bazel module, see + [Use @kleaf as root module (legacy)](bzlmod.md#use-kleaf-as-root-module-legacy). +* Building without Bzlmod is deprecated and will not be supported in + Android 16 and above. ## Building a custom kernel diff --git a/kleaf/docs/workspace.md b/kleaf/docs/workspace.md index 93b1f5b..b88fcb7 100644 --- a/kleaf/docs/workspace.md +++ b/kleaf/docs/workspace.md @@ -2,10 +2,18 @@ ## Using bzlmod (recommended) -See [bzlmod support in Kleaf.](bzlmod.md) +Recommended: If you are using `@kleaf` as a dependent module, +see [Setting up DDK workspace](ddk/workspace.md). + +If you are using `@kleaf` as the root module, see +[bzlmod support in Kleaf.](bzlmod.md) ## Legacy `WORKSPACE` support +**Warning**: Support for non-Bzlmod builds are deprecated and will be +removed in Android 16 branches. Information below are +outdated and not supported with Bzlmod enabled. + ### Using the provided `WORKSPACE` file Usually, the common kernel is checked out to `common/`. In this case, it is diff --git a/kleaf/hermetic_tools.bzl b/kleaf/hermetic_tools.bzl index 13aa70d..b5093a2 100644 --- a/kleaf/hermetic_tools.bzl +++ b/kleaf/hermetic_tools.bzl @@ -57,41 +57,66 @@ def _get_single_file(ctx, target): )) return files_list[0] -def _handle_hermetic_symlinks(ctx): - hermetic_symlinks_dict = {} - for actual_target, tool_names in ctx.attr.symlinks.items(): - for tool_name in tool_names.split(":"): - out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name)) - target_file = _get_single_file(ctx, actual_target) - ctx.actions.symlink( - output = out, - target_file = target_file, - is_executable = True, - progress_message = "Creating symlinks to in-tree tools {}/{}".format( - ctx.label, - tool_name, - ), - ) - hermetic_symlinks_dict[tool_name] = out - - return hermetic_symlinks_dict +def _handle_tool(ctx, tool_name, actual_target): + out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name)) + target_file = _get_single_file(ctx, actual_target) + + if tool_name not in ctx.attr.extra_args: + ctx.actions.symlink( + output = out, + target_file = target_file, + is_executable = True, + progress_message = "Creating symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) + return [out] + + internal_symlink = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}".format(ctx.attr.name, tool_name)) + ctx.actions.symlink( + output = internal_symlink, + target_file = target_file, + is_executable = True, + progress_message = "Creating internal symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) -def _hermetic_tools_impl(ctx): - deps = [] + ctx.files.deps + ctx.actions.symlink( + output = out, + target_file = ctx.executable._arg_wrapper, + is_executable = True, + progress_message = "Creating symlink to in-tree tool {}/{}".format( + ctx.label, + tool_name, + ), + ) + extra_args = "\n".join(ctx.attr.extra_args[tool_name]) + extra_args_file = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}_args.txt".format(ctx.attr.name, tool_name)) + ctx.actions.write(extra_args_file, extra_args) + return [out, internal_symlink, extra_args_file] + +def _handle_hermetic_symlinks(ctx, symlinks_attr): all_outputs = [] + for actual_target, tool_names in symlinks_attr.items(): + for tool_name in tool_names.split(":"): + tool_outs = _handle_tool(ctx, tool_name, actual_target) + all_outputs.extend(tool_outs) - hermetic_outs_dict = _handle_hermetic_symlinks(ctx) + return all_outputs - hermetic_outs = hermetic_outs_dict.values() - all_outputs += hermetic_outs - deps += hermetic_outs +def _hermetic_tools_impl(ctx): + all_outputs = _handle_hermetic_symlinks(ctx, ctx.attr.symlinks) if ctx.attr._disable_symlink_source[BuildSettingInfo].value: transitive_deps = [] else: transitive_deps = [target.files for target in ctx.attr.symlinks] - deps_depset = depset(deps, transitive = transitive_deps) + transitive_deps += [target.files for target in ctx.attr.deps] + transitive_deps.append(ctx.attr._arg_wrapper.files) fail_hard = """ # error on failures @@ -125,76 +150,58 @@ def _hermetic_tools_impl(ctx): """.format(path = hermetic_base_short) hermetic_toolchain_info = _HermeticToolchainInfo( - deps = deps_depset, + deps = depset(all_outputs, transitive = transitive_deps), setup = setup, run_setup = run_setup, run_additional_setup = run_additional_setup, ) - default_info_files = [ - file - for file in all_outputs - if "kleaf_internal_do_not_use" not in file.path - ] - infos = [ - DefaultInfo(files = depset(default_info_files)), + DefaultInfo(files = depset(all_outputs)), platform_common.ToolchainInfo( hermetic_toolchain_info = hermetic_toolchain_info, ), - OutputGroupInfo( - **{file.basename: depset([file]) for file in all_outputs} - ), + OutputGroupInfo(**{ + file.basename: depset([file]) + for file in all_outputs + if "kleaf_internal_do_not_use" not in file.path + }), ] return infos -_hermetic_tools = rule( +hermetic_tools = rule( implementation = _hermetic_tools_impl, - doc = "", + doc = "Provide tools for a hermetic build.", attrs = { - "deps": attr.label_list(doc = "Additional_deps", allow_files = True), + "deps": attr.label_list( + doc = "additional dependencies. These aren't added to the `PATH`.", + allow_files = True, + ), "symlinks": attr.label_keyed_string_dict( - doc = "symlinks to labels", allow_files = True, + doc = """A dictionary, where keys are labels to an executable, and + values are names to the tool, separated with `:`. e.g. + + ``` + {"//label/to:toybox": "cp:realpath"} + ``` + """, + ), + "extra_args": attr.string_list_dict( + doc = """Keys are names to the tool (see `symlinks`). Values are + extra arguments added to the tool at the end.""", ), "_disable_symlink_source": attr.label( default = "//build/kernel/kleaf:incompatible_disable_hermetic_tools_symlink_source", ), + "_arg_wrapper": attr.label( + default = "//build/kernel/kleaf/impl:arg_wrapper", + executable = True, + # Prevent inadvertent exec transition that messes up the + # path calculation. Exec transition needs to be done on the whole + # hermetic_tools target. + cfg = "target", + ), }, ) - -def hermetic_tools( - name, - deps = None, - symlinks = None, - **kwargs): - """Provide tools for a hermetic build. - - Args: - name: Name of the target. - symlinks: A dictionary, where keys are labels to an executable, and - values are names to the tool, separated with `:`. e.g. - - ``` - {"//label/to:toybox": "cp:realpath"} - ``` - deps: additional dependencies. These aren't added to the `PATH`. - **kwargs: Additional attributes to the internal rule, e.g. - [`visibility`](https://docs.bazel.build/versions/main/visibility.html). - See complete list - [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common - """ - - if symlinks == None: - symlinks = {} - - if deps == None: - deps = [] - - _hermetic_tools( - name = name, - deps = deps, - symlinks = symlinks, - **kwargs - ) diff --git a/kleaf/impl/BUILD.bazel b/kleaf/impl/BUILD.bazel index 51964f9..e8cd6fc 100644 --- a/kleaf/impl/BUILD.bazel +++ b/kleaf/impl/BUILD.bazel @@ -407,6 +407,7 @@ kernel_platform_toolchain( kernel_platform_toolchain( name = "kernel_toolchain_exec", + visibility = ["//build/kernel/kleaf/tests:__subpackages__"], deps = [ "//prebuilts/kernel-build-tools:linux_x86_imported_libs", ], @@ -485,3 +486,9 @@ python_runtime_files( name = "python_runtime_files", visibility = ["//build/kernel:__pkg__"], ) + +cc_binary( + name = "arg_wrapper", + srcs = ["arg_wrapper.cpp"], + visibility = ["//build/kernel:__pkg__"], +) diff --git a/kleaf/impl/abi/abi_stgdiff.bzl b/kleaf/impl/abi/abi_stgdiff.bzl index 4c0106c..1b048ef 100644 --- a/kleaf/impl/abi/abi_stgdiff.bzl +++ b/kleaf/impl/abi/abi_stgdiff.bzl @@ -77,7 +77,9 @@ EOF echo "INFO: $(cat {error_msg_file})" elif [[ $rc == {change_code} ]]; then echo "{log_level}: ABI DIFFERENCES HAVE BEEN DETECTED!" >&2 - echo "{log_level}: $(cat {short_report})" >&2 + echo "{log_level}: Report summary:" >&2 + echo >&2 + cat {short_report} >&2 else echo "ERROR: $(cat {error_msg_file})" >&2 echo "WARNING: exit code is not checked. 'tools/bazel run {label}' to check the exit code." >&2 @@ -116,8 +118,10 @@ EOF if [[ $rc == 0 ]]; then echo "INFO: $(cat {error_msg_file})" elif [[ $rc == 4 ]]; then - echo "{log_level}: ABI DIFFERENCES HAVE BEEN DETECTED!" - echo "{log_level}: $(cat {short_report})" + echo "{log_level}: ABI DIFFERENCES HAVE BEEN DETECTED!" >&2 + echo "{log_level}: Report summary:" >&2 + echo >&2 + cat {short_report} >&2 else echo "ERROR: $(cat {error_msg_file})" >&2 fi diff --git a/kleaf/impl/abi/abi_update.bzl b/kleaf/impl/abi/abi_update.bzl index 18306c7..11e60f1 100644 --- a/kleaf/impl/abi/abi_update.bzl +++ b/kleaf/impl/abi/abi_update.bzl @@ -14,6 +14,7 @@ """When `bazel run`, updates an ABI definition.""" +load("@bazel_skylib//lib:paths.bzl", "paths") load(":hermetic_toolchain.bzl", "hermetic_toolchain") visibility("//build/kernel/kleaf/...") @@ -22,37 +23,31 @@ def _abi_update_impl(ctx): hermetic_tools = hermetic_toolchain.get(ctx) script = """ - # run_additional_setup keeps the original PATH to host tools at the - # end of PATH. This is intentionally not hermetic and uses git - # from the host machine. - {semi_hermetic_setup} - - # nodiff_update is self-contained and hermetic. + {hermetic_setup} {nodiff_update} - # Use the semi-hermetic environment to execute git commands - # Create git commit if requested if [[ $1 == "--commit" ]]; then - real_abi_def="$(realpath {abi_definition})" - git -C $(dirname ${{real_abi_def}}) add $(basename ${{real_abi_def}}) - git -C $(dirname ${{real_abi_def}}) commit -F $(realpath {git_message}) + echo "WARNING: --commit is deprecated. Please add {abi_definition} and commit manually." >&2 + echo " You may use --print_git_commands to print sample git commands to run." >&2 fi - # Re-instate a hermetic environment - {hermetic_setup} - {diff} - if [[ $1 == "--commit" ]]; then - echo - echo "INFO: git commit created. Execute the following to edit the commit message:" - echo " git -C $(dirname $(rootpath {abi_definition})) commit --amend" + if [[ $1 == "--commit" ]] || [[ $1 == "--print_git_commands" ]]; then + echo " git -C ${{BUILD_WORKSPACE_DIRECTORY}}/{abi_definition_dir} commit \\\\" + echo " -F ${{BUILD_WORKSPACE_DIRECTORY}}/{git_message} \\\\" + echo " --signoff --edit -- {abi_definition_name}" fi + + {diff} """.format( hermetic_setup = hermetic_tools.run_setup, - semi_hermetic_setup = hermetic_tools.run_additional_setup, nodiff_update = ctx.executable.nodiff_update.short_path, abi_definition = ctx.file.abi_definition_stg.short_path, - git_message = ctx.file.git_message.short_path, diff = ctx.executable.diff.short_path, + # Use .path because these are displayed to the user relative + # to BUILD_WORKSPACE_DIRECTORY + abi_definition_dir = paths.dirname(ctx.file.abi_definition_stg.path), + abi_definition_name = paths.basename(ctx.file.abi_definition_stg.path), + git_message = ctx.file.git_message.path, ) executable = ctx.actions.declare_file("{}.sh".format(ctx.attr.name)) diff --git a/kleaf/impl/arg_wrapper.cpp b/kleaf/impl/arg_wrapper.cpp new file mode 100644 index 0000000..ee9c95c --- /dev/null +++ b/kleaf/impl/arg_wrapper.cpp @@ -0,0 +1,110 @@ +// 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. + +// Helper wrapper for hermetic tools to wrap arguments. +// +// This roughly equivalent to: +// 1. readlink /proc/self/exe, then dirname multiple times to determine the path +// internal_dir = +// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use +// 2. tool_name = basename($0) +// 3. call <internal_dir>/<tool_name> $@ \\ +// $(cat <internal_dir>/<tool_name>_args.txt) +// +// This is a C++ binary instead of a shell / Python script so that +// /proc/self/exe is a proper anchor to find internal_dir. If this were a +// script, /proc/self/exe would be the path to the interpreter. +// This also avoids using any hermetic tools in order to determine the path to +// them. + +#include <linux/limits.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include <filesystem> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +namespace { + +// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use +std::filesystem::path get_kleaf_internal_dir() { + std::error_code ec; + auto my_path = std::filesystem::read_symlink("/proc/self/exe", ec); + if (ec.value() != 0) { + std::cerr << "ERROR: read_symlink /proc/self/exe: " << ec.message() + << std::endl; + exit(EX_SOFTWARE); + } + return my_path.parent_path().parent_path().parent_path() / "hermetic-tools" / + "kleaf_internal_do_not_use"; +} + +// Loads <tool_name>_args.txt from hermetic_tools.extra_args +std::vector<std::string> load_arg_file(const std::filesystem::path& path) { + std::ifstream ifs(path); + if (!ifs) { + int saved_errno = errno; + std::cerr << "Unable to open " << path << ": " << strerror(saved_errno) + << std::endl; + exit(EX_SOFTWARE); + } + std::vector<std::string> args; + for (std::string arg; std::getline(ifs, arg);) { + args.push_back(arg); + } + return args; +} + +} // namespace + +int main(int argc, char* argv[]) { + auto internal_dir = get_kleaf_internal_dir(); + + if (argc < 1) { + std::cerr << "ERROR: argc == " << argc << " < 1" << std::endl; + return EX_SOFTWARE; + } + std::string tool_name(std::filesystem::path(argv[0]).filename()); + + // The actual executable we are going to call. Cast to string to use + // in new_argv. + std::string real_executable = internal_dir / tool_name; + + std::vector<char*> new_argv; + new_argv.push_back(real_executable.data()); + + for (int i = 1; i < argc; i++) { + new_argv.push_back(argv[i]); + } + + auto extra_args_file = internal_dir / (tool_name + "_args.txt"); + auto preset_args = load_arg_file(extra_args_file); + for (auto& preset_arg : preset_args) { + new_argv.push_back(preset_arg.data()); + } + new_argv.push_back(nullptr); + + if (-1 != execv(real_executable.c_str(), new_argv.data())) { + int saved_errno = errno; + std::cerr << "ERROR: execv: " << real_executable << ": " + << strerror(saved_errno) << std::endl; + return EX_SOFTWARE; + } + std::cerr << "ERROR: execv returns!" << std::endl; + return EX_SOFTWARE; +} diff --git a/kleaf/impl/declare_kernel_prebuilts.bzl b/kleaf/impl/declare_kernel_prebuilts.bzl index 0aba35d..993b670 100644 --- a/kleaf/impl/declare_kernel_prebuilts.bzl +++ b/kleaf/impl/declare_kernel_prebuilts.bzl @@ -40,23 +40,6 @@ _tag_class = tag_class( `--config=internet` is not necessary. """, ), - "auto_download_config": attr.bool( - doc = """If `True`, infer download configs from `target`.""", - ), - "download_configs": attr.string( - doc = """A JSON dictionary that configure the list of files to download. - - Key: local file name. - - Value: A dictionary with the following keys: - * `mandatory`: Whether the files in `outs_mapping` is mandatory. - If mandatory, failure to download the - file results in a build failure. - * `remote_filename_fmt`: remote file name format string, with the following anchors: - * {build_number} - * {target} - """, - ), "target": attr.string( doc = """Name of the build target as identified by the remote build server. @@ -82,8 +65,6 @@ def _declare_repos(module_ctx, tag_name): name = module_tag.name, apparent_name = module_tag.name, local_artifact_path = module_tag.local_artifact_path, - auto_download_config = module_tag.auto_download_config, - download_configs = module_tag.download_configs, target = module_tag.target, ) diff --git a/kleaf/impl/image/boot_images.bzl b/kleaf/impl/image/boot_images.bzl index e297bc1..5ba9152 100644 --- a/kleaf/impl/image/boot_images.bzl +++ b/kleaf/impl/image/boot_images.bzl @@ -65,6 +65,7 @@ def _boot_images_impl(ctx): ] inputs += ctx.files.deps inputs += ctx.files.vendor_ramdisk_binaries + inputs += ctx.files.vendor_ramdisk_dev_nodes transitive_inputs = [ kernel_build_outs, @@ -117,6 +118,13 @@ def _boot_images_impl(ctx): vendor_ramdisk_binaries = " ".join([file.path for file in ctx.files.vendor_ramdisk_binaries]), ) + if ctx.files.vendor_ramdisk_dev_nodes: + command += """ + VENDOR_RAMDISK_DEV_NODES="{vendor_ramdisk_dev_nodes}" + """.format( + vendor_ramdisk_dev_nodes = " ".join([file.path for file in ctx.files.vendor_ramdisk_dev_nodes]), + ) + command += """ # Create and restore DIST_DIR. # We don't need all of *_for_dist. Copying all declared outputs of kernel_build is @@ -241,6 +249,7 @@ Execute `build_boot_images` in `build_utils.sh`.""", * If `None`, skip `vendor_boot`. """, values = ["vendor_boot", "vendor_kernel_boot"]), "vendor_ramdisk_binaries": attr.label_list(allow_files = True), + "vendor_ramdisk_dev_nodes": attr.label_list(allow_files = True), "unpack_ramdisk": attr.bool( doc = """ When false it skips unpacking the vendor ramdisk and copy it as is, without modifications, into the boot image. Also skip the mkbootfs step. diff --git a/kleaf/impl/image/kernel_images.bzl b/kleaf/impl/image/kernel_images.bzl index 88027f6..1de3eff 100644 --- a/kleaf/impl/image/kernel_images.bzl +++ b/kleaf/impl/image/kernel_images.bzl @@ -52,6 +52,7 @@ def kernel_images( modules_blocklist = None, modules_options = None, vendor_ramdisk_binaries = None, + vendor_ramdisk_dev_nodes = None, system_dlkm_fs_type = None, system_dlkm_fs_types = None, system_dlkm_modules_list = None, @@ -289,6 +290,9 @@ def kernel_images( ``` # do not sort ``` + vendor_ramdisk_dev_nodes: List of dev nodes description files + which describes special device files to be added to the vendor + ramdisk. File format is as accepted by mkbootfs. ramdisk_compression: If provided it specfies the format used for any ramdisks generated. If not provided a fallback value from build.config is used. Possible values are `lz4`, `gzip`, None. @@ -467,6 +471,7 @@ def kernel_images( initramfs = ":{}_initramfs".format(name) if build_initramfs else None, mkbootimg = mkbootimg, vendor_ramdisk_binaries = vendor_ramdisk_binaries, + vendor_ramdisk_dev_nodes = vendor_ramdisk_dev_nodes, build_boot = build_boot, vendor_boot_name = vendor_boot_name, unpack_ramdisk = unpack_ramdisk, diff --git a/kleaf/impl/kernel_config.bzl b/kleaf/impl/kernel_config.bzl index b73874e..d1c5b2b 100644 --- a/kleaf/impl/kernel_config.bzl +++ b/kleaf/impl/kernel_config.bzl @@ -114,7 +114,6 @@ def _config_trim(ctx): return [] return [ - _config.disable("UNUSED_SYMBOLS"), _config.enable("TRIM_UNUSED_KSYMS"), ] @@ -481,8 +480,10 @@ def get_config_setup_command( rsync -aL {out_dir}/.config ${{OUT_DIR}}/.config rsync -aL --chmod=D+w {out_dir}/include/ ${{OUT_DIR}}/include/ rsync -aL --chmod=F+w {out_dir}/localversion ${{OUT_DIR}}/localversion - rsync -aL --chmod=F+w --ignore-missing-args \\ - {out_dir}/{raw_kmi_symbol_list_below_out_dir} ${{OUT_DIR}}/ + if [[ -f {out_dir}/{raw_kmi_symbol_list_below_out_dir} ]]; then + rsync -aL --chmod=F+w \\ + {out_dir}/{raw_kmi_symbol_list_below_out_dir} ${{OUT_DIR}}/ + fi # Restore real value of $ROOT_DIR in auto.conf.cmd sed -i'' -e 's:${{ROOT_DIR}}:'"${{ROOT_DIR}}"':g' ${{OUT_DIR}}/include/config/auto.conf.cmd diff --git a/kleaf/impl/kernel_prebuilt_repo.bzl b/kleaf/impl/kernel_prebuilt_repo.bzl index 9f7e185..9841ba4 100644 --- a/kleaf/impl/kernel_prebuilt_repo.bzl +++ b/kleaf/impl/kernel_prebuilt_repo.bzl @@ -19,10 +19,6 @@ load( "FILEGROUP_DEF_ARCHIVE_SUFFIX", "FILEGROUP_DEF_BUILD_FRAGMENT_NAME", ) -load( - ":kernel_prebuilt_utils.bzl", - "CI_TARGET_MAPPING", -) visibility("//build/kernel/kleaf/...") @@ -68,14 +64,6 @@ def _get_build_number(repository_ctx): build_number = repository_ctx.attr.build_number return build_number -def _infer_download_configs(target): - """Returns inferred `download_config` and `mandatory` from target.""" - chosen_mapping = CI_TARGET_MAPPING.get(target) - if not chosen_mapping: - fail("auto_download_config with {} is not supported yet.".format(target)) - - return chosen_mapping["download_configs"] - def _get_remote_filename( repository_ctx, build_number, @@ -182,16 +170,22 @@ def _download_remote_file(repository_ctx, local_filename, remote_filename_fmt, f block = False, ) -def _kernel_prebuilt_repo_impl(repository_ctx): - bazel_target_name = repository_ctx.attr.target - if repository_ctx.attr.auto_download_config: - if repository_ctx.attr.download_configs: - fail("{}: download_configs should not be set when auto_download_config is True".format( - repository_ctx.attr.name, - )) - download_configs = _infer_download_configs(bazel_target_name) +def _get_download_configs(repository_ctx): + if repository_ctx.attr.local_artifact_path: + path = repository_ctx.workspace_root.get_child(repository_ctx.attr.local_artifact_path).get_child("download_configs.json") else: - download_configs = json.decode(repository_ctx.attr.download_configs) + _download_remote_file( + repository_ctx = repository_ctx, + local_filename = "download_configs.json", + remote_filename_fmt = "download_configs.json", + file_mandatory = True, + ).wait() + path = _get_local_path(repository_ctx, "download_configs.json") + content = repository_ctx.read(path) + return json.decode(content) + +def _kernel_prebuilt_repo_impl(repository_ctx): + download_configs = _get_download_configs(repository_ctx) futures = {} for local_filename, config in download_configs.items(): @@ -314,23 +308,6 @@ kernel_prebuilt_repo = repository_rule( doc = "the default build number to use if the environment variable is not set.", ), "apparent_name": attr.string(doc = "apparant repo name", mandatory = True), - "auto_download_config": attr.bool( - doc = """If `True`, infer `download_configs` from `target`.""", - ), - "download_configs": attr.string( - doc = """A JSON dictionary that configure the list of files to download. - - Key: local file name. - - Value: A dictionary with the following keys: - * `mandatory`: Whether the files in `outs_mapping` is mandatory. - If mandatory, failure to download the - file results in a build failure. - * `remote_filename_fmt`: remote file name format string, with the following anchors: - * {build_number} - * {target} - """, - ), "target": attr.string(doc = "Name of target on the download location, e.g. `kernel_aarch64`"), "artifact_url_fmt": attr.string( doc = """API endpoint for Android CI artifacts. diff --git a/kleaf/impl/kernel_prebuilt_utils.bzl b/kleaf/impl/kernel_prebuilt_utils.bzl index 4ebfbc6..b8ea097 100644 --- a/kleaf/impl/kernel_prebuilt_utils.bzl +++ b/kleaf/impl/kernel_prebuilt_utils.bzl @@ -28,6 +28,13 @@ load( visibility("//build/kernel/kleaf/...") +_AARCH64_INIT_DDK_FILES = [ + # TODO(b/328770706): download_configs.json should be a proper rule to + # get the name of the file from :kernel_aarch64_ddk_headers_archive + "kernel_aarch64_ddk_headers_archive.tar.gz", + "build.config.constants", +] + # Key: Bazel target name in common_kernels.bzl # repo_name: name of download_artifacts_repo in bazel.WORKSPACE # outs: list of outs associated with that target name @@ -115,6 +122,13 @@ CI_TARGET_MAPPING = { "remote_filename_fmt": item, } for item in GKI_ARTIFACTS_AARCH64_OUTS + } | { + item: { + "target_suffix": "init_ddk_files", + "mandatory": True, + "remote_filename_fmt": item, + } + for item in _AARCH64_INIT_DDK_FILES }, }, } diff --git a/kleaf/impl/kmi_symbol_list.bzl b/kleaf/impl/kmi_symbol_list.bzl index 13ea008..ec6fd86 100644 --- a/kleaf/impl/kmi_symbol_list.bzl +++ b/kleaf/impl/kmi_symbol_list.bzl @@ -31,19 +31,16 @@ def _kmi_symbol_list_impl(ctx): outputs = [] out_file = ctx.actions.declare_file("{}/abi_symbollist".format(ctx.attr.name)) - report_file = ctx.actions.declare_file("{}/abi_symbollist.report".format(ctx.attr.name)) - outputs = [out_file, report_file] + outputs = [out_file] command = ctx.attr.env[KernelEnvInfo].setup + """ mkdir -p {out_dir} {process_symbols} --out-dir={out_dir} --out-file={out_file_base} \ - --report-file={report_file_base} --in-dir="${{ROOT_DIR}}" \ - {srcs} + --in-dir="${{ROOT_DIR}}" {srcs} """.format( process_symbols = ctx.executable._process_symbols.path, out_dir = out_file.dirname, out_file_base = out_file.basename, - report_file_base = report_file.basename, srcs = " ".join([f.path for f in ctx.files.srcs]), ) diff --git a/kleaf/tests/diff_test.bzl b/kleaf/tests/diff_test.bzl deleted file mode 100644 index 38b70d4..0000000 --- a/kleaf/tests/diff_test.bzl +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2022 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""A test that diffs two files.""" - -load("//build/kernel/kleaf/impl:hermetic_exec.bzl", "hermetic_exec_test") - -def diff_test( - name, - expected, - actual): - """Defines a test that diff two files.""" - - hermetic_exec_test( - name = name, - data = [ - expected, - actual, - ], - script = """ -expected=$(rootpath {expected}) -actual=$(rootpath {actual}) -if ! diff -q $actual $expected; then - echo "ERROR: test fails. expected:\n$(cat $expected)\nactual:\n$(cat $actual)" >&2 - exit 1 -fi -""".format( - expected = expected, - actual = actual, - ), - ) diff --git a/kleaf/tests/hermetic_test.bzl b/kleaf/tests/hermetic_test.bzl index fb7b95b..cdf5404 100644 --- a/kleaf/tests/hermetic_test.bzl +++ b/kleaf/tests/hermetic_test.bzl @@ -14,7 +14,9 @@ """Rules that wraps a py_test / py_binary (for test purposes) so it is more hermetic.""" +load("@bazel_skylib//lib:shell.bzl", "shell") load("//build/kernel/kleaf:hermetic_tools.bzl", "hermetic_toolchain") +load("//build/kernel/kleaf/impl:common_providers.bzl", "KernelPlatformToolchainInfo") def _hermetic_test_impl(ctx): hermetic_tools = hermetic_toolchain.get(ctx) @@ -25,7 +27,20 @@ def _hermetic_test_impl(ctx): else: run_setup = hermetic_tools.run_setup - script = """#!/bin/bash -ex + runfiles_transitive_files = [ + hermetic_tools.deps, + ] + + if ctx.attr.use_cc_toolchain: + kernel_toolchain_exec = ctx.attr._kernel_toolchain_exec[KernelPlatformToolchainInfo] + run_setup += """ +export PATH={quoted_real_bin_path}":${{PATH}}" +""".format( + quoted_real_bin_path = "${PWD}/" + shell.quote(kernel_toolchain_exec.bin_path), + ) + runfiles_transitive_files.append(kernel_toolchain_exec.all_files) + + script = """#!/bin/bash -e {run_setup} {actual} "$@" """.format( @@ -35,9 +50,6 @@ def _hermetic_test_impl(ctx): ctx.actions.write(script_file, script, is_executable = True) - runfiles_transitive_files = [ - hermetic_tools.deps, - ] transitive_runfiles = [ ctx.attr.actual[DefaultInfo].default_runfiles, ] @@ -54,6 +66,11 @@ def _hermetic_test_impl(ctx): runfiles = runfiles, ) +def _get_kernel_toolchain_exec(use_cc_toolchain): + if use_cc_toolchain: + return Label("//build/kernel/kleaf/impl:kernel_toolchain_exec") + return None + _RULE_ATTRS = dict( doc = "Wraps a test binary so it runs with hermetic tools.", implementation = _hermetic_test_impl, @@ -78,6 +95,12 @@ _RULE_ATTRS = dict( "data": attr.label_list(allow_files = True, doc = """ See [data](https://bazel.build/reference/be/common-definitions#typical.data) """), + "use_cc_toolchain": attr.bool( + doc = "Also include CC toolchain", + ), + "_kernel_toolchain_exec": attr.label( + default = _get_kernel_toolchain_exec, + ), }, toolchains = [hermetic_toolchain.type], ) diff --git a/kleaf/tests/integration_test/BUILD.bazel b/kleaf/tests/integration_test/BUILD.bazel index d8824c9..568a1c1 100644 --- a/kleaf/tests/integration_test/BUILD.bazel +++ b/kleaf/tests/integration_test/BUILD.bazel @@ -13,6 +13,7 @@ # limitations under the License. load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +load("//build/kernel/kleaf/tests:hermetic_test.bzl", "hermetic_test_binary") load("//build/kernel/kleaf/tests/utils:write_flag.bzl", "write_flag") # tools/bazel run //build/kernel/kleaf/tests/integration_test @@ -25,9 +26,20 @@ load("//build/kernel/kleaf/tests/utils:write_flag.bzl", "write_flag") # - The test involves running `tools/bazel clean` in between. If this were executed # with `tools/bazel test`, the directory structure in output_base would be broken # before results are reported back to bazel. -py_binary( +hermetic_test_binary( name = "integration_test", + actual = ":integration_test_internal", + # Preserve host PATH so: + # - The test can use `git` + # - The bazel wrapper can use `repo` when not running on CI + append_host_path = True, + use_cc_toolchain = True, +) + +py_binary( + name = "integration_test_internal", srcs = ["integration_test.py"], + main = "integration_test.py", deps = [ "//build/kernel/kleaf:wrapper", "//build/kernel/kleaf/analysis:inputs", diff --git a/kleaf/tests/integration_test/ddk_workspace_test/.gitignore b/kleaf/tests/integration_test/ddk_workspace_test/.gitignore new file mode 100644 index 0000000..39a231d --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/.gitignore @@ -0,0 +1,10 @@ +out +bazel-* +MODULE.bazel.lock + +# Generated files by init_ddk.py +device.bazelrc +MODULE.bazel +tools +external/kleaf/ +gki_prebuilts diff --git a/kleaf/tests/integration_test/ddk_workspace_test/README.md b/kleaf/tests/integration_test/ddk_workspace_test/README.md new file mode 100644 index 0000000..6c1fa37 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/README.md @@ -0,0 +1,15 @@ +# Experimental: DDKv2 tests + +This directory contains a list of tests for setting up the DDK workspace. +For details, see [Setting up DDK workspace](../../../docs/ddk/workspace.md). + +**Note**: Some tests uses hacks to work around kinks. These hacks +may be replaced by a better API in the future. **DO NOT** use these tests as a +reference to set up your DDK workspace ... yet. + +## Running the test + +```shell +$ tools/bazel run //build/kernel/kleaf/tests/integration_test \ + -- KleafIntegrationTestShard2.test_ddk_workspace_setup +``` diff --git a/kleaf/tests/integration_test/ddk_workspace_test/WORKSPACE.bzlmod b/kleaf/tests/integration_test/ddk_workspace_test/WORKSPACE.bzlmod new file mode 100644 index 0000000..841ee62 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/WORKSPACE.bzlmod @@ -0,0 +1,2 @@ +# empty file to work around rules_cc error +# TODO(b/338440785): Delete this file. diff --git a/kleaf/tests/integration_test/ddk_workspace_test/external/kleaf/README.md b/kleaf/tests/integration_test/ddk_workspace_test/external/kleaf/README.md new file mode 100644 index 0000000..40acdaf --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/external/kleaf/README.md @@ -0,0 +1 @@ +Empty directory that is used as a mount point for Kleaf tooling repository. diff --git a/kleaf/tests/integration_test/ddk_workspace_test/extra_setup.py b/kleaf/tests/integration_test/ddk_workspace_test/extra_setup.py new file mode 100644 index 0000000..71a3bf5 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/extra_setup.py @@ -0,0 +1,56 @@ +# 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. + +# TODO: This functionality should be moved into init_ddk.py + +"""Script that fix-ups files generated by init_ddk.py for testing.""" + +import argparse +import dataclasses +import pathlib +import textwrap + + +@dataclasses.dataclass +class DdkExtraSetup: + """Additional fix-ups after init_ddk.py for integration tests.""" + + # path to @kleaf. Its value will be used as-is. + kleaf_repo_rel: pathlib.Path + + # path to DDK workspace. Its value will be used as-is. + ddk_workspace: pathlib.Path + + def _generate_module_bazel(self): + path = self.ddk_workspace / "MODULE.bazel" + with path.open("a") as out_file: + print(textwrap.dedent("""\ + bazel_dep(name = "bazel_skylib") + """), file=out_file) + + def run(self): + self._generate_module_bazel() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--kleaf_repo_rel", + type=pathlib.Path, + help="If relative, it is against ddk_workspace", + ) + parser.add_argument("--ddk_workspace", + type=pathlib.Path, + help="If relative, it is against cwd",) + args = parser.parse_args() + DdkExtraSetup(**vars(args)).run() diff --git a/kleaf/tests/integration_test/ddk_workspace_test/in_tree/BUILD.bazel b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/BUILD.bazel new file mode 100644 index 0000000..9e02a17 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/BUILD.bazel @@ -0,0 +1,79 @@ +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load( + "@kleaf//build/kernel/kleaf:kernel.bzl", + "ddk_module", + "kernel_build", + "kernel_build_config", + "kernel_images", + "kernel_modules_install", +) + +# Workaround to set KERNEL_DIR correctly and +# avoid using the fallback (directory of the config). +# TODO(b/338438451): Clean this up with kernel_build.kernel_dir attr. +_set_kernel_dir_cmd = "KERNEL_DIR=\"{kernel_dir}\"".format( + kernel_dir = paths.join( + package_relative_label("@kleaf//:x").workspace_root, + "common", + ), +) + +write_file( + name = "set_kernel_dir_build_config", + out = "set_kernel_dir_build_config/build.config", + content = [ + _set_kernel_dir_cmd, + "", + ], +) + +kernel_build_config( + name = "in_tree_build_config", + srcs = [ + # do not sort + ":set_kernel_dir_build_config", + "build.config.in_tree", + ], +) + +# Test building in-tree module against kernel from sources +kernel_build( + name = "in_tree_kernel_build", + srcs = ["@kleaf//common:kernel_aarch64_sources"], + outs = [], + base_kernel = "@kleaf//common:kernel_aarch64", + build_config = ":in_tree_build_config", + defconfig_fragments = [ + "in_tree_defconfig", + ], + make_goals = [ + "modules", + ], + module_outs = [ + "psmouse.ko", + ], +) + +# Test building out-of-tree module against kernel with in-tree modules +ddk_module( + name = "out_of_tree_driver", + srcs = ["mydriver.c"], + out = "out_of_tree_driver.ko", + kernel_build = ":in_tree_kernel_build", + deps = ["@kleaf//common:all_headers_aarch64"], +) + +kernel_modules_install( + name = "device_modules_install", + kernel_modules = [ + ":out_of_tree_driver", + ], +) + +kernel_images( + name = "device_images", + build_vendor_dlkm = True, + kernel_modules_install = ":device_modules_install", + visibility = ["//tests:__pkg__"], +) diff --git a/kleaf/tests/integration_test/ddk_workspace_test/in_tree/build.config.in_tree b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/build.config.in_tree new file mode 100644 index 0000000..6f7a45d --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/build.config.in_tree @@ -0,0 +1,3 @@ +. ${ROOT_DIR}/${KERNEL_DIR}/build.config.common +. ${ROOT_DIR}/${KERNEL_DIR}/build.config.gki +. ${ROOT_DIR}/${KERNEL_DIR}/build.config.aarch64 diff --git a/kleaf/tests/integration_test/ddk_workspace_test/in_tree/in_tree_defconfig b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/in_tree_defconfig new file mode 100644 index 0000000..04c5a18 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/in_tree_defconfig @@ -0,0 +1,2 @@ +CONFIG_MOUSE_PS2=m +# CONFIG_MODULE_SIG_ALL is not set diff --git a/kleaf/tests/integration_test/ddk_workspace_test/in_tree/mydriver.c b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/mydriver.c new file mode 100644 index 0000000..5ee4ba8 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/in_tree/mydriver.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> + +MODULE_DESCRIPTION("A test module for DDK testing purposes"); +MODULE_AUTHOR("Yifan Hong <elsk@google.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/BUILD.bazel b/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/BUILD.bazel new file mode 100644 index 0000000..0f438e4 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/BUILD.bazel @@ -0,0 +1,29 @@ +load( + "@kleaf//build/kernel/kleaf:kernel.bzl", + "ddk_module", + "kernel_images", + "kernel_modules_install", +) + +# Test building no-op DDK module against kernel from sources +ddk_module( + name = "out_of_tree", + srcs = ["mydriver.c"], + out = "out_of_tree.ko", + kernel_build = "//tests:kernel", + deps = ["@kleaf//common:all_headers_aarch64"], +) + +kernel_modules_install( + name = "device_modules_install", + kernel_modules = [ + ":out_of_tree", + ], +) + +kernel_images( + name = "device_images", + build_vendor_dlkm = True, + kernel_modules_install = ":device_modules_install", + visibility = ["//tests:__pkg__"], +) diff --git a/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/mydriver.c b/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/mydriver.c new file mode 100644 index 0000000..5ee4ba8 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/mydriver.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> + +MODULE_DESCRIPTION("A test module for DDK testing purposes"); +MODULE_AUTHOR("Yifan Hong <elsk@google.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/kleaf/tests/integration_test/ddk_workspace_test/tests/BUILD.bazel b/kleaf/tests/integration_test/ddk_workspace_test/tests/BUILD.bazel new file mode 100644 index 0000000..d235ba0 --- /dev/null +++ b/kleaf/tests/integration_test/ddk_workspace_test/tests/BUILD.bazel @@ -0,0 +1,42 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +label_flag( + name = "kernel", + build_setting_default = "@kleaf//common:kernel_aarch64", + visibility = ["//out_of_tree:__pkg__"], +) + +config_setting( + name = "building_kernel_from_source", + flag_values = { + ":kernel": "@kleaf//common:kernel_aarch64", + }, +) + +filegroup( + name = "build_test_targets", + srcs = [ + "//out_of_tree:device_images", + ] + select({ + # If using prebuilts, we don't care about the tests against + # the kernel built from source. + ":building_kernel_from_source": [ + "//in_tree:device_images", + ], + "//conditions:default": [], + }), +) + +build_test( + name = "build_test", + targets = [":build_test_targets"], +) + +# TODO: Add tests on the image content + +test_suite( + name = "tests", + tests = [ + ":build_test", + ], +) diff --git a/kleaf/tests/integration_test/fake_repo_root/.gitignore b/kleaf/tests/integration_test/fake_repo_root/.gitignore new file mode 100644 index 0000000..257c8be --- /dev/null +++ b/kleaf/tests/integration_test/fake_repo_root/.gitignore @@ -0,0 +1 @@ +fake_workspace_root diff --git a/kleaf/tests/integration_test/fake_repo_root/fake_workspace_root/README.md b/kleaf/tests/integration_test/fake_repo_root/fake_workspace_root/README.md new file mode 100644 index 0000000..7958060 --- /dev/null +++ b/kleaf/tests/integration_test/fake_repo_root/fake_workspace_root/README.md @@ -0,0 +1 @@ +Mount point for workspace root for some tests. diff --git a/kleaf/tests/integration_test/integration_test.py b/kleaf/tests/integration_test/integration_test.py index 8676628..c677adf 100644 --- a/kleaf/tests/integration_test/integration_test.py +++ b/kleaf/tests/integration_test/integration_test.py @@ -36,8 +36,12 @@ Example: """ import argparse +import collections import contextlib +import dataclasses import hashlib +import io +import json import os import re import shlex @@ -48,6 +52,7 @@ import pathlib import tempfile import textwrap import unittest +import xml.dom.minidom from typing import Any, Callable, Iterable, TextIO from absl.testing import absltest @@ -85,6 +90,20 @@ def load_arguments(): dest="include_abi_tests", help="Include ABI Monitoring related tests." + "NOTE: It requires a branch with ABI monitoring enabled.") + parser.add_argument("--mount-spec", + type=_deserialize_mount_spec, + help="""A JSON dictionary specifying bind mounts. + + If not set, some tests will re-run itself + in an unshare-d namespace with the flag set.""", + default=MountSpec()) + parser.add_argument("--link-spec", + type=_deserialize_link_spec, + help="""A JSON dictionary specifying symlinks. + + If not set, some tests will re-run itself + with the flag set.""", + default=LinkSpec()) group = parser.add_argument_group("CI", "flags for ci.android.com") group.add_argument("--test_result_dir", type=_require_absolute_path, @@ -105,6 +124,69 @@ def _require_absolute_path(p: str) -> pathlib.Path: return path +MountSpec = dict[pathlib.Path, pathlib.Path] + + +def _serialize_mount_spec(val: MountSpec) -> str: + return json.dumps({str(key): str(value) for key, value in val.items()}) + + +def _deserialize_mount_spec(s: str) -> MountSpec: + return {pathlib.Path(key): pathlib.Path(value) + for key, value in json.loads(s).items()} + + +@dataclasses.dataclass +class Link: + # Value in repo manifest. Relative against repo root. + dest: pathlib.Path + + # Unlike the value in repo manifest, this is relative against repo root + # not project path. + src: pathlib.Path + + @classmethod + def from_element(cls, element: xml.dom.minidom.Element, + project_path: pathlib.Path) -> "Link": + return cls(dest=pathlib.Path(element.getAttribute("dest")), + src=project_path / element.getAttribute("src")) + + +LinkSpec = list[Link] + + +def _serialize_link_spec(links: LinkSpec) -> str: + return json.dumps([{"dest": str(link.dest), "src": str(link.src)} + for link in links]) + + +def _deserialize_link_spec(s: str) -> LinkSpec: + return [Link(dest=pathlib.Path(obj["dest"]), src=pathlib.Path(obj["src"])) + for obj in json.loads(s)] + + +@dataclasses.dataclass +class RepoProject: + # Project path + path: pathlib.Path + + # List of symlinks to create + links: list[Link] = dataclasses.field(default_factory=list) + + # List of groups + groups: list[str] = dataclasses.field(default_factory=list) + + @classmethod + def from_element(cls, element: xml.dom.minidom.Element) -> "RepoProject": + path = pathlib.Path( + element.getAttribute("path") or element.getAttribute("name")) + project = cls(path=path) + for link_element in element.getElementsByTagName("linkfile"): + project.links.append(Link.from_element(link_element, path)) + project.groups = re.split(r",| ", element.getAttribute("groups")) + return project + + class Exec(object): @staticmethod @@ -115,6 +197,13 @@ class Exec(object): subprocess.check_call(args, **kwargs) @staticmethod + def call(args: list[str], **kwargs) -> None: + """Executes a shell command.""" + kwargs.setdefault("text", True) + sys.stderr.write(f"+ {' '.join(args)}\n") + subprocess.call(args, **kwargs) + + @staticmethod def check_output(args: list[str], **kwargs) -> str: """Returns output of a shell command""" kwargs.setdefault("text", True) @@ -159,9 +248,19 @@ class KleafIntegrationTestBase(unittest.TestCase): if use_bazelrc: subprocess_args.append(f"--bazelrc={self._bazel_rc.name}") subprocess_args.append(command) - subprocess_args.extend(command_args) + + if "--" in command_args: + idx = command_args.index("--") + bazel_command_args = command_args[:idx] + script_args = command_args[idx:] + else: + bazel_command_args = command_args + script_args = [] + + subprocess_args.extend(bazel_command_args) if use_wrapper_args: subprocess_args.extend(arguments.bazel_wrapper_args) + subprocess_args.extend(script_args) # kwargs has known arguments filtered out. return subprocess_args, kwargs @@ -192,7 +291,8 @@ class KleafIntegrationTestBase(unittest.TestCase): def setUp(self) -> None: self.assertTrue(os.environ.get("BUILD_WORKSPACE_DIRECTORY"), - "BUILD_WORKSPACE_DIRECTORY is not set") + "BUILD_WORKSPACE_DIRECTORY is not set. " + + "Did you use `tools/bazel test` instead of `tools/bazel run`?") os.chdir(os.environ["BUILD_WORKSPACE_DIRECTORY"]) sys.stderr.write( f"BUILD_WORKSPACE_DIRECTORY={os.environ['BUILD_WORKSPACE_DIRECTORY']}\n" @@ -277,6 +377,112 @@ class KleafIntegrationTestBase(unittest.TestCase): """Returns the common package.""" return "common" + def _mount(self, kleaf_repo: pathlib.Path): + """Mount according to --mount-spec""" + for from_path, to_path in arguments.mount_spec.items(): + to_path.mkdir(parents=True, exist_ok=True) + Exec.check_call([shutil.which("mount"), "--bind", "-o", "ro", + str(from_path), str(to_path)]) + self.addCleanup(Exec.call, + [shutil.which("umount"), str(to_path)]) + + for link in arguments.link_spec: + real_dest = kleaf_repo / link.dest + real_src = kleaf_repo / link.src + relative_src = self._force_relative_to(real_src, real_dest.parent) + real_dest.parent.mkdir(parents=True, exist_ok=True) + real_dest.symlink_to(relative_src) + + @staticmethod + def _force_relative_to(path: pathlib.Path, other: pathlib.Path): + """Naive implementation of pathlib.Path.relative_to(walk_up)""" + if sys.version_info[0] == 3 and sys.version_info[1] >= 12: + return path.relative_to(other, walk_up=True) + + path = path.resolve() + other = other.resolve() + + if path.is_relative_to(other): + return path.relative_to(other) + + path_parts = collections.deque(path.parts) + other_parts = collections.deque(other.parts) + while path_parts and other_parts and path_parts[0] == other_parts[0]: + path_parts.popleft() + other_parts.popleft() + parts = [".."] * len(other_parts) + list(path_parts) + return pathlib.Path(*parts) + + @classmethod + def _get_repo_manifest(cls) -> xml.dom.minidom.Document: + parser = argparse.ArgumentParser() + parser.add_argument("--repo_manifest", type=_require_absolute_path) + known, _ = parser.parse_known_args(arguments.bazel_wrapper_args) + if known.repo_manifest: + with open(known.repo_manifest) as manifest_file: + return xml.dom.minidom.parse(manifest_file) + + manifest_content = Exec.check_output(["repo", "manifest"]) + return xml.dom.minidom.parseString(manifest_content) + + @classmethod + def _get_projects(cls) -> list[RepoProject]: + manifest_element = cls._get_repo_manifest().documentElement + project_elements = manifest_element.getElementsByTagName("project") + return [RepoProject.from_element(element) + for element in project_elements] + + def _get_project_mount_link_spec(self, kleaf_repo: pathlib.Path, + groups: list[str]) \ + -> tuple[MountSpec, LinkSpec]: + """Returns MountSpec / LinkSpec for projects that matches any group. + + Args: + groups: List of groups to match projects. Projects with the `groups` + attribute matching any of the given |groups| are included. + If empty, all projects are included. + kleaf_repo: The root of the mount point where projects will be + mounted below. + """ + + projects = self._get_projects() + + relevant_projects = list[RepoProject]() + for project in projects: + if not groups or any(group in project.groups for group in groups): + relevant_projects.append(project) + + real_kleaf_repo = pathlib.Path(".").resolve() + mount_spec = MountSpec() + link_spec = LinkSpec() + for project in relevant_projects: + mount_spec[real_kleaf_repo / project.path] = \ + kleaf_repo / project.path + link_spec.extend(project.links) + + return mount_spec, link_spec + + def _unshare_mount_run(self, mount_spec: MountSpec, link_spec: list[Link]): + """Reruns the test in an unshare-d namespace with --mount-spec set.""" + args = [shutil.which("unshare"), "--mount", "--map-root-user"] + + # toybox unshare -R does not imply -U, so explicitly say so. + args.append("--user") + + args.extend([shutil.which("bash"), "-c"]) + + test_args = [sys.executable, __file__] + test_args.extend(f"--bazel-arg={arg}" for arg in arguments.bazel_args) + test_args.extend(f"--bazel-wrapper-arg={arg}" + for arg in arguments.bazel_wrapper_args) + test_args.append(f"--mount-spec={_serialize_mount_spec(mount_spec)}") + test_args.append(f"--link-spec={_serialize_link_spec(link_spec)}") + test_args.append(self.id().removeprefix("__main__.")) + args.append(" ".join(shlex.quote(str(test_arg)) + for test_arg in test_args)) + + Exec.check_call(args) + # NOTE: It requires a branch with ABI monitoring enabled. # Include these using the flag --include-abi-tests @@ -365,6 +571,7 @@ class KleafIntegrationTestShard1(KleafIntegrationTestBase): output = subprocess.check_output([extract_ikconfig, vmlinux], text=True) self.assertIn("CONFIG_UAPI_HEADER_TEST=y", output.splitlines()) + class KleafIntegrationTestShard2(KleafIntegrationTestBase): def test_user_clang_toolchain(self): @@ -388,6 +595,131 @@ class KleafIntegrationTestShard2(KleafIntegrationTestBase): ] + _LTO_NONE self._build(args) + +class DdkWorkspaceSetupTest(KleafIntegrationTestBase): + """Tests setting up a DDK workspace with @kleaf as dependency.""" + + def setUp(self): + super().setUp() + + self.real_kleaf_repo = pathlib.Path(".").resolve() + self.ddk_workspace = (pathlib.Path(__file__).resolve().parent / + "ddk_workspace_test") + + @classmethod + def _get_projects(cls) -> list[RepoProject]: + parser = argparse.ArgumentParser() + parser.add_argument("--repo_manifest", type=_require_absolute_path) + known, _ = parser.parse_known_args(arguments.bazel_wrapper_args) + if known.repo_manifest: + with open(known.repo_manifest) as manifest_file: + return cls._get_projects_from_manifest(manifest_file) + + manifest_content = Exec.check_output(["repo", "manifest"]) + manifest_file = io.StringIO(manifest_content) + return cls._get_projects_from_manifest(manifest_file) + + @staticmethod + def _get_projects_from_manifest(manifest_file: TextIO) -> list[RepoProject]: + dom = xml.dom.minidom.parse(manifest_file) + project_elements = dom.documentElement.getElementsByTagName("project") + return [RepoProject.from_element(element) + for element in project_elements] + + def test_ddk_workspace_below_kleaf_module(self): + """Tests that DDK workspace is below @kleaf""" + self._run_ddk_workspace_setup_test(self.real_kleaf_repo) + + def test_kleaf_module_below_ddk_workspace(self): + """Tests that @kelaf is below DDK workspace""" + kleaf_repo = self.ddk_workspace / "external/kleaf" + if not arguments.mount_spec: + mount_spec = { + self.real_kleaf_repo: kleaf_repo + } + self._unshare_mount_run(mount_spec=mount_spec, link_spec=LinkSpec()) + return + self._run_ddk_workspace_setup_test(kleaf_repo) + + def test_setup_with_prebuilts(self): + """Tests that init_ddk --prebuilts_dir & _ddk_headers_archive works.""" + self._check_call("run", [f"//{self._common()}:kernel_aarch64_dist"]) + + kleaf_repo = self.ddk_workspace / "external/kleaf" + + if not arguments.mount_spec: + mount_spec, link_spec = self._get_project_mount_link_spec( + kleaf_repo, ["ddk", "ddk-external"], + ) + + self._unshare_mount_run(mount_spec=mount_spec, link_spec=link_spec) + return + + real_prebuilts_dir = self.real_kleaf_repo / "out/kernel_aarch64/dist" + prebuilts_dir = self.ddk_workspace / "gki_prebuilts" + self._run_ddk_workspace_setup_test( + kleaf_repo, + prebuilts_dir=prebuilts_dir, + local=False, + url_fmt=f"file://{real_prebuilts_dir}/{{filename}}") + + def _run_ddk_workspace_setup_test(self, + kleaf_repo: pathlib.Path, + prebuilts_dir: pathlib.Path | None = None, + local: bool = True, + url_fmt: str | None = None): + # kleaf_repo relative to ddk_workspace + kleaf_repo_rel = self._force_relative_to( + kleaf_repo, self.ddk_workspace) + + git_clean_args = [shutil.which("git"), "clean", "-fdx"] + if kleaf_repo.is_relative_to(self.ddk_workspace): + git_clean_args.extend([ + "-e", + str(kleaf_repo.relative_to(self.ddk_workspace)), + ]) + + Exec.check_call(git_clean_args, cwd=self.ddk_workspace) + + # Delete generated files at the end + self.addCleanup(Exec.check_call, git_clean_args, + cwd=self.ddk_workspace) + + self._mount(kleaf_repo) + + args = [ + "//build/kernel:init_ddk", + "--", + f"--kleaf_repo={kleaf_repo}", + f"--ddk_workspace={self.ddk_workspace}", + ] + if local: + args.append("--local") + if prebuilts_dir: + args.append(f"--prebuilts_dir={prebuilts_dir}") + if url_fmt: + args.append(f"--url_fmt={url_fmt}") + self._check_call("run", args) + Exec.check_call([ + sys.executable, + str(self.ddk_workspace / "extra_setup.py"), + f"--kleaf_repo_rel={kleaf_repo_rel}", + f"--ddk_workspace={self.ddk_workspace}", + ]) + + self._check_call("clean", ["--expunge"], cwd=self.ddk_workspace) + + args = [] + # Switch base kernel when using prebuilts + if prebuilts_dir: + args.append("--//tests:kernel=@gki_prebuilts//kernel_aarch64") + args.append("//tests") + self._check_call("test", args, cwd=self.ddk_workspace) + + # Delete generated files + self._check_call("clean", ["--expunge"], cwd=self.ddk_workspace) + + # Quick integration tests. Each test case should finish within 1 minute. # The whole test suite should finish within 5 minutes. If the whole test suite # takes too long, consider sharding QuickIntegrationTest too. @@ -674,7 +1006,7 @@ class ScmversionIntegrationTest(KleafIntegrationTestBase): def setUp(self) -> None: super().setUp() - self.strings = "bazel-bin/build/kernel/hermetic-tools/llvm-strings" + self.strings = shutil.which("llvm-strings") self.uname_pattern_prefix = re.compile( r"^Linux version [0-9]+[.][0-9]+[.][0-9]+(\S*)") @@ -720,9 +1052,10 @@ class ScmversionIntegrationTest(KleafIntegrationTestBase): lambda x: re.search(extraversion_pattern, x), ["EXTRAVERSION ="]) - def _get_vmlinux_scmversion(self): + def _get_vmlinux_scmversion(self, workspace_root=pathlib.Path(".")): strings_output = Exec.check_output([ - self.strings, f"bazel-bin/{self._common()}/kernel_aarch64/vmlinux" + self.strings, + str(workspace_root / f"bazel-bin/{self._common()}/kernel_aarch64/vmlinux") ]) ret = [] for line in strings_output.splitlines(): @@ -836,6 +1169,59 @@ class ScmversionIntegrationTest(KleafIntegrationTestBase): for scmversion in self._get_vmlinux_scmversion(): self.assertRegexpMatches(scmversion, scmversion_pat) + def test_stamp_repo_root_is_not_workspace_root(self): + """Tests that --config=stamp works when repo root is not Bazel workspace root.""" + + self._setup_mainline() + + real_workspace_root = pathlib.Path(".").resolve() + repo_root = (pathlib.Path(__file__).resolve().parent / + "fake_repo_root") + workspace_root = repo_root / "fake_workspace_root" + + if not arguments.mount_spec: + mount_spec = { + real_workspace_root : workspace_root + } + + self._unshare_mount_run(mount_spec=mount_spec, link_spec=LinkSpec()) + return + + self._mount(workspace_root) + + manifest = self._get_repo_manifest() + for project in manifest.documentElement.getElementsByTagName("project"): + path = project.getAttribute("path") or project.getAttribute("name") + project.setAttribute( + "path", str(pathlib.Path("fake_workspace_root") / path)) + + new_manifest_temp_file = tempfile.NamedTemporaryFile( + mode="w+", delete=False) + new_manifest = pathlib.Path(new_manifest_temp_file.name) + self.addCleanup(new_manifest.unlink) + + with new_manifest_temp_file as file_handle: + manifest.writexml(file_handle) + + # KI: For this build, git commands on certain projects (e.g. + # prebuilts/ndk-r26, prebuilts/clang) are slow because it needs to + # refresh index. + self._check_call( + "build", + _FASTEST + [ + "--config=stamp", + f"--repo_manifest={repo_root}:{new_manifest}", + f"//{self._common()}:kernel_aarch64", + ], + cwd=workspace_root, + env=ScmversionIntegrationTest._env_without_build_number(), + ) + + scmversion_pat = re.compile( + r"^-rc999-mainline(-[0-9]{5,})?-g[0-9a-f]{12,40}(-dirty)?$") + for scmversion in self._get_vmlinux_scmversion(workspace_root): + self.assertRegexpMatches(scmversion, scmversion_pat) + # Class that mimics tee(1) class Tee(object): diff --git a/kleaf/tests/kernel_build_config_test/BUILD.bazel b/kleaf/tests/kernel_build_config_test/BUILD.bazel index e7dc4a2..fd0159e 100644 --- a/kleaf/tests/kernel_build_config_test/BUILD.bazel +++ b/kleaf/tests/kernel_build_config_test/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") load("//build/kernel/kleaf:kernel.bzl", "kernel_build_config") -load("//build/kernel/kleaf/tests:diff_test.bzl", "diff_test") +load("//build/kernel/kleaf/tests:hermetic_test.bzl", "hermetic_test") # Test kernel_build_config @@ -28,7 +29,14 @@ kernel_build_config( ) diff_test( + name = "kernel_build_config_test_internal", + file1 = "build.config.expected", + file2 = ":kernel_build_config_test_actual", + # Avoid running it without the hermetic_test wrapper + tags = ["manual"], +) + +hermetic_test( name = "kernel_build_config_test", - actual = ":kernel_build_config_test_actual", - expected = "build.config.expected", + actual = ":kernel_build_config_test_internal", ) diff --git a/kleaf/workspace.bzl b/kleaf/workspace.bzl index 13eba05..5e95d72 100644 --- a/kleaf/workspace.bzl +++ b/kleaf/workspace.bzl @@ -186,7 +186,6 @@ WARNING: define_kleaf_workspace() should be called with common_kernel_package={} name = value["repo_name"], apparent_name = value["repo_name"], artifact_url_fmt = artifact_url_fmt, - download_configs = json.encode(value["download_configs"]), target = target, ) diff --git a/kleaf/workspace_status_stamp.py b/kleaf/workspace_status_stamp.py index 66f3d9f..97effee 100644 --- a/kleaf/workspace_status_stamp.py +++ b/kleaf/workspace_status_stamp.py @@ -32,7 +32,7 @@ class PathCollectible(object): path: pathlib.Path def collect(self) -> str: - return NotImplementedError + raise NotImplementedError @dataclasses.dataclass @@ -153,59 +153,49 @@ def list_projects() -> list[pathlib.Path]: """Lists projects in the repository. Returns: - a list of projects in the repository. + a list of Git projects relative to CWD. """ - if "KLEAF_REPO_MANIFEST" in os.environ: - with open(os.environ["KLEAF_REPO_MANIFEST"]) as repo_prop_file: - return parse_repo_manifest(repo_prop_file.read()) + repo_root_s, repo_manifest = os.environ["KLEAF_REPO_MANIFEST"].split(":") + repo_root = pathlib.Path(repo_root_s) + + if repo_manifest: + with open(repo_manifest) as repo_manifest_file: + return parse_repo_manifest(repo_root, repo_manifest_file.read()) try: - output = subprocess.check_output(["repo", "list", "-f"], text=True) - return parse_repo_list(output) + output = subprocess.check_output(["repo", "manifest", "-r"], text=True) + return parse_repo_manifest(repo_root, output) except (subprocess.SubprocessError, FileNotFoundError) as e: - logging.warning("Unable to execute repo list -f: %s", e) + logging.warning("Unable to execute repo manifest -r: %s", e) return [] -def parse_repo_manifest(manifest: str) -> list[pathlib.Path]: +def parse_repo_manifest(repo_root: pathlib.Path, manifest: str) \ + -> list[pathlib.Path]: """Parses a repo manifest file. Returns: a list of paths to all projects in the repository. """ + kleaf_repo_dir = pathlib.Path(".").resolve() try: dom = xml.dom.minidom.parseString(manifest) except xml.parsers.expat.ExpatError as e: logging.error("Unable to parse repo manifest: %s", e) return [] projects = dom.documentElement.getElementsByTagName("project") - # https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.md#element-project - return [ - pathlib.Path(proj.getAttribute("path") or proj.getAttribute("name")) - for proj in projects - ] - - -def parse_repo_list(repo_list: str) -> list[pathlib.Path]: - """Parses the result of `repo list -f`. - - Returns: - a list of paths to all projects in the repository. - """ - workspace = pathlib.Path(".").absolute() - paths = [] - for line in repo_list.splitlines(): - line = line.strip() - if not line or ":" not in line: - continue - proj = pathlib.Path(line.split(":", 2)[0].strip()) - if proj.is_relative_to(workspace): - paths.append(proj.relative_to(workspace)) + ret = list[pathlib.Path]() + for project in projects: + # https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.md#element-project + path_below_repo = pathlib.Path(project.getAttribute("path") or + project.getAttribute("name")) + realpath = repo_root / path_below_repo + if realpath.is_relative_to(kleaf_repo_dir): + ret.append(realpath.relative_to(kleaf_repo_dir)) else: - logging.info( - "Ignoring project %s because it is not under the Bazel workspace", - proj) - return paths + logging.warning("Skipping project %s because it is not below %s", + realpath, kleaf_repo_dir) + return ret def collect(popen_obj: subprocess.Popen) -> str: |