blob: 43762f385c88e0953e1b313730f9d377c59f164c [file] [log] [blame]
#!/usr/bin/env python3
# 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 script that converts existing build.config to a skeleton Bazel BUILD rules.
The skeleton Bazel BUILD file likely won't build properly. Manual intervention
is required after the skeleton file is created. Most instructions are presented
as "FIXME" comments in the generated file.
Running this script requires buildozer. Install it at
https://github.com/bazelbuild/buildtools/blob/master/buildozer/README.md
"""
import argparse
import collections
import json
import logging
import os
import re
import subprocess
import sys
import buildozer_command_builder
from typing import Optional, Mapping, Sequence
_BUILD_CONFIG_PREFIX = "build.config."
# See kernel_build.bzl
_DEFAULT_KERNEL_BUILD_SRCS = \
"""glob(["**"],\\ exclude=["**/.*",\\ "**/.*/**",\\ "**/BUILD.bazel",\\ "**/*.bzl",])"""
# Variables that should be conditionally ignored and not shown in BUILD files.
# Keys are variable names. Values are regular expressions.
# - If the value matches the regular expression, it is considered ignored (i.e. the BUILD
# file is not modified).
# - If the value of the variable in the build config does NOT match the regular expression, the
# variable is considered unsupported.
#
# Following are variables set by build configs or _setup_env.sh that does not
# need to be translated into BUILD definitions. Ignore these variables.
# ^(.|\n)*$ matches any (multi-line) string.
_IGNORED_BUILD_CONFIGS = dict.fromkeys(
[
"_", # reserved by bash
"OUT_DIR",
"MAKE_GOALS",
"LD",
"SKIP_MRPROPER",
"SKIP_DEFCONFIG",
"SKIP_IF_VERSION_MATCHES",
"SKIP_EXT_MODULES",
"SKIP_CP_KERNEL_HDR",
"SKIP_UNPACKING_RAMDISK",
"POST_DEFCONFIG_CMDS",
"IN_KERNEL_MODULES",
"AVB_SIGN_BOOT_IMG",
"AVB_BOOT_PARTITION_SIZE",
"AVB_BOOT_KEY",
"AVB_BOOT_ALGORITHM",
"AVB_BOOT_PARTITION_NAME",
"MODULES_ORDER",
"GKI_MODULES_LIST",
"LZ4_RAMDISK",
"LZ4_RAMDISK_COMPRESS_ARGS",
"KMI_STRICT_MODE_OBJECTS",
"GKI_DIST_DIR",
"BUILD_GKI_ARTIFACTS",
"GKI_KERNEL_CMDLINE",
"AR",
"ARCH",
"BRANCH",
"BUILDTOOLS_PREBUILT_BIN",
"CC",
"CLANG_PREBUILT_BIN",
"CLANG_VERSION",
"COMMON_OUT_DIR",
"DECOMPRESS_GZIP",
"DECOMPRESS_LZ4",
"DEFCONFIG",
"DEPMOD",
"DTC",
"HOSTCC",
"HOSTCFLAGS",
"HOSTCXX",
"HOSTLDFLAGS",
"KBUILD_BUILD_HOST",
"KBUILD_BUILD_TIMESTAMP",
"KBUILD_BUILD_USER",
"KBUILD_BUILD_VERSION",
"KCFLAGS",
"KCPPFLAGS",
"KMI_GENERATION",
"LC_ALL",
"LLVM",
"MODULES_ARCHIVE",
"NDK_TRIPLE",
"NM",
"OBJCOPY",
"OBJDUMP",
"OBJSIZE",
"PATH",
"RAMDISK_COMPRESS",
"RAMDISK_DECOMPRESS",
"RAMDISK_EXT",
"READELF",
"ROOT_DIR",
"SOURCE_DATE_EPOCH",
"STRIP",
"TOOL_ARGS",
"TZ",
"UNSTRIPPED_DIR",
"UNSTRIPPED_MODULES_ARCHIVE",
"USERCFLAGS",
"USERLDFLAGS",
"_SETUP_ENV_SH_INCLUDED",
],
r"^(.|\n)*$"
)
# Conditionally ignored.
# These variables are ignored only if the value matches the condition.
_IGNORED_BUILD_CONFIGS.update(
{
"HERMETIC_TOOLCHAIN": r"^1$", # Ignore iff HERMETIC_TOOLCHAIN=1
}
)
# Variables not supported by Kleaf. If any of these variables are set to
# a non-empty value, it is considered unsupported.
# Device owners will need to migrate away from these variables.
_IGNORED_BUILD_CONFIGS.update(dict.fromkeys(
[
"EXT_MODULES_MAKEFILE",
"COMPRESS_MODULES",
"ADDITIONAL_HOST_TOOLS",
"POST_KERNEL_BUILD_CMDS",
"TAGS_CONFIG",
"EXTRA_CMDS",
"DIST_CMDS",
"VENDOR_RAMDISK_CMDS",
"STOP_SHIP_TRACEPRINTK",
],
r"^$"
))
def die(msg):
logging.error("%s", msg)
sys.exit(1)
def order_dict_by_key(d: Mapping[str, str]) -> Mapping[str, str]:
return collections.OrderedDict(sorted(d.items()))
def find_build_config(env: Mapping[str, str]) -> str:
# Set by either environment or _setup_env.sh
if env.get("BUILD_CONFIG"):
real_build_config = os.path.realpath(env["BUILD_CONFIG"])
real_this = os.path.realpath(".")
if os.path.commonpath([real_build_config, real_this]) != real_this:
die(f"realpath $BUILD_CONFIG ({real_build_config}) is not under the repository root")
return os.path.relpath(real_build_config, real_this)
die("$BUILD_CONFIG is not set, and top level build.config file is not found.")
def infer_target_name(args, build_config: str) -> str:
if args.target:
return args.target
build_config_base = os.path.basename(build_config)
if build_config_base.startswith(
_BUILD_CONFIG_PREFIX) and build_config_base != _BUILD_CONFIG_PREFIX:
return build_config_base[len(_BUILD_CONFIG_PREFIX):]
die("Fail to infer target name. Specify with --target.")
class BuildConfigToBazel(buildozer_command_builder.BuildozerCommandBuilder):
def __init__(self, *init_args, **init_kwargs):
super().__init__(*init_args, **init_kwargs)
self.new_env = order_dict_by_key(json.loads(subprocess.check_output(
"source build/kernel/_setup_env.sh > /dev/null && build/kernel/kleaf/dump_env.py",
shell=True, stderr=self.stderr, env=self.environ, executable="/bin/bash")))
logging.info("Captured env: %s", json.dumps(self.new_env, indent=2))
build_config = find_build_config(self.new_env)
target_name = infer_target_name(self.args, build_config)
self.package = os.path.dirname(build_config)
self.target_name = target_name
self.pkg = f"//{self.package}:__pkg__"
self.dist_name = f"{target_name}_dist"
self.unstripped_modules_name = f"{target_name}_unstripped_modules_archive"
self.images_name = f"{target_name}_images"
self.abi_name = f"{target_name}_abi"
self.dts_name = f"{target_name}_dts"
self.modules_install_name = f"{target_name}_modules_install"
# set elsewhere
self.dist_targets: Optional[set[str]] = None
def _new(self, kind: str, name: str, package=None, load_from="//build/kernel/kleaf:kernel.bzl",
add_to_dist=True) -> str:
if package is None:
package = self.package
new_target = super()._new(kind, name, package, load_from=load_from)
if add_to_dist:
self.dist_targets.add(new_target)
return new_target
def _create_buildozer_commands(self) -> None:
"""Fills in self.out_file."""
common = self.args.common_kernel_tree
self.dist_targets = set()
target = self._new("kernel_build", self.target_name)
dist = self._new("copy_to_dist_dir", self.dist_name,
load_from="//build/bazel_common_rules/dist:dist.bzl", add_to_dist=False)
self._set_attr(dist, "flat", True)
images = None
need_unstripped_modules = False
abi = None
modules = []
target_comment = []
# List of build configs unknown to this script. They require attention from
# the developers to be translated properly.
unknowns = []
for key, value in self.new_env.items():
esc_value = value.replace(" ", "\\ ").replace("\n", "\\n")
if key in _IGNORED_BUILD_CONFIGS:
if not re.match(_IGNORED_BUILD_CONFIGS[key], value):
target_comment.append(f"FIXME: {key}={esc_value} not supported")
continue
# else ignore
elif type(self)._is_bash_func(key):
continue
elif key == "BUILD_CONFIG":
self._set_attr(target, "build_config", os.path.basename(value), quote=True)
elif key == "BUILD_CONFIG_FRAGMENTS":
target_comment.append(
f"FIXME: {key}={esc_value}: Please manually convert to kernel_build_config")
elif key == "FAST_BUILD":
if value:
target_comment.append(f"FIXME: {key}: Specify --config=fast in device.bazelrc")
elif key == "LTO":
if value:
target_comment.append(f"FIXME: {key}: Specify --lto={value} in device.bazelrc")
elif key == "DIST_DIR":
rel_dist_dir = os.path.relpath(value)
self._add_comment(dist, "dist_dir",
f'FIXME: or dist_dir = "{rel_dist_dir}"')
elif key == "DO_NOT_STRIP_MODULES":
self._set_attr(target, "strip_modules", value != "1")
elif key == "FILES":
for elem in value.split():
self._add_attr(target, "outs", elem, quote=True)
elif key == "EXT_MODULES":
module_packages = [token.strip() for token in value.split() if token.strip()]
for module_package in module_packages:
module = self._new("kernel_module",
name=os.path.basename(module_package),
package=module_package,
add_to_dist=False)
self._set_attr(module, "kernel_build", target, quote=True)
# buildozer converts None to ["None"] for outs, so use a different name
# then rename.
self._add_comment(module, "temp_outs",
f"FIXME: set to the list of external modules in this package. You may "
f"run `tools/bazel build {module}` and follow the instructions "
f"in the error message.",
lambda attr_val: attr_val.is_missing_or_none())
self._rename(module, "temp_outs", "outs")
modules.append(module)
elif key == "KERNEL_DIR":
if value != self.package:
if value.removesuffix("/") == common:
self._set_attr(target, "srcs", _DEFAULT_KERNEL_BUILD_SRCS, quote=False,
command="set_if_absent")
self._add_attr(target, "srcs", f"//{common}:kernel_aarch64_sources",
quote=True)
else:
self._add_comment(
target, "srcs",
f"FIXME: add files from KERNEL_DIR {self.new_env['KERNEL_DIR']}")
# else keep srcs unchanged
elif key == "KCONFIG_EXT_PREFIX":
self._set_attr(target, "kconfig_ext", value, quote=True)
elif key == "UNSTRIPPED_MODULES":
self._set_attr(target, "collect_unstripped_modules", bool(value))
elif key == "COMPRESS_UNSTRIPPED_MODULES":
if value == "1":
need_unstripped_modules = True
elif key == "ABI_DEFINITION":
abi = self._new("kernel_abi", self.abi_name)
self._add_comment(abi, "abi_definition",
f"Usually not set in Kleaf. See "
f"build/kernel/kleaf/docs/abi_device.md. Original value: "
f"//{common}:{value}",
lambda attr_val: attr_val.is_missing_or_none())
elif key in ("KMI_ENFORCED", "KMI_SYMBOL_LIST_ADD_ONLY"):
abi = self._new("kernel_abi", self.abi_name)
if value == "1":
self._set_attr(abi, key.lower(), True)
elif key == "KMI_SYMBOL_LIST_MODULE_GROUPING":
abi = self._new("kernel_abi", self.abi_name)
if value == "1":
self._set_attr(abi, "module_grouping", True)
elif key == "KMI_SYMBOL_LIST":
self._set_attr(target, "kmi_symbol_list", f"//{common}:{value}", quote=True)
elif key == "ADDITIONAL_KMI_SYMBOL_LISTS":
kmi_symbol_lists = value.split()
for kmi_symbol_list in kmi_symbol_lists:
self._add_attr(target, "additional_kmi_symbol_lists",
f"//{common}:{kmi_symbol_list}", quote=True)
elif key in (
"TRIM_NONLISTED_KMI",
"GENERATE_VMLINUX_BTF",
"KMI_SYMBOL_LIST_STRICT_MODE",
"KBUILD_SYMTYPES",
):
self._set_attr(target, key.lower(), bool(value == "1"))
elif key == "PRE_DEFCONFIG_CMDS":
target_comment.append(
"FIXME: PRE_DEFCONFIG_CMDS: Don't forget to modify PRE_DEFCONFIG_CMDS "
"so it writes to $OUT_DIR, not the source tree: "
"https://android.googlesource.com/kernel/build/+/refs/heads/main/kleaf/docs/errors.md#defconfig-readonly")
elif key in (
"BUILD_BOOT_IMG",
"BUILD_VENDOR_BOOT_IMG",
"BUILD_DTBO_IMG",
"BUILD_VENDOR_KERNEL_BOOT",
"BUILD_INITRAMFS",
):
images = self._new("kernel_images", self.images_name)
# bool(value) checks if the string is empty or not
self._set_attr(images, key.removesuffix("_IMG").lower(), bool(value))
elif key == "SKIP_VENDOR_BOOT":
images = self._new("kernel_images", self.images_name)
self._set_attr(images, "build_vendor_boot", not bool(value))
elif key == "MKBOOTIMG_PATH":
images = self._new("kernel_images", self.images_name)
self._add_comment(images, "mkbootimg",
f"FIXME: set mkbootimg to label of {esc_value}")
elif key == "MODULES_OPTIONS":
images = self._new("kernel_images", self.images_name)
modules_options_filename = f"modules.options.{self.target_name}"
modules_options_path = os.path.join(self.package, modules_options_filename)
self._create_extra_file(modules_options_path, value)
self._set_attr(images, "modules_options",
f"//{self.package}:{modules_options_filename}",
quote=True)
elif key in (
"MODULES_LIST",
"MODULES_BLOCKLIST",
"SYSTEM_DLKM_FS_TYPE",
"SYSTEM_DLKM_MODULES_LIST",
"SYSTEM_DLKM_MODULES_BLOCKLIST",
"SYSTEM_DLKM_PROPS",
"VENDOR_DLKM_ETC_FILES",
"VENDOR_DLKM_FS_TYPE",
"VENDOR_DLKM_MODULES_LIST",
"VENDOR_DLKM_MODULES_BLOCKLIST",
"VENDOR_DLKM_PROPS",
):
images = self._new("kernel_images", self.images_name)
if os.path.isabs(value):
value = os.path.relpath(value)
if os.path.commonpath((value, self.package)) == self.package:
self._set_attr(images, key.lower(), os.path.relpath(value, start=self.package),
quote=True)
else:
self._add_comment(images, key.lower(),
f"FIXME: set {key.lower()} to label of {esc_value}")
elif key == "GKI_BUILD_CONFIG":
if value == f"{common}/build.config.gki.aarch64":
self._set_attr(target, "base_kernel", f"//{common}:kernel_aarch64", quote=True)
else:
self._add_comment(target, "base_kernel",
f"FIXME: set base_kernel to kernel_build for {esc_value}")
elif key == "GKI_PREBUILTS_DIR":
target_comment.append(
f"FIXME: {key}={esc_value}: Please manually convert to kernel_filegroup")
elif key == "DTS_EXT_DIR":
dts = self._new("kernel_dtstree", self.dts_name, package=value,
add_to_dist=False)
self._set_attr(target, "dtstree", dts, quote=True)
elif key == "BUILD_GKI_CERTIFICATION_TOOLS":
if value == "1":
self.dist_targets.add("//build/kernel:gki_certification_tools")
elif key in self.environ:
if self.environ[key] == value:
logging.info(f"Ignoring variable {key} in environment.")
else:
target_comment.append(f"FIXME: Unknown in build config: {key}={esc_value}")
unknowns.append(key)
else:
target_comment.append(f"FIXME: Unknown in build config: {key}={esc_value}")
unknowns.append(key)
for dist_target in self.dist_targets:
self._add_attr(dist, "data", dist_target, quote=True)
unstripped_modules = None
if need_unstripped_modules or abi:
unstripped_modules = self._new("kernel_unstripped_modules_archive",
self.unstripped_modules_name)
self._set_attr(unstripped_modules, "kernel_build", target, quote=True)
for module in modules:
self._add_attr(unstripped_modules, "kernel_modules", module, quote=True)
modules_install = None
need_modules_install = images or modules
if need_modules_install:
modules_install = self._new("kernel_modules_install", self.modules_install_name)
self._set_attr(modules_install, "kernel_build", target, quote=True)
for module in modules:
self._add_attr(modules_install, "kernel_modules", module, quote=True)
if abi:
for module in modules:
self._add_attr(abi, "kernel_modules", module, quote=True)
self._set_attr(abi, "unstripped_modules_archive", unstripped_modules, quote=True)
self._set_attr(abi, "kernel_build", target, quote=True)
if images:
self._set_attr(images, "kernel_build", target, quote=True)
self._set_attr(images, "kernel_modules_install", modules_install, quote=True)
self._add_comment(target, "base_kernel",
f"FIXME: base_kernel should be migrated to //{common}:kernel_aarch64.",
lambda attr_val: attr_val.value not in (
f"//{common}:kernel_aarch64", f"//{common}:kernel"))
self._add_comment(target, "module_outs",
f"FIXME: set to the list of in-tree modules. You may run "
f"`tools/bazel build {target}` and follow the instructions "
f"in the error message.",
lambda attr_val: attr_val.is_missing_or_none())
self._add_target_comment(target, target_comment)
if unknowns:
logging.info("Unknown variables:\n%s", ",\n".join(f'"{e}"' for e in unknowns))
self.out_file.flush()
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("-t", "--target",
help="Name of target. Otherwise, infer from the name of the "
"build.config file.")
parser.add_argument("-v", "--verbose", help="verbose mode", action="store_true")
parser.add_argument("-k", "--keep-going",
help="buildozer keeps going on errors. Use when targets are already "
"defined. There may be duplicated FIXME comments.",
action="store_true")
parser.add_argument("--stdout",
help="buildozer writes changed BUILD file to stdout (dry run)",
action="store_true")
parser.add_argument("--common-kernel-tree",
help="path to common kernel source tree; default is common.",
default="common")
return parser.parse_args(argv)
def main(argv: Sequence[str]):
args = parse_args(argv)
log_level = logging.INFO if args.verbose else logging.WARNING
logging.basicConfig(level=log_level, format="%(levelname)s: %(message)s")
BuildConfigToBazel(args=args).run()
if __name__ == "__main__":
main(sys.argv[1:])