summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.bazel29
-rwxr-xr-xabi/process_symbols.py82
-rw-r--r--abi/symbols.allow2
-rwxr-xr-xabi_compliance.sh35
-rwxr-xr-xbuild-tools/kleaf_internal_do_not_use_path/linux-x86/rsync3
-rwxr-xr-xbuild-tools/kleaf_internal_do_not_use_path/linux-x86/tar8
-rwxr-xr-xgki/download_from_ci9
-rw-r--r--init/init_ddk.py164
-rw-r--r--init/init_ddk_test.py63
-rw-r--r--kleaf/BUILD.bazel13
-rw-r--r--kleaf/README.md4
-rw-r--r--kleaf/artifact_tests/kernel_test.bzl1
-rwxr-xr-xkleaf/bazel.py82
-rw-r--r--kleaf/bazelrc/canary.bazelrc4
-rw-r--r--kleaf/bazelrc/flags.bazelrc2
-rw-r--r--kleaf/bazelrc/silent.bazelrc2
-rw-r--r--kleaf/bzlmod/bazel.MODULE.bazel32
-rw-r--r--kleaf/common_kernels.bzl1
-rw-r--r--kleaf/docs/abi.md23
-rw-r--r--kleaf/docs/api_reference/kernel_prebuilt_ext.md5
-rw-r--r--kleaf/docs/bzlmod.md34
-rw-r--r--kleaf/docs/canary.md9
-rw-r--r--kleaf/docs/ddk/main.md2
-rw-r--r--kleaf/docs/ddk/workspace.md241
-rw-r--r--kleaf/docs/download_prebuilt.md8
-rw-r--r--kleaf/docs/impl.md29
-rw-r--r--kleaf/docs/workspace.md10
-rw-r--r--kleaf/hermetic_tools.bzl157
-rw-r--r--kleaf/impl/BUILD.bazel7
-rw-r--r--kleaf/impl/abi/abi_stgdiff.bzl10
-rw-r--r--kleaf/impl/abi/abi_update.bzl35
-rw-r--r--kleaf/impl/arg_wrapper.cpp110
-rw-r--r--kleaf/impl/declare_kernel_prebuilts.bzl19
-rw-r--r--kleaf/impl/kernel_config.bzl7
-rw-r--r--kleaf/impl/kernel_prebuilt_repo.bzl26
-rw-r--r--kleaf/impl/kernel_prebuilt_utils.bzl14
-rw-r--r--kleaf/impl/kmi_symbol_list.bzl7
-rw-r--r--kleaf/tests/diff_test.bzl42
-rw-r--r--kleaf/tests/hermetic_test.bzl31
-rw-r--r--kleaf/tests/integration_test/BUILD.bazel14
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/.gitignore10
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/README.md15
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/WORKSPACE.bzlmod2
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/external/kleaf/README.md1
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/extra_setup.py56
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/in_tree/BUILD.bazel79
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/in_tree/build.config.in_tree3
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/in_tree/in_tree_defconfig2
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/in_tree/mydriver.c21
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/BUILD.bazel29
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/out_of_tree/mydriver.c21
-rw-r--r--kleaf/tests/integration_test/ddk_workspace_test/tests/BUILD.bazel42
-rw-r--r--kleaf/tests/integration_test/fake_repo_root/.gitignore1
-rw-r--r--kleaf/tests/integration_test/fake_repo_root/fake_workspace_root/README.md1
-rw-r--r--kleaf/tests/integration_test/integration_test.py425
-rw-r--r--kleaf/tests/kernel_build_config_test/BUILD.bazel14
-rw-r--r--kleaf/workspace.bzl1
-rw-r--r--kleaf/workspace_status_stamp.py83
58 files changed, 1750 insertions, 432 deletions
diff --git a/BUILD.bazel b/BUILD.bazel
index 958984da..9690baef 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 5d7bada0..0d43da91 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 4ecdb38e..00000000
--- 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 00000000..71a5160c
--- /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 5abcde96..00000000
--- 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 46c7d65a..00000000
--- 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/gki/download_from_ci b/gki/download_from_ci
index 865a7eb1..4cb3a261 100755
--- a/gki/download_from_ci
+++ b/gki/download_from_ci
@@ -90,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)")
diff --git a/init/init_ddk.py b/init/init_ddk.py
index 2798924a..998dd02c 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 db6773f9..77fa3ba1 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 ce58f2ed..b45d0486 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 02900e90..e7e7c3ad 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/kernel_test.bzl b/kleaf/artifact_tests/kernel_test.bzl
index 3843e8e0..ef9a5519 100644
--- a/kleaf/artifact_tests/kernel_test.bzl
+++ b/kleaf/artifact_tests/kernel_test.bzl
@@ -73,6 +73,7 @@ def kernel_build_test(
hermetic_test(
name = name,
actual = Label("//build/kernel/kleaf/artifact_tests:kernel_build_test"),
+ use_cc_toolchain = True,
data = data,
args = args,
timeout = "short",
diff --git a/kleaf/bazel.py b/kleaf/bazel.py
index 2d3c6890..cdd1648c 100755
--- a/kleaf/bazel.py
+++ b/kleaf/bazel.py
@@ -294,10 +294,42 @@ 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.
+
+ If unspecified, REPO_ROOT is the root of the repo repository
+ determined by .repo, and REPO_MANIFEST is retrieved with
+ `repo manifest -r`.
+
+ This is used to gather the list of Git projects under the
+ workspace to get scmversion. If your workspace is not controlled
+ with `repo`, use --extra_git_project.
+ """),
+ type=self._check_repo_manifest,
+ default=(None, None),
+ )
+ group.add_argument(
+ "--extra_git_project", metavar="PATH",
+ dest="extra_git_projects", action="append",
+ help=textwrap.dedent("""\
+ Multiple uses are accumulated. Specify a Git project besides
+ the ones in `repo` or in --repo_manifest. The value should be
+ the path to the root of the Git project relative to the
+ workspace.
+
+ This is useful if you have an extra Git project not in the
+ repo manifest, but you need to stamp scmversion on the kernel
+ or kernel modules built from this directory.
+ """),
+ type=self._check_extra_git_project,
+ default=[],
)
group.add_argument(
"--ignore_missing_projects",
@@ -329,6 +361,36 @@ class BazelWrapper(KleafHelpPrinter):
type=_require_absolute_path,
)
+ def _check_repo_manifest(self, value: str) \
+ -> tuple[pathlib.Path | None, pathlib.Path | None]:
+ tokens = value.split(":")
+ match len(tokens):
+ case 0: return (None, None)
+ case 1:
+ sys.stderr.write(textwrap.dedent(f"""\
+ WARNING: --repo_manifest=<path> is deprecated. Use
+ --repo_manifest={self.workspace_dir}:{value}
+ to achieve the same effect.
+ """))
+ return (self.workspace_dir, _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 _check_extra_git_project(self, value: str) -> pathlib.Path:
+ path = pathlib.Path(value)
+ if not path.is_absolute():
+ return path
+ if path.is_relative_to(self.kleaf_repo_dir):
+ return path.relative_to(self.kleaf_repo_dir)
+ raise argparse.ArgumentTypeError(
+ f"Must be a relative path against {self.kleaf_repo_dir}",
+ )
+
def _parse_command_args(self):
"""Parses the given list of command_args.
@@ -375,8 +437,12 @@ 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
+ self.env["KLEAF_REPO_MANIFEST"] = f"{repo_root or ''}:{repo_manifest or ''}"
+
+ if self.known_args.extra_git_projects:
+ self.env["KLEAF_EXTRA_GIT_PROJECTS"] = ":".join(
+ str(path) for path in self.known_args.extra_git_projects)
if self.known_args.ignore_missing_projects:
self.env["KLEAF_IGNORE_MISSING_PROJECTS"] = "true"
@@ -408,7 +474,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 +526,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 afa8455e..d4603bd6 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 f8b6764e..a487ccb2 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 1416c5e5..e0ad6966 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 fc6cfe3e..dc08b32b 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 32693659..0a2f5ff3 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 5a9217bb..266dcaa8 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/kernel_prebuilt_ext.md b/kleaf/docs/api_reference/kernel_prebuilt_ext.md
index cc7f46f8..68aef2ae 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 0e21198d..513de6fa 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 624df94c..807caa34 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 1f6ad8c8..7dc80e3c 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 00000000..3cb1c2a7
--- /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 05f201c2..2e46e122 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 aa57a350..80953668 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 93b1f5b8..b88fcb73 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 13aa70d4..b5093a2a 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 51964f90..e8cd6fc3 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 4c0106c4..1b048ef6 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 18306c7a..11e60f1c 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 00000000..ee9c95ce
--- /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 0aba35d8..993b6708 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/kernel_config.bzl b/kleaf/impl/kernel_config.bzl
index b73874ec..d1c5b2bd 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 ac28bf37..9841ba48 100644
--- a/kleaf/impl/kernel_prebuilt_repo.bzl
+++ b/kleaf/impl/kernel_prebuilt_repo.bzl
@@ -171,7 +171,17 @@ def _download_remote_file(repository_ctx, local_filename, remote_filename_fmt, f
)
def _get_download_configs(repository_ctx):
- content = repository_ctx.attr.download_configs
+ 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_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):
@@ -298,20 +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),
- "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 4ebfbc6d..b8ea0978 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 13ea0080..ec6fd868 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 38b70d46..00000000
--- 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 fb7b95b7..cdf5404f 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 d8824c95..568a1c12 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 00000000..39a231d0
--- /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 00000000..6c1fa375
--- /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 00000000..841ee62a
--- /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 00000000..40acdaf8
--- /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 00000000..71a3bf50
--- /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 00000000..9e02a17b
--- /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 00000000..6f7a45d1
--- /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 00000000..04c5a189
--- /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 00000000..5ee4ba8d
--- /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 00000000..0f438e4a
--- /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 00000000..5ee4ba8d
--- /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 00000000..d235ba02
--- /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 00000000..257c8be0
--- /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 00000000..7958060e
--- /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 86766285..eb7dd9c5 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,13 @@ 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("."),
+ package : str | pathlib.Path | None = None):
+ if not package:
+ package = self._common()
strings_output = Exec.check_output([
- self.strings, f"bazel-bin/{self._common()}/kernel_aarch64/vmlinux"
+ self.strings,
+ str(workspace_root / f"bazel-bin/{package}/kernel_aarch64/vmlinux")
])
ret = []
for line in strings_output.splitlines():
@@ -836,6 +1172,85 @@ 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)
+
+ def test_stamp_if_kernel_dir_is_symlink(self):
+ """Tests that --stamp works if KERNEL_DIR is a symlink."""
+ self._setup_mainline()
+
+ new_kernel_dir = pathlib.Path("test_symlink")
+ with open(self.build_config_gki_aarch64_path, "a") as f:
+ f.write(f"KERNEL_DIR={new_kernel_dir}\n")
+
+ if not new_kernel_dir.is_symlink():
+ new_kernel_dir.symlink_to(self._common(), True)
+ self.addCleanup(new_kernel_dir.unlink)
+
+ self._check_call(
+ "build",
+ _FASTEST + [
+ "--config=stamp",
+ "--config=local",
+ f"//{new_kernel_dir}:kernel_aarch64",
+ f"--extra_git_project={new_kernel_dir}"
+ ],
+ 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(package=new_kernel_dir):
+ 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 e7dc4a23..fd0159eb 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 13eba05f..5e95d727 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 66f3d9f4..f4146a15 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
@@ -149,63 +149,70 @@ def get_localversion_from_git(project: pathlib.Path) -> PathCollectible | None:
)
+def _find_repo(curdir: pathlib.Path) -> pathlib.Path | None:
+ """Find repo installation."""
+ while curdir.parent != curdir: # is not root
+ maybe_dot_repo = curdir / ".repo"
+ if maybe_dot_repo.is_dir():
+ return curdir
+ curdir = curdir.parent
+ return None
+
+
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.get("KLEAF_REPO_MANIFEST", ":").split(":")
+ if repo_root_s:
+ repo_root = pathlib.Path(repo_root_s)
+ else:
+ repo_root = _find_repo(pathlib.Path(".").resolve())
+
+ if not repo_root:
+ logging.warning("Unable to determine repo root. Please specify --repo_manifest.")
+ return []
+
+ 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:
@@ -230,7 +237,13 @@ class Stamp(object):
"KLEAF_IGNORE_MISSING_PROJECTS") == "true"
self.use_kleaf_localversion = os.environ.get(
"KLEAF_USE_KLEAF_LOCALVERSION") == "true"
+
self.projects = list_projects()
+ extra_git_project_env_var = os.environ.get("KLEAF_EXTRA_GIT_PROJECTS")
+ if extra_git_project_env_var:
+ self.projects.extend(pathlib.Path(value) for value in
+ extra_git_project_env_var.split(":"))
+
self.init_for_dot_source_date_epoch_dir()
def init_for_dot_source_date_epoch_dir(self) -> None: